fix(cli): address Volva code-vs-contract drift (issue #3)
Volva code-review surfaced 5 findings against the TDD-passing implementation; all 5 addressed. Drift fixes (code): - Add `assert argv is None or all(isinstance(a, str) for a in argv)` at both `main` and `_parse_args` entry points (PRE-001 was unenforced). - `main` now catches `SystemExit` and returns `exc.code` verbatim — argparse's --help (SystemExit(0)) was escaping through main as an unhandled exception. Contract amended in-place to spell out the SystemExit-from-argparse-clean-exits passthrough in both `main` and `_parse_args` ERROR_ROUTING. New `help_exits_cleanly` test added per the contract amendment. - Add the PRE-001 union-type assert at `_render_event` entry — unmatched Event variants would have silently no-op'd. - `_run_turn` now awaits `cancel_task` in the `finally` block before returning. Under fast-stream + slow-cancel scenarios the `[cancel_failed]` line could miss being written before _run_turn returns, AND _amain could close the AsyncClient while the cancel POST was still in flight. `_cancel_and_log` swallows all errors per INV-009 so the await is safe. Test gap fix: - New `_FlushCountingIO` subclass counts flush() calls; `test_text_to_stdout_only` and `test_done_writes_newline_and_label` now assert `flush_count == 1` to verify INV-010 (per-chunk flush). Previously the tests would have passed even with flush removed. Meta-note carried in persistent-memory: TDD caught central behavior (stdout/stderr routing, exit-code mapping, create-session ordering, SIGINT idempotence); the cross-model code review consistently catches assert-boundary + observability-shape gaps across all three issues (#1: 4 findings, #2: 3 findings, #3: 5 findings). 118/118 tests GREEN; ruff clean; drift check clean.
This commit is contained in:
@@ -187,6 +187,10 @@ ERROR_ROUTING:
|
||||
local_handling: write `[auth_error] no API key (set --api-key or WORLDTREE_API_KEY)` to stderr
|
||||
flow_control: abort
|
||||
state_recovery: none
|
||||
SystemExit (from argparse clean exits — `--help`, `--version`):
|
||||
local_handling: catch and return `exc.code` verbatim (typically 0); argparse already printed help/version to stdout
|
||||
flow_control: abort
|
||||
state_recovery: none
|
||||
STEPS:
|
||||
1. [setup, flexibility=prescriptive] TRY: args = _parse_args(argv)
|
||||
ON UsageError as exc:
|
||||
@@ -195,6 +199,8 @@ STEPS:
|
||||
ON _AuthError as exc:
|
||||
WRITE f"[auth_error] {exc}\n" to stderr
|
||||
RETURN 11
|
||||
ON SystemExit as exc:
|
||||
RETURN int(exc.code) if exc.code is not None else 0
|
||||
2. [sequential, flexibility=prescriptive] RETURN asyncio.run(_amain(args))
|
||||
TESTS:
|
||||
happy_returns_amain_exit_code [happy,tracer]: argv specifies a complete --send invocation; monkeypatch _amain to return 0 → main returns 0
|
||||
@@ -202,6 +208,7 @@ TESTS:
|
||||
usage_error_both_session_and_new [error]: argv has both --session and --new → returns 10; stderr "[usage_error]"
|
||||
auth_error_missing_key [error]: argv specifies --send/--new/--agent but neither --api-key nor WORLDTREE_API_KEY is set → returns 11; stderr "[auth_error]"; _amain never called
|
||||
no_argv_uses_sys_argv [trace]: argv=None → _parse_args is called with sys.argv[1:] (monkeypatched argparse capture confirms)
|
||||
help_exits_cleanly [happy]: argv=["--help"] → main returns 0 (or whatever code argparse exits with); _amain never called; help text was printed to stdout by argparse
|
||||
```
|
||||
|
||||
```contract
|
||||
@@ -232,6 +239,10 @@ ERROR_ROUTING:
|
||||
local_handling: raise _AuthError("no API key (set --api-key or WORLDTREE_API_KEY)")
|
||||
flow_control: abort
|
||||
state_recovery: none
|
||||
argparse SystemExit (clean exits — `--help`, `--version` etc., code=0):
|
||||
local_handling: allow to propagate from `_parse_args` to `main`; `main` catches and returns the code verbatim
|
||||
flow_control: passthrough — argparse already printed help/version to stdout; no further work needed
|
||||
state_recovery: none (no resources acquired before _parse_args)
|
||||
STEPS:
|
||||
1. [setup, flexibility=prescriptive] Construct argparse.ArgumentParser:
|
||||
--send <content> (required, str, non-empty)
|
||||
|
||||
Reference in New Issue
Block a user