v1.17.0
Agent-CLI round: the arch CLI audited against the 7 Principles for Agent-Friendly
CLIs rubric and hardened where it fell short.
The audit's good news is that the foundations already held — zero interactive prompts, zero ANSI
colour, --json almost everywhere, a documented 0/2/1/3 exit contract, a uniform stdin - seam,
and JSON diagnostics that already carry their own fixes. What it found was one blocker (a CLI an
agent literally could not ask for help), a parser that silently swallowed typo'd flags as
filenames, a fix that clobbered your source with no preview and no recovery, and reads that
had no way to be narrowed. All four are closed here. The language is untouched; compile() stays
pure and its default output byte-identical.
Added
- Per-command help — the blocker.
arch <cmd> --helpandarch help <cmd>now print help.
Before this,--helpafter a verb fell throughparseArgsinto the positionals and was read as a
filename (cannot read --help), so the standard two-hop probe an agent uses to learn a CLI —
top-level help, then subcommand help — was broken at the second hop. - The manifest now carries worked
examples[]per command (a required field), and a new
src/cli/help.tsrenders both the top-level and the per-command help from the manifest.
The hand-maintainedHELPstring — a second, un-drift-tested source of truth for the command list
— is gone, so help can no longer advertise a flag a command doesn't take. arch --version.arch fix --backup— saves the original bytes to<file>.bakbefore rewriting in place
(opt-in, so no.baklitter by default), andfixnow prints the unified diff it would write
to stderr,--dry-runincluded.fixrewrites your source; it should be previewable and
reversible.- Bounded reads (so one big plan can't blow an agent's context window):
arch describe --select <keys>/--room <ids>,arch lint|validate --code <CODE>/
--severity <sev>, andarch context --section <spec|workflow|cli|errors>— the error catalog
alone drops the bundle from 60,187 → 13,161 bytes. unifiedDiffmoves fromdataset/into the pure core assrc/unified-diff.ts(zero-dep,
deterministic);dataset/diff.tsre-exports it.
Changed
- An unrecognized flag or verb is now a usage error (exit 3), not a silently-swallowed filename.
AFLAG_KEYSparse table replaces the old if/else chain and is bidirectionally drift-tested
against the manifest, so the parser, the help, and the docs cannot disagree about what a command
accepts.arch lint --jsnnow exits 3 withdid you mean \--json`?and ausage:echo;arch complesuggestscompile`. - Human-mode diagnostics print the catalog's
= fix:line. JSON mode already carried it; a
human (or an agent reading stderr) previously had to make a second call toarch explain <CODE>. arch lint --profile <bogus>routes throughusageErrorlike every other bad-usage path (it used
to return a bare3with an ad-hoc, prefix-less message).arch mdvalidates-fthrough the sharedparseFormatinstead of a hand-rolled check, so an
unknown format gives the same error everywhere.- Bare
archprints its help to stderr (exit code 3, unchanged) rather than stdout — a missing
command is a usage error, and its output shouldn't pollute a pipe. - Previously-ignored misplaced flags (e.g.
arch describe --strict, on a command that takes no such
flag) now exit 3 instead of being ignored.
Invariants pinned by tests
- A display filter never changes gating.
--code/--severity/--select/--roomfilter
what is shown; the exit code andokare computed from the unfiltered diagnostic set, so
lint --code W_FOOon a plan failing for a different reason still fails. - The
context --sectionsplitter is welded to its generator by a test that regenerates
llms-full.txtin memory and asserts the split — a format change breaks loudly instead of
silently slicing garbage.