Activate ruvector self-learning, agentic-qe, security + activation status line - #1
Merged
Merged
Conversation
… self-learning Patches native better-sqlite3 (6 agentdb dirs), guarded controller-registry compat check (no-op on >=3.10), guarded @ruvector native repair, then asserts real capability (core/VectorDb, sona, gnn, agentdb v3) rather than the lazy 'neural status' display strings. 5/5 green on ruflo 3.10.5 / Node 26.
… disk Trains in an isolated temp dir and asserts patterns 0->>0 read directly from .claude-flow/neural/patterns.json + stats.json (on-disk truth), not the lazy neural-status display. Verified 0->7 patterns, 50 learned, 55 trajectories.
… note CVE gap Uses 'security defend' exit code (1=threat, 0=clean) to assert proactive defense, robust to an upstream stdout render crash. Documents the cve --list no-database gap with npm audit fallback. All green on ruflo 3.10.5.
…segments Extends ruflo-fix-statusline-version to inject a fast fs-only helper (no subprocess) that appends 🧠 N (trained patterns) / 🛡 on (aidefence present) / 🎓 qe (.agentic-qe db) — each rendered only when active. Shebang-safe insertion, marker-guarded idempotent.
…alf-init repair Discovers and fixes a NEW bug beyond the gist: agentic-qe depends on better-sqlite3@^12 directly and ships without the prebuilt .node on Node >=24, so 'aqe init' fails at persistence-db init. setup-aqe installs the native binary into the global agentic-qe first, then runs aqe init --auto with half-init repair (re-run --upgrade if the .claude/skills/agentic-quality-engineering marker is missing). Verified: 86 skills, both markers, idempotent.
…pply Statusline footer upgraded from minimal (🧠 N 🛡 on 🎓 qe) to a two-line labeled render: '🧠 SONA <patterns>·<traj>[·⚡HNSW] 🛡 aidefence on' and '🎓 Agentic QE <patterns>[·traj][·vec]·<size>'. Append-only (never rewrites ruflo's lines), upgrade-safe (strips legacy or BEGIN/END block then re-injects), fs-only + one guarded sqlite3 for the QE line. Adds ruflo-resync: one command to re-apply everything an upgrade wipes (enable-learning + agentic-qe native repair + statusline). Extracts shared _ruflo_aqe_ensure_native helper. Verified live: 5/5 learning, footer renders.
…README, troubleshooting)
…write README
- Security: the agentic-qe footer's sqlite3 read now uses execFileSync('sqlite3',
[db, sql]) instead of a shell-interpolated execSync, so the cwd-derived db path is
never shell-evaluated (flagged by automated review, MEDIUM command-injection).
- README: full rewrite — decomposed, dual-audience (developer + non-technical),
emoji section headers, friendly tone, and citations to ruflo, agentic-qe,
Ciprian Melian's gist, ruflo#2219, better-sqlite3, and Claude Code.
uninstall.sh removed only the two original bins, leaving ruflo-enable-learning, ruflo-learning-verify, and ruflo-security-verify behind. Both scripts now iterate bin/* so they stay in sync automatically as bins are added/removed. uninstall.sh header also clarifies that sourced functions (ruflo-resync/-setup-aqe) go away with the rc source line, and that per-project artifacts are intentionally left untouched.
…s in the current repo Strips the activation footer (ruflo-seg block), the console.log wrap, and the version-probe injection, restoring ruflo's native render. Backs up first; leaves the statusline file and all ruflo/agentic-qe data intact (points to 'ruflo cleanup --force' for data). Flag parsing now loops so --dry-run and --this-project compose.
…hijacks it) aqe init repoints .claude/settings.json statusLine.command at its minimal statusline-v3.cjs, hiding the activation footer even though statusline.cjs is patched. ruflo-fix-statusline-version now also makes statusline.cjs primary (idempotent; falls back to v3 then a literal), so ruflo-resync self-heals it. Adds a TROUBLESHOOTING entry. Docs accuracy pass: corrected the README status-line mockup (it implied ruflo's native 'DDD Domains' line is relabeled 'Learning' — we don't; the footer is append-only), documented uninstall --this-project, and softened 'reverses everything' claims. Verified all cited specifics (controller-registry.js:313-315/:655, agentdb 3.0.0-alpha.14, security 3.0.0-alpha.8, aidefence 3.0.3).
… + ruflo-neural-train Matches the additional fields in Ciprian's statusline, append-only (no relabeling of ruflo's native lines): - SONA line: volume bar + Δ LoRA (cached) + ⚡HNSW - Agentic QE line: git branch (⎇), icon-tagged 🎓 patterns / 🧭 traj / 🧬 vec⚡ / 💾 size - ruflo-neural-train: wraps 'ruflo neural train' and caches MicroLoRA Delta Norm to .claude-flow/neural/lora-delta.json. Source finding: deltaNorm is a transient last-step metric (ruvector-training.js), not persisted and not derivable from the lora-checkpoint, so capture-at-train is the only faithful way to surface it.
…dy in ruflo header) Also: docs for the enriched footer (SONA bar + Δ LoRA + AQE icons), ruflo-neural-train, and the Δ LoRA source finding (BACKGROUND.md, spec R16/R16a).
…not empty 'vectors' This aqe version stores per-pattern embeddings in qe_pattern_embeddings; the 'vectors' table is empty, so 🧬 vec was omitted. Now tries qe_pattern_embeddings → vectors → embeddings and uses the first with data (robust across aqe schema versions).
…README bullet Updates the 'one guarded sqlite3 read' wording (it's now a few reads) and documents the vec table fallback (qe_pattern_embeddings → vectors → embeddings) in README, the reference block, BACKGROUND.md, and the spec. Also removes a stale duplicate SONA/aidefence/Agentic-QE bullet block left in the README status-line section.
10 tasks
7 tasks
pacphi
added a commit
that referenced
this pull request
Aug 7, 2026
…reclaimables
The collectors from the previous run were built and tested but never called —
index.mjs had no owner, so nothing composed them into the payload. Wiring them
up is most of this change; the rest is what wiring them exposed.
Now live (verified against GET /api/system, not asserted):
- projects: 50 ever seen / 25 on disk / 21 git repos, de-duped across hosts by
resolved real path. Was 4, because discovery reused discoverRuvfloProjects()
— which requires .claude-flow/neural/ state and answers a different question
that Intelligence still depends on, so it is left alone.
- consumers: 80 roots, top 20 ranked, grouped by ecosystem. The panel called
npx cache the #1 consumer at 6 GB; it is #12. The real leaders were entirely
unscanned — Ollama 141 GB, LM Studio 49 GB, Hugging Face 36 GB, npm's
_cacache 22 GB. Overlapping roots collapse by path so the list cannot go
self-similar (~/.npm never appears beside its own _cacache).
- snapshot persists consumers; runtime is still absent, and that allow-list is
what structurally enforces the ephemeral-census invariant.
- refreshDeep finally takes includeProjectTrees, which the caller was already
passing into a zero-arity function. Sticky across rescans; default off,
because one repository here is 175 GB and flattens every other row.
A HARD HANG, found only because the wider discovery reached it: the deep scan
parked forever at 0% CPU inside a Dropbox tree. Cloud providers leave evicted
placeholders — stat returns instantly, read blocks in the kernel until the
provider materializes the bytes, which never happens while it is signed out.
There is no timeout, so the scan never completed and the dashboard served a
17-hour-old snapshot. Worse, reading them silently pulls the file down. Guarded
by the allocated-blocks basis the DDD already names (blocks === 0 && size > 0):
placeholders are stat-ed, never opened. Zero false positives across 3,667 files
here, including sub-2KB files that APFS stores inline. The tree that hung
forever now returns in 50ms; a full scan takes 204s.
Also: Playwright was invisible on macOS (the scan looked only at the XDG and
Windows paths, so 1.86 GB read as a measured zero), and the RuvNet Brain was
under-reported by 85% because only kb/ was measured — the other 11 GB is five
dated kb.bak snapshots, now broken out rather than silently folded in.
Reclaimables grew from two detectors to cover those backups, npm's regenerable
cache, orphaned transcripts, and browser downloads — split into two safety
tiers that never sum: 'regenerable' for caches a tool refetches on demand, and
'review' for things like mise's 8 node versions, where recommending deletion of
a live runtime would be worse than saying nothing.
pacphi
added a commit
that referenced
this pull request
Aug 8, 2026
…rent model catalog (#123) * fix(usage): catch the "<synthetic>" placeholder even without isApiErrorMessage Some builds emit Claude Code's dropped-connection placeholder turn (model: "<synthetic>", zero usage) without setting isApiErrorMessage, so it slipped past the exception filter and surfaced as a real \$0 "model in play" on the scorecard. The literal model marker is now checked alongside the flag, SCHEMA_VERSION bumps to 9 so cached sessions re-derive, and a regression test covers the flagless shape. Also re-anchors the usage-doc file:line citations shifted by this file's line movement (doc-citations gate). * feat(dashboard): date-windowed Observability History browsing Observability's History scope was limited to the live tailer's moving window (256 newest transcript files, 100-session projection). It now browses retained sessions over an explicit calendar window — 1d, 7d, 14d (default), 1mo, 3mo, 6mo, 1y, all — like Usage's day chips. - discoverJsonl() gains an optional sinceMs mtime cutoff - LiveSessionsService.historySnapshot({sinceMs}): a one-shot scan with its own projection (never touches live tailer state), swept with all-zero windows so unterminated sessions read as stale, not live - GET /api/live/history?window=<token>, same publicLivePayload scrubbing as /api/live; 501 when the service lacks historySnapshot - window chips in the History sub-nav; History renders from a separate state.historySnapshot bucket so live SSE deltas can never clobber it The UI harness also allowlists /api/live/intelligence EventSource teardown aborts (pre-existing flake — Chromium reports deliberate stream closes as ERR_ABORTED, same as the existing events/transcripts entries) and gives LIVE_STUB a historySnapshot. * docs: propose the System area — ADR-0025, machine-footprint domain, design mock Drafts for review; nothing implemented yet. - ADR-0025 (Proposed): a Machine footprint bounded context and a fourth System primary area (Summary / Storage / Runtime / Catalog / Projects), tiered honest collection with a persisted asOf snapshot, the initial metric taxonomy (install, runtime, storage, catalog, per-project LOC/disk, git-remote links), GET /api/system + ak footprint delivery, advisory-only reclaimables, and a documented absolute-path exception - docs/ddd/machine-footprint.md: purpose, boundaries against Usage / Observability / Project intelligence / Integration management, the FootprintSnapshot model, measurement semantics, 12 invariants, and proposed ubiquitous-language terms - docs/assets/system-tab-mock.html: self-contained both-theme mock of the System area on the dashboard's own tokens, every card annotated with its chart-form rationale; illustrative data only * docs: propose the About area — ADR-0026, component-directory domain, design mock Drafts for review; nothing implemented yet. - ADR-0026 (Proposed): a leftmost About primary area introducing every component ak installs or configures — curated editorial copy joined with existing detection facts (no new endpoint, no probing), a registry↔directory parity gate so a managed tool cannot ship without its About card, official host marks + honest monogram tiles, and outbound user-initiated links inside the zero-egress contract - docs/ddd/component-directory.md: the editorial/detection split as the load-bearing boundary, the new-user register contract (~50-word plain-language paragraphs, no runtime claims in prose), 10 invariants, and proposed ubiquitous-language terms - docs/assets/about-tab-mock.html: self-contained both-theme mock on the dashboard's own tokens — hero orientation strip with a how-it-fits map, category card grid (hosts first, honest not-installed state shown, configured surfaces with manage: commands), per-section design notes, and an annotated card anatomy * feat(about): component directory, ak about, and a registry parity gate Implements ADR-0026. A curated directory module carries each component's editorial identity — tagline, one plain-language paragraph, source/npm/docs links, icon, category — rewritten from verified upstream sources rather than assumption. It collects nothing: state chips join client-side against the status payload the dashboard already polls, so prose never claims runtime state and a failed join degrades chips to unknown while content still renders. The parity test is the point: every managed tool must have exactly one entry and no entry may exist for something ak neither installs nor configures, so a future tool cannot ship without its card. * feat(system): machine-footprint collectors incl. first-class Windows census Implements ADR-0025's collectors: a bounded walker (never follows symlinks, one bad subtree degrades to unknown), install/storage/catalog/projects, and an ephemeral runtime census. Unknown is never rendered as zero — every figure carries measured/partial/unknown provenance, and lower bounds print as ">= N". Windows is real rather than unsupported. A shipped PowerShell script gives the guaranteed floor via Get-CimInstance (pid/ppid/CPU/RSS/uptime, argv deliberately excluded), and a best-effort inline P/Invoke walks NtQueryInformationProcess -> PEB -> RTL_USER_PROCESS_PARAMETERS for true cwd. A bitness mismatch is detected rather than read through with wrong offsets, and any probe failure degrades to an honest "not attributable" — never a fabricated path, and never taking the census down with it. No dependency added; package.json still declares none. The script lives beside its consumer under src/ because `files` ships src/ wholesale — under scripts/ it would never have reached an npm-installed Windows user at all. A guard test asserts that placement on every platform, and three live tests execute the real PowerShell on windows-latest, checking the census and the PEB walk against this process's own pid and cwd. * feat(system): GET /api/system, snapshot persistence, and the ak system CLI Cheap tier (census + known-file stats + snapshot carry-forward, TTL-cached) on every read; deep tier explicit and single-flight so concurrent refreshes attach to the in-flight scan. The deep result persists with an asOf; a missing or corrupt snapshot reads as "never measured", never as zeros. Rescan is manual only — nothing scans on dashboard open. The payload deliberately carries absolute paths, unlike /api/live's leaf-only reduction, because in this domain the path is the answer; file contents are never read, so nothing sensitive can travel with them. dashboard.test.cjs's self-contained assertion is replaced with a shared assertSelfContained() helper. The old regex conflated "no external fetch" with "no https string" and so failed on About's curated link pills; the replacement pins the invariant to the directory itself — every external URL must be one about-directory.mjs declares — and still bans external script/stylesheet/img. That is strictly stronger, and the browser suite independently asserts the run requests nothing off the loopback origin. * feat(dashboard): About and System primary areas About lands leftmost as the reading-order entry point; Overview remains the default landing view, with a dismissible first-run nudge rather than a hijacked view. System adds Summary/Storage/Runtime/Catalog/Projects with the charted treatments from the design mock, a freshness label that nudges once a snapshot goes stale, and honest empty states — "not measured yet" is never rendered as a zero. This makes five primary areas. ADR-0005's "exactly three stable primary areas" assertion is updated to state the new contract, which both ADR-0025 and ADR-0026 record as a deliberate amendment. * docs: mark ADR-0025/0026 implemented and document About and System Both ADRs flip Proposed -> Implemented, and their "open points for review" sections are rewritten as resolved decisions with rationale: tab and CLI both "System" (ak system), Projects stays its own sub-view, manual-only rescan with a staleness nudge, Windows gets a guaranteed census plus best-effort P/Invoke cwd, About gets a dismissible nudge, ak about ships, six configured cards. Both bounded contexts join the context map and ubiquitous language, the ADR index gains rows and narrative, and DASHBOARD.md documents the two new areas for users — including how to read ">= N" lower bounds, "not measured yet", approximate LOC, and the platform differences. README's command list gains ak about and ak system. The DDD docs lose their draft banners and record what shipped rather than what was planned, including limits stated honestly: daemon budget state, the statusline two-row join, and unattributable Codex bytes. * fix(dashboard): strip mock annotations, report every version, unbreak Windows CI Three fixes from reviewing the running dashboard. Mock leakage. The design mocks were written as reviewer-facing artifacts with their rationale inline, then handed to the UI agent as the visual spec, so it faithfully reproduced the annotations along with the design. Removed 4 About "Design note" blocks, 11 System "Why …" blocks, the "Two bars, two colour jobs" note, all 13 chart-type pills ("radial gauge", "ranked bars", …), the About footer, and the page-wide read-only footer, plus the CSS those left orphaned. Kept every genuine data caveat — "line counts are approximate", "durations are session span", "secrets masked server-side" — because those qualify the numbers rather than explaining the design. Also dropped the hero's installed tally, which restated each card's own chip less precisely. Version chips: 6 of 9 components reported a version, for three different reasons. driftReport() walks npm globals only, so a host installed by mise/brew/native (Claude Code) was invisible to it; agentdb is a real global but pinned to ruflo's bundled version, so it is deliberately excluded from the update banner and fell out of the array with it; aidefence ships inside ruflo and has no global install at all. foldKnownVersions() adds all three from structured probes — never scraped from a status row's prose — as outdated:false so noticeHtml (which filters on outdated) still shows an empty banner. The host probe costs ~300ms, so it sits behind a 5-minute in-process TTL rather than riding every 30s poll. Now 9 of 9. Windows CI: the new absolute-paths assertion compared a fixture path against the raw JSON body. A Windows path carries backslashes, which JSON escapes on the wire, so the check passed on POSIX and failed on all three windows-latest legs for a payload that was correct — the sibling assertions that compare parsed values were green throughout. Compares the encoded form now. * fix(dashboard): correct foldKnownVersions' JSDoc so tsc --checkJs passes The CI quality gate went red on the previous commit: the param was annotated Array<{pkg:string}> while the function pushes {pkg, installed, latest, outdated}, so tsc rejected the object literal. The annotation was simply narrower than the array driftReport() and the selfDrift/brain/ruvector folds have always produced. The return type keeps those fields OPTIONAL rather than required, because incoming entries are passed through untouched — promising them as present would be a second wrong annotation in the other direction. * test(ui): defuse the dated fixture corpus, and match the hero's new contract The UI suite went from 241/0 to 183/25 with no code change between the runs. Cause: the fixture corpus is pinned to 2026-07-24 and the panel requests a 14-day window, so at 00:00 on 2026-08-07 the whole corpus aged out of its own window. Proof, straight from the index: days=14 -> 0 sessions, days=30 -> 3. One data-fixture check failed and 23 session-view assertions cascaded off it. The kit suites avoid this by pinning `now` (usage-index.test.mjs says so in its header), but this harness drives a REAL server against the real clock, so it cannot. extendedCorpus() already copies the fixtures into a temp dir, so the copy is shifted forward instead — by a WHOLE number of days, which preserves every relative fact the assertions rest on: the 85-minute idle gap separating the three time tiers, the worktree session nested inside another's span, and each turn's local time-of-day for the punchcard's hour buckets. The checked-in fixtures keep their literal dates, because the kit suites pin `now` against exactly those. The remaining failure was a real contract change, not a bomb: the hero no longer counts detections, so asserting it says "unknown" tested behaviour that was deliberately removed. It now asserts the actual contract — the hero states only what ak MANAGES, making no detection claim in either direction, because each card's own chip already carries per-component state and an aggregate could only restate it less precisely. * feat(system): wire the footprint collectors, fix a hard hang, expand reclaimables The collectors from the previous run were built and tested but never called — index.mjs had no owner, so nothing composed them into the payload. Wiring them up is most of this change; the rest is what wiring them exposed. Now live (verified against GET /api/system, not asserted): - projects: 50 ever seen / 25 on disk / 21 git repos, de-duped across hosts by resolved real path. Was 4, because discovery reused discoverRuvfloProjects() — which requires .claude-flow/neural/ state and answers a different question that Intelligence still depends on, so it is left alone. - consumers: 80 roots, top 20 ranked, grouped by ecosystem. The panel called npx cache the #1 consumer at 6 GB; it is #12. The real leaders were entirely unscanned — Ollama 141 GB, LM Studio 49 GB, Hugging Face 36 GB, npm's _cacache 22 GB. Overlapping roots collapse by path so the list cannot go self-similar (~/.npm never appears beside its own _cacache). - snapshot persists consumers; runtime is still absent, and that allow-list is what structurally enforces the ephemeral-census invariant. - refreshDeep finally takes includeProjectTrees, which the caller was already passing into a zero-arity function. Sticky across rescans; default off, because one repository here is 175 GB and flattens every other row. A HARD HANG, found only because the wider discovery reached it: the deep scan parked forever at 0% CPU inside a Dropbox tree. Cloud providers leave evicted placeholders — stat returns instantly, read blocks in the kernel until the provider materializes the bytes, which never happens while it is signed out. There is no timeout, so the scan never completed and the dashboard served a 17-hour-old snapshot. Worse, reading them silently pulls the file down. Guarded by the allocated-blocks basis the DDD already names (blocks === 0 && size > 0): placeholders are stat-ed, never opened. Zero false positives across 3,667 files here, including sub-2KB files that APFS stores inline. The tree that hung forever now returns in 50ms; a full scan takes 204s. Also: Playwright was invisible on macOS (the scan looked only at the XDG and Windows paths, so 1.86 GB read as a measured zero), and the RuvNet Brain was under-reported by 85% because only kb/ was measured — the other 11 GB is five dated kb.bak snapshots, now broken out rather than silently folded in. Reclaimables grew from two detectors to cover those backups, npm's regenerable cache, orphaned transcripts, and browser downloads — split into two safety tiers that never sum: 'regenerable' for caches a tool refetches on demand, and 'review' for things like mise's 8 node versions, where recommending deletion of a live runtime would be worse than saying nothing. * feat(dashboard): one project census, current models, reworked System area Addresses a review of the local diagnostic panel. Four themes. Project counting. Overview/Usage/Observability/System each discovered projects their own way and reported four different numbers for the same machine (4, 14, another 14, ~48). ADR-0027 makes discoverProjectSources() the single census with four named scopes, and no surface may render a project count without the sentence explaining what it counted. The Intelligence panel now asks whether memory/intelligence has been ACTIVATED (.claude-flow, .agentic-qe or .swarm, any host) rather than whether ruflo has trained: 4 projects becomes 17. The learning scope folds a repo's sub-directories and agent worktrees onto one identity — without that, keying the picker off identity left 7 of 24 rows unreachable. Retires project-discovery.mjs; registryWorkspaces() is module-private again. Models. gpt-5.4/gpt-5.4-mini retire from Codex on 2026-08-31 and gpt-5.3-codex is already withdrawn, so execution routes to gpt-5.6-terra and mechanical work to gpt-5.6-luna. RETIRED_MODELS substitutes a withdrawn model at the read boundary — the one place a user pin is overridden, because honoring a pin into a dead model fails the run — and ak sync rewrites seeded routes naming one. claude-opus-4-8 is deliberately NOT listed: it carries no deprecation notice, so it is divergence, not retirement. System area. Seven sub-views: Advisory and Sessions split out of Storage, Advisory because it is the only part of System that suggests an action. Storage lifts learning stores (99% of retained bytes) onto their own card so the donut is legible, restricts the per-host split to real hosts, and gives growth five axed sparklines. Catalog covers project scope across every project on disk and gains kind/host filters. Projects lists only repositories with a remote that a host has recorded a session in. Honest degradation. Removes the AI-worker budget tile: no code path could ever populate it, so it was a permanent "unavailable" rather than a degradation. ADR-0023 gains §9 (a permanently unmeasurable quantity is deleted, not degraded) and §10 (an excluded figure is still stated). Also fixes eslint linting gitignored .ui-artifacts/, which failed `pnpm run check` for anyone who had run `pnpm test:ui`. * fix(dashboard): loosen the System layout and plainen the Advisory copy Follow-up to fdd7052, all presentation — no collector or payload change. Advisory copy. "Two safety tiers that are reported separately and never added" stated an accounting rule before the reader had met the tiers, so they now introduce themselves by what they mean to you. On the card, "no total — pointers, not a sum" told you a number was MISSING without saying why you should be glad it is; it now reads as a deliberate choice. Catalog KPI. "232" and "skills" were on separate lines, so the tile's one fact read as two. "skills" moves up beside the number as a unit — the same treatment GB already gets on the byte tiles — at 12px, so it does not inherit the 27px odometer size. Summary band. A margin-top:-5px was actively pulling the projects liner up into the KPI cards (5px of separation), and the disk strip carried 5px of padding around a 12px meter. Now 16px, 18px and 13px. That liner is also capped at 120ch: two lines of dense accounting prose run the full 1440px, well past a readable measure. Largest consumers. 110px showed about four rows, too few to read as a ranking — you compared the top of the list against nothing. Sized for the denser by-ecosystem mode where every row also carries a note: 9 full rows there, 13 ranked. Reclaimable rows. The removal hint was a bare .why div flush against the rationale, reading as its next clause. Its own class now, with real separation and the command in high-contrast mono, so the thing you could run is distinct from the thing that was found. * feat(dashboard): sortable headers on the System project footprints table Every column sorts, one at a time, announced through aria-sort — exactly one <th> is ever anything but "none". Clicking the active column reverses it; clicking another takes over. Opens on project name, ascending. First click uses each column's NATURAL direction rather than always ascending: nobody opens a size column wanting the smallest project first, or a recency column wanting the stalest. Project and language open ascending; lines, disk and last-active open descending. An unmeasured figure sorts LAST in both directions. These cells are Measurement wrappers, so a row can carry `unknown` rather than a number, and letting it rank would present an absent figure as a small one — the same rule ADR-0023 applies everywhere else in the System area. A deliberately unmeasured fixture row pins it. The whole header is the button, so the target is the column's width rather than a glyph, and the arrows always render: a control that only appears on hover is invisible to anyone who never hovers, and a column that changes width when pointed at is worse than no affordance. * fix(test): canonicalise fixture roots the way the collectors do, and stop asserting a POSIX-only encoding on Windows Windows CI has failed since 2387a35 with 13 failures across three footprint test files. Two distinct causes, both in the harness rather than the product. realpathSync vs realpathSync.native. The fixtures canonicalised with the JS realpath; every collector canonicalises with the native one. On POSIX these agree, so it passed everywhere else. On Windows the JS realpath leaves an 8.3 short name alone (C:\Users\RUNNER~1\...) while the native one resolves it to the long form the code under test returns (C:\Users\runneradmin\...) — the same directory in two spellings, compared against each other. Fixtures now use the native variant with the same `?? realpathSync` fallback the product uses. footprint-collectors did not canonicalise at all. A POSIX-rooted encoding asserted on Windows. Two tests build a Claude transcript-directory name with `path.sep`, which yields `-a-b-c` on POSIX and `C:-Users-...` on Windows. decodeClaudeProjectDir documents a drive prefix as undecodable and returns null, so those tests were asserting the platform, not the decoder. They are POSIX-only now, and a new test asserts the refusal contract — including the Windows shape — on every platform. Also fixes a real over-claim this surfaced. Because nothing decodes on Windows, System > Sessions would have labelled EVERY row "deleted project" there. labelSessions now reports which reason applies — `gone` for a POSIX-rooted name that no longer resolves, `encoding` for a name that was never decodable — and the panel says "name not decodable" rather than asserting a deletion that did not happen. * fix(footprint): the cloud-placeholder rule condemned every file on Windows `fs.Stats.blocks` is a POSIX field. On win32 Node reports it as 0 for every file, and `isCloudPlaceholder` read that as "a provider has evicted this" — so every file was treated as dataless. Manifests were never queued, every source file was skipped, and a scan returned no lines, no dependencies and no stack. Eight of the thirteen Windows CI failures were this one gate; two independent symptoms pinned it (`manifestsRead` 0 where 2 were present, and a skip count one higher than the fixture's only binary file). win32 is excluded from the heuristic. The cost is real and stated in the code: Windows is where OneDrive Files On-Demand actually lives, so it is the platform that most needs this check and the one platform that cannot have it — detecting a placeholder there means FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS, which fs.Stats does not surface. A Windows placeholder is therefore opened and may block, exactly as before the heuristic existed. Reading one file slowly is recoverable; measuring nothing at all is not. `platform` is a parameter rather than a direct process.platform read, and the predicate is exported, so the win32 branch is testable from any machine. The end-to-end test shims lstatSync to report zero blocks — without that it would pass with or without the fix, since real POSIX files have blocks — and an anti-vacuity test asserts the damage is still reproducible on POSIX, so the guard cannot quietly stop being load-bearing. * fix(footprint): the same POSIX-blocks assumption in two more places The previous commit fixed the cloud-placeholder rule in stack-detect but missed a hand-rolled DUPLICATE of it in projectRemote, which is why Windows CI went 13 → 8 → 2 rather than to zero. Both remaining failures were that copy: `st.blocks === 0 && st.size > 0` on a .git/config, which on win32 is true for every file, so every project reported status 'unknown' with no remote URL. It now calls the shared exported predicate instead of restating the rule — the duplication is what let the first fix look complete. A third instance was latent rather than failing. measureAllocated derives allocated bytes from per-file `blocks` and falls back to apparent size only when the count is not finite. On win32 the count IS finite — it is 0 — so an entire machine's allocated total came out as zero bytes beside a correct apparent total. Its own doc comment already promised the Windows fallback this commit actually implements. No test covered it, so CI was green on a figure that would have been wrong on every Windows machine. Each fix carries an anti-vacuity assertion: the identical zero-blocks input on POSIX must still be read as a placeholder, and must still allocate zero. Without those the platform argument could stop being load-bearing and the tests would keep passing.
This was referenced Aug 18, 2026
pacphi
added a commit
that referenced
this pull request
Aug 27, 2026
…lication bugs (#184) * test+ci+lint: complexity-program P0 safety nets 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 * chore: sync worktree to refactor/complexity-program P0 safety nets 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. * fix(live): recognize newer item_completed Codex message generation 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. * fix(live): reuse event-schema's inferredSignal instead of a duplicate 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. * refactor(status): extract host-detail rendering to its own module 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. * fix(providers): heal retired routes from ak host pick and ak setup 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. * refactor: decompose rufloActivationSegments into per-segment functions 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. * refactor(status): split collectDejaVuRows into per-concern functions 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. * refactor(usage): share blankSession/addUsage between transcript sources 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). * refactor(providers): extract convergeProviderStack shared pipeline 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. * refactor(usage): decompose detectInsights into 13 independent detectors 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. * refactor(status): generalize the section-registry pattern to all of collect() 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. * refactor(dashboard): split the 15-route request handler into a route 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. * docs: archive PR-131 consistency dossier and issue-110 swarm prompt 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 * refactor(providers): applyAqeRouter as ordered surface reconcilers 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. * refactor: extract loopback-server.mjs for shared dashboard/admin plumbing 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. * refactor(sync): ordered step registry replaces inline subsystem branches 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). * refactor(telemetry): share wire-record decode between batch scan and 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. * refactor(setup): decompose run/run_machine/run_project into named steps 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. * refactor(usage): extract scan()'s per-source concerns into named functions 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. * refactor(live): decompose reduceLiveEvent into phase functions 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). * refactor(host): split pick() into parse/decide/apply stages 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). * docs(providers): note pick/setup also heal retired routes 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. * fix(live): recognize item_completed in the Codex content-plane adapter 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). * refactor(providers): share the routing-retirement report line, trim setup'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. * refactor(dashboard): split client.mjs's 4,066-line template literal into 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. * refactor(dashboard): split styles.mjs's 1,309-line stylesheet per area 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>`). * docs(adr): add ADR-0036 for the dashboard client/loopback-server refactor 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. * docs: resolve ADR-pending references now that ADR-0036 is written 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. * fix(providers): kill the status/writer drift-comparator duplication (#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. * docs(adr): ADR-0037 — complexity program structural patterns and gates 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 * docs: archive the adapter-contract dossier and host-extensibility explainer 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 * test: fix Windows shim spawning and expected intelligence-503 in CI 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 * fix(dashboard): tolerate CRLF checkouts in the client bundle import-strip 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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Extends the machine kit beyond memory persistence to activate and verify the features that silently stay dormant on Node 24/26, and adds a one-command re-apply for upgrades.
ruflo-enable-learning(native SQLite + 5 real capability probes) andruflo-learning-verify(train → patterns persist 0→N). Verified live: 5/5 active, 0→7 patterns.ruflo-security-verifyconfirms@claude-flow/security+@claude-flow/aidefenceload, prompt-injection defense fires (exit-code based, robust to an upstream render crash), and flags the CVE-DB gap.ruflo-setup-aqe. New finding: agentic-qe carries the same Node-≥24 native-SQLite bug as ruflo (aqe initfails at persistence-db init); the helper repairs it first, then handles half-init.🧠 SONA … 🛡 aidefence on/🎓 Agentic QE …. Renders only what's active.ruflo-resync— one command to re-apply everything an upgrade wipes.Corrected diagnosis
The colleague gist's
controller-registry.jspatches are already upstream in ruflo 3.10.5 (agentdb v3, ESM fix, ReasoningBank embedder). The real lever is the missing native better-sqlite3 binary; "HNSW: Not loaded" inneural statusis a cosmetic lazy-display, not real dormancy. Full story indocs/BACKGROUND.md. Prior art credited: Ciprian Melian's gist + the agentic-qe repo.Test plan
All verified live on ruflo 3.10.5 / Node 26 (darwin-arm64):
ruflo-enable-learning→ 5/5 capability probes greenruflo-learning-verify→ patterns 0→7, 50 learned, 55 trajectories on diskruflo-security-verify→ security + aidefence load, injection flagged, scan/secrets runruflo-setup-aqe→ 118 skills, both markers, idempotentruflo-resync→ full chain greenbash -nclean on all shell artifacts;--helpon all binsexecFileSync(no shell)Docs
Design spec + implementation plan under
docs/superpowers/;docs/BACKGROUND.md,docs/TROUBLESHOOTING.md, the machine-wide CLAUDE.md reference, and a fully rewritten README all updated.🤖 Generated with Claude Code