Files
ConfigKit/docs/roadmap/complexity.md

16 KiB
Raw Blame History

Maintainability And Complexity Audit

Reviewed revision: 320d0c5f439a059a7081cf7532c9b5d65891f5c7. Audit date: 2026-10-25.

Scope And Method

This audit examines readability, responsibility boundaries, state handoffs, and interface duplication. It covers the root facade, engine subsystems, document and YAML boundaries, and relevant tests, documentation, and prior refactoring decisions. It does not propose new feature behavior or performance guarantees.

The codebase-memory graph was refreshed and its function spans, cyclomatic and cognitive complexity estimates, parameter counts, and caller traces were used to select code for inspection. The graph contains 907 non-test functions/methods and 940 test-file functions/methods. Its index excludes examples, documentation, and scripts; tracked-file inventory and direct inspection supplemented it. Standalone gocyclo, gocognit, and golangci-lint were unavailable. No tool installation, benchmarks, or empirical performance testing was needed.

The tracked production inventory, excluding examples, contains 16 root Go files, 97 engine files, one document file, and two YAML adapter files: approximately 22,612 physical lines overall. The largest production files are compose.go (599 lines), history.go (568), overlay.go (564), and discovery.go (540). These measurements locate responsibilities; they are not refactoring thresholds.

Representative graph measurements:

Function File Function span Cognitive estimate
valueOperation.clone internal/engine/clone.go 130 104
acquireInput internal/engine/acquisition.go 202 99
valueBuilder.build internal/engine/value.go 104 93
decodeValue internal/engine/decode.go 115 85
selectGlobImportCandidates internal/engine/glob.go 112 73
assembleLayeredInput internal/engine/load_request.go 146 63
executeLoadRequest internal/engine/load.go 198 56
mergeOverlayMappingsWithCollectionPolicy internal/engine/overlay.go 163 46

Spans include comments and blank lines; scores are tool estimates for comparative triage, not standardized quality grades. Source review, rather than a numerical cutoff, determines the findings below.

Architectural Assessment

The implemented architecture remains coherent: the root owns the supported public types and explicit conversions; the engine owns loading and frozen inspection; the document package owns the ordered tree; and the YAML adapter isolates the upstream parser. Ordinary and layered loads share one normalized request and execution lifecycle. Imports and inheritance retain their separate scopes and use the common dependency scheduler.

The recent consolidation improved import ownership and file boundaries, separated inheritance preparation/combination/finalization, shared filesystem facts, and clarified schema and projection responsibilities. The findings concern remaining maintenance opportunities, not a recommendation to repeat that refactor or split the engine into a package per subsystem.

The existing correctness and efficiency audits remain separate owners of their findings. No additional runtime defect was established in this pass. Related risks are referenced rather than duplicated.

1. Remove The Test-Only Eager Acquisition Mode

Status: open. Priority: medium. Scope: engine acquisition and its test helpers.

Relevant code. inputAcquisitionRequest.deferAssembly in acquisition.go, and the final branch of acquireImportedWithAccess in import_acquisition.go, lines 206–224. Production construction sites in executeLoadRequest (load.go, lines 210–214) and acquireUnderlayGroupScopes (load_request.go, lines 243–246) both set this flag to true.

Complexity. Caller tracing found no production caller selecting eager composition. The false branch remains exercised by private test entry helpers, including testImportRequest, testAcquireImport, and testAcquireInput in imports_test.go. Acquisition therefore maintains two return protocols: an acquired scope with a nil composed root, or an already composed document. The eager branch reconstructs assembledInput and reenters composition from the acquisition function, while the published loading paths perform that phase later. Tests can consequently exercise a different handoff from the one consumers use.

Potential refactoring. Make acquisition return retained scopes consistently and remove the unused production mode. Tests needing composed values can invoke the real composition phase explicitly through a small test-only convenience helper. Keep acquisition-only tests acquisition-only. Preserve handle closure, pending-item placement reservation, accounting, diagnostics, and meaningful composition coverage. This removes a private alternative path, not support for any public loading workflow. Do not infer that other nil-state or directive contexts are unused without separately tracing their callers.

2. Clarify Acquisition And Load-Coordinator Phase Handoffs

