voyage/examples/01-add-verbose-flag/plan.md
Kjell Tore Guttormsen c3641ffa42
fix: round 5 of the release review of 6.3.0, the structural fix
Round 5 (v6.2.0..d37e538, WARN: 11 MAJOR, 2 MINOR). The patching did not
converge (the same two places every round), so the operator chose a
structural fix on 2026-10-11. Each change behind a test that failed first.

- A check runs with no shell. criteria-runner's runCommand starts the runner
  with the very words the screen checked (spawnSync(words[0], words.slice(1)),
  without NODE_TEST_CONTEXT); Phase 7 criteria, Regression: (its exec copy is
  gone, MINOR 0e98d5c7), proof of red, the done line and the test lock all
  run through it. No quote, glob, tilde or variable can rewrite a checked
  word any more. The screen stays and says why: a word that means something
  else in a shell (quote, backslash, $, brace, leading ~) is refused; a glob
  reaches only node --test, which expands its own patterns (sh read
  `tests/.?/` as `tests/../`); no path climbs out with .. or starts at /;
  a word starting with @ is refused (pytest reads @file as more arguments).
- The K6 plan-file sweep has v6.2.0's own Files: reader again (516dffa):
  backticked paths, and plain words only in a field without them. The
  shared reader from round 3 planned directories a description named and
  dropped paths v6.2.0 planned. fieldPaths stays for the tests-only check,
  with a path test on spans (`parse()`, `p.parse()` are no paths), plain
  descriptions ignored when the field has backticked paths, dotfiles,
  backslash paths and glued spans read.
- Skill frontmatter reads a value as YAML does: trailing space is nothing,
  and a comment ends a plain value, a flow list or a block indicator
  (`model: opus # pinned`). A comment that cuts a plain description, or is
  the whole value, is still a hazard.
- release-check reads the diff with --no-renames: a test moved into an
  allowed place after the review shows as removed.
- The example plan's step 2 heading no longer says "must FAIL initially";
  steps 1-2 keep the tests-only warning (operator 2026-10-09, 73262b6).

Open: MINOR c1fef1ad (one shared isMain helper) is not in this round.
Measured: 0 of 463 Files: fields with backticked paths (520 fields in 182
plans across the marketplace repos) carry another path-like word.

Suite 2185 (2183/0/2).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 00:38:42 +02:00

