From fb517e61715f8fecd329f452100302fe78f04d1d Mon Sep 17 00:00:00 2001 From: Kurt Overmier Date: Sat, 17 Oct 2026 20:30:58 -0500 Subject: [PATCH 1/2] fix(cli): fill help-text gaps for doctor, adf tidy, adf vendor, adf context (#363) - Root --help: doctor line lists --ci and --format, plus the hook-only --staged/--tidy-skipped; the adf entry names all 15 subcommands. - adf --help: describe tidy --source and --print-paths; one line per adf vendor subcommand with its flags; add the missing adf context entry. Merged the fmt/sync --explain blocks and rewrapped a few descriptions so adf.ts stays under its LOC ceiling (net -1 line). - cli-reference: document doctor --format json. - Tests pin the new help lines and check that the adf subcommand list in both helps matches adfCommand's dispatch. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 1 + docs/cli-reference.md | 1 + .../cli/src/__tests__/help-routing.test.ts | 52 +++++++++++++++++++ packages/cli/src/commands/adf.ts | 46 ++++++++-------- packages/cli/src/index.ts | 10 +++- 5 files changed, 85 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 46f24b76..8f4520a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ The format is based on Keep a Changelog and follows Semantic Versioning. - **The interactive y/N prompts (`bootstrap`'s "Register these modules now?" and the `--reset-adf` "Replace them?") left the CLI to exit 0 mid-run when stdin closed at the prompt or Ctrl-C was pressed** (`#375`). Ctrl-D on an empty line (or any close before an answer) now counts as N, with `No answer (input closed); treating it as N.` on stderr: bootstrap leaves the orphan modules unregistered and carries on, and `--reset-adf` is declined (exit 1, nothing written). Ctrl-C stops the command: it prints `charter: Aborted.` on stderr and exits 130 (the usage-log event is still recorded), so bootstrap does not go on to migrate agent configs or install packages. - **`charter init --guided --preset ` silently ignored `--preset`** (`#375`). It now seeds the answer: the preset is used when the language answer names no known stack (blank, or something like `cobol`), and the first question says `blank keeps --preset `. A recognized language or a Cloudflare `y` still decides, as before. - Preset lists named four or five presets where six are accepted: the root `--help` (`charter init`, `charter setup` and the `--preset` option), `charter adf --help`, `packages/cli/README.md`, `docs-snippets/charter-cli-reference.md` and the Global Flags table of `docs/cli-reference.md` now list all six (`worker|frontend|backend|fullstack|docs|rust-wasm`). +- **Help text gaps** (`#363`). The root `--help` lists `charter doctor [--adf-only] [--ci] [--format text|json]` and the pre-commit-hook-only `--staged [--tidy-skipped ""]`, and its `charter adf` entry names all fifteen subcommands (it named nine). `charter adf --help` describes `tidy --source` and `--print-paths`, gives each `adf vendor` subcommand (`status`, `set`, `init`, `restore`, `rename-legacy`) its own line and flags, and documents `adf context`, which it left out. `docs/cli-reference.md` lists doctor's `--format json`. ## [2.1.0] - 2026-10-17 diff --git a/docs/cli-reference.md b/docs/cli-reference.md index 8f95b0c9..1c497618 100644 --- a/docs/cli-reference.md +++ b/docs/cli-reference.md @@ -169,6 +169,7 @@ npx charter doctor --ci --format json # CI mode: exit 1 on warnings - `--adf-only` — run only ADF readiness checks, skip Charter config validation - `--ci` — non-interactive, exits with policy violation code on warnings +- `--format json` — print the result (`status`, `checks`) as JSON instead of text - `--staged` — the pre-commit hook's gate ([#357](https://github.com/Stackbilt-dev/charter/issues/357)): the `adf vendor bloat` check counts only vendor files staged in the git index (staged deletions excluded). It selects files by name and still reads their working-tree content, not the staged blob. Bloat in other vendor files moves to an INFO row, `adf vendor bloat (not blocking)`, so it does not fail `--ci`. No other check is scoped. If the index cannot be read, every vendor file counts, as without the flag. - `--tidy-skipped ""` — with `--staged` only: space-separated vendor files the hook's tidy block skipped. Their bloat goes to the same INFO row. The value may be empty (`""`). Used without `--staged`, it is a usage error (exit 2). - `CHARTER_HOOK_SCOPED_DOCTOR` (environment) — set only by the generated pre-commit hook while it runs `verify:adf`, after its own `--staged` doctor. Doctor honours it only when its value has the hook's token form (`-`) and the file `charter-hook-scoped-doctor-` in the git directory holds that token. It then reports the `adf vendor bloat` check as INFO (`adf vendor bloat (checked by pre-commit hook)`) and says so on stderr. No other check changes. The variable on its own (no matching file) has no effect, and `--staged` takes precedence over it. diff --git a/packages/cli/src/__tests__/help-routing.test.ts b/packages/cli/src/__tests__/help-routing.test.ts index 33844c61..b776a508 100644 --- a/packages/cli/src/__tests__/help-routing.test.ts +++ b/packages/cli/src/__tests__/help-routing.test.ts @@ -107,6 +107,58 @@ describe('--help routing', () => { expect(createdEntries).toEqual([]); }); + it('documents doctor\'s --ci, --format and hook-only --staged/--tidy-skipped flags in the root help (#363)', async () => { + const { output } = await captureRun(['--help']); + + expect(output).toContain('charter doctor [--adf-only] [--ci] [--format text|json]'); + expect(output).toMatch(/--staged \[--tidy-skipped ""\]: pre-commit hook only/); + }); + + // The subcommands adfCommand dispatches (its "Supported:" list). Each must be + // named in the root help's adf entry and documented in `adf --help`. + const ADF_SUBCOMMANDS = [ + 'init', 'fmt', 'patch', 'create', 'populate', 'bundle', 'sync', 'evidence', + 'migrate', 'tidy', 'metrics', 'context', 'compile', 'suggest', 'vendor', + ]; + + it('keeps the subcommand list above in step with adfCommand\'s dispatch', async () => { + const message = await captureRun(['adf', 'no-such-subcommand']).then( + () => '', + (err: Error) => err.message, + ); + const supported = message.match(/Supported: (.*)$/)?.[1].split(', '); + + expect(supported).toEqual(ADF_SUBCOMMANDS); + }); + + it('lists every adf subcommand in the root help and in `adf --help` (#363)', async () => { + const root = (await captureRun(['--help'])).output; + const adfEntry = root.slice(root.indexOf('charter adf '), root.indexOf('charter serve')); + const adf = (await captureRun(['adf', '--help'])).output; + + for (const sub of ADF_SUBCOMMANDS) { + expect(adfEntry).toMatch(new RegExp(`[(\\s]${sub}[,)]`)); + expect(adf).toContain(`charter adf ${sub}`); + } + }); + + it('describes tidy --source/--print-paths and each adf vendor subcommand in `adf --help` (#363)', async () => { + const { output } = await captureRun(['adf', '--help']); + + expect(output).toContain('--source : tidy only this file; repeat it'); + expect(output).toContain('--print-paths: print only `vendor ` then `module ` lines'); + expect(output).toContain('--mode compiled: with --write'); + for (const usage of [ + 'status [--all]:', + 'set [--dry-run]:', + 'init [--dry-run]:', + 'restore [--dry-run]:', + 'rename-legacy [--dry-run]:', + ]) { + expect(output).toContain(` ${usage}`); + } + }); + it('still reports the version for `--version` after a command name', async () => { const { exitCode, output } = await captureRun(['adf', '--version']); diff --git a/packages/cli/src/commands/adf.ts b/packages/cli/src/commands/adf.ts index 8aa0e12a..dbf6c1cc 100644 --- a/packages/cli/src/commands/adf.ts +++ b/packages/cli/src/commands/adf.ts @@ -903,11 +903,11 @@ function printHelp(): void { console.log(' --preset : scaffold preset-aware modules (worker|frontend|backend|fullstack|docs|rust-wasm)'); console.log(' --emit-pointers: also generate thin pointer files (CLAUDE.md, .cursorrules, AGENTS.md)'); console.log(''); - console.log(' charter adf fmt [--check] [--write]'); + console.log(' charter adf fmt [--check] [--write] | charter adf fmt --explain'); console.log(' Parse and reformat an ADF file to canonical form.'); console.log(' --check: exit 1 if not canonical (no write)'); console.log(' --write: reformat file in place'); - console.log(' Default: print formatted output to stdout.'); + console.log(' Default: print formatted output to stdout. --explain: show the canonical section ordering.'); console.log(''); console.log(' charter adf patch --ops | --ops-file '); console.log(' Apply ADF_PATCH operations to a file.'); @@ -929,33 +929,26 @@ function printHelp(): void { console.log(' charter adf bundle --task "" [--ai-dir ]'); console.log(' Resolve manifest modules for a task and output merged context.'); console.log(''); - console.log(' charter adf fmt --explain'); - console.log(' Show canonical section ordering used by the formatter.'); + console.log(' charter adf context [--files ""] [--keywords ""] [--bundle] [--ai-dir ]'); + console.log(' Resolve which modules apply to file paths and/or keywords; --bundle: emit the merged ADF document.'); console.log(''); - console.log(' charter adf sync --check [--ai-dir ]'); - console.log(' Verify source .adf files match their locked hashes.'); - console.log(' Exit 1 if any source has changed since last sync.'); - console.log(''); - console.log(' charter adf sync --write [--ai-dir ]'); - console.log(' Update .adf.lock with current source hashes.'); - console.log(''); - console.log(' charter adf sync --explain'); - console.log(' Show .adf.lock schema documentation.'); + console.log(' charter adf sync <--check | --write | --explain> [--ai-dir ]'); + console.log(' --check: verify source .adf files match their locked hashes; exit 1 if any changed since last sync'); + console.log(' --write: update .adf.lock with current source hashes'); + console.log(' --explain: show .adf.lock schema documentation'); console.log(''); console.log(' charter adf evidence [--task ""] [--ai-dir ] [--auto-measure]'); console.log(' [--context \'{"key": value}\']'); console.log(' Validate metric constraints and produce a structured evidence report.'); console.log(' --task: resolve on-demand modules for task. Omit for defaultLoad only.'); console.log(' --auto-measure: count lines in files from manifest METRICS section.'); - console.log(' --context: JSON object of external metric overrides (wins over auto).'); - console.log(' --context-file: read --context JSON from a file instead.'); + console.log(' --context: JSON object of external metric overrides (wins over auto); --context-file: read it from a file.'); console.log(' In --ci mode, exit 1 if any constraint fails.'); console.log(''); console.log(' charter adf migrate [--dry-run] [--source ] [--no-backup]'); console.log(' [--merge-strategy append|dedupe|replace] [--ai-dir ]'); - console.log(' Ingest existing agent config files (CLAUDE.md, .cursorrules, etc.) and'); - console.log(' migrate their content into structured ADF modules. Replaces originals'); - console.log(' with thin pointers that retain environment-specific rules.'); + console.log(' Ingest existing agent config files (CLAUDE.md, .cursorrules, etc.) into structured ADF modules.'); + console.log(' Replaces originals with thin pointers that retain environment-specific rules.'); console.log(' --dry-run: preview migration plan without writing files'); console.log(' --source: migrate a single file instead of scanning all agent configs'); console.log(' --no-backup: skip creating .pre-adf-migrate.bak backups'); @@ -965,6 +958,9 @@ function printHelp(): void { console.log(' Scan vendor config files for content added beyond the thin pointer,'); console.log(' classify and route to ADF modules, restore thin pointer.'); console.log(' --dry-run: preview without modifying files'); + console.log(' --source : tidy only this file; repeat it to tidy several, in argument order'); + console.log(' --print-paths: print only `vendor ` then `module ` lines for files tidy writes'); + console.log(' (would write, with --dry-run); no lines means nothing written. Not with --format json.'); console.log(' --ci: exit 1 if bloat found (with --dry-run, for pre-commit gating)'); console.log(''); console.log(' charter adf metrics recalibrate [--headroom ] [--reason ""|--auto-rationale] [--dry-run]'); @@ -980,15 +976,19 @@ function printHelp(): void { console.log(' --force: with --write, overwrite hand-authored content (never changes a file\'s mode)'); console.log(' Default (no --write/--check): print to stdout (single target only)'); console.log(''); - console.log(' charter adf vendor | init | restore | rename-legacy> [--ai-dir ] [--dry-run]'); + console.log(' charter adf vendor [--ai-dir ]'); console.log(' Show or record whether each vendor file is a pointer, compiled output, or unmanaged'); - console.log(' (VENDORS in manifest.adf). adf compile and adf tidy act on it. restore rewrites one pointer, refusing on any content loss.'); + console.log(' (VENDORS in manifest.adf). adf compile and adf tidy act on it.'); + console.log(' status [--all]: recorded or inferred mode of each vendor file (read-only; --all adds absent names)'); + console.log(' set [--dry-run]: record one file\'s mode'); + console.log(' init [--dry-run]: record the inferred mode of every unrecorded vendor file on disk'); + console.log(' restore [--dry-run]: rewrite one pointer from its template; refuses on any content loss'); + console.log(' rename-legacy [--dry-run]: rename a pre-#297 agents.md to AGENTS.md, moving its VENDORS key'); console.log(''); console.log(' charter adf suggest [--min-occurrences ] [--window-minutes ] [--ai-dir ]'); console.log(' [--emit-ops ]'); - console.log(' Diagnostics over .charter/telemetry/events.ndjson: dead modules,'); - console.log(' recurring unmatched keywords, and modules that load yet a downstream'); - console.log(' command still fails — all report-only, no module attribution guessed.'); + console.log(' Diagnostics over .charter/telemetry/events.ndjson: dead modules, recurring unmatched keywords,'); + console.log(' and modules that load yet a downstream command still fails (report-only, no attribution guessed).'); console.log(' Also reports co-occurrence trigger candidates (a weaker, non-failure-'); console.log(' backed heuristic: keyword X often appears alongside a keyword that'); console.log(' already triggers module M). --emit-ops writes non-ambiguous candidates'); diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index bfea503b..e4a39a01 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -68,7 +68,9 @@ Usage: charter hook install --pre-commit [--force] Install git pre-commit hook for ADF evidence gate charter hook print --claude Print Claude Code session hook config (paste into .claude/settings.json) - charter adf ADF context format tools (init, fmt, patch, create, bundle, sync, evidence, migrate, metrics) + charter adf ADF context format tools; details: charter adf --help + (init, fmt, patch, create, populate, bundle, sync, evidence, migrate, tidy, + metrics, context, compile, suggest, vendor) charter serve [--name ] [--ai-dir ] Expose ADF project context as an MCP server (stdio, for Claude Code/Codex/Cursor) charter score [--ai-dir ] AI-readiness audit for the current repo @@ -80,7 +82,11 @@ Usage: --exclude : skip paths matching glob (repeatable; supports ** and *) charter telemetry report Local telemetry summary (passive CLI observability) charter why Explain why teams adopt Charter and expected ROI - charter doctor [--adf-only] Check CLI + config health (or ADF-only wiring checks) + charter doctor [--adf-only] [--ci] [--format text|json] + Check CLI + config health (or ADF-only wiring checks) + --ci: exit 1 on any WARN; --format json: print the checks as JSON + --staged [--tidy-skipped ""]: pre-commit hook only; vendor bloat + blocks only in staged files (minus those tidy skipped), the rest is INFO charter --help Show this help charter --version Show version From 039826bf4d300a82db64fd560f31c63bef9a310a Mon Sep 17 00:00:00 2001 From: Kurt Overmier Date: Sat, 17 Oct 2026 20:47:12 -0500 Subject: [PATCH 2/2] fix(cli): tighten adf context/sync/tidy --ci usage lines from review adf context needs --files or --keywords; sync --explain takes no --ai-dir; tidy --ci only gates with --dry-run. Same line count. Co-Authored-By: Claude Opus 5.5 --- packages/cli/src/commands/adf.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/cli/src/commands/adf.ts b/packages/cli/src/commands/adf.ts index dbf6c1cc..d097f5c4 100644 --- a/packages/cli/src/commands/adf.ts +++ b/packages/cli/src/commands/adf.ts @@ -929,10 +929,10 @@ function printHelp(): void { console.log(' charter adf bundle --task "" [--ai-dir ]'); console.log(' Resolve manifest modules for a task and output merged context.'); console.log(''); - console.log(' charter adf context [--files ""] [--keywords ""] [--bundle] [--ai-dir ]'); + console.log(' charter adf context <--files "" and/or --keywords ""> [--bundle] [--ai-dir ]'); console.log(' Resolve which modules apply to file paths and/or keywords; --bundle: emit the merged ADF document.'); console.log(''); - console.log(' charter adf sync <--check | --write | --explain> [--ai-dir ]'); + console.log(' charter adf sync <--check | --write> [--ai-dir ] | charter adf sync --explain'); console.log(' --check: verify source .adf files match their locked hashes; exit 1 if any changed since last sync'); console.log(' --write: update .adf.lock with current source hashes'); console.log(' --explain: show .adf.lock schema documentation'); @@ -961,7 +961,7 @@ function printHelp(): void { console.log(' --source : tidy only this file; repeat it to tidy several, in argument order'); console.log(' --print-paths: print only `vendor ` then `module ` lines for files tidy writes'); console.log(' (would write, with --dry-run); no lines means nothing written. Not with --format json.'); - console.log(' --ci: exit 1 if bloat found (with --dry-run, for pre-commit gating)'); + console.log(' --ci: with --dry-run, exit 1 if bloat found (pre-commit gating); no effect without --dry-run'); console.log(''); console.log(' charter adf metrics recalibrate [--headroom ] [--reason ""|--auto-rationale] [--dry-run]'); console.log(' Recalibrate metric baselines/ceilings from current LOC with required rationale.');