feat(cli)!: one --refresh[=live|machine] flag for status, system and maintain (ADR-0063) - #263
Merged
Merged
Conversation
readLimits gates the app-server spawn on providers.mjs's new
recordedHostPresence('codex'), reading the host-setup evidence detectHosts
already records for every host on each /api/status poll instead of probing.
Absent, stale (>6h) or PATH-invalidated evidence reads as 'unconfirmed', never
'not-found'; either non-'found' outcome serves the last cached figure (if any)
and skips the spawn, and the Limits panel now names both new reasons instead
of falling back to its generic sentence.
…not requested codexFailedShort prefixed every Codex unavailable reason with "last refresh failed", which is false for host-not-found/host-unconfirmed: B6b-D1's presence gate skips the app-server spawn entirely for those, so no refresh was ever attempted. Those two reasons now render "not refreshed" instead, alongside every other reason keeping "last refresh failed" unchanged. Also matches the two new CODEX_WHY strings' apostrophes (’) to their neighbours.
…arting with ak status src/lib/refresh.mjs holds the shared refresh operation (ADR-0063): the strengths (a bare --refresh, =live, =machine), one ordered stage table, the stage runner, the CLI stage set over one lazily built collector, Maintenance service and management facade, and the stage renderer. The bin rewrites a bare --refresh to --refresh= before parseArgs for commands whose refresh option takes a value. ak status takes the flag. --deep and --live are gone and get the parser's generic unknown-option error. --refresh=live runs the same live checks the old --live ran, then re-checks local evidence so the rows show them. With --json, every human line goes to stderr while the stages run and stdout carries one JSON object with a refresh summary. A failed stage exits 1. The stale-result hint on remembered live checks now names ak status --refresh=live. humanOutputToStderr moves from sync.mjs to output.mjs unchanged.
…sals for stray arguments and half-injected services The status help said a refresh under --json sends its stage lines to stderr; no stage lines print there at all, only what a stage itself writes. The live stage now announces its start with a dim line, since the checks can run for a minute with nothing else on screen. A refresh with a positional (ak status --refresh live) is a usage error naming the one-token spelling instead of silently running the bare strength. cliRefreshStages refuses a Maintenance service or facade injected without the other, because the half it would build uses the default control root. A test pins that the machine stages' ticker stays off stdout under --json.
…eftovers in Branch 9
The live checks and slow proofs move to src/lib/live-checks.mjs. --only names checks for --refresh=live and decides the exit code; without it the quick checks that apply run (mcp whenever Codex is enabled) and a failure stays a warning. Slow proofs (learning, harvest, aqe, memory-routes) run only when named. Results are remembered as status-refresh-live; records from the earlier sources read as an earlier live check. ak x verify is gone and gets the generic unknown plumbing command error. The nightly deep proofs call the new spelling.
Rows and failed refresh stages still print and reach --json, but with --only the exit code is 1 only when a named check failed, was inconclusive or did not run. ak system refuses --only: it reports no live checks. The live-check evidence store takes its id list from the refresh vocabulary, and a named check that does not apply says on its line that its result is not remembered.
`ak sync --skip versions` (and `--skip self`, `--skip ruvnet-brain`) still made the forced pre-plan lookup for that part, and the plan read fetched again whenever the 24 h cache had expired. A skipped part is now never looked up: the plan read and the converge proof get what kit.json recorded for it, through a new `versionEvidence` on status's collect() context. The plan-time lookups move out of sync.mjs into src/commands/sync/plan-versions.mjs: refreshPlanVersions makes the forced lookups for every part --skip does not name and returns cache-only evidence for the parts it does; skippedVersionEvidence is recomputed for the converge proof, so installed versions are read after the apply phase. The health snapshot reads the same cache-only drift when versions is skipped. A sync that skips nothing behaves as before (ADR-0063). versions.mjs gains cachedOnlyLatest, a fetchLatest that never touches the network. ruvnet-brain.mjs drift() gains cacheOnly, which reports the recorded release with no lookup and no write; its cached read and its lookup become two small helpers. converge takes an injectable brainDrift for tests.
…ding it A dry run now makes the lookups a real sync makes before it plans (npm versions for ruflo, agentic-qe and npm-managed hosts, the kit's own tags, Ruflo's release dates, the Brain release while ak manages it) and records nothing: driftReport, selfDrift and the Brain drift take record: false, and the release dates land in a copy of kit.json. The results reach the plan read through versionEvidence. Every npm view in the preview runs with its npm cache in a per-run folder under the OS temp directory (logs off, update notifier off), removed once the lookups are done, so ~/.npm is untouched. When every lookup fails, the dry run prints one line saying versions were not checked online and the plan uses the recorded ones. A registered ruvector is read the way a real sync's plan read does, looked up once its cache has expired, again without recording. --dry-run --no-upgrade makes no lookup and reads every part from the cache. Before this change, a dry run on an expired cache wrote kit.json and ~/.npm. A failed lookup never erases a good one: when the Brain's GitHub release or ruvector's npm lookup answers nothing, the recorded latest (and the Brain's asset fact) stays, last is not restamped so the next call retries, and the drift reports it as a cache fallback. This holds on every path, including ak status --refresh. Each Brain drift helper now returns its own source. --skip ruvector leaves ruvector's lookup out, like versions, self and ruvnet-brain: both sync reads get the recorded ruvector drift. The skip tests now answer newer versions for the skipped part, so a leaked lookup changes that part's kit.json record and fails the byte comparison.
… TTL window before retrying When the Brain's GitHub release or ruvector's npm lookup answers nothing, the recorded latest (and the Brain's asset fact, install record and held refresh) stays in kit.json and is reported as a cache fallback, and last is restamped. An offline ak status or dashboard poll therefore waits on these lookups once per TTL window instead of on every read, and ak status --refresh still forces a retry. A new observedAt field keeps when the recorded latest was actually seen, carried forward across failures, so the Brain row's observation label never names the failed attempt. record: false and cacheOnly still write nothing. The dry-run preview makes its temporary npm cache folder only when an npm lookup runs, and a failed removal of that folder no longer fails the preview. Its offline line gives the age of the parts it could not check, or the oldest of them when they differ. The sync command tests' dry runs now answer every version lookup themselves, so a machine with npm in /usr/bin never queries the real registry from the suite.
…aintainer restructure)
No registry is configured for any installation, so ak maintain recipes refresh, the ADR-0048 v2 route, the facade method, and the recipeRegistry/fetchImpl service options that existed only for it can never succeed. Remove every user-reachable path; the recipe store's verified-staging function (allowlist/HTTPS/redirect/signature checks, maintenance/management/recipes.mjs) and its own tests stay as the library a future registry calls. The retired recipes refresh subverb gets the parser's generic unknown-subverb error, and the retired v2 route answers 405 exactly like any other unknown method or route.
…ever build a default service beside injected stages
…aits one TTL window driftReport and selfDrift now follow the rule the Brain and ruvector lookups already use. When every npm lookup fails, the recorded versions (seen, and the kit's best candidate) stay in kit.json and last is restamped, so an offline ak status, dashboard poll or drift nudge waits on the registry once per TTL window instead of on every read. force (a real ak sync, ak status --refresh) still retries. observedAt keeps when the recorded versions were actually seen (per package for driftReport, and now for selfDrift too), taken from the previous last for a record written before it existed, so the dry-run offline line still gives the age of the recorded versions. A partial kit lookup whose cached candidate still wins renews nothing, as before. record: false never writes. The reads for a part --skip names now pass cacheOnly (no lookup, no write) instead of a lookup that always fails, which the new rule would otherwise have restamped; cachedOnlyLatest is removed. Two tests that read kit.json after a failed forced lookup now compare only what they meant to: the versions part of the record, and the config the live checks saw.
…e sync goes on Before it reads the plan, a real ak sync re-checks each enabled host and the shared host setup. A probe that threw used to end the whole sync. Each host's probe and the host-setup probe are now guarded: a warning names the host (or host setup) and the error, and the plan reads that host as ak last recorded it. kit.json itself is still read unguarded, so an unreadable config keeps its own error and recovery text.
The non-dry sync tests inside withOpencodeCli put /usr/bin on PATH, so on a machine with /usr/bin/npm their forced version lookups would have queried the real npm registry. realSync now injects offline lookups, as the dry-run helper already did, and so does the child-process helper the --json tests use.
…kip lines print once
A --json invocation now always gets exactly one JSON object on stdout, with
every human line (the message, the help, the recovery commands) on stderr.
- An option the parser rejects answers { error, exitCode: 2 }; ak sync answers
its own empty result shape with error (jsonUsageError). A retired spelling
gets the parser's generic unknown-option text, with no hint.
- The usage errors ak status, ak system and ak maintain raise themselves (a
bad --refresh value, a stray strength argument, --only or --project-trees
misuse, an unknown maintain verb) answer { error, exitCode: 2 }, each
through its command's one usage-error path.
- An unreadable kit.json answers { error, exitCode: 1, recovery } from the
bin, and ak sync --json adds recovery to its result. configErrorRecovery in
config.mjs builds the same mv or Move-Item commands the human output prints.
Without --json the output is unchanged. ak sync no longer repeats a skipped
item's "skipped by request" line in the verdict when the plan announcement
already printed it; the JSON skipped list is unchanged. The help for sync,
status, system and maintain, and the sync --json notes in UPGRADING and
INSTALLATION, describe the JSON failure answers.
A failed lookup restamps last even when nothing was ever recorded, so the dry-run offline line fell back to that last and said "this plan uses the versions ak recorded just now" (or "2d ago" for an expired ruvector record) where it should say ak has recorded none. Each part's last now stands for an observation only when its record holds a latest: seen entries, the kit's best, the Brain's or ruvector's latest. A stable install whose recorded kit candidate came from the next channel can never use it, so its record is never fresh and every offline lookup restamped and rewrote kit.json. That failure now writes nothing, as before the restamp rule. Dropping the candidate instead would make an empty record look fresh and stop the lookup for a TTL window once the registry is back, so ak status could not show a real latest.
…json after -- is positional
ak maintain's catch-all turned every thrown error into a usage refusal, so
an unreadable kit.json under a verb that reads it (discovery, sources add)
answered { error, exitCode: 2 } with no recovery. A config error now reaches
the CLI's fatal handler: { error, exitCode: 1, recovery } under --json, the
recovery commands otherwise, as for every other command.
A rejected option answered in JSON when --json appeared only after the --
terminator, where it is a positional. Only the tokens before -- now decide.
Add tests/kit/refresh-vocabulary-guard.test.mjs (R16-R18) to guard the CLI vocabulary and rewrite every retired spelling it finds (ak x verify, ak status --live/--deep, ak system --deep, ak maintain scan, --refresh-inventory, ak host refresh, ak usage prompts --deep, ak maintain recipes refresh) to its current CLI spelling across README, docs/, claude/ and src/ comments, with no old -> new tables. Dashboard docs keep today's control names and state the new CLI equivalents beside them, as this branch closes CLI-only. Corrects two stale DASHBOARD.md claims (Maintenance does reload on the shared poll; the quoted re-measure status text does not exist) and documents the two new "not refreshed" Codex reasons. Replaces the ledger-only B6b-D1 / Branch 6b Task 1 labels in source/test comments with ADR-0010, and drops the dangling (R17) suffix from three test assertion messages.
…y narration Recipe trust and the live-check evidence glossary row named what used to exist instead of only the current state (R17); AQE-EMBEDDINGS.md named the wrong live check for the unmanaged-backend note (the full aqe proof, not aqe-embedding); the troubleshooting "run everything" example named memory redundantly alongside memory-routes, which already covers it.
…t pick --dry-run previews
… retired verbs in listed alternatives
ADR-0048 header: this branch's CLI-equivalents line stays the Updated line; Branch 9's M1b line becomes the first Earlier update.
ak maintain --refresh=live --only <check> accepted the flag and ran the
live checks, but always exited 0 regardless of a failed named check: unlike
ak status, ak maintain never renders live-check results or reports their
verdict, so the flag was silently accepted and then ignored (final review
I-1). --only is now refused with exit 2 for every verb, including the
default report, mirroring ak system's own refusal.
refreshedReport also built two unrelated collector instances for one
--refresh: defaultStages built its own for the machine/inventory stages and
the management facade, while createMaintenanceService() (no args) built a
second, cold one for the maintenance stage and the later service.report()
read (m-2). The collector is now built once in refreshedReport and threaded
through both.
renderReport printed only the scan-required finding's generic label ("Run
Maintenance scan") on an unmeasured machine, naming no command — a new
user's first hint invited the retired ak maintain scan verb. It now also
prints the finding's own nextAction.steps[0], the exact refresh command
(m-3).
The post-command drift nudge ran driftReport() (which shells npm view) whenever mod.run() returned, without checking its exit code. A run-level usage refusal a command raises itself — e.g. a rejected ak status --refresh=bogus — reached the nudge exactly like a successful run and spent a network call a parse error never did (final review m-1). The nudge (and the local-artifact-drift check beside it) is now also skipped when the command's own exit code is 2.
ak system --help said a bare --refresh (or --refresh=live) "refreshes Maintenance evidence and the inventory, then reprints this same snapshot", omitting that it also re-checks local status (including the online version lookups) and that =live additionally runs the live checks (final review m-6).
…options The guard's adjacency regexes required a retired flag directly after the command word, so a bracketed usage line (system [--deep] [--json], status [--json] [--live]) and ak usage prompts --deep behind unrelated prose slipped through undetected — exactly how four current-state docs kept the retired spellings past the original guard (final review I-2). Two new patterns tolerate intervening single-line bracketed options for status/system, and scan the whole line for prompts ... --deep. Also drops the ledger/plan task label from the file header in favor of an ADR-0063 reference (final review m-8).
machine-footprint.md and ubiquitous-language.md still showed ak system [--deep] [--json]; ubiquitous-language.md also claimed ak system and ak about are "both read-only twins" of their dashboard areas, which is no longer true of ak system --refresh. USAGE-SCORECARD-METRICS.md and prompt-telemetry.md still named --deep as ak usage prompts's text-bearing flag; the current flag is --show-text (final review I-2).
ADR-0010 and ADR-0063 said /api/limits and ak status both go through readLimits to ask codex app-server for its quota. readLimits has exactly one caller, dashboard-server.mjs's /api/limits handler; no status section imports quota.mjs. Both ADRs now say /api/limits only (final review I-3). Both ADRs also cited ledger-only ids (B6b-D1, B6b-D3, M-11) that live only in the gitignored SDD ledger. They now point at the committed branch 6b plan's named rulings (R9, R17) or its "Closing this branch" section instead (final review I-3). Also, ADR-0063's --only bullet read as spanning all three refresh commands; it now says --only is ak status's alone, and that ak system/ak maintain both refuse it outright (paired with the ak maintain code fix). Three precision nits from final review m-5: setup.mjs's collectIntegrationFacts call passes no cwd; selfRecord restamps on a total failure with no cached candidate at all (only a partial answer, or an unusable cached candidate, saves nothing); and ak sync --dry-run's online lookups redirect to, and clean up, a temporary npm cache under the OS temp folder rather than writing "nothing at all".
README.md's compact command table listed ak host status|pick|reset-routes|off, omitting check-connection even though the usage block and ak --help --all both list it (final review m-7).
…llector The m-2 fix built refreshedReport's shared collector unconditionally, even when deps.refreshStages was injected — a fully-injected test (refreshStages + service) used to trigger zero real collector or facade construction (defaultStages was bypassed entirely); it now dynamically imported footprint/index.mjs and constructed a real createSystemCollector() for nothing, contradicting the doc comment two lines above and the plan's hermeticity rule. Collector construction is now skipped outright when deps.refreshStages is injected: the half-injection guard already guarantees deps.service is present in that case, so nothing downstream would have read it anyway. Also adds the one test the review's own wording implies but the diff didn't yet prove: --refresh=live WITHOUT --only stays valid (runs the live stage, reads service.report(), exits 0).
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
One refresh vocabulary across the CLI (ADR-0063):
--refresh[=live|machine]onak status,ak systemandak maintain.--refresh--refresh=live--refresh=machine--refreshrunsEach stage prints one line,
--jsonadds arefreshstage list, and a failed stage exits 1.What changed around it:
ak x verify) run throughak status --refresh=live.--only a,bnarrows the checks, and only the named checks decide the exit code.aqe-embedding,deja-vu,mcp,memory,providers,security. Slow proofs:aqe,harvest,learning,memory-routes.ak host check-connectionis the CLI twin of the dashboard's connection check. It shows the same consent text, works on managed hosts only, and answers in JSON on every path.ak host reset-routesreplacesak host refresh.pick,offandreset-routeshonour--dry-run.ak sync:--skip versions|self|ruvnet-brain|ruvectoralso skips that part's online lookup.--dry-runpreviews real version lookups, records nothing, and removes its temporary npm cache.--jsonfailures print one JSON object on stdout, with recovery guidance for an unreadablekit.json.POST /api/refreshare the next remediation program's work.Breaking changes
Every retired spelling below now exits 2 with the generic parser error. None has an alias or a hint. Every replacement was checked against the parser.
ak x verifyak status --refresh=live --only learning,memory-routes,security,aqe,providers,harvest,deja-vuak status --refresh=livealone runs the quick set.ak x verify all,mcpfor the Codex MCP checkallused to omitmcpak x verify learning|security|aqe|providers|harvest|deja-vu|mcpak status --refresh=live --only <same name>ak x verify memoryak status --refresh=live --only memory-routes--only memoryis the quick round trip, without the route observationak status --liveak status --refresh=liveak status --deepak status, orak status --refreshto re-probeak system --deepak system --refresh=machine--project-treesto measure project working treesak maintain scanak maintain --refreshak maintain scan --refresh-inventoryak maintain --refreshak maintain scan --deep [--refresh-inventory]ak maintain --refresh=machineak maintain scans start --deepak maintain --refresh=machine, thenak maintain scans startak maintain plan --deep …ak maintain --refresh=machine, thenak maintain plan …ak maintain recipes refreshrecipes list|accept|withdrawremainPOST /api/maintenance/v2/recipes/refreshak host refresh/ak x host refreshak host reset-routes/ak x host reset-routesak usage prompts --deepak usage prompts --show-textnode bin/agentic-kit.mjs x verify <suite>node bin/agentic-kit.mjs status --refresh=live --only <suite>memory→memory-routesBehaviour changes under unchanged spellings:
ak maintain/ak maintain --jsonreads the last saved report (verbreport); it used to run the provider scan. Add--refresh[=live|machine]to refresh first.--refreshand--project-treeson any other verb, and--onlyon any maintain verb, are usage errors (exit 2).ak status --refreshalso refreshes Maintenance evidence and rebuilds the inventory before the local re-check, prints one line per stage, and exits 1 when a stage fails.ak system --refresh[=live|machine]runs the same stages and exits 1 on a failed stage.ak sync --dry-runperforms the online version lookups a real sync performs (network access), records nothing, and uses a temporary npm cache it then removes. Host evidence is still read as last recorded.--jsonfailures: a rejected option prints{ error, exitCode: 2 }on stdout, with the message and help on stderr (help used to go to stdout). An unreadablekit.jsonprints{ error, exitCode: 1, recovery }.ak host pick|off|reset-routes --dry-runpreview and write nothing.ak host adapters --dry-runexits 2.status-refresh-live. Records written by the older live checks still read back, labelled "an earlier live check"./api/limits:codexUnavailable.reasoncan now behost-not-foundorhost-unconfirmed, meaning Codex was not asked. The field is additive.Testing
.cjssuites: 238 passed, 0 failed.main(chore(upstream-watch): record the 2026-09-28 closures and replies in the registry #258, fix: Branch 9 follow-ups — Discovery restart state, Partial data card, redundant safety copies #259, fix: unreliable CLAUDE.md → AGENTS.md prose pointer; issue title standard #260, ci: stop every job after 30 minutes #261): 208 of 208 focused tests pass.--dry-run --jsonoutput ofhost off/reset-routes, which goes to the next program.Not in this PR (next remediation program)
POST /api/refresh, and read-only GET routes.--jsonfor usage/models/host/audit/heal andak x ….--dry-run --jsonrefusals answering one JSON object.selfDriftedge cases.🤖 Generated with Claude Code