Status: open. Priority: medium. Scope: engine, across acquisition, version, assembly, and lifecycle files.

Relevant code. acquireInput in acquisition.go, executeLoadRequest in load.go, and assembleLayeredInput in load_request.go.

Complexity. acquireInput still combines search outcome handling, parsing, disabled-directive checks, three directive/version-processing branches, import-versus-standalone dispatch, placement/accounting, and passive context construction. Its version-processing section repeats source-ID extraction, failure handling, and mainProcessing updates, while the layered branch must extract directives separately and restore them after version processing.

The coordinators also manipulate several intentionally distinct representations: parsed source, local payload, acquired scope, composed root, inherited root, local/protected version paths, and effective version evidence. For example, executeLoadRequest includes a substantial ordinary-load resource-context fallback between assembly and inheritance, while assembleLayeredInput repeats declaration gathering, tier application, and destination projection for underlays and main. Understanding a source-version or placement change requires tracing both those mechanics and the overall phase order through long functions.

Potential refactoring. Extract cohesive operations for processing one parsed input's directives/version evidence and for placing/registering a standalone contribution. Reuse the existing processing result and acquisition state rather than inventing another context hierarchy. Isolate the substantial context and structural-expansion operations from the top-level lifecycle, and consider one helper for the repeated tier application/projection handoff. Keep phase order, error exits, and source acquisition order readable in the coordinators.

Extraction is useful only when it gives one operation a clear owner and result; moving blocks into helpers that still share the same mutable surrounding locals would not solve the problem. Preserve the deliberate ordinary/layered overlay differences, default versus enabled version-check timing, warning accumulation, exactly-once callbacks, and load-wide versus tier-local state. This is separate from finding 1: removing the eager branch does not simplify these active paths.

3. Separate Collection-Policy Preparation From Merge Evidence Application

Status: open. Priority: medium. Scope: engine precedence and inheritance.

Relevant code. mergeOverlayMappingsWithCollectionPolicy and applyOverlaySequenceEvidence in overlay.go; resolveStrictCollectionPolicies in compose.go; combineInheritanceTemplate in inheritance_combine.go; and the policy/origin helpers in inheritance_collection.go.

Complexity. The overlay coordinator prepares claims and declaration groups, discovers applicable destinations, resolves operations, configures recursive merge callbacks, accumulates diagnostics/history, reconciles declaration-only policies after mutation, and sorts the output. Its callbacks close over several maps and mutate the assembly while reporting failures through an outer slice. applyOverlaySequenceEvidence then mixes policy retirement, displaced origins, replacement records, copied incoming origins, and record-policy reconstruction.

There is also concrete duplicated bookkeeping: overlay and inheritance callbacks both translate a sequence result into replace-versus-mapped history descriptors; overlay evidence and applyInheritanceSequencePolicy both rebuild record policy order from lower/higher item projections. The shared combineSequence is already the correct value-combination owner, but maintainers must separately coordinate these sidecar translations when its result representation changes.

Potential refactoring. Give overlay preparation, value/evidence application, and declaration-only finalization explicit local boundaries. Let a small operation owner or result carry the diagnostic and evidence collections currently captured by the large callbacks. Share the straightforward history-descriptor construction; evaluate a common projected-record selection helper while retaining named precedence and inheritance ownership rules.

Do not merge the distinct origin semantics or hide mode-lifetime differences behind a generic strategy framework. Keep combineSequence and the recursive mapping merge shared, and consume their result rather than recomputing values. Coordinate this work with collection-policy findings 1–2 in the efficiency audit so clearer preparation does not preserve or introduce repeated whole-table copying/scanning. Preserve replacement ordering, pending declarations, new versus retained record owners, and stable history.

4. Extract Patterned Glob Entry Preparation From Traversal Orchestration

Status: open. Priority: medium. Scope: engine import discovery.

Relevant code. selectGlobImportCandidates in glob.go, especially its patterned-entry branch. The corresponding directory-search entry operation already has a named owner, prepareImportDirectoryEntry in import_selection.go.

