feat(statusline): show Agentic QE version + regroup footer divider - #6
Merged
Merged
Conversation
…line The 🎓 Agentic QE footer line now renders the installed agentic-qe package version (e.g. "🎓 Agentic QE V3.10.1"), mirroring how ruflo's native header shows "RuFlo V<x>". Version is read from agentic-qe/package.json — preferring the global install (same path style as the aidefence probe) and falling back to a project-local node_modules copy. Guarded by try/catch so a missing package.json leaves the line unchanged. Updated the footer-format comment and README example/description to match.
Previously a 44-char rule sat above the SONA line, separating ruflo's native
AgentDB line from the kit's footer. SONA and aidefence are ruflo features, so
that rule split the ruflo block awkwardly.
Now: no rule above SONA (it sits flush under ruflo's native lines, keeping all
ruflo features/metrics together), and the divider is drawn BETWEEN the SONA/
aidefence line and the Agentic QE line. Widened 44->53 to match ruflo's native
header divider ('─'.repeat(53)), so the two rules line up.
The divider only renders when both halves are present.
10 tasks
pacphi
added a commit
that referenced
this pull request
Sep 27, 2026
…eam watch) (#241) * docs(audits): record verification and decisions for #237, #238 and #239 Adversarial verification of the reported sync, dashboard and tracker claims against 847486c, the maintainer's decisions with the options offered, the AgentDB and Ruflo memory alignment addendum, and the remediation plan those decisions authorize. * docs(audits): record the memory-location and install-transparency addendum Three problems the upstream-reporting track returned as agentic-kit's own: provider registration relocating Ruflo's memory root, Codex's Ruflo launcher writing stores into system and tool folders, and the native-binding repair editing Ruflo's install without a receipt. Records each decision with the options offered and adds Stage 5 to the plan. * test(telemetry): keep the hermetic export from reading real host sessions The hermetic `ak telemetry export` test pinned HOME, XDG_CONFIG_HOME and XDG_STATE_HOME but inherited XDG_DATA_HOME and XDG_CACHE_HOME from the developer shell. OpenCode keeps its store at $XDG_DATA_HOME/opencode/ opencode.db (usage-opencode.defaultOpencodeDbPath), so on a machine that exports XDG_DATA_HOME the export read the developer's real OpenCode session and the test saw 1 session instead of 0. Pinning XDG_DATA_HOME alone takes the leak from 1 to 0; running with every XDG variable unset passes on main. The product is right to follow XDG_DATA_HOME, as OpenCode does. The gap was the test sandbox, so no product code changes and Ruflo is not involved. The test now plants a one-session OpenCode store under a hostile XDG_DATA_HOME, so it fails on every machine without the fix (not only on shells that export the variable), and the sandbox pins every XDG base and drops CODEX_HOME/HERMES_HOME, as sandboxHome() already does. (cherry picked from commit b9452a6c6ec7db24c67f2082d737da03aad657c0) * test(isolation): run lifecycle sync and setup tests in a sandbox project The baseline `pnpm test` rewrote the real repository's .claude/helpers/statusline.cjs to the fixture's ruflo 9.9.9 and released the real project's CLAUDE_FLOW_DB_PATH pin (leaving .ak-*-backup files). HOME was sandboxed, but external-lifecycle's sync.run, setup.run_machine and uninstall.run write relative to process.cwd(), which was the repository root. uninstall-command and deja-vu-teardown-verify had the same exposure: releaseRufloComponents adds rufloProjectRoot(cwd) to its release set, and stepThisProject rewrites the cwd project's statusline. isolateProject() moves each command-driving test file into a throwaway git project and registers a tripwire that fails the file if the real repository's guarded project files (settings.local.json, settings.json, statusline.cjs, the helper stamp, llm-config.json, CLAUDE.md, AGENTS.md) or their .ak-*/.agentic-kit-* siblings change. The #137 provider-cli tripwire now uses the same guard. A census test fails when a test file calls sync/setup/uninstall .run* without isolateProject(). (cherry picked from commit fe48cf1a3f1695cc9b7e17defcc5a4f222e7d1b5) * fix(statusline): stop overwriting Ruflo's baked version fixStatusline replaced the helper's baked `let ver` with the installed ruflo version (a leftover from the v4 port). Ruflo bakes that value as a floor and shows the highest version it finds at render time, so a value ak wrote too high never corrects itself: a test fixture's fake ruflo 9.9.9 reached a real project and its statusline read "RuFlo V9.9.9". ak now leaves the baked version exactly as Ruflo wrote it and only strips its own legacy probe marker and (re)injects the footer. The setup and sync lines drop the version they no longer write. (cherry picked from commit 8d9003c088bd2e7c81bccb82fd552f0bf5cd05ee) * feat(status): flag a statusline that shows a different Ruflo version than installed Ruflo's helper bakes `let ver` as a floor and renders the highest version it finds, so a baked value above every install (the leaked 9.9.9) pins the statusline to a Ruflo version that is not installed and never corrects itself. `ak status` and the drift nudge now compare the helper's baked version with the installed ruflo/@claude-flow/cli and warn when it is higher. The sync statusline step repairs it through Ruflo's own refresh: it clears the helper stamp so the forward-only refresh regenerates the helper with Ruflo's baked value, then re-injects the footer. ak never writes a version. A refresh that does not lower the version gets its stamp back and sync warns with the manual edit; when the refresh cannot run at all (.LOCKED, RUFLO_HELPERS_LOCKED, no hook-handler, no module) the status row names the manual edit and plans no sync fix. A higher runtime candidate (e.g. a newer marketplace checkout) is Ruflo's own choice and is not flagged. (cherry picked from commit 33bc698b2b91bb607039e34bb39a70e24ae732dd) * fix(blocks): read guidance drift from the writer's dry-run in status and nudge `ak status` and the post-command nudge rebuilt the guidance reconcile loop with only {dualMode, opencodeEnabled}, so every `enabled` detector fell back to a PATH, directory, or always probe, and retired rows were force-stripped without the known-target universe. Both readers reported drift in either direction that `ak sync` (which applies kit.json intent) would never act on, for example "ruflo-aqe-reference→stripp" on a Claude-only machine with AQE managed but not on PATH (#237). reconcileGuidance now returns each target's raw per-row results, and the status blocks section and localDrift render from its dry run with sync's exact context. The drift label prints the writer's action verbatim, which fixes "stripped" rendering as "stripp". The golden status fixture now shows the AQE reference sync would upsert. ADR-0008 and UPGRADING note the shared source. (cherry picked from commit ee35f6e6a7707797c804449e1d96c8962baeef8f) * fix(mcp): share one legacy-ruflo ownership predicate between status and register Status classified every user-scope `ruflo` MCP entry as auto-migratable and promised "sync migrates it", while register() removes only the exact registration agentic-kit wrote (`ruflo mcp start`, env limited to AGENT_BROWSER_CONFIG) and silently kept every other shape. Sync then printed a success line and the legacy entry stayed, with the promise repeated on the next status (#237). legacyRufloDisposition(entry) is now the one predicate both use. register() returns { ok, preserved[] } (never env values); the sync mcp step, setup, and `ak x mcp` name each preserved entry with its manual `claude mcp remove ruflo -s <scope>` command. The status row for preserved scopes carries that command in its message and no fix, so sync never plans work it will not do. ADR-0016 and TROUBLESHOOTING describe the boundary. (cherry picked from commit 350b6a810e5a693ffc689448e38f691182688b7d) * fix(codex-mcp): honor aqe:false in the Codex MCP topology row The Codex MCP topology rows ignored kit.json, so a machine that opted out of AQE (`aqe: false`) with Codex enabled was still warned that agentic-qe is not registered in Codex and told to run `aqe platform setup codex --overwrite --with-ruflo` for a tool it declined. topologyRows now receives cfg and checks the Agentic-QE registration only while AQE is managed, matching the aqe status section. The recursive-Codex and duplicate-Ruflo checks are unchanged. ADR-0033 records the gate. (cherry picked from commit 0123f857e8c7b718b8d1981dbe474e7b59788cfe) * fix(status): mark manual-only remediation rows so sync never plans them A status row's `fix` meant "sync does this" in some rows and "you must do this" in others (run `ak x verify aqe`, repoint a memory pin, log in, remove a registration agentic-kit does not own). Sync planned every row with a fix, so advisory rows became sync actions no step performed, and sync still reported convergence (#237). Rows now carry a repair contract: row(subsystem, level, message, fix, { repair }) sets repair to 'sync' (default for a fix), 'manual', or null without a fix, and rejects unknown values. Sync plans only 'sync' fixes and counts manual ones instead of printing "all subsystems healthy". Text status prints "→ manual:", the dashboard tags the fix `manual`, and `ak status --json` and /api/status carry the field. Marked manual: the preserved legacy-ruflo MCP scopes (the removal command moves from the message into the fix), the AQE verify hint, memory-pin, host login, model-lifecycle advice, the opencode JSONC/catalog/ledger rows, and the Codex agentic-qe and user-owned codex mcp-server rows. The Codex recursive and duplicate rows are 'sync' only when sync's confirmed repair would clear them (codexMcpRepairOutcome); custom tables are manual. A census test scans every row() call and fails when a subsystem can emit a 'sync' fix that no SYNC_STEPS step handles. It found `statusline/cve` planned with no step; the statusline step, whose fixStatusline injects that overlay, now runs for it. ADR-0023 §11, the ubiquitous language, README, DASHBOARD, UPGRADING, and the status/sync help text describe the contract. (cherry picked from commit fe35bc5171a69b176b6de2338914e49a3b03863c) Integration (I1): tests/kit/status-repair-contract.test.mjs now calls isolateProject() at module scope; lane H's project-isolation census (5618e972) flagged its sync.run calls. The file's own inProject() chdir is unchanged. * refactor(agentdb): retire the standalone agentdb install and harvest's skill step ak installed a second, standalone global agentdb for `ak x harvest`. It was a worse duplicate of the copy Ruflo bundles (it lagged the bundled version and ran on the WebAssembly fallback), its install ran on every sync while harvest stayed off by default (#237 §3, an EEXIST loop when another package owns the bin), and `agentdb skill consolidate` read ./agentdb.db, a store no Ruflo writer uses, so it consolidated nothing. Decision A of the 2026-09-26 audit: ak facilitates and monitors what Ruflo does; it does not run parallel copies. - setup, sync and heal no longer install or repin agentdb; status emits no agentdb row; nothing is uninstalled - a kit.json `agentdb` key is recognized as retired: preserved, ignored, and no unknown-key warning - harvest runs only Ruflo verbs from the project memory root with the project memory pin; `--distill` runs `ruflo memory distill run --db <root>/.swarm/memory.db` - `ak x verify harvest` seeds nothing and isolates CLAUDE_FLOW_DB_PATH, CLAUDE_FLOW_MEMORY_PATH and AGENTDB_PATH inside its temp dir; it fails when ruflo is missing instead of skipping - About (CLI and dashboard version fold) reports Ruflo's bundled agentdb - docs: README, MANAGED-TOOLS, INSTALLATION, TROUBLESHOOTING, MAINTAINER, explainer, UPGRADING entry, ADR-0026 amendment tests/agentdb.test.cjs and tests/harvest.test.cjs are retired (their subjects are gone); tests/kit/agentdb-retirement.test.mjs replaces them. (cherry picked from commit 1f76eaab67dd5293d15bd3d31f2c3a960047a76b) Manifest: package.json scripts.test drops `&& node tests/agentdb.test.cjs` and `&& node tests/harvest.test.cjs` (both files are deleted here). Integration (I1): README status row keeps A1's `→ manual:` wording with the agentdb subsystem removed; UPGRADING keeps both 2026-09-26 entries (this one first); status-golden drops the agentdb row on top of A1's repair keys. * fix(brain): refresh existing installs through the updater; stamp only the observed release Sync refreshed every existing Brain with the fresh-install path plus --force. The installer refuses that for a Brain with private stores, after downloading the whole bundle, so each sync repeated a doomed download (#237 §4/§C). It also stamped the release it asked for, not the one on disk. The heal now chooses the path from disk. When the KB ships its own updater (kb/forge-update.mjs, the installer's precondition for --update) it runs `npx -y ruvnet-brain@latest --update --no-nightly-prompt --no-telemetry` with RUVNET_BRAIN_NO_UPDATE_FALLBACK=1, because the installer's fallback is a fresh --force install that drops ak's opt-out flags. A present bundle without the updater keeps the pinned --force reinstall; nothing installed gets a pinned fresh install. `--update` ignores --version, so every path stamps only the release it then reads from SOURCE.json; an update that exits 0 with the release unchanged is degraded and stamps nothing. Sync no longer passes {force:true}. Installer failures lose ANSI codes and keep the installer's remediation hint. (cherry picked from commit 881ccc98782885bb6877d584c6468960cbfb1936) * fix(brain): hold a refused refresh as blocked until the release pair changes When the Brain installer or the bundle's own updater refuses a refresh (a private-overlay preflight, a stale updater's walker defect) the cause is on the Brain side, yet status kept `fix: 'sync refreshes the KB'`, so every sync ran the refused refresh again and failed the same way (#237 comments, B-deps D3). With commit "refresh existing installs through the updater" alone, the loop would only have changed its message. A deliberate refusal ("install stopped:", "[forge-update] ERROR:", "refusing to update", a missing updater), or an updater run that leaves the installed release unchanged, is recorded under versionCheck.ruvnetBrain.heldRefresh with its cause and the (installed, latest) pair it was refused for. While that exact pair stands, the status row stays a warning with the cause and the user's options and no sync action; either release changing is a new attempt, and a successful install clears the hold. Transient failures (network, timeout) and first installs are never held. Docs: TROUBLESHOOTING row for the held refresh; ADR-0033 update note and decision 8 sentence. (cherry picked from commit a9fdedfcf51d472389c6fcf82802c54d9a7b58ea) Integration (I1): ADR-0033 header conflict with A1 (0123f857) resolved by keeping A1's Updated 2026-09-26 line and its Earlier-update line; this commit's 2026-09-26 update-note paragraph and decision-8 sentence are kept. * fix(natives): keep the native probe cause; separate unavailable from inconclusive The Ruflo memory-runtime load probe reported the last stderr line of an uncaught throw, which is Node's own "Node.js vNN" banner, and status called every failed probe "WASM fallback" with the fix "sync builds the native binding" even when sync's heal would do nothing (a binding file that exists but will not load) or when the probe merely timed out. The probe child now reports a load failure as one tagged JSON line and exit 2, so the probe returns native, unavailable (with the load error, paths reduced to file names before the 160-char cap) or inconclusive (timeout, crash, spawn error). A timeout is detected only through the probe's own abort signal and retried once; run()'s timeout is a backstop behind it. Status emits one row per context that is not native. Unavailable fails and names the cause; the sync fix appears only when the binding file is missing, which is what the heal builds. A present binding that will not load names the manual rebuild with no sync fix. Inconclusive is a warning with no fix, so a slow machine no longer fails sync's convergence proof. (cherry picked from commit bdac1674539398b79c1a2151aeb18ee2a9a73926) Integration (I1): ADR-0023 Updated line conflicted with A1 (fe35bc51, §11 repair contract); resolved as one 2026-09-26 Updated line naming both the §11 contract and this native runtime probe amendment. Both sections are kept. * fix(natives): rebuild a binding that exists but will not load Status load-tests the better-sqlite3 that Ruflo's memory runtime resolves, but sync's natives heal skipped any context whose binding FILE existed. A binding built for another Node ABI (the usual state after a Node major upgrade) or a damaged one therefore failed status on every run while sync reported "already native everywhere" and made no npm call. The heal now uses the same load test for a present binding and rebuilds it only when the probe proves it will not load (unavailable), never on an inconclusive probe, so a slow probe cannot trigger a rebuild in Ruflo's tree. It removes the old file first: prebuild-install extracts over an existing file in place (tar-fs createWriteStream), and a process started before a Node upgrade can still map it. The rebuild counts only when the load test then passes; a rebuilt file that still will not load is a failed heal that names the load error. The status row for this state now carries the sync fix. A missing binding keeps the existing build ladder unchanged. (cherry picked from commit 3290ca64c8fe5ea21990cf086e4b04170c8382a4) Integration (I1): ADR-0023 Updated line conflicted again (A1 §11 + B2 commit 1); resolved as one 2026-09-26 line naming the §11 repair contract, the probe split, and this commit's shared load test in the natives heal. * fix(sync): report promised repairs that did not converge After the apply phase, sync kept only fail-level rows, a deja-vu special case, and recorded apply failures. A planned warn row whose step returned ok while its fix still stood was dropped, so sync printed "converged" and exited 0 with the repair pending (#237 section F). The post-apply verdict now reports as unresolved every planned (subsystem, fix) that is still present after sync re-collects status, and every planned subsystem that no SYNC_STEPS step (or the host-alignment tail) performs. Each prints "unresolved: [subsystem] fix - reason" and sync exits 1. The proof uses the plan's own admission test (a fix that is not manual), so a row cannot enter the plan under one rule and escape the proof under another. Manual rows never enter the plan and fix-less advisories carry no fix, so neither can fail sync. The verdict moved into convergenceVerdict/reportVerdict, which drops run()'s complexity from 32 to 22. The new check exposed one row that promised a repair no step performs: natives "no agentdb locations found under global ruflo" with fix "setup/sync installs ruflo". Sync installs a missing ruflo only through the versions row, and nothing restores agentdb inside a present ruflo. A present ruflo now gets a manual reinstall; an absent one defers to the versions row with no second plan item. The status golden fixture changes only that row. Docs: ADR-0033 decision 9, update note, consequence and verification; UPGRADING dated section (exit code 0 -> 1 for scripts and CI); TROUBLESHOOTING row; README sync row; DDD term "Unresolved repair"; ak sync --help. (cherry picked from commit 6a26372db371ecf61b0f2721604b368286a02a74) * feat(sync): add --skip for one-run subsystem exclusions Sync had no way to leave one component out of a run (#237, #239 P1). The kit.json opt-outs change ownership and have side effects (aqe:false and ruvnetBrain:false also strip guidance), and --no-upgrade withholds a whole class of upgrades, not a component. --skip SUBSYSTEM is repeatable (or comma-separated) and validated against the subsystems sync knows plus every lifecycle host; an unknown name exits 2 with the accepted list before anything is collected. A skip acts in the three places decision D5 names: - the plan: a skipped subsystem's items leave the plan, and so does a fix only a skipped step performs (statusline/cve when statusline is skipped), so running the rest cannot report it unresolved; - the steps: a step never runs when the subsystem it repairs is skipped, even when another planned subsystem triggers it (natives on versions or security, providers on routing or codex-mcp); removing the subsystem from the plan already stops the triggers it would derive, host-lifecycles checks each host, and the codex-mcp reconcile and host-alignment tail passes honor it too; - the proof: skipped items and a skipped subsystem's failing rows print "skipped by request" and never count as unresolved or failing. An empty plan after skipping no longer claims "all subsystems healthy". A census test proves every subsystem a step's `when` names is accepted and every accepted name has a step that performs it. run() stays under the complexity threshold (the tail moved into runTail). Docs: ak sync --help; README command list and sync row; INSTALLATION update table; UPGRADING and TROUBLESHOOTING live-session advice (--skip versions); ADR-0033 decision 9, update note and verification; DDD "Unresolved repair" entry. (cherry picked from commit 3ca275ca81a88fba293ca4e7946e0d91bdd01db4) * feat(sync): emit one JSON result with --json `ak sync --json` was declared and documented but never read, so it printed the ordinary human output (#237 review, decision D6). With --json, everything sync would write to stdout (ok/warn/fail/info lines, the plan listing, prompts, the progress ticker) goes to stderr for the whole run, and stdout carries exactly one JSON object, pretty-printed like `ak status --json`: { plan[], steps[{id, ok, detail}], unresolved[], skipped[], converged, exitCode } plan and skipped items use status's row fields; each unresolved item carries a reason (not-converged, no-step, failing, apply-failed, declined), so the fail-level rows and apply failures that text mode prints as "still failing" are not lost. A step is listed when it runs; it is not ok when a result it reported failed, it printed a failed sub-surface, it recorded a failure in the run state, or it threw, and its detail is what it printed. converged is null when the run stopped before a verdict (a dry run with a plan, a rejected --skip, an error); a rejected flag or an error also sets `error`, and an error still yields the one JSON object. run() now fills one result object on every exit path (converge() holds the old body). Text mode is unchanged. The synthetic aqe-embedding plan item is built with row() so every plan entry has one shape. Tests spawn sync (a child with a stubbed collector, and the real CLI for --dry-run and a rejected --skip) and parse stdout as a single JSON value while the human text arrives on stderr. Docs: ak sync --help; README command list and sync row; UPGRADING dated section with the result shape; INSTALLATION CI guidance; ADR-0033 decision 9, update note and verification. (cherry picked from commit af88a3c1cc5a9c8a0d2699ec079b189c2556d51d) * fix(aqe-embedding): say Ollama is not running when it is installed The local-provisioning catch swallowed the fetch error and always printed "Install Ollama ... and start it", so a user with Ollama installed but stopped could not tell the two cases apart (#237, audit item S4). Read only the refused-connection code (TypeError('fetch failed') whose cause carries ECONNREFUSED, including the AggregateError for localhost) or the probe's endpoint-unreachable reason. When the selected endpoint is kit-provisioned Ollama and the ollama command is on PATH, say it is installed but not running at the loopback endpoint and how to start it; otherwise keep the install guidance. The install check runs only on that path, never for other failures or external endpoints, and raw service text is still never reflected. Docs: AQE-EMBEDDINGS, SETUP, TROUBLESHOOTING; ADR-0055 updated line. (cherry picked from commit 208b1856c9d34336d2bb9caccd83a64febf98370) * feat(status): remember the last live check and show it with its age ak status reads configuration only, so it showed the AQE embedding backend as "configured-unverified" information while ak sync failed the live request (#237 S4, audit decision 9a). Status never learned what sync found. Add one evidence store for live results (src/lib/live-check-evidence.mjs): a per-check file under the kit state directory holding passed, failed or inconclusive, a bounded reason, the source and the time, keyed by a hash of the inputs the check ran against. Sync's aqe-embedding step and the ak x verify suites (mcp, memory, security, providers, deja-vu, and the aqe live embedding request) record into it; a skipped proof records nothing. The status aqe-embedding row reads it and shows the result with its age: a failure is a warning (never fail, which sync's convergence proof counts), a fresh pass is ok, a stale pass is information, and a different backend selection marks the result as changed. Status still never probes and never writes. Verify keeps its output unchanged; a scoped output capture reads a failed proof's first failure line as the remembered reason. Integration (I2): ADR-0055 header conflict with lane C 208b1856 -- both 2026-09-26 Updated lines kept (C's Ollama line first). ak status --help conflict with A1 fe35bc51 -- G1's "never runs a live check" wording and A1's "→ manual:" / repair wording combined into one paragraph. (cherry picked from commit 1de21fe16f3a3b9a9526d4b2042b4dec8ad1d077) * feat(status): add opt-in --live running the quick, free verify checks The maintainer asked for an opt-in way for ak status to test live services (audit decision 9b). Plain status and the dashboard stay probe-free. ak status --live first runs the quick, free ak x verify checks, reusing the suite functions rather than a second copy: the AQE embedding request for a kit-managed backend (sync's gate), Codex MCP initialize/tools-list when Codex is enabled, provider wiring, the security packages, deja-vu's structural proof when enabled, and a memory round trip in a temp dir. They run in parallel, each with captured output and its own timeout; a timeout or a check that cannot run is inconclusive, never failed. A scoped default abort signal in exec.run stops a timed-out check's child processes so its own temp-dir cleanup still runs. Results are remembered as status-live evidence, a new live-checks section shows every remembered result with its age, and --live --json adds a live array. A disposable-HOME run showed that AQE 3.14.3 `aqe health` initializes a .agentic-qe store in its working directory. The provider check (also used by ak x verify providers) now runs it only where .agentic-qe exists. Integration (I2): ADR-0055 header conflict -- lane C's Ollama line kept; G1's own 2026-09-26 line replaced by this commit's extended --live wording (as on lane/G1). ak status --help conflict with A1 fe35bc51 -- G1's "Without --live it is read-only" wording plus A1's "→ manual:" / repair wording in one paragraph. (cherry picked from commit b538debfee2511e440912502a649bc1f7b0bcf8e) * fix(aqe-embedding): accept unrelated dotted root TOML keys Any dotted or quoted root assignment in Codex config.toml (for example tui.status_line = ["model"]) made the AQE embedding editor refuse the whole file, so the endpoint projection never converged (#237, audit P1). Meanwhile an inline AQE registration under [mcp_servers] or a root `mcp_servers = { ... }` passed the bare-key guard and read as absent. Table headers and assignments now share one TOML key decoder that splits dotted keys, keeps dots inside quoted segments and decodes every TOML 1.0 escape (including 8-digit \U, which JSON.parse rejected). At the root and under [mcp_servers], an assignment is skipped unless its decoded key path can define the AQE registration; those forms (inline, dotted, quoted or escaped) are refused as conflicts. Keys inside the AQE tables keep the existing refusal. The helper keeps the editor's complexity at 23. Visible change: an inline AQE registration now reports a conflict instead of absent; UPGRADING carries the note. Table-driven test covers the 26 audit cases plus unrelated dotted servers and escaped headers. Integration (I2): ADR-0055 header conflict -- this commit's 2026-09-26 line appended after the Ollama (C) and live-check (G1) lines. docs/UPGRADING.md conflict -- the four earlier 2026-09-26 sections (agentdb, status rows, sync --json, unresolved repairs) kept; "AQE embedding edits in Codex TOML" placed after them, directly above 2026-09-10 as on lane/C. (cherry picked from commit 0eaa1bab969ef090301003c82fa9854c55932bc0) * fix(aqe): share one AQE MCP transport recognizer across all hosts ak edits AQE_EMBEDDER_ENDPOINT only inside AQE registrations it recognizes. The #230 allow-list accepted aqe-mcp and the pinned npx form for Claude and Codex, while OpenCode's separate check accepted only ['aqe-mcp']. A correct manual entry such as `/opt/homebrew/bin/aqe mcp` stayed "unrecognized AQE MCP transport preserved" with no instruction, and the same command got different verdicts per host (#237, audit P2). Per audit decision 3, one recognizer now serves Claude, Codex and OpenCode: aqe-mcp with no args; aqe, agentic-qe or aqe-v3 with exactly ["mcp"] (one CLI in agentic-qe's bin map whose mcp command starts the same server); npx with exactly -y agentic-qe@latest mcp; npm .cmd shims, case-insensitive with Windows separators on win32. Extra flags, subcommands and wrappers stay preserved. OpenCode's array command goes through the same rule. Table-driven test per platform, plus a cross-host test proving Claude, Codex and OpenCode agree for each shape. ADR-0055 amendment; AQE embeddings and UPGRADING describe the recognized commands. Integration (I2): ADR-0055 conflict at the end of the file -- G1's "Amendment 2026-09-26: remembered live checks" kept, and this commit's "Amendment — 2026-09-26: recognized AQE start commands" appended after it. The header Updated line and the UPGRADING paragraph merged cleanly. (cherry picked from commit 11d5e4ebc940df3ecd0efe45dea493eaaef1b301) * fix(aqe): treat live RVF lock contention as busy in verify On a live RVF lock, agentic-qe 3.14.3 logs the busy warning, then falls through to a create attempt that fails with FsyncFailed; store and lock bytes are unchanged (agentic-qe#574; PR #719 is partial). classifyAqeStartup checked FsyncFailed first, so `ak x verify aqe` reported AQE as failed whenever its MCP server held patterns.rvf (#237/#239, audit U3k). Per audit decision 7, only the exact three-line sequence is contention: the lock warning, the live-owner quarantine refusal (emitted only from AQE's live-owner branch) and FsyncFailed/0x0303. It skips the FsyncFailed rule, still yields to a failed embedding initialization, and reports busy with an honest reason. A bare FsyncFailed, or a lock warning plus FsyncFailed without the middle line, still fails. TEMPORARY: the rule and its test cite #240 and agentic-qe#574/#719 and are removed once the AQE release carrying #719 is the kit floor. Refs #240. (cherry picked from commit 9069298d7766aea67f82054035ec10f8ff7e95a7) * fix(status): name the unreviewed Brain hook delta and the options Brain 4.3.28 added one automatic hook, and the plugin row only said the hooks "differ from the exact reviewed 4.3.17/4.3.26 contracts" (#237 #6): a permanent warning with no hint of what changed or what to do. An unqualified hook set is now compared with the nearest reviewed contract and the issue names each added, removed or changed hook by event and shim action. For 4.3.28 that is `adds UserPromptSubmit capacity-aware-parallel-work`, with the reviewed fact that it is declared offBehavior "run" (keeps running when the Brain is off) and can inject "launch actual workers now" (plugin 4.3.28 scripts/hook-shim.mjs:89). The row lists the options: keep it until an ak release reviews it, `claude plugin disable ruvnet-brain@ruvnet-brain`, or "ruvnetBrain": false. Per decision 4 there is still no 4.3.28 contract, and the row keeps fix:null because sync cannot review a hook. Docs: TROUBLESHOOTING row for the hook-delta warning. (cherry picked from commit 89ecdaf09e9311153da4efda15b17ce0c978eec5) * fix(status): list options for an out-of-range external agent-browser An external agent-browser outside Ruflo's >=0.27.0 <0.28.0 range is preserved by design (ADR-0043 section 3), so the row has no sync fix and stayed amber forever with no guidance (#237, audit D5). The external branch now says Ruflo's browser tools may not work with that version and lists the options: install a Ruflo-compatible 0.27.x yourself, or set agentBrowser: false in kit.json, naming what that gives up (ak's trusted browser config for Ruflo MCP and the readiness checks). The fix stays null, so sync never plans it; the kit-owned branch and its sync fix are unchanged. Docs: TROUBLESHOOTING row, MANAGED-TOOLS boundary, ADR-0043 updated line. (cherry picked from commit 311ae7a4be4522ee3a6051b20820f24d90fecee7) * fix(status): point non-git project hints at ak setup --project The memory and learning rows told users in a non-Git folder to "run setup here", but plain ak setup there configures the machine only (#237 setup item 1, audit N3). Following the hint changed nothing about the project the row described. Move setup's project-scope rule (a .git entry in the directory itself, never HOME) into one shared predicate that setup now uses, and build the status hints from it: a repository root keeps the plain hint, a folder outside any repository names `ak setup --project`, and a repository subdirectory names the repository root or --project. Setup's own "not inside a project" line names --project too. (cherry picked from commit bea8c1555de80b825deaa6c20c3cf846980b57f3) * fix(live): parse ps paths containing spaces; classify desktop-hosted Claude CLI The POSIX header survey ended its regex with (\S+), so every row whose comm= path contains a space was dropped (72 of 970 rows on the maintainer's machine), including the Claude Code CLI that the Claude desktop app runs from ~/Library/Application Support/Claude/claude-code/<ver>/claude.app. Once such rows parse, three follow-ons had to change with it: - hostFromCommand treats a known executable path that contains spaces as argv[0], so a Codex `mcp-server` under a spaced path is still excluded and a Node launcher under a spaced path still names its host script. - controllerKind classifies the desktop-hosted Claude CLI as a project-session before the generic .app/Contents/ desktop-app rule. - rootControllers no longer lets a desktop-app ancestor absorb a project-session it hosts; the app's own services (app-server) still fold into the app, so the census does not grow new app-service roots. Windows parsing is unchanged. ADR-0012, the machine-footprint DDD and OBSERVABILITY.md describe the new classification. Refs #238 (item 3). (cherry picked from commit 3408566d128f3c397789224f537aac968eb639d6) * fix(live): report live source health truthfully A registered --live-source file that did not exist showed "1 file / 0 events / 0 errors / ok", and so did an unreadable one. The tailer returned silently on ENOENT, #reconcile set "ok" after every tailer pass (overwriting the "degraded" an error had set in the same pass), a file created after start was read from its end so its first records were lost, schema-invalid structured records vanished without a count, and the client only counted a status ("error") the service never emits. - JsonlTailer exposes presence (absent/readable/unreadable), probes readability when nothing new is pending so a mode-000 file cannot pass as healthy, reports an I/O failure once per transition, refuses to open a non-regular file, and reads a file created or recreated after tailing began from its first byte (this also applies to startOffset and startAtEnd). - The service recomputes tailed-adapter health after each pass from the files: readable/missing/unreadable gauges plus accepted/rejected counts, lastAcceptedAt and a fixed lastRejection code. Status precedence is degraded > awaiting-file > no-events > ok; a bad latest record stays a fault until a later record from that file is accepted. - structuredRecordRejection() is the one acceptance rule shared by the adapter and the diagnostic; it never echoes record content. - The Sources toggle counts degraded (and legacy error) as issues and names sources awaiting a file. It does not count "unavailable": the Codex state ledger is unavailable on every Claude-only machine. `files` keeps meaning tailed files here; the next commit recomputes it. Docs: OBSERVABILITY (source health, --live-source reads an existing producer's file), TROUBLESHOOTING, DDD AdapterHealth, ADR-0012, CLI help. Refs #237 (section E). (cherry picked from commit 5faa8cac26b72c1207c54a1df91edb07d75280cf) * fix(live): reflect the discovery file cap in coverage; stop the files counter drifting Live discovery tails only the newest transcripts per host (128 each by default), but #discover called discoverJsonl, which throws away the truncated/candidateCount facts, so /api/live reported acquisitionCoverage {complete:true, truncated:false} while 257 Claude and 756 Codex files were capped to 256 tailed. Separately, #add incremented health.files and close() never reset it; the dashboard reuses one service across its 30 s idle stop, so files grew 2 -> 4 -> 6 on each restart (the reporter's 256/256 was one such restart). - #discover uses discoverJsonlDetailed and keeps, per host, {candidateFiles, returnedFiles, fileLimit (the host's share), truncated}. A capped window makes acquisitionCoverage complete:false and truncated:true, with omittedFiles and per-host sources. - health.files is recounted from the tailed files on every pass (the increment in #add and the rotation decrement are gone); native adapters also carry candidateFiles, and Sources renders "N files (newest of M)". Docs: OBSERVABILITY (bounds and symptom row), DDD observability, ADR-0012. Refs #238 (item 5). Integration (I3): tests/kit/ga-surface-guard.test.mjs failed after this pick ("docs/adr/0012-observability.md retains historical vocabulary without a GA amendment"): the lengthened 2026-09-26 Updated line pushed the ADR-0020 marker and the Update note past the first 1,200 characters the guard reads. Fixed in this commit: the Updated line is now one line naming the items, and its detail (process survey, source health, discovery coverage) moved verbatim into a new "Amendment — 2026-09-26: live acquisition" section at the end of ADR-0012. (cherry picked from commit 9fd5e444c8509231295491c05619397b77785389) * fix(live): keep tail offsets across idle stop and restart The dashboard reuses one LiveSessionsService and calls close() 30 s after the last Live client leaves, then start() on the next visit. close() dropped every tailer and context, and start() re-bootstrapped and re-tailed each file from its end, so operations appended during the idle gap were lost and the session was rediscovered (#238 item 4). ADR-0012 promises retained offsets and a safe restart. close() now stops polling but keeps tailers (offset, partial line) and contexts; only the first start bootstraps metadata and tails from the end. A restart resumes each file where it stopped, and a file that appeared during the stop is read from its first byte, the same policy the moving window applies while running. The snapshot reports acquisitionCoverage.observedSince, and the Live map tells the user when a session has no operations drawn since Live started watching, so pre-observation work is disclosed rather than looking absent. Integration (I3): ADR-0012 header conflict with the re-done pick of 9fd5e444 (which moved the 2026-09-26 detail into "Amendment — 2026-09-26: live acquisition" to keep the ga-surface-guard 1,200-char header rule). The one-line Updated note now also names idle restarts (#238 item 4), and this commit's Updated-line sentence moved verbatim into that amendment as an "Idle restarts" bullet. The §6 body edits auto-merged unchanged. (cherry picked from commit 54ec713af7b35030dfb9e7fa23023052fbaef7dd) * feat(live): show non-git folder sessions through an exact-folder match A running session in a folder that is not a Git repository showed in System -> Runtime but dropped out of Live whenever it stopped writing, because ADR-0012 only granted a runtime lease to canonical Git repositories (#238 item 2). The rule exists because a plain folder's project key hashes only its name: the reporter's name-based join was reproduced attaching one `scratch` folder's process to another `scratch` folder's transcript. Decision 2 (exact-folder match): a process in a plain folder may lease a transcript session only when an HMAC of its real working folder equals the HMAC of the folder the transcript records. The secret is random per collector and the correlator lives only in memory, never in events, snapshots, replay, the workspace store, or logs (ADR-0053's per-server-secret precedent). Project keys are unchanged. An unmatched plain-folder process gets no runtime-only session, a missing folder never matches, and a bound process whose folder changes loses the lease through the normal miss count. ADR-0012 is amended. Integration (I3): ADR-0012 header conflict with the re-done picks of 9fd5e444/54ec713a (the 2026-09-26 detail lives in "Amendment — 2026-09-26: live acquisition" so the ga-surface-guard 1,200-char header rule holds). The one-line Updated note now also names exact-folder leases (#238 item 2), and this commit's Updated-line sentence moved verbatim into that amendment as an "Exact-folder leases" bullet. The runtime identity amendment body edit auto-merged unchanged. (cherry picked from commit 1de615513c5785cc5e0e4dc727a719a488b40470) * fix(live): keep the exact-folder correlator across the metadata bootstrap The first start ingests bootstrap records through a copy of each source context and then copies its properties back. The folder learned from a Codex session_meta was noted against that copy, so the live context had none, and the first streamed record without a cwd (a Codex tool call or message) cleared the session's folder correlator. The bound process then failed the folder check and the session quiesced after three surveys: the symptom decision 2 set out to fix, for any Codex session in a non-Git folder that was mid-turn when Live started. Carry the noted folder from the bootstrap copy to the live context, and only clear a session's correlator when its source names a Git repository or a missing folder, never when a record names no folder at all. (cherry picked from commit 80921288827e5ff5f7c5990faeb3b1d0322a6e3d) * feat(hosts): report managed, found-not-managed, and not-installed hosts consistently kit.json cannot tell "left out on purpose" from "never changed the claude-only default", yet the dashboard called every unrouted host Disabled, skipped even its free local checks, and offered a "Check local setup" button that could never produce evidence (stuinfla #238, audit decision 1). Management and health are now separate facts with one vocabulary (src/lib/host-management.mjs): Managed by ak / Found, not managed / Not installed. Every found host gets the bounded tool checks automatically (codex doctor still excluded); for an unmanaged host the wiring check is FYI, its problems are information (never Attention, no sync fix), and the paid connection check stays managed-only. The header badge, details dialog ("Check again"), About (dashboard and ak about), Overview -> Providers rows, a new Hosts & Routing participation strip and ak host status all use the same words. The enable hint is copyable text naming the complete --host list, fixing the old hard-coded "--host claude,opencode" hint that would have disabled an enabled codex. ADR-0053 amended (Disabled retired); its "disabled hosts are neutral" test is replaced. Integration (I3): three conflicts, both intents kept. (1) ADR-0026 header: one 2026-09-26 Updated line naming B1's agentdb amendment and this commit's host-card management words; the "Earlier updates" line is B1's. (2) docs/DASHBOARD.md Overview: A1's Summary sentence (arrow vs manual tag) with this commit's Hosts & Routing participation sentence. (3) status-golden.json providers hunk: this commit's three management rows, each with A1's "repair": null key (regenerating the golden with STATUS_GOLDEN_UPDATE=1 is a no-op). (cherry picked from commit b37a92e9c53dfee97f8296151c303f48b7ca44bd) * fix(maintenance): do not scan or count absent source roots The Hermes automatic source is enabled on every machine. When ~/.hermes did not exist, resolveAutomaticSourceRoots marked it present:false, but scannableSources ignored that and drove it anyway: planPartitions found no partitions, the orchestrator recorded planReadFailure=ENOENT and finalizeFailed mapped it to io-failure. The Inventory banner then said "1 source stopped at a limit (io-failure). See Discovery", while Discovery, which reads the live orchestrator, lost the failed row after any config change or restart. With the failed row gone, the banner fell back to "not been scanned yet" forever, so most installs could never clear it (#238 item 6). An absent host root is not installed, not incomplete. Now: - scannableSources skips present:false sources, and an explicit start is refused with SOURCE_NOT_PRESENT (409 on the dashboard, plain message in the CLI); - projectionSourceCoverage drops them from the inventory sourceCoverage, so the banner and "coverage gaps" never count them; - scanProgress leaves them out of the narrative totals and the forbidden-claims check; - discovery() and the coverage rows carry present, the public v2 COVERAGE schema keeps it (it was stripped before), and the dashboard and `ak maintain discovery|scans` show "Not installed" with no start hint. Roots the user added are unchanged: a missing user root still reports its real failure. The closed LIMITING_REASONS/SOURCE_COVERAGE_STATES enums are untouched. The test harness hid this because hermeticPaths() omitted hermesDir, which production's DEFAULT_PATHS includes; it now mirrors production, and the stale "stuck at scanning" comment is replaced. Docs: MAINTENANCE, DASHBOARD, DDD maintenance, ADR-0048 amendment, and `ak maintain --help`. (cherry picked from commit d3eef34871c59a7cba539ace3d1d1d1d0272bea3) * fix(usage): rank project concentration by repository identity The "X dominates your usage" Finding and Score -> Projects reported different cost and session counts for the same project and window (#238 item 7). Both read the same sessions, but the finding ranked `byProject`, keyed by the folder or repo-name label, while Score -> Projects ranks `gitProjects`, keyed by verified repository identity since PR #210. The folder label merges separate clones, plain non-git folders, repositories that no longer exist, and sessions with no evidence, so it cannot support a "one project" claim. The subagent theory in the report is not the cause: both paths see the same sessions. detectProjectConcentration now ranks `gitProjects` (label, cost, sessions) and stays silent when an older aggregate has no such field. The share keeps the whole window's spend as its denominator and the text now says so ("of all API-equivalent spend in this window"). The unused entriesByCost helper is removed. Tests: the finding reads $350 / 4 sessions from gitProjects when byProject says $400 / 7; no gitProjects -> no finding. Existing concentration fixtures move to gitProjects. Docs: USAGE-SCORECARD-METRICS section 11, ADR-0050 amendment, DDD context map. (cherry picked from commit 1c5491859c32dfdc34c241f5b504d412f10664b7) * docs(maintenance): qualify fresh-install coverage wording for absent hosts Follow-up to d3eef34 (doc gate). DASHBOARD and MAINTENANCE still said a fresh installation shows "every automatic source" as Not scanned yet; a host source whose root is not on the machine now reads Not installed and is not counted. Adds the "Not installed (Discovery source)" term beside SourceCoverage in the DDD ubiquitous language. Integrator may squash this into d3eef34. (cherry picked from commit 512533d6db5cd0accb5263190cb404c9ea1c69b6) * fix(usage): explain an empty Claude limits panel when the user statusLine is custom Claude limits reach ak only through the kit footer's tee, and Claude Code runs the statusLine with the highest precedence (project local, shared project, then ~/.claude/settings.json). A user-level statusLine that runs another script (for example the RuvNet Brain version segment) never tees, yet the panel told every user to "run one session, then revisit". /api/limits now carries claudeChannel, the class of the user-level statusLine (none, kit-footer, project-helper, custom, unknown), from a read-only look at the settings file and the script it names. It returns no path and writes nothing, so ADR-0010's channel is unchanged. The empty Claude panel names the class, states the project precedence rule, and points at `ak setup --project` / `ak sync`. The claude field keeps its null-or-windows contract; an older server falls back to generic copy. Refs #238 (M3). Integration (I3): tests/kit/doc-citations.test.mjs failed after this pick on its own ("docs/USAGE-SCORECARD-METRICS.md:1357 cites quota.mjs:209 but ... codexAppServerRateLimits@294"), as lane F2 reported: the classifier block shifts quota.mjs. Fixed in this commit: the quota.mjs citations in USAGE-SCORECARD-METRICS §13b are re-anchored to this tree (69->70, 209->294, :43->:44, :264->:349, :145->:230, :127-137->:212-222, 51->52). The next commit (8b53dead) re-anchors them again for its own quota.mjs changes. (cherry picked from commit f9d4db509b18ab03a4165ebfba0fa655d0ab47b7) * fix(usage): show why Codex limits are unavailable Every codex app-server failure collapsed to codex:null, and the Limits panel guessed "not installed, not logged in, or did not answer". It could not tell a missing CLI from one that rejects the read-only flags (the case that starved the cache for 10 days), a timeout, an RPC refusal, or an answer with no plan window. The exchange now keeps the failure class (not-installed, spawn-failed, exited with its exit code, timeout, rpc-error with its numeric code, no-limit-windows) and /api/limits carries it as codexUnavailable beside an unchanged codex field. Stderr stays ignored and vendor message text is dropped. The empty panel names the cause and the next check, and a stale answer served instead says its last refresh failed. Also re-anchors the quota.mjs line citations in USAGE-SCORECARD-METRICS that the previous commit shifted (tests/kit/doc-citations.test.mjs). Refs #238 (P4). Integration (I3): USAGE-SCORECARD-METRICS §13b conflict with the fix-forward in the previous pick (which re-anchored the quota.mjs citations for the f9d4db50-only tree). Took this commit's side: the Codex paragraph is identical to lane F2's final text (codexAppServerExchange :313, collectCodexLimitsDetailed :385). doc-citations passes. (cherry picked from commit 8b53deadcbfae836a908afa61984f54168d1c9d1) * perf(dashboard): serve the System page from a slim summary endpoint GET /api/system sends the footprint collector's payload verbatim, and its catalog repeats every presence fact in item.presence, item.consumerBindings, item.artifacts and again in top-level artifacts and consumerBindings. It grows with items x projects x hosts (tens of MB on a real machine), the page draws about 0.4% of the item bytes, and the Runtime view re-fetched it every 30 seconds. GET /api/system/summary serves the same read and the same ?refresh=deep and &trees= parameters with the catalog projected by dashboard/system-summary.mjs: an allow-list of catalog keys, and items cut to key, kind, name, hosts, sourceScopes, digestCoverage and distinct presence[].provider {ref, version}, the shape paintCatalogMatrix already reads. loadSystem (and so the Runtime poll and Full scan) uses it. GET /api/system and `ak system --json` are unchanged, as UPGRADING documents. A render-parity test draws the KPI band and every catalog card from both payloads and requires identical HTML. The UI harness intercepts now match /api/system/summary. Refs #237 (M4), decision 8. (cherry picked from commit 00ca5f5ff819d13e3b1e59d69ba5d22d1a0c16c3) * refactor(exec): share a process-tree kill helper The MCP discovery probe carried its own copy of the process-group and taskkill teardown, and the tool-call client the memory route proof needs (issue #213) would have added a third. killProcessTree() is the one helper for MCP stdio clients: POSIX reuses run()'s killGroup (group kill, then the child itself), Windows uses taskkill /T /F with a direct-child fallback. run() keeps its own kill path unchanged on every platform. (cherry picked from commit 3fc6b0870d418eb557481a161b485d2baa8253a0) * feat(verify): prove the memory route across CLI and MCP `ak x verify memory` failed on ruflo 3.45.0 with "isolated namespace purge did not remove the proof row" (lane G1 unresolved #1). Observed in a disposable project with the suite's own environment: the CLI mirrors a `memory store` into memory.db and agentdb-memory.db, and a default `ruflo memory purge` clears memory.db only while reporting success. The suite now clears the sibling of its throwaway project with --path and says so. After the CLI proof it observes, in that project only, whether a key written through the CLI is readable through MCP and the reverse (issue #213), naming where each landed and the MCP backend. A split is a warning and an unusable MCP server is "not observed"; neither fails the suite. The proof isolates CLAUDE_FLOW_MEMORY_PATH, which the native store follows instead of the DB-path pin, so a user's memory root never receives proof rows. `ak status --live` keeps its memory check to the CLI proof. New: mcp-tool-call.mjs (bounded, ordered MCP tool calls on the shared process-tree kill) and memory-route-probe.mjs (the observation and its verdicts). Docs: ADR-0016, TROUBLESHOOTING, SETUP, DDD term, verify help, Ruflo guidance block. Integration (I4): ADR-0016 header conflict with A1's 2026-09-26 line (legacyRufloDisposition, #237), which already occupies the header budget that ga-surface-guard reads (the Update note must sit in the first 1,200 characters; it was at 924 before this pick). A1's line is kept unchanged. This commit adds a second, one-line 2026-09-26 Updated note that points to a new "Amendment — 2026-09-26: project memory" section at the end of the ADR, and this commit's Updated-line sentence moved verbatim into that section as its first bullet. A1's form of the 2026-09-23 line ("Earlier update") is kept. The project-memory body edit auto-merged unchanged. Update note offset after this pick: 1,070. (cherry picked from commit d20e11b952e784e4877328fa816e136e8bdde339) * feat(status): gate memory routing claims on observed release evidence When both project stores exist, status said only that MCP routing needs separate verification. It now states which interface reads which file (CLI memory.db, MCP agentdb-memory.db with the native bridge) for an exact @claude-flow/cli release and platform where that routing was observed, and keeps the unverified wording for anything else: neighbours, prereleases, build tags, other platforms, or no resolvable CLI. The gate reads the @claude-flow/cli nested under ruflo (else a hoisted copy), not the wrapper. Observed pairs: 3.42.4/darwin (issue #213 work) and 3.45.0/darwin, recorded 2026-09-26 in a disposable project with the verify suite's environment and again with scripts/ruflo-memory-routing-repro.mjs (public CLI and MCP only): CLI writes reach MCP, MCP writes are invisible to a CLI read, a default purge leaves the sibling's rows, and MCP ignores a non-default CLAUDE_FLOW_DB_PATH. The row stays a warning with no sync fix; both stores are preserved. Docs: ADR-0016 (status no longer "identifies the active writer"), TROUBLESHOOTING routing section, DDD term, Ruflo guidance blocks. Integration (I4): ADR-0016 header conflict with the header built in the previous pick (A1's 2026-09-26 line plus a one-line note pointing to "Amendment — 2026-09-26: project memory"). The note now also names the release-gated routing claims, and this commit's Updated-line addition ("status states the routing only for an observed Ruflo release and platform") moved verbatim into the amendment as a "Status routing claims" bullet. The project-memory body edit auto-merged unchanged. Update note offset after this pick: 1,084. (cherry picked from commit 208e7992703be424ad1bd8e1ea74eebdafae3f2f) * fix(setup): remove the setup memory probe from every store `ak setup --project` proves a memory write in the user's real project with a `_setup/verify-*` row, then deleted it from the first store that held it (memory.db). The Ruflo CLI mirrors that write into agentdb-memory.db (observed on 3.45.0), so every setup run left a probe row in the MCP corpus. removeMemoryProbe() deletes the row from every store that holds it with bound parameters (Ruflo's `memory delete` only tombstones), opens a store for writing only when it holds the row, skips a store with no memory table (agentdb-memory.db right after `memory init`), and reports an unreadable or busy store so setup names it for manual cleanup instead of claiming "VERIFIED". Adds the opt-in live test tests/live/ruflo-memory-routing.test.mjs, which runs the route probe against the installed Ruflo and fails when an observed release/platform pair stops routing as claimed. It needs the `test:ruflo-memory-live` package script (integrator applies the manifest change). Docs: ADR-0016 (setup cleanup; "active writer" wording corrected), TROUBLESHOOTING. Manifest: package.json scripts gains "test:ruflo-memory-live": "node --test tests/live/ruflo-memory-routing.test.mjs" after test:qe-court-live (lane G2 manifestChanges), so the opt-in live routing test that TROUBLESHOOTING points maintainers to can run. Integration (I4): ADR-0016 header conflict with the header built in the two previous picks. The one-line 2026-09-26 note now also names the setup probe cleanup, and this commit's Updated-line addition ("setup removes its write probe from every store, correcting the earlier "active writer" wording") moved verbatim into "Amendment — 2026-09-26: project memory" as a "Setup probe cleanup" bullet. The project-memory body edit auto-merged unchanged. Update note offset after this pick: 1,105. (cherry picked from commit 7ad2045f4188f7f7adfd49e4d56c27e24f3ccc85) * feat(status): report the canonical memory store, its size, and stray stores Nothing in ak showed which store the hosts actually use, how large it is, or that memory was piling up elsewhere. The audit found a 168 MB MCP store (98% `commands` rows with no expiry), a nested .swarm/.swarm/agentdb-memory.db, and stray agentdb.db, agentdb.rvf, ruvector.db and .agentic-qe/ folders that no status row mentioned. - status resolves the canonical store at <root>/.swarm for the root every launch contract pins (memoryProjectRoot), so a subfolder run reports the hosts' store rather than a folder-local one - each file row adds size, live WAL, the largest namespace and its expiry, from one read-only query on Ruflo's namespace index - a store with no memory_entries table (agentdb-memory.db right after `ruflo memory init`) is empty, not unreadable (lane G2 unresolved #1); removeMemoryProbe and findMemoryEntry skip it explicitly - findStrayMemoryStores: a bounded, read-only walk (4 levels, 2000 folders, never node_modules, .git or dot-folder contents, never .swarm/backups) listing strays by owner; rows are info with no fix, because ak leaves them in place and a warning with no resolution would stay amber forever (#237/#238 comments, audit N4) Docs: ADR-0016 update and paragraph, DDD terms (canonical memory store, stray memory store), TROUBLESHOOTING table row and two sections. Integration (I4): two conflicts, both resolved keeping both intents. (1) docs/TROUBLESHOOTING.md symptom table: lane C's external agent-browser row and this commit's stray-memory-store row were inserted at the same place; both rows are kept, C's first. (2) ADR-0016 header: the one-line 2026-09-26 project-memory note now also names canonical and stray stores, and this commit's Updated-line addition moved verbatim into "Amendment — 2026-09-26: project memory" as a "Canonical and stray stores" bullet. The project-memory body edit and the two new TROUBLESHOOTING sections auto-merged unchanged. Update note offset after this pick: 1,132. (cherry picked from commit 8e477b522b072102fe8ad4606bb0e326b61fdf41) * feat(status): report memory backup and distillation age Ruflo backs up and distills project memory only inside the project's daemon, which ends itself after 12 hours. Nothing in ak showed that the last backup here was 16 days old, and the daemons row reported "none running" as ok. ak also plays a part: setup keeps claudeFlow.daemon.autoStart false, which Ruflo 3.45.0 reads as "do not start the daemon when a ruflo command runs" (daemon-autostart.js), so backups stop soon after setup. This commit reports that; it does not change setup's policy (a maintainer decision). - memory-maintenance.mjs reads Ruflo's own evidence, read-only: the daemon's .claude-flow/metrics/{backup,consolidation}.json and the newest .swarm/backups/memory-*.db (so a manual `ruflo memory backup` counts); ak never runs either job - memory rows: backup age (warn when older than 48 h or absent with no daemon for the project, or when the latest attempt failed), distillation age (warn only on a failed or corrupt run; age alone would stay amber forever), and an info row for agentdb-memory.db, which neither worker covers (upstream gap), with a manual backup command verified in a sandbox on 3.45.0 - daemons row: info, not ok, when the project has memory.db and no live daemon pidfile; names `ruflo daemon start` and any setting that turns off Ruflo's start-on-use; never a sync fix Docs: ADR-0016, DDD term, TROUBLESHOOTING table row and section, SETUP.md, the Ruflo reference guidance, and the About card for the background daemon. Integration (I4): one conflict and one red test, both fixed in this commit. (1) ADR-0016 header: the one-line 2026-09-26 project-memory note now also names backup and distillation age (and was tightened to keep headroom under ga-surface-guard's 1,200-character header rule). This commit's Updated-line addition moved into "Amendment — 2026-09-26: project memory" as a "Backup and distillation age" bullet, verbatim except that its opening "It" became "Status" so the bullet stands alone. Lane G3's "commit 2 alone fails ga-surface-guard" defect does not occur here: the Update note offset after this pick is 1,128. (2) tests/kit/daemons-status.test.mjs failed 2 of 6 after the pick: two deepEqual expectations were written before lane A1's status-row contract (commit 4), which gives every row a `repair` field (`null` when there is no fix). Both expectations now carry `repair: null`, as A1 did for status-command. The rows' level, message and fix are unchanged. The body edits, SETUP, TROUBLESHOOTING, the DDD term, the guidance paragraph and the about-directory background-daemon paragraph (separate from B1's agentdb hunk) auto-merged unchanged. (cherry picked from commit 51f86c024cd6fff86311ce8fa45a76732f5166d7) * test(live): run the qe-court live test in a disposable project The opt-in qe-court participant-transport test ran its seats in the checkout and cleaned up with a default `ruflo memory purge`, which clears memory.db only; the seats write through MCP to agentdb-memory.db, so 118 rows in 30 ak-qe-court-live-* namespaces stayed in the real store (audit H D7). - tests/live/disposable-memory-project.mjs creates a throwaway project: `git init` (codex exec refuses to run outside a Git repository, and it makes the folder its own memoryProjectRoot for `ak x ruflo-mcp`), claude-flow.config.json {daemon:{autostart:false}} before any ruflo command (the env opt-out does not reach an MCP server Codex launches), `ruflo memory init`, and CLAUDE_FLOW_DB_PATH/CLAUDE_FLOW_MEMORY_PATH pinned inside it; cleanup stops a daemon holding its pidfile (through reap()'s identity check) and deletes the folder - the live test runs every seat there, checks the proof in that project's store, fails on a file edit there or in the checkout, and runs a read-only tripwire that fails if any proof row reaches the checkout's memory - tests/kit/disposable-memory-project.test.mjs covers the helper with an injected runner, a real keep-alive "daemon" child and a real SQLite store; the paid live test itself was not run The helper lives beside the live test because tests/live/*.test.mjs ships in the package; it needs its own `files` entry (manifest change for the integrator). Also shortens the ADR-0016 Updated line added in the previous commit, which pushed the update note past the 1,200 characters ga-surface-guard reads. Docs: ADR-0033 update note and verification bullet, PROVIDERS.md, MAINTAINER.md tarball list. Manifest: package.json `files` gains "tests/live/disposable-memory-project.mjs" next to "tests/live/qe-court-participant-transport.test.mjs" (lane G3 manifestChanges). The live test ships in the tarball and imports this helper, so without the entry a packaged copy cannot resolve its import. MAINTAINER.md already names the helper beside the live test, as ga-surface-guard requires for every `files` entry. Integration (I4): two conflicts, both resolved keeping both intents. (1) ADR-0033: lanes B1 and A2 had already set a descriptive 2026-09-26 Updated line (2026-09-23 became "Earlier update") and appended two 2026-09-26 update-note entries. This commit's date change is already satisfied; its subject is appended to that Updated line in one clause, and its update-note entry follows A2's. Its verification-bullet change auto-merged beside A2's sync-command bullet. (2) ADR-0016: this commit only shortened the l…
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.
What
Two refinements to the kit's append-only status-line footer:
Show the installed Agentic QE version on the
🎓 Agentic QEline, mirroring how ruflo's native header showsRuFlo V<x>:Version is read from
agentic-qe/package.json— preferring the global install (same path style as the existing aidefence probe), falling back to a project-localnode_modulescopy. Guarded by try/catch, so a missing package.json leaves the line unchanged. It rides the existing TTL cache, adding only onefs.readFileSync(zero extra process spawns).Regroup the footer divider. Previously a 44-char rule sat above the SONA line, splitting ruflo's native block from the footer. Since SONA + aidefence are ruflo features, that rule was placed awkwardly. Now:
'─'.repeat(53)), so the two rules line up.Where
Single source of truth:
rufloActivationSegments()inshell/ruflo-functions.sh(the injector thatruflo-fix-statusline-version/ruflo-resyncuse to regeneratestatusline.cjs). README example + field descriptions updated to match.Verification
bash -n shell/ruflo-functions.sh✓node --checkon the regeneratedstatusline.cjs✓