235 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Add `--verbose` flag to small-auth CLI
plan_version: 1.7
> **Plan quality: A** (92/100) — APPROVE
>
> Generated by trekplan v3.1.0 on 2026-05-01.
## Context
The `small-auth` CLI has six commands and emits only final results; no
progress, no internal step trace. Operators debugging slow `token-refresh`
or mis-routed `users-list` calls have no signal between "started" and
"finished". This plan adds a `--verbose` / `-v` flag that, when set,
emits structured progress lines to stderr without changing stdout. The
default path stays byte-identical.
This is a textbook minimal-scope addition: the parser is small,
centralized, and already supports global flags.
## Architecture Diagram
```mermaid
graph TD
subgraph "Changes in this plan"
cli["src/cli.mjs<br/>parse globalFlags"]
ctx["ctx object<br/>+ verbose: boolean"]
login["src/commands/login.mjs<br/>+ 3 verbose calls"]
token["src/commands/token-refresh.mjs<br/>+ 4 verbose calls"]
userlist["src/commands/users-list.mjs<br/>+ 2 verbose calls"]
usercreate["src/commands/users-create.mjs<br/>+ 3 verbose calls"]
logout["src/commands/logout.mjs<br/>+ 2 verbose calls"]
whoami["src/commands/whoami.mjs<br/>(accepts flag, no traces)"]
help["src/cli.mjs<br/>--help text"]
tests["tests/cli-verbose-flag.test.mjs<br/>tests/cli-no-verbose-stability.test.mjs"]
cli --> ctx
ctx --> login
ctx --> token
ctx --> userlist
ctx --> usercreate
ctx --> logout
ctx --> whoami
cli --> help
login --> tests
end
```
## Codebase Analysis
- **Tech stack:** Node.js ≥ 18, no external runtime dependencies, `node:test` for tests
- **Key patterns:** hand-rolled argv parser, two-pass extract (globals → command), handler contract `run(positional, flags, ctx)`
- **Relevant files:** `src/cli.mjs`, `src/commands/{login,logout,whoami,token-refresh,users-list,users-create}.mjs`, `tests/`
- **Reusable code:** existing `[error]` stderr pattern at `src/cli.mjs:67` — mirror it for `[verbose]`
- **External tech:** none
- **Recent git activity:** parser last changed in commit `ab1c2d3` (added `--version`); pattern still current
## Research Sources
*Internal research only — see `research/01-cli-parser-conventions.md`.*
## Implementation Plan
Each step targets 1–2 files and one focused change. TDD structure: test
or stability harness comes before behavior change.
### Step 1: Capture golden stdout for stability test
- **Files:** `tests/golden/login.stdout` (new file), `tests/golden/whoami.stdout` (new file), `tests/golden/users-list.stdout` (new file)
- **Changes:** Run current CLI for three representative commands, save stdout byte-for-byte. Use `node src/cli.mjs login alice > tests/golden/login.stdout` and similar.
- **Verify:** `wc -c tests/golden/*.stdout` → expected: each file > 0 bytes
- **Checkpoint:** `git commit -m "test(small-auth): capture pre-change golden stdout for verbose-flag stability"`
- **On failure:** revert files; do not proceed. Likely cause: CLI itself broken — investigate before continuing.
- **Manifest:**
```yaml
manifest:
expected_paths:
- tests/golden/login.stdout
- tests/golden/whoami.stdout
- tests/golden/users-list.stdout
min_file_count: 3
commit_message_pattern: "^test\\(small-auth\\): capture"
bash_syntax_check: []
forbidden_paths: []
must_contain: []
```
### Step 2: Add stability test (passes today — the canary for step 5 onwards)
- **Files:** `tests/cli-no-verbose-stability.test.mjs` (new file)
- **Changes:** Three subtests, one per golden file. Each runs `node src/cli.mjs <cmd> ...` and asserts stdout `===` `readFileSync('tests/golden/<cmd>.stdout')`. The test should PASS today (no behavior change yet) — it's the canary for step 5 onwards.
- **Verify:** `node --test tests/cli-no-verbose-stability.test.mjs` → expected: 3 pass
- **Checkpoint:** `git commit -m "test(small-auth): stdout stability harness for verbose-flag work"`
- **On failure:** escalate — if subtests fail, the goldens are wrong: re-run step 1.
- **Manifest:**
```yaml
manifest:
expected_paths:
- tests/cli-no-verbose-stability.test.mjs
min_file_count: 1
commit_message_pattern: "^test\\(small-auth\\): stdout stability"
bash_syntax_check: []
forbidden_paths: []
must_contain:
- path: tests/cli-no-verbose-stability.test.mjs
pattern: "tests/golden/login\\.stdout"
```
### Step 3: Extend parser to recognize `--verbose` and `-v`
- **Files:** `src/cli.mjs`
- **Changes:** At `src/cli.mjs:34` (alias table) add `'-v': '--verbose'`. At `src/cli.mjs:48` (globalFlags loop) add `'--verbose'` case that sets `globalFlags.verbose = true`. Default the field to `false`. The flag is consumed (removed from argv) like `--help` and `--version`.
- **Verify:** `node src/cli.mjs --verbose login alice 2>&1 | head -1` → expected: no parse error
- **Checkpoint:** `git commit -m "feat(cli): recognize --verbose / -v as global flag"`
- **On failure:** revert `src/cli.mjs`; rerun stability test to confirm clean baseline.
- **Manifest:**
```yaml
manifest:
expected_paths:
- src/cli.mjs
min_file_count: 1
commit_message_pattern: "^feat\\(cli\\): recognize --verbose"
bash_syntax_check: []
forbidden_paths: []
must_contain:
- path: src/cli.mjs
pattern: "globalFlags\\.verbose"
```
### Step 4: Pass `verbose` into handler `ctx`
- **Files:** `src/cli.mjs`
- **Changes:** At `src/cli.mjs:62` (ctx construction) add `verbose: globalFlags.verbose` to the ctx literal. No handler changes yet.
- **Verify:** `node --test tests/cli-no-verbose-stability.test.mjs` → expected: 3 pass (handlers ignore the new field for now)
- **Checkpoint:** `git commit -m "feat(cli): thread verbose into command handler ctx"`
- **On failure:** revert — stability tests fail → ctx mutation broke something. Bisect by reverting and adding back one line at a time.
- **Manifest:**
```yaml
manifest:
expected_paths:
- src/cli.mjs
min_file_count: 1
commit_message_pattern: "^feat\\(cli\\): thread verbose"
bash_syntax_check: []
forbidden_paths: []
must_contain:
- path: src/cli.mjs
pattern: "verbose: globalFlags\\.verbose"
```
### Step 5: Wire verbose output in `login`, `token-refresh`, `users-list`, `users-create`, `logout`
- **Files:** `src/commands/login.mjs`, `src/commands/token-refresh.mjs`, `src/commands/users-list.mjs`, `src/commands/users-create.mjs`, `src/commands/logout.mjs`, `tests/cli-verbose-flag.test.mjs` (new file)
- **Changes:** First the test, `tests/cli-verbose-flag.test.mjs`: spawn `node src/cli.mjs login --verbose alice`, capture stderr, assert exit 0, assert stderr contains all three expected verbose lines: "[verbose] parsing argv", "[verbose] credential lookup", "[verbose] issuing session token", in that order. It fails before the wiring below (no verbose lines yet): the test for a behaviour lands in the step that builds it. Then, at each internal step (3 for login, 4 for token-refresh, 2 for users-list, 3 for users-create, 2 for logout — 14 call sites total), add `if (ctx.verbose) ctx.stderr.write(\`[verbose] <step description>\\n\`);`. Step descriptions per file:
- login: "parsing argv", "credential lookup", "issuing session token"
- token-refresh: "parsing argv", "validating refresh token", "rotating session token", "persisting new token"
- users-list: "parsing argv", "querying user store"
- users-create: "parsing argv", "validating input", "writing user record"
- logout: "parsing argv", "invalidating session token"
- **Verify:** `node --test tests/cli-verbose-flag.test.mjs` → expected: 1 pass
- **Regression:** `node --test tests/cli-no-verbose-stability.test.mjs` → expected: 3 pass (stdout unchanged when flag absent)
- **Checkpoint:** `git commit -m "feat(commands): emit verbose stderr trace for 5 commands"`
- **On failure:** retry — stability tests fail → likely a stray `console.log` or `ctx.stdout.write` instead of `ctx.stderr.write`. Re-grep all five files for `stdout` mentions added in this step. If the verbose test misses a line, check the `[verbose]` strings for typos; if its exit code != 0, check that login still works without verbose.
- **Manifest:**
```yaml
manifest:
expected_paths:
- src/commands/login.mjs
- src/commands/token-refresh.mjs
- src/commands/users-list.mjs
- src/commands/users-create.mjs
- src/commands/logout.mjs
- tests/cli-verbose-flag.test.mjs
min_file_count: 6
commit_message_pattern: "^feat\\(commands\\): emit verbose"
bash_syntax_check: []
forbidden_paths: []
must_contain:
- path: src/commands/login.mjs
pattern: "ctx\\.verbose"
- path: src/commands/token-refresh.mjs
pattern: "ctx\\.verbose"
- path: tests/cli-verbose-flag.test.mjs
pattern: "\\[verbose\\] credential lookup"
```
### Step 6: Update `--help` text
- **Files:** `src/cli.mjs`
- **Changes:** At the help-text constant (`src/cli.mjs:78`), add a line under "Global flags": ` -v, --verbose emit per-step trace to stderr (single level only)`.
- **Verify:** `node src/cli.mjs --help | grep -E "verbose"` → expected: 1 line containing "emit per-step trace"
- **Checkpoint:** `git commit -m "docs(cli): document --verbose / -v in --help text"`
- **On failure:** revert just the constant; help text isn't load-bearing.
- **Manifest:**
```yaml
manifest:
expected_paths:
- src/cli.mjs
min_file_count: 1
commit_message_pattern: "^docs\\(cli\\): document --verbose"
bash_syntax_check: []
forbidden_paths: []
must_contain:
- path: src/cli.mjs
pattern: "emit per-step trace"
```
## Verification
Final acceptance run after step 6:
```bash
node --test tests/ # all 26 tests pass (24 + 2 new)
node src/cli.mjs login alice > /tmp/out 2>/dev/null
diff /tmp/out tests/golden/login.stdout # exit 0
node src/cli.mjs login --verbose alice 2>/tmp/err 1>/dev/null
grep -c "\[verbose\]" /tmp/err # ≥ 3
node src/cli.mjs --help | grep -c "\-v, --verbose" # 1
```
## Plan-critic notes
- No deferred decisions: every step names its files, lines, and exact
string changes.
- TDD: stability harness (step 2) precedes behavior changes (steps 3-5),
and the verbose-content test lands in step 5, with the code it tests.
- Verify commands are runnable, not "test it works".
- Step 5 wires 5 files (and their test) in one commit; this is over the 1–2 file
guideline but is justified by symmetry — the change is mechanical
and atomic across the five files; splitting would create five tiny
commits with no test value between them.
## Execution Strategy
Single session, 6 steps, ~15-20 minutes. No parallel decomposition needed.