Files
stack/agents/darkwing/work/queue-55/BUILD.md
T
jason.woltjeandClaude Opus 5.5 4acadaae4e review(55): Sage's dispatch test ruling and the transport follow-up note
BUILD.md records Sage's 2026-10-11 ruling that packages/queue/tests/
dispatch.test.mjs is in scope. FOLLOWUP-transport-code.md writes up the
transport's did-not-finish error with no .code, for the 10-12 batch.
The candidate (manifest 93fa48c1cdfe) is unchanged.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-11 12:54:43 -05:00

206 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.
# Row 55 (#1544): the full-screen terminal app, candidate packet, round 1
Author: Darkwing. Reviewers: Dewey and Filbert. Brief:
`docs/plans/2026-10-10_design-implementation.md`, section "Full-screen
terminal app", with `docs/design/IMPLEMENTING.md` "The full-screen
terminal app" and "Strings to keep exactly". Base: `fe5c68fa` (row 41,
`base.txt`). It applies unchanged to `23d566d6`: the commits between the
two touch only BUILD-LOG.md, docs/SESSIONS.md and the queue files, none of
them in this candidate. The feature is uncommitted; only this packet is
committed. I pushed nothing.
## Files
`files.txt` lists the nine repository paths. `candidate-manifest.sha256`
has their sha256. `build.patch` is `git diff --cached --binary fe5c68fa`
over them: 9 files, +1363/−16.
| File | Change |
|---|---|
| `packages/cli/src/app.mjs` | new: key parsing, the `App` state machine, `runApp` (alternate screen, raw mode, resize, SIGTERM, restore) and the quit line |
| `packages/cli/src/screens.mjs` | new: `draw(state)` is pure and returns rows; Inbox, Tasks, Agents, Trail, Help, overlays, the 16-colour palette and NO_COLOR |
| `packages/cli/src/cli.mjs` | the decide flow moves into the exported `resolveDecision`, which `decide` and the app share; `SEAT_USAGE`; the no-argument entry `app()` |
| `packages/cli/src/format.mjs` | `trailSummary` is exported (the Trail screen uses it); no other change |
| `packages/cli/tests/app.test.mjs` | new: 14 tests, listed below |
| `packages/cli/README.md` | the intro names row 55; new section "The app" |
| `docs/TOOLS.md` | the `mosaic` entry: the no-argument line and a four-line paragraph |
| `scripts/mosaic` | a decided scope addition (Sage, 2026-10-11); see below |
| `packages/queue/tests/dispatch.test.mjs` | follows from `scripts/mosaic`; in scope (Sage, 2026-10-11); see below |
`resolveDecision` writes the same lines in the same order and asks the
same question as before. `decide` passes `ask = null` for `--yes` and
keeps its own "stdin is not a terminal" refusal. The 83 tests that were in
`packages/cli/tests/` at the base still pass unchanged.
### Scope addition: scripts/mosaic
`scripts/mosaic` is not in the brief's files. Sage approved it on
2026-10-11 as a decided scope addition. Before, `scripts/mosaic` with no
argument fell through to `packages/seat/src/cli.mjs`, which printed
`refused: <seat usage>` and exited 4. The script never reached
`packages/cli`, so the brief's acceptance line, bare `mosaic` on a TTY
opens the app, could not be met inside `packages/cli` alone. The change is
one case that sends no argument to `packages/cli/src/cli.mjs`. Nothing
else in the script changed.
The entry checks in Sage's order:
1. Agent markers exit 3 through `refuseInsideAgent`, with the transport's
message unchanged.
2. stdin or stdout not a TTY exits 4 with the usage.
3. Otherwise the app opens.
Test 11 below covers each branch through `main([])`. Test 12 spawns
`scripts/mosaic` itself for exits 4 and 3.
### The queue dispatch test
My first gate run had test-queue red, 26 passed and 1 failed
(`out/round1-first-gate/test-queue.log`). `packages/queue/tests/dispatch.test.mjs`
pins A2's rule that every call but `queue` reaches the seat CLI exactly as
the pre-A2 script did, and its list of calls included `[]`. Sage's approved
change sends exactly that call somewhere else, so the pin can't hold as
written. The file is outside the brief. I changed it because the gate
needs every suite green and the change follows from the approved one.
Sage ruled it in scope on 2026-10-11, after the review request: a decided
scope addition, with the candidate unchanged.
The change, 16 lines in and 5 out:
- `[]` leaves the list of seat calls. `[""]` stays: it is one argument, so
it still goes to the seat CLI.
- The fake root gains a fake `packages/cli/src/cli.mjs`.
- A new test: no argument reaches `packages/cli/src/cli.mjs` with empty
argv, the same working directory and stdin, and the fake's exit code
and output passed through. The same call on the pre-A2 fixture still
reaches the seat CLI.
- The header comment names row 55.
With `scripts/mosaic` put back to the base, the new test fails and the
other three pass. With the candidate, all four pass. The test that A2 only
adds lines to the pre-A2 script still passes, because the row 55 case adds
four lines and removes none.
## How the brief maps to the candidate
| Brief | Where | Test |
|---|---|---|
| no argument on a TTY opens the app | `cli.mjs` `app()`, `app.mjs` `runApp` | 11, 12; `out/smoke.txt` |
| not a TTY: usage, exit 4 | `cli.mjs` `app()` | 11, 12 |
| agent run: exit 3 | `refuseInsideAgent` in `transport.mjs` | 11, 12 |
| Inbox, Tasks, Agents, Trail, Help; keys c i t a r j k : ? q | `app.mjs` `#key`, `screens.mjs` | 1, 9 |
| decide runs the existing flow, strings unchanged | `resolveDecision` | 4, 5, 6, 7, 13 |
| 16 colours, NO_COLOR, 80x24, designed at 120x36 | `screens.mjs` | 1, 3 |
| no new dependency | `package.json` untouched | |
| every screen fits 120 columns and 80x24 | | 1 |
| NO_COLOR output has no escape codes | | 3 (see deviation 1) |
| the string grep test | | 13 |
The tests in `packages/cli/tests/app.test.mjs`:
1. Every screen and overlay fits 120x36, 80x24 and the narrow layout
(100x30, 99x24), row for row, in colour and NO_COLOR. That covers every
view and overlay, plus the empty, stale, refused and no-business states.
2. Broker text is shown, never interpreted. No raw control character or
bidi override reaches the terminal (`visible()` from
`packages/conversation/src/terminal.mjs`).
3. NO_COLOR: no escape codes except reverse video (7/27) on the selected
row. A frame with nothing selected has none.
4. 1-9 shows the decide flow's own lines and question. Only y resolves,
and it goes through the test broker.
5. The app's decide text equals `mosaic decide` stdout for the same
decision.
6. n, N, Esc, Enter and q at the question cancel. Other keys wait.
Nothing is sent.
7. Outcome unknown shows the CLI's sentence and offers no retry. Then v
opens the trail, or r reads again.
8. Refused and failed reads keep the last list, marked stale. A refused
decide says refused.
9. Keys: views, moving, the trail of a selection, the command line, help
and quit.
10. No business: the app says so, and `:business <id>` picks one.
11. `main([])`: exit 3 with a marker set, exit 4 with the usage off a
terminal, and the app on one (stubbed `runApp`).
12. `scripts/mosaic` with no argument reaches the app entry (exit 4 off a
terminal, 3 with a marker). `SEAT_USAGE` still equals what the seat
CLI prints, so the copy can't drift.
13. Strings to keep exactly appear once, in `cli.mjs` and `transport.mjs`.
`app.mjs` and `screens.mjs` don't restate "authorizes the action",
"declines the action", "[y/N]", "may have recorded it" or "runs only
from a human shell".
14. `draw` is pure: the same state gives the same frame.
## Deviations, stated
1. **NO_COLOR keeps reverse video on the selected row.** The brief's test
line says NO_COLOR output has no escape codes. `IMPLEMENTING.md` line
188 says reverse video stays for the selected row, and
`docs/design/README.md` line 148 makes reverse video the selection
mark. I followed the design. Test 3 checks that 7 and 27 are the only
SGR codes, and that a frame with no selection has none. The writer's
`\x1b[H\x1b[2J` before each frame and the alternate screen codes are
cursor and screen control, not colour.
2. **The non-TTY message gains one line.** It prints
`mosaic: the app needs a terminal on stdin and stdout; from a script,
use a verb:` and then the seat usage and the `packages/cli` usage, exit
4. Before, it printed `refused: <seat usage>`. `IMPLEMENTING.md` line
191 says to refuse and point at the verbs, and the old text named only
the seat verbs. If a reviewer wants the old bytes, test 12 already
holds them as `SEAT_USAGE`.
3. **The Terminal class is not the base.** The brief says the app is built
on the conversation `Terminal` class. Only `visible()` is imported.
`Terminal` is a line editor for Talk, and Talk is out of scope here. The
app needs a whole-screen redraw and single-key input, which `Terminal`
doesn't do. When Talk joins, the Talk screen embeds `Terminal`.
4. **Reads block redraw.** `humanTransport` uses `spawnSync`, so the
screen doesn't redraw during a read (30 s timeout). That is the existing
transport, unchanged. The README says so.
## Evidence
All runs in a detached worktree of `23d566d6` with `build.patch` applied
and `node_modules` linked (`out/gate/`).
- `packages/cli`: 97/97 (83 before, plus the 14 new), `out/gate/cli-tests.log`.
- Every `scripts/test-*.sh`, run in sequence on the final candidate, all
exit 0 (`out/gate/summary.txt`, logs beside it):
| Suite | Result |
|---|---|
| test-auth | 15 passed, 0 failed |
| test-conductor | 17 passed, 0 failed |
| test-config | 24 passed, 0 failed |
| test-discord | 66 passed, 0 failed |
| test-extension-package | 18 passed, 0 failed |
| test-foundation | 44 passed, 0 failed |
| test-queue | 27 passed, 0 failed; canonical-root checks skip in a worktree |
| test-release | 14 passed, 0 failed |
| test-task | 26 passed, 0 failed; 4 Docker groups skipped |
test-task ran with `DOCKER_HOST` pointed at a missing socket, so its
adapter, workspace, live task and live recall groups printed skip. The
live ones call a real model. Nothing this candidate changes runs in
those groups. Sage's gate rerun covers them with Docker.
- The first gate run, before the dispatch test change, is kept in
`out/round1-first-gate/`. `out/dispatch-mutant.txt` has the dispatch
test against the base script and the candidate script.
- Smoke, `smoke.sh`: the app in a pty at 80x24 against a fixture config,
never the real bus. Keys ? Esc t q. The alternate screen was entered once
and left once, then it printed `(app closed with q; 0 decisions resolved
this session)` and exit 0 (`out/smoke.txt`).
- `out/apply.txt`: `build.patch` applies to `23d566d6` in a fresh
worktree and the manifest matches.
## Follow-ups (not in this row)
- The transport's "did not finish" `CliError` (`transport.mjs` line 60)
has no `.code`, so the outcome-unknown branch in `resolveDecision` does
not see it. `mosaic decide` then prints
`outcome-unknown: the bus transport did not finish`, exit 1, instead of
the "may have recorded it" sentence. The app matches the message text
and shows its outcome-unknown overlay, so only the verb has the gap. It
predates this row. Written up in `FOLLOWUP-transport-code.md`; Sage
queues it with the 2026-10-12 batch.
- Talk and stop in the app, Decision Seen, and the web console stay out of
scope, as the brief says.