Root cause (GAP-009, same class as GAP-001..008): docs/hilo-cli.md documented a phantom clap surface (hilo backend add|remove|sync, hilo workspace init|list) while the real clap surface is backend mount|list, workspace mount|unmount. Drift was invisible because nothing diffed docs against the actual hilo --help output before committing.
Root cause (GAP-009, same class as GAP-001..008): docs/hilo-cli.md documented a phantom clap surface (hilo backend add|remove|sync, hilo workspace init|list) while the real clap surface is backend mount|list, workspace mount|unmount. Drift was invisible because nothing diffed docs against the actual hilo --help output before committing.
Fix — three layers (all in ~/<project>):
1. Docs corrected to the real surface (docs/hilo-cli.md): table + examples now contain only real subcommands, verified by the gate below.
2. scripts/verify-cli-docs.sh — the drift gate, run by the pre-commit hook. It performs exactly the three mandated checks:
# (a) Extract the REAL surface from clap --help output (source of truth)
subcommands_of() { # $1 = parent group ("" for top level)
if [[ -n "$1" ]]; then "$HILO_BIN" "$1" --help; else "$HILO_BIN" --help; fi \
| awk '/^Commands:/{in_cmds=1; next} /^Options:/{in_cmds=0}
in_cmds && /^ [a-z][a-z0-9-]* /{print $1}' \
| grep -v '^help$' # clap auto-injects 'help'; not surface
}
# REAL["backend mount"]=1 etc., built from `hilo --help` and `hilo <sub> --help`
# (b) Extract DOCUMENTED surface from docs table + examples
DOCUMENTED="$( {
grep -oE '`hilo [a-z]+ [a-z]+' "$DOCS" | sed 's/^`//; s/^hilo //'
grep -oE '\$ hilo [a-z]+ [a-z]+' "$DOCS" | sed 's/^\$ //; s/^hilo //'
} | sort -u )"
# (c) Bidirectional diff: docs→clap (no phantoms) AND clap→docs (nothing missing)
# (d) PHANTOM GREP: grep -c of phantom names == 0
PHANTOM_PATTERN='hilo (backend (add|remove|sync)|workspace (init|list))'
count="$(grep -cE "$PHANTOM_PATTERN" "$DOCS" || true)" # must be 0
# (e) EXIT CODES: old phantom commands must exit 2 (unrecognized subcommand)
expect_exit 2 "old: hilo backend add s3-nyc" "$HILO_BIN" backend add s3-nyc
expect_exit 2 "old: hilo workspace init dev-a" "$HILO_BIN" workspace init dev-a
3. Pre-commit hook (.githooks/pre-commit, wired via git config core.hooksPath .githooks) runs the verifier and exits nonzero on any drift, and tests/cli_surface.rs enforces the same contract under cargo test against the real binary (CARGO_BIN_EXE_hilo).
Verified against the real clap binary (Rust 1.97, clap 4):
| Check | Drift docs (before) | Fixed docs (after) |
|---|---|---|
Surface diff (documented vs hilo --help/hilo <sub> --help) |
5 phantoms + backend mount missing |
bidirectional match |
grep -c phantom names |
9 | 0 |
hilo backend sync / hilo workspace init exit code |
2 ("unrecognized subcommand") | 2 (unchanged — binary was always right) |
Real commands (backend list, mount, workspace mount/unmount) |
0 | 0 |
Output captured: error: unrecognized subcommand 'sync' ... exit=2.
Edge cases tested:
- Bidirectional diff — catches both phantom docs and docs falling behind (real hilo backend mount was missing from drift docs; flagged).
- Mutation test — re-introduced one phantom table row (hilo backend sync); verifier went PASS(14)→FAIL(grep -c == 1 + surface diff) and the pre-commit hook blocked the commit; drift commit absent from git log.
- list takes no positional arg — integration test passes args only to mount/unmount.
- clap's auto-injected help subcommand excluded from surface extraction so it isn't misreported as undocumented.
- Strict grep-0 gate — the docs' own GAP note must not mention phantom names (they'd self-trip the count); note reworded to describe the class, not the names.
- Exit 2 = clap usage error — verified phantom commands exit 2 while real ones exit 0 (guard against overzealous fix).
Final state: verify-cli-docs.sh: PASS (14 checks), cargo test: 4/4 pass, git status clean, one commit: 841a2f9 GAP-009: fix docs-vs-clap drift.
{"problem_class":"rust-cli-doc-drift-subcommand-surface","model":"deepseek-v4-flash","result":"passed","tests":18}
(tests = 14 shell-verifier checks + 4 Rust integration tests, all passing in the final state; plus a mutation test proving the gate rejects drift.)
Solved by Pi Agent (deepseek-v4-flash).
Root cause (GAP-009, same class as GAP-001..008): docs/hilo-cli.md documented a phantom clap surface (hilo backend add|remove|sync, hilo workspace init|list) while the real clap surface is backend mount|list, workspace mount|unmount. Drift was invisible because nothing diffed docs against the actual hilo --help output before committing.
Fix — three layers (all in ~/<project>):
1. Docs corrected to the real surface (docs/hilo-cli.md): table + examples now contain only real subcommands, verified by the gate below.
2. scripts/verify-cli-docs.sh — the drift gate, run by the pre-commit hook. It performs exactly the three mandated checks:
# (a) Extract the REAL surface from clap --help output (source of truth)
subcommands_of() { # $1 = parent group ("" for top level)
if [[ -n "$1" ]]; then "$HILO_BIN" "$1" --help; else "$HILO_BIN" --help; fi \
| awk '/^Commands:/{in_cmds=1; next} /^Options:/{in_cmds=0}
in_cmds && /^ [a-z][a-z0-9-]* /{print $1}' \
| grep -v '^help$' # clap auto-injects 'help'; not surface
}
# REAL["backend mount"]=1 etc., built from `hilo --help` and `hilo <sub> --help`
# (b) Extract DOCUMENTED surface from docs table + examples
DOCUMENTED="$( {
grep -oE '`hilo [a-z]+ [a-z]+' "$DOCS" | sed 's/^`//; s/^hilo //'
grep -oE '\$ hilo [a-z]+ [a-z]+' "$DOCS" | sed 's/^\$ //; s/^hilo //'
} | sort -u )"
# (c) Bidirectional diff: docs→clap (no phantoms) AND clap→docs (nothing missing)
# (d) PHANTOM GREP: grep -c of phantom names == 0
PHANTOM_PATTERN='hilo (backend (add|remove|sync)|workspace (init|list))'
count="$(grep -cE "$PHANTOM_PATTERN" "$DOCS" || true)" # must be 0
# (e) EXIT CODES: old phantom commands must exit 2 (unrecognized subcommand)
expect_exit 2 "old: hilo backend add s3-nyc" "$HILO_BIN" backend add s3-nyc
expect_exit 2 "old: hilo workspace init dev-a" "$HILO_BIN" workspace init dev-a
3. Pre-commit hook (.githooks/pre-commit, wired via git config core.hooksPath .githooks) runs the verifier and exits nonzero on any drift, and tests/cli_surface.rs enforces the same contract under cargo test against the real binary (CARGO_BIN_EXE_hilo).
Verified against the real clap binary (Rust 1.97, clap 4):
| Check | Drift docs (before) | Fixed docs (after) |
|---|---|---|
Surface diff (documented vs hilo --help/hilo <sub> --help) |
5 phantoms + backend mount missing |
bidirectional match |
grep -c phantom names |
9 | 0 |
hilo backend sync / hilo workspace init exit code |
2 ("unrecognized subcommand") | 2 (unchanged — binary was always right) |
Real commands (backend list, mount, workspace mount/unmount) |
0 | 0 |
Output captured: error: unrecognized subcommand 'sync' ... exit=2.
Edge cases tested:
- Bidirectional diff — catches both phantom docs and docs falling behind (real hilo backend mount was missing from drift docs; flagged).
- Mutation test — re-introduced one phantom table row (hilo backend sync); verifier went PASS(14)→FAIL(grep -c == 1 + surface diff) and the pre-commit hook blocked the commit; drift commit absent from git log.
- list takes no positional arg — integration test passes args only to mount/unmount.
- clap's auto-injected help subcommand excluded from surface extraction so it isn't misreported as undocumented.
- Strict grep-0 gate — the docs' own GAP note must not mention phantom names (they'd self-trip the count); note reworded to describe the class, not the names.
- Exit 2 = clap usage error — verified phantom commands exit 2 while real ones exit 0 (guard against overzealous fix).
Final state: verify-cli-docs.sh: PASS (14 checks), cargo test: 4/4 pass, git status clean, one commit: 841a2f9 GAP-009: fix docs-vs-clap drift.
{"problem_class":"rust-cli-doc-drift-subcommand-surface","model":"deepseek-v4-flash","result":"passed","tests":18}
(tests = 14 shell-verifier checks + 4 Rust integration tests, all passing in the final state; plus a mutation test proving the gate rejects drift.)
Solved by Pi Agent (deepseek-v4-flash).