Complexity. The glob function nests pattern-segment traversal, parent candidates, and directory entries. Its innermost branch handles matching, hidden-entry policy, lexical and access paths, containment, link inspection, follow behavior, intermediate-directory eligibility, native/host identity differences, canonicalization, labels, and candidate creation. Set-union and component-advancement mechanics are interleaved with those admission decisions, making the important lexical-versus-canonical distinction difficult to follow.

Potential refactoring. Extract a glob-specific entry preparation operation returning an admitted candidate, an ordinary skip, or a diagnostic. Leave pattern component advancement, literal segments, per-component union/sorting, and final exclusion/requiredness visible in the selector. Reuse low-level filesystem facts where semantics coincide; directory and glob admission need not become one general visitor interface. Preserve inspection/error order, entry accounting, native namespace spelling, and selected-file validation. This is a readability change, not a proposal to change the selected discovery algorithm or add caching.

5. Simplify Redundant Facade-To-Engine Input Transport

Status: open. Priority: low. Scope: root facade and engine, across packages.

Relevant code. Root toEngineImportDefaults in conversion.go, toEngineOptions in lifecycle.go, and toEngineLayeredOptions in layered.go; engine transport/private defaults and option decorators in import_spec.go, lines 46–87; and placement transport helpers in destination.go, lines 23–39.

Complexity. Import defaults have a root-owned public representation, an engine facade-transport representation, and a second engine-private representation with the same five fields. Conversion constructs the transport, an option decorator converts it to private defaults, and load normalization snapshots it again. Placement similarly requires constructing an engine record and then passing it through a separate setter-style helper. Even a simple control change has multiple mechanical mapping sites across the package boundary.

Caller and text-reference checks also found no uses of the standalone PlacementModeFromFacade and PlacementModeForFacade functions beyond their declarations. The similarly named SourceInfoPlacementModeForFacade is used by root source-record conversion and is not dead code. The unused pair adds a second apparent placement conversion route for maintainers to investigate.

Root type ownership and explicit conversion are deliberate and useful; the opportunity is the extra engine-internal representation/construction protocol, not eliminating the facade. The public wrappers do not need to change.

Potential refactoring. Remove the two unused enum bridges. Then consider one engine representation of import-default input values and a straightforward construction surface on the existing engine option/input records. Review inert placement decorators as part of the same bounded cleanup. Keep helpers that actually encode domain rules or ownership conversion, and keep definition/registration compilation separate from transport. Avoid adding another universal request DTO, code generator, reflective mapper, or adapter framework to remove a few mechanical functions.

Preserve root-owned named types and signatures, invalid-enum validation, nil-versus-empty patterns, pointer/slice snapshots, and the two public loading entry points. Exported declarations inside Go's internal boundary need not become public root aliases or bridge methods. Pursue this low-priority cleanup only if it reduces total concepts and mapping sites, rather than relocating them.

Areas To Retain And Review Boundaries

  • The clone traversal was explicitly reviewed and retained in the completed consolidation record at 34cbc0d:docs/roadmap/consolidation.md. Its high score is not new evidence for reopening that work. valueBuilder.build, typed decoding, and the YAML translator also contain substantial but recognizable type dispatch; no repository-wide visitor framework is recommended.
  • The cohesive engine package and explicit root record conversions follow ADR 0006. Function/file counts alone do not justify package extraction, public API consolidation, or generated conversions.
  • The shared scheduler, mapping/sequence primitives, recently separated inheritance phases, schema field assembly, and resolved filesystem facts remain useful common owners. Preserve those improvements.
  • history.go and the larger integration-test files have several substantial routines, but file size alone did not establish another prioritized refactor. The deferred replay, copy-handoff, caching, and contributor-index experiments remain in future work.

These are opportunities for further consideration, not a staged implementation plan or new release blockers. Favor the concrete unused-mode removal and active acquisition/collection boundaries before lower-value transport cleanup. A later implementation should preserve the established contracts and relevant behavioral coverage; complexity scores, helper counts, and private call sequences should not become test thresholds or repository guarantees.

Validation

This audit changes only this findings document. No production code, tests, dependencies, policies, or public contracts are changed. Validation consists of the existing local documentation-link test and git diff --check; runtime and performance measurements are not part of this review.