Skip to content

refactor: complexity wave 2 — zero functions over CC 50, ceiling enforced - #185

Merged
pacphi merged 49 commits into
mainfrom
refactor/complexity-wave-2
Aug 27, 2026
Merged

refactor: complexity wave 2 — zero functions over CC 50, ceiling enforced#185
pacphi merged 49 commits into
mainfrom
refactor/complexity-wave-2

Conversation

@pacphi

@pacphi pacphi commented Aug 27, 2026

Copy link
Copy Markdown
Owner

What this is

Wave 2 of the complexity program (ADR-0037; wave 1 was #184). Seven parallel file-disjoint tracks cleared the entire residual backlog wave 1 recorded, then graduated the earned gate: no function in the repo exceeds CC 50, enforced as an ESLint error (pnpm run lint:cc, wired into check and CI's quality job). The warn-25 tier stays advisory (~30 functions in the 26–49 band, led by the deliberate createLiveEvent validation-boundary exemption at 49).

Headline decompositions (all behavior-preserving)

Function Before After
uninstall run() CC 100 5 (step registry, mirrors sync)
renderSysStorage (client) 88 2-CC orchestrator over ≤22 helpers
applyOpencode 71 21 (via generic reconcileOwnedMap)
adaptCodexLedger 65 20
discoverOllamaApi 65 13
renderHistory (client) 63 15
verifyDejaVu 62 17
runTieredConformance 60 19
collectProjects 59 16
parseCodex 53 11

Structural splits (exports preserved, importers untouched)

  • opencode.mjs 1,573 → 74-line barrel over 5 files
  • usage-index.mjs 1,736 → index-I/O + usage-parsers.mjs + usage-aggregate.mjs (one-way dependency graph; removed a latent circular import with usage-opencode.mjs)
  • providers.mjs → AQE-router machinery extracted to aqe-router.mjs; both under the 1,000-line warning
  • footprint/storage.mjs 1,482 → 3 files (tree collector / reclaim orchestration / detectors)

Bug found and fixed (6th of the program)

A literal NUL byte in mliRouteValue's "used" sort key — the Models tab's user-clickable "Used for" column sort compared strings joined by \0 instead of a space. Pre-existing, isolated in its own commit (a7dc519).

Verification

Serial gated integration: full pnpm run check after each of the 7 track merges, exit 0 every time; Playwright suite (331 cases) green after the client track; golden status snapshot and drift-parity test byte-identical throughout (never regenerated); the 73 doc-enforced file:line citations realigned and doc-citations green. Docs gates ran per track (ADR-0017 note, machine-footprint.md table cell, citation realignment); ADR-0037 carries the wave-2 closure record.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WMKwDpp14PPjj8Bn1g7Uza

pacphi added 30 commits August 26, 2026 19:44
… helpers

collectModelBindings (CC 48) folded four independently-sourced binding
families (kit.json routes+escalations, kit.json integrations, agentic-qe
config, ruflo candidates) into one function. Extract one helper per
family (routeBindings/escalationBindings, integrationBindings,
aqeBindings/aqeFallbackBindings/aqeAgentOverrideBindings, rufloBindings)
so each stays independently testable; collectModelBindings now just
concatenates their outputs in the original order (CC 6). No behavior
change: binding shape, ids, ordering, and diagnostics codes are
byte-identical to before.
Splits the ADR-0031 §2 tiered-conformance orchestrator (CC 60) into small,
independently testable helpers: resolveConsentFile/resolveWorkerCwd for
setup, resolveBaseDir/resolveEffectiveManifest/resolveEffectiveTimeoutMs/
computeActivityRoutingResult for context derivation, a shared
runActivityRoutingDependentTier gate (replacing two copy-pasted skip
blocks for aqe-provider and primary-eligible), buildTierRunners for the
per-tier closures, persistTierResults for the ADR-0031 §1 recording step,
and cleanupConformanceRun for the finally-block teardown. The orchestrator
itself now just resolves context and loops CONFORMANCE_TIERS (the fixed
graduation order) through the runner map — CC 60 -> under 25, with 0 new
lint warnings and identical tier-push order/behavior (all 28
conformance-tiers.test.mjs cases pass unchanged).
discoverOllamaApi (CC 65) mixed tags/ps response decoding, per-row
validation, /api/show detail resolution, variant/capability field
mapping, and diagnostic/completion accounting into one function.
Extract parseOllamaTags/parseOllamaRuntime (response decoding),
resolveOllamaShow (bounded per-model detail lookup), ollamaFamilies/
ollamaAdvertisedCapabilities/ollamaVariant/ollamaCapabilities (field
mapping, using new whenPresent/whenTruthy conditional-field helpers
to keep the variant builder's CC at 11 instead of the boundary 25),
ollamaModelFromRow/ollamaModelsFromRows (per-row orchestration), and
markOllamaRuntimeEvidence (evidence-class patching).

discoverOllamaApi itself now only wires these together (CC 65 -> 13).
Diagnostic ordering, evidence classes, and the exact variant/capability
shapes are preserved byte-for-byte (existing fixtures assert this).
…named helpers

collectProjects (CC 59 -> resolveProjectCatalog/measureSelectedProjects/
buildProjectsSection, orchestrator now trivial) and collectCatalog (CC 44 ->
pluginSurfaceSpecs/readCatalogSurfaces/trackIncompleteness/sortCatalogItems/
tallyCatalogCounts/tallyCatalogPerHost) are mechanical extractions: identical
branching, identical payload shapes, no behavior change.
…elow CC 25

decodeCodexRecord (CC 26) now dispatches through a per-record-type table
(decodeSessionMeta/decodeTurnContext/decodeEventMsg/decodeResponseItem)
instead of one branch-heavy function. decodeClaudeRecord (CC 29) splits
into splitClaudeBlocks/claudeRole/claudeUsage. Decode-layer behavior is
unchanged; realigns the two USAGE-SCORECARD-METRICS.md citations that
moved.
…71→21)

applyOpencode hand-instantiated the same prune-stale-owned →
collision-detect → adopt/own-with-{prior,written} algorithm three times
(mcp block, skills.paths block, permission block). Extract it into one
generic reconcileOwnedMap(obj, desiredEntries, prevRecords, opts), a
reconcileFamilyPermissions wrapper that preserves the permission block's
family-atomicity subtleties (a collision on either claude-flow_*/claude_flow_*
spelling blocks both), and a reconcileSkillPaths helper for the
collision-free skills.paths case. Also extract applyOpencodeSummary for the
trailing result-notes assembly. Mechanical, behavior-preserving — every
prior/written/collision/prune decision point is reproduced exactly,
including the mcp-vs-permission prune-label asymmetry (mcp prunes push the
bare name; permission prunes push "permission.<key>").

No behavior change; tests/kit/opencode.test.mjs (46/46) and the full suite
pass unchanged.
admitOne (CC 36) becomes a sequence of small pipeline steps
(validateEntryShape, readEntryManifest, checkContractPin,
parseEntryManifest, checkHostIdentity, computeEntryIntegrity,
checkConsent), each preserving its exact original {admitted:false,
reason, detail} failure shape and short-circuit order — fail-closed
admission semantics unchanged.

bootstrapHostAdapters (CC 55, plus a max-depth-6 violation at the
lifecycle-registration block) becomes an orchestrator over five
extracted stages: resetAqeProviderBridge, resolveConsentStore,
buildGrantsByName, registerAqeProviderCandidates,
registerExecutionCandidates, registerLifecycleCandidates. Each stage
keeps its own guarded/non-fatal posture and warning strings verbatim;
extracting the lifecycle stage into its own function also resolves the
max-depth violation (nesting resets per function).

CC: admitOne 36 -> under 25, bootstrapHostAdapters 55 -> under 25.
0 new lint warnings; all 175 admission/conformance/execution/aqe-
provider/host-adapters-cli/external-lifecycle tests pass unchanged,
plus the full suite and typecheck.
…Opencode/removeArtifacts

Continue the CC>25 cleanup in opencode.mjs's config-wiring section:

- normalizeManaged (32->~4): extract normalizeOwnedRecordMap (the mcp and
  permissions loops were literally duplicated) and normalizeManagedArtifacts.
- opencodeConverged (28->~8): extract mcpConvergenceReasons,
  staleOwnedReasons (shared by the mcp and permission stale checks, another
  duplication), skillPathReasons, permissionAllowReasons.
- undoOpencode (26->16): extract restoreSkillPaths and
  collapsePermissionScalar.
- removeArtifacts (26->~5): extract removeIfReceiptOwned (the
  exists+hash-match+remove pattern was repeated 4x) and
  removeGeneratedAgents.

Mechanical, behavior-preserving — same reasons/removal order, same JSDoc
positioning (kept immediately above each exported function so tsc's
checkJs keeps inferring param types from it). All new/extracted functions
land under CC 25 (mostly under 15). tests/kit/opencode.test.mjs (46/46)
and the full suite pass unchanged; typecheck clean.
…C 25

adaptCodexRecord (CC 48) and adaptCodexLedger (CC 65) split their
event-base/actor/source-fields construction and per-decoded-type dispatch
into small named functions; adaptClaudeRecord (CC 47) does the same for
its base/containment/tool-event construction. No behavior change — same
live event shapes, same field values, same array ordering.
The withDb() callback (CC 48) splits into buildPartsIndex,
recordUserMessage/recordAssistantMessage (with recordAssistantUsage/
recordAssistantTurn), processMessageRow, collectScanToolCounts, and
initSessionRecord. Provider/model bookkeeping moves inline per assistant
message instead of a deferred lastProviderId — same final value, since
only a truthy providerID ever overwrote it. No output change.
Splits the two over-CC-25 async arrow functions in the deja-vu companion
lifecycle adapter into small, independently testable helpers:

detect() (CC 45) -> resolveDoctorState, buildHostFact (per-host target
fact loop body), buildInstallFacts (install sub-object + repair-capable
check), computeDetectError.

plan() (CC 49) -> resolveLatestVersion (registry read + bounded
fallback ladder), planPackageOperation (the install/upgrade decision,
still first-match-wins via early return, same priority order as the
original if/else-if chain), planHostOperations (per-host target loop
body, `continue` mapped to `return`), needsIndexOperation.

Every extracted function reproduces its slice of the original logic
verbatim — same warning strings, same error strings, same operation
shapes, same short-circuit order. CC: detect 45 -> under 25, plan 49 ->
under 25, 0 new lint warnings. All 46 deja-vu-specific tests plus the
footprint/companion-registry/integration-config/setup/sync/verify
suites and the full test/typecheck/lint gates pass unchanged.
read-model.mjs had 12 functions over CC 10 (up to CC 38), all in the
owner-visible/public-catalog privacy projection that /api/models and
the Dashboard depend on. Split each by concern, preserving output
shape and privacy behavior exactly (existing fixtures assert both):

- ownerVisibleCapabilities (16) -> booleanCapabilityFields/
  limitCapabilityFields/capabilityModalities/directionalCapabilityFields
- ownerVisiblePricing (15) -> evidencedPricing/publishedPricing
- trustedModelLinks's flatMap callback (13) -> trustedModelLink
- publicModelIdentity (19) -> codexPublicIdentity/claudePublicIdentity
  (+ isOfficialClaudeIdentity)/catalogPublicIdentity, tried in order
- sanitizeModel (27) -> sanitizedIdentity/sanitizedAliases/
  sanitizedVariantBlock/sanitizedLifecycle/sanitizedEdges/
  sanitizedDimensions
- consumerLabel (11) -> routeConsumerLabel/ordinalConsumerLabel/
  agentOverrideConsumerLabel, tried in order like publicModelIdentity
- sanitizeBinding (28) -> bindingPublishedPricing
- observedWindow (38) -> matchModelForSession/newObservedGroupRow/
  observedGroupRow/accumulateSession/foldSessionIntoGroups/
  sortObservedGroups
- changeDetail (26) -> a CHANGE_DETAIL_BY_KIND lookup table (mirrors
  the existing CHANGE_LABELS table just above it) plus
  changeDetailLifecycle/Visibility/Capability/Variant
- sanitizeChange (13) -> changeRawSubject
- the attention-sanitizing arrow in createDashboardModelReadModel (30)
  -> sanitizeAttentionItem dispatching to sanitizedSourceAttention/
  sanitizedMigrationAttention (+ affectedRoutesFor/migrationAction)/
  sanitizedConsumerAttention/sanitizedAliasAttention
- createDashboardModelPayload (13) -> timestampIso/
  dashboardHistoryEntries/dashboardComparisonBlock

Every function in the file is now under CC 25 (worst case 21). No
behavior change: identity/pricing precedence, evidence classes,
diagnostic ordering, and the exact owner-visible/public-catalog field
shapes are unchanged (model-dashboard-read-model.test.mjs and
model-inventory-impact.test.mjs assert this).
Finish the eslint complexity:25 gate cleanup for opencode.mjs's remaining
named targets:

- opencodeStack (44->20): extract ownershipMarkersSnapshot (the
  before/after JSON.stringify([...]) pattern was repeated 3x),
  blockedArtifactResults (the adoptionBlocked skip-shape), and
  deployOpencodeArtifacts (the plugin/gateway/agents/skill orchestration
  body).
- syncAgents (50->15): extract deployDesiredAgents, retireStaleGeneratedAgents,
  writeAgentStamp.
- agentsStatus (46->16): extract readAgentStamp, countGeneratedAgentFiles,
  onDiskAgentFiles, agentContentDiverged, expectedAgentStampText,
  agentReceiptDivergence, and agentsStatusResult for the final shape.
- Also dedupe the receipts-tolerant-map coercion (identical in syncAgents
  and agentsStatus) into a shared `asReceiptMap` helper.

Every function in the file is now under eslint's complexity:25 gate (worst
remaining is 21). Mechanical, behavior-preserving — same write/removal
order, same stamp/receipt semantics. tests/kit/opencode.test.mjs (46/46)
and the full suite pass unchanged; typecheck clean; lint has 0
complexity warnings left for this file (only the pending max-lines
warning, addressed by the next commit's file split).
…orage CC

storage.mjs was 1,482 lines (max-lines warning) with collectStorage at CC 35.
Split along the tree-vs-reclaim seam the file's own section comments already
drew:

- storage.mjs (629 lines): the category/host/project/session tree collector
  (collectStorage, defaultStorageRoots, growth/session-label helpers).
- storage-reclaim.mjs (251 lines): collectReclaimables orchestration, the
  safety-tier vocabulary, candidate()/summarizeReclaimables, npx and
  adopted-consumer-figure plumbing.
- storage-reclaim-detectors.mjs (677 lines): the per-accumulation-pattern
  detectors (superseded snapshots, regenerable caches, browser revisions,
  runtime versions, orphaned transcripts, orphaned worktrees).

storage.mjs re-exports every one of the 23 original named/const exports so
both existing importers (index.mjs, tests/kit/footprint-collectors.test.mjs,
including its `Object.keys(storageModule)` advisory-only sweep) keep working
unchanged.

Also extracted walkStorageRoots/fillMissingStorageCategories out of
collectStorage (CC 35 -> under 25; the walk loop and its onFile handler are
byte-for-byte unchanged, just relocated).

docs/ddd/machine-footprint.md's read-surface table is updated to name the file
that now actually performs the gitdir read.
…st.mjs

Both are sequential fail-closed validators (Adapter Contract Dossier
structural cap layer) — same rule-list shape as the rest of the
validator family, decomposed into small, order-preserving steps rather
than a validator registry (each check depends on state computed by the
previous one, e.g. host -> capabilities -> execution coupling, so a
literal (input)->error|null rule list doesn't fit; a sequential
pipeline of named steps does, without changing throw order).

validateAqe (CC 49) -> checkAqeProviderEligibility, validateAqeProviderHook,
validateAqeProviderEnvPolicy, validateAqeProviderModels,
validateAqeProviderLimits, buildAqeProviderRecord. Throw-precedence
preserved exactly (billingMode check stays inline, between the env-policy
and models steps, matching the original body's order).

validateAdapterManifest (CC 33) -> validateManifestIdentity,
assertHostAllowedKeys, parseHostAdapter, enforceHostCapabilityCaps,
checkExecutionRoutable.

Every extracted function reproduces its slice of the original logic
verbatim — same ManifestRejected reasons, same messages, same order.
CC: validateAqe 49 -> under 25, validateAdapterManifest 33 -> under 25,
0 new lint warnings. All 144 adapter-manifest/trust-manifest/aqe-
provider/admission/conformance tests pass unchanged, plus the full
suite, typecheck, and lint gates.
…under 25)

readQueuedManifests() and matchSignatures() pull the manifest-dependency fold
and the signature-matching loop out of detectStack's own scope, each keeping
its exact branching. detectStack's remaining complexity is dominated by its
options-object defaults, not control flow.
…nctions

discovery/opencode.mjs had 11 functions over CC 10 (max 23, plus a
nested arrow at 18), covering catalog-document validation, capability
normalization, the bounded JSON-block scanner, verbose-listing metadata
parsing, streamed models.dev fetch, output-line parsing, config
selector resolution, and the two collectors. Split each by concern:

- catalogDocument (17) -> parseCatalogValue/catalogModelCount/
  countValidCatalogModels/isValidCatalogProvider/isValidCatalogModel
- safeCapabilities (11) -> safeCapabilityBooleans/Modalities/
  Interleaved/Limits
- jsonBlock (12) -> advanceJsonScan (one mutable-state char-scan step)
- metadataFor (21) -> parseModelMetadataJson/validatedSelectorParts/
  metadataStatus/metadataFamily/metadataCatalogBlock/
  metadataAvailableVariants
- fetchCatalog (14) -> readBoundedCatalogText/readBoundedCatalogStream
- outputRows (11) -> nextNonBlankLineIndex/readRowMetadata
- selection (13) -> referenceString/splitSelectorVariant
- configuredRefs (21) -> parseOpenCodeConfig/modelRefsFromGroup/
  dedupeSelections
- discoverOpenCode (12) + its models.map arrow (18) ->
  openCodeModelFromId/openCodeModelStates/openCodeModelVariant/
  openCodeSourceUnsupportedResult
- collectOpenCode (23) -> unavailableOpenCodeResult/
  resolveOpenCodeConfigRaw/resolveModelsDevCatalogRaw/
  refreshOpenCodeOnline

Every function is now under CC 15 (worst case: collectOpenCode 15).
No behavior change: selector/variant parsing, diagnostic codes and
messages, catalog-proof precedence, and CLI argv are unchanged
(model-discovery-opencode-ollama.test.mjs and the collect/contracts/
route-feed suites assert this).
…checks

validateRegistries (CC 49) becomes a validator-registry: four independent
`(registries) => Error[]` rules — checkDuplicateIds, checkHostInvariants
(configProjection/observability referential integrity plus the
canDriveSession -> canBePrimary/canRouteActivities dependency, kept as
one function so a host violating more than one invariant still reports
them in the original interleaved per-host order), checkProviderInvariants,
checkModelDiscoveryOwners — run in the same order the original body
evaluated its blocks, via flatMap. validateRegistries itself drops to a
single flatMap call.

Note: the check-function array is built fresh inside validateRegistries
rather than hoisted to a module-level const, because validateRegistries
runs once at MODULE LOAD time (registries.mjs's own construction-time
self-check) before a later top-level const's temporal dead zone would
have cleared — confirmed by a direct import smoke test in addition to
the test suite.

CC 49 -> 1 (a single flatMap), 0 new lint warnings. All 24
adapter-registries tests pass unchanged, including the exact-array-order
deepEqual assertions and the construction-time import check, plus the
full suite, typecheck, and lint gates.
…functions

renderSysStorage (CC 88), renderSysRuntime (CC 39), renderSysCatalog (CC 29),
and renderSysProjects (CC 33) each mixed several independent DOM regions (or,
for renderSysProjects, eligibility filtering + row building + table assembly)
in one function. Split each into one function per region/concern, all under
CC 25, with no behavior change -- every region keeps its original guard and
logic verbatim, just relocated. This also resolves the file's one max-depth
violation (renderSysStorage's per-host split block nested 6 deep; flattening
its wrapper ifs to early returns brings it to 4).

Incidental: dropped a dead `_diskBar` variable in the old renderSysProjects
row loop that was computed but never read (no rendered-output change).

pnpm test (356/356) and pnpm run test:ui (331/331) both pass unchanged from
baseline; typecheck, lint, and build are clean.
migrateIntegrationConfig (CC 45) becomes a sequence of extracted helpers,
in the same order as the original body:

hasUnmigratableIntegrationsShape / hasUnmigratableProvidersShape — the
seven shape-guard checks all produced the identical immutable(out)
bail-out, so they collapse into two boolean predicates rather than a
rule list (nothing distinct to report — just "still safely migratable
or not").

resolveMigratedHosts, deriveInferredBindings, resolveMigratedTools,
applyLegacyCodexOwnership, applyLegacyOpenCodeOwnership,
pruneLegacyProviderKeys — each reproduces one independent slice of the
original computation verbatim (same field names, same merge/ownership
semantics, same legacy-key pruning list).

CC 45 -> under 25, 0 new lint warnings. All 24 integration-config tests
pass unchanged, plus the full suite, typecheck, and lint gates.
renderHistory mixed four independent data-shaping computations
(patterns/deltas, pattern-store day-bucketing, graph series, curve values --
each dense with ternaries/&&/|| chains, which is what drove the cyclomatic
count) with five independent sparkline DOM renders. Split into
historySeriesAndDeltas/historyPatternStoreSeries/historyGraphSeries/
historyCurveValues (pure data) and renderPatternsSpark/
renderPatternStoreSpark/renderGraphSpark/renderDeltaSpark/renderCurveSpark
(DOM writes), each under CC 25. renderHistory itself is now a thin
orchestrator; all logic and DOM output is otherwise byte-identical to the
original.

pnpm test (356/356) and pnpm run test:ui (331/331) both pass unchanged from
baseline; typecheck, lint, and build are clean.
…the gate

Wave-2 audit item 5 ("everything else over CC 25"). Three functions:

- dashboard-query.mjs parseQuery (27) -> parseNonInventoryQuery (the
  full/summary branch) and parseInventoryQuery (+ queryInteger/
  inventorySortSelection/inventoryEvidenceFilter), dispatched on `view`
- discovery/codex.mjs discoverCodex (41) -> codexUnsupportedSchemaResult/
  codexModelsFromCache/codexModelFromCacheEntry/codexReasoningEfforts/
  codexModelStates/codexConfiguredFallbackModel/codexOverallStatus (the
  latter also de-duplicates a repeated `complete ? (stale ? ...) : ...`)
- discovery/index.mjs modelRecord (40, the shared record builder every
  discovery/*.mjs module calls) -> resolveEvidenceClass/
  normalizedModelStates/makeEvidenceFor/stateDimensions/
  resolveReplacement/normalizedModelAliases/lifecycleEvidenceRefs/
  normalizedModelPricing/modelEdges

modelRecord needed extra care: its evidenceFor() calls append to a
shared `evidence` array in a specific sequence (dimensions, then
aliases, then lifecycle, then digest, then variant fields, then
capability fields, then pricing), and at least one caller (Codex's
discoverCodex) indexes evidence[0] positionally. The extracted helpers
take evidenceFor as a parameter and are invoked in the same lexical
order as the original inline code, so the call sequence — and every
evidence id (a hash of source/host/provider/modelId/scope/field/
capturedAt) — is unchanged. A comment on modelRecord documents this.

All three are now under CC 25 (parseQuery ~5, discoverCodex 20,
modelRecord 23). Full suite (2284 tests, all model-inventory discovery
and contract suites) passes unchanged.
runAdapterHook (CC 31) becomes a sequence of extracted lifecycle-phase
helpers, in the same order as the original body:

assertRunAdapterHookArgs (sync arg-shape guards), verifyHookIntegrityOrFailure
(the pre-spawn content re-check), spawnAdapterChild (isolates the
synchronous ENOENT-class spawn throw), wireAdapterStdin (stdin write +
EPIPE/already-closed-stream tolerance), awaitAdapterClose (the
close/error race setup, returning a getSpawnError() accessor over the
same shared-mutable-then-read-after-await semantics the original body
used), buildAdapterTimeoutResult and buildAdapterCloseResult (the two
terminal result shapes). A shared EMPTY_HOOK_RESULT constant replaces
three copies of the identical empty-output literal the pre-refactor
code repeated at each early-failure return site.

Every extracted function reproduces its slice of the original process-
supervision logic verbatim — same kill-group behavior, same truncation/
capture semantics, same detail strings. CC 31 -> under 25, 0 new lint
warnings. All 26 adapter-hook-runner tests (timeout-and-kill, ENOENT,
stdin EPIPE, cwd anchoring, output truncation/capping) pass unchanged,
plus 133 admission/conformance/execution/integrity/lifecycle tests, the
full suite, typecheck, and lint gates.
pacphi added 19 commits August 26, 2026 20:09
…h.mjs)

Last two functions over CC 25 in the model-inventory family:

- observed.mjs collectObservedModels (29) -> unavailableObservedResult/
  observedModelsMap/foldSessionModels/observedModelRecord/
  finalizeObservedResult/observedResultStatus. The evidence[0] mutation
  (tagging providerProvenance) still targets the same entry: modelRecord's
  dimensions-first evidence ordering (fixed in the prior commit) means
  the "observed" dimension's evidence is still evidence[0] for this
  states shape ({observed, entitled, policyAllowed, routable}: true,
  configured/effective/discoverable/recommended default 'unknown').
- refresh.mjs composeModelSnapshot (26) -> discoveryProfileFingerprints/
  snapshotSources/discoveredAndObservedModels/snapshotDiagnosticCodes/
  modelSnapshotId

Every named function in observed.mjs is now under CC 10; refresh.mjs's
composeModelSnapshot is 19 (mergeModels/applyBindings/
refreshModelDiscovery were already under 25 and untouched — out of
this item's scope). Full suite (2284 tests) and model-inventory-collect
suite pass unchanged; lint 0 errors (91 warnings machine-wide, down
from the 102 baseline before this track's work).
…r 1000

opencode.mjs had grown to 1,747 lines and tripped the eslint max-lines:1000
warning gate. Split along the file's own pre-existing section markers,
respecting one-directional dependencies to avoid an import cycle:

- opencode-receipts.mjs (27 lines): shared primitives (deepEqual,
  contentHash, hasReceiptValue, receiptMatches, asReceiptMap) — previously
  defined once and used across every section; now imported by all four
  implementation modules below instead of only reachable from one file.
- opencode-core.mjs (693 lines): opencode.json config-wiring —
  applyOpencode/undoOpencode, opencodeConverged/opencodeMcpStatus, the
  receipt ledger, mcp entry resolution.
- opencode-agents.mjs (489 lines): ruflo catalog resolution (catalogSource,
  skillPathsFor) + the Claude Code agent .md -> OpenCode subagent .md
  conversion/sync/status pipeline.
- opencode-artifacts.mjs (282 lines): the plugin (lifecycle bridge + lazy
  rUv gateway) and platform-skill deployment/status, plus the shared
  teardown (removeArtifacts).
- opencode-lifecycle.mjs (311 lines): the shared enable/retire stack
  composition (opencodeStack/retireOpencode) and the ADR-0016 lifecycle
  adapter — depends on opencode-core.mjs, opencode-agents.mjs, and
  opencode-artifacts.mjs, never the other way around.
- opencode.mjs (74 lines): now a pure re-export barrel. Every name any
  consumer imports from './opencode.mjs' (grepped across src/, bin/,
  tests/: providers.mjs, adapters/lifecycle-registry.mjs, commands/setup.mjs,
  commands/status/host-detail.mjs, commands/x/host.mjs, and the two owned
  test files) is re-exported unchanged — no import path elsewhere in the
  repo needed to change. A handful of previously-private helpers
  (normalizeManaged, opencodeOwnership, mutableOpencodeOwnership,
  specialistDispatcherState, gatewayAgentCatalog, STAMP_FILE,
  parseFrontmatter, SPECIALIST_AGENT) gained `export` only for this
  cross-module wiring; they are deliberately NOT re-exported through the
  opencode.mjs barrel, so the public surface is unchanged (still exactly
  the 33 names the original file exported).

Mechanical, behavior-preserving — no logic changed, only file boundaries.
All CC numbers are identical to before the split (verified). typecheck
clean (added two `@typedef {import(...)}` cross-file type references for
CatalogSource); lint 0 errors; tests/kit/opencode.test.mjs (46/46),
tests/kit/lifecycle-registry.test.mjs (31/31), and `pnpm run check` (full
typecheck+lint+markdownlint+build+test) all pass.
… key

mliRouteValue(binding,"used") concatenated binding.activity and
binding.role with a literal NUL control byte (0x00) instead of a space
character -- pre-existing in the source, not introduced by this branch.
This value feeds mliRouteCompare's string comparison, which backs the real,
user-clickable "Used for" column sort in the Model Routes table
(data-mli-route-sort="used" in page.mjs), so it was a live sort-key defect
rather than dead code. Replaced the NUL byte with a plain space.

pnpm test (356/356) and pnpm run test:ui (331/331) both pass; typecheck,
lint, and build are clean.
…36 -> 12/12)

Both functions' complexity came almost entirely from optional-chain/nullish
operators, not obvious branching, so extraction targets the shape rather
than the individual conditions:

- terminalResult (30->12): extract classifyObservation (the
  observation-type if/else-if chain mapping to {status, exitCategory,
  failure}) and terminalResultUsage (the tokens/cost ternary).
- launch (36->12): extract spawnServerChild (invocation resolution + spawn +
  port acquisition, with its own try/catch cleanup) and startSession
  (health-check + session open + event-stream subscribe + prompt post),
  both as closures inside createOpenCodeExecutionAdapter so they keep
  direct access to its injected fetchFn/spawnFn/resolveFn/etc. without
  threading ten parameters through.

Mechanical, behavior-preserving — same request order, same try/catch
boundaries and cleanup-on-abort behavior. tests/kit/opencode-execution.test.mjs
(22/22) and the full suite pass unchanged; typecheck clean; lint has 0
warnings left for this file.
…28->~3)

The deployed lazy-gateway plugin's config(cfg) hook (called by OpenCode on
every config load) mixed three concerns: computing today's availability
map, pruning tool entries for unavailable families, and projecting
cfg.tools/cfg.permission. Extract each into its own top-level function
(computeAvailability stays a closure inside rufloGateway — it needs
rufloClient/aqeClient; pruneUnavailableTools/projectGatewayTools/
projectGatewayPermissions are pure functions of their params, no closure
needed).

This is an emitted template (deployed verbatim to
~/.config/opencode/plugins/ruflo-gateway.js, receipt-hashed by content) —
plain ESM, no build step, no constraint beyond "valid self-contained
module"; adding top-level function declarations is safe the same way the
file's existing dozen helpers already are.

Mechanical, behavior-preserving — identical availability/tools/permission
computation and identical mutation order (prune tools before assigning
cfg.tools/cfg.permission). tests/kit/opencode-ruflo-gateway.test.mjs
(13/13) and the full suite pass unchanged; lint has 0 warnings left for
this file (worst remaining function is CC 19, deployManagedPlugin's sibling
terminateChild, already below the audit's original findings).
… (CC 27)

mliDetail computed several dense ternary-chain text fields (published/
access/routable/next-step status, lifecycle-scope/availability/retirement,
and the ollama-only local-install block) inline while also building the
whole detail-panel DOM. Extracted the three text/HTML computations into
mliDetailStatusText, mliDetailVariantFields, and mliDetailLocalBlock;
mliDetail itself now only assembles the panel from their results.

renderModelLifecycle wrote to ~10 unrelated DOM targets (badge/asof,
attention list, routes+observed, history, consumers+impact) in one
function. Split into mliRenderBadgeAndAsof/mliRenderAttention/
mliRenderRoutesAndObserved/mliRenderHistory/mliRenderConsumersAndImpact,
called in the original order.

Both splits keep every original expression verbatim; only the grouping
changed. pnpm test (356/356) and pnpm run test:ui (331/331) both pass
unchanged from baseline; typecheck, lint, and build are clean.
…C cleanup

ADR-0017 named src/lib/opencode.mjs as the literal home of
opencodeStack/retireOpencode/reconcileOpencodeGuidance and described it as
"one owner module" in the singular-file sense; ADR-0037 listed the
opencode.mjs receipt-reconciliation family as deferred backlog. Both are
now stale after this track's work:

- ADR-0017: note the 2026-08-26 split (opencode.mjs is now a re-export
  barrel over opencode-core/agents/artifacts/lifecycle.mjs) in the update
  note, the "One owner module" section, and the References file list —
  no architectural change, still one owner module from every caller's
  point of view.
- ADR-0037: replace the opencode.mjs backlog line with a "Wave 2 update"
  noting reconcileOwnedMap/reconcileFamilyPermissions now back
  applyOpencode (71->21) and every other named opencode.mjs/
  execution/opencode.mjs/opencode-ruflo-gateway.js function over CC 25 is
  under the gate.
renderScore wrote ~10 independent scorecard regions (hero KPIs, cost-per-day
bars, host cards, token bar/legend, punchcard, models, OpenRouter, projects,
categories) in one function. Split into renderScoreHero/DayBars/Hosts/
TokBar/Punchcard/Models/OpenRouter/Projects/Categories, called in the
original order; renderScore is now a thin orchestrator.

renderLimits mixed the Claude window, the Codex lane, and the insights
panel. Split into renderLimitsClaude/renderLimitsCodex/
renderLimitsInsights.

Both splits keep every original expression verbatim. pnpm test (356/356)
and pnpm run test:ui (331/331) both pass unchanged from baseline; typecheck,
lint, and build are clean.
… split into usage-parsers.mjs + usage-aggregate.mjs

parseClaude (CC 33), parseCodex (CC 53), and aggregate (CC 44) each
decompose into small per-record/per-phase helper functions, all under
CC 25. usage-index.mjs (1,736 raw / 1,067 counted lines) then splits
along its natural seams:

- usage-parsers.mjs: the per-vendor transcript parsers (parseClaude,
  parseCodex, blankSession, addUsage, projectLabel, IDLE_GAP_MS) and
  their private helpers.
- usage-aggregate.mjs: pure arithmetic over already-parsed records
  (mergeIntervals, maskSecrets, normalizeSessionIdentity,
  applyCodexLedger, aggregate, sessionPayload, MAX_TURN_CHARS) — a leaf
  module with no dependency on its siblings.
- usage-index.mjs: index I/O, the on-disk cache, build/scan
  orchestration, and single-session resolution; re-exports the moved
  names so every existing import path (tests, dashboard-server,
  usage-opencode.mjs) keeps working unchanged.

usage-opencode.mjs now imports blankSession/addUsage directly from
usage-parsers.mjs instead of usage-index.mjs, removing the pre-existing
circular import between the two (usage-index.mjs -> usage-opencode.mjs
-> usage-parsers.mjs is now a straight line, not a cycle).

All three files land under the 1,000-counted-line max-lines threshold
(usage-index.mjs 771 raw lines / usage-parsers.mjs 594 / usage-aggregate.mjs
517), and every eslint complexity warning in this file family is gone.
No behavior change: identical aggregation output, identical cache
schema, identical exports. Realigns every usage-index.mjs citation in
docs/USAGE-SCORECARD-METRICS.md and docs/TRANSCRIPTS.md that moved
file or line.
….mjs

renderSysSummary mixed the KPI band, the disk gauge band, and a call into
renderSysConsumers in one function. Split into renderSysKpis and
renderSysGaugeBand; renderSysSummary is now a thin orchestrator calling
both plus renderSysConsumers, in the original order.

pnpm test (356/356) and pnpm run test:ui (331/331) both pass unchanged from
baseline; typecheck, lint, and build are clean.
setTab mixed lazy per-tab data loads, the tab button/area/secondary-rail
paint loop, and sub-view/scroll bookkeeping in one function. Extracted
tabLazyLoad(id) and paintTabButtons(id,focus), called in their original
order; setTab is now a thin orchestrator.

This was the last function over CC 25 in src/lib/dashboard/client/**;
`pnpm exec eslint src/lib/dashboard/client --rule '{"complexity":["warn",25]}'`
now reports zero warnings.

pnpm run check (typecheck + lint + markdownlint + build + test, 356/356)
and pnpm run test:ui (331/331) both pass unchanged from baseline.
…rage 88->2 orchestrator; NUL-byte sort-key bug fixed)
providers.mjs (1,086 counted lines, over the max-lines warning) hosted
the whole applyAqeRouter/aqeRouterDrift/undoAqeRouter convergence
pipeline: AQE_ROUTER_SURFACES, the four surface reconcilers,
buildAqeRouterContext/runAqeRouterFold, and reconcileExternalProviders
(CC 40). All of it moves to a new src/lib/aqe-router.mjs, which imports
back the shared hash/ownership primitives (AQE_OWNERSHIP_KEY,
stableValue, declarationHash, plainRecord — now exported) and the
provider-detection helpers it needs (aqeExternalProviders,
aqeRouterFile, aqeSupportsExternalProviders, aqeSupportsAgentOverrides,
aqeSelectableChainProviderTypes, credentialGaps) from providers.mjs.
providers.mjs re-imports applyAqeRouter/aqeRouterDrift/undoAqeRouter
and re-exports them, so every existing `./providers.mjs` import path
(commands, status sections, tests) keeps working unchanged — the two
modules do have a real bidirectional dependency (mirroring the
pre-existing, safe usage-index.mjs/usage-opencode.mjs pattern: hoisted
function declarations, nothing read at module-eval time), which is a
deliberate tradeoff against pulling much more of providers.mjs's public
detection API into the new file.

reconcileExternalProviders (CC 40) decomposes into
reconcileDesiredProvider/retireStaleProvider (with
reconcileConflictingDeclaration/reconcileProviderActivation) — each
well under CC 25. No behavior change: identical router-file writes,
identical drift detection, identical ownership receipts.
applyProviders (CC 34) splits its per-model registration loop into
validProviderEntry, resolveProviderEndpointArgs, and applyOneProvider.
Same id/model grammar check (De Morgan-equivalent, verified), same
Ollama default-endpoint heuristic, same done-list labels and attempted
count. No behavior change.
…parseCodex 53->11, usage-index + providers split)
Zero functions over CC 50 remain, so the ceiling earned by the complexity
program graduates from advisory to enforced: `pnpm run lint:cc` (error at
50) joins `check` and CI's quality job. The warn-25 tier stays advisory.
ADR-0037 carries the wave-2 closure record.

Claude-Session: https://claude.ai/code/session_01WMKwDpp14PPjj8Bn1g7Uza
@pacphi
pacphi merged commit 73b7b1c into main Aug 27, 2026
15 checks passed
@pacphi
pacphi deleted the refactor/complexity-wave-2 branch August 27, 2026 03:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant