Skip to content

refactor: complexity program — remove all CC>100 functions, fix 5 duplication bugs - #184

Merged
pacphi merged 40 commits into
mainfrom
refactor/complexity-program
Aug 27, 2026
Merged

refactor: complexity program — remove all CC>100 functions, fix 5 duplication bugs#184
pacphi merged 40 commits into
mainfrom
refactor/complexity-program

Conversation

@pacphi

Copy link
Copy Markdown
Owner

What this is

The 2026-08-26 full-codebase complexity audit found 399 functions over CC 10 (six over CC 100, worst CC 250), eleven files over 1,000 lines, and five real behavioral divergences caused by duplicated logic. This PR is the complete remediation program: P0 safety nets, five file-disjoint parallel refactor tracks, a cross-track finisher, and program docs. ADR-0037 is the program record; ADR-0036 covers the dashboard specifics.

Bugs fixed (each with its own regression test)

  1. Live Codex status-plane adapter never decoded the newer item_completed generation — newer Codex sessions looked dead in the live view (a56f9d4)
  2. Same gap in the content-plane transcript adapter, found mid-track (b96e8b2)
  3. ak host pick/setup persisted retired-model routes only sync would heal (920c8d9)
  4. Divergent duplicate signal-kind inference (dead code with a wrong fallback) (78e9ac2)
  5. Status re-implemented the writer's drift comparisons read-side — the issue fix: AQE project-scope and route projections make ak sync report permanent drift #129 class; now writer-owned with a status/writer parity test (e34a4e0)

Headline decompositions (all behavior-preserving, golden-snapshot / suite-pinned)

FunctionBeforeAfter
status.mjs collect()CC 2504 (33 section modules)
dashboard-server request handlerCC 19413 (route table + sseRoute())
rufloActivationSegmentsCC 1939 (segment providers)
x/host.mjs pick()CC 14411 (parse/decide/apply)
applyAqeRouterCC 11121 (ordered surface reconcilers)
sync run()CC 11020 (ordered step registry)
dashboard/client.mjs4,066-line string104-line collector + 11 real lintable modules

CC>100 count: 6 → 0. The raw CC>10 count rises (399→~507) because the client's ~184 functions are now visible to ESLint for the first time — the denominator became honest.

New gates

  • Golden byte-for-byte snapshot of ak status collect() (deliberate regeneration only)
  • Dashboard Playwright suite (test:ui, 331 cases) wired into CI — was manual-only
  • complexity: 25 / max-depth: 5 / max-lines: 1000 ESLint warnings over src+bin, ratcheting to errors as areas come clean
  • Status/writer drift parity test

Verification

Serial gated integration: full pnpm run check (typecheck + lint + markdown lint + build + full test chain) after every track merge, exit 0 each time; test:ui 331/331 after the dashboard merge; golden snapshot byte-identical throughout. Final suite: 2,289 tests, 0 failures.

Docs

docs/PROVIDERS.md, USAGE-SCORECARD-METRICS.md, TRANSCRIPTS.md aligned; ADR-0036 + ADR-0037 added and indexed; PR-131 consistency dossier and the implemented issue-110 plan archived into docs/archive/ per its convention.

Honest residuals (deliberately out of scope — wave-2 backlog, listed in ADR-0037)

uninstall run() (CC 100), opencode.mjs receipt-reconciliation family (71), model-inventory/footprint/adapters functions in the 50–65 band, newly visible client functions (system-projects 88, overview 63), providers.mjs just over the 1,000-line warning.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WMKwDpp14PPjj8Bn1g7Uza

