61c3941ec3cce2c6bcabd7d905111cabfccdaa6f
3 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
dd89239c34 |
feat(tui): implement issue #4 contract via TDD; amend cli for TUI dispatch
47 contract-listed tests authored + GREEN (43 tui + 4 issue-#3 amendments). 164/164 tests GREEN suite-wide; ruff clean. Vertical-slice ordering: _render_event_to_log → _cancel_via_sse → CLI amendments → RatatoskrApp class + on_mount + on_unmount → on_input_submitted → _stream_turn_worker → action_interrupt + action_quit → run_tui. Two in-flight contract amendments caught during TDD: - PRE-002 of run_tui was `(args.session_id is None) != args.new` — backwards (fails when --session is set + new=False). Corrected to `bool(args.session_id) != bool(args.new)`. - RichLog created with markup=False (contract drafted markup=True). Rich interprets `[xxx]` as style markup and strips it, which would break every labeled stderr-style line ([cancel_failed], [done], [error], etc.). The post-Done Markdown rendering still works because rich.markdown.Markdown is a Renderable and doesn't need widget-level markup. Implementation notes: - _stream_turn_worker takes the log widget as a parameter passed from on_input_submitted. Querying #transcript from inside a Textual worker context fails with NoMatches; capturing the reference once at handler-time and threading it through the worker sidesteps the issue. - _spy_writes(monkeypatch) test helper records every RichLog.write call. RichLog's `.lines` Strip buffer isn't populated synchronously after .write() returns, which makes post-app-shutdown inspection unreliable; a write-spy gives deterministic verification. - SIGINT-mid-stream tests use custom httpx.AsyncByteStream subclasses with asyncio.Event gates to make timing deterministic without sleep-based polling — the cancel-respx-mock sets the gate event when its endpoint is observed, releasing the next SSE chunk. - _submit_and_wait test helper needs `await pilot.pause()` BEFORE the polling loop so the Input.Submitted message has a chance to dispatch. Discovered via debug-print trace; tracked in the test helper. CLI amendments (per issue #4 in-place amendment of #3 contract): - ParsedArgs.send_content: str | None (was str) - ParsedArgs.raw: bool added - _parse_args: --send default=None; empty-string still rejected; --raw added - main: branches on args.send_content — None → lazy `from ratatoskr.tui import run_tui` + run_tui(args); else asyncio.run(_amain(args)). Lazy import preserves issue #3 INV-001. Persistent-memory updated per the commit-along rule: tui module landed, recent-decisions entries for #4 (contract + Volva + TDD), next natural moves rotated to Volva code-review + manual smoke against the personal Worldtree (key landed in env.sh per infra-ops's earlier delivery). |
||
|
|
9717fb80e2 |
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. |
||
|
|
db27774c51 |
feat(cli): implement issue #3 contract via TDD
54 contract-listed tests authored + GREEN per the vertical-slice ordering (_parse_args → _render_event → _cancel_and_log → _run_turn → _amain → main). 117/117 tests GREEN suite-wide; ruff clean. The _run_turn race-loop is the load-bearing piece. Per iteration, the await on the next event is raced against sigint_event.wait() when NOT cancelling. Once SIGINT fires (with last_turn_id known), _cancel_and_log is spawned, cancelling=True flips, and subsequent iterations skip wait()-task creation entirely — the bug Volva flagged in contract review would otherwise busy-wake on the already-set event each iteration. Implementation notes: - _UsageErrorParser subclasses argparse.ArgumentParser and overrides error() to raise _ArgparseError instead of calling sys.exit; _parse_args catches and re-raises as UsageError per the contract's ERROR_ROUTING. - _GatedStream test helper (custom httpx.AsyncByteStream that pauses on asyncio.Event entries) makes SIGINT-mid-stream tests deterministic without sleep-based timing — gates release via side-channels (the cancel-mock sets an event when its endpoint is observed). - _sse_resp test helper wraps respx Response with the text/event-stream content-type, dedupes the boilerplate across the 13 _run_turn tests. - Strong-ref cancel_task local in _run_turn holds the fire-and-forget cancel task to suppress RUF006 / asyncio GC warning. One in-flight contract amendment during TDD: no_busy_loop_after_cancel test description originally said "exactly ONE wait()-shaped task" but the natural race-loop shape produces 2 (iter 1 raced w/ text, iter 2 raced w/ sigint → flipped cancelling; iter 3+ skipped). Amended to "TWO total wait() coroutines" with rationale; the busy-loop check is preserved (iter 3+ MUST skip). Persistent-memory updated per the commit-along rule: new module landed, recent-decisions log entries for #3 (contract + Volva paraphrase + TDD), next natural moves rotated to /volva-code-review on the implementation. |