pacphi added 30 commits August 26, 2026 17:40
Nets under the refactor tracks that follow, no behavior change:
- golden snapshot of `ak status` collect() for the offline fixture
(row order, messages, and fix strings are load-bearing for sync's plan)
- dashboard Playwright UI suite wired into CI (was manual-only; the only
rendering verification the dashboard has)
- complexity/max-depth/max-lines ESLint warnings over src+bin (visibility
only; ratchets to errors per-directory as tracks land)
Claude-Session: https://claude.ai/code/session_01WMKwDpp14PPjj8Bn1g7Uza
Cherry-pick e5f3d53 (golden snapshot test + fixture for status collect(),
CI wiring, complexity ESLint visibility warnings) — this worktree's branch
point predated it on refactor/complexity-program. No behavior change;
establishes the baseline Track D's task depends on.
The live adapter only handled the legacy user_message/agent_message event
pair, while the batch usage scanner (usage-index.mjs's codexEvent) already
decodes the newer item_completed envelope wrapping UserMessage/AgentMessage
items. A Codex rollout written in the newer generation therefore emitted no
session.input/agent.output live events and looked dead in the live view even
though the batch scan counted its prompts/responses correctly.
Teach adaptCodexRecord the same generation-detection the batch parser uses,
via a small codexMessageKind() helper mirroring codexEvent()'s dispatch.
… guess
projection.mjs's signalKind() re-implemented its own action→kind mapping as a
fallback for events lacking event.signal.kind, and disagreed with
event-schema.mjs's inferredSignal(): the projection copy only recognized
presence/operation and defaulted everything else to 'metadata', missing the
'relationship' (agent.spawned/planned) and 'activity'
(session.input/agent.output/session.started) cases inferredSignal knows about.
createLiveEvent always stamps signal.kind today, so this fallback is a
defensive no-op in current code paths, but it was a second, independently
maintained answer to the same question and a wrong one if it were ever
exercised. Export inferredSignal and reuse it instead of the duplicate.
Moves opencodeDetailRows, HOST_DETAIL_RENDERERS, renderHostDetailRows, and
admittedLifecycleFallbackRows out of status.mjs into a new
src/commands/status/host-detail.mjs, and extracts a shared row() helper into
src/commands/status/row.mjs.
opencodeDetailRows (CC 86) is also decomposed: the plugin/gateway/skill
artifacts shared a near-identical adoptable->foreign->absent->stale ladder,
each condition re-prefixed with !receiptState.adoptionBlocked (12
repetitions). Extracted a single artifactRow(subsystem, label, state, opts)
helper plus one early return on adoptionBlocked; the wiring-convergence and
agents ladders (which don't fit that shape) become their own small
functions. Result: opencodeDetailRows CC 86 -> 19, all extracted helpers
under CC 20.
Pure decomposition: no messages, ordering, or logic changed. Byte-for-byte
identical collect() output, verified against the golden snapshot.
applyHosts -> seedActivityRoutesIfMultiHost -> applyAqeRouter ->
retireCodexMcp -> ensureRufloMcpInCodex -> applyProviders is duplicated
across host.mjs, sync.mjs, and setup.mjs, but only sync.mjs called
migrateRetiredRoutesInConfig — so `ak host pick` and `ak setup --project`
could persist a per-activity route naming a model the host has withdrawn,
left for the next `ak sync` to repair. Call it from both paths too, in the
same seed-then-migrate order sync.mjs already uses.
pick() and run_project() take an injectable `migrateRoutes` (defaulting to
the real migrateRetiredRoutesInConfig) purely as a test seam, since
routing.mjs's RETIRED_MODELS table is currently empty (no cited withdrawal)
and so cannot demonstrate a real rewrite end-to-end.
rufloActivationSegments (statusline-footer.cjs) rendered nine independent
statusline segments (quota tee, SONA, LoRA, route-RL, proof, aidefence,
daemon, brain, QE) in one 437-line body with CC 193. Lift each segment
into its own top-level function taking an explicit ctx (fs/path/cp/os/
colors/cwd/stdin), split the LoRA block (session id, staleness, weight
recompute, pattern replay, formatting) into single-purpose helpers, and
split the RuvNet Brain and Agentic QE blocks into version/size/query
sub-helpers, since those two also exceeded the CC budget as single units.
The one real coupling (LoRA appends onto SONA's line) is now explicit:
rufloLoraSegment(ctx, learn) takes SONA's rendered string and returns the
combined line. rufloActivationSegments itself reduces to an ordered
segment-provider array plus a small assembler (CC 193 -> 9).
Normalizes the DIM/G/Y/C/R color constants from embedded raw ESC bytes to
\x1b escape notation (matching RED's existing style) — identical runtime
strings, safer to read and diff.
Behavior is unchanged: tests/statusline-segments.test.cjs (46) and
tests/statusline-brain.test.cjs (10) pass unmodified. All functions in
the file are now well under the repo's CC-25 lint warning threshold
(worst case 20, in the untouched rufloStatuslineDebug); the file no
longer appears in `pnpm run lint` output at all.
The emitted template remains one self-contained file within the
ruflo-seg:BEGIN/END markers, with no imports from outside the block.
collectDejaVuRows (CC 95) mixed five concerns in one function: error
mapping, the install ladder, doctor health, the 6-way per-host target
ladder, and the derived-index ladder. Extracted dejaErrorRow,
dejaInstallRows, dejaDoctorRows, dejaTargetRows (via a dejaTargetContext
guard-clause helper to keep both under the CC budget), and dejaIndexRows
into src/commands/status/deja-vu.mjs; collectDejaVuRows is now a ~20-line
orchestrator that assembles their rows in the same order and keeps its
existing try/catch and exact exported signature.
Result: collectDejaVuRows CC 95 -> 21, every extracted helper under CC 25.
Pure decomposition: no messages, ordering, or logic changed. status.mjs
re-exports collectDejaVuRows unchanged for existing test imports.
usage-opencode.mjs hand-mirrored the per-session record shape and the
(day, model) usage-row accumulator that parseClaude/parseCodex already
define in usage-index.mjs — its own comment admitted it was "mirroring
parseClaude/parseCodex exactly". Export both and have opencode's parser
build on them instead of a separate hand-written copy, so the three
transcript sources share one definition of "what a session record looks
like" and "how a usage row accumulates".
addUsage now returns the row it touched so a source with a per-source extra
field (opencode's observed costObserved) can set it without a second find().
Also re-anchors the usage-index.mjs file:line citations in
docs/USAGE-SCORECARD-METRICS.md and docs/TRANSCRIPTS.md that this shift
rendered stale (doc-citations.test.mjs).
applyHosts -> seedActivityRoutesIfMultiHost -> migrateRetiredRoutesInConfig
-> applyAqeRouter -> retireCodexMcp -> ensureRufloMcpInCodex ->
applyProviders was pasted across host.mjs (pick), sync.mjs (run), and
setup.mjs (run_project). Extract the ONE pipeline into
providers.mjs's convergeProviderStack(cfg, cwd, options); each call site now
supplies only its own report/save policy via an injected `reporter`
callback (fired once per step, in order) plus a couple of per-site knobs
(`seedRoutes` — pick already seeded earlier in its own flow; `codexMcp` —
setup only runs the legacy/reverse Codex MCP steps while codex is enabled,
matching its pre-existing behavior; `runProviders` — sync wraps the
terminal call with its progress ticker).
Output strings, config-write ordering, and save-on-change gating are
unchanged at every call site; only the pipeline definition itself is no
longer triplicated.
detectInsights (CC=71) inlined 13 numbered, independent heuristics in one
~480-line body sharing only a windowCost/sessions prelude. Extract each into
its own detectX(ctx) function returning zero or one insight, collect them in
a DETECTORS registry, and rebuild detectInsights as prelude + flatMap +
the existing ranking sort. Output is identical: same firing conditions, same
text, same ranking (DETECTORS keeps the original numbered order, and sort is
stable).
detectInsights's own complexity drops from 71 to 7 (dominated by its
defensive-guard ternaries); each extracted detector sits well under the
project's CC 25 threshold.
Adds a direct unit test per detector via a new `_detectors` test-only export,
including first-time coverage for parallel-sessions, subagent-share and
long-session-share, which previously had no dedicated fixtures.
…ollect()
collect() (CC 250, the worst function in the repo) inlined ~25 subsystem
concerns as ad-hoc try/catch + branch ladders. The file already had the
right pattern for exactly one concern (HOST_DETAIL_RENDERERS +
renderHostDetailRows); this generalizes it to the rest.
Each concern becomes its own module under src/commands/status/sections/,
exporting { id, collect: async (ctx) => Row[] } with
ctx = { cfg, cwd, pkgRoot, integrationFacts }. collect() is now two ordered
walks over SECTIONS_BEFORE_HOST_DETAIL / SECTIONS_AFTER_HOST_DETAIL (split
only because three existing calls -- collectDejaVuRows,
renderHostDetailRows, admittedLifecycleFallbackRows -- keep their own
bespoke signatures and error contracts between them, unchanged), each row
wrapped in a uniform try/catch that falls back to a generic
'<id> check unavailable' warn row. Sections that already had their own
try/catch (most of them) keep it verbatim for their exact original
message; the uniform wrapper is a backstop, and for the handful of
concerns that had NO try/catch before (security, learning, aqe, agentdb,
mcp, statusline, qe-court), it's a strict improvement: an unexpected throw
there now degrades one row instead of crashing all of collect().
The providers section (~8 sub-concerns under one try/catch, per audit) is
split into five sections -- providers-status, providers-external-intent,
providers-external-projection, providers-ruflo-models,
providers-local-bindings -- sharing a small computeProviderExternalState
helper (_providers-external.mjs) that each calls and catches
independently, so one probe failing no longer collapses all eight rows
into a single warn. Its drift-comparison logic duplicates write-side logic
in src/lib/providers.mjs by design for now; carries a
"TODO(complexity-program)" marker for a later cross-track re-homing.
The three-block codex-mcp concern and the four-block statusline concern
each become one section file with independently-caught inner functions,
preserving their existing per-probe error isolation.
Result: collect() CC 250 -> ~5 (a loop + a try/catch), status.mjs
1216 -> 118 lines. Every new section under CC 25 (worst is 21). Pure
decomposition: no messages, row order, or logic changed -- verified
byte-for-byte against the golden snapshot and all 48 status-command
behavior tests, plus the full status-aqe-drift and status-viability
suites.
…table
dashboard-server.mjs's http.createServer callback was one if-chain closure
spanning ~680 lines (CC=194): 15 routes including two SSE state machines
whose reserve-slot/early-close/channel-open lifecycle was copy-pasted
verbatim three times. Split each route into its own named handler, dispatch
via an exact-path lookup table plus a small parametrized-route list, and
extract that shared SSE lifecycle into sse.mjs's new sseRoute() helper
(reserve-before-await, early-close forwarding, header/channel setup, and a
route-controlled activate()/setOnClose() for the parts that genuinely differ
per route). handleLiveEvents' own snapshot/replay reconciliation is further
pulled into a pure deliverLiveInit() helper.
Every route's behavior, security header set, and concurrency/TOCTOU
handling is unchanged — same 401/403/404 shapes, same SSE resumption and
dedup guarantees, same client-cap semantics. dashboard.test.cjs's 77 cases
(including the snapshot/replay race and TOCTOU regression tests) and the
Playwright dashboard-ui suite pass unmodified.
Both are implemented history: the Host & Provider Consistency master
review's decisions live in ADRs 0028-0031 (and its structural citations
predate the complexity-program refactor); the issue-110 session prompt
drove PR #179, recorded durably in ADR-0032. Renamed per the archive's
date-origin-topic convention and indexed in its README.
Claude-Session: https://claude.ai/code/session_01WMKwDpp14PPjj8Bn1g7Uza
applyAqeRouter (CC 111) braided five reconcilers (externalProviders,
managed fallbackChain, defaultProvider + its two ownership-receipt kinds,
agentOverrides, and the stale-override recompute) together via shared
mutable accumulators with implicit cross-surface feedback (externalActive
constrained what the later surfaces could reference).
Split into four (draft, ctx) => {detail, error, changed, ctx?} surface
functions folded over one draft via a small foldSurfaces helper; the one
real cross-surface dependency (externalActive -> refined
projected/staleOverrides) is now an explicit ctx patch instead of a loose
outer-scope `let`. Extracted the "nothing to apply" gate and the
stale-ownership-receipt pre-clear into named helpers, and the
externalProviders detail-line formatting into its own function, to keep
each surface's own branch count legible.
Also: change detection stringified `existing` twice for the same
never-mutated object (once before tagging `_managedBy`, once after) -
compute that snapshot once and reuse it for both compares.
CC: applyAqeRouter 111 -> 21; new surfaces land at 21-24. Output strings,
file-write conditions, and ordering are unchanged — the full suite (2265
tests, extensively covering this function's branches) passes unmodified.
…bing
dashboard-server.mjs and admin-server.mjs each defined their own identical
readJsonSafe, minted their session token the same way, wrote the same
401/404 JSON response headers, and repeated the same
server.listen(...).then(resolve {url, urlWithToken, port, token, close})
boilerplate. dashboard-server.mjs also imported tokenMatches FROM
admin-server.mjs — a security primitive with no business being homed in one
specific server.
New src/lib/loopback-server.mjs owns all of it: mintToken/tokenMatches,
readJsonSafe, sendJson/sendUnauthorized/sendNotFound, and listenLoopback()
for the bind-to-127.0.0.1-and-resolve lifecycle (each server still supplies
its own close(), since dashboard's also tears down SSE clients and
background services). admin-server.mjs re-exports tokenMatches so its
existing public surface and tests/admin.test.cjs are unaffected.
Every security behavior is unchanged byte-for-byte: 127.0.0.1 binding,
token-in-fragment URL shape, DNS-rebinding Host guard, Sec-Fetch-Site/Origin
enforcement, CSP, and the 401/404 response shapes. dashboard.test.cjs and
admin.test.cjs pass unmodified.
run() (CC 110) was ~20 `if (subsystems.has(X)) { ... }` blocks inlined in
one function, with real ordering invariants (natives last among npm-tree
mutations, statusline after providers, kit self-update last of all) proven
only by source order and explained only in comments.
Replace with SYNC_STEPS: an ordered [{id, when(subsystems, flags, cfg),
run(ctx)}] registry. Array position is now the ordering invariant instead
of prose; `when` is a pure, explicitly-parameterized predicate so it can be
reasoned about independent of `run`'s side effects. `run(ctx)` receives the
per-invocation context (cfg, cwd, pkgRoot, flags, dejaVuAdapter,
subsystems, report, step, state) — `state` carries the two cross-step
signals (dejaVuApplyFailed, aqeRouterApplyFailure) the final convergence
check needs.
Output strings, config writes, and step ordering are byte-identical to
before; the full suite (2265 tests) passes unmodified.
CC: run() 110 -> 20; every step lands at 1-8 (the 'providers' step's
reporter callback, unavoidably multi-branch, lands at 22).
…live adapters
Batch usage scanning and live session adaptation each decoded the same
Codex and Claude transcript wire formats separately, and the copies had
diverged (the item_completed generation the previous commit fixed in the
live adapter is exactly this class of drift): session_meta/turn_context
extraction, the model_provider-vs-legacy-provider tolerance, Claude role
discrimination and content block-walking, and the tool call/result callId
tolerance were each implemented twice.
Add src/lib/telemetry-records.mjs with decodeCodexRecord/decodeClaudeRecord
as the one place each vendor's wire shape gets interpreted, plus the
resolveCodexProvider tolerant lookup, claudeText flattening and the
artifactName helper (previously duplicated verbatim in both live adapters).
usage-index.mjs's parseClaude/parseCodex and the live codex-adapter.mjs/
claude-adapter.mjs now all decode through these functions; aggregation vs.
event emission stay separate, reading whichever decoded fields they need.
Behavior-preserving with one deliberate widening: parseCodex now also
tolerates a bare legacy `provider` field on session_meta/turn_context
(previously only the live adapter did), unifying the "spelled two ways"
duplication the audit flagged. No existing fixture or test exercises that
field shape without model_provider also present, so this is not observable
as a regression; it makes batch and live agree instead of quietly disagreeing.
Re-anchors the usage-index.mjs file:line citations in
docs/USAGE-SCORECARD-METRICS.md and docs/TRANSCRIPTS.md that this move
rendered stale, including two that now correctly point at
telemetry-records.mjs instead.
run_machine (CC 44), run_project (CC 42), and the top-level run() (CC 56)
were each one long function walking a numbered-comment sequence of
install/heal/wire steps, several with early-return gates threaded through.
Extract each numbered step into its own named function (e.g.
installMachinePackages, applyMachineHostLifecycles, rufloProjectInit,
initProjectAgenticQe, resolveSetupTrust, finalizeSetupGuidanceAndMcp);
the three entry points become short linear call sequences with the same
early-return gates. Also extract providers.mjs's guidanceContext(cfg) —
the exact {flags:{dualMode, opencodeEnabled}} shape both `ak sync`'s
`blocks` step and setup's finalizeSetupGuidanceAndMcp build for
blocks.mjs's reconcileGuidance — so that shared shape is defined once.
Output strings, config writes, and step ordering are unchanged; the full
suite (2265 tests) passes unmodified.
CC: run_machine 44 -> 4, run_project 42 -> 7, run() 56 -> 21; every
extracted helper lands at 2-16.
…tions
scan() (CC=73) inlined provider-specific logic straight into the generic
scan loop: opencode discovery+health (coupled, since a SQLite read can fail
in ways a directory walk cannot), codex-only per-file diagnostics, opencode's
pseudo-key carry-forward with its mid-loop health mutation, and the codex
ledger resolution — three hand-built health objects and a comment elsewhere
in the file already conceding the hardcoded source triple as a known smell.
Extract each concern into its own function: discoverOpencodeSource,
processCandidate (the per-candidate parse+diagnostics step),
carryForwardCachedEntries (+ carryForwardOpencodeEntry, split out to keep
both under the complexity threshold), and resolveCodexLedger. scan() itself
is now the orchestration: discover, loop candidates, carry forward, write
cache, resolve the ledger, aggregate, assemble health.
Not a fully generic per-source descriptor array as literally suggested:
opencode's discovery is coupled to its health in a way the claude/codex
directory-walk sources aren't, and forcing a uniform {list, parse, health,
carryForward} shape over that asymmetry risked obscuring the real behavior
difference (opencode's carry-forward re-queues into `records`; claude/codex's
does not) rather than clarifying it. Named-function extraction gets the same
complexity reduction with lower risk of a subtle regression in a function
this load-bearing.
Behavior-preserving: same candidates, same cache entries, same aggregate,
same sourceHealth shape — verified against the full existing test suite,
including the scan-level cache/health/carry-forward tests. Complexity:
scan() 73 -> 13; extracted functions each land under 25 (discoverOpencodeSource
8, processCandidate 20, carryForwardCachedEntries 16, carryForwardOpencodeEntry
11, resolveCodexLedger 9).
Re-anchors two more usage-index.mjs file:line citations this shift moved.
reduceLiveEvent (CC=81) was not a switch-on-type, but a monolithic merge
touching actor-node identity, session status, target node/edge, updatedAt,
and lifecycle all in one body. Decompose into cloneOrCreateSession,
mergeActorNode, applyStatus, applyTarget, resolveUpdatedAt, and
applyLifecycle, each owning a disjoint slice of the session it mutates.
Reordering is safe because the phases are largely independent: applyStatus
(presence/activity/workspace/project/evidence/session.status) reads only
`event` and the session's own prior fields, so its relative position versus
mergeActorNode does not change the result — verified against the full
existing projection test suite, which pins exact output shapes. applyTarget
still runs after mergeActorNode, matching the original's inline order, in
case a target id ever collides with the actor's own id.
The source.adapter==='codex-state' string-matching this function relies on
in three places is left as string-matching, not promoted to an authority
field: that would be a semantic change to how source authority is modeled,
and the fix's own rule is "prove byte-identical output or defer" — deferred,
noted in the track's final report.
Complexity: reduceLiveEvent 81 -> 12; extracted functions each land under 25
(cloneOrCreateSession 5, mergeActorNode 24, applyStatus 14, applyTarget 18,
resolveUpdatedAt 9, applyLifecycle 5).
pick() (CC 144 pre-refactor; 121 after the earlier convergeProviderStack
extraction) welded flags-vs-readline input parsing, host/primary-host/aqe
validation, and the install/wire/converge apply step into one function
with a stdin dependency that made the decision logic untestable in
isolation.
Split into three stages: parsePickInput (delegates to
parsePickInputFromFlags / promptPickInputInteractively),
resolvePickDecision (host validation, primary-host resolution, admission
refresh, aqe selection validation via the extracted
validatePickAqeSelections, routing-policy construction — mutates cfg,
returns the resolved decision or an abort code), and the apply stage
(retireCodexOnDisable, installPickAbsentHosts,
applyPickOpencodeLifecycle split into enable/disable halves,
applyPickProviderStack using convergeProviderStack). pick() itself is now
the sequencing of these plus the handful of side effects between them.
Output strings, config writes, and step ordering are unchanged; the full
suite (2265 tests, including the real-spawn pick() integration tests and
the routing-retirement regression tests added for the earlier bug fix)
passes unmodified.
CC: pick() 121 -> 24; every extracted stage/helper lands at 1-25 (only
the pre-existing, untouched `status()` still exceeds 25 in this file).
The retired-Codex-models section described only the retirement-rule
citation policy, not which commands apply the resulting route rewrite.
ak host pick and ak setup now run the same heal ak sync always has
(audit #1 fix) — state that plainly next to the existing citation note.
…r too
adaptCodexTranscriptRecord (src/lib/live/transcript-adapter.mjs) had the same
legacy-only gap codex-adapter.mjs's item_completed fix addressed for the
status plane: it recognized only the legacy user_message/agent_message
event_msg pair, so a newer-generation Codex rollout surfaced no message
content in the transcript/playback view even though the status-plane adapter
(after the earlier fix) and the batch scanner both handle it.
Add an item_completed branch that decodes through decodeCodexRecord
(telemetry-records.mjs) rather than re-deriving the UserMessage/AgentMessage
item-type dispatch locally, keeping that wire knowledge single-sourced.
decodeCodexRecord joins multi-block content into one string, so this new
branch yields at most one message per item_completed event; the existing
legacy branch's per-content-block splitting (codexMessageText) is untouched
and unaffected.
Regression test mirrors the one written for codex-adapter.mjs: UserMessage,
AgentMessage (multi-block Text content), and an unrecognized item type
(which must yield no message, matching the "no encrypted reasoning or tool
bodies" contract this adapter already upholds for other unrecognized shapes).
Two audit bugs (item_completed live gap, divergent signal-kind inference)
plus a third found mid-track (same gap in the content-plane transcript
adapter), the shared telemetry-records.mjs decode layer, and mechanical
decompositions: scan 73->13, detectInsights 71->7, reduceLiveEvent 81->12.
Note: parseCodex now honors a bare legacy `provider` field on session
meta/turn context (deliberate widening resolving the batch-vs-live
disagreement; unobservable on existing fixtures).
…etup's reporter
Extract providers.mjs's reportRetiredRouteChanges(changes) — the identical
per-change print loop that ak sync's, ak host pick's, and ak setup's
convergeProviderStack 'routing-retired' reporters each carried inline
(same detail-string construction, same reportOutcome call) — so the
wording can never drift between the three, matching #2's "one shared
pipeline" goal for the report side too.
Also split setup.mjs's applyProjectProviderStack reporter (CC 30, over
the repo's complexity budget) into reportProjectAqeRouterStep and
reportProjectRufloCodexMcpStep, mirroring the same split already applied
to host.mjs's pick reporter.
Output strings and ordering are unchanged; the full suite (2265 tests)
passes unmodified.
…nto real modules
client.mjs's entire browser bundle lived as ONE template literal string
(export const JS = `...4044 lines...`) — invisible to node --check, ESLint,
and tsc alike (Finding 2 of the 2026-08 complexity audit). Split it along its
own section markers into 11 real, individually lintable/typecheckable
browser modules under src/lib/dashboard/client/ (bootstrap, overview,
intelligence, poll, usage, model-lifecycle, usage-orchestrators, about,
system-readout, system-projects, boot), each declaring real import/export
for its actual cross-file dependencies (wiring verified mechanically via
ESLint's own no-undef output, not hand-traced).
client.mjs is now a ~90-line COLLECTOR: it reads each split file's source,
strips the never-really-resolved cross-file import/export lines (concatenation
collapses the module graph into one flat scope, exactly as the pre-split
bundle already was), splices in the same Node-computed values the bundle
always carried (groups.mjs's functions/tables via .toString(), the About
directory via JSON.stringify — unchanged interpolation mechanism, just
relocated), and reassembles the exact same single IIFE. The serving contract
is byte-for-byte unchanged: page.mjs still does `import { JS } from
'./client.mjs'` and embeds one `<script>${JS}</script>` — same HTML response,
same CSP, no new routes.
Verified against a captured snapshot of the pre-refactor bundle's own
resolved output: the only diffs are harmless inter-file blank lines and two
deliberate `_`-prefixed renames of pre-existing dead locals (about.mjs's
joined/detected, system-projects.mjs's diskBar) that ESLint's first-ever pass
over this code surfaced. dashboard.test.cjs and the full Playwright
dashboard-ui suite (331 cases) pass unmodified.
Cross-file MUTABLE state (~26 names reassigned from more than one file, e.g.
usageView, SYSTEM) is declared as shared globals in eslint.config.mjs's new
client override rather than imported — real ES import bindings are read-only
from the importing side, which real-import would have made illegal. Each
split file also carries @ts-nocheck (stripped from the served bundle by the
collector): this code is never node-imported, so nothing in it should be
typechecked against node's lib, the same reasoning tsconfig.json already
applies to admin-view.mjs.
…istries
Retired-routes healing now converges from pick/setup/sync alike (audit bug
fix, own regression suite); convergeProviderStack is the single pipeline
definition; applyAqeRouter folds ordered surface reconcilers (111->21);
sync run() is an ordered step registry (110->20); setup run_* decomposed
(44/42/56 -> 4/7/21); pick() split parse/decide/apply (144->11).
pacphi added 10 commits August 26, 2026 18:56
styles.mjs's inline CSS lived as one 1,309-line template literal, flagged by
max-lines (item #4 of the 2026-08 complexity audit — asset, not logic, so
lower priority than the client.mjs split it rides alongside). Split it into
four plain data modules under src/lib/dashboard/styles/ (base, usage, about,
system), each a pure `export const X_CSS = \`...\`` with no interpolation —
unlike client.mjs's browser modules these are real Node-imported modules, so
no placeholder/import-stripping mechanism is needed.
styles.mjs is now a small collector: it imports the four pieces and
concatenates them in the exact order the pre-split stylesheet always declared
them in, so cascade order and selector specificity are unchanged. Verified
against the pre-refactor CSS string: the only diffs are harmless blank lines
at the concatenation seams. Serving contract unchanged (page.mjs still does
`<style>${CSS}</style>`).
…ctor
Records the sseRoute() lifecycle contract, loopback-server.mjs as the home
for loopback security primitives, and the readFileSync-concat module pattern
(generalized from ADR-0007's admin page precedent) now used to split
client.mjs and styles.mjs into real, lintable modules. Indexed in
docs/adr/README.md alongside the existing ADR narrative.
No other docs needed updates: DASHBOARD.md and the other cross-referencing
docs describe user-facing behavior, which this refactor does not change.
Two comments I wrote during the dashboard-server.mjs route-table split and
the client/ eslint override said "(ADR pending)"; point them at ADR-0036
now that it exists. No code change.
…rver
Route-table dispatcher (CC 194->13) with one sseRoute() lifecycle helper
replacing three pasted SSE cleanup state machines; client.mjs 4,066-line
template literal split into 11 real browser modules via the ADR-0007
readFileSync-concat pattern (serving contract unchanged, bundle output
snapshot-verified); styles split per area; loopback-server.mjs now owns
token mint/compare + listen lifecycle for both servers. ADR-0036.
…129-shaped)
status/sections re-implemented the env-drift, aqe-router chain-order-drift,
and external-provider-intent comparisons whose write-side twins live in
providers.mjs (applyHosts, applyAqeRouter, aqeExternalProviderState) — the
exact failure shape issue #129 already shipped once. Move the comparison
logic into providers.mjs as read-only exports derived from the writer's own
code path:
- providerEnvDrift(cfg, env) — the same predicate applyHosts uses to decide
whether to write, now shared instead of restated.
- aqeRouterDrift(cfg, cwd) — runs applyAqeRouter's own dry-run fold
(buildAqeRouterContext + runAqeRouterFold, factored out of applyAqeRouter
itself) and reads the fallback-chain slice of the result, replacing a
hand-rolled approximation of chain validity.
- configuredAdapterIds / externalProviderIntent / providerExternalState —
the external-AQE-provider intent-vs-live derivation, relocated verbatim
from status/sections/_providers-external.mjs (now deleted) onto the
library that owns the rest of this domain.
status/sections/providers-status.mjs, providers-external-intent.mjs, and
providers-external-projection.mjs now consume these exports instead of
recomputing their own view. Adds a parity test
(tests/kit/providers-drift-parity.test.mjs) that imports both the writer's
dry-run comparator and the live status row for a fixture with induced
drift, asserting they agree — so a future edit that reintroduces a second,
independently-derived comparison fails immediately instead of shipping a
silent divergence.
Zero behavior change: full suite (2289 tests), lint (0 errors), and
typecheck all pass; the status-golden snapshot is byte-identical.
…ass)
providerEnvDrift/aqeRouterDrift now live in providers.mjs, computed from
the writer's own predicates and dry-run fold; status sections consume them
and the hand-mirrored copies are deleted. A parity test pins status's
reported drift to the writer's own computation so the #129 failure class
(status vs sync disagreeing) cannot silently recur.
Program-level record of the 2026-08-26 audit and refactor: sanctioned
structures (section registry, one provider pipeline, writer-owned drift
comparators, telemetry decode layer, segment providers), the lint gates
and their ratchet policy, and the residual backlog.
Claude-Session: https://claude.ai/code/session_01WMKwDpp14PPjj8Bn1g7Uza
…lainer
Both are artifact snapshots whose design shipped via ADRs 0028-0031;
renamed per the archive's date-origin-topic convention, indexed in its
README, and ADR-0031's companion links repointed.
Claude-Session: https://claude.ai/code/session_01WMKwDpp14PPjj8Bn1g7Uza
withProjectCli hand-wrote a POSIX-only sh shim, so run_project() aborted
at `ruflo init` on Windows before the step under test — delegate to
withFakePath, whose shims carry .cmd/.ps1 twins. The UI suite's console
gate now ignores the intelligence endpoint's 503 on machines with no
ruflo-initialized project (CI runners), following its 404 precedent.
Claude-Session: https://claude.ai/code/session_01WMKwDpp14PPjj8Bn1g7Uza
…trip
A Windows checkout without eol pinning leaves `;\r\n` line ends, the
collector's import-strip required `;\n`, and a surviving import broke the
served classic-script bundle. Match admin-server's \r tolerance, and pin
source files to LF via .gitattributes so text-read/concat paths exercise
the same bytes on every platform.
Claude-Session: https://claude.ai/code/session_01WMKwDpp14PPjj8Bn1g7Uza
@pacphi
pacphi merged commit 2a5c42e into mainAug 27, 2026
15 checks passed
@pacphi
pacphi deleted the refactor/complexity-program branch August 27, 2026 02:36
Sign up for freeto 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

@pacphi