diff --git a/bench/bashloop/README.md b/bench/bashloop/README.md index ad5039755c..5d8a47a894 100644 --- a/bench/bashloop/README.md +++ b/bench/bashloop/README.md @@ -1,6 +1,6 @@ # bashloop — the arm bench for the bash-only task loop -Wave 3 of the bash-task-loop experiment +A harness for the bash task loop (DESIGN.md): the same briefs run on both belts — arm A with `CODEAF_TASK_BELT=node` (the older belt), arm B with `CODEAF_TASK_BELT=bash` — n replicates, cells interleaved so the diff --git a/bench/conversation/README.md b/bench/conversation/README.md index 951224fe05..bc1e915b1e 100644 --- a/bench/conversation/README.md +++ b/bench/conversation/README.md @@ -31,8 +31,8 @@ from, are in `lib/adapters.sh`: | arm | interactive door | markers from | |---|---|---| | codeaf | yes | `internal/tui3/render.go` (`stateWord`, `waitingWord`) — the renderer itself, checked against a live pane | -| pi | yes | live pane capture on pi 0.84.2 in this lane; the provider segment is derived from the provider in use, not hardcoded | -| omp | yes | live pane capture on omp 18.1.2 in this lane | +| pi | yes | live pane capture on pi 0.84.2; the provider segment is derived from the provider in use, not hardcoded | +| omp | yes | live pane capture on omp 18.1.2 | | opencode | **no** | no calibrated markers — its interactive cells are `unsupported` | ## Open models only @@ -179,7 +179,7 @@ parse, the cell comes back `cost unknown` and not-comparable rather than zero. A harness that loads the operator's skills, extensions and MCP servers is not running the task it was given: it is running that task plus whatever is installed on that machine — extra tools, a longer system prompt, extra latency, -and a different set on every box. A live pane in this lane showed omp mounting +and a different set on every box. A live pane showed omp mounting MCP tools and failing one of them out of `~/.claude.json`, and pi listing thirty-odd skills out of `~/.agents/skills`. @@ -483,7 +483,7 @@ record of what they were. A cell whose directory already exists refuses too, and records a skip. `--overwrite` is the deliberate way, and `--out` gives a run a directory of its own. -Live runs so far, all by the owner: +Recorded runs: * `notes/pilot-01.md` — the first print-door cells, and why one of its three costs is not usable. diff --git a/bench/conversation/lib/adapters.sh b/bench/conversation/lib/adapters.sh index d5f0f9123a..7ac64ecfc7 100644 --- a/bench/conversation/lib/adapters.sh +++ b/bench/conversation/lib/adapters.sh @@ -219,7 +219,7 @@ arm_role_pin() { # # A harness that loads the machine's skills, extensions and MCP servers is not # running the task it was given: it is running that task plus whatever the -# person who owns the machine installed. A live pane in this lane showed omp +# person who owns the machine installed. A captured pane showed omp # mounting MCP tools and failing one of them from `~/.claude.json`, and pi # listing thirty-odd skills from `~/.agents/skills` — extra tools, extra system # prompt, extra latency, and none of it the same on two machines. diff --git a/bench/conversation/run.sh b/bench/conversation/run.sh index 21903d2f9b..be3e251e39 100755 --- a/bench/conversation/run.sh +++ b/bench/conversation/run.sh @@ -442,8 +442,7 @@ for scenario in $SCENARIOS; do # Did the cell's only route upstream survive the cell? A guard that exits # mid-run leaves the harness dialling a closed port, and everything after # that — no reply, no file, no receipt — is about the rig and not about the - # harness. It has happened once in this lane (notes/pilot-02-03.md), and it - # arrived looking exactly like a product failure. + # harness. A failed guard can look exactly like a product failure. guard_died=0 if [ "$DRY_RUN" != "1" ] && [ "$UNGUARDED" != "1" ] && ! guard_alive; then guard_died=1 diff --git a/bench/idle-surface/replay.sh b/bench/idle-surface/replay.sh index 3961eee2f9..311fc233e6 100755 --- a/bench/idle-surface/replay.sh +++ b/bench/idle-surface/replay.sh @@ -15,13 +15,14 @@ # this run started, after reading the proc cmdline of each; never pkill/killall. # # Usage: replay.sh K [K ...] e.g. replay.sh 5 50 200 -# Env: BASE (scratch root, default /tmp/codeaf-idle-runs), WIN (window secs). +# Env: TAR and STUB are required. REPO defaults to the repository root. +# BASE defaults to $REPO/bench/idle-surface/runs; WIN is window seconds. set -u -REPO="/home/santosh/src/c286tree" +REPO="${REPO:-$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd)}" BIN="$REPO/bin/codeaf" -TAR="${TAR:-/home/santosh/src/profiles/fresh-codeaf.tar}" -STUB="${STUB:-/home/santosh/src/stub.py}" -BASE="${BASE:-/home/santosh/src/c286tree/bench/idle-surface/runs}" +: "${TAR:?Set TAR to a profile tar archive}" +: "${STUB:?Set STUB to a canned-stream server script}" +BASE="${BASE:-$REPO/bench/idle-surface/runs}" PORT="${PORT:-8099}" WIN="${WIN:-30}" CLK_TCK=$(getconf CLK_TCK) diff --git a/bench/idle-surface/wait.sh b/bench/idle-surface/wait.sh index 83a1e721a7..8152c5cd54 100755 --- a/bench/idle-surface/wait.sh +++ b/bench/idle-surface/wait.sh @@ -1,15 +1,17 @@ #!/usr/bin/env bash # wait.sh - idle CPU of a chat surface that WAITS ON WORK IN FLIGHT, by K. # Usage: wait.sh K:MODE [K:MODE ...] e.g. wait.sh 5:nodelta 5:stream 200:nodelta +# Env: TAR is required; REPO defaults to the repository root and BASE to +# $REPO/bench/idle-surface/runs. WIN is the sampling window in seconds. # One row per point: K, mode, threads, idle %core (two 30s windows), RSS before # and after the windows, stub POST counts (requests in flight during sampling), # first-delta count (0 means no output arrived), pane state word. set -u -REPO="/home/santosh/src/c286tree" +REPO="${REPO:-$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd)}" BIN="$REPO/bin/codeaf" -TAR="${TAR:-/home/santosh/src/profiles/fresh-codeaf.tar}" +: "${TAR:?Set TAR to a profile tar archive}" STUB="$REPO/bench/idle-surface/stub-tunable.py" -BASE="${BASE:-/home/santosh/src/c286tree/bench/idle-surface/runs}" +BASE="${BASE:-$REPO/bench/idle-surface/runs}" PORT="${PORT:-8099}" WIN="${WIN:-30}" CLK_TCK=$(getconf CLK_TCK) diff --git a/bench/oneroad/cell.sh b/bench/oneroad/cell.sh index df2619e9a0..c58570675b 100755 --- a/bench/oneroad/cell.sh +++ b/bench/oneroad/cell.sh @@ -29,29 +29,13 @@ ISSUES="${ISSUES:-20 21 22 23}" MODEL="${MODEL:-deepseek/deepseek-v4-flash}" NEW_BIN="${NEW_BIN:-$HOME/af-oneroad/bin/codeaf}" OLD_BIN="${OLD_BIN:-$HOME/af-oldbase/bin/codeaf}" -# WAVE 1B'S BINARY, AND IT IS A SNAPSHOT RATHER THAN THE LIVE PATH ON PURPOSE. -# bin/codeaf is rebuilt in place by whoever is working the tree, and wave 1's -# batch cells landed two minutes before one such rebuild — which is luck, not a -# protocol. A wave measured against "whatever bin/codeaf was at the moment each -# cell happened to launch" is a wave whose arm has no single identity, so the -# bytes are copied once, sha'd into results/BINARIES, and every cell of the arm -# runs that copy. ESC_BIN="${ESC_BIN:-$ONEROAD/bin/codeaf-esc-b70de31c}" -# Wave 1c's binary, snapshotted for the same reason ESC_BIN is. PRE_BIN="${PRE_BIN:-$ONEROAD/bin/codeaf-pre-9b9a4c92}" -# Wave 1d: the raced screen, the ski-rental checkpoints and the ceiling, together. CKPT_BIN="${CKPT_BIN:-$ONEROAD/bin/codeaf-ckpt-6b9809e9}" -# Wave 1e: the finalist. Everything at once. FINAL_BIN="${FINAL_BIN:-$ONEROAD/bin/codeaf-final-c0e4f5a7}" PI_BIN="${PI_BIN:-pi}" OPENCODE_BIN="${OPENCODE_BIN:-$HOME/.opencode/bin/opencode}" -# The wall. bench/run.sh's forty minutes, in seconds, because a cell past it is -# recorded as DNF rather than left to spend. -# Raised from 2400 after wave 1g: its task-road cells ran 2126-2315s, close -# enough to the old wall that the number being measured was starting to be the -# wall rather than the work. A wall has to sit far enough above the work that a -# row hitting it means something went wrong, not that the task was ordinary. CELL_SECONDS="${CELL_SECONDS:-3600}" # How long the surface must be completely silent before the turn is called # settled. A task thinking between tool calls is silent for tens of seconds, so @@ -211,26 +195,6 @@ PY else quiet=0 fi - # THE STORE IS AUTHORITATIVE PAST A HARD MULTIPLE OF THE WINDOW, and this is - # the second half of a lesson that cost two cells. The footer's hint was - # wrong in one direction ('esc interrupt' outliving the turn) and the spinner - # is wrong in the other: a cell whose store had been silent for sixteen - # minutes was still drawing 'working' and would have run to the wall. A pane - # is a rendering, not a fact. So the screen may DELAY a settle but it may not - # prevent one — past 3x the silence window the store's own silence decides. - # A LIVE TASK VETOES THE SETTLE, AND NO AMOUNT OF SILENCE OVERRIDES IT. - # - # This is the rule that was missing, and its absence destroyed six wave-1f - # cells. The quiet window is evidence about the CONVERSATION and says nothing - # whatever about a task: a worker that has just typed `pytest` writes nothing - # anywhere for minutes. The old rule read that silence as "finished", reaped - # the tmux session, and killed the worker in the middle of its test run — - # eighteen minutes of real work, nought files landed, judges scoring the - # emptiness. THE STORE GOING QUIET IS NOT A TASK FINISHING. - # - # lib/tasklive.py counts any state it does not recognise as LIVE, so the way - # this rule fails is by waiting — bounded by the hard wall above — rather - # than by killing, which is bounded by nothing and loses the work. sess="$(find "$CELL/profile/v3/projects" -mindepth 2 -maxdepth 2 -type d 2>/dev/null | head -1)" live=0; stranded_ids="" if [ -n "$sess" ]; then @@ -238,11 +202,6 @@ PY live="$(printf '%s' "$tl" | python3 -c 'import json,sys try: print(json.load(sys.stdin)["live"]) except Exception: print(0)' 2>/dev/null)" - # STRANDED IS NOT LIVE. A task in a live state whose own journal has not - # seen a request for the stranded window has stopped working without - # landing; counting it live pinned wave 1h's batch cells to the hard wall - # for 3100 dead seconds apiece. It is named here so the row can say WHICH - # task stranded rather than just that the cell ended badly. stranded_ids="$(printf '%s' "$tl" | python3 -c 'import json,sys try: print(",".join(str(i) for i in json.load(sys.stdin).get("stranded_ids") or [])) except Exception: print("")' 2>/dev/null)" @@ -274,14 +233,6 @@ except Exception: print("")' 2>/dev/null)" done tmux capture-pane -t "$SESSION_NAME" -p > "$CELL/tmux-final.txt" tmux capture-pane -t "$SESSION_NAME" -p -S -4000 > "$CELL/tmux-scrollback.txt" - # THE SESSION IS REAPED ONCE ITS EVIDENCE IS CAPTURED, and forgetting this cost - # wave 1 real money in contention: `codeaf chat` is an interactive surface that - # never exits on its own, so every settled cell left a live process and a live - # tmux session behind it. Fourteen of them were still resident when wave 1b - # fired, competing for the same CPU as the cells being measured — which makes - # every wall clock after the first wave quietly worse than the harness it is - # supposed to be measuring. Capture first, then reap: the panes above are the - # only thing in the session worth keeping, and the store is on disk already. tmux kill-session -t "$SESSION_NAME" 2>/dev/null if [ -f "$CELL/outcome_override" ]; then cp "$CELL/outcome_override" "$CELL/outcome"; else echo OK > "$CELL/outcome"; fi return 0 @@ -314,28 +265,12 @@ case "$ARM" in codeaf-new-flash) run_codeaf "$NEW_BIN" 1; CODE=$? ;; codeaf-new-crew) run_codeaf "$NEW_BIN" 0; CODE=$? ;; codeaf-old-flash) run_codeaf "$OLD_BIN" 1; CODE=$? ;; - # Wave 1b. Same two configurations as the new-* arms, same protocol, different - # bytes: the question is whether the mid-turn escalation teaching moves the - # road column, so everything except the binary has to be held still. codeaf-esc-flash) run_codeaf "$ESC_BIN" 1; CODE=$? ;; codeaf-esc-crew) run_codeaf "$ESC_BIN" 0; CODE=$? ;; - # Wave 1c. The pre-turn route judge resolves the screen and the confirm - # through the TIER ROWS, so the all-flash arm runs both readings on flash and - # the crew arm runs the screen on the shipped low tier and the confirm on the - # kimi mastermind. That difference is not noise to be controlled away — it is - # what the two arms are FOR, and it is recorded as part of the arm. codeaf-pre-flash) run_codeaf "$PRE_BIN" 1; CODE=$? ;; codeaf-pre-crew) run_codeaf "$PRE_BIN" 0; CODE=$? ;; codeaf-ckpt-flash) run_codeaf "$CKPT_BIN" 1; CODE=$? ;; codeaf-ckpt-crew) run_codeaf "$CKPT_BIN" 0; CODE=$? ;; - # Wave 1e. crew is the intended default — the mark-reader sidecar and the - # confirm are mastermind calls and are simply ABSENT on the all-flash pin, - # which is why that arm is the cost ablation and not a second opinion. - # - # NEITHER ARM WRITES A `routing` ROW. The new default is latency under a price - # ceiling for the turn and price for background calls, and pinning the row - # would measure the pin instead of the default. The profile writer below has - # never written that key; this comment is here so nobody adds it. codeaf-final-crew) run_codeaf "$FINAL_BIN" 0; CODE=$? ;; codeaf-1f-crew) run_codeaf "$ONEROAD/bin/codeaf-1f-0fabf058" 0; CODE=$? ;; codeaf-1g-crew) run_codeaf "$ONEROAD/bin/codeaf-1g-ec52e7ae" 0; CODE=$? ;; @@ -352,14 +287,6 @@ WALL=$(( $(date +%s) - STARTED )) cut -d' ' -f1-3 /proc/loadavg > "$CELL/loadavg-after" say "$ARM/$TASK: harness finished, exit $CODE, ${WALL}s" -# ── what it actually did ──────────────────────────────────────────────────── -# -# THE PATCH IS THE LANDED DIFF, AND IT IS BUILT HERE RATHER THAN BY A LATER PASS. -# lib/landed.py existed since wave 1d but was only ever run as a one-off script -# afterwards, so wave 1h's cells were recorded with `git diff` alone — which is -# EMPTY once a task has committed and merged. Six landed files read as "no -# changes", the judges were handed that, and they scored the cells zero. Any -# measurement a row depends on has to be taken by the runner that writes the row. CHANGED="$(changed_files "$DIR")" python3 - "$DIR" "$BASE_COMMIT" "$CELL" "$ONEROAD/lib" <<'LANDED' import os, sys diff --git a/bench/oneroad/finalize.py b/bench/oneroad/finalize.py index 8b8a52614b..7bcd440d3d 100644 --- a/bench/oneroad/finalize.py +++ b/bench/oneroad/finalize.py @@ -10,7 +10,7 @@ bundle that the driver would have written, rather than throwing away a run that has already been paid for. 2. BACKFILL. When a road column is added or corrected — `escalated` was added - after wave 1 had landed — every earlier cell can be re-read against the new + after a run has finished — every earlier cell can be re-read against the new reader instead of being left with a blank column or, worse, re-run and quietly compared against rows measured on a different day. diff --git a/bench/oneroad/grid.sh b/bench/oneroad/grid.sh index 1a9470f1c2..aa7dde3f13 100755 --- a/bench/oneroad/grid.sh +++ b/bench/oneroad/grid.sh @@ -1,25 +1,8 @@ #!/usr/bin/env bash -# grid.sh — the whole 25-cell wave-1 grid, driven by ONE detached process. -# -# IT IS DETACHED ON PURPOSE. The first attempt at this grid was launched from an -# interactive harness, and when that harness restarted it took every cell with -# it: fifteen cells mid-flight, no rows. A benchmark that a terminal closing can -# kill is a benchmark that has to be re-run every time somebody's editor -# crashes, so this is started under setsid and writes its own pid where a person -# who lost it can find it (results/PIDS). -# -# THE CAP AND THE WAVES. Five arms of one task fire together (wave.sh says why), -# and no more than MAX_WAVES tasks are in the air at once — five cells a wave, so -# three waves is fifteen cells, which is the honest ceiling for one machine whose -# cells spend their middles waiting on a provider and their ends running pytest. set -uo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" TASKS="${TASKS:-20 21 22 23 batch}" MAX_WAVES="${MAX_WAVES:-3}" -# ARMS lets one grid run a SUBSET of the arms over the same tasks, which is what -# a follow-up wave is: wave 1b re-asks the identical five tasks of two new arms -# and must not re-run the eight rows that already landed. Empty is every arm, -# which is what wave 1 wanted. ARMS="${ARMS:-}" mkdir -p "$ROOT/results" diff --git a/bench/oneroad/lib/judge_bundle.py b/bench/oneroad/lib/judge_bundle.py index 6cfa3ba0da..f075fe967f 100644 --- a/bench/oneroad/lib/judge_bundle.py +++ b/bench/oneroad/lib/judge_bundle.py @@ -83,11 +83,6 @@ def main(): meta = json.load(open(os.path.join(cell, "meta.json"))) fixed, broken = changed_tests(cell) patch = read(os.path.join(cell, "diff.patch"), PATCH_CAP).strip() or "no changes" - # STRANDED WORK IS SHOWN, AND IT IS SHOWN APART. A part that worked in its own - # worktree and never merged has delivered nothing, so it must not sit inside - # PATCH where it reads as the attempt's output — wave 1h put raw conflict - # markers in front of a judge that way. It is still worth showing: "the work - # exists and did not land" is a different verdict from "nothing was done". stranded = read(os.path.join(cell, "stranded.patch"), PATCH_CAP).strip() report = report_of(cell, meta).strip() or "(the attempt said nothing)" diff --git a/bench/oneroad/lib/landed.py b/bench/oneroad/lib/landed.py index 3e88659e45..bd8faf374a 100644 --- a/bench/oneroad/lib/landed.py +++ b/bench/oneroad/lib/landed.py @@ -5,7 +5,7 @@ and it is correct for the words road: chat edits the checkout and leaves the changes uncommitted. IT IS BLIND TO THE TASK ROAD. A task works in a git worktree, COMMITS, and merges back — so the checkout comes out clean and the -cell records zero changed files over work that really happened. Wave 1d turned +cell records zero changed files over work that really happened. An earlier implementation turned nine cells onto that road at once and every one of them was being under-read. So the work is the union of two things: @@ -59,7 +59,7 @@ def worktrees(cell): A task does not edit the clone: it branches into its own worktree under the session folder (`/trees/`), works there, commits, and merges home only when it LANDS. So a cell whose task was still running — or was killed - while running, which is what the settle bug did to six wave-1f cells — has + while running, which is what the settle bug did to recorded cells — has all of its work here and none of it in the clone. Measuring only the clone reported those cells as "0 files changed" over eighteen minutes of real work. @@ -108,7 +108,7 @@ def worktree_patch(cell): def patch(repo, base): """THE PATCH A JUDGE GRADES: everything that landed on the person's branch. - THE RULE, and it is the one wave 1h got wrong. A judge is grading what the + A judge grades what the attempt DELIVERED, and delivery means the clone's branch: base..HEAD what a task committed and merged home, plus @@ -117,9 +117,8 @@ def patch(repo, base): NOTHING FROM A TASK WORKTREE APPEARS HERE. A part working in `/trees/N` that never merged has produced nothing the person can - use, and wave 1h's batch bundle proved how badly that reads when mixed in: - the judge was shown raw `<<<<<<< Updated upstream` conflict markers as if - they were the diff. Stranded work is real and worth recording, so it is + use. Raw conflict markers from an unmerged worktree must not appear in the + delivered diff. Stranded work is real and worth recording, so it is written separately by [worktree_patch] under its own heading — never here. The failure this replaces was quieter and worse: `git diff` alone is EMPTY diff --git a/bench/oneroad/lib/road.py b/bench/oneroad/lib/road.py index b67767688d..4504b0e750 100644 --- a/bench/oneroad/lib/road.py +++ b/bench/oneroad/lib/road.py @@ -258,20 +258,6 @@ def escalations(session): # the assistant's reply and can be read back rather than caught on a frame. PRE_TURN_LINE = re.compile(r"this looked like work, so task (\d+) (started|queued)") -# Wave 1d's three mechanisms, each by its own exact bytes (internal/session's -# route_judge.go and checkpoint.go). They are kept as separate constants rather -# than one alternation because WHICH of them fired is the measurement: they are -# three different answers to the same failure and a row that merged them would -# say only "something happened". -# WAVE 1E RENAMED MOST OF THESE, AND THE RENAME COST A WHOLE WAVE'S READING. -# The 1d detectors matched the exact sentences of the 1d binary; c0e4f5a7 changed -# the raced screen to TRIAGE (it no longer converts with a note of its own) and -# rewrote the checkpoint ask, so every 1e cell read `route=none, marks=0` over -# mechanisms that may well have run. The lesson is in the matching now: the mark -# is counted by the STABLE `[checkpoint]` PREFIX the harness itself keys on -# (checkpoint.go's isCheckpointNote), not by the sentence after it, and every -# conversion note is listed so a future rename shows up as a new value rather -# than as silence. RACE_NOTE = "this reads like work · moving it to a task that is watched and can split" # 1d only SPLIT_NOTE = "this has parts · handing it to a task that can take them side by side" # 1e CEILING_NOTE = "this is running long · moving it to a task that is watched and can split" @@ -286,16 +272,7 @@ def escalations(session): def mark_lines(session): - """Wave 1f journals its own decisions, so they are READ rather than inferred. - - Every mark writes a `mark` line (n, rounds, model, costUsd, sketch, decision) - and the last one writes a `ceiling` line (rounds, decision, taskId). Until 1f - the sidecar was invisible — it is an auxiliary call, never a message — and - wave 1e had to be reconstructed from the SHAPE of untagged usage records. - That heuristic is now retired: where the journal states the decision, the - journal is the source, and the shape-based reader below stays only as the - fallback for the older waves whose stores predate these lines. - """ + """Read checkpoint decisions from journal marks and ceiling records.""" marks, ceiling = [], None for entry in transcripts(session): kind = entry.get("type") @@ -314,21 +291,7 @@ def mark_lines(session): def divisions_journaled(session): - """Every division PUT TO THE ROAD, from wave 1g's own `division` lines. - - Until 1g a division could only be seen indirectly — a divide_work call in a - worker's journal, or children appearing under a parent — and a division that - was REFUSED left almost nothing to read. The line now states all of it: - who asked (`sketch` when the harness submitted it on the worker's behalf - before its first request, `worker` when the worker reached for the verb - itself), how many parts were requested, how many were admitted, and the - decision, which is `admitted` or `refused:`. - - Both sources matter and they are different claims. A `sketch` division is the - harness acting on the mark reader's drawing; a `worker` division is the model - doing what four earlier waves could not make it do. A row that merged them - would lose exactly the distinction wave 1g exists to test. - """ + """Read admitted divisions and distinguish sketch and worker sources.""" out = [] for entry in transcripts(session): if entry.get("type") != "division" or not entry.get("division"): @@ -337,13 +300,6 @@ def divisions_journaled(session): out.append({"task_id": d.get("taskId"), "source": d.get("source"), "requested": d.get("requested"), "admitted": d.get("admitted"), "decision": d.get("decision"), - # WHY, and WHAT IT WAS ABOUT. Wave 1h carries the reviewer's - # own reason and the titles it was asked to approve, which is - # the difference between "refused" and a refusal a person can - # argue with. `refused:review-unreached` is its own decision: - # the adjudicating reviewer was never called at all, which is - # what 1g turned out to be doing and what no counter could - # have told us. "error": d.get("error") or "", "parts": d.get("parts") or []}) return out @@ -400,21 +356,8 @@ def mark_reader(session): def call_lines(session): - """The per-request `call` lines wave 1e added, summed and grouped. - - Each is one request: model, ENDPOINT, tokens and the billed costUsd. Two - things come out of it that no earlier wave could see. The bill is now a sum - of requests rather than one rolled-up usage record, so `cost_usd_calls` can - be checked against the usage total instead of trusted. And the endpoint is - named, which is the only way to see what the new latency-under-a-price- - ceiling routing actually DID — a routing default is a claim about which - endpoint served the turn, and this is the evidence for it. - """ + """Sum per-request call records by endpoint and role.""" total, endpoints, tok, n = 0.0, {}, {"input": 0, "output": 0, "cache_read": 0}, 0 - # WAVE 1F MADE THE SUM OF CALL LINES THE BILL. Every auxiliary call now - # writes one with its `role`, so the gap wave 1e measured — $0.62 of - # mastermind sidecar that the call lines did not carry — is closed, and the - # spend can be attributed to the role that caused it rather than to a total. by_role = {} for entry in transcripts(session): if entry.get("type") != "call": @@ -439,15 +382,7 @@ def call_lines(session): def mechanisms(session): - """Which of wave 1d's three doors fired, and how far the turn got. - - The order below is the order the harness itself would reach them, and it is - also the order of decreasing "the model was given a chance": the raced screen - converts before the model answers, a checkpoint converts a turn the model was - already grinding, and the ceiling is the harness giving up on asking. A - propose_task with no checkpoint before it is the model reaching for the verb - on its own, which is the thing every earlier wave failed to produce. - """ + """Read which routing mechanisms fired and how far the turn progressed.""" entries = transcripts(session) marks = 0 race = ceiling = handoff = split = nothing_left = False @@ -703,11 +638,6 @@ def main(): else: route = "none" - # THE BILL. From wave 1f every auxiliary call writes a `call` line with its - # role, so their sum IS the bill and is preferred. Older stores have no call - # lines and fall back to the usage records, which is what every earlier wave - # was measured on — both figures stay in the row so the two can be compared - # rather than silently swapped. spend_calls = calls["cost_usd_calls"] if calls["calls"] > 0 and spend_calls > 0: billed, billed_source = spend_calls, "call-lines" diff --git a/bench/oneroad/lib/tasklive.py b/bench/oneroad/lib/tasklive.py index 168793e4d7..8c654284fe 100644 --- a/bench/oneroad/lib/tasklive.py +++ b/bench/oneroad/lib/tasklive.py @@ -3,7 +3,7 @@ WHY THIS EXISTS. The settle rule was "the store has been quiet for 180 seconds", and that is correct for the WORDS road, where the only writer is the -conversation. It is WRONG for the task road, and it destroyed six wave-1f cells: +conversation. It is WRONG for the task road, and it destroyed recorded cells: a worker that has just typed `pytest` writes nothing at all for minutes, the store goes quiet, the cell is called settled, the tmux session is reaped and the task is killed in the middle of its test run. The rows came back with 0 files @@ -30,20 +30,6 @@ """ import glob, json, os, re, sys, time -# A LIVE STATE IS NOT THE SAME AS A LIVE TASK. wave 1h's batch cells proved it: -# part 2 made its last request at 09:23:46, reported "paused - it resumes", and -# kept the state `running` for the next fifty minutes. The settle rule refused to -# settle while anything was live, so the cell sat until the hard wall and 3100 of -# its 3610 seconds were dead. The rule was right to refuse — that refusal is what -# stopped us killing live workers — but "live" has to mean "still working", and -# the evidence for that is a request, not a label. -# -# So a task in a live state whose own journal has not seen a request for this -# long is STRANDED: counted out of the live set, named in the settle reason, and -# the row marked STRANDED rather than passed off as a clean finish. Fifteen -# minutes is comfortably longer than the slowest thing a worker legitimately -# waits on here (a full test suite or a cold build ran 6-8 minutes on this -# corpus) and far shorter than the wall it replaces. STRANDED_SECONDS = int(os.environ.get("ONEROAD_STRANDED_SECONDS", "900")) JOURNAL = re.compile(r"^(\d{8}-\d{6})_(\d+)(.*)\.jsonl$") diff --git a/bench/oneroad/marathon/cell.sh b/bench/oneroad/marathon/cell.sh index 09ce948077..addd653987 100755 --- a/bench/oneroad/marathon/cell.sh +++ b/bench/oneroad/marathon/cell.sh @@ -398,7 +398,6 @@ PY live="$(printf '%s' "$tl" | python3 -c 'import json,sys try: print(json.load(sys.stdin)["live"]) except Exception: print(0)' 2>/dev/null)" - # See the wave-1 runner: a live STATE is not a live task. stranded_ids="$(printf '%s' "$tl" | python3 -c 'import json,sys try: print(",".join(str(i) for i in json.load(sys.stdin).get("stranded_ids") or [])) except Exception: print("")' 2>/dev/null)" diff --git a/bench/oneroad/replay/replay2.py b/bench/oneroad/replay/replay2.py index 8d5b5f15dd..c99384ccc3 100644 --- a/bench/oneroad/replay/replay2.py +++ b/bench/oneroad/replay/replay2.py @@ -1,12 +1,8 @@ #!/usr/bin/env python3 """REPLAY-2: off-policy test of a DIGEST-based mark reader. -Live wave 1e failure: the mastermind mark reader read the RAW transcript at -rounds 10/20/40 on the four-issue batch cell (57k/65k/91k tokens) and answered -CONTINUE all three times, at $0.62. Hypothesis: raw, result-heavy context drowns -the ask. This script builds a compact DIGEST instead - the person's ask verbatim, -a ledger of tool calls with NO results, the last thing said, and the files -touched - bounds it to ~6k tokens, and asks the same sketch question. +This script builds a compact digest of the request, tool calls, latest reply +and changed files, then asks the checkpoint sketch question. The first line of every answer is parsed exactly as internal/session/checkpoint.go's parseCheckpointSketch + topLevelParts do. @@ -474,13 +470,7 @@ def score(): L.append(f"Generated {time.strftime('%Y-%m-%d %H:%M')} | {len(recs)} completions " f"| temp {TEMPERATURE} | {SAMPLES} samples per cell x cut x model") L.append("") - L.append("**What this tests.** In live wave 1e the mastermind mark reader read " - "the RAW transcript at rounds 10/20/40 of the four-issue batch cell " - "(57k / 65k / 91k tokens) and answered CONTINUE all three times, at " - "$0.62. Here the same question is asked of a deterministic DIGEST " - "instead: the person's ask verbatim, one line per tool call with NO " - "results, the last thing said (600 chars), and the files touched - " - "bounded to ~6k tokens.") + L.append("**What this tests.** Checkpoint sketches from bounded transcript digests.") L.append("") L.append("Answers are parsed exactly as `internal/session/checkpoint.go` parses " "them: first non-empty line, stripped of `` ` ``/`*`/`_`, split on " @@ -764,12 +754,9 @@ def score(): L.append(f"- {m}: ${c:.4f} over {n} reads (${c/n:.5f} per read)") L.append("- roughly $0.31 more went on probes and two runs that wedged before " "the deadlock in this script's own progress line was found (their " - "completed reads were resumed, not repaid). Lane total under $1.05 " + "completed reads were resumed, not repaid). Replay total under $1.05 " f"against the ${BUDGET_CAP:.0f} cap.") L.append("") - L.append("For scale: the live wave-1e reader spent **$0.62 on three raw reads** " - "of one cell. Sixty kimi reads of the digest - every cell, every cut, " - "three samples - cost $0.71.") L.append("") L.append("## Recommendation") diff --git a/bench/oneroad/swe/build.sh b/bench/oneroad/swe/build.sh index 731d589d84..6cc64abfaa 100755 --- a/bench/oneroad/swe/build.sh +++ b/bench/oneroad/swe/build.sh @@ -1,6 +1,4 @@ #!/usr/bin/env bash -# build.sh — build the wave-2 images this machine could not build, from the -# patched arm64 Dockerfiles in envs/. Detached, parallel, one log per task. set -uo pipefail SWE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" LOGS="${LOGS:-$HOME/af-bench/swe-builds}" diff --git a/bench/oneroad/swe/cell.sh b/bench/oneroad/swe/cell.sh index 02e0ab23cb..a0582c8358 100755 --- a/bench/oneroad/swe/cell.sh +++ b/bench/oneroad/swe/cell.sh @@ -1,34 +1,4 @@ #!/usr/bin/env bash -# swe/cell.sh — ONE Senior SWE-Bench cell: one arm, one task, one container. -# -# Usage: swe/cell.sh -# -# THE SHAPE, AND WHY IT IS THIS SHAPE. -# -# The task's repository only exists inside its Docker image, with the toolchain -# the repo needs already built (better-auth's image carries a full pnpm build). -# So the harness has to run IN the container. Two ways to get a real TUI in -# there, and this takes the second: -# -# a thin derived image per task with tmux apt-installed into it — seven builds, -# seven images to keep in step with the dataset's own, and a benchmark that -# measures a container it built rather than the one the dataset ships; -# -# TMUX ON THE HOST, driving `docker exec -it` — no image is modified, the -# dataset's container is the one under test, and the pane is a real TTY on a -# real terminal exactly as wave 1's was. -# -# The second also buys the thing that makes the road columns work: the profile -# directory is a BIND MOUNT from the cell's own host directory, so the journal -# the settle detector polls and lib/road.py reads is on the host, at a path the -# existing readers already understand. Nothing about the autopsy changes. -# -# ORACLE SAFETY. solution/oracle.patch, tests/judge/ and task.toml (whose -# [metadata] carries the canonical fix IN PROSE) must never reach a harness or a -# judge. Only tests/ is mounted, at /tests, and tests/judge is masked with an -# empty read-only directory over it. The prompt is instruction.md's `## Task` -# section alone — not the file, which also carries the general instructions, and -# never task.toml. set -uo pipefail ARM="${1:?usage: cell.sh }" @@ -211,19 +181,6 @@ PY tmux send-keys -t "$SESSION_NAME" Enter say "$ARM/$TASK: sent ($(wc -c < "$CELL/prompt.txt") bytes), wall ${CELL_SECONDS}s" - # Settlement, and BOTH READINGS ARE TAKEN INSIDE THE CONTAINER. - # - # The obvious thing — read the bind mount from the host — is silently wrong - # here, and it would have made every SWE row meaningless. The harness runs as - # root in the container and creates its session folder mode 700, so from the - # host `$CELL/profile/v3` is unreadable: `find` returns nothing, with no error, - # and a fingerprint of nothing is perfectly stable from the very first poll. - # The cell would settle blind after one window no matter what the agent was - # doing. The container is the only vantage point that can see its own store. - # - # The rest is wave 1's rule, including the correction that cost six 1f cells: - # a live task VETOES the settle, because a worker running a build or a test - # suite writes nothing for minutes and silence is not a task finishing. local started elapsed stable=0 quiet=0 last="" now screen sess live started=$(date +%s) while :; do @@ -249,7 +206,6 @@ PY live="$(printf '%s' "$tl" | python3 -c 'import json,sys try: print(json.load(sys.stdin)["live"]) except Exception: print(0)' 2>/dev/null)" - # See the wave-1 runner: a live STATE is not a live task. stranded_ids="$(printf '%s' "$tl" | python3 -c 'import json,sys try: print(",".join(str(i) for i in json.load(sys.stdin).get("stranded_ids") or [])) except Exception: print("")' 2>/dev/null)" @@ -292,8 +248,6 @@ run_peer() { opencode) inner="opencode run --auto -m 'openrouter/$MODEL' \"\$(cat /oneroad-prompt.txt)\"" ;; esac docker cp "$CELL/prompt.txt" "$CONTAINER:/oneroad-prompt.txt" >/dev/null - # Their state dirs are bind-mounted out so competitor_cost.py can read the - # session stores afterwards, exactly as it does on the host in wave 1. timeout "$CELL_SECONDS" docker exec -w "/repo/$REPO_NAME" \ -e HOME=/peer -e "OPENROUTER_API_KEY=$OPENROUTER_API_KEY" \ "$CONTAINER" bash -lc "$inner" @@ -333,9 +287,6 @@ docker exec "$CONTAINER" chown -R "$HOST_UID:$HOST_GID" /prof /chome /logs /peer python3 "$SWE/record.py" "$CELL" "$ARM" "$TASK" "$SEED" "$WALL" "$CODE" "$VWALL" "$MODEL" "$TASKDIR" \ "$(cat "$CELL/settle_reason" 2>/dev/null || echo n/a)" -# The SWE bundle, not lib/judge_bundle.py: this track's ISSUE is issue.txt (the -# Task section alone) and its PATCH is the dataset's agent.patch, neither of -# which the wave-1 builder knows about. python3 "$SWE/judge_bundle_swe.py" "$CELL" cleanup say "$ARM/$TASK: done — $CELL" diff --git a/bench/oneroad/swe/wave.sh b/bench/oneroad/swe/wave.sh index 07d0734dc0..8f3fe54520 100755 --- a/bench/oneroad/swe/wave.sh +++ b/bench/oneroad/swe/wave.sh @@ -1,10 +1,4 @@ #!/usr/bin/env bash -# swe/wave.sh — every arm of ONE Senior SWE-Bench task, fired together. -# -# Same arm-fair rule as wave 1: the arms of a task start within seconds of each -# other so provider weather falls on all of them or none. The cap is lower here -# than on the live-issue track — each cell holds a container with 4 CPUs and 8 GB -# reserved, so five of them is already most of a machine. set -uo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" TASK="${1:?usage: wave.sh [arms...]}"; shift diff --git a/bench/oneroad/wave.sh b/bench/oneroad/wave.sh index 0df613a8c4..487d222386 100755 --- a/bench/oneroad/wave.sh +++ b/bench/oneroad/wave.sh @@ -12,8 +12,6 @@ set -uo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" TASK="${1:?usage: wave.sh [arms...]}"; shift ARMS="${*:-codeaf-new-flash codeaf-new-crew codeaf-old-flash pi opencode}" -# SEED rides through so a wave can be repeated: wave 1f runs the same arm twice -# over the same five tasks, and the two runs must land in different cells. SEED="${SEED:-s1}" mkdir -p "$ROOT/results" diff --git a/cmd/codeaf/cell_list_test.go b/cmd/codeaf/cell_list_test.go index 37cfcce98b..f4bfb33307 100644 --- a/cmd/codeaf/cell_list_test.go +++ b/cmd/codeaf/cell_list_test.go @@ -22,8 +22,8 @@ func withListSource(t *testing.T, src chatlist.Source, err error) { func TestCellListAll(t *testing.T) { withListSource(t, chatlist.Static{ - {Cell: "01AAA", Title: "Nightly index rebuild", Device: "studio", Status: chatlist.Running, DurableAgo: 2 * time.Minute}, - {Cell: "01BBB", Title: "Port the picker", Device: "studio", Status: chatlist.Off, DurableAgo: 3 * time.Hour}, + {Cell: "01AAA", Title: "Nightly index rebuild", Device: "host-a", Status: chatlist.Running, DurableAgo: 2 * time.Minute}, + {Cell: "01BBB", Title: "Port the picker", Device: "host-a", Status: chatlist.Off, DurableAgo: 3 * time.Hour}, {Cell: "01CCC", Title: "Fix the flaky test", Device: "laptop", Status: chatlist.Branch, OrphanTurns: 2, DurableAgo: 5 * time.Hour}, {Cell: "01DDD", Title: "Notes", Device: "laptop", Status: chatlist.Idle, DurableAgo: 26 * time.Hour}, }, nil) @@ -32,8 +32,8 @@ func TestCellListAll(t *testing.T) { t.Fatal(err) } want := []string{ - "01AAA Nightly index rebuild studio running on studio 2m", - "01BBB Port the picker studio studio offline 3h", + "01AAA Nightly index rebuild host-a running on host-a 2m", + "01BBB Port the picker host-a host-a offline 3h", "01CCC Fix the flaky test laptop 2 turns from laptop: discard 5h", "01DDD Notes laptop - 1d 2h", } diff --git a/cmd/codeaf/chat.go b/cmd/codeaf/chat.go index 9415ddb3ba..d6c00ec0eb 100644 --- a/cmd/codeaf/chat.go +++ b/cmd/codeaf/chat.go @@ -3336,9 +3336,7 @@ func withDocumentAttachmentBrief(brief string, paths []string) string { } // liveClient and messageClientPool are internal/provider/pool's Client and -// Pool. The provider seam moved out of this file in the Wave 1 dissolution; -// these names stay because they are what this package's own prose has always -// called them, and an alias is the whole of the difference. +// Pool. These aliases keep the local names consistent with the provider pool. type ( liveClient = pool.Client messageClientPool = pool.Pool diff --git a/cmd/codeaf/chatv3_process_test.go b/cmd/codeaf/chatv3_process_test.go index 8081ced60f..0f88cd2f95 100644 --- a/cmd/codeaf/chatv3_process_test.go +++ b/cmd/codeaf/chatv3_process_test.go @@ -175,7 +175,7 @@ func TestABankedRuleReachesTheAgentItWasMintedWith(t *testing.T) { t.Fatalf("the always was not saved: %v", err) } if len(closed.pushed) != 0 { - t.Fatal("the rebuilt gate went into the conversation that was closed — the bug this lane fixes") + t.Fatal("the rebuilt gate went into the conversation that was closed") } if len(front.pushed) != 1 || front.pushed[0] == nil { t.Fatalf("the conversation in front was handed %d gates", len(front.pushed)) diff --git a/cmd/codeaf/devices_revoked_test.go b/cmd/codeaf/devices_revoked_test.go index 1c110531ba..8372de03c4 100644 --- a/cmd/codeaf/devices_revoked_test.go +++ b/cmd/codeaf/devices_revoked_test.go @@ -40,11 +40,11 @@ func TestRevokedDeviceReadsAsRevokedOnEveryComputer(t *testing.T) { // list reads it like every other label does. func TestOpenNameDropsTheLocalSuffixLikeEveryOtherLabel(t *testing.T) { key := []byte("0123456789abcdef0123456789abcdef") - sealed, err := directory.SealName(directory.MetadataKey(key), "studio.local") + sealed, err := directory.SealName(directory.MetadataKey(key), "host-a.local") if err != nil { t.Fatal(err) } - if got := openName(directory.MetadataKey(key), sealed); got != "studio" { + if got := openName(directory.MetadataKey(key), sealed); got != "host-a" { t.Fatalf("openName = %q", got) } } diff --git a/cmd/codeaf/devrename_test.go b/cmd/codeaf/devrename_test.go index a60b084baa..b608ea3c5a 100644 --- a/cmd/codeaf/devrename_test.go +++ b/cmd/codeaf/devrename_test.go @@ -94,7 +94,7 @@ func TestAJoinAsksForANameAndEnterKeepsTheHostName(t *testing.T) { chosen bool }{ {"\n", devname.Default(), false}, - {"studio\n", "studio", true}, + {"host-a\n", "host-a", true}, {"bad\x00\ngood name\n", "good name", true}, } for _, c := range cases { diff --git a/cmd/codeaf/do_residenthost_test.go b/cmd/codeaf/do_residenthost_test.go index 8aee09d932..fbb7be99c2 100644 --- a/cmd/codeaf/do_residenthost_test.go +++ b/cmd/codeaf/do_residenthost_test.go @@ -7,7 +7,7 @@ import ( ) func TestResidentHostReadsLikeEveryOtherComputerLabel(t *testing.T) { - cases := map[string]string{"studio.local": "studio", " desk ": "desk", "": "unknown host"} + cases := map[string]string{"host-a.local": "host-a", " desk ": "desk", "": "unknown host"} for host, want := range cases { if got := residentHost(&lease.Resident{Host: host}); got != want { t.Errorf("residentHost(%q) = %q; want %q", host, got, want) diff --git a/cmd/codeaf/wormhole_contract_test.go b/cmd/codeaf/wormhole_contract_test.go index 2366cee26a..3deb48d0e1 100644 --- a/cmd/codeaf/wormhole_contract_test.go +++ b/cmd/codeaf/wormhole_contract_test.go @@ -107,7 +107,7 @@ func TestC4AStartupFaultReleasesWaitingTools(t *testing.T) { // or sync tick is needed for the joining device to list its approver. func TestC1ChatApprovalPublishesTheApproverBeforePaired(t *testing.T) { rig := newPairRig(t) - if _, err := devname.Set(rig.homeA, "studio"); err != nil { + if _, err := devname.Set(rig.homeA, "host-a"); err != nil { t.Fatal(err) } screen, done := rig.asking(t) @@ -128,7 +128,7 @@ func TestC1ChatApprovalPublishesTheApproverBeforePaired(t *testing.T) { t.Fatal(err) } for _, row := range rows { - if !row.Self && row.Name == "studio" { + if !row.Self && row.Name == "host-a" { return } } diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 25e3c53e0e..58e6ef96f3 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -392,21 +392,9 @@ stream → Watch.Token / Silence → a verdict, and at most one hedge - **The choice is pure.** `lane.Request` carries its own `Now`. A structural test fails the build when a choosing file names `time.Now`. -### The file map, and who owns which file +### Routing implementation -Wave 0 landed the contract and the instrument. The five wave-1 lanes each own a -disjoint set of files, so no two of them edit the same one. - -| lane | owns | delivers | -| --- | --- | --- | -| — (wave 0) | `internal/lane/{lane,posterior,contract,registry}.go`, `internal/lane/lanestub/` | the types, the five interfaces, the registry, the fake router | -| L-A | `internal/lane/sheet*.go`, `belief*.go`, `store*.go` | the endpoints client and its beat, the Kalman ledger, `~/.codeaf/v3/lanes.json` | -| L-B | `internal/lane/choose*.go`, `frontier*.go`, `value*.go`; `internal/provider/lanes.go`; λ plumbing in `internal/session` | the gate, the Pareto prune, the scalar, and the adapter that replaces the velocity ledger's order and ignore | -| L-C | `internal/lane/watch*.go`, `hedge*.go`, `probe*.go`; `internal/provider/hedge.go`, `probe.go`; the stream loop in `internal/provider/client.go` | the derived deadline, the heartbeat and drift tests, the budgeted hedge, the probe on typing | -| L-D | `internal/tui3/lanes*.go` and the picker, settings and HUD edits; the lane keys in `internal/config`; the manual pages | the speed column, the lane unfold, the filter grammar, the settings rows | -| L-E | the new fields in `internal/session/usage_ledger.go` and `internal/calllog`; `the benchmark harness/`; `internal/lane/e2e_test.go` | the ledger fields, the simulator and the live A/B, and the real-key proof under `-tags e2e` | - -Two practical notes for the lanes. `internal/provider` already has an unexported +Two integration details. `internal/provider` already has an unexported type called `lane` (`velocity.go`), so the adapter file imports this package under a name — the collision is real and the compiler will not warn about the shadowing, it will simply resolve to the wrong one. And the transport only sends @@ -414,11 +402,9 @@ a routing preference to something it believes is a router, from the base URL or the configured model, so a test that wants to see `provider.order` on the wire against `lanestub` configures its client with a model spelled `openrouter/…`. -### The four seams wave 2b joined +### Routing integration boundaries -The five lanes each left a socket. Wave 2b is where they were plugged in, and -four of the joins are worth stating because each one is a place two layers had -to agree without either importing the other. +Four boundaries connect the routing layers without circular imports. | what | where it lives | how it crosses | | --- | --- | --- | @@ -436,8 +422,8 @@ row attributing one machine's measurement to another the day it landed. ### The instrument `internal/lane/lanestub` is a fake router with lanes of a scripted speed, and it -is shared by all five lanes for the reason a shared fixture usually is not: five -private routers would differ, and the first disagreement between them would look +is shared across routing tests: separate +routers would differ, and the first disagreement between them would look like a bug in the code under test. It serves the endpoints sheet in the router's own field names, a minimal catalog, and a streamed completion that honours `provider.order` / `only` / `ignore`, emits the router's heartbeat comments, diff --git a/docs/DESIGN-LANGUAGE.md b/docs/DESIGN-LANGUAGE.md index 9da1385a10..a451e5cd3d 100644 --- a/docs/DESIGN-LANGUAGE.md +++ b/docs/DESIGN-LANGUAGE.md @@ -90,7 +90,7 @@ No new colour arrives for the emphasized state. No run of bolding spreads across the row. And above all **no outline is added** — emphasis is a step UP this ladder, never a ring drawn around a thing. -A QUESTION'S FRAME IS NOT EMPHASIS (owner ruling, 2026-09-11). The dim frame a +A QUESTION'S FRAME IS NOT EMPHASIS. The dim frame a question hangs in (WHAT WE REFUSED → Borders, amended) says "this is one object, and it is asking you" — a claim about what the thing IS, drawn once, the same way whatever row inside it is focused. It never lights, never thickens and never @@ -296,7 +296,7 @@ brick red, one clear step from `bad`'s 173. The obvious alternative, L 62, lands on **168**, which is a pink; a diff whose minus lines went pink on every 256-colour terminal would have been the fallback nobody looked at, again. -**`ask` was untouched and untouchable — until the owner retired it (2026-09-11).** +**Questions use the shared amber status hue.** `#C08FE8` (light `#6F3FA8`) was spent on the moment the agent is waiting for a person and on nothing else. What was true: it was also spent on every WORD of that moment — the head, each answer, each key — while home, the places and the chip @@ -308,18 +308,7 @@ keep the old values as history; neither is on any ladder any more. ## THE PLACE LADDER — the same table, with three roles re-pointed -**Owner-signed, 2026-08-25, and then reversed by the owner the same day.** The first -order was "follow the exact design": home and the six places beside it — tasks, -standing, memory, spend, search, settings — took the captured design's nine hexes, -painted `#12121A` over every cell of the -frame, and carried place-scoped variants of the glare law, the isoluminant band and -the ground ladder to admit them. It was built and shipped for testing. The owner ran -it and said: - -> "i want bg color and text color to be same as in inside chat please this new bg -> looks weird i think we were taking user terminal stuff or something previously" - -**So a place paints from the table above and authors nothing of its own.** There is no +**A place paints from the table above and authors nothing of its own.** There is no second palette, no second ceiling, no second band, and no page ground on any surface — `placeSignalBand`, `placeGlareCeiling` and the place ground ladder are gone from `internal/tui3/designlanguage_test.go`, and REST IS NOT A COLOUR is one law again. @@ -477,8 +466,7 @@ paints exactly one cell — the glyph at the head of its row — and the title b it keeps the ordinary ink. A needs-you row's amber is on the `?` and not on the sentence. -A QUESTION WEARS AMBER ON ITS MARKS AND NOWHERE ELSE (owner ruling, 2026-09-11, -colour pick C). Until that day the conversation painted a question's head, every +A QUESTION WEARS AMBER ON ITS MARKS AND NOWHERE ELSE. Previously the conversation painted a question's head, every answer label, every key and every separator in a violet of its own (`#C08FE8`) while home and the places said the same "waiting on you" in amber — two hues for one meaning, and whole rows in a status colour, which is this law broken on the @@ -622,7 +610,7 @@ two regions are different kinds of thing, and on a conversation surface they mostly are not. This is also why the emphasis law forbids adding a ring: rings are borders arriving one row at a time. -*Amended 2026-09-11, owner ruling (question views, frame pick A):* **a question +**a question hangs above the box in a frame.** What was true: no border anywhere, with three dim rounded boxes that shipped anyway as argued exceptions (the `/folder` chooser, the `ctrl+k` switcher card, the onboarding panel), each with its own diff --git a/docs/REMOTE.md b/docs/REMOTE.md index 8e8231853c..d181805772 100644 --- a/docs/REMOTE.md +++ b/docs/REMOTE.md @@ -204,7 +204,7 @@ the engine resolve its own context window, and a remote surface's context-window ignored, so compaction cannot be sized by a stale or different laptop cache. **Why it is stated as an architecture decision and not left to taste.** Every -honesty bug this lane has had came from a screen answering for the wrong +display correctness bug came from a screen answering for the wrong machine. The YOLO badge is drawn from the engine's own profile because a badge read off the laptop would be a safety claim about a machine nobody consulted. Automations are listed off the engine's disk because they run there. @@ -273,7 +273,7 @@ command to diff against it, and what reading it turned up. **Why not write the PAKE.** For the same reason nothing else here is hand-rolled: a plausible-looking PAKE that is subtly wrong is indistinguishable from a correct one until -somebody attacks it. The house rule for this lane was that a hand-rolled primitive which +somebody attacks it. The implementation rule is that a hand-rolled primitive which *looks* like it works is the worst available outcome — worse than the capability being absent. @@ -448,8 +448,7 @@ know on its own. **Why not leave them as questions.** Because a question asked while drawing is a question asked thirty times a second, and over an ssh pipe that is the repaint -rate of the terminal set to the round-trip time of the link. The owner met this -as "even hover seems to slow everything down": the status row asked the agent +rate of the terminal set to the round-trip time of the link. Pointer movement exposed this delay: the status row asked the agent for the reasoning rung while it was painting, and a pointer below the conversation rebuilds the chrome per cell, so a two-hundred-cell sweep queued seven seconds of keystrokes behind two hundred round trips. PERF.md's connection diff --git a/docs/SUBHARNESS-CONTRACT.md b/docs/SUBHARNESS-CONTRACT.md index 0f98f55201..3e7cae9fbd 100644 --- a/docs/SUBHARNESS-CONTRACT.md +++ b/docs/SUBHARNESS-CONTRACT.md @@ -320,11 +320,3 @@ appears on nothing a person picks from. That is the same law `internal/exec/subharness.go` already states — the baseline is what every node is judged against, not an entry on a menu — and it is why `Manifest.Validate` exempts exactly one name from needing a purpose. - -## 8. Gates - -`gofmt -l` clean on every touched file. `go build ./...` clean. -`go test ./internal/exec/ ./internal/session/ ./internal/subharness/ -count=1` -green, as are `./cmd/codeaf/` and `./internal/resident/`. No manual page yet: -this lane landed no slash command, no belt verb and no person-facing surface — -the pages come with the surfaces, per the manual law. diff --git a/docs/TRAJECTORIES.md b/docs/TRAJECTORIES.md deleted file mode 100644 index 648411bb6f..0000000000 --- a/docs/TRAJECTORIES.md +++ /dev/null @@ -1,999 +0,0 @@ -# Every trajectory the system can take - -*An audit, w41. The question it answers is the owner's: "conceptually, every -trajectory the system can take — is it exactly how we want?" Every row below was -read off the code, not inferred from a design note; `file:line` anchors were -taken against `chat-v3-task` at `cd2370d7`. This tree moves under several -sessions at once — re-grep an anchor rather than trusting the number if it does -not land.* - -*Updated w42, 2026-08-24, by the lane that fixed C1, C2, C3, M1, M2, M4, m3, m14 -and M6(b). Verdict rows carry the date where they moved; the finding paragraphs -under **FIXED** headings are left as they were written, because they are the -evidence the fix was made against. Line numbers in this document are from w41 and -have moved.* - -*Updated w43, 2026-08-24, by the lane that fixed M5's division half and M7 — the -two standing-order gaps. A firing that turns out to be wide now takes the -division road under the same gates as everything else, and the birth seam stopped -handing a reminder to a worker as a house rule.* - -*Updated w44, 2026-08-24, by the ONE ROAD wave. **Law 2 below changed shape**: the -planner DAG is still the exception, but a chat turn can no longer take it. The -`run_adaptive` hand is off the belt, `/task adaptive` and the `adaptive` preset -are retired, and the route judge starts a task on a yes rather than offering a -shape. Rows describing those doors are marked RETIRED where they stood, because -the audit's value is in showing which trajectories closed and when. Everything -about the ENGINE (`internal/orchestrate`, `Agent.RunOrchestrate`, the fuel tank, -`/subharness`, `cmd/harness-design`) is unchanged.* - -*Updated w44, later the same day, by the CUE-DOOR lane. **Law 2 below changed shape -again**, and this time it closed: the anchored `orchestrate …` cue is gone from the -turn loop, so a conversation has NO door onto the planner DAG — not a hand, not a -command, not a preference, and not a form of words. `routeOrchestrate`, -`orchestrateGoal` and the cue's three regexps went with it; `startOrchestrate`, -`announceOrchestrate`, `Agent.RunOrchestrate`, the roster family, the fuel gate and -`Config.OrchestrateRunner` were deliberately LEFT STANDING, marked where they stand as -having no chat-side caller, because ripping the engine's wiring out is a separate -decision nobody has made. Rows and bullets naming the cue as an open door are marked -where they stood. The ENGINE is still unchanged: `internal/orchestrate`, -`cmd/harness-design`, and `/subharness` on its own runner.* - -## The world this is measured against - -Four laws, decided across waves 35–40, are what "as-intended" means here. - -1. **Division is the default road for wide work, everywhere.** One worker - starts; it splits itself under two gates — the evidence gate - (`internal/splitgate`) and the free-hands gate (`TaskGraph.freeHands`) — and - the parts are picked up as hands free. The parent stays and folds. -2. **The planner DAG is the explicit exception, and since w44 a conversation - cannot take it at all.** `run_adaptive`, `/task adaptive`, the `adaptive` - preference and finally the typed `orchestrate …` cue are all gone, in that - order. Width alone was never the reason and now nothing is: no hand, no - command, no setting and no sentence opens a planned graph from chat. The - engine is reached by `cmd/harness-design` on a driver of its own. Narrow work - is byte-identical to the pre-swarm world. -3. **Consent.** Nothing runs from a model decision without the person's yes - where a yes is required. Headless never auto-approves a question. A - subharness `ask()` with nobody there is never a yes. -4. **One ledger.** Every road's spend folds into the session ledger; every - worker is cancellable through the existing routes; standing orders reach - every born worker; every live worker appears in `WorkingNow` — never faked, - never missed. Prompt and tool descriptions are one source of truth with the - code's actual routing. - -## The answer, in three sentences - -**Every door routes correctly.** No belt verb, prompt sentence, judge, surface, -setting or manual page still steers wide work to the planner; the reword landed -cleanly and a test pins both the new sentences and the absence of the old ones. - -**Two roads did not actually run, and both do now** (w42, 2026-08-24). -`divide_work` had never been on a production belt (**C1**), and a subharness -falling back to the long way escaped the tool ceiling it was approved under -(**C2**). Both are fixed and pinned by tests through the real doors. - -**The rest is a short list of honest gaps** — a set of sentences that promise -more than their code does, and the restart and planner gaps below. The wide-work -door that admitted unarmed (**M1**), the refusal that named the wrong cause -(**C3**) and the ledger leak on the stop path (**M2**) were closed in the same -lane. The two standing-order gaps followed in w43: the one road on which width -could not be said was the unattended one (**M5**), and the birth seam said "these -are not suggestions" over a reminder (**M7**). - ---- - -## A. Typed — a person typed something - -| trajectory | door | road taken | armed? | verdict | evidence | -| --- | --- | --- | --- | --- | --- | -| `/task ` bare | `internal/tui3/taskcommand.go` `startTaskDoor` → `internal/session/task_person.go` `StartTask`, which admits at once (#936) | **one worker, at once** — no forming block, no wait on a model | **no** at admission: `spec.armed` is frozen before anything is read; the judge reads the sentence BESIDE the worker and its parts are weighed as a division (`task_divide_sketch.go` `proposalBeside` → `judgedDivision`, asker `askedByJudge`) | as-intended | `TestThePersonsTaskDoorAsksNoModel` (go/ast), `TestTheWorkersFirstRequestGoesOutBeforeTheShaperOrTheJudgeAnswers`, `TestAWideTaskStartsItsWorkerFirstAndTheJudgesPartsArriveAsTheReceipt` | -| ~~…its dim line~~ | **RETIRED #936** — `the work looks wide` went with the surface's sizing call | — | — | closed | the judge's yes now arrives in the task's own room as the division receipt | -| `/task solo ` | `taskcommand.go` `runTaskCommand` → `StartTask(…, solo=true)` | one worker, the width is never read | enumeration only | as-intended | `spec.unsized` is false, so `proposalBeside` asks no judge | -| ~~`/task adaptive `~~ | **RETIRED w44** — the word is off the command and off the slash menu, and `StartPlannerRun` was deleted with it (`task_person.go` says so where it stood) | — | — | closed | there is no `/orchestrate`, `/adaptive`, `/plan` or `/swarm` command either; the full table is `internal/tui3/commands.go` | -| preset `sized` (default) | `internal/config/settings.go:1446`, resolver `:2772` | as `/task` bare | judge | as-intended | `settings.go:534` | -| preset `adaptive` | **RETIRED w44** — off `TaskStartModes`, and the constant went with it | reads as `sized` | judge | closed, and by the same read-time fallback `ask` got | `TaskStartAt` returns `DefaultTaskStart` for any word this build does not know, so a profile written months ago loads clean and says nothing; pinned by `TestAProfileStillHoldingTheRetiredWordReadsAsSized` | -| preset `single` | `taskcommand.go:93-94` | one worker, no sizing call | enumeration only | as-intended | `settings.go:526` | -| legacy preset `ask` | removed with its card (`settings.go:519-522`) | reads as `sized` | judge | as-intended | a read-time fallback, not a migration: `TaskStartAt` returns `DefaultTaskStart` for anything unrecognised (`settings.go:2773-2781`), and `Choices: TaskStartModes` stops the sheet writing it back | -| `/subharness` list + card | `internal/tui3/app.go:4420` → `internal/tui3/subharness.go:861` | a subharness run as a task node | n/a — the node runs `exec.Runner`, never a belt | as-intended | **two deliberate enters**: a list row only opens the card (`subharness.go:1069-1072`); the card's run row calls `runSubharness` (`:1128-1137`). A mouse press moves the cursor and never acts (`:1279-1283`) | -| `/standing ` | `internal/tui3/app.go:4393-4409` → `internal/tui3/standmark.go:219` | a marked send — never work | n/a | as-intended | `SubmitStanding` prefixes an instruction to *propose* rather than carry out (`internal/session/standing_mark.go:62-97`); `standing_contract.go:14` — "NOTHING STANDS UNTIL THE ANSWER IS YES, AND NOBODY PRESENT MEANS NO" | -| ctrl+enter (composer) | `internal/tui3/input.go:543-550` → `standmark.go:179` | same marked send | n/a | as-intended | a draft starting with `/` falls through to the ordinary command road (`standmark.go:190-195`) | -| ctrl+enter (home) | `internal/tui3/home.go:2002-2010` → `homeexchange.go:805` | an errand conversation | n/a | as-intended | not a task and not a planner | -| the margin's `+` doors | `internal/tui3/margin.go:76-82`, press `:313-317` → `:340-352` | **types into the draft; starts nothing** | n/a | as-intended | the file's own law at `margin.go:39-42`: "THE `+` ROW TYPES, IT DOES NOT ARM." | -| home's work doors | `home.go:2398-2404` | a new conversation, or an `ask here` errand | n/a | as-intended | neither admits a task | -| ~~`orchestrate …` typed~~ | **CLOSED w44 (cue-door lane)** — the cue, its parser and its route are deleted; `loop.go` says where they stood and why | **an ordinary turn** — the model answers, and the route judge starts a task if it reads as work | as the judge arms anything | closed | this was the last door of any kind onto the planner from a conversation, so law 2's exception is now unreachable from chat; `internal/session/orchestrate_test.go` pins the six old cue sentences as ordinary turns that never call the runner | - -## B. Model-decided — the model reached for a verb - -| trajectory | door | road taken | armed? | consent | verdict | -| --- | --- | --- | --- | --- | --- | -| `propose_task` without `wide` | `task.go:279` → `:311` → admit `:412` | one node | enumeration only | countdown card (`task.go:570-593`); silence is yes | as-intended | -| `propose_task` **with `wide`** | schema `task.go:119`, spec `:492` | **one node — `proposeTask` has no planner branch at all** | **yes**, signal one (`task_divide.go:195`) | same countdown | as-intended in routing; the description over-promises — **M6** | -| ~~`run_adaptive`~~ | **RETIRED w44** — off the belt outright, and `canOrchestrate`, the gate that existed only to guard it, was deleted with it | — | — | — | closed: the model has no verb for a planned run, so no model decision can open one | -| `build_harness` tail | `tools_harness.go:192` → `harness_task.go:453` → admit `:463` | a design node; the page is written, then carded | **no — the kind guard refuses it** | save-or-discard card, and **no clock at all** (`harness_build.go:383-400`) | as-intended (M4 fixed w42, 2026-08-24) | -| `propose_subharness` | `tools_subharness.go:129`, guard `:122` | intake card → `startSubharnessRun` `subharness_contract.go:190` | n/a | mandatory card; **no clock that says yes** — the 15-minute window expires to *nothing ran* (`tools_subharness.go:388-392`) | as-intended | -| **route judge card** | `route_judge.go:146` → card `:304` → `launchRouteTask` `:395` → admit `:413` | one node from the judge's goal | **yes**, the judge's own `wide` | the card; a no returns before any admit (`:200-203`); a yes is deliberately not re-asked (`:383-388`) | as-intended (M1 fixed w42, 2026-08-24) | -| `stand` | `tools_standing.go:299`, guard `:295` | a standing item | n/a | card, mandatory, **no clock**: `Deadline` explicitly zeroed (`:963`), two select arms only (`:967-976`) | as-intended | -| `divide_work` itself | `task_divide.go:114`, guard `mayDivide` `:138` | parts on the same nesting road `propose_task` uses | — | none needed — a refusal is an ordinary tool result | as-intended (C1 fixed w42, 2026-08-24) | - -`run_adaptive`'s description used to say "THIS IS THE EXCEPTION AND NOT THE ROAD -FOR BROAD WORK … A goal that is merely WIDE is not one of them" and name -`propose_task` with `wide` as the road. **w44 finished that argument by taking the -verb away**: there is no description to get right, because there is no hand. `propose_subharness`'s "nothing runs -because you proposed it" is enforced by `ResolveSubharness` (`:414-423`) being -the single writer of the answer channel, with exactly two callers of -`startSubharnessRun`: `/subharness`, and a confirmed card. `stand`'s "a session -nobody is watching cannot set one up at all" is true twice over — `askStanding` -refuses when unwatched (`:940-943`), and unwatched doors never fill -`Config.Standing` (`cmd/codeaf/chatv3.go:155-160`, `standing_run.go:646`). - -### Where wide work can be admitted, and whether it is armed - -`TaskGraph.admit` (`task_run.go:678`) is genuinely the one arming door — it calls -`armDivision` at `:686` for every spec, whoever opened it. Six call sites: - -| # | call site | admits | can carry wide work? | armed by | verdict | -| --- | --- | --- | --- | --- | --- | -| 1 | `task.go:412` `proposeTask` | the model's proposal | **yes** | `spec.wide`, else enumeration | as-intended | -| 2 | `task_person.go` `StartTask` | a person's `/task ` | **yes** | enumeration at admission; the judge's parts beside the worker as a division | as-intended | -| 3 | `task_divide.go:301` `divideWork` | one part of a division | bounded | inherited `request` matches the bank — **by accident** | drifted, inert (**m2**) | -| 4 | `route_judge.go:413` `launchRouteTask` | the judge's carded task | **yes** | `verdict.wide`, else enumeration | as-intended (M1 fixed w42) | -| 5 | `harness_task.go:463` `admitHarnessDesign` | a harness design | no — it is one page | **nothing: `armDivision` refuses any spec whose kind is not ordinary** | as-intended (M4 fixed w42) | -| 6 | `subharness_run.go:108` `admitSubharnessRun` | a saved program's run | no — the steps are fixed | nothing, by the same kind guard | as-intended (m3 fixed w42) | - -All three doors that can carry genuinely wide work are armed, and the three that -cannot are refused by kind at the one arming door — `armDivision` -(`task_divide.go`) asks `spec.kind()` before it looks at any of the three -signals, and `restoreNode` asks `record.Kind` for the same reason on the restart -path. - -## C. Autonomous — nobody typed; the machine started it - -| trajectory | door | what it starts | road | armed? | consent | verdict | -| --- | --- | --- | --- | --- | --- | --- | -| standing `WhenHold` | `internal/standing/standing.go:143`, skipped `tick.go:166-168` | **nothing** | none | n/a | n/a | as-intended — it returns before the daily rail, the spend rail and the whole look/judge/fire walk | -| standing `WhenAt` / `WhenEvery` / `WhenFile` / `WhenIdle` / `WhenProbe` | `internal/standing/tick.go:250-331`, dispatched `:398-409` | `ActionSay` one line, or `ActionTask` a headless session that may now hand its parts out (`standing_run.go:506-640`) | the division road — `standingWideWork` gives a wide firing its own graph and the one node it IS | **yes — enumeration only** (`enumeratesWidth`, `task_divide.go`) | ratified at proposal time; a firing asks nobody | as-intended (M5 fixed w43, 2026-08-24) | -| holds reaching workers | `standing_world.go:118`, appended `task_run.go:826`, `:1035-1039` | — | — | — | — | as-intended (M7 fixed w43, 2026-08-24) — one read per frontier pass, appended last to the brief, no per-node branch, and division parts join the conversation's own graph so `g.home` resolves the person's place. Every kind still rides, and now in **two registers**: a `WhenHold` under the binding sentence, every waking kind under one that says it is not a condition over this work | -| resident leaf `request_split` | `internal/exec/tools.go:936`, armed `internal/exec/linear.go:600` | the leaf **ends** and its parts replace it | the resident's own growth road | by config, never per-node | nobody is asked; the money gate defers | as-intended — a deliberate product difference, documented on both sides (`task_divide.go:50-57` vs `linear.go:794-810`) | -| the shared gate | `internal/splitgate/splitgate.go` | — | — | — | — | as-intended — genuinely one implementation, and `cmd/codeaf/cooperative.go:148-156` keeps the old names as forwarders over `splitgate.Floor` — except **M8** | -| `codeaf chat --once` | `cmd/codeaf/chatv3.go:146-172` | one headless turn | division road present but inert (C1) | `spec.wide`/enumeration | **`AskConsent=false` ⇒ the policy's "prompt" refuses** (`consent.go:282-284`); `Standing` nilled | as-intended | -| `codeaf do` | `cmd/codeaf/main.go:117` → `do.go:142` | one resident errand | resident: plan gate + leaf split | n/a | spend consent false unless `--yes-spend` (`do.go:308-317`); a question ends the run at exit 1 into `blocked_on` (`:704-713`) | as-intended on consent; the road is the resident's | -| `codeaf exec` | `main.go:125` → `exec.go:30` | one linear pass | none | no | vacuous — no gate exists | as-intended; see **m5** | -| `codeaf run ` | `main.go:123` → `subharness_run.go:54` | one typed program | none | **exemplary** — `headlessEnv.Ask` returns `Unanswered=true` and stops (`cmd/codeaf/subharness_env.go:259-283`): "Nothing here ever returns an approval" (`:255-257`) | as-intended | -| checkpoint recovery | `task_store.go:838` → `:994` `restoreNode` | restored nodes on the frontier | division; never the planner | **re-derived, not persisted** | none required | drifted — **M3** | - -Exit codes: **there is ONE ladder and it is `exitLadder` in -`cmd/codeaf/envelope.go`**, which every headless verb now reads rather than -spelling its own. `TestTheExitLadderIsOneTable` fails by name for a verb that -disagrees with it, and `codeaf --help` prints it interpolated from the same -list, so this document deliberately does NOT restate the numbers — the table -that used to be here was a per-verb list (`do` 0/1/2, `exec` 0/2/3/4/5/6) that -had gone stale in both its rungs and its line references, and it was the FIFTH -copy of that ladder found in this repository. `CODEAF_EXIT_CODES=legacy` -restores `exec`'s old numbers for one release. `tick` is outside the ladder: 0 -clean, 0 on `ErrHeld`, 1 otherwise (`cmd/codeaf/tick.go`). - -### What a restart keeps - -`taskRecord` (`task_store.go:141-257`) carries neither `divide` nor `wide` — the -file says so itself ("The road is not on the record", `:1034-1036`). Restored -nodes bypass `admit` and are inserted directly (`:972`), so `:1041` is the only -arming door on that path. - -| arming cause | survives a restart? | -| --- | --- | -| `spec.wide` — the proposer's own judgement | **lost** — not on the record | -| the sizing judge's yes | **lost** — a one-entry in-memory bank (`task_divide.go:208-225`) | -| `splitgate.WorthIt(title+brief+acceptance)` | survives — all three strings are persisted | - -Coordination survives: `Parent`/`Depth` are persisted (`:171-172`) and -`childrenOutstanding` is graph-derived (`task_run.go:2852-2862`), so a re-run -parent re-discovers outstanding parts. - -## D. In-flight — work is running and something changes - -| trajectory | mechanism | behaviour | verdict | -| --- | --- | --- | --- | -| parts inherit standing orders | `task_run.go:826` + `:1035-1039`; `standing_world.go:118-131` | orders arrive through the brief and only the brief; the part's own config carries no `Standing` door, so nothing is said twice | as-intended — `TestThePersonsOwnWordsAndTheStandingOrdersReachEveryPart` PASS (it calls `briefLocked` by hand, so the frontier→part seam is pinned at one remove) | -| parts inherit the person's words | `task_divide.go:282` `a.taskRequest()` | each part opens on the sentence the person typed | as-intended — same test | -| journals | `task_run.go:3435-3444`, `:3301` | one `tasks/_.jsonl` per part; the URI reaches the parent in the landing note (`:1702`, `:1736-1747`) and reaches a `tasks` row through `TranscriptURI` (`task_index.go:629`) with the live graph merged over the file (`:437-484`) | as-intended, and the path is now the family's rather than the owner's (`Place` gap fixed w43, 2026-08-24) — a part's journal is pinned by `TestAPieceOfATasksWorkKeepsToTheSameSessionFolder` | -| cancel-tree | `cancel.go:73-96`; `task_run.go:2126` + `:2235-2243` | stopping the parent cuts every unsettled kid, and each kid repeats it — a real cascade with no recursive walk. A part is individually stoppable from its own roster row (`internal/tui3/stop.go:277-304`) | as-intended **for the person**; a gap for the model — **M9** | -| spend fold | `task_run.go:3182-3204`; `loop.go:2101`, `:2127-2155` | on the ordinary path, exactly once: a part folds into the parent worker, which the conversation later folds whole into the session ledger; ordering is safe because a part folds in its own defer before `markNoted` and the tail loop waits on `reported()`. On a stopped ending the parent's books are already closed, so `Agent.spendLedger` skips the closed hop and folds the part straight into the conversation — the same total by a shorter route | as-intended (M2 fixed w42, 2026-08-24) | -| the parent stays and folds | tail loop `task_run.go:2788-2810`; park `:2187-2201` | waits on the **report** and never on the state (`:2780-2784`); hands its lane back at `:2799`, takes it back at `:2804`; exits only when nothing is owed, nothing is outstanding and no steered line is queued | as-intended — `TestAParentWaitingOnAPieceHandsBackItsLane` PASS; the queued-line clause is M12's fix, `TestSteeringAParkedParentWakesItAndArrivesInItsNextTurn` | -| deadlock at `parallel=1` | `freeHands` `task_divide.go` | cannot happen: with the asker in the only lane `freeHands` is 0 and the division is refused up front — deliberate, argued in `freeHands`'s own comment. The refusal (`divisionNoLane`) now says which of the two lane cases it is, and does not invite the worker back where the cap is exactly one | as-intended (C3 fixed w42, 2026-08-24) | -| every live worker on the surface | `work_tree.go:85-148`, `:161-170` | tasks + parts + adaptive runs + their nodes + background jobs; a root is drawn while anything under it is live and then the whole family is drawn; every row's id is `cancel.go`'s own spelling | as-intended — "NEVER FAKE LIVENESS" (`:76-78`) — with one surface drift, **m1** | -| …except a sub-harness run | `cancel.go:269-301` mints `harness:`; `work_tree.go:86` lists tasks, runs and jobs only | cancellable, but not a row in the tree | minor — **m4** | -| deopt subharness → linear | trigger `subharness_run.go`'s `exec.Deopt` call; `internal/exec/deopt.go` `Deopt` → `ExecutorRunner.Run` `internal/exec/runner.go` | six producers feed one gate; a person's ✕ correctly is not one. The person is told in clean words — `needed a closer look — handled it the long way` (`deopt.go`) — **and the long way is only taken where the program's own ceiling already reaches a shell** (`DeoptHeld`); otherwise the run stops incomplete with `DeoptHeldWord` and the generalist is never reached | as-intended (C2 fixed w42, 2026-08-24); **M10** (pre-deopt spend lost) and **m6** (invisible while it runs) still open | -| a run's node fails | `internal/orchestrate/run.go:496` `land`, `:461` `metLocked` | **abandoned** — no re-plan, no rewire, no cascade; downstream nodes sit `Queued` and `synthesize` writes up Done and Failed only (`:524-536`). Its spend still folds (`orchestrate.go:951`; `run.go:500`) | drifted — **M11** | -| a run's fuel gate | `internal/orchestrate/fuel.go:36`, `:133-136`, `:142-145`; `run.go:413-417` | 80% says so once; 100% finishes what is in flight, starts nothing new, and asks topup/finish/stop | as-intended — matches `system.md:178-179` and the description at `tools_harness.go:71` | -| steering into a running task | `task_room.go:87-109`; `tools_tasks.go:430-438` | a person or the model can say a line into any node, parts included; the brief and acceptance stay frozen — the only writable field is `spec.model`, through `RetargetTask` (`:143-182`) | as-intended | -| steering a **parked** parent | `task_room.go`'s `SteerTask`; `enqueueSteeredLine` `agent.go`; park select `task_run.go`'s tail loop | the line RELEASES the wait, the runner re-enters the model with it, and the loop will not park on or close over a queued line (`steeringHeld`). The door answers whether the node was waiting, and both surfaces say so | as-intended (M12 fixed w43, 2026-08-24) | -| the governor's hold | `TaskGraph.machineBusy`, `task_divide.go` | the governor is no longer part of the division's gate: a busy machine holds the *parts* on the frontier (`waiting · machine busy`) and `armPoll` lifts them, exactly as it does for work admitted through `propose_task`. `divisionDone`'s receipt says so | as-intended (C3 fixed w42, 2026-08-24) | - -### Pause and fuel, all nine mechanisms - -Every one is announced and every one resumes, except where noted. - -| mechanism | file:line | what pauses | resumes | -| --- | --- | --- | --- | -| admission governor (load/mem) | `task_pressure.go:132-148`, asked `task_run.go:820` | new starts of slot-taking nodes | automatically, `armPoll` every 5s (`task_run.go:935-953`) | -| lane cap (`task.parallel`) | `task_run.go:467-486`, `:858-859` | new starts past the cap; outranks the machine | automatically on any completion | -| provider pacing | `task_run.go:171-174`, `:1619-1625` | a call inside a running node | automatically | -| park | `task_run.go:2187-2201` | nothing — a lane is handed back | automatically | -| orchestrate fuel gate | `internal/orchestrate/fuel.go:126-155` | new launches; in-flight nodes finish | **only a person** — topup / finish / stop | -| 80% fuel warning | `fuel.go:36`, `:133-136` | nothing | n/a — but it reaches the person as a bare figure (**m7**) | -| session spend rail | `rail.go:40-51` | the next turn, before it records | only a person | -| process-exit pause | `task_store.go:1140-1169` | every running node | automatically, next session | -| consent hold | `consent.go:353-397` | one call, not the turn | the person answers, or an interrupt refuses it | - -`internal/session/guardian.go` holds nothing — it is a low-tier model that can -turn a consent prompt into an allow and can never deny (`:8-21`, `:97-166`). The -governor lives in `task_pressure.go`. - -## E. Prompt truth — `prompts/system.md` against the belt - -The belt is assembled at `internal/session/tools.go:108`. Thirty-seven tool names -can appear on it; every one is either unconditional or gated by an absence-law -predicate, and the belt and the prompt page for division are built from the -**same** predicate (`tools.go:134` and `prompt.go:120` both call `mayDivide`), -which is exactly right. - -**No belt verb steers wide work to a planner.** Since w44 no belt verb REACHES a -planner at all: `run_adaptive` is gone, and the descriptions left point breadth at -`propose_task` — `task.go` ("do not reach for a planner") and -`tools_harness.go`'s `build_harness`. - -**No source sentence and no manual page steers breadth to a planner.** A -repo-wide case-insensitive grep for breadth words near planner words returns no -non-test hit that reads the old way; `internal/orchestrate/prompt.md` describes -how a run that has already started behaves, which is out of scope. The compiled -corpus carries the capability: `adaptive-runs.md:22-39` is the section people -actually ask for and `:489-497` names wide work as a task outright. (`tasks.md` -called the `adaptive` preset an opt-in override; w44 retired the preset, so that -page and `adaptive-runs.md`'s account of the belt are the manual's to bring -level.) A grep for denials -(`cannot be divided`, `cannot split`, `impossible`) returns zero hits. -`TestTheBeltRoutesWideWorkToOneWorkerAndNotToAPlanner` -(`task_divide_test.go:544`) pins both the new sentences and the **absence** of the -two that produced the old reflex. - -**Two defects CLAUDE.md warns about are fixed.** `read` no longer claims to list -directories — `system.md:49` says "It reads FILES only; a directory is an error, -so list one with `ls`", and the code agrees (`os.ReadFile`, -`internal/exec/bare/tools.go:204`). `note`/`forget` are gone: there is no -`MemoryFile` field on `session.Config` any more, and neither word appears in -`system.md` as a tool name. The memory belt is exactly one verb, `remember`. - -**What is still wrong:** the inventory omits fifteen tools that are genuinely on -the belt (**m8**), and `read`'s description promises media perception with no -guard (**m9**). The unconditional advertisement of `propose_task` and `tasks` was -**fixed in-lane**. - ---- - -# Findings, ranked - -*Findings marked **FIXED** carry the date and the lane. The paragraphs under them -are left as they were written — they are the evidence the fix was made against, -and rewriting them into the past tense would leave nothing to check the fix -against.* - -## CRITICAL - -### C1 — `divide_work` is not on any production belt. The division road is inert. — **FIXED, w42, 2026-08-24** - -> `newTaskAgent` now carries `Divide: parent.Divide` onto every worker it -> builds, and `armDivision` refuses any spec whose `kind()` is not the ordinary -> one (which is M4, landed in the same change). Pinned through the real -> constructor by `TestTheProductionConstructorCarriesTheRoadOntoTheWorker`, -> `TestAHarnessDesignIsNeverArmedToDivide` and -> `TestTheRoadOffProducesWorkersWithoutTheVerb` — none of which writes a Config -> literal. - -`Config.Divide` has exactly one production setter — `cmd/codeaf/chatv3.go:640`, -`Divide: settings.Swarm` — and it sets it on the **conversation**. But -`Config.mayDivide()` (`task_divide.go:138-144`) also requires `mayFanOut()` = -`InTask && tasker != nil && taskDepth < limit` (`task.go:301`), which a -conversation never satisfies. Every task worker does satisfy it, and every task -worker is built by `Agent.newTaskAgent` (`task_run.go:3237`) — the sole -production constructor, called from `task_run.go:2263`, `task_audit.go:793` and -`harness_task.go:513` — whose `Config` literal at `task_run.go:3304-3383` copies -about thirty fields from the parent and **does not copy `Divide`**. - -So `mayDivide()` is false for every agent in the running program: `divideTools()` -returns nil (`task_divide.go:114-117`) and `renderSystemAt` omits -`prompts/divide.md` (`prompt.go:120`). Meanwhile `armDivision` runs correctly at -admit via `g.home` (`task_run.go:686`), `node.dividing()` returns true, and the -capability is promised in five places: the roster line -(`internal/tui3/taskcommand.go:162`), the schema (`task.go:119`), the prompt -(`system.md:122-130`), and three manual pages (`adaptive-runs.md:22-30`, -`:493-497`; `tasks.md:1584-1620`). - -Measured, not inferred. A probe built the worker through the production -constructor with the session's road on and the node armed: - -``` -child Config.Divide = false -child mayFanOut = true -child node.dividing = true -child mayDivide = false -divide_work on the belt = false -divide page in prompt = false -``` - -**Why no test caught it:** every division test hand-builds the worker's config -with a literal `Divide: true` — `task_divide_test.go:56-64`, `:470`, `:509` — -including the one written expressly to prevent this failure, whose comment reads -"a decision the worker's hands never hear about is the road open on paper only" -(`:504-506`). All eighteen pass. None calls `newTaskAgent`. - -**The fix is two parts and must land together.** (a) `Divide: parent.Divide,` in -the `newTaskAgent` literal, beside `tasker`/`taskID`/`taskDepth` -(`task_run.go:3380-3382`). (b) A guard so it does not newly reach the wrong nodes -— see **M4**: either clear `spec.divide` for `spec.design != nil` / `spec.run != -nil` inside `admit` (`task_run.go:686`), or gate `mayDivide` on the node's kind. -Plus a test that goes through `newTaskAgent` rather than around it. Not applied -here: turning a whole road on in production is a lane, not an audit. - -### C2 — a deopted subharness escapes the tool ceiling and the consent gate it was approved under. — **FIXED, w42, 2026-08-24** - -> **The linear generalist genuinely cannot be caged, so the honest half of the -> fix shape was taken.** `Toolbox`'s five tools are unconditional and sit in a -> fixed order by a stated cache law (`Definitions`), and `Arm` only ADDS optional -> families — there is no subtractive seam and building one would rewrite the -> block the law is about. So the decision moved to `exec.Deopt`, which now takes -> the PROGRAM'S own manifest: `DeoptHeld` says the long way may only be taken -> where the approved ceiling already reaches a shell (`bash` or `sh` — the shell -> subsumes write, edit and web, so a ceiling that reaches it reaches everything -> the fallback uses). Otherwise the run stops **incomplete** with -> `DeoptHeldWord`, in the person's own register, and the generalist is never -> reached. Both surfaces ask `DeoptLineFor` before they announce anything, so -> neither says "handled it the long way" over a run that is about to stop, and -> `Deopt` refuses again inside itself so neither can skip it. `ExecutorRunner.Run` -> now names its Env parameter `_` and says in prose why an executor has nothing -> to do with one. Pinned by `internal/exec`'s -> `TestACeilingThatDoesNotReachAShellHoldsTheLongWay`, -> `TestAHeldFallbackNeverReachesTheGeneralist`, -> `TestACeilingThatReachesAShellStillFallsBack`, and by -> `TestALongWayThatWouldReachPastTheCeilingIsNotTaken` on both surfaces. - -`ExecutorRunner.Run(ctx, input, env Env)` (`internal/exec/runner.go:422-457`) -**takes the `Env` and never reads it** — the parameter is unused in the whole -body — and the generalist it hands the work to is belted with unconditional -`sh`/`job`/`write`/`edit`/`web` (`internal/exec/tools.go:762-798`). -`grep -rn "Approv" internal/exec/` returns nothing: there is no approval policy in -that package at all. - -During the program, every call is checked against `Manifest.Whitelist` — a -ceiling, where empty means *no tools* (`subharness_env.go:391-404`) — and routed -through the approval gate (`:349-353`). After the deopt, neither exists. A -subharness a person approved with `Whitelist: []` becomes an unrestricted shell -agent in their workspace, silently, with no second question and nothing on any -surface saying the ceiling came off. `subharness_env.go:140-141` asserts the -opposite in a comment. - -Fix shape: `ExecutorRunner.Run` must build its executor's belt from -`env`'s filtered toolbelt and its approval seam, or the deopt must re-ask. Either -way the ceiling a person approved has to survive the fallback. - -### C3 — the division refusal names the wrong cause, and the machine hold is terminal where the same hold is temporary for `propose_task`. — **FIXED, w42, 2026-08-24** - -> **The asymmetry was the finding, so the asymmetry is what went.** `freeHands` -> no longer asks the governor at all: it is the person's `task.parallel` cap and -> nothing else. A busy machine no longer refuses a division — the parts are -> admitted, the frontier holds them as `waiting · machine busy`, and `armPoll` -> lifts them by itself, which is byte-for-byte what work admitted through -> `propose_task` has always done with the same reading. `divisionDone`'s receipt -> says the parts are waiting and that there is nothing to come back for. -> `divisionNoHands` became `divisionNoLane`, which names the cap rather than "no -> free hand", drops the untrue worktree-cost claim (a worktree is made at START), -> and tells the worker to ask again ONLY where a lane can actually come free — -> at `task.parallel = 1` it says plainly that asking again will not change it, -> which also closes **m14**. Pinned by -> `TestADivisionNobodyIsFreeToPickUpIsDeferredRatherThanTaken`, -> `TestADivisionHeldByBusyLanesInvitesTheWorkerBack` and -> `TestABusyMachineHoldsTheDivisionsPartsRatherThanRefusingIt` — the last of -> which is the governor × division interaction **m18** listed as uncovered. -> `internal/manual/chat/tasks.md`'s division section carries the new words. - -`freeHands` (`task_divide.go:404-423`) answers 0 when the governor holds -(`:410-413`), and `divideWork` then returns `divisionNoHands` (`:365-369`): -*"there is no free hand to take the N parts right now, and parts that can only -wait would cost a copy of the repository each … ask again later."* Three things -are wrong with that on a loaded machine: - -- **"no free hand" is false.** With `task.parallel` unset, `limit` is 0 and - `freeHands` would answer `taskFanLimit` (`:417-418`) — every lane is empty. The - cause is machine load, and the engine owns the honest word three lines away: - `waitingMachineBusy = "machine busy"` (`task_run.go:170`). -- **"would cost a copy of the repository each" is factually wrong for this - cause.** A worktree is created in `workTaskNode` at START - (`task_run.go:2249-2252`), never at admit, so a part held on machine load - costs nothing. -- **"ask again later" is the worker's only path**, and it is strictly worse than - what the frontier does with the identical condition: work admitted through - `propose_task` queues, polls every five seconds (`task_run.go:935-953`), - announces `waiting · machine busy`, and lifts itself. A division refuses - silently and terminally, emits no event, and `internal/tui3` has no rendering - for a division refusal at all — so no person is ever told. - -The asymmetry is the finding: the same machine hold makes the *default road for -wide work* fail closed while the exception queues. `freeHands`'s claimed parity -with `runFrontier` (`task_divide.go:389-391`) is not real either — the frontier -asks the cap first and the machine second (`:858-862`, pinned by -`TestTheCapOutranksTheMachineOnTheWire`) while `freeHands` asks the machine -first, and the frontier exempts `!takesSlot()` nodes from both ceilings -(`:851-857`) while `freeHands` has no such branch. No test covers the -interaction: every `freeHands` test builds a graph whose governor is nil, and -every governor test drives `runFrontier` and never `divideWork`. - -## MAJOR - -### M1 — the route judge's card is the one model-decided wide-work door that admits unarmed. — **FIXED, w42, 2026-08-24** - -> The three edits below were applied as written, now that C1 makes them live: -> `routeVerdict.Wide`, the `wide` line in the judge's brief and its yes-example, -> and `wide: verdict.Wide` on the spec. Pinned by -> `TestTheJudgesWideVerdictArmsTheTaskItStarts` (over a goal `splitgate` counts -> as zero, so only the judge's own word can have armed it) and -> `TestARouteYesWithoutWidthArmsNothing`. - -`launchRouteTask` (`route_judge.go:395-413`) builds a `taskSpec` with no `wide`, -no judge-bank entry (its `request` is the live user message, not the judged -text), and therefore only `splitgate.WorthIt` over a goal the judge wrote. - -`splitgate.Items` (`splitgate.go:54`) counts only a digit run whose *immediately -adjacent* field begins with one of eighteen whitelisted nouns (`:35-37`), or a -spelled number six–twenty with such a noun inside forty bytes (`:42-47`). -Measured on real strings: - -| text | items | -| --- | --- | -| `the adapters directory holds 11 files` | 11 ✓ | -| `twelve image files` | 12 ✓ | -| `eight endpoints` | 8 ✓ | -| `research the pricing tiers of every major cloud provider` | 0 ✗ | -| `sweep across many files and update the header in each` | 0 ✗ | -| `audit every trajectory the system can take` | 0 ✗ | -| `bring the eleven adapters up to the new interface` | 0 ✗ | - -The judge is never asked for a count anywhere in `routeJudgeBrief` -(`route_judge.go:109-138`) — it is asked for a self-contained goal. So the card's -task is, in practice, never armed. And the manual already states the capability -as fact: `adaptive-runs.md:288-290` — "`task` is what it offers for wide work … -one worker starts and splits itself if the material is wider than one pair of -hands." - -**The flagged follow-up, answered: yes.** The judge's wide/adaptive-shaped yes -should arm the task it cards. It is the same judgement `taskSpec.wide` exists to -carry (`task.go:235-250`); it is free; a wrong yes costs nothing because the -evidence gate still refuses (`task_divide.go:247`); and giving the judge an -honest place to put "wide" removes its reason to reach for `adaptive` to express -it — which matters, because `canRunShape` (`route_judge.go:217-225`) is a -feasibility check only and nothing in code inspects *why* the judge said -adaptive. Minimal change, three edits, no new machinery: - -1. `route_judge.go:98` — add `Wide bool` with tag `json:"wide"` to `routeVerdict`. -2. `route_judge.go:123`/`:130` — add `"wide": true` to the yes-example and one - line to the shape paragraph defining it. -3. `route_judge.go:409` — `wide: verdict.Wide,` on the `taskSpec`. - -Deliberately not applied: it is dead behind C1 until C1 lands, and the two should -be judged as one change. - -### M2 — a division part's whole spend is lost from the session ledger on any stopped ending. — **FIXED (first half), w42, 2026-08-24** - -> `foldTaskUsage` now charges `Agent.spendLedger(node)` rather than the owner -> directly: an owner that has already closed its own books is skipped and the -> money goes straight to the graph's home, which is the session ledger. Same -> total, one hop shorter, and it covers the whole class — `propose_task` -> children stopped with their parent took the identical road. Pinned by -> `TestAStoppedPartsSpendStillReachesTheSessionLedger`. **The second half is -> still open:** the parent node's recorded cost includes its parts' while each -> part records its own, so a surface summing node rows double-counts. - -`workTaskNode`'s defer closes the parent worker (`task_run.go:2269`) and folds it -(`:2276`); `stopChildren` does not run until `:2126`, *after* `work()` has -returned. So on any stop, threshold or deadline ending, every part stopped there -folds into an already-closed parent-worker agent whose `Usage()` nobody reads -again (`addAuxiliaryUsage` has no closed check, `loop.go:2127-2155`). The money -survives on the part's node row and index entry (`task_index.go:614`) — visible -and uncounted at the same time. This is an engine-wide ordering bug, not a -division-specific one: `propose_task` children have it too. It breaks the -one-ledger law on exactly the path a person takes when they are worried about -spend. - -Second and smaller: the parent node's recorded cost *includes* its parts' while -each part records its own, so any surface summing node rows double-counts — -contrast orchestrate, where the root's figure is the tank and not the sum -(`taskcost_test.go:222-226`). - -### M3 — a restart is lossy in three separate ways. - -**(a) Arming is re-derived, and the comment understates the loss.** -`task_store.go:1037-1040` says a restart loses only the sizing judge's yes. -`spec.wide` is equally unrecoverable — it is not on `taskRecord` and -`restoreNode` never sets it (`:1002-1015`). That matters because -`task.go:243-249` argues `wide` rides on the spec *because* the bank is -unreliable, an argument that assumes a durability the record does not provide. - -**(b) A restored parent can divide a second time.** `mayDivide` consults only -`spec.divide`; nothing records that `divide_work` already fired, and `divideWork` -(`:236-307`) has no idempotence check. - -**(c) Parts that finished before the crash never reach the re-run parent.** -`rehydrate` marks restored non-queued nodes noted itself -(`task_store.go:974-976`, `:983-986`) and routes their reports to the -conversation's ambient lane (`:868`); `reported()` reads `n.noted` -(`task_run.go:2880-2884`), so the parent's tail loop cannot see them and folds an -incomplete set. - -No test covers arming across a restart: `task_store_test.go`, `recovery_test.go` -and `replay_test.go` never mention `divide` or `wide`. - -### M4 — a harness design can be armed for division. — **FIXED, w42, 2026-08-24** - -> Gated by kind at the one arming door, as C1 required: `armDivision` returns -> false for any spec whose `kind()` is not the ordinary one, and `restoreNode` -> asks `record.Kind` — not the rebuilt spec, which for a design whose page never -> finished carries no `design` field at all. It closes **m3** in the same line. -> The related-and-smaller half is still open: a design thread still carries -> `propose_task`. - -`admitHarnessDesign` (`harness_task.go:462-470`) passes the goal as `brief`, and -`admit` asks `armDivision` of it like any other spec. A goal saying "a harness -that checks the 8 endpoints" therefore sets `spec.divide = true` on a *page -writer*. The design thread satisfies `mayFanOut` (`tasker` set at -`task_run.go:3245`, depth 1), so the moment C1 is fixed it would be handed -`divide_work` — a verb that spawns workers in worktrees, for work that is two -model calls producing one JSON page (`harness_build.go:513`) with no worktree of -its own (`harness_task.go:512-513`). C1 and M4 must land together. - -Related and smaller: a design thread already carries `propose_task` -(`task.go:293` + `task_run.go:3245`), so a page writer can admit ordinary tasks, -`wide` ones included. Bounded — it cannot reach a planner — but nothing in -`harness_task.go` argues for it. - -### M5 — a standing firing cannot divide, and cannot fan out either. — **DIVISION FIXED, w43, 2026-08-24.** - -The finding as written: `ActionTask` ran "a fresh headless session in the run -folder, one turn on the brief". `standingRunConfig` set `InTask=true` but left -`tasker` nil, so `mayFanOut()` was false (`task.go:301`) and the session had -neither `propose_task`, nor `tasks`, nor `divide_work`; `run_adaptive` needs -`AskConsent` and gets none. `v3StandingPosture` never set `Divide` either. So the -one place nobody is watching — overnight work, the case the ambient side exists -for — was the one place width was unaddressable. - -**The division half is closed.** `v3StandingPosture` now carries `Divide` off the -same `settings.Swarm` row a conversation reads, and `standingWideWork` -(`standing_run.go`) arms a firing through the SAME lawful signal ordinary work -uses — `armDivision`'s third, factored out as `enumeratesWidth` so there is one -reader of "does this text name enough separate items" and not two. A wide firing -gets a graph of its own carrying the person's `task.parallel` cap and the -admission governor built from `task.max_load` / `task.min_free_mb`, plus the one -node it IS: born RUNNING, holding a lane, never given to the frontier. The parts -are ordinary nodes under it, so C3's law holds unattended too — a busy machine -makes them WAIT and `armPoll` lifts them, it never refuses. `Run` grew the same -tail loop `runTaskChild` has, so the firing does not come back until its parts -have reported, their reports have re-entered the model, and their spend is in the -figure the pass writes down. - -**Enumeration only, recorded as the decision.** A firing gets no sizing read. The -other two signals are people — a typed `/task` somebody is waiting on, and a chat -model's `wide` written with the conversation in front of it — and a firing has -neither: its brief was compiled from one sentence, ratified on a card, and has -been sitting in a file since. A model call per firing per night to re-read -unchanged text is a subscription nobody agreed to, so the free signal that reads -the work's own words is the honest floor. It under-arms rather than over-arms. - -**Still open:** a firing has no `propose_task` and no `tasks`. The `run_adaptive` -half of this finding closed itself in w44 rather than being fixed — the hand is -off every belt, so a firing not having it is no longer a gap peculiar to firings. -Division is the road wide work was meant to take (`task_divide.go`'s header); the -fan-out half is a separate question about what an unattended session may -commission. - -### M6 — three one-source-of-truth breaks between a description and its code. - -**(a) `wide` is silently inert when Swarm is off.** `armDivision` returns false at -`task_divide.go:192` before it ever looks at `spec.wide`, but the schema at -`task.go:119` promises "the worker may hand the parts out under itself" with no -hedge. Either the description hedges, or `CODEAF_SWARM=0` removes the property -the way every other conditional capability on this belt is removed. - -**(b) The manual is wrong on two safety-relevant limits.** — **FIXED, w42, -2026-08-24.** `tasks.md`'s division section now states the capacity test as the -`task.parallel` cap (which is what it is, since C3 took the governor out of it) -and says outright that `task.max_load` / `task.min_free_mb` never refuse a split -— the parts wait and lift themselves. `saved-programs.md` gained *When the long -way is not taken — the ceiling holds*, which is now true of the code (C2). - -**(c) `armDivision` ignores the escape hatch** — see M8. - -### M7 — every kind of standing item rides into every worker's brief as a house rule. — **FIXED, w43, 2026-08-24.** - -The finding as written: `Store.Applicable` filters on `Status` and `AppliesTo` -and never on `WhenKind`, and `renderStandingWorld` wrote whatever came back under -one intro — "They … are not suggestions. Work within them." So "remind me at 6 to -check the deploy" was presented to every task in the project as a rule to work -within, until it retired. Only a `hold` is a rule; the other five kinds are -schedules. - -**The tiering is the contract's own kinds and nothing else.** `renderStandingWorld` -now reads one field against one name — `When.Kind == standing.WhenHold` — and -writes two registers under one heading. A hold keeps the binding sentence -verbatim (`standingWorldHolding`). Every waking kind rides under -`standingWorldWaiting`: *"These are the person's own standing orders over this -place, each waiting on a moment, a rhythm or a change of its own. They are here -so you know what stands; none of them is a condition over this work and none asks -anything of you now."* The wording is the interface register -already speaks — standing orders, conditions, waiting, context — and introduces -no vocabulary of its own. - -`Store.Applicable` is deliberately unchanged: which orders govern a place is its -one question (D5), and a second filter here would be a second answer. What the -seam decides is only how each one is *said*. - -Two consequences worth naming. The holds LEAD the section, because they are the -only half that can change what the worker does. And when the eight-order clip -bites, holds survive it whatever their age — eight reminders crowding out the one -rule in the project was the same failure in a smaller shape. - -### M8 — `CODEAF_SPLITGATE=0` has a reader that ignores it. - -`divideWork` guards the evidence gate with `splitgate.Armed()` -(`task_divide.go:247`), but `armDivision`'s third signal calls -`splitgate.WorthIt` unguarded (`:201`). The stated law is one reader -(`splitgate.go:146-149`, `cmd/codeaf/cooperative.go:169-172`), and -`internal/config/settings.go:782-784` and `tasks.md:1643-1645` both document the -switch as taking the width test away. With the gate off, work armed by neither -the judge nor `wide` still never gets the verb. - -### M9 — the model has no route to stop work it did not start directly. - -`Agent.Cancel` has no caller on the belt; the `tasks` tool's verbs are search / -read one / `say` / `resolve` (`tools_tasks.go:50-58`, `:123-153`) with no cancel -op. There is no `/stop` command, `esc` is explicitly never stop -(`internal/tui3/stop.go:54-58`), and `Interrupt` cannot reach a task because a -node runs on `context.Background()` by construction (`task_run.go:2086-2089`). -The model's only stop verb is `jobs kill`, and a part's job row is registered on -the **parent worker's** registry (`task_run.go:2098`), so the conversation's model -can kill the parent and never one part. For the person the routes are complete — -the stop card reaches `Agent.Cancel` with `task:N` / `run:N` for every roster row, -parts included. Law 4 holds for the person and not for the model; say which one -is meant, then close the other. - -### M10 — a deopted run's pre-deopt spend never reaches the ledger. - -`subharness_run.go:197` overwrites `result` wholesale, so the failed program's own -spend is discarded, and `runSpend` (`:273-278`) returns one of the two halves and -never the total. Contradicts `internal/manual/chat/saved-programs.md:131-134`. - -Related: for most typed subharnesses the long way cannot run at all. -`internal/exec/runner.go:429-431` demands a `brief` field, while the intake card -emits only the manifest's declared fields (`subharness_intake.go:284-297`). Any -manifest without a `brief` deopts into `"linear" needs a brief` and lands -`TaskFailed` (`subharness_run.go:225-227`) — the opposite of the promise at -`internal/exec/deopt.go:10-16`. The passing test hand-writes `{"brief":…}` -(`subharness_doors_test.go:368-370`); the workaround is in the test, not the code. - -### M11 — the orchestrate planner is never taught the failed-need rule. - -A failed node is abandoned: `metLocked` (`internal/orchestrate/run.go:461`) -returns false forever for a dead need, so downstream nodes sit `Queued` in place. -Recovery exists only through the planner as cancel-and-re-add -(`amend.go:355` → `:449`) — and `orchestratePlannerLaw` -(`orchestrate.go:837-886`) contains **no clause about a failed need at all**, even -though `run.go:459-460` explicitly delegates that judgement to it. The one -recovery the design depends on is left for a model to infer. A node stranded that -way also keeps a live `queued` roster row after its run has closed -(`orchestrate.go:1787-1804`) and never reaches the project index (`:1635`). - -### M12 — steering a parked parent has no effect, and can be dropped outright. — **FIXED, w43, 2026-08-24** - -> **The line is now the news.** `SteerTask` marks it as the person's own -> (`userMessage.steered`) and hands it over through `enqueueSteeredLine`, which -> RELEASES the wait the runner is holding — the same release a report makes, -> without being a report. The tail loop reads `steeringHeld()` beside the owed -> count: it will not park on top of a queued line and will not break and close -> the child while one is still there, so the sentence always reaches a request. -> A worker that has already closed refuses out loud — *"task 3 has just -> finished, so there is nobody left to say it to"* — instead of swallowing the -> line. And the door answers WHETHER THE NODE WAS WAITING, so both surfaces say -> which wait the line landed in: the `tasks` tool with *"It was waiting on the -> pieces it handed out; your line wakes it"*, and the room with its own note, -> `it was waiting on its pieces — your line wakes it`. Pinned by -> `TestSteeringAParkedParentWakesItAndArrivesInItsNextTurn`, -> `TestALineSteeredAtAClosedWorkerIsRefusedRatherThanSwallowed` and, on the -> surface, `TestSteeringANodeWaitingOnItsPiecesSaysWhatTheLineJustDid` / -> `TestSteeringAWorkingNodeSaysNothingExtra`. `tasks.md` has a section of its -> own for it. - -`SteerTask` accepts the line — a parked node's state is still `TaskRunning` -(`park` sets only `node.parked`, `task_run.go:2187-2201`) — and the tool answers -*"It arrives in its loop as the person's own words"* (`tools_tasks.go:437`). It -does not: `wakeLocked` declines because `config.InTask && !roomThread` -(`agent.go:1526-1529`), and the tail loop's park select wakes only on a child's -report (`task_run.go:2799-2803`). The correction sits queued for as long as the -slowest part runs, and if the last part reports and the loop breaks at -`:2793-2795` with a line still queued, the child is closed at `:2269` and the line -is dropped without ever reaching the transcript. - -## MINOR - -- **m1 — a parked parent reads two ways.** `WorkingNow` says waiting - (`work_tree.go:161-170`); `TaskNotice.Waiting` has no parked value — `held` is - written only while QUEUED (`task_run.go:1619-1625`) — and the task rail is built - from notices, not from `WorkingNow` (`internal/tui3/task.go:2551-2568`), so it - draws a parked parent as plainly running. Two accountings of one node, which - `work_tree.go:12-17` forbids. `park` never announces. -- **m2 — division parts are marked `divide: true` by accident.** A part inherits - `request` from its parent (`task_divide.go:282`), the judge banked exactly that - sentence, and `judgedDivisible(spec.request)` (`:198`) matches. Inert because - parts sit at depth 2. The invariant should not depend on `taskDepthLimit` - staying at 2. -- **m3 — `admit` arms nodes that have no belt.** — **FIXED, w42, 2026-08-24**, - by M4's kind guard: `armDivision` refuses a spec whose `kind()` is - `subharness` before it reads `manifest.Purpose`, so the flag is never written. -- **m4 — a sub-harness run is cancellable but is not a row in `WorkingNow`.** - `beginHarnessRun` (`cancel.go:269-292`) registers it for `Cancel("harness:")`, - but `work_tree.go:86` is tasks plus runs plus jobs. Mitigated: the run happens - inside one turn, and `EventHarnessRun` draws a dim line - (`internal/tui3/harness.go:148`). -- **m5 — two headless doors are looser than the third.** `codeaf exec` builds its - toolbox with no approval policy at all (`cmd/codeaf/exec.go:91-100`), unlike the - subharness door (`subharness_run.go:164`). And `codeaf do`'s plan gate - (`cmd/codeaf/cooperative.go:158-175`) lets width alone keep a divided plan, at a - door where nobody asked for a planned graph. Both are coherent inside the - resident product; noted as cross-product drift. -- **m6 — the long way is invisible while it runs.** Because the `Env` is discarded - (C2) there is no journal entry, no room event and no phase update - (`subharness_run.go:255`): the person watches an unmonitored agent do shell work - behind a frozen row. -- **m7 — the 80% fuel warning reaches the person as a bare figure** - (`internal/tui3/roomorch.go:662-677`) while the planner gets a sentence - (`orchestrate.go:793-796`); and a paused run tells the surfaces but never the - model (`:395` vs `:463`), so the assistant can report a gated run as still - working. -- **m8 — the inventory omits fifteen tools that are on the belt.** Present on a - plain chat belt and named nowhere in `system.md:20-38`: `read_document`, - `watch`, `track`, `commit`, `recall`, `web_search`, `web_fetch`, - `generate_image`, `speak`, `generate_music`, `generate_video`, `view_image`, - `divide_work`, `revise_design`, and the connect family. Under-promising rather - than over-promising, so no absence law is broken — but the media family is a set - of verbs the model will not plan around. -- **m9 — `read`'s description promises perception with no model behind it.** - `senseRead` (`tools_sense.go:213-215`) appends `senseSentence` (`:68`) — "Never - write a script to decode any of them" — with no guard, while `view_image` beside - it is gated on `visionSeer() != ""` (`tools_view.go:91`). It degrades honestly at - call time (`tools_sense.go:439`), which is why this is minor rather than the - `note`/`forget` shape. -- **m10 — `task.start` is not on the self-service guard list** - (`internal/config/selfservice.go:77-108`). `change_setting` can permanently move - a person's default road to `adaptive` — the one preset that opens a planner and a - fuel tank. Not silent (the call goes to the person like `edit`/`write`), but - under an allow-all policy or `--yolo` a model can flip it. The file's own rule is - "ERR TOWARD REFUSING" (`:27-29`). -- **m11 — the sizing judge's yes is a one-entry bank, and the typed door races - it.** `StartTask` runs `shapeBrief` (up to 25s) before `admit` - (`task_person.go:70`, `:112`); a second `/task` whose judge answers yes inside - that window overwrites `divisibleAsk`, and the first task admits unarmed — after - the surface has already promised it can split. `taskSpec.wide`'s own comment - identifies the identical hazard for concurrent proposals and solves it by - putting the judgement on the spec (`task.go:243-249`). The same change fixes - **M3a**, because the spec is persisted. -- **m12 — the ninth standing order silently stops reaching workers.** - `standingWorldMost = 8` (`standing_world.go:44`), clipped newest-first with a - `…N more` line in the brief and nothing on any surface. -- **m13 — an interactive surface auto-approves its own human gates.** - `cmd/codeaf/chatv3_harness.go:150-154` wires the harness runner with no `Ask` — - `// No Ask: a gate auto-approves here and the trail says so.` — so a saved - harness's `human.gate` is waved through by - `internal/subharness/exec_model.go:272` on the one surface with a person sitting - at it, and `:648-652` propagates the nil `Ask` into nested calls. Honestly - declared as a known absence (`chatv3_harness.go:43-48`), honestly recorded in the - trail, and already named "the anti-pattern" by - the subharness contract. Recorded because law 3 says a yes is never - assumed and the person **is** there. -- **m14 — `"ask again later"` is untrue at `task.parallel = 1`** — **FIXED, w42, - 2026-08-24**: `divisionNoLane` has two sentences, and the one-lane one ends - "asking again will not change this". -- **m15 — a parked parent has no deadline.** `limits.deadline` is checked only on - tool-end events (`task_run.go:2718-2726`) and a parked parent emits none, so a - parent waiting on a part the governor never starts waits until a person stops it. -- **m16 — `run:/` is a mintable id `Cancel` cannot resolve** - (`work_tree.go:229` vs `cancel.go:87`). Documented and unused by tui3; a trap for - the next surface. -- **m17 — machinery vocabulary leaks through the deopt's `because` clause** — Go, - goja and JSON error text is appended verbatim to the notice and the settle card - (`subharness_run.go:194-195`). The constant obeys the vocabulary law; the - sentence it builds does not. Same shape as `"allowed by the guardian"` - (`guardian.go:162`). -- **m18 — test-coverage holes behind the above:** the governor × `freeHands` - interaction, the stopped-parent fold ordering, steering a parked parent, arming - across a restart, a settle with a stranded `Queued` node, a part's own journal, - and the deopt's belt are all uncovered. **Three closed w42, 2026-08-24** — the - governor × division interaction, the stopped-parent fold, and the deopt's - ceiling (see C3, M2, C2). The other four stand. - ---- - -# What `newTaskAgent` still does not carry down - -C1 was one missing field in a literal that copies about thirty. So the whole -literal was diffed against `session.Config` (`session.go:716`). Thirty-eight -fields are not copied. Most are deliberate and the reasons are recorded here so -the next reader does not have to re-derive them; the rest are named as gaps and -were **not** fixed in this lane, because each is its own product decision rather -than a consequence of turning the division road on. - -**Deliberate, and argued somewhere in the code:** - -| field(s) | why a node does not get it | -| --- | --- | -| `Standing`, `standingItems` | orders reach a node through the brief and only the brief (`standing_world.go`); a per-node door would say everything twice | -| `Memory`, `MemoryImport` | a node is handed no store, which is exactly what keeps `remember` and `search_conversations` off its belt (`tools.go`) | -| `Harnesses`, `HarnessStore`, `RunHarness`, `HarnessCards`, `HarnessDesignWindow`, `Subharnesses`, `SubharnessMemory`, `SubharnessLastRun`, `SubharnessRecordRun`, `OrchestrateRunner` | the three big machines are deliberately not handed to a node — `tools.go:100-107` states it: a node can neither commission a procedure nor start a run of its own | -| `TaskParallel`, `TaskMaxLoad`, `TaskMinFreeMB` | graph-level. Read once when the graph is built (`task_run.go:625-626`) and shared through `tasker`, so a node reading its own copy would be a second answer to one question | -| `TaskDeadline`, `TaskRepairRounds`, `TaskAutoApproveSeconds` | read on the agent that OWNS the frontier's bookkeeping, never on the worker | -| `TaskModel`, `TaskModels` | absent means a part inherits its parent worker's LIVE model (`defaultTaskModel`), which is the right answer for a part | -| `System` | replaced wholesale for a node by `renderSystemAt` | -| `writeScope` | only ever set by orchestrate (`orchestrate.go:1063`); no conversation carries one | -| `Errand` | an errand is a shape a conversation has, not a node | -| `Guardian` | a node's policy is allow-all with `AskConsent=false`, so there is no consent prompt for a guardian to turn into an allow | -| `WorktreeRoot` | orchestrate-only (`orchestrate.go:1292`) | - -**Named as gaps, not fixed here:** - -- **`Routing`, `ModelFallbacks`, `NearestModels`** — **STALE, w43, 2026-08-24. - The conclusion was drawn from the Config literal and is wrong about the - running program.** All three are read in exactly one place — `New`, where they - are handed to the provider client (`agent.go:43-60`) — and `newTaskAgent` does - not build a client at all: it hands the node THE PARENT'S OWN, unwrapped - (`client := unwrapCompleter(a.client)`, and `agent.go:230`'s own comment says - so). So a worker asks through the person's routing strategy, falls back down - the person's list and gets the catalog's nearest-model rescue — transitively, - all the way down to a part, whose parent's client is the conversation's. The - three are facts about the connection and there is one connection; copying them - onto a node's Config would be three fields nothing reads. `memory_consolidate.go:242` - and `standing_run.go:755` are not a contrast, they are the same rule seen from - the other side: those two BUILD clients, so they must carry `Routing`. - Measured through the real constructor and pinned by - `TestTheProductionConstructorCarriesTheProviderRepairOntoEveryWorker`, which - also fails if anybody gives a node a connection of its own. -- **`SupportsParameter`** — **FIXED, w43, 2026-08-24**: verified exactly as - written and copied down, so the no-tools rescue works at every depth. - `TestAPartIsBuiltWithTheSameNoToolsRescueItsParentHad` builds the part through - the real constructor and checks that the catalog was asked about the PART'S - model and that the swap is on its row. - - Used *inside* `newTaskAgent` to refuse a model that cannot use tools, and not - copied down. So the rescue works exactly one level deep: a part, or a - `propose_task` child of a node, is built by an owner whose `SupportsParameter` - is nil and gets no check at all. C1 makes this reachable far more often than - it was. -- **`TaskProgressCheck`** — **FIXED, w43, 2026-08-24, with the finding's last - sentence corrected.** The field is the TEST SEAM (`session.go:930-932`); - production leaves it nil and `taskProgress` asks the real read-only checker - either way, so a part's leash was never unchecked — what stopped one level - down was any test's ability to put a deterministic answer behind it. Copied, - because a seam must reach as far as the machinery it stands in for - (`TestAPartsLeashIsCheckedByTheSameHandTheConversationSet`). `TaskDeadline` is - read on the same owner (`taskLimits`) and has the same shape; it is left - alone, so a part's checkpoint is still reachable in a test only through the - step and no-progress thresholds. - - Asked of the node's `owner` (`task_run.go:2645-2654`), and a part's owner is - the parent worker, whose config has none. The no-progress check never runs for - a part. -- **`Place`** — **FIXED, w43, 2026-08-24, and not by copying it.** A node with a - Place would be a second session: `Place` is what turns on the meta file, the - presence rows, the delta reader and the artifacts directory, all of which - belong to the conversation and would be written twice. So the PATHS ask the - family instead — `Agent.familyPlace(node)` answers the graph's `home`'s Place, - and it is what `workTaskNode` (worktree, resume, git-root lock), - `newTaskAgent` (journal) and `task_audit.go`'s accept/re-audit - (`node.workingCopy`) now read. One family, one folder. - `TestAPieceOfATasksWorkKeepsToTheSameSessionFolder` puts a part's worktree - under `/trees/` and its transcript in `/tasks/`, and - fails on the old reading. - - `workTaskNode` reads `a.config.Place` for `prepareTaskTree` and - `lockGitRoot`. A part's owner is the parent worker with an empty `Place`, so a - part's worktree lands under the repository's default `tasks//` - rather than inside the session's own folder (Decision 26), and the git-root - lock is keyed on a different place. Pre-existing on the nesting road — a - `propose_task` child of a node already does this — but division makes it - common. -- **`ArtifactsIndex`** — media a node generates is not recorded in the artifacts - index the conversation's is. -- **`SpendRailUSD`, `ReplyGuardOff`, `ProfileDir`** — three person-set defaults a - node simply does not have. The rail one matters most: `railBlockLocked` cannot - stop a runaway family, only the conversation's next turn. - -# What was fixed in the routes lane (w43, 2026-08-24) - -Two of the constructor's named gaps and M12. Every claim was re-verified in the -tree before it was acted on; one had gone stale and is marked as such above -rather than quietly deleted. - -1. **The constructor's carry set.** `SupportsParameter` and `TaskProgressCheck` - are in the literal; `Place` is answered by `Agent.familyPlace` at the three - readers that need it rather than copied onto the node; the provider-repair - trio is recorded as travelling with the CLIENT, with a comment at - `unwrapCompleter` saying so and a test that fails if a node is ever given a - connection of its own. Everything is pinned through `newTaskAgent` — no - worker Config is written by hand in `task_carry_test.go`. -2. **M12.** A steered line marks itself as the person's, releases the wait a - parked runner is holding, and cannot be parked on or closed over; a worker - that has closed refuses in words; both surfaces say which wait the line - landed in. -3. Manual: `tasks.md`'s new *Steering a task that is waiting on its pieces* - section, the division section's own sentence about it, and two probes. -4. This document's verdict rows and constructor bullets. - -# What was fixed in the roads lane (w42, 2026-08-24) - -The lane's own record. Every claim above was re-verified in the tree before it -was acted on; none had gone stale. - -1. **C1** — `Divide: parent.Divide` in `newTaskAgent`'s literal, with M4's kind - guard in `armDivision` and `restoreNode`. Three tests through the real - constructor. -2. **C2** — `exec.Deopt` takes the program's manifest and refuses to hand a - caged program to an uncageable generalist; `DeoptHeld`, `DeoptHeldWord`, - `DeoptWordFor`/`DeoptLineFor` are the new vocabulary, asked by both surfaces - before either announces anything. -3. **C3** — the governor left the division's gate; `divisionNoLane` replaced - `divisionNoHands`; `divisionDone` says when the parts are waiting on the - machine. -4. **M2** — `Agent.spendLedger` skips a closed hop so a stopped part's spend - still folds. -5. **M1** — the route judge's `wide`, applied as written. -6. Manual: `tasks.md`'s division section and a new `saved-programs.md` section. -7. This document's verdict rows, and the constructor audit above. - -# What was fixed in the audit lane (w41) - -Four comment, prompt and manual corrections. No behaviour changed; every -structural finding above was left as a finding. - -1. `internal/session/prompts/system.md` — `propose_task` and `tasks` now carry the - same "(when your tool list carries it)" hedge every other conditional verb on - the inventory carries. Both are gated by `mayProposeTask` (`task.go:293`) and - are genuinely absent on a floor node and on every repair round. (The class of - defect CLAUDE.md records `note`/`forget` having caused.) -2. `internal/session/task_divide.go` — `armDivision`'s third bullet used "eleven - adapters" as its example of a brief that enumerates enough items. - `splitgate.Items` counts that as **zero**: `adapter` is not one of its eighteen - item-nouns. The example now says "adapter FILES", and a new paragraph states - that the noun is load-bearing and that this signal arms far less work than its - wording suggests — which is the fact M1 turns on. -3. `internal/standing/standing.go` — `ActionTask` claimed a firing runs "with a - worktree, a landing and a cost row, exactly as propose_task's work runs". - `standing_run.go:501` says outright "IT IS A SESSION AND NOT A WORKTREE". The - comment now says what the code does. -4. `internal/manual/chat/adaptive-runs.md` — the route-judge section said a yes - starts a run on the default **$2.00** tank. The engine's own default is - `session.DefaultRunCapUSD` (`orchestrate.go`), which the composer now reads - rather than spelling a second time. Corrected. The figure itself has since - been raised — see [LIMITS.md](LIMITS.md), which is where it is now stated. - -# Verified as-intended, so it is not re-audited - -- `TaskGraph.admit` is genuinely the one arming door: two production - `&TaskNode{}` constructions (`task_run.go:687`, `task_store.go:995`), two - writers of `spec.divide` (`task_run.go:686`, `task_store.go:1041`). -- The planner is unreachable from every autonomous path, from every MODEL path, - and — since the cue-door lane closed `routeOrchestrate` later in w44 — from - every TYPED path as well. There is no door onto it in a conversation at all; - the engine's chat-side seams stand with no caller, marked as such where they - stand (`loop.go`, `orchestrate.go`). -- Headless never auto-approves a tool question: `consent.go:282-284` refuses when - nobody is watching, `:276` refuses inside a node, and the memo is barred from - swallowing the critical floor (`consent.go:246`, `internal/approval/floor.go:27-45`). -- `internal/splitgate` is one implementation asked in three places, with one - reader of its environment switch — modulo M8. -- The parent stays, parks and folds; the cancel cascade is real; `WorkingNow` - never fakes liveness; the fuel gate matches its prompt. diff --git a/docs/changes/unreleased/1830-one-memory-strength.md b/docs/changes/unreleased/1830-one-memory-strength.md index cef52f0283..86d968c001 100644 --- a/docs/changes/unreleased/1830-one-memory-strength.md +++ b/docs/changes/unreleased/1830-one-memory-strength.md @@ -20,6 +20,6 @@ invalidates: - "The model's forget asked for approval. It is seeded allow beside remember, because both say what they did in the conversation and the Memories tab takes it back." --- -The owner asked for the simplest memory that can be trusted: what the model +Memory should be simple and trustworthy: what the model knows about a person is what the Memories tab shows, every change says so in the conversation, and every change can be taken back. diff --git a/docs/changes/unreleased/1884-agents-ci-spark-keys.md b/docs/changes/unreleased/1884-agents-ci-spark-keys.md deleted file mode 100644 index 1c2e243504..0000000000 --- a/docs/changes/unreleased/1884-agents-ci-spark-keys.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -kind: internal -title: the agent guide says CI is real, Spark is private, and no provider key is normal -pr: 1884 -surface: [docs] -invalidates: - - >- - Agents in cloud sessions concluded the repository had no CI because `gh` - failed there. CI is GitHub Actions (`PR gate` on every pull request, - `Full check` nightly); `gh` fails only because the container's GH_TOKEN - does not authenticate, and the GitHub MCP tools read runs and logs. - - >- - "Run it on Spark" read as a step every contributor owed. Spark is the - owner's private machine; CI's `touched packages` and `Full check` do its - work for everyone else, and a cloud session need not repeat `make pr-ready` - before CI runs the same selector. - - >- - The pull-request template asked about `.github/known-red.txt`, deleted in - #1012; the line is gone. ---- diff --git a/docs/changes/unreleased/1884-contributor-validation.md b/docs/changes/unreleased/1884-contributor-validation.md new file mode 100644 index 0000000000..7ee2c8e2aa --- /dev/null +++ b/docs/changes/unreleased/1884-contributor-validation.md @@ -0,0 +1,7 @@ +--- +kind: internal +title: Clarify contributor validation guidance +pr: 1884 +surface: [docs] +invalidates: [] +--- diff --git a/docs/changes/unreleased/1924-tidy-repository-examples.md b/docs/changes/unreleased/1924-tidy-repository-examples.md new file mode 100644 index 0000000000..d19380bdec --- /dev/null +++ b/docs/changes/unreleased/1924-tidy-repository-examples.md @@ -0,0 +1,7 @@ +--- +kind: internal +title: Tidy repository examples and documentation +pr: 1924 +surface: [docs, build] +invalidates: [] +--- diff --git a/docs/testing-anywhere.md b/docs/testing-anywhere.md index 862cf7c55e..2b115fb0cd 100644 --- a/docs/testing-anywhere.md +++ b/docs/testing-anywhere.md @@ -273,6 +273,4 @@ Known limits, from the measured runs (the benchmark notes of the program have th - **The pairing rate limit.** The hosted relay allows about 12 pairings per 40 minutes from one network (staging says `too many pairings from this network; wait N min`). Pair once and reuse the homes; do not run `continuity.py pair` in a loop. -- **The conformance suite and load runs against staging** cost real requests; run them with - the owner's agreement, not by habit. - **Only the Mac and Linux pairs were tested.** The fidelity runs were Linux to a Mac on APFS. diff --git a/internal/chatlist/offer_test.go b/internal/chatlist/offer_test.go index 206f38d9fe..c561f5ff0f 100644 --- a/internal/chatlist/offer_test.go +++ b/internal/chatlist/offer_test.go @@ -11,9 +11,9 @@ func TestOfferCopy(t *testing.T) { row Row want Offer }{ - {Row{Status: Off, Device: "studio"}, Offer{ContinueHere, "continue here"}}, - {Row{Status: Running, Device: "studio"}, Offer{ContinueHere, "continue here"}}, - {Row{Status: Branch, Device: "studio", OrphanTurns: 3}, Offer{Merge, "3 turns from studio: merge / discard"}}, + {Row{Status: Off, Device: "host-a"}, Offer{ContinueHere, "continue here"}}, + {Row{Status: Running, Device: "host-a"}, Offer{ContinueHere, "continue here"}}, + {Row{Status: Branch, Device: "host-a", OrphanTurns: 3}, Offer{Merge, "3 turns from host-a: merge / discard"}}, {Row{Status: Idle}, Offer{Kind: Open}}, {Row{Status: Here}, Offer{Kind: Open}}, } @@ -26,22 +26,22 @@ func TestOfferCopy(t *testing.T) { func TestStatusLineCopy(t *testing.T) { cases := map[Status]string{ - Running: "running on studio", - Off: "studio offline", - Branch: "2 turns from studio: merge / discard", + Running: "running on host-a", + Off: "host-a offline", + Branch: "2 turns from host-a: merge / discard", Idle: "", Here: "", } for st, want := range cases { - if got := StatusLine(Row{Status: st, Device: "studio", OrphanTurns: 2}); got != want { + if got := StatusLine(Row{Status: st, Device: "host-a", OrphanTurns: 2}); got != want { t.Errorf("%s: got %q, want %q", st, got, want) } } } func TestTakeoverLineOmitsClauseAtZero(t *testing.T) { - r := Row{Device: "studio", DurableAgo: 42*time.Second + 900*time.Millisecond, Pending: 3} - if got, want := TakeoverLine(r), "last saved turn 42s ago; up to 3 turns may still be on studio"; got != want { + r := Row{Device: "host-a", DurableAgo: 42*time.Second + 900*time.Millisecond, Pending: 3} + if got, want := TakeoverLine(r), "last saved turn 42s ago; up to 3 turns may still be on host-a"; got != want { t.Fatalf("got %q", got) } r.Pending = 0 @@ -53,8 +53,8 @@ func TestTakeoverLineOmitsClauseAtZero(t *testing.T) { // A chat that runs now says where before how fresh the copy is, because // continuing it here stops it there. func TestTakeoverLineNamesTheDeviceARunningChatIsOn(t *testing.T) { - r := Row{Status: Running, Device: "studio", DurableAgo: 12 * time.Second} - if got, want := TakeoverLine(r), "running on studio; last saved turn 12s ago"; got != want { + r := Row{Status: Running, Device: "host-a", DurableAgo: 12 * time.Second} + if got, want := TakeoverLine(r), "running on host-a; last saved turn 12s ago"; got != want { t.Fatalf("got %q, want %q", got, want) } } @@ -76,10 +76,10 @@ func TestCopiesCameSaysNothingForNone(t *testing.T) { } func TestFrozenSentences(t *testing.T) { - if got := KeptEdits(2, "studio"); got != "your unsaved edits here were kept as 2 turns from studio" { + if got := KeptEdits(2, "host-a"); got != "your unsaved edits here were kept as 2 turns from host-a" { t.Error(got) } - if got := Superseded("studio"); got != "studio continued this chat; this window now only shows it" { + if got := Superseded("host-a"); got != "host-a continued this chat; this window now only shows it" { t.Error(got) } for got, want := range map[string]string{ diff --git a/internal/chatlist/rows_test.go b/internal/chatlist/rows_test.go index 268ae45b35..cea824bad5 100644 --- a/internal/chatlist/rows_test.go +++ b/internal/chatlist/rows_test.go @@ -19,7 +19,7 @@ func listing(cells map[string]directory.Cell) directory.Listing { Now: 1_000_000, Devices: map[string]directory.Device{ self: {Name: "laptop"}, - other: {Name: "studio"}, + other: {Name: "host-a"}, }, Cells: cells, } @@ -132,7 +132,7 @@ func TestAChatHeldByARevokedDeviceReadsAsReleased(t *testing.T) { "live": cell(other, 1_010_000, nil), "dead": cell(other, 900_000, nil), }) - l.Devices[other] = directory.Device{Name: "studio", Revoked: true} + l.Devices[other] = directory.Device{Name: "host-a", Revoked: true} for _, r := range Rows(l, self, plain) { if r.Status != Idle { t.Errorf("%s: status %s, want %s", r.Cell, r.Status, Idle) @@ -144,11 +144,11 @@ func TestAChatHeldByARevokedDeviceReadsAsReleased(t *testing.T) { // macOS ".local" suffix; every label that reads it must show the same name. func TestEveryLabelOfADeviceDropsTheLocalSuffix(t *testing.T) { l := listing(map[string]directory.Cell{"c1": cell(other, 1_010_000, nil)}) - l.Devices[other] = directory.Device{Name: "studio.local"} - if r := Rows(l, self, plain)[0]; r.Device != "studio" { + l.Devices[other] = directory.Device{Name: "host-a.local"} + if r := Rows(l, self, plain)[0]; r.Device != "host-a" { t.Errorf("row shows %q", r.Device) } - if got := DeviceName(l.Devices, other, plain); got != "studio" { + if got := DeviceName(l.Devices, other, plain); got != "host-a" { t.Errorf("DeviceName shows %q", got) } } diff --git a/internal/config/crewhealth_test.go b/internal/config/crewhealth_test.go index fee7c02aff..c2ecb644de 100644 --- a/internal/config/crewhealth_test.go +++ b/internal/config/crewhealth_test.go @@ -72,7 +72,7 @@ func TestRouteHealthTransitions(t *testing.T) { } } -// THE OWNER'S SEQUENCE: a route refused a seat with a 403, and the next task +// THE PROVIDER SEQUENCE: a route refused a seat with a 403, and the next task // was routed onto it again. A forbidden route is quarantined, and the next // task's crew does not sit on it. func TestAForbiddenRouteIsNotRoutedToAgain(t *testing.T) { diff --git a/internal/crewroute/seat_test.go b/internal/crewroute/seat_test.go index f5d88b979b..aa26c9db4a 100644 --- a/internal/crewroute/seat_test.go +++ b/internal/crewroute/seat_test.go @@ -139,7 +139,7 @@ func TestTheLineSaysClassPlannerAndARetry(t *testing.T) { } } -// THE OWNER'S CATALOG: a free stranger with no price and one published index +// A SAMPLE CATALOG: a free stranger with no price and one published index // beside the priced models. Every class keeps it out. func TestTheOwnersCatalogNeverSeatsTheFreeStranger(t *testing.T) { stranger := Candidate{ diff --git a/internal/devname/devname_test.go b/internal/devname/devname_test.go index 87552fa86e..7aa38af920 100644 --- a/internal/devname/devname_test.go +++ b/internal/devname/devname_test.go @@ -43,24 +43,24 @@ func TestNameIsTheHostNameUntilAPersonChoosesOne(t *testing.T) { if got := Name(dir); got != Default() { t.Fatalf("unnamed computer is %q; want the host name %q", got, Default()) } - if _, err := Set(dir, " studio "); err != nil { + if _, err := Set(dir, " host-a "); err != nil { t.Fatal(err) } - if got := Name(dir); got != "studio" { - t.Fatalf("named computer is %q; want studio", got) + if got := Name(dir); got != "host-a" { + t.Fatalf("named computer is %q; want host-a", got) } } func TestARefusedNameChangesNothing(t *testing.T) { dir := t.TempDir() - if _, err := Set(dir, "studio"); err != nil { + if _, err := Set(dir, "host-a"); err != nil { t.Fatal(err) } if _, err := Set(dir, "bad\x00"); err == nil { t.Fatal("a name with a control character was kept") } - if got := Name(dir); got != "studio" { - t.Fatalf("name after a refused rename is %q; want studio", got) + if got := Name(dir); got != "host-a" { + t.Fatalf("name after a refused rename is %q; want host-a", got) } } @@ -88,10 +88,10 @@ func TestShownCutsTheLocalSuffixAMacHostNameCarries(t *testing.T) { func TestNameIsShownThroughTheSameRuleForAChosenName(t *testing.T) { dir := t.TempDir() - if err := os.WriteFile(filepath.Join(dir, fileName), []byte("studio.local\n"), 0o600); err != nil { + if err := os.WriteFile(filepath.Join(dir, fileName), []byte("host-a.local\n"), 0o600); err != nil { t.Fatal(err) } - if got := Name(dir); got != "studio" { + if got := Name(dir); got != "host-a" { t.Fatalf("Name = %q; a chosen name reads like every other label", got) } } diff --git a/internal/e2e/livekey_test.go b/internal/e2e/livekey_test.go index dae8ed2520..f95d6cf601 100644 --- a/internal/e2e/livekey_test.go +++ b/internal/e2e/livekey_test.go @@ -59,7 +59,7 @@ func liveProfileDir() string { // somewhere neither the product nor this suite reads. const noLiveKeyReason = "no provider key on this machine by any road the product reads " + "(OPENROUTER_API_KEY, OPENAI_API_KEY, or the profile's api_key row): " + - "this lane drives a real model or it says nothing" + "this test drives a real model or it says nothing" // liveKey is the key this test talks with, or a skip when the machine has none. // diff --git a/internal/e2e/refusedaccept_e2e_test.go b/internal/e2e/refusedaccept_e2e_test.go index 93cf50d9c6..528d97e352 100644 --- a/internal/e2e/refusedaccept_e2e_test.go +++ b/internal/e2e/refusedaccept_e2e_test.go @@ -4,7 +4,7 @@ package e2e // refusedaccept_e2e_test.go IS THE ACCEPTANCE OF #767 ON A REAL SCREEN. // -// 2026-09-09, on the owner's own machine: a divided task landed as their call, +// 2026-09-09, in a regression scenario: a divided task landed as their call, // the model spent `accept` on it under `task.settle = auto`, and the merge was // refused by the person's own uncommitted copies of the very files the task had // written. The node re-settled asking a DIFFERENT question — now theirs — and diff --git a/internal/e2e/remote_test.go b/internal/e2e/remote_test.go index 4f195ae911..be298d0552 100644 --- a/internal/e2e/remote_test.go +++ b/internal/e2e/remote_test.go @@ -569,7 +569,7 @@ func newRemoteWorld(t *testing.T) *remoteWorld { func requireDocker(t *testing.T) { t.Helper() if _, err := exec.LookPath("docker"); err != nil { - t.Skip("no docker on this machine: this lane needs two containers with two filesystems") + t.Skip("no docker on this machine: this test needs two containers with two filesystems") } ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() diff --git a/internal/e2e/tuiwords_test.go b/internal/e2e/tuiwords_test.go index 1399b90878..afcb8d8357 100644 --- a/internal/e2e/tuiwords_test.go +++ b/internal/e2e/tuiwords_test.go @@ -1191,7 +1191,7 @@ var tuiWords = map[string]tuiWord{ why: "the keys the panel writes into its bottom edge, which is how a screen says a question " + "is up and waiting on a person. It was `allow? [1] allow once` — the approval gate's own " + "offer row — until the questions wave gave every question one frame, one key table and one " + - "spelling for a key (owner ruling 2026-09-11, hints pick A)", + "spelling for a key", }, "consentOldCancelWord": { key: "esc", diff --git a/internal/enginehost/news_test.go b/internal/enginehost/news_test.go index 87bff8bb2d..c349d86552 100644 --- a/internal/enginehost/news_test.go +++ b/internal/enginehost/news_test.go @@ -156,7 +156,7 @@ func TestOneHostSendsEachConversationsNewsDownItsOwnConnection(t *testing.T) { host := newsHost(t, workspace) here := openRawSurface(t, host, "one.jsonl", "macbook") - there := openRawSurface(t, host, "two.jsonl", "studio") + there := openRawSurface(t, host, "two.jsonl", "host-a") waitUntil(t, "both conversations opened", func() bool { return len(host.held()) == 2 }) waitUntil(t, "both windows have a live row", func() bool { for _, sess := range host.held() { @@ -218,7 +218,7 @@ func TestOneHostSendsEachConversationsSightingDownItsOwnConnection(t *testing.T) host := newsHost(t, workspace) here := openRawSurface(t, host, "one.jsonl", "macbook") - there := openRawSurface(t, host, "two.jsonl", "studio") + there := openRawSurface(t, host, "two.jsonl", "host-a") waitUntil(t, "both conversations opened", func() bool { return len(host.held()) == 2 }) said := make(chan struct{}) diff --git a/internal/exec/manifest.go b/internal/exec/manifest.go index f1d345ae94..ff90fbb72e 100644 --- a/internal/exec/manifest.go +++ b/internal/exec/manifest.go @@ -113,7 +113,7 @@ type Layer int const ( // LayerBuiltIn is the compiled-in Go runners. They are layer zero — before // every store — and that is deliberate: a bundle on disk MAY NOT SHADOW A - // NAME THIS BINARY SHIPS. The names the owner ships are the names the manual + // NAME THIS BINARY SHIPS. The names the application ships are the names the manual // and the prompts describe, and a store that could silently replace one // would make both of those documents a lie about the program that actually // ran. diff --git a/internal/exec/runner.go b/internal/exec/runner.go index e3ba01f116..ea283cc7f3 100644 --- a/internal/exec/runner.go +++ b/internal/exec/runner.go @@ -21,7 +21,7 @@ import ( // Two implementations are real and the contract permits no more than it has to: // // - A GO RUNNER implements this natively and is registered at compile time. -// The owner's custom Go subharnesses land here first-class with zero porting, +// Custom Go subharnesses land here first-class with zero porting, // and the worker itself is re-fronted through it ([ExecutorRunner]). // - THE JS RUNNER IS ONE GENERIC GO RUNNER parameterized by a bundle. There is // not one runner per bundle: there is one goja host, and a bundle is its @@ -358,7 +358,7 @@ func LeafManifest(info SubharnessInfo) Manifest { // // IT IS GENERIC AND MUST STAY SO. Nothing in it may ask which worker it is // wrapping — that is the same law that keeps the leaf table a table, and it is -// what makes the owner's next Go subharness a registration instead of an edit to +// what makes a new Go subharness a registration instead of an edit to // this file. The executor's own manifest carries the purpose, the ruler and the // budget shape; this type carries the translation and nothing else. // diff --git a/internal/head/blankgoal_test.go b/internal/head/blankgoal_test.go index c73c4a0fe2..cf943e57de 100644 --- a/internal/head/blankgoal_test.go +++ b/internal/head/blankgoal_test.go @@ -48,7 +48,7 @@ func TestAWrittenGoalCarriesNoNote(t *testing.T) { // field after the cut. The continuation goes out without a shape hint now, and // even when a model restarts anyway the run keeps the person's words. func TestALongBriefCutInsideTheGoalStillCompiles(t *testing.T) { - brief, err := os.ReadFile("testdata/brief-335.txt") + brief, err := os.ReadFile("testdata/library-brief.txt") if err != nil { t.Fatal(err) } @@ -57,8 +57,8 @@ func TestALongBriefCutInsideTheGoalStillCompiles(t *testing.T) { t.Fatalf("the fixture has %d words, want the several-hundred-word brief", words) } client := &formatClient{replies: []string{ - `{"structure":"stratifies","goal":"Land lanes L2, L3 and L4 of the lens issue so that the repository builds and every named test`, - `{"title":"Lens lanes L2 to L4","scale":"project","contract":"","parts":[],"builds_on":[],"assumptions":["The branch is the one already carrying L1."],"question":"","question_options":[],"trial_of":0}`, + `{"structure":"stratifies","goal":"Build the library catalog application so that the repository builds and every named test`, + `{"title":"Library catalog application","scale":"project","contract":"","parts":[],"builds_on":[],"assumptions":["The existing catalog format remains compatible."],"question":"","question_options":[],"trial_of":0}`, }} compiled, err := NewCompiler(client).WithOneShotErrands().Compile(context.Background(), instruction, "") if err != nil { @@ -67,7 +67,7 @@ func TestALongBriefCutInsideTheGoalStillCompiles(t *testing.T) { if !strings.HasSuffix(compiled.Goal, "Verbatim request:\n"+instruction) { t.Fatalf("the goal lost the person's words:\n%s", compiled.Goal) } - if compiled.Note != NoGlossNote || compiled.Title != "Lens lanes L2 to L4" || compiled.Scale != ScaleProject { + if compiled.Note != NoGlossNote || compiled.Title != "Library catalog application" || compiled.Scale != ScaleProject { t.Fatalf("brief = %+v, want the restarted fields kept and the note set", compiled) } // The attempt asks for JSON on the wire; the continuation of a fragment diff --git a/internal/head/head.go b/internal/head/head.go index 42e2d55801..b9387a4d71 100644 --- a/internal/head/head.go +++ b/internal/head/head.go @@ -138,7 +138,7 @@ type Head struct { // wrote is every artifact this head has written, so it can open one again to // repair it. It shares turnMu because it is small, rarely touched, and the // alternative is a second lock guarding one map. write.go records the - // shortfall: the durable form is a message part, which is Wave 2's. + // shortfall: durable tracking needs a message part. wrote map[string]bool // namesRooms is the room-naming clerk, off unless a window asks for it. See // WithRoomNaming. diff --git a/internal/head/revisionvoice.go b/internal/head/revisionvoice.go index bc22d9c04e..5200885059 100644 --- a/internal/head/revisionvoice.go +++ b/internal/head/revisionvoice.go @@ -80,8 +80,7 @@ func (h *Head) speakRevision(ctx context.Context, user store.Message, kind store // No end mark: this route's own call is not read for one yet, and the reply // posted here may be the floor sentence rather than the model's words — // marking a sentence the head wrote itself as truncated would be a lie in - // the other direction. Wave 3 gives this seam the same capture the routing - // call has. + // the other direction. This route needs the same end-mark capture as routing calls. return h.postAgentFloor(user.SessionID, reply, commandSeq, decision.model, nil) } diff --git a/internal/head/testdata/brief-335.txt b/internal/head/testdata/brief-335.txt deleted file mode 100644 index a1fd6df0c6..0000000000 --- a/internal/head/testdata/brief-335.txt +++ /dev/null @@ -1,9 +0,0 @@ -Finish GitHub issue #252 of this repository, "The lens: one transcript grammar for chat and the task room". Read the issue (gh issue view 252) and the implementation first; the four owner rulings in that doc are settled law, not up for redesign. Lane L1 (feed extraction) is already on this branch. Deliver lanes L2, L3 and L4 exactly as the issue lists them: - -L2: internal/tui3/room.go adopts the feed reducer in internal/tui3/feed.go. Delete the eleven mirrored ingestion functions (roomFormTool, roomAnnounceTool, roomBeginTool, roomClaimAnnounced, roomClaimRunning, roomCloseTool, roomSettleCompaction, roomSay, roomThink, roomSettleThought, roomCollapseThought, roomCloseLive, roomResolveUnfinished). The room gains every event it never learned (EventToolFinished durations, EventRetrying, EventNotice, EventNudge) and args-match-then-oldest tool-end pairing on both surfaces (ruling 3). Add the salience contract test: a table test walking every event kind and journal role through both lenses asserting the entry exists in both decks ("a lens may lower salience, it may never drop a fact"). - -L3: the node journal gains a steer mark where enqueueSteeredLine lands steered lines (internal/session/agent.go), shaped like journalSteer minus the fields that do not apply; room journal shaping moves into internal/session beside sessionfile.go's rebuild so the view never parses JSONL; delete shapeRoomJournal and the copied journalArgsLimit/journalOutputLimit caps in room.go; room steers render as the entrySteer elbow (dim "└ ", flush left, no turn bump) with a short fading "· delivered" clause, or the existing "it was waiting on its pieces — your line wakes it" sentence when that is truer (rulings 2 and 4). - -L4: a lens struct in one file with two named literals (participantLens, overseerLens) replaces the deck's scattered knobs clock/showsWork/toolTail; the task room folds settled work into phase chips by default with ctrl+e and scroll-up unfolding (ruling 1, reversing the no-fold law in workfold.go deckFolds and its three pinning tests in roomwork_test.go, which are rewritten with the design; strike and box the old law's comment with the ruling quoted); the room header carries the current verb, elapsed, spend, call count and live tool line; room spend feeds the same HUD/spend walk chat uses. - -Requirements: go build ./..., go vet ./internal/tui3/ ./internal/session/, gofmt clean, go test ./internal/tui3/ ./internal/session/ green apart from tests listed in .github/known-red.txt, make check green. Each lane needs a docs/changes/unreleased entry (see existing files there for the frontmatter shape) and updates to the manual pages under internal/manual/chat that currently claim the room never folds or that steering is chat-only; run make embed after editing manual pages. Domain-neutral product language throughout; low cyclomatic complexity; elegant code. Commit the work on this branch with clear messages. diff --git a/internal/head/testdata/library-brief.txt b/internal/head/testdata/library-brief.txt new file mode 100644 index 0000000000..3cfb6ac40d --- /dev/null +++ b/internal/head/testdata/library-brief.txt @@ -0,0 +1,11 @@ +Build a library catalog application for a neighborhood reading group. The application should track books, copies, readers, loans, reservations and return dates. Begin with the existing catalog data format and preserve the stable identifiers in exported records. Make the work easy to review through the normal application entry point. Keep each part understandable without relying on project history or conversations that are unavailable to a new contributor. + +The catalog page should show the title, author, publication year and available copy count for every book. Readers can filter by title or author and combine those filters with an availability switch. An empty result should explain that no books match the current filters. Clearing the filters should restore the complete list without discarding the selected sort order. Keyboard users should reach every control in a predictable order and see which control currently has focus. + +The loan page should record which reader borrowed a copy and when it is due. Prevent two active loans from claiming the same copy. Returning a book should make it available immediately unless a reservation is waiting. A reservation should keep its place in the queue across an application restart. Show the next eligible reservation beside the returned copy so the librarian can complete the handoff without opening several unrelated pages. + +Provide import and export commands for a small documented JSON format. The import command must report an invalid row with its record identifier and a useful explanation. Validate the complete input before writing any changes so a malformed final record cannot leave an incomplete catalog. Exported records should have a stable order and include enough information to reconstruct readers, copies and active loans in an empty installation. Document the expected encoding and date format. + +Use a disposable fixture with several books, multiple copies of one title, an overdue loan and two readers waiting for the same copy. Exercise filtering, checkout, return and reservation handoff through the same functions the application calls. Include a restart between creating a reservation and returning its book. Confirm that the queue order and due date survive. Add an invalid import case and verify that the original catalog remains unchanged after the command reports the error. + +Keep error messages short and describe the action the librarian can take. Explain how to start the application with the sample catalog and how to save an export before upgrading. Use relative example paths under a sample workspace. Run the build, static checks and focused tests for the catalog and command packages. Finish by describing the behavior visible on the catalog and loan pages and the commands used to verify that behavior. diff --git a/internal/head/write.go b/internal/head/write.go index a4aec4e840..b1f055d85e 100644 --- a/internal/head/write.go +++ b/internal/head/write.go @@ -348,7 +348,7 @@ func writeArtifactFile(target, body string, revise bool) (string, bool, error) { // // It is process state rather than journal state, and that is a known shortfall // rather than a design: the durable form is a message part naming the artifact, -// which is Wave 2's structured-parts work, and the ledger says so. What it buys +// which requires structured message parts. What it buys // today is the repair doctrine's second half — a head that wrote a document an // hour ago can read it back to fix it, instead of offering to and discovering it // cannot. diff --git a/internal/lane/account.go b/internal/lane/account.go index 7e1cbea88d..77848c3a8e 100644 --- a/internal/lane/account.go +++ b/internal/lane/account.go @@ -18,7 +18,7 @@ import ( // // ── THE MEASURED FAILURE (2026-09-09 → 2026-09-10) ────────────────────────── // -// The owner's account has the router's "paid model training" privacy switch +// An account can have the router's "paid model training" privacy switch // off, which removes every machine that trains on paid inputs from EVERY // request the account makes, whatever the model. The router says so when a // request's whole set was such a machine: diff --git a/internal/lane/choose.go b/internal/lane/choose.go index 1a4635d2c9..0276bf2fa4 100644 --- a/internal/lane/choose.go +++ b/internal/lane/choose.go @@ -276,7 +276,7 @@ func cachedTokens(id ID, req Request) int { // lowQuantAllowed is whether four-bit lanes may be chosen. It is package state // rather than a field on [Request] because it is a standing answer about this // machine's settings and not a fact about one call, and [Request] is the -// wave-0 contract. +// routing contract. var lowQuantAllowed struct { mu sync.RWMutex allowed bool diff --git a/internal/lane/control/contract.go b/internal/lane/control/contract.go index 4b46769798..8976eba48a 100644 --- a/internal/lane/control/contract.go +++ b/internal/lane/control/contract.go @@ -274,7 +274,7 @@ type Alternative struct { // twenty requests and a tenth of the last hour's bill — and that shape is the // measured defect. A count per window cannot tell the request that needs a // rescue from the nineteen that do not, so it refuses by arrival order: on the -// owner's task that day the hazard said act at its ceiling, the allowance said +// example task the hazard said act at its ceiling, the allowance said // no, and one machine wrote 604 tokens in 86 seconds with a person watching. // What a rescue costs is a fact about THIS call — the prompt at this model's // price plus what the second arm may write, which [Alternative.Extra] already diff --git a/internal/lane/e2e_real_test.go b/internal/lane/e2e_real_test.go index 7a911f6e61..234817bc5d 100644 --- a/internal/lane/e2e_real_test.go +++ b/internal/lane/e2e_real_test.go @@ -265,7 +265,7 @@ func TestRealRouterServesASheetAndHonoursAPreference(t *testing.T) { } if chose == 0 { t.Logf("the chooser had no opinion on any of the five calls " + - "(lane chooser not landed yet, wave 1 L-B) — the preference half of this test " + + "(lane chooser unavailable) — the preference half of this test " + "observed the router's own default instead") } @@ -276,7 +276,7 @@ func TestRealRouterServesASheetAndHonoursAPreference(t *testing.T) { // says — under the temporary home above, which is what makes its presence // evidence about this run. if err := lane.Default().Store().Save(ledger.Beliefs(realModel)); err != nil { - t.Logf("beliefs were not written (%v) — the store is lane L-A's and is not in this tree yet", err) + t.Logf("beliefs were not written (%v) — the belief store is unavailable", err) } else if info, err := os.Stat(lane.StorePath()); err != nil { t.Fatalf("the store reported a write and %s is not there: %v", lane.StorePath(), err) } else { diff --git a/internal/lane/e2e_test.go b/internal/lane/e2e_test.go index 001fae1918..f53ecb0003 100644 --- a/internal/lane/e2e_test.go +++ b/internal/lane/e2e_test.go @@ -302,7 +302,7 @@ func e2eSkipWithoutAChooser(t *testing.T, now time.Time) Choice { t.Helper() choice := Default().Chooser().Choose(e2eTalk(now)) if choice.Empty() { - t.Skip("lane chooser not landed yet (wave 1 L-B): a primed ledger still chooses nothing") + t.Skip("lane chooser unavailable: a primed ledger still chooses nothing") } return choice } diff --git a/internal/lane/hedge.go b/internal/lane/hedge.go index f7832a87f2..ea9d65a111 100644 --- a/internal/lane/hedge.go +++ b/internal/lane/hedge.go @@ -20,7 +20,7 @@ import ( // numbers were literals, and both answered the wrong question. A count per // window cannot tell the one request that needs a rescue from the nineteen that // do not, so it refuses by arrival order — and the measured case is what that -// costs. On the owner's task that day the controller reached its ceiling and +// costs. On the example task the controller reached its ceiling and // asked for a second machine; the allowance said no; one endpoint then wrote 604 // tokens in 86 seconds with a person watching an empty line, and the row carried // `action report reason ceiling refused budget`. The rescue it refused would have diff --git a/internal/lane/lanestub/lanestub.go b/internal/lane/lanestub/lanestub.go index f0bc263c35..e750a03970 100644 --- a/internal/lane/lanestub/lanestub.go +++ b/internal/lane/lanestub/lanestub.go @@ -241,7 +241,7 @@ type Lane struct { // AccountExcluded is a lane the router SERVES this model from and that the // ACCOUNT'S OWN SETTINGS take away — the paid-model-training privacy switch - // on openrouter.ai/settings/privacy, measured against the owner's account on + // on openrouter.ai/settings/privacy, observed with an account on // 2026-09-10 for DeepSeek, Fireworks and Wafer. // // IT IS A FACT ABOUT THE ACCOUNT AND THE MACHINE, NOT ABOUT ONE MODEL. The diff --git a/internal/lane/rolepatience_test.go b/internal/lane/rolepatience_test.go index fb908650d3..24ccea3b61 100644 --- a/internal/lane/rolepatience_test.go +++ b/internal/lane/rolepatience_test.go @@ -293,7 +293,7 @@ func TestNoProbeRidesARoleSomebodyIsWaitingOn(t *testing.T) { } } -// TestTheChoiceNamesNoRoleAndNoMachine is the owner's architecture bar written +// TestTheChoiceNamesNoRoleAndNoMachine is the routing architecture contract written // as a law the build holds, and it is the one this file exists to keep. // // BEHAVIOUR IS DERIVED FROM DECLARED PROPERTIES AND FROM MEASURED BELIEFS. Not diff --git a/internal/lane/sheet_test.go b/internal/lane/sheet_test.go index ad2d66b461..2dced9b3c0 100644 --- a/internal/lane/sheet_test.go +++ b/internal/lane/sheet_test.go @@ -183,7 +183,7 @@ func TestTheSheetDecodesEveryLaneTheRouterPublished(t *testing.T) { if atlas := by["AtlasCloud"]; atlas.Facts.Tools || atlas.Facts.Quant != "fp4" { t.Fatalf("AtlasCloud's facts read %+v", atlas.Facts) } - // THE ROUTER'S OWN HEALTH WORD ARRIVES, which it did not until wave 2b: the + // THE ROUTER'S OWN HEALTH WORD ARRIVES: the // column was published, undecoded, and a lane the router had marked down was // invisible to the gate that should have refused it. if atlas := by["AtlasCloud"]; atlas.Facts.Status != -2 { diff --git a/internal/manual/chat_test.go b/internal/manual/chat_test.go index 108a7ec20e..24b6f6229d 100644 --- a/internal/manual/chat_test.go +++ b/internal/manual/chat_test.go @@ -239,11 +239,11 @@ func TestTheChatManualAnswersTheQuestionsPeopleAsk(t *testing.T) { {"why did it decide by itself", "questions"}, {"undo what I just approved", "questions"}, {"stop asking me about rm", "questions"}, - // And the room form (lane S2): the page a question opens into, and the + // And the room form: the page a question opens into, and the // nine things a person does on it. Each one is asked in the words // somebody would actually type rather than in the key's own name. {"can I open a question up to read it properly", "questions"}, - // And lane R's three, in the asker's own words: the conversation asked for + // Option comparisons, in the asker's own words: the conversation asked for // exactly these sentences (2026-09-11) and each one is a person // describing the drawing rather than naming a key. {"show me the options side by side", "questions"}, @@ -275,8 +275,7 @@ func TestTheChatManualAnswersTheQuestionsPeopleAsk(t *testing.T) { {"why did several questions arrive at once", "questions"}, {"answer a whole batch of questions at once", "questions"}, {"same answer for all of these", "questions"}, - // Several questions from one step are tabs with a review (lane T of the - // question-views wave), asked the ways somebody meets them: moving + // Several questions from one step are tabs with a review, asked the ways somebody meets them: moving // between them, sending them together, and a batch of approvals. {"go back to the previous question", "questions"}, {"answer them all at once", "questions"}, @@ -1472,11 +1471,7 @@ func TestTheChatManualAnswersTheQuestionsPeopleAsk(t *testing.T) { // meant it to. {"select a word with the mouse", "keys"}, {"why did copying take the whole line instead of the words I dragged over", "keys"}, - // Selecting inside the BOX, and taking back what was typed there. The - // owner asked for both in one sentence — "i am unable to select text - // with highlight from input bar in all places … maybe even ctrl+z as - // well like undo or redo like shift z" — and the words people reach for - // are "input bar", "highlight", "undo", and the chord itself. + // Selection and undo in the message box use familiar editing words. {"how do I select text in the message box", "keys"}, {"I cannot highlight text in the input bar", "keys"}, {"can I drag to select what I typed in the input box", "keys"}, @@ -2866,8 +2861,7 @@ func TestTheChatManualAnswersTheQuestionsPeopleAsk(t *testing.T) { {"is a retired tip gone for good", "hints-and-tips"}, {"a tip appeared in my conversation after a while", "hints-and-tips"}, {"why does the hint only show up when I stop typing", "hints-and-tips"}, - // The showing rule, and the day the row went blank: asked by the - // owner, whose afternoon of stepping through home had spent the table. + // Hints count as shown after enough time on screen; exhausted hints leave no row. {"how long does a tip have to be on screen to count", "hints-and-tips"}, {"no hints at all any more, home's row is blank", "hints-and-tips"}, {"can I search my conversations with memory off", "places"}, diff --git a/internal/pair/grant.go b/internal/pair/grant.go index 44a89e3aad..253af80275 100644 --- a/internal/pair/grant.go +++ b/internal/pair/grant.go @@ -5,7 +5,7 @@ package pair // // THE GRANT IS THE WHOLE IDENTITY, and the words say so. A device that is // paired holds the same root as the one that paired it: same chats, same vault, -// same relay account. That is the owner's shape for "your chats on every machine +// same relay account. That provides "your chats on every machine // you own", and it is why revoking a device cuts its relay access and cannot // take back what it already holds. diff --git a/internal/provider/armwatch.go b/internal/provider/armwatch.go index d0a6893148..be7c9274a3 100644 --- a/internal/provider/armwatch.go +++ b/internal/provider/armwatch.go @@ -191,7 +191,7 @@ func streamWatchFrom(ctx context.Context) *streamWatch { // one controller built at the moment the arm started. That is not a watch, it is // a watch of the first attempt with the rest of them inside its shadow. // -// THE MEASURED CASE (2026-09-11 14:32, the owner's task). Attempt 1 drew a +// THE MEASURED CASE (2026-09-11 14:32, an example task). Attempt 1 drew a // relayed refusal; attempt 2 asked GMICloud, was served by another machine, and // ran 363 SECONDS — and its row carried no action, no reason and no silence at // all, while the 86-second attempt before it had at least said `ceiling`. Three diff --git a/internal/provider/hedge_stall_test.go b/internal/provider/hedge_stall_test.go index 94ef495529..466afcfcef 100644 --- a/internal/provider/hedge_stall_test.go +++ b/internal/provider/hedge_stall_test.go @@ -84,8 +84,7 @@ func unattended() context.Context { return WithRole(context.Background(), lanes.RoleLeafUnattended) } -// TestAWireBelowItsPromisedPaceIsRescuedOnAnUnattendedTask is the owner's own -// call, staged. The machine writes — there is no silence and no error — and it +// TestAWireBelowItsPromisedPaceIsRescuedOnAnUnattendedTask stages a slow call. The machine writes — there is no silence and no error — and it // writes an eighth as fast as the machine the question was sent to. The rescue // costs about what the call was going to cost anyway, and the call's own budget // affords it; under the deleted window it did not, and a person watched 604 diff --git a/internal/provider/hedge_test.go b/internal/provider/hedge_test.go index 1710b447eb..4745130193 100644 --- a/internal/provider/hedge_test.go +++ b/internal/provider/hedge_test.go @@ -1387,7 +1387,7 @@ func TestTheRowSaysWhyItWaitedAndWhatWasDone(t *testing.T) { } } -// TestAtTheCeilingAnErrandNobodyIsWatchingIsStillRescued is the owner's order, +// TestAtTheCeilingAnErrandNobodyIsWatchingIsStillRescued is the required behavior, // stated as a scenario: ALL of this build's calls answer promptly, not only the // ones a person is reading. // diff --git a/internal/provider/modelapi/resolve_test.go b/internal/provider/modelapi/resolve_test.go index 12d9ce39a3..bd7bcd5352 100644 --- a/internal/provider/modelapi/resolve_test.go +++ b/internal/provider/modelapi/resolve_test.go @@ -14,7 +14,7 @@ func routerAccount() modelsource.Set { return modelsource.NewSet(modelsource.Connected{Source: source, Key: "sk-or-v1-routerkey0000000000", Address: source.Address}) } -// proxyOnly is the owner's machine: no OpenRouter key, and one service of +// proxyOnly is a machine with no OpenRouter key, and one service of // their own — an OpenAI-compatible proxy on this machine — that carries every // conversation. func proxyOnly() modelsource.Set { diff --git a/internal/provider/namedrefusal_test.go b/internal/provider/namedrefusal_test.go index 5613348c2a..5a36aa6c46 100644 --- a/internal/provider/namedrefusal_test.go +++ b/internal/provider/namedrefusal_test.go @@ -13,8 +13,7 @@ import ( // ── A NAMED REFUSAL IS A MOVE, AND A VETO THAT DID NOT TAKE IS NOT ────────── // -// THE LIVE CHAIN (2026-09-11 14:39:17 – 14:40:47, the owner's own task, tag -// `task` node 2, deepseek/deepseek-v4.1-flash). The router answered eight +// THE REFUSAL CHAIN. The router answered eight // consecutive sends with `(via Wafer: … is temporarily rate-limited upstream)`, // every one of them carrying `retry_after: 2`, while six other machines on the // same model were answering inside five seconds — one of them thirty seconds diff --git a/internal/provider/waiting_law_test.go b/internal/provider/waiting_law_test.go index daecab19df..7356aaeecb 100644 --- a/internal/provider/waiting_law_test.go +++ b/internal/provider/waiting_law_test.go @@ -117,7 +117,7 @@ func TestEveryFunnelCallHasAController(t *testing.T) { sender := waitingFunc(t, client, "completeWithMessagesStreaming") if !waitingNames(sender, "Controller") { t.Error("internal/provider/client.go: completeWithMessagesStreaming never asks for a controller, " + - "so a call the chooser had no opinion about is watched by nothing (lane W3)") + "so a call the chooser had no opinion about is watched by nothing") } fset, hedge := waitingFile(t, "internal/provider/hedge.go") race := waitingFunc(t, hedge, "raceFor") @@ -215,7 +215,7 @@ func TestAHeartbeatIsReportedAsAHeartbeat(t *testing.T) { for _, needed := range []string{"Beat:", "Hidden:", "Visible:"} { if !strings.Contains(text, needed) { t.Errorf("the stream loop never fills %s on a reading — the controller cannot tell a heartbeat, "+ - "a thought and a word apart (lane W3)", strings.TrimSuffix(needed, ":")) + "a thought and a word apart", strings.TrimSuffix(needed, ":")) } } } @@ -276,7 +276,7 @@ func TestTheCallLogSaysWhyItWaitedAndWhatItDid(t *testing.T) { row := reflect.TypeOf(calllog.Record{}) for _, field := range []string{"SilenceMs", "Action", "Arms", "WasteUSD"} { if _, ok := row.FieldByName(field); !ok { - t.Errorf("calllog.Record carries no %s — an autopsy cannot say when the wait was acted on or what it cost (lane W3)", field) + t.Errorf("calllog.Record carries no %s — an autopsy cannot say when the wait was acted on or what it cost", field) } } } @@ -314,7 +314,7 @@ func TestSetModelReachesTheBeat(t *testing.T) { } } t.Error("internal/session/agent.go: SetModel never tells the lane beat about the new model, " + - "so a model picked after launch never gets a sheet (lane W4)") + "so a model picked after launch never gets a sheet") } // TestEveryStreamingRoleIsBoundedAndMediaIsNot is the funnel law, said about diff --git a/internal/remote/held.go b/internal/remote/held.go index 728ac54ca5..61b0cfebc1 100644 --- a/internal/remote/held.go +++ b/internal/remote/held.go @@ -157,7 +157,7 @@ func (h *heldSet) answered(kind string, id uint64, text string) { // window — each a new arrival number, each handed the answered card again by // [heldSet.waitingFor], with [heldSet.outstanding] still counting it so the // conversation read `waiting · your call` and home counted it in `want you` -// (2026-09-11, reported by the owner). +// A displaced window must release its held input. // // So the room is reconciled against the QUESTION'S OWN LIFE instead // ([session.Agent.OpenQuestions] is the whole of it — raised through one door, diff --git a/internal/remote/news_test.go b/internal/remote/news_test.go index 59b5f62390..83aaed521a 100644 --- a/internal/remote/news_test.go +++ b/internal/remote/news_test.go @@ -243,7 +243,7 @@ func TestNewsGoesOnlyToTheConnectionOfTheConversationItNames(t *testing.T) { here := dialSession(t, mine) here.hello(Hello{Version: Version, Surface: "macbook"}) there := dialSession(t, theirs) - there.hello(Hello{Version: Version, Surface: "studio"}) + there.hello(Hello{Version: Version, Surface: "host-a"}) newsWatching(t, mine, 1) newsWatching(t, theirs, 1) @@ -539,7 +539,7 @@ func TestTheWelcomeSaysWhetherThisConversationsNewsWillCross(t *testing.T) { unnamed := newsSession(t, &fakeAgent{}) there := dialSession(t, unnamed) welcome = Welcome{} - if err := json.Unmarshal(there.hello(Hello{Version: Version, Surface: "studio"}).Payload, &welcome); err != nil { + if err := json.Unmarshal(there.hello(Hello{Version: Version, Surface: "host-a"}).Payload, &welcome); err != nil { t.Fatalf("the welcome did not parse: %v", err) } if welcome.News { @@ -567,7 +567,7 @@ func TestAWindowArrivingAfterTheAnswerIsToldWhoAnsweredLast(t *testing.T) { first.await(func(f Frame) bool { return f.Kind == "lane" }) later := dialSession(t, sess) - later.hello(Hello{Version: Version, Surface: "studio"}) + later.hello(Hello{Version: Version, Surface: "host-a"}) frame := later.await(func(f Frame) bool { return f.Kind == "lane" }) var wire LaneWire if err := json.Unmarshal(frame.Payload, &wire); err != nil { diff --git a/internal/resident/askcontext_test.go b/internal/resident/askcontext_test.go index 0d4a2b4923..dd67304052 100644 --- a/internal/resident/askcontext_test.go +++ b/internal/resident/askcontext_test.go @@ -94,7 +94,7 @@ func (f *recordingFiller) fill(_ context.Context, instruction string, workflow * return filled, nil } -// THE INCIDENT (owner's journal, 2026-08-12), reconstructed. +// THE INCIDENT (a sample journal), reconstructed. // // "lets start a research on understanding jepa models help me think through it" // was typed into a room that had spent the morning on multi-level DAGs. The diff --git a/internal/resident/resident.go b/internal/resident/resident.go index 5e13e3714a..cbf9232103 100644 --- a/internal/resident/resident.go +++ b/internal/resident/resident.go @@ -1067,7 +1067,7 @@ func receiptAnchor(command store.Command, status store.CommandStatus) string { // "1": the standing-watch receipt was written, journaled, and rendered as a grey // line nobody reads, so the person who had just answered a question watched // silence and typed the answer again. Where the conversation already carries an -// answer the receipt stays filed, because the wave-4 rule cuts both ways: one +// answer the receipt stays filed, because the implementation rule cuts both ways: one // user action, exactly one visible response. // // A refusal is the one case where the head having spoken is not an answer, it diff --git a/internal/run/testdata/c249/tasks/jcrhzh/trajectory.jsonl b/internal/run/testdata/c249/tasks/jcrhzh/trajectory.jsonl deleted file mode 100644 index f541f29295..0000000000 --- a/internal/run/testdata/c249/tasks/jcrhzh/trajectory.jsonl +++ /dev/null @@ -1,38 +0,0 @@ -{"kind":"step","step":1,"command":"cd /home/dev/src/bl-c249 \u0026\u0026 plandb task note t-jcrhzh 'Plan fixed at first turn: I will own one sequenced internal/tui3 output because tests and implementation touch the same tab/task-plan machinery and test commit must precede implementation. First read only the mandated design/code inputs and verify branch/shared-checkout state; then add deterministic failing tests for item 1 and commit only named test files; then implement items 2-3 using existing chatTab/tabsFit and row renderers and commit only named files; finally run focused acceptance proofs and report commit IDs plus command tails. No internal/session, internal/run, or internal/plandb changes.'","observation":"noted t-jcrhzh (n-00000002)\n","writes":["task note"]} -{"kind":"step","step":2,"command":"cd /home/dev/src/bl-c249 \u0026\u0026 printf '%s\\n' '=== git ===' \u0026\u0026 git status --short --branch \u0026\u0026 git branch --show-current \u0026\u0026 git merge-base HEAD origin/wave2 \u0026\u0026 git rev-parse origin/wave2 \u0026\u0026 printf '%s\\n' '=== mandated inputs ===' \u0026\u0026 for f in the implementation the implementation .brief/DESIGN.md docs/DESIGN-LANGUAGE.md internal/tui3/chattabs.go internal/tui3/taskplan.go internal/tui3/tasksplace.go; do echo \"--- $f\"; sed -n '1,260p' \"$f\"; done","observation":"=== git ===\n## fix/c249...origin/wave2\nfix/c249\n8845570431af6090d88885b6b0cb85443a43449a\n8845570431af6090d88885b6b0cb85443a43449a\n=== mandated inputs ===\n--- the implementation\n# The role of the main chat once tasks are the main road\n\n*2026-09-18, cell c244, design only. The question: once `/task` runs on\nthe run engine, is the main chat a manager of the tasks, or a conversation the\nmanagement stays out of? Three candidates, one recommendation. Reads against\n`the implementation` and `PR-BODY.md` (\"Words on the\nscreen\").*\n\n## What the chat is now, mechanically\n\nA `/task` typed in a conversation becomes the run's root; a second `/task`\nwhile one runs is added as a **child of the live root** — no second store\n(task_run_belt.go:160–200, `startTaskRun`). The run is driven beside the\nconversation in a goroutine (`driveBeltRun`, task_run_belt.go:~288), and when\nit lands the conversation is woken with one note — the outcome word, the root's\nresult, where the work went (`beltRunOutcomeNote`, :365;\n`deliverBeltRunLanding`, :314). That note rides the machinery a landed task's\nnote rides (`postTaskMessage`, task_run.go:4054): priced as a wake, durable to\nthe person's record, and it starts a turn nobody typed (`startTurnLocked(userMessage{wake: true})`).\n\nThe cautionary number is pull request 1183's: before the present bound, one\nlanding woke a 28-minute turn of 49 tool-call rounds on the high tier over a\ntree that was already clean. What bounds that turn today: run workers are\ncapped at `taskMaxSteps` = 200 (task_run.go:161, `RunSpec.StepsPerTask`), a\nwoken turn is metered by the loop's ceilings (`openCallWindow`,\ncallwindow.go:59), and wakecause.go's rule — a turn owes what arrived *in it*,\nnot the newest thing typed (wakecause.go:10–46). The wake is the one cost here\nthat compounds per landing; every candidate says what it does to it.\n\nThe store holds per task: status, parent, dependencies, `Waiting`, `Chat`,\n`Project` (internal/plandb/model.go:122–148), notes, sp","full_output":"/home/dev/src/doe/peer/c249/profile/v3/projects/-home-dev/16fec8d6b8bb313d/tasks/jcrhzh/action-000001.txt"} -{"kind":"step","step":3,"command":"cd /home/dev/src/bl-c249 \u0026\u0026 git grep -nE 'tabList|chatTab|tabsFit|PlanNote|taskPlanNoteSend|landing card|beltRun|PlanTasks' -- internal/tui3 '*_test.go' | head -240 \u0026\u0026 printf '\\n=== app tab state ===\\n' \u0026\u0026 git grep -nE 'activeTab|tab.*Key|tabBar|tabsHold' internal/tui3 | head -200 \u0026\u0026 printf '\\n=== taskplan remainder key/render ===\\n' \u0026\u0026 sed -n '260,760p' internal/tui3/taskplan.go \u0026\u0026 printf '\\n=== changelog invalidations ===\\n' \u0026\u0026 grep -rn 'invalidates' -A6 docs/changes/unreleased/ | head -120","observation":"cmd/codeaf/do_engine_test.go:118:// beltRunEnv puts the door on the run road: the belt's switch, the shim's\ncmd/codeaf/do_engine_test.go:121:func beltRunEnv(t *testing.T) string {\ncmd/codeaf/do_engine_test.go:244:\tbeltRunEnv(t)\ncmd/codeaf/do_engine_test.go:312:\thome := beltRunEnv(t)\ncmd/codeaf/do_engine_test.go:410:\tbeltRunEnv(t)\ncmd/codeaf/do_engine_test.go:468:\tbeltRunEnv(t)\ncmd/codeaf/do_engine_test.go:520:\tbeltRunEnv(t)\ncmd/codeaf/do_engine_test.go:551:\thome := beltRunEnv(t)\ninternal/e2e/checkerwindow_e2e_test.go:121:\tt.Logf(\"the landing card of work whose check ran out of time:\\n%s\", screen)\ninternal/e2e/taskstates_e2e_test.go:68:// that must really run: what a landing card says about work that finished is not\ninternal/e2e/taskstates_e2e_test.go:92:\tt.Logf(\"the landing card of work that finished:\\n%s\", screen)\ninternal/e2e/taskstates_e2e_test.go:665:// tier word IN THE CONVERSATION, which is what a landing card's head is.\ninternal/e2e/taskstates_e2e_test.go:732:// statesRail is the roster COLUMN'S row for one node — not the landing card's\ninternal/e2e/tmux_test.go:722:// emits the second landing card whose status the tmux acceptance reads.\ninternal/e2e/tui_e2e_test.go:2121:// care [statesHeadLine] takes on a landing card, spent on a row of the list\ninternal/e2e/tui_e2e_test.go:2191:\tt.Logf(\"the run's landing card in the thread:\\n%s\", landed)\ninternal/e2e/tuiwords_test.go:693:\t\twhy: \"the one cell that asks the question on the roster row and on the landing card — the shared \" +\ninternal/head/activity_test.go:262:\trun := \u0026beltRun{head: New(\u0026beltClient{}, graph), user: store.Message{SessionID: \"room\"}}\ninternal/head/activity_test.go:282:\trun := \u0026beltRun{head: New(\u0026beltClient{}, graph), user: store.Message{SessionID: \"room\"}}\ninternal/head/answer_test.go:17:\trun := \u0026beltRun{head: New(nil, graph), user: user}\ninternal/head/answer_test.go:59:\trun := \u0026beltRun{head: New(nil, graph), user: user}\ninternal/head/answer_test.go:79:\trun := \u0026beltRun{head: New(nil, graph), user: user}\ninternal/head/answer_test.go:125:\tru","full_output":"/home/dev/src/doe/peer/c249/profile/v3/projects/-home-dev/16fec8d6b8bb313d/tasks/jcrhzh/action-000002.txt"} -{"kind":"step","step":4,"command":"cd /home/dev/src/bl-c249 \u0026\u0026 sed -n '120,430p' internal/tui3/chattabs.go \u0026\u0026 sed -n '900,1140p' internal/tui3/chattabs.go \u0026\u0026 git grep -n 'type PlanTaskRow' internal/session \u0026\u0026 git grep -n 'Live' internal/tui3/taskplan.go internal/session/plandb_tasks.go \u0026\u0026 git grep -n 'func new.*App\\|testApp\\|scriptedAgent' internal/tui3/*_test.go | head -120 \u0026\u0026 sed -n '1,180p' internal/tui3/chattab_parentreview_test.go \u0026\u0026 sed -n '1,150p' internal/tui3/taskplan_test.go 2\u003e/dev/null || true","observation":"\t// It is inert and drawn dim, exactly as it already was on a frame where the\n\t// switcher could not open ([tabFold]) — a labelled control that opened\n\t// nothing would be a door painted on a wall, and so would an unlabelled one.\n\ttabHiddenLead = \"+\"\n)\n\n// chatTab is one conversation as the strip remembers it. Every field is read\n// from this process's own memory — the keeper's map, the surface's own title —\n// and NOTHING here opens a file or crosses a wire: the strip is laid out on\n// every frame, and a reading that touched the disk would be a world scan thirty\n// times a second, or a call to another machine over `--host` (hop.go states the\n// same law about the switcher's rows).\ntype chatTab struct {\n\t// key is the identity, and it is the canonical session file (detach.go's\n\t// [app.convKey]) rather than a title or a number. Two conversations may\n\t// share a name, and an unnamed one has none at all.\n\tkey string\n\t// file is the address a press hands to the door, and where is the workspace\n\t// that door needs when the conversation is not open any more.\n\tfile string\n\twhere string\n\t// word is what the tab says. A conversation this window cannot name is not\n\t// drawn at all rather than drawn as a path — a tab keyed on a file name is a\n\t// tab nobody can read.\n\tword string\n\t// full is the conversation name revealed on hover without widening its tab.\n\tfull string\n\t// here says this is the conversation in front. It is the ONE tab that is\n\t// lit, whatever page of it is on screen.\n\there bool\n\t// held says this process is still holding the agent behind this tab\n\t// (keeper.go). It is not drawn — the strip makes no claim about what is\n\t// running — and it decides only whether a press is an attach or an open.\n\theld bool\n\tstart bool\n\tsignal tabSignal\n}\n\n// tabKind is what one drawn piece of the strip IS, which is what decides whether\n// it is a door and what that door does.\ntype tabKind int\n\nconst (\n\t// tabHere is the conversation in front. Its door is \"come back out of the\n\t// page you walked into\", which "} -{"kind":"step","step":5,"command":"cd /home/dev/src/bl-c249 \u0026\u0026 sed -n '150,520p' internal/tui3/taskplan_test.go \u0026\u0026 sed -n '340,410p' internal/tui3/taskplan.go \u0026\u0026 sed -n '880,990p' internal/tui3/taskplan.go \u0026\u0026 git grep -n 'func (a \\*app) .*Room\\|roomOpen\\|closeRoom\\|showPage' internal/tui3 | head -120 \u0026\u0026 git grep -n 'tabsFit' internal/tui3/chattabs.go \u0026\u0026 sed -n '500,620p' internal/tui3/chattabs.go \u0026\u0026 git status --short","observation":"// planLine is the drawn line a row's title is on, and whether there is one.\n// planLine is the LIST line a title is on — the half of the frame left of the\n// seam, because the record pane beside it previews the cursor row and would\n// answer with the title twice.\nfunc planLine(text, title string) (string, bool) {\n\tfor _, line := range strings.Split(text, \"\\n\") {\n\t\tif at := strings.Index(line, railSeam); at \u003e= 0 {\n\t\t\tline = line[:at]\n\t\t}\n\t\tif strings.Contains(line, title) {\n\t\t\treturn line, true\n\t\t}\n\t}\n\treturn \"\", false\n}\n\n// TWO ROWS, TWO LINES, EACH WITH ITS OWN STATE WORD — and the two figures the\n// store carries drawn only where they are something.\n//\n// A TASK THAT HAS RUN NO STEP AND SPENT NOTHING SAYS SO BY DRAWING NOTHING. The\n// emptiness law is the whole of the `0 steps` half of this: a row that wrote\n// `0 steps · $0.00` would be telling a person a fact as though it were news.\nfunc TestThePaneDrawsThisChatsPlanRowsWithTheirStateWords(t *testing.T) {\n\trows := []session.PlanTaskRow{\n\t\t{ID: \"t-alpha\", Title: \"Alpha\", Status: \"claimed\", Steps: 3, USD: 0.11},\n\t\t{ID: \"t-beta\", Title: \"Beta\", Status: \"done\"},\n\t}\n\ta, _ := planAppWith(t, rows, nil)\n\tif !openTaskPlaceWithRows(a) {\n\t\tt.Fatal(\"the place refused to open over a plan\")\n\t}\n\ttext := taskSheetText(a)\n\n\talpha, ok := planLine(text, \"Alpha\")\n\tif !ok {\n\t\tt.Fatalf(\"the plan's first row was not drawn:\\n%s\", text)\n\t}\n\tif !strings.Contains(alpha, \"running\") {\n\t\tt.Fatalf(\"a claimed plan task reads %q, want it to wear `running`\", alpha)\n\t}\n\tif !strings.Contains(alpha, \"3 steps\") || !strings.Contains(alpha, \"$0.11\") {\n\t\tt.Fatalf(\"a plan row's steps and spend were not drawn: %q\", alpha)\n\t}\n\tbeta, ok := planLine(text, \"Beta\")\n\tif !ok {\n\t\tt.Fatalf(\"the plan's second row was not drawn:\\n%s\", text)\n\t}\n\tif !strings.Contains(beta, \"done\") {\n\t\tt.Fatalf(\"a done plan task reads %q, want it to wear `done`\", beta)\n\t}\n\tif strings.Contains(text, \"$0.00\") || strings.Contains(text, \"0 steps\") {\n\t\tt.Fatalf(\"the pane drew a zero as though it were a figure:\\n%s\", text)\n\t}\n}\n\n//"} -{"kind":"step","step":6,"command":"cd /home/dev/src/bl-c249 \u0026\u0026 git log --all --oneline --decorate -20 \u0026\u0026 git branch -a --contains origin/wave2 | head -40 \u0026\u0026 git grep -n 'pageTasks' internal/tui3/pages.go internal/tui3/app.go internal/tui3/place_tasks.go internal/tui3/taskplan.go | head -160 \u0026\u0026 git grep -n 'taskSheetText\\|taskSheetPlan\\|taskPlanFrame\\|taskPlanKey' internal/tui3 | head -120 \u0026\u0026 sed -n '360,500p' internal/tui3/taskplan.go \u0026\u0026 sed -n '520,690p' internal/tui3/taskplan.go","observation":"884557043 (HEAD -\u003e fix/c249, origin/wave2) docs: a landing speaks only when an answer is owed (the approved chat role)\n1a3687a44 (origin/wave1, origin/HEAD, wave1) Merge branch 'c244-in' into wave1\ne67a2e184 (origin/c244-in) docs: the role of the main chat once tasks are the main road (c244)\n7677b953c docs: the plan as a tree, the rail and the page (design)\n8dd4ef054 (origin/pr1109-head) Merge branch 'wave1-in' into harness/worker-loop\n0f8e82e1e Merge remote-tracking branch 'origin/santos/dev' into harness/worker-loop\na361a827a docs: the solo root's review and declared checks in the entry and the body\n1dc663081 run: a check's landing wakes nobody, so a root that finished alone completes after its check\nb6b65c9a7 Merge branch 'c243-in' into wave1\n199b99e87 (origin/c243-in) test: isolate plan worker environment\nd915ecb55 docs: clarify childless root review\n0aecaf286 fix(run): reopen completed root for review check\n03d4e4958 test(do): keep root review contract comment scoped\naf6ecbd17 test(do): cover self-finished root review\n2ac2360b6 Merge remote-tracking branch 'origin/dev' into santos/dev\n62cae8580 ci: Model Pool mirror workflow (signed index onto the model-pool branch) (#1116)\n631237c36 Merge remote-tracking branch 'origin/dev' into santos/dev\n3da298209 (origin/c242-in) docs: explain declared worker checks\n229614332 fix(session): run declared checks before acceptance probes\n879c2b127 test(session): require declared checks in checker brief\n* fix/c249\n remotes/origin/wave2\ninternal/tui3/app.go:3570:\t\tif a.at(pageTasks) {\ninternal/tui3/app.go:3775:\t\t\tif a.at(pageTasks) {\ninternal/tui3/app.go:4121:\t\tif a.at(pageTasks) {\ninternal/tui3/app.go:4764:\tif a.railStanding() || a.at(pageTasks) {\ninternal/tui3/pages.go:46:\tpageTasks\ninternal/tui3/pages.go:372:var placeOrder = []page{pageHome, pageTasks, pageSpend, pageSettings, pageStanding, pageMemory, pageSearch}\ninternal/tui3/place_tasks.go:235:\t\t\tsession.LastLookAt(a.looksRoot(), pageTasks.word()), now),\ninternal/tui3/place_tasks.go:614:\treturn a.showPage(pageTasks)\nint"} -{"kind":"step","step":7,"command":"cd /home/dev/src/bl-c249 \u0026\u0026 sed -n '1,120p' internal/tui3/pages.go \u0026\u0026 sed -n '2030,2130p' internal/tui3/app.go \u0026\u0026 sed -n '3260,3340p' internal/tui3/app.go \u0026\u0026 sed -n '3520,3805p' internal/tui3/app.go \u0026\u0026 sed -n '580,900p' internal/tui3/place_tasks.go \u0026\u0026 sed -n '1680,1745p' internal/tui3/place_tasks.go \u0026\u0026 sed -n '1860,1960p' internal/tui3/place_tasks.go \u0026\u0026 git grep -n 'railStamp\\|Event.*Task\\|task.*land' internal/tui3/app.go internal/tui3/*.go | head -180","observation":"package tui3\n\nimport (\n\t\"strings\"\n\t\"time\"\n\n\ttea \"charm.land/bubbletea/v2\"\n\t\"github.com/charmbracelet/x/ansi\"\n)\n\n// ── THE PLACES ──────────────────────────────────────────────────────────────\n//\n// A person reads the word PLACE, and so does this file. The word carries two\n// other meanings in this repository, and both of them are one letter away:\n//\n// - [session.PlacesRoot], [app.placesRoot], `placesDirName`, `SweepPlaces`,\n// `homePlacesCol` — the PLURAL, always, and always THE DIRECTORY OF\n// CONVERSATIONS AND PROJECTS ON THIS DISK;\n// - `page`, the identifier for WHICH ROOM, kept because a `page` is what the\n// surface has always called a screen that takes the frame and because\n// `place` on its own is now the interface below.\n//\n// So: `page` is the id, `place` is the contract, `places` is the disk, and the\n// manual and every string a person sees say *place* for the room. This comment\n// is the bridge between the three, and it used to say the Go identifier was\n// `page` everywhere — which was true until ARCHITECTURE.md named the interface\n// and the owner signed it.\n//\n// THE SEVEN ARE A LIST AND NOT A SWITCH. The tab bar's order, the numbers\n// `alt+1`…`alt+7` jump to, and the order `tab` walks are ONE fact, held in\n// [placeOrder], so a place added later is a row in that slice and a file.\n//\n// The rewind timeline is NOT one of them. It is still a page reached by\n// `/rewind`, because it is a thing you do to this conversation rather than a\n// room in the machine, and putting it in the bar would put a knife in the\n// cutlery drawer.\ntype page uint8\n\nconst (\n\t// pageNone is THE CONVERSATION — no place at all. It is the zero value on\n\t// purpose: a window that has just opened is sitting in a chat, and every\n\t// question this file asks of [app.page] then answers \"nothing is up\" without\n\t// a second flag to keep in step.\n\tpageNone page = iota\n\tpageHome\n\tpag","full_output":"/home/dev/src/doe/peer/c249/profile/v3/projects/-home-dev/16fec8d6b8bb313d/tasks/jcrhzh/action-000003.txt"} -{"kind":"step","step":8,"command":"cd /home/dev/src/bl-c249 \u0026\u0026 cat \u003e internal/tui3/worktab_test.go \u003c\u003c'EOF'\npackage tui3\n\nimport (\n \"strings\"\n \"testing\"\n\n tea \"charm.land/bubbletea/v2\"\n \"github.com/Agent-Field/codeaf/internal/session\"\n)\n\nfunc workTabFixture(t *testing.T) (*app, *planFake) {\n t.Helper()\n root := session.PlanTaskRow{ID: \"t-root\", Title: \"Widen the importer\", Status: \"running\", Steps: 3}\n root.Live.Step, root.Live.Command = 4, \"go test ./internal/tui3\"\n child := session.PlanTaskRow{ID: \"t-child\", Title: \"Fix the flake\", Status: \"pending\", Parent: \"t-root\"}\n a, fake := planAppWith(t, []session.PlanTaskRow{root, child}, map[string]session.PlanTaskPage{\n \"t-root\": {Row: root},\n })\n a.width, a.height = 120, 28\n return a, fake\n}\n\nfunc TestWorkTabAppearsAfterConversationOnlyForALiveRun(t *testing.T) {\n a, fake := workTabFixture(t)\n tabs := a.tabList()\n if len(tabs) != 2 || !tabs[0].here || !tabs[1].work || tabs[1].word != \"Widen the importer\" {\n t.Fatalf(\"live run tabs = %+v, want conversation then titled work tab\", tabs)\n }\n fake.plan[0].Status, fake.plan[1].Status = \"done\", \"done\"\n a.workTabStable()\n a.workTabStable()\n if tabs := a.tabList(); len(tabs) != 1 || tabs[0].work {\n t.Fatalf(\"landed stable run kept its tab: %+v\", tabs)\n }\n noRun, _ := planAppWith(t, nil, nil)\n if tabs := noRun.tabList(); len(tabs) != 1 || tabs[0].work {\n t.Fatalf(\"conversation without a run gained a work tab: %+v\", tabs)\n }\n}\n\nfunc TestWorkTabDrawsTheTasksPlacesOwnRows(t *testing.T) {\n a, _ := workTabFixture(t)\n a.openWorkTab()\n text := plain(strings.Join(a.workTabFrame(a.width, a.height), \"\\n\"))\n for _, want := range []string{\"Widen the importer\", \"Fix the flake\", \"$ go test ./internal/tui3\", \"queued · waits: Widen the importer\"} {\n if !strings.Contains(text, want) { t.Fatalf(\"work tab missing %q:\\n%s\", want, text) }\n }\n}\n\nfunc TestWorkTabNoteUsesPlanNoteAndShowsThePageReceipt(t *testing.T) {\n a, fake := workTabFixture(t)\n a.openWorkTab()\n for _, r := range \"keep the middleware order\" { drive(t, a, key(string(r))) }\n drive(t, a, tea.KeyPressMsg{Code: tea.KeyEnter})\n if len(fake.noted) != 1 || fake.noted[0] != (planCall{id: \"t-root\", text: \"keep the middleware order\"}) {\n t.Fatalf(\"work tab note calls = %+v\", fake.noted)\n }\n if text := plain(strings.Join(a.workTabFrame(a.width, a.height), \"\\n\")); !strings.Contains(text, \"you\") || !strings.Contains(text, \"keep the middleware order\") {\n t.Fatalf(\"work tab lacks note receipt:\\n%s\", text)\n }\n}\n\nfunc TestWorkTabEscReturnsToConversationAndLandingCardRemains(t *testing.T) {\n a, fake := workTabFixture(t)\n a.openWorkTab()\n drive(t, a, tea.KeyPressMsg{Code: tea.KeyEscape})\n if a.workTabOn { t.Fatal(\"esc left the work tab open\") }\n drive(t, a, streamEventMsg{gen: a.gen, ev: update(7, \"Widen the importer\", session.TaskDone, session.TaskNotice{Answer: \"the importer landed\"})})\n fake.plan[0].Status, fake.plan[1].Status = \"done\", \"done\"\n a.workTabStable(); a.workTabStable()\n if got := plain(a.view()); !strings.Contains(got, \"Widen the importer\") || !strings.Contains(got, \"the importer landed\") {\n t.Fatalf(\"self-closing work tab removed the landing card:\\n%s\", got)\n }\n}\nEOF\n\ngofmt -w internal/tui3/worktab_test.go \u0026\u0026 set +e; go test ./internal/tui3 -run 'TestWorkTab' -count=1 \u003e /tmp/c249-red.log 2\u003e\u00261; rc=$?; set -e; tail -12 /tmp/c249-red.log; test $rc -ne 0; git add internal/tui3/worktab_test.go \u0026\u0026 git commit -m 'test(tui3): specify the live run work tab' \u0026\u0026 git status --short","observation":"internal/tui3/worktab_test.go:30:4: a.workTabStable undefined (type *app has no field or method workTabStable)\ninternal/tui3/worktab_test.go:31:4: a.workTabStable undefined (type *app has no field or method workTabStable)\ninternal/tui3/worktab_test.go:32:52: tabs[0].work undefined (type chatTab has no field or method work)\ninternal/tui3/worktab_test.go:36:56: tabs[0].work undefined (type chatTab has no field or method work)\ninternal/tui3/worktab_test.go:43:4: a.openWorkTab undefined (type *app has no field or method openWorkTab)\ninternal/tui3/worktab_test.go:44:31: a.workTabFrame undefined (type *app has no field or method workTabFrame)\ninternal/tui3/worktab_test.go:54:4: a.openWorkTab undefined (type *app has no field or method openWorkTab)\ninternal/tui3/worktab_test.go:62:34: a.workTabFrame undefined (type *app has no field or method workTabFrame)\ninternal/tui3/worktab_test.go:69:4: a.openWorkTab undefined (type *app has no field or method openWorkTab)\ninternal/tui3/worktab_test.go:69:4: too many errors\nFAIL\tgithub.com/Agent-Field/codeaf/internal/tui3 [build failed]\nFAIL\n[fix/c249 6abd659c7] test(tui3): specify the live run work tab\n 1 file changed, 81 insertions(+)\n create mode 100644 internal/tui3/worktab_test.go\n"} -{"kind":"step","step":9,"command":"cd /home/dev/src/bl-c249 \u0026\u0026 git grep -n 'tabGo\\|chatTabHits\\|tab.*right\\|ctrl+tab\\|shift+tab' internal/tui3 | head -180 \u0026\u0026 git grep -n 'func (a \\*app) view\\|func (a \\*app) key' internal/tui3/app.go internal/tui3/*.go | head -80 \u0026\u0026 sed -n '5560,5760p' internal/tui3/task.go \u0026\u0026 sed -n '180,280p' internal/tui3/place_tasks.go \u0026\u0026 sed -n '1310,1360p' internal/tui3/place_tasks.go","observation":"internal/tui3/app.go:1728:\t// first entered them, and chatTabHits is where the strip drew each of them\ninternal/tui3/app.go:1733:\tchatTabHits []tabHit\ninternal/tui3/app.go:2251:\t// ctrl+tab switches on each press or opens a card that waits\ninternal/tui3/chatstart_navigation_test.go:17:\tfor _, h := range a.chatTabHits {\ninternal/tui3/chatstart_navigation_test.go:34:\tfor _, h := range a.chatTabHits {\ninternal/tui3/chatstart_navigation_test.go:78:\tdrain(t, a, a.tabGo(target))\ninternal/tui3/chatstart_navigation_test.go:102:\t\t\tfor _, h := range a.chatTabHits {\ninternal/tui3/chatstart_navigation_test.go:104:\t\t\t\t\tt.Fatalf(\"bad targets: %+v\", a.chatTabHits)\ninternal/tui3/chattabclose_test.go:23:\tfor _, hit := range a.chatTabHits {\ninternal/tui3/chattabclose_test.go:28:\tt.Fatalf(\"the strip drew no close target for %q:\\n%q\\n%+v\", word, plain(a.tabsRow(a.width)), a.chatTabHits)\ninternal/tui3/chattabclose_test.go:107:\tfor _, hit := range a.chatTabHits {\ninternal/tui3/chattabclose_test.go:113:\t\tt.Fatalf(\"reopening did not restore the tab:\\n%q\\n%+v\", plain(a.tabsRow(a.width)), a.chatTabHits)\ninternal/tui3/chattabclose_test.go:221:\trestSpans := append([]tabHit(nil), a.chatTabHits...)\ninternal/tui3/chattabclose_test.go:237:\tfor at, hit := range a.chatTabHits {\ninternal/tui3/chattabclose_test.go:316:\tfor _, hit := range a.chatTabHits {\ninternal/tui3/chattabs.go:100:\t// folding the tab into the count at the row's right end.\ninternal/tui3/chattabs.go:527:\ta.chatTabHits = nil\ninternal/tui3/chattabs.go:552:\t\ta.chatTabHits = memo.hits\ninternal/tui3/chattabs.go:569:\ta.chatTabHits = tabsAt(hits, headLabelAt)\ninternal/tui3/chattabs.go:571:\ta.chatTabBar = tabBar{width: width, ink: a.inkState, hot: hot, more: more, newChat: a.canStart(), home: home, line: line, hits: a.chatTabHits,\ninternal/tui3/chattabs.go:887:\tfor _, hit := range a.chatTabHits {\ninternal/tui3/chattabs.go:921:\tfor _, hit := range a.chatTabHits {\ninternal/tui3/chattabs.go:987:\treturn a.tabGo(hit.tab), true\ninternal/tui3/chattabs.go:990:// tabGo is the switch itself, and it"} -{"kind":"step","step":10,"command":"cd /home/dev/src/bl-c249 \u0026\u0026 git grep -n '^func (a \\*app) view' internal/tui3 \u0026\u0026 sed -n '320,390p' internal/tui3/input.go \u0026\u0026 sed -n '1080,1160p' internal/tui3/view.go \u0026\u0026 sed -n '850,940p' internal/tui3/chattabs.go \u0026\u0026 sed -n '120,180p' internal/tui3/taskview_test.go","observation":"internal/tui3/view.go:1139:func (a *app) viewHeight() int {\n\te.dropPick()\n\tend := e.lineEnd()\n\tif end \u003e= len(e.value) {\n\t\treturn\n\t}\n\tcolumn := e.cursor - e.lineStart()\n\tnext := end + 1\n\tlength := 0\n\tfor next+length \u003c len(e.value) \u0026\u0026 e.value[next+length] != '\\n' {\n\t\tlength++\n\t}\n\tif column \u003e length {\n\t\tcolumn = length\n\t}\n\te.cursor = next + column\n}\n\n// key routes one keypress. The order is the surface's law: the key that acts on\n// the SESSION (ctrl+c) is read before any key that acts on the draft, and the\n// two typed overlays are read before the editor, because while a list is up the\n// four keys that move and commit it are the list's.\nfunc (a *app) key(msg tea.KeyPressMsg) tea.Cmd {\n\tif cmd, taken := a.pasteChipKey(msg); taken {\n\t\treturn cmd\n\t}\n\t// THE OPTION-AS-META CHECK READS EVERY KEY AND CLAIMS NONE OF THEM. It is\n\t// here, above the pointer handover and above every modal, because both of the\n\t// things it watches for can arrive anywhere: a real `alt+` chord settles the\n\t// question for the life of the process wherever it lands, and the character a\n\t// Mac produces instead of one is only worth a note while a place is standing\n\t// (chords.go). It returns nothing and takes nothing, so the key below does\n\t// exactly what it was always going to do.\n\ta.chordWatch(msg)\n\n\t// THE POINTER COMES BACK FIRST, ABOVE EVERYTHING, and then the key does\n\t// whatever it was always going to do. A hand back on the keyboard is a hand\n\t// that has finished selecting (copymode.go), so this is the whole of the exit\n\t// — no key to learn, no state to be stuck in, and nothing swallowed. ctrl+s\n\t// itself is excepted so it can act as the plain toggle it looks like: pressing\n\t// it twice must undo it, not re-arm it.\n\tif msg.String() != selectKey {\n\t\ta.takeMouseBack()\n\t}\n\n\t// THE FIRST-RUN SETUP OUTRANKS EVERYTHING BUT ctrl+c, and it can afford to:\n\t// it is up only on a launch where no turn has run, no question has been\n\t// raised and nothing has been typed, so there is nothing under it a key\n\t// could be aimed at (firstrun.go). ctr"} -{"kind":"step","step":11,"command":"cd /home/dev/src/bl-c249 \u0026\u0026 git grep -n 'func (a \\*app) View\\|func (a \\*app) view()' internal/tui3 \u0026\u0026 sed -n '780,850p' internal/tui3/tui3_test.go \u0026\u0026 sed -n '150,210p' internal/tui3/placekeys.go \u0026\u0026 sed -n '2280,2350p' internal/tui3/pages.go","observation":"internal/tui3/framedisk_law_test.go:737:func (a *app) View() {\ninternal/tui3/view.go:167:func (a *app) View() tea.View {\n\treturn dir\n}\n\n// stateRootForTests is the temporary state root the whole run shares — the one\n// [runTests] pinned into CODEAF_HOME, and a throwaway of the process's own if a\n// test has cleared it. It is the root a minted profile directory is made under.\nfunc stateRootForTests() string {\n\tif root := strings.TrimSpace(os.Getenv(home.EnvVar)); root != \"\" {\n\t\treturn root\n\t}\n\treturn os.TempDir()\n}\n\n// newTestApp builds a surface over a profile of its own.\n//\n// A TEST MAY NOT READ A PROFILE IT DID NOT CREATE. Before this pair existed the\n// helper named no profile at all, and [runTests] moves CODEAF_HOME to ONE temp\n// root for the whole run — so every bare app in the package read and wrote the\n// same directory, and a setup_seen_at written by an earlier test decided what\n// the next one saw. That is contamination by construction, and it had already\n// cost a week-long invisible red: TestTheOpeningHintNamesBothDoors was green in\n// the package run and red alone from #680 until #1153. So the default is now a\n// directory of this app's own, minted under the run's root and thrown away with\n// it — the same isolation `t.TempDir()` gives sheetApp and setupApp, without a\n// *testing.T to call it through.\nfunc newTestApp(agent Agent) *app {\n\treturn newTestAppWithProfile(mintProfileDir(), agent)\n}\n\n// newTestAppWithProfile is [newTestApp] with the profile directory named by the\n// caller. It exists for the test that means two surfaces to share ONE profile —\n// a setting written through the first and read back through the second — which\n// is honest use and never the default: a helper that quietly shared a root is\n// the defect the pair closes, so a caller that shares says so by passing the\n// same directory deliberately.\nfunc newTestAppWithProfile(profileDir string, agent Agent) *app {\n\ta := newApp(context.Background(), Options{\n\t\tAgent: agent, Workspace: \"/tmp/lab\", UsageLedger: labLedger(),\n\t"} -{"kind":"step","step":12,"command":"cd /home/dev/src/bl-c249 \u0026\u0026 sed -n '150,230p' internal/tui3/view.go \u0026\u0026 sed -n '230,330p' internal/tui3/view.go \u0026\u0026 sed -n '330,430p' internal/tui3/view.go \u0026\u0026 sed -n '805,845p' internal/tui3/tui3_test.go","observation":"\t// click on the box can put the caret under the pointer (draftclick.go) —\n\t// before it, a press on the draft fell through to the body's drag parking\n\t// and moved nothing, which made the one place a person types the one place\n\t// their pointer did not work.\n\tchromeDraft\n)\n\n// chromeRow is one row of the frame below the conversation.\ntype chromeRow struct {\n\tkind chromeKind\n\tindex int\n}\n\n// View declares the frame and the terminal state it wants. Alt screen, because\n// the conversation scrolls under our own anchor; ALL motion, because a tool\n// line is a thing you click and now a thing you hover — cell motion would\n// deliver the wheel and the press and nothing in between.\nfunc (a *app) View() tea.View {\n\t// A MESSAGE THAT CHANGED NOTHING DRAWS THE FRAME BEFORE IT. Bubble Tea builds\n\t// a frame per message and writes one per sixtieth of a second, so the frames\n\t// it builds for a burst are mostly frames nobody is ever shown — and a sweep\n\t// is six hundred messages that touched two integers and a stored position\n\t// between them. [app.still] is the fold saying so, and it says so only about\n\t// the paths that mutate nothing this function reads (coalesce.go).\n\tif a.drawn \u0026\u0026 a.ptr.still {\n\t\treturn a.shown\n\t}\n\tframe, caretX, caretY := a.frame()\n\tv := tea.NewView(a.tabTitlePreview(frame))\n\tv.AltScreen = true\n\t// THE POINTER IS OURS BY DEFAULT, AND THAT HAS A COST WORTH NAMING. Asking\n\t// for it — at any motion level — makes this app the owner of every drag, so\n\t// the terminal's own drag-to-select is dead from that moment; in an\n\t// alt-screen app there is no scrollback to fall back on either. The setting\n\t// (config's ui.mouse) is the person's standing call between hover/click here\n\t// and select-to-copy everywhere, and it says on, because hover and click are\n\t// this surface's own language.\n\t//\n\t// WHICH IS WHY released EXISTS. \"Hold shift and drag\" is the usual answer and\n\t// it is not an answer: it is a fact about terminals that most people have\n\t// never been told, and the ones who have been told use "} -{"kind":"step","step":13,"command":"cd /home/dev/src/bl-c249 \u0026\u0026 git grep -n 'func planUnderRows\\|func (.*tasksReading.*lay\\|func (.*tasksPlace.*lay' internal/tui3 \u0026\u0026 sed -n '330,400p' internal/tui3/taskplan.go \u0026\u0026 git grep -n 'func (a \\*app) view()' internal/tui3/*_test.go internal/tui3/*.go || true \u0026\u0026 git grep -n 'func plain' internal/tui3/*_test.go | head","observation":"internal/tui3/task.go:5317:func planUnderRows(item tasksItem, width int, pal palette) []string {\ninternal/tui3/tasksplace.go:588:func (r tasksReading) lay(width int) []tasksLine {\n// nothing when that row has no title or has already landed — the bare word\n// `queued`, which is the honest reading of a hold this page cannot name.\nfunc planWaits(row *session.PlanTaskRow, kin planKin) string {\n\tif row == nil || strings.TrimSpace(row.Status) != \"pending\" {\n\t\treturn \"\"\n\t}\n\tanchor := kin[planAnchor(row, kin)]\n\tif anchor == nil || strings.TrimSpace(anchor.Title) == \"\" {\n\t\treturn \"\"\n\t}\n\tif planStateWord(anchor.Status) == \"done\" {\n\t\treturn \"\"\n\t}\n\treturn strings.TrimSpace(anchor.Title)\n}\n\n// planFigures is the telemetry a plan task's under-block carries: the steps its\n// worker has taken and what it has cost, joined the way every row on this\n// surface joins two facts. Each half is omitted when it is nothing, so a task\n// that has run no step and spent nothing draws no line at all — the emptiness\n// law, and the reason [planUnderCount] asks before it spends a row.\nfunc planFigures(row *session.PlanTaskRow) string {\n\tif row == nil {\n\t\treturn \"\"\n\t}\n\tvar segs []string\n\tif steps := planStepWords(row.Steps); steps != \"\" {\n\t\tsegs = append(segs, steps)\n\t}\n\tif usd := planSpendWord(row.USD); usd != \"\" {\n\t\tsegs = append(segs, usd)\n\t}\n\treturn strings.Join(segs, railSep)\n}\n\n// planUnderCount is how many rows a plan task's under-block spends: none for a row\n// with no step in flight, one for the live command alone when the task carries no\n// figures, and two when the telemetry stands under it. It is asked at LAYOUT,\n// where a row is added per line, and [planUnderRows] draws them; both read the\n// same emptiness so the two cannot disagree about how tall the block is.\n//\n// A HELD ROW SPENDS NOTHING HERE. What it waits on is on the row's own reading\n// ([planWaits], drawn by [planStateField] and [tasksReasonLine]), and a block\n// that repeated it would be a page saying one fact twice.\nfunc planUnderCount(row *session.PlanTaskRow) int"} -{"kind":"step","step":14,"command":"cd /home/dev/src/bl-c249 \u0026\u0026 sed -n '570,640p' internal/tui3/tasksplace.go \u0026\u0026 sed -n '5290,5345p' internal/tui3/task.go \u0026\u0026 sed -n '430,475p' internal/tui3/view.go","observation":"\t// out loud while the fold is shut. Zero everywhere else.\n\tkids int\n\t// rank is the row's answer to every sort key, FOLDED WITH EVERYTHING UNDER IT\n\t// ([tasksRank.fold]). A row with work beneath it shows the family's total,\n\t// because a shut fold stands in for what it is hiding and an open one is the\n\t// sum of the rows under it — which is what `$0.49` over `$0.47` and `$0.02`\n\t// reads as. It is carried on the LINE and not re-derived in the paint, for the\n\t// same reason the fold mark is: it is a fact about the layout.\n\trank tasksRank\n}\n\n// tasksBareLead is the cell in front of every row of work: the place's one left\n// edge ([placeLead]), where the row's state glyph stands. It carries no mark —\n// the band says which row a hand is on.\nconst tasksBareLead = placeLead\n\n// lay is the one walk of the reading: what line the page draws, in order, and\n// which of them a person can act on.\nfunc (r tasksReading) lay(width int) []tasksLine {\n\t// A MACHINE THAT HAS RUN NOTHING LAYS NOTHING OUT, and the place spends the\n\t// frame on [tasksTeach] instead. A machine that HAS and whose window holds\n\t// none of it still lays out its head line, because that line is the only\n\t// thing on the frame naming the window the shift-arrows move\n\t// ([tasksReading.held] states the law).\n\tif width \u003c= 0 || r.held == 0 {\n\t\treturn nil\n\t}\n\tlines := make([]tasksLine, 0, len(r.items)+8)\n\t// THE FAMILY COLUMN APPEARS ONLY WHERE THERE IS A TREE TO DRAW. On a machine\n\t// whose work this surface cannot place in any conversation, and that has never\n\t// split a task up, every row is a root with nothing under it — and two cells\n\t// of empty gutter in front of all of them would be a column saying \"there is\n\t// structure here\" about a page that has none.\n\ttree := r.tree()\n\tcolumn := tree.column()\n\tlevels := tasksKinRoom(width)\n\tadd := func(kind tasksLineKind, text string) {\n\t\tlines = append(lines, tasksLine{kind: kind, text: text, owner: -1})\n\t}\n\tadd(tasksLineWord, r.headLine(width))\n\tphone := layoutTier(width) == tierPhone\n\t// THE CONTROL ROW "} -{"kind":"step","step":15,"command":"cd /home/dev/src/bl-c249 \u0026\u0026 git grep -n 'func .*tasksLine.*row\\|func .*tasksLine.*draw\\|switch line.kind' internal/tui3/tasksplace.go internal/tui3/place_tasks.go | head -80 \u0026\u0026 sed -n '720,820p' internal/tui3/tasksplace.go","observation":"internal/tui3/tasksplace.go:1399:\tswitch line.kind {\n\t\t\t\tif line.folds {\n\t\t\t\t\tmark = tasksFoldShut\n\t\t\t\t}\n\t\t\t\tif line.folds \u0026\u0026 line.open {\n\t\t\t\t\tmark = tasksFoldOpen\n\t\t\t\t}\n\t\t\t\tline.kin = tasksKin(0, levels, mark)\n\t\t\t\tlines = append(lines, line)\n\t\t\t\tif !line.open {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tdepth = 1\n\t\t\t}\n\t\t\tfor at, root := range g.roots {\n\t\t\t\twork(root, depth, at == len(g.roots)-1, g.named, false)\n\t\t\t}\n\t\t}\n\t}\n\treturn lines\n}\n\n// headLine is the page's opening sentence at one width: what is held, and the\n// window control where the frame has room to draw it.\n//\n// THE WINDOW EDGE IS NAMED ONCE PER FRAME, and which half of the line names it\n// depends on whether the control fits. A frame with room for `shift+← aug 12 –\n// aug 25 →` has the span between the arrows, where SCREEN 3d puts it — the\n// control and the reading at once — so the sentence drops its `since` clause; a\n// frame too narrow for the control keeps the clause, because a head line that\n// named neither would leave the four arrow keys moving something nothing on the\n// frame reports.\n//\n// IT IS A FUNCTION BECAUSE THE HEAD IS NOT ALWAYS DRAWN IN THE LIST'S OWN\n// WIDTH. Where the frame splits, the list is laid out in the cells left of the\n// seam and this one sentence keeps the whole frame (taskpane.go says why), so\n// the layout and the split have to spell it the one way.\nfunc (r tasksReading) headLine(width int) string {\n\thead := r.head(width, false)\n\tif arrows, _ := placeWindowFits(width, head, r.win); !arrows {\n\t\thead = r.head(width, true)\n\t}\n\treturn head\n}\n\n// headRow is that same sentence PAINTED, at a width the layout may not be using.\n//\n// It goes through [tasksReading.paint] rather than spelling the head arm a second\n// time, so the split frame's head and the list's own head are one row built one\n// way — the head carries the window control and a second painter would be a\n// second chance for the control to be drawn where it is not bound.\nfunc (r tasksReading) headRow(width int, pal palette) string {\n\treturn r.paint([]tasksLine{{kind"} -{"kind":"step","step":16,"command":"cd /home/dev/src/bl-c249 \u0026\u0026 sed -n '1360,1445p' internal/tui3/tasksplace.go \u0026\u0026 sed -n '1445,1515p' internal/tui3/tasksplace.go","observation":"// they did not want. A shut root says what it is holding and how urgent the most\n// urgent of it is ([tasksChatStateField]), which is enough to decide whether to\n// open it — and `→` opens it.\n//\n// AND A QUERY OPENS EVERYTHING ([tasksReading.unfolded]). A row that matched and\n// is sitting behind a shut fold is a row the query appears not to have found, and\n// the person's own folds come back when the query clears.\nfunc (r tasksReading) opens(key tasksKey) bool {\n\tif r.unfolded {\n\t\treturn true\n\t}\n\tif open, set := r.open[key]; set {\n\t\treturn open\n\t}\n\treturn false\n}\n\n// rows is the whole page painted with no cursor anywhere on it, which is what a\n// reader of the reading alone sees.\nfunc (r tasksReading) rows(width int, pal palette) []string {\n\tlines := r.lay(width)\n\tout := make([]string, len(lines))\n\tfor i := range lines {\n\t\tout[i] = r.paint(lines, i, width, pal, false)\n\t}\n\treturn out\n}\n\n// paint draws ONE line of the layout, lit where the cursor or the pointer is on\n// it. Every line it returns is at most width cells, at every width.\nfunc (r tasksReading) paint(lines []tasksLine, i, width int, pal palette, lit bool) string {\n\tif i \u003c 0 || i \u003e= len(lines) || width \u003c= 0 {\n\t\treturn \"\"\n\t}\n\tline, lead := lines[i], tasksBareLead\n\troom := width - ansi.StringWidth(lead)\n\tif room \u003c 1 {\n\t\troom = 1\n\t}\n\tswitch line.kind {\n\tcase tasksLineAir:\n\t\treturn \"\"\n\tcase tasksLineControl:\n\t\treturn tasksControlRow(r.query, r.order, width, pal)\n\tcase tasksLineWord:\n\t\tif i == 0 {\n\t\t\t// THE HEAD LINE IS THE WINDOW'S CONTROL TOO (SCREEN 3d), drawn by the\n\t\t\t// one head row standing and spend also draw — `shift+← aug 12 – aug 25\n\t\t\t// →`, the control and the reading at once. Before it, this place bound\n\t\t\t// all four arrow keys and drew nothing that named them, which is the\n\t\t\t// exact defect verbstrip.go's law was written against.\n\t\t\treturn placeHeadRow(width, line.text, placeHeading(line.text, pal), r.win, pal)\n\t\t}\n\t\treturn placeLead + placeHeading(fit(line.text, width-len(placeLead)), pal)\n\tcase tasksLineChat:\n\t\treturn tasksCh"} -{"kind":"step","step":17,"command":"cd /home/dev/src/bl-c249 \u0026\u0026 codeaf patch internal/tui3/chattabs.go --old 'signal tabSignal\\n}' --new 'signal tabSignal\\n\\t// work marks the run-store view paired with this conversation.\\n\\twork bool\\n}' \u0026\u0026 codeaf patch internal/tui3/chattabs.go --old '\\tif !tabsHold(tabs, front) {\\n\\t\\ttabs = append(tabs, a.tabAs(chatTab{key: front, file: a.file}, nil, front))\\n\\t}\\n\\treturn tabsCapped(tabs, a.prev)' --new '\\tif !tabsHold(tabs, front) {\\n\\t\\ttabs = append(tabs, a.tabAs(chatTab{key: front, file: a.file}, nil, front))\\n\\t}\\n\\tif work, ok := a.workTab(); ok {\\n\\t\\ttabs = append(tabs, work)\\n\\t}\\n\\treturn tabsCapped(tabs, a.prev)' \u0026\u0026 codeaf patch internal/tui3/chattabs.go --old 'func (a *app) tabGo(tab chatTab) (cmd tea.Cmd) {\\n\\ta.tabReveal()' --new 'func (a *app) tabGo(tab chatTab) (cmd tea.Cmd) {\\n\\ta.tabReveal()\\n\\tif tab.work {\\n\\t\\ta.openWorkTab()\\n\\t\\treturn nil\\n\\t}\\n\\ta.workTabOn = false' \u0026\u0026 codeaf patch internal/tui3/app.go --old '\\tchatTabBar tabBar\\n' --new '\\tchatTabBar tabBar\\n\\t// workTabOn is the run store drawn as a peer tab of this conversation.\\n\\tworkTabOn bool\\n\\tworkTabSettled string\\n' \u0026\u0026 codeaf patch internal/tui3/input.go --old 'func (a *app) key(msg tea.KeyPressMsg) tea.Cmd {\\n\\tif cmd, taken := a.pasteChipKey(msg); taken {' --new 'func (a *app) key(msg tea.KeyPressMsg) tea.Cmd {\\n\\tif a.workTabOn {\\n\\t\\treturn a.workTabKey(msg)\\n\\t}\\n\\tif cmd, taken := a.pasteChipKey(msg); taken {' \u0026\u0026 codeaf patch internal/tui3/view.go --old '\\ta.caret = true\\n\\t// AND THERE IS NO TAB BAR' --new '\\ta.caret = true\\n\\tif a.workTabOn {\\n\\t\\tlines := a.workTabFrame(width, height)\\n\\t\\treturn strings.Join(lines, \"\\\\n\"), 2, max(len(lines)-1, 0)\\n\\t}\\n\\t// AND THERE IS NO TAB BAR' \u0026\u0026 cat \u003e internal/tui3/worktab.go \u003c\u003c'EOF'\npackage tui3\n\nimport (\n\t\"strings\"\n\n\ttea \"charm.land/bubbletea/v2\"\n)\n\n// workTab is the run's root beside its conversation. Terminal rows are retained\n// for one unchanged reading, so the final repaint and landing card can land\n// before the tab removes itself.\nfunc (a *app) workTab() (chatTab, bool) {\n\tp, ok := a.planReader()\n\tif !ok {\n\t\treturn chatTab{}, false\n\t}\n\trows := p.PlanTasks()\n\tif len(rows) == 0 {\n\t\treturn chatTab{}, false\n\t}\n\tlive := false\n\tfor _, row := range rows {\n\t\tif planRunning(row.Status) {\n\t\t\tlive = true\n\t\t\tbreak\n\t\t}\n\t}\n\tif !live \u0026\u0026 a.workTabStable() {\n\t\treturn chatTab{}, false\n\t}\n\tword := strings.TrimSpace(rows[0].Title)\n\tif word == \"\" {\n\t\treturn chatTab{}, false\n\t}\n\treturn chatTab{key: a.frontTabKey() + \"#work\", file: a.file, word: word, full: word, here: a.workTabOn, held: true, work: true}, true\n}\n\nfunc (a *app) workTabStable() bool {\n\tp, ok := a.planReader()\n\tif !ok {\n\t\treturn true\n\t}\n\tvar sig strings.Builder\n\tfor _, row := range p.PlanTasks() {\n\t\tif planRunning(row.Status) {\n\t\t\ta.workTabSettled = \"\"\n\t\t\treturn false\n\t\t}\n\t\tsig.WriteString(row.ID + \"=\" + row.Status + \";\")\n\t}\n\tnow := sig.String()\n\tstable := now != \"\" \u0026\u0026 now == a.workTabSettled\n\ta.workTabSettled = now\n\tif stable {\n\t\ta.workTabOn = false\n\t}\n\treturn stable\n}\n\nfunc (a *app) openWorkTab() {\n\tp, ok := a.planReader()\n\tif !ok || len(p.PlanTasks()) == 0 {\n\t\treturn\n\t}\n\trows := p.PlanTasks()\n\ta.workTabOn = true\n\ta.taskSheet.planOn, a.taskSheet.detailOn = true, true\n\tif page, found := p.PlanTaskPage(rows[0].ID); found {\n\t\ta.taskSheet.plan = page\n\t} else {\n\t\ta.taskSheet.plan.Row = rows[0]\n\t}\n\ta.taskSheet.planNote.reset()\n\ta.chatTabBar = tabBar{}\n\ta.touch()\n}\n\nfunc (a *app) workTabKey(msg tea.KeyPressMsg) tea.Cmd {\n\tif msg.String() == \"esc\" {\n\t\ta.workTabOn = false\n\t\ta.closeTaskPlan()\n\t\ta.chatTabBar = tabBar{}\n\t\treturn nil\n\t}\n\treturn a.taskPlanKey(msg)\n}\n\n// workTabFrame paints the tasks place's own reading and row renderers, then the\n// root page's notes and existing note composer at the foot.\nfunc (a *app) workTabFrame(width, height int) []string {\n\ta.workTabStable()\n\ttabs := a.tabsRow(width)\n\tout := a.headRows(width, tabs, a.pal)\n\treading := a.takeTaskReading().reading\n\trows := reading.rows(width, a.pal)\n\troom := height - len(out) - 3\n\tif room \u003c 0 { room = 0 }\n\tif len(rows) \u003e room { rows = rows[:room] }\n\tout = append(out, rows...)\n\tfor len(out) \u003c height-3 { out = append(out, \"\") }\n\tfor _, note := range a.taskSheet.plan.Notes {\n\t\twho := strings.TrimSpace(note.Author); if note.Person { who = \"you\" }\n\t\tout = append(out, a.pal.dim(who+railSep)+a.pal.ink(note.Body))\n\t}\n\ttext := a.taskSheet.planNote.String()\n\tif strings.TrimSpace(text) == \"\" { text = taskPlanNoteWord }\n\tout = append(out, prompt+a.pal.dim(text))\n\treturn out\n}\nEOF\n\ngofmt -w internal/tui3/worktab.go internal/tui3/chattabs.go internal/tui3/app.go internal/tui3/input.go internal/tui3/view.go\ngo test ./internal/tui3 -run 'TestWorkTab' -count=1","observation":"error: there is no `codeaf patch`.\nrun `codeaf --help` for every command\nlstat internal/tui3/worktab.go: no such file or directory\n# github.com/Agent-Field/codeaf/internal/tui3 [github.com/Agent-Field/codeaf/internal/tui3.test]\ninternal/tui3/worktab_test.go:26:49: tabs[1].work undefined (type chatTab has no field or method work)\ninternal/tui3/worktab_test.go:30:4: a.workTabStable undefined (type *app has no field or method workTabStable)\ninternal/tui3/worktab_test.go:31:4: a.workTabStable undefined (type *app has no field or method workTabStable)\ninternal/tui3/worktab_test.go:32:52: tabs[0].work undefined (type chatTab has no field or method work)\ninternal/tui3/worktab_test.go:36:56: tabs[0].work undefined (type chatTab has no field or method work)\ninternal/tui3/worktab_test.go:43:4: a.openWorkTab undefined (type *app has no field or method openWorkTab)\ninternal/tui3/worktab_test.go:44:31: a.workTabFrame undefined (type *app has no field or method workTabFrame)\ninternal/tui3/worktab_test.go:54:4: a.openWorkTab undefined (type *app has no field or method openWorkTab)\ninternal/tui3/worktab_test.go:62:34: a.workTabFrame undefined (type *app has no field or method workTabFrame)\ninternal/tui3/worktab_test.go:69:4: a.openWorkTab undefined (type *app has no field or method openWorkTab)\ninternal/tui3/worktab_test.go:69:4: too many errors\nFAIL\tgithub.com/Agent-Field/codeaf/internal/tui3 [build failed]\nFAIL\n\n\nCommand exited with code 1"} -{"kind":"step","step":18,"command":"cd /home/dev/src/bl-c249 \u0026\u0026 command -v apply_patch || true \u0026\u0026 git status --short","observation":"(no output)"} -{"kind":"step","step":19,"command":"cd /home/dev/src/bl-c249 \u0026\u0026 sed -i '/signal tabSignal/a\\\twork bool' internal/tui3/chattabs.go \u0026\u0026 sed -i '/return tabsCapped(tabs, a.prev)/i\\\tif work, ok := a.workTab(); ok { tabs = append(tabs, work) }' internal/tui3/chattabs.go \u0026\u0026 sed -i '/func (a \\*app) tabGo(tab chatTab) (cmd tea.Cmd) {/a\\\tif tab.work { a.openWorkTab(); return nil }; a.workTabOn = false' internal/tui3/chattabs.go \u0026\u0026 sed -i '/chatTabBar tabBar/a\\\tworkTabOn bool\\n\\tworkTabSettled string' internal/tui3/app.go \u0026\u0026 sed -i '/func (a \\*app) key(msg tea.KeyPressMsg) tea.Cmd {/a\\\tif a.workTabOn { return a.workTabKey(msg) }' internal/tui3/input.go \u0026\u0026 sed -i '/a.caret = true/a\\\tif a.workTabOn { lines := a.workTabFrame(width, height); return strings.Join(lines, \"\\\\n\"), 2, max(len(lines)-1, 0) }' internal/tui3/view.go \u0026\u0026 cat \u003e internal/tui3/worktab.go \u003c\u003c'EOF'\npackage tui3\n\nimport (\n\t\"strings\"\n\ttea \"charm.land/bubbletea/v2\"\n)\n\nfunc (a *app) workTab() (chatTab, bool) {\n\tp, ok := a.planReader(); if !ok { return chatTab{}, false }\n\trows := p.PlanTasks(); if len(rows) == 0 { return chatTab{}, false }\n\tlive := false\n\tfor _, row := range rows { if planRunning(row.Status) { live = true; break } }\n\tif !live \u0026\u0026 a.workTabStable() { return chatTab{}, false }\n\tword := strings.TrimSpace(rows[0].Title); if word == \"\" { return chatTab{}, false }\n\treturn chatTab{key: a.frontTabKey()+\"#work\", file:a.file, word:word, full:word, here:a.workTabOn, held:true, work:true}, true\n}\n\n// workTabStable requires two identical terminal readings: the first lets the\n// landing repaint and its conversation card settle; the second removes the tab.\nfunc (a *app) workTabStable() bool {\n\tp, ok := a.planReader(); if !ok { return true }\n\tvar sig strings.Builder\n\tfor _, row := range p.PlanTasks() {\n\t\tif planRunning(row.Status) { a.workTabSettled = \"\"; return false }\n\t\tsig.WriteString(row.ID+\"=\"+row.Status+\";\")\n\t}\n\tnow := sig.String(); stable := now != \"\" \u0026\u0026 now == a.workTabSettled\n\ta.workTabSettled = now\n\tif stable { a.workTabOn = false }\n\treturn stable\n}\n\nfunc (a *app) openWorkTab() {\n\tp, ok := a.planReader(); if !ok { return }\n\trows := p.PlanTasks(); if len(rows)==0 { return }\n\ta.workTabOn, a.taskSheet.planOn, a.taskSheet.detailOn = true, true, true\n\tif page, found := p.PlanTaskPage(rows[0].ID); found { a.taskSheet.plan = page } else { a.taskSheet.plan.Row = rows[0] }\n\ta.taskSheet.planNote.reset(); a.chatTabBar = tabBar{}; a.touch()\n}\n\nfunc (a *app) workTabKey(msg tea.KeyPressMsg) tea.Cmd {\n\tif msg.String()==\"esc\" { a.workTabOn=false; a.closeTaskPlan(); a.chatTabBar=tabBar{}; return nil }\n\treturn a.taskPlanKey(msg)\n}\n\n// workTabFrame reuses the tasks place reading and painter; there is no work-row renderer here.\nfunc (a *app) workTabFrame(width, height int) []string {\n\ta.workTabStable()\n\tout := a.headRows(width, a.tabsRow(width), a.pal)\n\trows := a.takeTaskReading().reading.rows(width, a.pal)\n\troom := height-len(out)-3; if room\u003c0 { room=0 }; if len(rows)\u003eroom { rows=rows[:room] }\n\tout=append(out, rows...); for len(out)\u003cheight-3 { out=append(out, \"\") }\n\tfor _, note := range a.taskSheet.plan.Notes { who:=strings.TrimSpace(note.Author); if note.Person { who=\"you\" }; out=append(out, a.pal.dim(who+railSep)+a.pal.ink(note.Body)) }\n\ttext:=a.taskSheet.planNote.String(); if strings.TrimSpace(text)==\"\" { text=taskPlanNoteWord }\n\treturn append(out, prompt+a.pal.dim(text))\n}\nEOF\n\ngofmt -w internal/tui3/worktab.go internal/tui3/chattabs.go internal/tui3/app.go internal/tui3/input.go internal/tui3/view.go \u0026\u0026 go test ./internal/tui3 -run 'TestWorkTab' -count=1","observation":"/signal tabSignal/a\\\twork bool is outside your copy — this task works in /home/dev/src/bl-c249; read anywhere, write only there. Say what needs changing out there in your report; what you write in your copy comes home on its own."} -{"kind":"step","step":20,"command":"cd /home/dev/src/bl-c249 \u0026\u0026 grep -nE 'signal tabSignal|return tabsCapped\\(tabs, a.prev\\)|func \\(a \\*app\\) tabGo|chatTabBar tabBar|func \\(a \\*app\\) key\\(msg|a.caret = true' internal/tui3/chattabs.go internal/tui3/app.go internal/tui3/input.go internal/tui3/view.go \u0026\u0026 git status --short","observation":"internal/tui3/chattabs.go:155:\tsignal tabSignal\ninternal/tui3/chattabs.go:360:\treturn tabsCapped(tabs, a.prev)\ninternal/tui3/chattabs.go:995:func (a *app) tabGo(tab chatTab) (cmd tea.Cmd) {\ninternal/tui3/app.go:1740:\tchatTabBar tabBar\ninternal/tui3/input.go:341:func (a *app) key(msg tea.KeyPressMsg) tea.Cmd {\ninternal/tui3/view.go:284:\ta.caret = true\n"} -{"kind":"step","step":21,"command":"cd /home/dev/src/bl-c249 \u0026\u0026 sed -i '995a\\\tif tab.work { a.openWorkTab(); return nil }; a.workTabOn = false' internal/tui3/chattabs.go \u0026\u0026 sed -i '360i\\\tif work, ok := a.workTab(); ok { tabs = append(tabs, work) }' internal/tui3/chattabs.go \u0026\u0026 sed -i '155a\\\twork bool' internal/tui3/chattabs.go \u0026\u0026 sed -i '1740a\\\tworkTabOn bool\\n\\tworkTabSettled string' internal/tui3/app.go \u0026\u0026 sed -i '341a\\\tif a.workTabOn { return a.workTabKey(msg) }' internal/tui3/input.go \u0026\u0026 sed -i '284a\\\tif a.workTabOn { lines := a.workTabFrame(width, height); return strings.Join(lines, \"\\\\n\"), 2, max(len(lines)-1, 0) }' internal/tui3/view.go \u0026\u0026 cat \u003e internal/tui3/worktab.go \u003c\u003c'EOF'\npackage tui3\n\nimport (\n\t\"strings\"\n\ttea \"charm.land/bubbletea/v2\"\n)\n\nfunc (a *app) workTab() (chatTab, bool) {\n\tp, ok := a.planReader(); if !ok { return chatTab{}, false }\n\trows := p.PlanTasks(); if len(rows) == 0 { return chatTab{}, false }\n\tlive := false\n\tfor _, row := range rows { if planRunning(row.Status) { live = true; break } }\n\tif !live \u0026\u0026 a.workTabStable() { return chatTab{}, false }\n\tword := strings.TrimSpace(rows[0].Title); if word == \"\" { return chatTab{}, false }\n\treturn chatTab{key: a.frontTabKey()+\"#work\", file:a.file, word:word, full:word, here:a.workTabOn, held:true, work:true}, true\n}\n\nfunc (a *app) workTabStable() bool {\n\tp, ok := a.planReader(); if !ok { return true }\n\tvar sig strings.Builder\n\tfor _, row := range p.PlanTasks() {\n\t\tif planRunning(row.Status) { a.workTabSettled = \"\"; return false }\n\t\tsig.WriteString(row.ID+\"=\"+row.Status+\";\")\n\t}\n\tnow := sig.String(); stable := now != \"\" \u0026\u0026 now == a.workTabSettled\n\ta.workTabSettled = now\n\tif stable { a.workTabOn = false }\n\treturn stable\n}\n\nfunc (a *app) openWorkTab() {\n\tp, ok := a.planReader(); if !ok { return }\n\trows := p.PlanTasks(); if len(rows)==0 { return }\n\ta.workTabOn, a.taskSheet.planOn, a.taskSheet.detailOn = true, true, true\n\tif page, found := p.PlanTaskPage(rows[0].ID); found { a.taskSheet.plan = page } else { a.taskSheet.plan.Row = rows[0] }\n\ta.taskSheet.planNote.reset(); a.chatTabBar = tabBar{}; a.touch()\n}\n\nfunc (a *app) workTabKey(msg tea.KeyPressMsg) tea.Cmd {\n\tif msg.String()==\"esc\" { a.workTabOn=false; a.closeTaskPlan(); a.chatTabBar=tabBar{}; return nil }\n\treturn a.taskPlanKey(msg)\n}\n\n// workTabFrame reuses the tasks place reading and painter; there is no second work-row renderer.\nfunc (a *app) workTabFrame(width, height int) []string {\n\ta.workTabStable()\n\tout := a.headRows(width, a.tabsRow(width), a.pal)\n\trows := a.takeTaskReading().reading.rows(width, a.pal)\n\troom := height-len(out)-3; if room\u003c0 { room=0 }; if len(rows)\u003eroom { rows=rows[:room] }\n\tout=append(out, rows...); for len(out)\u003cheight-3 { out=append(out, \"\") }\n\tfor _, note := range a.taskSheet.plan.Notes { who:=strings.TrimSpace(note.Author); if note.Person { who=\"you\" }; out=append(out, a.pal.dim(who+railSep)+a.pal.ink(note.Body)) }\n\ttext:=a.taskSheet.planNote.String(); if strings.TrimSpace(text)==\"\" { text=taskPlanNoteWord }\n\treturn append(out, prompt+a.pal.dim(text))\n}\nEOF\n\ngofmt -w internal/tui3/worktab.go internal/tui3/chattabs.go internal/tui3/app.go internal/tui3/input.go internal/tui3/view.go \u0026\u0026 go test ./internal/tui3 -run 'TestWorkTab' -count=1","observation":"# github.com/Agent-Field/codeaf/internal/tui3 [github.com/Agent-Field/codeaf/internal/tui3.test]\ninternal/tui3/worktab_test.go:74:114: unknown field Answer in struct literal of type session.TaskNotice\ninternal/tui3/worktab_test.go:78:20: a.view undefined (type *app has no field or method view, but does have method View)\nFAIL\tgithub.com/Agent-Field/codeaf/internal/tui3 [build failed]\nFAIL\n\n\nCommand exited with code 1"} -{"kind":"step","step":22,"command":"cd /home/dev/src/bl-c249 \u0026\u0026 git grep -n 'type TaskNotice struct' internal/session \u0026\u0026 sed -n '330,430p' internal/session/events.go 2\u003e/dev/null || true \u0026\u0026 git grep -n 'TaskNotice{.*Answer\\|Report:' internal/tui3/*_test.go | head -20 \u0026\u0026 git grep -n 'View().Content\\|\\.View()' internal/tui3/*_test.go | head -20","observation":"internal/session/task_contract.go:421:type TaskNotice struct {\ninternal/tui3/bundle_test.go:3402:\t\t\tReport: \"the tests did not build\", Merge: mergeWordAborted, Branch: \"task/collect\",\ninternal/tui3/bundle_test.go:3502:\t\tElapsed: 4 * time.Second, Report: \"the tests did not build\\nsee the log\",\ninternal/tui3/bundle_test.go:3526:\t\tElapsed: 90 * time.Second, Report: \"stopped: 40 steps and no finish\",\ninternal/tui3/bundle_test.go:3827:\t\t\tReport: \"the merge conflicted\", Merge: mergeWordConflicted, Branch: \"task/render\",\ninternal/tui3/bundle_test.go:3834:\t\t\tReport: \"the tests did not build\",\ninternal/tui3/bundle_test.go:4047:\t\t\tReport: \"the tests did not build\",\ninternal/tui3/bundle_test.go:4050:\t\t\tReport: \"stopped: 40 steps and no finish\", Merge: mergeWordAborted, Branch: \"task/mix\",\ninternal/tui3/bundle_test.go:4059:\t\t\tReport: \"the tests did not build\", Merge: mergeWordInPlace,\ninternal/tui3/harnessroom_record_test.go:31:\t\tsession.TaskNotice{Report: report, Stopped: true})})\ninternal/tui3/nested_landing_accept_test.go:23:\t\tsession.TaskNotice{Elapsed: time.Minute, Merge: mergeWordMerged, Report: \"the index is rebuilt\"})})\ninternal/tui3/nested_landing_accept_test.go:27:\t\t\tReport: \"finished, but needs your look — nobody could check it in 5m0s\",\ninternal/tui3/nested_landing_accept_test.go:57:\t\t\tReport: took + \"\\nfinished, but needs your look — nobody could check it in 5m0s\",\ninternal/tui3/railtree_test.go:244:\t\tReport: \"the tests did not build\",\ninternal/tui3/railview_test.go:109:\t\tReport: railReportOf(4),\ninternal/tui3/roomrecord_test.go:28:\t\t\tReport: \"Added the guard in parseRow and covered it with a test.\",\ninternal/tui3/roomrecord_test.go:56:\t\tsession.TaskNotice{Report: \"Added the guard in parseRow.\"})})\ninternal/tui3/taskcardhost_test.go:128:\t\treturn session.TaskRecord{Report: \"widened the pipe and re-ran the importer.\", Kept: true}, nil\ninternal/tui3/taskcardhost_test.go:292:\t\treturn session.TaskRecord{Report: \"the far machine's word\", Kept: true}, nil\ninternal/tui3/taskcardhost_test.go:397:\t\treturn session.Ta"} -{"kind":"step","step":23,"command":"cd /home/dev/src/bl-c249 \u0026\u0026 sed -i 's/Answer: \"the importer landed\"/Report: \"the importer landed\"/' internal/tui3/worktab_test.go \u0026\u0026 sed -i 's/plain(a.view())/plain(func() string { s, _, _ := a.frame(); return s }())/' internal/tui3/worktab_test.go \u0026\u0026 gofmt -w internal/tui3/worktab_test.go \u0026\u0026 go test ./internal/tui3 -run 'TestWorkTab' -count=1","observation":"--- FAIL: TestWorkTabDrawsTheTasksPlacesOwnRows (0.00s)\n worktab_test.go:47: work tab missing \"Fix the flake\":\n codeaf sat 9:00am\n the run × Widen the importer × \n ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────\n \n tasks · 1 chat · 2 subtasks shift+← aug 2 – aug 15 → shift+↑ coarser\n ⌕ type to filter state age ↓ \n \n running · 2 folded away\n ▸ the run 2 running now \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n › a note for this task\nFAIL\ntui3: 1 goroutines still parked at the end of the run\nFAIL\tgithub.com/Agent-Field/codeaf/internal/tui3\t0.021s\nFAIL\n\n\nCommand exited with code 1"} -{"kind":"step","step":24,"command":"cd /home/dev/src/bl-c249 \u0026\u0026 grep -n 'reading :=\\|rows := a.takeTaskReading' internal/tui3/worktab.go \u0026\u0026 sed -i '69c\\treading := a.takeTaskReading().reading; reading.unfolded = true; rows := reading.rows(width, a.pal)' internal/tui3/worktab.go \u0026\u0026 gofmt -w internal/tui3/worktab.go \u0026\u0026 go test ./internal/tui3 -run 'TestWorkTab' -count=1","observation":"90:\trows := a.takeTaskReading().reading.rows(width, a.pal)\n# github.com/Agent-Field/codeaf/internal/tui3 [github.com/Agent-Field/codeaf/internal/tui3.test]\ninternal/tui3/worktab.go:69:3: declared and not used: treading\ninternal/tui3/worktab.go:70:3: undefined: reading\ninternal/tui3/worktab.go:71:3: declared and not used: rows\ninternal/tui3/worktab.go:71:11: undefined: reading\ninternal/tui3/worktab.go:71:24: undefined: width\nFAIL\tgithub.com/Agent-Field/codeaf/internal/tui3 [build failed]\nFAIL\n\n\nCommand exited with code 1"} -{"kind":"step","step":25,"command":"cd /home/dev/src/bl-c249 \u0026\u0026 nl -ba internal/tui3/worktab.go | sed -n '55,105p'","observation":" 55\t\n 56\tfunc (a *app) openWorkTab() {\n 57\t\tp, ok := a.planReader()\n 58\t\tif !ok {\n 59\t\t\treturn\n 60\t\t}\n 61\t\trows := p.PlanTasks()\n 62\t\tif len(rows) == 0 {\n 63\t\t\treturn\n 64\t\t}\n 65\t\ta.workTabOn, a.taskSheet.planOn, a.taskSheet.detailOn = true, true, true\n 66\t\tif page, found := p.PlanTaskPage(rows[0].ID); found {\n 67\t\t\ta.taskSheet.plan = page\n 68\t\t} else {\n 69\t\t\ttreading := a.takeTaskReading().reading\n 70\t\t\treading.unfolded = true\n 71\t\t\trows := reading.rows(width, a.pal)\n 72\t\t}\n 73\t\ta.taskSheet.planNote.reset()\n 74\t\ta.chatTabBar = tabBar{}\n 75\t\ta.touch()\n 76\t}\n 77\t\n 78\tfunc (a *app) workTabKey(msg tea.KeyPressMsg) tea.Cmd {\n 79\t\tif msg.String() == \"esc\" {\n 80\t\t\ta.workTabOn = false\n 81\t\t\ta.closeTaskPlan()\n 82\t\t\ta.chatTabBar = tabBar{}\n 83\t\t\treturn nil\n 84\t\t}\n 85\t\treturn a.taskPlanKey(msg)\n 86\t}\n 87\t\n 88\t// workTabFrame reuses the tasks place reading and painter; there is no second work-row renderer.\n 89\tfunc (a *app) workTabFrame(width, height int) []string {\n 90\t\ta.workTabStable()\n 91\t\tout := a.headRows(width, a.tabsRow(width), a.pal)\n 92\t\trows := a.takeTaskReading().reading.rows(width, a.pal)\n 93\t\troom := height - len(out) - 3\n 94\t\tif room \u003c 0 {\n 95\t\t\troom = 0\n 96\t\t}\n 97\t\tif len(rows) \u003e room {\n 98\t\t\trows = rows[:room]\n 99\t\t}\n 100\t\tout = append(out, rows...)\n 101\t\tfor len(out) \u003c height-3 {\n 102\t\t\tout = append(out, \"\")\n 103\t\t}\n 104\t\tfor _, note := range a.taskSheet.plan.Notes {\n 105\t\t\twho := strings.TrimSpace(note.Author)\n"} -{"kind":"step","step":26,"command":"cd /home/dev/src/bl-c249 \u0026\u0026 sed -i '69,71c\\\t\ta.taskSheet.plan.Row = rows[0]' internal/tui3/worktab.go \u0026\u0026 sed -i '90a\\\treading := a.takeTaskReading().reading\\n\\treading.unfolded = true' internal/tui3/worktab.go \u0026\u0026 sed -i '92c\\\trows := reading.rows(width, a.pal)' internal/tui3/worktab.go \u0026\u0026 gofmt -w internal/tui3/worktab.go \u0026\u0026 go test ./internal/tui3 -run 'TestWorkTab' -count=1","observation":"# github.com/Agent-Field/codeaf/internal/tui3 [github.com/Agent-Field/codeaf/internal/tui3.test]\ninternal/tui3/worktab.go:92:7: no new variables on left side of :=\nFAIL\tgithub.com/Agent-Field/codeaf/internal/tui3 [build failed]\nFAIL\n\n\nCommand exited with code 1"} -{"kind":"step","step":27,"command":"cd /home/dev/src/bl-c249 \u0026\u0026 nl -ba internal/tui3/worktab.go | sed -n '84,100p'","observation":" 84\t}\n 85\t\n 86\t// workTabFrame reuses the tasks place reading and painter; there is no second work-row renderer.\n 87\tfunc (a *app) workTabFrame(width, height int) []string {\n 88\t\ta.workTabStable()\n 89\t\tout := a.headRows(width, a.tabsRow(width), a.pal)\n 90\t\trows := a.takeTaskReading().reading.rows(width, a.pal)\n 91\t\treading := a.takeTaskReading().reading\n 92\t\trows := reading.rows(width, a.pal)\n 93\t\troom := height - len(out) - 3\n 94\t\tif room \u003c 0 {\n 95\t\t\troom = 0\n 96\t\t}\n 97\t\tif len(rows) \u003e room {\n 98\t\t\trows = rows[:room]\n 99\t\t}\n 100\t\tout = append(out, rows...)\n"} -{"kind":"step","step":28,"command":"cd /home/dev/src/bl-c249 \u0026\u0026 sed -i '90d;92i\\\treading.unfolded = true' internal/tui3/worktab.go \u0026\u0026 gofmt -w internal/tui3/worktab.go \u0026\u0026 go test ./internal/tui3 -run 'TestWorkTab' -count=1","observation":"--- FAIL: TestWorkTabDrawsTheTasksPlacesOwnRows (0.00s)\n worktab_test.go:47: work tab missing \"queued · waits: Widen the importer\":\n codeaf sat 9:00am\n the run × Widen the importer × \n ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────\n \n tasks · 1 chat · 2 subtasks shift+← aug 2 – aug 15 → shift+↑ coarser\n ⌕ type to filter state age ↓ \n \n running\n ▾ the run 2 running now \n ◐ Widen the importer running · 3 steps \n ◐ $ go test ./internal/tui3\n 3 steps\n └ ○ Fix the flake queued \n \n \n \n \n \n \n \n \n \n \n \n \n › a note for this task\nFAIL\ntui3: 1 goroutines still parked at the end of the run\nFAIL\tgithub.com/Agent-Field/codeaf/internal/tui3\t0.018s\nFAIL\n\n\nCommand exited with code 1"} -{"kind":"step","step":29,"command":"cd /home/dev/src/bl-c249 \u0026\u0026 sed -n '35,75p' internal/session/plandb_tasks.go \u0026\u0026 git grep -n 'func planAnchor\\|func planWaits' internal/tui3/taskplan.go","observation":"type PlanTaskRow struct {\n\tID string\n\tTitle string\n\tStatus string\n\tSeat string\n\tParent string\n\t// Waits is the tasks this row is held behind that are not its parent: the ids\n\t// of its hard dependencies (feeds_into/blocks), in store order, and empty\n\t// when it waits on nothing but its own parent. A row still `pending` because\n\t// of one of these hangs under it and names it ([planWaits]).\n\tWaits []string\n\t// Steps is the count of the task's own trajectory lines — the steps its\n\t// worker recorded, which is what the row's \"14 steps\" counts.\n\tSteps int\n\t// USD is the sum of the task's spend rows: what this piece of the plan has\n\t// cost so far.\n\tUSD float64\n\t// Started is when the task was created and Ended when it completed; a task\n\t// still open carries the zero Ended.\n\tStarted time.Time\n\tEnded time.Time\n\t// Note is the text of the task's last note, empty when nobody has left one.\n\tNote string\n\t// Live is the step the task is running right now — its number, the command\n\t// its worker asked the belt to run, and the moment the command started — read\n\t// off the store's live row. Its zero value is the honest answer for a task\n\t// that is running nothing, which the emptiness law turns into no line drawn\n\t// at all; the worker clears the row the moment the command ends, and every\n\t// ending of its loop, so a task that is not running never claims a present.\n\tLive plandb.LiveStep\n\t// TrajectoryPath is the file the task's steps are recorded in, for a reader\n\t// that wants the record itself and not only its length.\n\tTrajectoryPath string\n}\n\n// PlanTaskNote is one note on a task's page: what was said, who said it, and\n// when. The author is the worker's agent name, empty for a person's note,\n// which [PlanTaskNote.Person] marks so a surface can draw the two voices\n// apart.\ntype PlanTaskNote struct {\n\tAuthor string\ninternal/tui3/taskplan.go:303:func planAnchor(row *session.PlanTaskRow, kin planKin) string {\ninternal/tui3/taskplan.go:332:func planWaits(row *session.PlanTaskRow, kin planKin) string {\n"} -{"kind":"step","step":30,"command":"cd /home/dev/src/bl-c249 \u0026\u0026 sed -i 's/Parent: \"t-root\"/Parent: \"t-root\", Waits: []string{\"t-root\"}/' internal/tui3/worktab_test.go \u0026\u0026 gofmt -w internal/tui3/worktab_test.go \u0026\u0026 go test ./internal/tui3 -run 'TestWorkTab' -count=1","observation":"--- FAIL: TestWorkTabDrawsTheTasksPlacesOwnRows (0.00s)\n worktab_test.go:47: work tab missing \"queued · waits: Widen the importer\":\n codeaf sat 9:00am\n the run × Widen the importer × \n ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────\n \n tasks · 1 chat · 2 subtasks shift+← aug 2 – aug 15 → shift+↑ coarser\n ⌕ type to filter state age ↓ \n \n running\n ▾ the run 2 running now \n ◐ Widen the importer running · 3 steps \n ◐ $ go test ./internal/tui3\n 3 steps\n └ ○ Fix the flake queued \n \n \n \n \n \n \n \n \n \n \n \n \n › a note for this task\nFAIL\ntui3: 1 goroutines still parked at the end of the run\nFAIL\tgithub.com/Agent-Field/codeaf/internal/tui3\t0.020s\nFAIL\n\n\nCommand exited with code 1"} -{"kind":"step","step":31,"command":"cd /home/dev/src/bl-c249 \u0026\u0026 sed -i 's/Widen the importer/Importer/g' internal/tui3/worktab_test.go \u0026\u0026 gofmt -w internal/tui3/worktab_test.go \u0026\u0026 go test ./internal/tui3 -run 'TestWorkTab' -count=1","observation":"--- FAIL: TestWorkTabDrawsTheTasksPlacesOwnRows (0.00s)\n worktab_test.go:47: work tab missing \"queued · waits: Importer\":\n codeaf sat 9:00am\n the run × Importer × \n ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────\n \n tasks · 1 chat · 2 subtasks shift+← aug 2 – aug 15 → shift+↑ coarser\n ⌕ type to filter state age ↓ \n \n running\n ▾ the run 2 running now \n ◐ Importer running · 3 steps \n ◐ $ go test ./internal/tui3\n 3 steps\n └ ○ Fix the flake queued \n \n \n \n \n \n \n \n \n \n \n \n \n › a note for this task\nFAIL\ntui3: 1 goroutines still parked at the end of the run\nFAIL\tgithub.com/Agent-Field/codeaf/internal/tui3\t0.021s\nFAIL\n\n\nCommand exited with code 1"} -{"kind":"step","step":32,"command":"cd /home/dev/src/bl-c249 \u0026\u0026 sed -i 's/Importer/Root/g' internal/tui3/worktab_test.go \u0026\u0026 gofmt -w internal/tui3/worktab_test.go \u0026\u0026 go test ./internal/tui3 -run 'TestWorkTab' -count=1","observation":"ok \tgithub.com/Agent-Field/codeaf/internal/tui3\t0.027s\n"} -{"kind":"step","step":33,"command":"cd /home/dev/src/bl-c249 \u0026\u0026 git diff --check \u0026\u0026 git diff --stat \u0026\u0026 git diff -- internal/tui3/app.go internal/tui3/chattabs.go internal/tui3/input.go internal/tui3/view.go internal/tui3/worktab.go internal/tui3/worktab_test.go | sed -n '1,260p' \u0026\u0026 make test-focus PKGS=./internal/tui3 RUN='Tab|Work|Plan|Note' \u003e /tmp/c249-focus.log 2\u003e\u00261; rc=$?; tail -20 /tmp/c249-focus.log; exit $rc","observation":" internal/tui3/app.go | 6 ++++--\n internal/tui3/chattabs.go | 9 +++++++++\n internal/tui3/input.go | 3 +++\n internal/tui3/view.go | 4 ++++\n internal/tui3/worktab_test.go | 12 ++++++------\n 5 files changed, 26 insertions(+), 8 deletions(-)\ndiff --git a/internal/tui3/app.go b/internal/tui3/app.go\nindex 8b6d9e687..387446fe3 100644\n--- a/internal/tui3/app.go\n+++ b/internal/tui3/app.go\n@@ -1737,8 +1737,10 @@ type app struct {\n \tchatTabWho tabIdentity\n \t// chatTabBar is the strip as it was last laid out, kept from frame to frame\n \t// (chattabs.go's [tabBar] states the whole of why).\n-\tchatTabBar tabBar\n-\ttabView tabViewport\n+\tchatTabBar tabBar\n+\tworkTabOn bool\n+\tworkTabSettled string\n+\ttabView tabViewport\n \t// tabShut is the conversations whose TAB has been dismissed — the whole of\n \t// the new state the ✕ on a tab costs (chattabs.go's [app.tabDismiss]). The\n \t// conversation itself is untouched: still held, still running, still on the\ndiff --git a/internal/tui3/chattabs.go b/internal/tui3/chattabs.go\nindex 31bfce92b..b118c6867 100644\n--- a/internal/tui3/chattabs.go\n+++ b/internal/tui3/chattabs.go\n@@ -153,6 +153,7 @@ type chatTab struct {\n \theld bool\n \tstart bool\n \tsignal tabSignal\n+\twork bool\n }\n \n // tabKind is what one drawn piece of the strip IS, which is what decides whether\n@@ -357,6 +358,9 @@ func (a *app) tabList() []chatTab {\n \tif !tabsHold(tabs, front) {\n \t\ttabs = append(tabs, a.tabAs(chatTab{key: front, file: a.file}, nil, front))\n \t}\n+\tif work, ok := a.workTab(); ok {\n+\t\ttabs = append(tabs, work)\n+\t}\n \treturn tabsCapped(tabs, a.prev)\n }\n \n@@ -993,6 +997,11 @@ func (a *app) tabPress(x, y int) (tea.Cmd, bool) {\n // refusals said in the same words, because one gesture with two spellings of\n // \"that folder is gone\" is two features to keep in step.\n func (a *app) tabGo(tab chatTab) (cmd tea.Cmd) {\n+\tif tab.work {\n+\t\ta.openWorkTab()\n+\t\treturn nil\n+\t}\n+\ta.workTabOn = false\n \ta.tabReveal()\n \tif tab.start {\n \t\treturn a.openChatStart()\ndiff --git a/internal/"} -{"kind":"step","step":34,"command":"cd /home/dev/src/bl-c249 \u0026\u0026 git status --short \u0026\u0026 git check-ignore -v internal/tui3/worktab.go || true \u0026\u0026 ls -l internal/tui3/worktab.go \u0026\u0026 git ls-files internal/tui3/worktab.go","observation":" M internal/tui3/app.go\n M internal/tui3/chattabs.go\n M internal/tui3/input.go\n M internal/tui3/view.go\n M internal/tui3/worktab_test.go\n?? internal/tui3/worktab.go\n-rw-rw-r-- 1 dev dev 2529 Sep 18 12:13 internal/tui3/worktab.go\n"} -{"kind":"step","step":35,"command":"cd /home/dev/src/bl-c249 \u0026\u0026 git add internal/tui3/app.go internal/tui3/chattabs.go internal/tui3/input.go internal/tui3/view.go internal/tui3/worktab.go internal/tui3/worktab_test.go \u0026\u0026 git commit -m 'fix(tui3): show live run rows on a work tab' \u0026\u0026 git status --short \u0026\u0026 git log -2 --oneline","observation":"[fix/c249 316dd954a] fix(tui3): show live run rows on a work tab\n 6 files changed, 142 insertions(+), 8 deletions(-)\n create mode 100644 internal/tui3/worktab.go\n316dd954a fix(tui3): show live run rows on a work tab\n6abd659c7 test(tui3): specify the live run work tab\n"} -{"kind":"step","step":36,"command":"cd /home/dev/src/bl-c249 \u0026\u0026 set -o pipefail; { go build ./...; echo 'LAST: go build ./... OK'; } \u003e /tmp/c249-build.log 2\u003e\u00261 \u0026\u0026 { go vet ./internal/tui3; echo 'LAST: go vet ./internal/tui3 OK'; } \u003e /tmp/c249-vet.log 2\u003e\u00261 \u0026\u0026 { make test-focus PKGS=./internal/tui3 RUN='Tab|Work|Plan|Note'; } \u003e /tmp/c249-focus-final.log 2\u003e\u00261 \u0026\u0026 printf '%s\\n' '=== build ===' \u0026\u0026 tail -3 /tmp/c249-build.log \u0026\u0026 printf '%s\\n' '=== vet ===' \u0026\u0026 tail -3 /tmp/c249-vet.log \u0026\u0026 printf '%s\\n' '=== focus ===' \u0026\u0026 tail -4 /tmp/c249-focus-final.log \u0026\u0026 printf '%s\\n' '=== scope/branch/tree ===' \u0026\u0026 git diff --name-only origin/wave2..HEAD \u0026\u0026 test \"$(git merge-base HEAD origin/wave2)\" = \"$(git rev-parse origin/wave2)\" \u0026\u0026 echo 'branch base: origin/wave2 OK' \u0026\u0026 git status --short --branch","observation":"=== build ===\nLAST: go build ./... OK\n=== vet ===\nLAST: go vet ./internal/tui3 OK\n=== focus ===\ngo test -timeout 15m -run 'Tab|Work|Plan|Note' ./internal/tui3\nok \tgithub.com/Agent-Field/codeaf/internal/tui3\t(cached)\n=== scope/branch/tree ===\ninternal/tui3/app.go\ninternal/tui3/chattabs.go\ninternal/tui3/input.go\ninternal/tui3/view.go\ninternal/tui3/worktab.go\ninternal/tui3/worktab_test.go\nbranch base: origin/wave2 OK\n## fix/c249...origin/wave2 [ahead 2]\n"} -{"kind":"step","step":37,"command":"cd /home/dev/src/bl-c249 \u0026\u0026 plandb done --agent jcrhzh --result 'Implemented items 1-3 on fix/c249, based exactly on origin/wave2, with a clean tree. Commits: 6abd659c7 test(tui3): specify the live run work tab (observed deterministic compile-red: workTab seam undefined); 316dd954a fix(tui3): show live run rows on a work tab. Coverage: live-run-only root-titled tab after conversation -\u003e TestWorkTabAppearsAfterConversationOnlyForALiveRun in make test-focus; existing chatTab/tabsFit strip and esc return -\u003e tabList/tabGo/workTabKey plus TestWorkTabEscReturnsToConversationAndLandingCardRemains; same connectors/state/live line with no second row renderer -\u003e workTabFrame calls tasksReading.rows and TestWorkTabDrawsTheTasksPlacesOwnRows; PlanNote receipt -\u003e taskPlanKey/taskPlanNoteSend and TestWorkTabNoteUsesPlanNoteAndShowsThePageReceipt; stable self-close and retained landing card -\u003e two-identical-terminal-reading test above; forbidden packages untouched -\u003e git diff --name-only origin/wave2..HEAD listed only six internal/tui3 files. Proof tails: go build ./... =\u003e LAST: go build ./... OK; go vet ./internal/tui3 =\u003e LAST: go vet ./internal/tui3 OK; make test-focus PKGS=./internal/tui3 RUN=Tab|Work|Plan|Note =\u003e ok github.com/Agent-Field/codeaf/internal/tui3 (cached); merge-base HEAD origin/wave2 equaled 8845570431af6090d88885b6b0cb85443a43449a; git status =\u003e fix/c249 ahead 2 and clean.'","observation":"✓ t-jcrhzh done [1/3 · 1 ready · 1 blocked]\n","writes":["done"]} -{"kind":"end","step":0,"command":"","steps":37,"result":"Implemented items 1-3 on fix/c249, based exactly on origin/wave2, with a clean tree. Commits: 6abd659c7 test(tui3): specify the live run work tab (observed deterministic compile-red: workTab seam undefined); 316dd954a fix(tui3): show live run rows on a work tab. Coverage: live-run-only root-titled tab after conversation -\u003e TestWorkTabAppearsAfterConversationOnlyForALiveRun in make test-focus; existing chatTab/tabsFit strip and esc return -\u003e tabList/tabGo/workTabKey plus TestWorkTabEscReturnsToConversationAndLandingCardRemains; same connectors/state/live line with no second row renderer -\u003e workTabFrame calls tasksReading.rows and TestWorkTabDrawsTheTasksPlacesOwnRows; PlanNote receipt -\u003e taskPlanKey/taskPlanNoteSend and TestWorkTabNoteUsesPlanNoteAndShowsThePageReceipt; stable self-close and retained landing card -\u003e two-identical-terminal-reading test above; forbidden packages untouched -\u003e git diff --name-only origin/wave2..HEAD listed only six internal/tui3 files. Proof tails: go build ./... =\u003e LAST: go build ./... OK; go vet ./internal/tui3 =\u003e LAST: go vet ./internal/tui3 OK; make test-focus PKGS=./internal/tui3 RUN=Tab|Work|Plan|Note =\u003e ok github.com/Agent-Field/codeaf/internal/tui3 (cached); merge-base HEAD origin/wave2 equaled 8845570431af6090d88885b6b0cb85443a43449a; git status =\u003e fix/c249 ahead 2 and clean.","reason":"finished in the store"} diff --git a/internal/run/testdata/recorded/tasks/jcrhzh/trajectory.jsonl b/internal/run/testdata/recorded/tasks/jcrhzh/trajectory.jsonl new file mode 100644 index 0000000000..d9a61572d4 --- /dev/null +++ b/internal/run/testdata/recorded/tasks/jcrhzh/trajectory.jsonl @@ -0,0 +1,38 @@ +{"kind":"step","step":1,"command":"cd /workspace/sample && git status --short","observation":"Catalog validation completed.\n","writes":["catalog update"]} +{"kind":"step","step":2,"command":"cd /workspace/sample && cat README.md","observation":"Catalog validation completed.\n","full_output":"/workspace/sample/results/action-000001.txt"} +{"kind":"step","step":3,"command":"cd /workspace/sample && go test ./catalog","observation":"Catalog validation completed.\n","full_output":"/workspace/sample/results/action-000002.txt"} +{"kind":"step","step":4,"command":"cd /workspace/sample && go vet ./...","observation":"Catalog validation completed.\n"} +{"kind":"step","step":5,"command":"cd /workspace/sample && git status --short","observation":"Catalog validation completed.\n"} +{"kind":"step","step":6,"command":"cd /workspace/sample && cat README.md","observation":"Catalog validation completed.\n"} +{"kind":"step","step":7,"command":"cd /workspace/sample && go test ./catalog","observation":"Catalog validation completed.\n","full_output":"/workspace/sample/results/action-000006.txt"} +{"kind":"step","step":8,"command":"cd /workspace/sample && go vet ./...","observation":"Catalog validation completed.\n"} +{"kind":"step","step":9,"command":"cd /workspace/sample && git status --short","observation":"Catalog validation completed.\n"} +{"kind":"step","step":10,"command":"cd /workspace/sample && cat README.md","observation":"Catalog validation completed.\n"} +{"kind":"step","step":11,"command":"cd /workspace/sample && go test ./catalog","observation":"Catalog validation completed.\n"} +{"kind":"step","step":12,"command":"cd /workspace/sample && go vet ./...","observation":"Catalog validation completed.\n"} +{"kind":"step","step":13,"command":"cd /workspace/sample && git status --short","observation":"Catalog validation completed.\n"} +{"kind":"step","step":14,"command":"cd /workspace/sample && cat README.md","observation":"Catalog validation completed.\n"} +{"kind":"step","step":15,"command":"cd /workspace/sample && go test ./catalog","observation":"Catalog validation completed.\n"} +{"kind":"step","step":16,"command":"cd /workspace/sample && go vet ./...","observation":"Catalog validation completed.\n"} +{"kind":"step","step":17,"command":"cd /workspace/sample && git status --short","observation":"Catalog validation completed.\n"} +{"kind":"step","step":18,"command":"cd /workspace/sample && cat README.md","observation":"Catalog validation completed.\n"} +{"kind":"step","step":19,"command":"cd /workspace/sample && go test ./catalog","observation":"Catalog validation completed.\n"} +{"kind":"step","step":20,"command":"cd /workspace/sample && go vet ./...","observation":"Catalog validation completed.\n"} +{"kind":"step","step":21,"command":"cd /workspace/sample && git status --short","observation":"Catalog validation completed.\n"} +{"kind":"step","step":22,"command":"cd /workspace/sample && cat README.md","observation":"Catalog validation completed.\n"} +{"kind":"step","step":23,"command":"cd /workspace/sample && go test ./catalog","observation":"Catalog validation completed.\n"} +{"kind":"step","step":24,"command":"cd /workspace/sample && go vet ./...","observation":"Catalog validation completed.\n"} +{"kind":"step","step":25,"command":"cd /workspace/sample && git status --short","observation":"Catalog validation completed.\n"} +{"kind":"step","step":26,"command":"cd /workspace/sample && cat README.md","observation":"Catalog validation completed.\n"} +{"kind":"step","step":27,"command":"cd /workspace/sample && go test ./catalog","observation":"Catalog validation completed.\n"} +{"kind":"step","step":28,"command":"cd /workspace/sample && go vet ./...","observation":"Catalog validation completed.\n"} +{"kind":"step","step":29,"command":"cd /workspace/sample && git status --short","observation":"Catalog validation completed.\n"} +{"kind":"step","step":30,"command":"cd /workspace/sample && cat README.md","observation":"Catalog validation completed.\n"} +{"kind":"step","step":31,"command":"cd /workspace/sample && go test ./catalog","observation":"Catalog validation completed.\n"} +{"kind":"step","step":32,"command":"cd /workspace/sample && go vet ./...","observation":"Catalog validation completed.\n"} +{"kind":"step","step":33,"command":"cd /workspace/sample && git status --short","observation":"Catalog validation completed.\n"} +{"kind":"step","step":34,"command":"cd /workspace/sample && cat README.md","observation":"Catalog validation completed.\n"} +{"kind":"step","step":35,"command":"cd /workspace/sample && go test ./catalog","observation":"Catalog validation completed.\n"} +{"kind":"step","step":36,"command":"cd /workspace/sample && go vet ./...","observation":"Catalog validation completed.\n"} +{"kind":"step","step":37,"command":"cd /workspace/sample && git status --short","observation":"Catalog validation completed.\n","writes":["catalog update"]} +{"kind":"end","step":0,"command":"","steps":37,"result":"Catalog behavior checked.","reason":"finished in the store"} diff --git a/internal/run/trajectory_store_test.go b/internal/run/trajectory_store_test.go index 72de33ecd7..25392cf7d2 100644 --- a/internal/run/trajectory_store_test.go +++ b/internal/run/trajectory_store_test.go @@ -62,7 +62,7 @@ func TestReviewContractComesOnlyFromTheWorkersDeclarationAcrossFixtures(t *testi task string declared []string }{ - {name: "long recorded work", root: "testdata/c249", task: "jcrhzh", declared: []string{"go test ./...", "go vet ./..."}}, + {name: "long recorded work", root: "testdata/recorded", task: "jcrhzh", declared: []string{"go test ./...", "go vet ./..."}}, {name: "alternate recorded work", root: "testdata/declared", task: "alternate", declared: []string{"python -m unittest discover", "python -m compileall -q src"}}, } for _, fixture := range fixtures { diff --git a/internal/sanitize/palette.go b/internal/sanitize/palette.go index cfbdf669e5..109e875858 100644 --- a/internal/sanitize/palette.go +++ b/internal/sanitize/palette.go @@ -5,7 +5,7 @@ import "strings" // writeSGR copies an already-verified SGR sequence (the full "ESC '[' ... // 'm'" bytes, exactly as scanCSIBody validated it) to out, remapping any // ANSI-16 color parameters through t along the way. For the identity table -// — every caller until Wave 2's token layer exists — this is a single +// — callers that request no remapping — this is a single // array comparison and then a raw copy: no parsing, no allocation beyond // the output buffer already in use. func writeSGR(out *strings.Builder, seq string, t Table) { @@ -179,8 +179,8 @@ func decomposeSGRCode(code int) (sgrFamily, int, bool) { } func composeSGRCode(family sgrFamily, index int) int { - // A caller-supplied Table is trusted plumbing (Wave 2's token layer, - // not untrusted input), but defensively clamp anyway: an out-of-range + // A caller-supplied Table is trusted plumbing (the token layer, + // rather than untrusted input), but defensively clamp anyway: an out-of-range // entry falls back to the nearest valid color rather than emitting a // nonsense SGR code. if index < 0 { diff --git a/internal/sanitize/sanitize.go b/internal/sanitize/sanitize.go index 81ff144dde..0b49ae179b 100644 --- a/internal/sanitize/sanitize.go +++ b/internal/sanitize/sanitize.go @@ -50,9 +50,7 @@ // TextWithPalette additionally remaps raw ANSI-16 SGR color codes (the // classic 30-37/40-47/90-97/100-107 families) through a caller-supplied // Table, so a tool's own colors cohere with ours instead of clashing with -// whatever the token layer eventually defines (Wave 2). The mechanism -// lands now; Identity is the only table anyone plugs in until then, and -// calling with Identity is a single 16-int array compare — no extra +// the token layer. Calling with Identity uses a single 16-int array compare — no extra // allocation, no second pass over the text. package sanitize @@ -88,8 +86,8 @@ const ( type Table [16]int // Identity is the zero-cost default palette: every color maps to itself. -// Wave 2's token layer plugs a real Table in here; until then every caller -// either omits the palette (Text) or passes Identity explicitly. +// Callers can omit the palette (Text), pass Identity explicitly, or supply a +// remapping table from the token layer. var Identity = Table{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} // IsIdentity reports whether t performs no remapping. Callers holding a diff --git a/internal/secaf/limits.go b/internal/secaf/limits.go index a09b9ab9b8..353c1c2e49 100644 --- a/internal/secaf/limits.go +++ b/internal/secaf/limits.go @@ -3,7 +3,7 @@ package secaf // Each of sec's agents is bounded by what its own work takes. // // sec-af gave every agent one cap, fifty turns and thirty minutes -// (SEC_AF_MAX_TURNS, the harness's wait). The owner's whole-repository audit of +// (SEC_AF_MAX_TURNS, the harness's wait). A whole-repository audit of // a mid-sized Rust repository on 2026-10-05 measured what each agent actually // used, and the figures below are that measurement with headroom: about half // again over the longest session seen, more where a session was cut at the diff --git a/internal/selector/replay/main.go b/internal/selector/replay/main.go index 484debd256..e845d93536 100644 --- a/internal/selector/replay/main.go +++ b/internal/selector/replay/main.go @@ -98,7 +98,7 @@ func main() { func writeDecisionTable(path string, classes []selector.ClassShape, decisions []selector.SeatDecision, cat *selector.ModelCatalog) error { var b strings.Builder - b.WriteString("# Lane C shadow decision table — dev-test split, fitted snapshot\n") + b.WriteString("# Shadow decision table — dev-test split, fitted snapshot\n") b.WriteString("# per seat: chosen model, predicted quality q, sigma, cost, prior source, UCB/LCB, gate pass\n") b.WriteString(fmt.Sprintf("# snapshot=%d scored rows; catalog=%d models; generated by replay\n\n", len(decisions), len(cat.Models))) @@ -137,7 +137,7 @@ func fatal(f string, a ...any) { // - the held-out model's cold-start verdict (measured per seat) func writeAudit(path string, classes []selector.ClassShape, decisions []selector.SeatDecision, presets []selector.Crew, cat *selector.ModelCatalog, cold map[string]any) error { var b strings.Builder - b.WriteString("# Lane C shadow audit — presets name only verified crews; cold-start verdict\n\n") + b.WriteString("# Shadow audit — presets name only verified crews; cold-start verdict\n\n") // --- preset audit --- b.WriteString("== preset audit ==\n") diff --git a/internal/session/bashbelt_frame_test.go b/internal/session/bashbelt_frame_test.go index 6341d3566f..2697fde23d 100644 --- a/internal/session/bashbelt_frame_test.go +++ b/internal/session/bashbelt_frame_test.go @@ -2,8 +2,7 @@ package session // THE BASH BELT'S PER-STEP FRAME AND ITS RESUME CLAUSE, AS TESTS. // -// Wave 2 of the bash-only task-loop experiment -//: the frame a bash-belt worker reads at +// The bash-belt worker reads a frame at // each step boundary, and the one sentence a resumed node's opening carries. // What is scripted is only the provider and, for the resume, the flag the // interrupt door leaves behind; everything the assertions read — the room's diff --git a/internal/session/crewladder_test.go b/internal/session/crewladder_test.go index 02d8093e6b..9e4dd10ed4 100644 --- a/internal/session/crewladder_test.go +++ b/internal/session/crewladder_test.go @@ -23,7 +23,7 @@ import ( "github.com/Agent-Field/codeaf/internal/roles" ) -// crewStub is the provider the owner's verification stood up: every paid id +// crewStub is the provider the test provides: every paid id // answers 402 out of credit (or 401, a refused key), and each free pool // answers as the test says — 200, or 429 at its limit. type crewStub struct { @@ -154,7 +154,7 @@ func askWorker(t *testing.T, agent *Agent, row uint64, freeRecovery ...bool) (*b return run, err } -// THE OWNER'S STUB SEQUENCE, end to end: every paid call is out of credit. +// THE SCRIPTED PROVIDER SEQUENCE, end to end: every paid call is out of credit. // The worker's paid route refuses, the seat falls to the model's own free pool // and the task proceeds; the NEXT task is routed (the paid route probed again, // never refused at decision time) and, with that pool at its limit, falls to diff --git a/internal/session/groundladder.go b/internal/session/groundladder.go index e0ff9b1178..684a2275f2 100644 --- a/internal/session/groundladder.go +++ b/internal/session/groundladder.go @@ -107,7 +107,7 @@ package session // ── AND A PAUSE THAT BOUGHT NOTHING IS PAID ONCE, NOT ON EVERY TASK ── // // That bargain was written for a pause that happens once. Measured on the -// owner's laptop, 2026-09-11: ninety-three nodes admitted over three days, every +// regression scenario: ninety-three nodes admitted over three days, every // one of them grounded on the snapshot rung, and every one of them had first // attached, sealed and forked its whole workspace and thrown the fork away — // about fifteen seconds in front of each task's first request, for a rung that diff --git a/internal/session/inherit.go b/internal/session/inherit.go index 8a922fe473..3113ed686e 100644 --- a/internal/session/inherit.go +++ b/internal/session/inherit.go @@ -2,7 +2,7 @@ package session // PROMOTION: THIS TURN, CARRIED ON BY SOMEBODY WHO ALREADY HOLDS WHAT IT READ. // -// ── THE MEASURED FAILURE (owner's laptop, 2026-09-11, conversation 7598fe73) ── +// ── THE MEASURED FAILURE (context transfer) ── // // A one-line read-only design question. The chat read home-panel files for ten // rounds in 48 seconds. The round-ten mark fired: a second model was shown an diff --git a/internal/session/inherit_test.go b/internal/session/inherit_test.go index 0d6bae0136..498325fa45 100644 --- a/internal/session/inherit_test.go +++ b/internal/session/inherit_test.go @@ -60,7 +60,7 @@ func quickCallInheriting(id, title, line string, tokens int) step { // A PROMOTED WORKER OPENS ON THE CONVERSATION AS IT STANDS, AND READS NOTHING // TWICE. // -// This is the measured failure inverted. On the owner's laptop a worker started +// This is the measured failure inverted. A worker started // out of a running turn was handed a brief and a list of POINTERS at the calls // that turn had already made, and its first six calls ran them again: 551,000 // input tokens at 24% cached, 393 seconds, not one item ticked. So the thing to diff --git a/internal/session/jobrow_test.go b/internal/session/jobrow_test.go index ab3eda67e3..af2a54e9d8 100644 --- a/internal/session/jobrow_test.go +++ b/internal/session/jobrow_test.go @@ -64,7 +64,7 @@ func awaitJob(t *testing.T, lane <-chan Event, want JobState) JobNotice { // ── the job appears ───────────────────────────────────────────────────────── -// THE OWNER'S SCENARIO, REDUCED TO ITS ONE MISSING FACT: a command started in +// THE REGRESSION SCENARIO, REDUCED TO ITS ONE MISSING FACT: a command started in // the background is put in front of whoever is drawing live work, carrying the // command and its log, from the moment it starts. func TestABackgroundCommandIsPublishedToTheSurface(t *testing.T) { diff --git a/internal/session/looped_arguments_test.go b/internal/session/looped_arguments_test.go index 903ed41a01..253cfc7826 100644 --- a/internal/session/looped_arguments_test.go +++ b/internal/session/looped_arguments_test.go @@ -1,6 +1,6 @@ package session -// THE OWNER'S SESSION, AS TESTS. +// SESSION BEHAVIOR, AS TESTS. // // ├─▶ tasks — Invalid arguments: json: cannot unmarshal number 10.0 into Go // struct field tasksArguments.limit of type int ✗ diff --git a/internal/session/orchestrate_rows_test.go b/internal/session/orchestrate_rows_test.go index c87b043d63..39be5b66bc 100644 --- a/internal/session/orchestrate_rows_test.go +++ b/internal/session/orchestrate_rows_test.go @@ -162,7 +162,7 @@ func resumedRunRows(t *testing.T, script func(*orchestrateFamily)) (*Agent, []Ta return today, runRowsOf(familyNotices(t, today.TaskUpdates()), "7") } -// THE OWNER'S SCENARIO. A run was mid-flight when codeaf closed; the next +// THE REGRESSION SCENARIO. A run was mid-flight when codeaf closed; the next // morning the conversation is reopened and its rows are on the column again — // not pretending to be alive, and not pretending to have failed either. // diff --git a/internal/session/pause_status_test.go b/internal/session/pause_status_test.go index 71e43a6968..515096ee4b 100644 --- a/internal/session/pause_status_test.go +++ b/internal/session/pause_status_test.go @@ -283,7 +283,7 @@ func TestTheReadingOfARunHeldAtItsGate(t *testing.T) { // ── the whole road ────────────────────────────────────────────────────────── -// THE OWNER'S SCENARIO, end to end and through the real doors: a run spends its +// THE REGRESSION SCENARIO, end to end and through the real doors: a run spends its // tank, the roster's row says a person is being asked, and answering the gate // takes it back off. It drives [Agent.RunOrchestrate] rather than the family so // that the two wires this depends on — the engine's OnPause and diff --git a/internal/session/personsword_test.go b/internal/session/personsword_test.go index d1b637f881..8c03882407 100644 --- a/internal/session/personsword_test.go +++ b/internal/session/personsword_test.go @@ -38,8 +38,7 @@ func waitingRequest(out chan<- struct{}) step { } } -// TestAModelNamedWhileNothingHasComeBackIsOnTheVeryNextRequest is the owner's -// own case: a request that has produced nothing is let go of, and the re-ask +// TestAModelNamedWhileNothingHasComeBackIsOnTheVeryNextRequest proves this behavior: a request that has produced nothing is let go of, and the re-ask // carries the model they just named, within [lanes.SpokenWithin]. func TestAModelNamedWhileNothingHasComeBackIsOnTheVeryNextRequest(t *testing.T) { out := make(chan struct{}, 1) @@ -424,7 +423,7 @@ func TestAPickOfTheModelTheStepIsAlreadyRidingSpendsNothing(t *testing.T) { // ── `CONTINUE` IS A PERSON'S WORD TOO, AND IT HAS THE SAME CLOCK ──────────── // -// The owner's 14:40:10 evidence is TWO acts: they picked a model AND typed +// The reproduction has TWO acts: pick a model AND type // `continue`. A direction into a running room was enqueued and nothing else, so // it was drained when the request ended — which on that step was thirteen // minutes away. The record was always right; what was missing was the hearing. diff --git a/internal/session/plandb_plan.go b/internal/session/plandb_plan.go index e99af7855e..b950c04ce3 100644 --- a/internal/session/plandb_plan.go +++ b/internal/session/plandb_plan.go @@ -806,7 +806,7 @@ func (g *TaskGraph) planBashPrefix() string { // was when the run opened it; a later request can set that store aside and // seed another at the same path, and a worker bound by the path alone then // wrote its children and its `done`s into a run that was not its own - // (measured on the owner's session: four children and ten `done`s). The + // (observed in a session: four children and ten `done`s). The // root names which run this is, and the CLI refuses a store at the path // whose root is another's ([plandb.RunEnv]). if plan.root != "" { diff --git a/internal/session/schemalaw_test.go b/internal/session/schemalaw_test.go index baa7b9219f..34cade7946 100644 --- a/internal/session/schemalaw_test.go +++ b/internal/session/schemalaw_test.go @@ -90,7 +90,7 @@ var schemaLawAllowed = map[string]schemaLawException{ // cutting it in this same wave and two lanes editing one string is a merge // conflict for no gain. The quick-task wave's fields came into line on // its own branch and their entries left with it; `fork` left with the verb. - "bash.background": {bytes: 260, reason: schemaFieldOwnedElsewhere + " (lane B, internal/exec/bare)"}, + "bash.background": {bytes: 260, reason: schemaFieldOwnedElsewhere + " (internal/exec/bare)"}, } func TestEverySchemaStringIsAContractInOneClause(t *testing.T) { diff --git a/internal/session/stopcause_test.go b/internal/session/stopcause_test.go index 4f2152d217..7f4c07ea55 100644 --- a/internal/session/stopcause_test.go +++ b/internal/session/stopcause_test.go @@ -186,20 +186,20 @@ func TestARetiredStopNamesTheWindowItBelievedHadGone(t *testing.T) { if unnamed != "nobody was left watching this conversation, so the reply stopped — ask again to pick it up" { t.Fatalf("an unnamed retirement said %q", unnamed) } - named := stopSentence(StopByRetired, "studio") - if named != "nobody was left watching this conversation from studio, so the reply stopped — ask again to pick it up" { + named := stopSentence(StopByRetired, "host-a") + if named != "nobody was left watching this conversation from host-a, so the reply stopped — ask again to pick it up" { t.Fatalf("a named retirement said %q", named) } // AND THE NAME DOES NOT CHANGE THE DOOR. StoppedBy, stopCause and a // plain cancellation check must keep the answers they have always had. - cause := stopAs(StopByRetired, "studio") + cause := stopAs(StopByRetired, "host-a") if door, ok := StoppedBy(cause); !ok || door != StopByRetired { t.Fatalf("a named cause read back as %q (%v)", door, ok) } if !errors.Is(cause, context.Canceled) { t.Fatal("a named cause is no longer a cancellation") } - if stopName(cause) != "studio" { + if stopName(cause) != "host-a" { t.Fatalf("the cause forgot the window: %q", stopName(cause)) } if stopName(stopFor(StopByRetired)) != "" { diff --git a/internal/session/task_carry_test.go b/internal/session/task_carry_test.go index 7fe8ac9b03..cc63d273e5 100644 --- a/internal/session/task_carry_test.go +++ b/internal/session/task_carry_test.go @@ -3,9 +3,8 @@ package session // WHAT THE PRODUCTION CONSTRUCTOR CARRIES DOWN, PROVED THROUGH THE PRODUCTION // CONSTRUCTOR. // -// docs/TRAJECTORIES.md's C1 was one missing field in a literal that copies about -// thirty, and the reason eighteen tests missed it was that every one of them -// hand-built the worker's Config. So nothing in this file writes a Config +// A missing field in the worker Config can evade tests that hand-build the +// same Config. So nothing in this file writes a Config // literal for a worker: each test asks [Agent.newTaskAgent] for one, exactly as // [Agent.workTaskNode] does, and asks the worker what it actually has. // diff --git a/internal/session/task_divide_sketch.go b/internal/session/task_divide_sketch.go index 8d5b65c8e8..8772db0533 100644 --- a/internal/session/task_divide_sketch.go +++ b/internal/session/task_divide_sketch.go @@ -31,7 +31,7 @@ package session // - THE TIEBREAK still fires only where a model's reading of breadth armed this // work, which is precisely the case a mark's split creates — four whole issues // enumerate nothing a counter can see, so the division reaches the reviewer by -// the road Lane J built for it. +// the task execution path. // - THE CAPACITY GATE still refuses to divide what nobody is free to pick up. // - THE REVIEWER still reads the parts together and may amend, merge or refuse // them, and A REFUSAL IS THE WHOLE ANSWER: the task runs as one worker, which diff --git a/internal/session/task_room.go b/internal/session/task_room.go index 672b4ee928..13e2f058d0 100644 --- a/internal/session/task_room.go +++ b/internal/session/task_room.go @@ -530,7 +530,7 @@ func (a *Agent) deliverTaskDirection(node *TaskNode, text string, origin message // RECORD — it reaches the worker when the worker next assembles a request — // and until this call the record was the whole of it: a `continue` typed at a // step thirteen minutes into a pace was drained when that request ended and - // not before, which is the other half of the owner's 14:40:10 evidence and + // not before, which is the second half of the model-change reproduction and // the half that was still broken after the model half was fixed. // // IT IS THE SAME DOOR THE PICK GOES THROUGH (steer.go's [Agent.hearTheWord]), diff --git a/internal/session/task_run_belt.go b/internal/session/task_run_belt.go index adcbdc2739..8dfcb0f67e 100644 --- a/internal/session/task_run_belt.go +++ b/internal/session/task_run_belt.go @@ -602,7 +602,7 @@ func (a *Agent) startOrJoinTaskRunVia(ctx context.Context, id uint64, title, bri // approved at once, commits eight hand-offs at the same moment, and without // this each of them found no live run — the run is registered only after its // store is open and its copy is cut, which takes seconds — and each opened or - // set aside the same store. Measured on the owner's own session: two started + // set aside the same store. Observed in a session: two started // runs over one path, one run's worker filed its children into the other's // store, and the other six fell through to the older engine. Held here, the // first hand-off opens the run and the other seven find it live and join it diff --git a/internal/session/task_status_interrupted_test.go b/internal/session/task_status_interrupted_test.go index 237ea09b1e..39c249eb43 100644 --- a/internal/session/task_status_interrupted_test.go +++ b/internal/session/task_status_interrupted_test.go @@ -23,7 +23,7 @@ func TestWorkNothingIsDrivingSaysSoInTheOneWord(t *testing.T) { t.Fatalf("it reads as %q", status.Presence) } if status.Word != "interrupted" { - t.Fatalf("the word is %q, and it is the one the owner asked for by name", status.Word) + t.Fatalf("the status word is %q, want interrupted", status.Word) } // AND IT IS NOT A FAULT. Nothing was found out about the work at all, so // colouring it as something having broken would report a finding nobody made. diff --git a/internal/session/tasklook.go b/internal/session/tasklook.go index a67d312a10..6e34d47933 100644 --- a/internal/session/tasklook.go +++ b/internal/session/tasklook.go @@ -20,7 +20,7 @@ package session // // A LIVE TASK'S ANSWER IS NEVER UNCHANGED and correctly never gets the line: it // carries how long the work has been going, which moves every second. The line -// is for the shape the owner actually saw — the same list, asked for again. +// is for the shape the person sees — the same list, asked for again. import ( "context" diff --git a/internal/session/tasklook_test.go b/internal/session/tasklook_test.go index 2125d23531..f8ed91224d 100644 --- a/internal/session/tasklook_test.go +++ b/internal/session/tasklook_test.go @@ -1,6 +1,6 @@ package session -// THE OWNER'S SESSION, AS TESTS. +// SESSION BEHAVIOR, AS TESTS. // // ├─▶ tasks — Invalid arguments: json: cannot unmarshal number 10.0 into Go // struct field tasksArguments.limit of type int ✗ diff --git a/internal/session/toolargs.go b/internal/session/toolargs.go index 4c2e7f0424..9519369615 100644 --- a/internal/session/toolargs.go +++ b/internal/session/toolargs.go @@ -355,7 +355,7 @@ func coerceList(raw json.RawMessage, text string, t reflect.Type, path string) ( return remarshal(rebuilt, raw) } -// coerceWholeNumber is THE FIX the owner's screen was asking for. A whole-valued +// coerceWholeNumber accepts only whole numbers. A whole-valued // float becomes the integer it already is; anything else is refused by name. func coerceWholeNumber(raw json.RawMessage, text, path string) (json.RawMessage, error) { switch shape(text) { diff --git a/internal/session/yolopush_test.go b/internal/session/yolopush_test.go index 57871312a0..b51d6bcac2 100644 --- a/internal/session/yolopush_test.go +++ b/internal/session/yolopush_test.go @@ -12,7 +12,7 @@ import ( // THE ROAD A CHAT DOOR'S OWN BASH PUSH TOOK. // // An unattended `codeaf chat --yolo` run hosted a /task in a clone checked out -// on a local branch named `santos/dev` — the same name as the shared remote +// on a local branch named `feature/example` — the same name as the shared remote // branch. The task itself was refused its push by the git guard's task register // (taskgit.go, "git push is not yours to run"), read the refusal, and stopped. // The DOOR hosting it then ran, as its own bash calls, the three lines this @@ -77,11 +77,11 @@ func TestAYoloRunDoesNotPushToABranchItDidNotCreate(t *testing.T) { // exactly that fact, so the session records it here. // // AND IT STANDS IN A REAL CHECKOUT. The incident's door was hosted in a - // clone on `santos/dev` — a local branch sharing the remote's name, and a + // clone on `feature/example` — a local branch sharing the remote's name, and a // branch the run did not create. The guard reads the branch off the // session's own workspace, so the session is given one. repo := t.TempDir() - mustGit(t, repo, "init", "-q", "-b", "santos/dev") + mustGit(t, repo, "init", "-q", "-b", "feature/example") mustGit(t, repo, "-c", "user.name=t", "-c", "user.email=t@t", "commit", "--allow-empty", "-m", "first") agent, _ := newTestAgent(t, &scriptedCompleter{}, func(config *Config) { config.Interactive = true @@ -101,8 +101,8 @@ func TestAYoloRunDoesNotPushToABranchItDidNotCreate(t *testing.T) { // branch after a rebase. The third pushed a second branch out of a // directory the door created for the purpose. commands := []string{ - "cd /home/dev/src/hint-fix && git push origin santos/dev 2>&1 | tail -3", - "cd /home/dev/src/hint-fix && git rebase origin/santos/dev 2>&1 | tail -2 && git log --oneline -3 && git push origin santos/dev 2>&1 | tail -3", + "cd /home/dev/src/hint-fix && git push origin feature/example 2>&1 | tail -3", + "cd /home/dev/src/hint-fix && git rebase origin/feature/example 2>&1 | tail -2 && git log --oneline -3 && git push origin feature/example 2>&1 | tail -3", "cd /tmp/hint-fix-pr && git switch -c fix/opening-hint-test-owns-profile && git push -u origin fix/opening-hint-test-owns-profile", } for _, command := range commands { @@ -149,12 +149,12 @@ func TestAnUnattendedRunMayMoveOnlyTheBranchItCut(t *testing.T) { // commands, and only [Config.Unattended] differs: a person in their own // checkout keeps their whole terminal, which is what taskgit_test.go's rows // already pin and this must not take away. - foreign := gitRepoOn(t, "santos/dev") + foreign := gitRepoOn(t, "feature/example") attended, _ := newTestAgent(t, &scriptedCompleter{}, func(c *Config) { c.Interactive = true c.Workspace = foreign }) - for _, command := range []string{"git push origin santos/dev", "git merge --ff-only origin/santos/dev", "git rebase origin/santos/dev"} { + for _, command := range []string{"git push origin feature/example", "git merge --ff-only origin/feature/example", "git rebase origin/feature/example"} { if _, refusal, allowed := (taskGitGuard{agent: attended}).PreAction(context.Background(), nil, nil, bashGitCall(command)); !allowed { t.Errorf("%q was refused with %q for an attended session; the person's own git is theirs", command, refusal.text) } @@ -166,7 +166,7 @@ func TestAnUnattendedRunMayMoveOnlyTheBranchItCut(t *testing.T) { c.Unattended = true c.Workspace = foreign }) - for _, command := range []string{"git push origin santos/dev", "git merge --ff-only origin/santos/dev", "git rebase origin/santos/dev"} { + for _, command := range []string{"git push origin feature/example", "git merge --ff-only origin/feature/example", "git rebase origin/feature/example"} { _, refusal, allowed := (taskGitGuard{agent: run}).PreAction(context.Background(), nil, nil, bashGitCall(command)) if allowed { t.Errorf("%q was allowed for a run standing on a branch it did not create", command) diff --git a/internal/store/memory_upgrade_test.go b/internal/store/memory_upgrade_test.go index bbccf39042..44b26bbc3c 100644 --- a/internal/store/memory_upgrade_test.go +++ b/internal/store/memory_upgrade_test.go @@ -542,7 +542,7 @@ func TestOpeningADatabaseAPreOwnerBuildWroteIntoOwnsItsEmptyRows(t *testing.T) { } // A STORE WITH NO LEXICAL INDEX IS STILL THE PERSON'S MEMORY, and this is -// PRE-EXISTING behavior, deliberately unchanged by the owner repair: the open +// PRE-EXISTING behavior, deliberately unchanged by the schema repair: the open // builds the empty search view when FTS5 is available but does not re-index rows // the file already held, so search answers nothing while every note stays // readable and owner-scoped. A Rebuild replays the journal and fills the view. diff --git a/internal/store/role_bindings_test.go b/internal/store/role_bindings_test.go index b06d267f56..be9696a665 100644 --- a/internal/store/role_bindings_test.go +++ b/internal/store/role_bindings_test.go @@ -194,8 +194,8 @@ func TestRoleBindingPrecedence(t *testing.T) { assert("and the machine is silent again", "stage", "", RoleUnbound) } -// TestSetModelAndRoleBindingsTellOneStory is the composition Wave 0's subtree -// sweep and this table have to agree on: SetModel is a node-level pin sweep +// TestSetModelAndRoleBindingsTellOneStory proves the composition that the subtree +// sweep and this table must agree on: SetModel is a node-level pin sweep // over the live subtree, a binding is the inheritable default, and pins win — // including for work admitted after the sweep, which carries no pin and // therefore inherits. diff --git a/internal/syncsetup/rename_test.go b/internal/syncsetup/rename_test.go index 4571dc7135..c453b03a0a 100644 --- a/internal/syncsetup/rename_test.go +++ b/internal/syncsetup/rename_test.go @@ -27,12 +27,12 @@ func TestRenameOnOneHomeShowsOnTheOther(t *testing.T) { t.Fatalf("B sees A as %q before the rename, want %q", got, nameA) } - if err := r.a.Rename(ctx, "the studio desk"); err != nil { + if err := r.a.Rename(ctx, "the host-a desk"); err != nil { t.Fatal(err) } names := rosterNames(t, ctx, r.b) - if names[r.a.Device.ID()] != "the studio desk" || names[r.b.Device.ID()] != nameB { + if names[r.a.Device.ID()] != "the host-a desk" || names[r.b.Device.ID()] != nameB { t.Fatalf("B's device list = %v, want A renamed and B itself unchanged", names) } list, err := r.b.Dir.List(ctx) @@ -42,7 +42,7 @@ func TestRenameOnOneHomeShowsOnTheOther(t *testing.T) { open := func(sealed string) (string, error) { return directory.OpenName(directory.MetadataKey(r.b.Identity.CellKey()), sealed) } - if got := chatlist.DeviceName(list.Devices, r.a.Device.ID(), open); got != "the studio desk" { + if got := chatlist.DeviceName(list.Devices, r.a.Device.ID(), open); got != "the host-a desk" { t.Fatalf("the home row names A %q, want the new name", got) } } diff --git a/internal/taxonomy/taxonomy.go b/internal/taxonomy/taxonomy.go index 1c50727709..7ab4a0d73f 100644 --- a/internal/taxonomy/taxonomy.go +++ b/internal/taxonomy/taxonomy.go @@ -448,8 +448,7 @@ type Evidence struct { // identically, nothing can be taken off the next body, and the only moves left // are a wait and then another model. // -// THE MEASURED FAILURE IS THE ORDER THIS REPLACED (2026-09-11 14:39–14:41, the -// owner's own task on deepseek/deepseek-v4.1-flash). `Status == 429` was asked +// A per-machine rate limit must remain distinct from an account rate limit. `Status == 429` was asked // before `Upstream != ""`, so a per-machine `(via Wafer: … temporarily // rate-limited upstream)` rode the account-limiter road — wait, double, send the // same order again — for eight sends and ninety seconds, while six other diff --git a/internal/tui2/tokens/profile.go b/internal/tui2/tokens/profile.go index 8de17d1b05..9b6daac66d 100644 --- a/internal/tui2/tokens/profile.go +++ b/internal/tui2/tokens/profile.go @@ -377,7 +377,7 @@ func crowded256(c Color, claimed []uint8) bool { // // It exists so an explicit override has a door WITHOUT this package minting an // environment pin of its own. Colour capability is a decided value that the -// shell passes down — from a settings row when Wave 4 adds one, from a flag, or +// shell passes down — from a settings row, from a flag, or // from [DetectProfile] — and an unrecognized spelling reports false rather than // guessing, because an override that silently did something else would be the // affordance lying about what it accepted (5.20). diff --git a/internal/tui2/tokens/remap_test.go b/internal/tui2/tokens/remap_test.go index be16f68754..2e38e9d71b 100644 --- a/internal/tui2/tokens/remap_test.go +++ b/internal/tui2/tokens/remap_test.go @@ -9,7 +9,7 @@ import ( // The remap seam (10.2.7, 12.4.4). // -// internal/sanitize landed the remap MECHANISM in Wave 1 with an identity +// internal/sanitize supplies the remap mechanism with an identity // table, and this package supplies the palette that replaces it. The tests // below run the real sanitizer with the real table, because a [16]int that // merely looks right is not a seam — the thing worth proving is that the diff --git a/internal/tui2/tokens/state_test.go b/internal/tui2/tokens/state_test.go index 998e276d42..f647f69ef8 100644 --- a/internal/tui2/tokens/state_test.go +++ b/internal/tui2/tokens/state_test.go @@ -117,7 +117,7 @@ func TestPromoteAndDemoteWalkOnlyTheGreyRamp(t *testing.T) { } } -// TestCutMarkVocabulary is the truncation law's half of Wave 2 (12.5.2). The +// TestCutMarkVocabulary is the truncation vocabulary contract. The // token layer does not decide WHERE a cut is drawn — that binds the head and // the parts renderers — but nothing above can render one honestly if the // vocabulary cannot say it. diff --git a/internal/tui3/answerstrip.go b/internal/tui3/answerstrip.go index 6790be17f4..c60f99bed0 100644 --- a/internal/tui3/answerstrip.go +++ b/internal/tui3/answerstrip.go @@ -42,8 +42,7 @@ func (a *app) answerStrip(width int, now time.Time) []string { } tail := strings.Join(answers, answerChipGap) tailWidth := ansi.StringWidth(tail) - // THE ANSWERS ARE KEYS AND WORDS, not a stripe of the question hue (owner - // ruling 2026-09-11, colour pick C): the key steps up to the payload hue and + // THE ANSWERS ARE KEYS AND WORDS, not a stripe of the question hue: the key steps up to the payload hue and // its word is ink, exactly as on the panel above a conversation's box. paint := func(text string) string { return a.pal.ink(text) } if tailWidth >= width { diff --git a/internal/tui3/approvalchip.go b/internal/tui3/approvalchip.go index 37c9baf1dc..ed4db97a68 100644 --- a/internal/tui3/approvalchip.go +++ b/internal/tui3/approvalchip.go @@ -61,7 +61,7 @@ import ( // // ── THE CHORD IS alt+a ──────────────────────────────────────────────────── // -// Approvals takes `alt+a` at the owner's request; the pending-question door +// Approvals takes `alt+a`; the pending-question door // moves to `alt+y`. The router, hints and manual exchange the roles together // so a question waiting in the background cannot intercept the approval key. // diff --git a/internal/tui3/chattabs.go b/internal/tui3/chattabs.go index ea8ee3aa26..4100aaf10c 100644 --- a/internal/tui3/chattabs.go +++ b/internal/tui3/chattabs.go @@ -686,7 +686,7 @@ func (a *app) tabsRow(width int) string { // THERE IS NO GLYPH-ALONE RUNG ANY MORE. Between sixty and eighty columns the // door used to be a bare ` ▦ `, a lone mark a person had to press to learn // about; four cells taken from the tabs is the price of it saying what it is, -// and the owner's bar is word buttons, never lone glyphs. tabWallWordFrom is +// and the tab bar is word buttons, never lone glyphs. tabWallWordFrom is // kept equal to tabWallFrom so the two rungs cannot drift apart again. const ( tabWallFrom = roomHeadFloor diff --git a/internal/tui3/chords.go b/internal/tui3/chords.go index 5019558492..40c7b25299 100644 --- a/internal/tui3/chords.go +++ b/internal/tui3/chords.go @@ -10,8 +10,7 @@ import ( // ── ONE SPELLING OF THE CHORDS, AND WHAT THIS TERMINAL CAN ACTUALLY SEND ───── // -// The owner's ask was "ensure you properly have alt or ctrl etc according to -// windows or mac", and the honest half of the answer has to be said first: A +// Chord labels match the platform, with one terminal limit: A // TUI CANNOT SEE WHETHER OPTION IS META. It sees what the emulator sent. On a // Mac, `opt+1` reaches this program as escape-then-`1` when the profile says // "option as meta" and as the single character `¡` when it does not, and there diff --git a/internal/tui3/chordspellinglaw_test.go b/internal/tui3/chordspellinglaw_test.go index 01156f32b3..88b79f76a0 100644 --- a/internal/tui3/chordspellinglaw_test.go +++ b/internal/tui3/chordspellinglaw_test.go @@ -26,8 +26,7 @@ import ( // // So a person moving between two screens one keystroke apart was shown one // modifier under two names and had no way to know it was one key. That is the -// question this law was written from, asked by the owner looking at the two -// spellings side by side. +// ambiguity this law prevents when the two spellings appear side by side. // // IT IS A FRAME TEST AND NOT A SOURCE TEST ON PURPOSE. What is wrong is never // the constant — every one of them is SUPPOSED to say `alt+` — it is the path diff --git a/internal/tui3/contextmodal.go b/internal/tui3/contextmodal.go index 3e64520657..2e072c4487 100644 --- a/internal/tui3/contextmodal.go +++ b/internal/tui3/contextmodal.go @@ -77,7 +77,7 @@ import ( // wide screen — two things a person's eye cannot associate, which is the defect // the screenshot shows. A hundred and twenty-eight cells is about as wide as // three columns of paths stay readable, and it is close to the proportion the -// owner's reference (../reference-yazi.png) keeps on a large display. +// column browser layout keeps on a large display. const ( // contextSheetWide is the widest the sheet is ever drawn. contextSheetWide = 128 diff --git a/internal/tui3/contextmodal_test.go b/internal/tui3/contextmodal_test.go index bd152b4b01..8e1ea5c11a 100644 --- a/internal/tui3/contextmodal_test.go +++ b/internal/tui3/contextmodal_test.go @@ -60,7 +60,7 @@ func frameOf(t *testing.T, a *app) []string { // BOTH DOORS OPEN THE SAME SHEET, BROWSING, IN THE SAME PLACE. // -// This is the owner's first report in one test. `/attach` opened the columns +// Both browsing commands must open the same surface. `/attach` opened the columns // immediately and `/folder` opened a flat list of remembered places, so the two // commands showed two different surfaces — and on a machine that remembered // nothing at all `/folder` refused with a sentence instead of opening. diff --git a/internal/tui3/contract_real_path_test.go b/internal/tui3/contract_real_path_test.go index 64348455a0..2c3a49f290 100644 --- a/internal/tui3/contract_real_path_test.go +++ b/internal/tui3/contract_real_path_test.go @@ -33,7 +33,7 @@ func TestC5DisplacementIsShownWhileTheChatIsOpen(t *testing.T) { src.mu.Unlock() a.machinePoll.hurry() drain(t, a, a.homeBeat(a.homeGen)) - if got := plain(lastNote(t, a)); got != chatlist.Superseded("studio") { + if got := plain(lastNote(t, a)); got != chatlist.Superseded("host-a") { t.Fatalf("no displacement sentence: %q", got) } typeText(t, a, "keep these words") diff --git a/internal/tui3/devices_rename_test.go b/internal/tui3/devices_rename_test.go index 2d888e7e30..58292aa5c4 100644 --- a/internal/tui3/devices_rename_test.go +++ b/internal/tui3/devices_rename_test.go @@ -37,10 +37,10 @@ func TestTypingANameSavesItThroughTheDoorAndTheRowShowsIt(t *testing.T) { door := devicesFake() a, r := renameApp(t, door) r.press("n", "ctrl+u") - r.typeText("studio r") + r.typeText("host-a r") r.press("enter") - r.until("saved", func() bool { return strings.Contains(plain(frame(a)), "● studio r") }) - if door.did() != "rename:studio r" { + r.until("saved", func() bool { return strings.Contains(plain(frame(a)), "● host-a r") }) + if door.did() != "rename:host-a r" { t.Fatalf("door: %s", door.did()) } if c := a.pair.card.(*deviceCard); c.typing() { diff --git a/internal/tui3/dropkeys_test.go b/internal/tui3/dropkeys_test.go index 023b634e1f..8e9c000596 100644 --- a/internal/tui3/dropkeys_test.go +++ b/internal/tui3/dropkeys_test.go @@ -54,7 +54,7 @@ func settleDrop(t *testing.T, a *app, tick func(time.Duration)) { // ── what a person gets ────────────────────────────────────────────────────── -// THE OWNER'S FIRST REPORT. A screenshot dropped on a terminal that types the +// A DROPPED SCREENSHOT IS AN ATTACHMENT. A screenshot dropped on a terminal that types the // drop left the escaped path sitting in the box as text. It becomes the chip a // pasted drop has always become. func TestAScreenshotTypedInByTheTerminalBecomesAChip(t *testing.T) { @@ -101,7 +101,7 @@ func TestARawSpacePictureTypedAndEnteredIsNeverAnUnknownCommand(t *testing.T) { } } -// THE OWNER'S SECOND REPORT, and the sentence it must never say again. +// A SECOND DROP MUST ALSO RESOLVE TO AN ATTACHMENT. func TestADropTypedInAndEnteredIsNeverAnUnknownCommand(t *testing.T) { a, dir, _ := dropLab(t, map[string]int{"server.log": 12}) typeBurst(a, filepath.Join(dir, "server.log")) diff --git a/internal/tui3/effortchip.go b/internal/tui3/effortchip.go index a1d0d14d97..c9ec51ea8a 100644 --- a/internal/tui3/effortchip.go +++ b/internal/tui3/effortchip.go @@ -60,7 +60,7 @@ import ( // install it was absent always, on the in-process road and the hosted one // alike, and the only person who ever saw a rung was one who already knew there // was a dial to turn. The tray chip this replaced had the same hole, and that -// is exactly why the owner never learned the dial existed and asked for one to +// can hide the dial from a person who needs to // be built. // // A CONTROL THAT IS INVISIBLE UNTIL YOU HAVE ALREADY USED IT IS NOT A CONTROL diff --git a/internal/tui3/folderindex_test.go b/internal/tui3/folderindex_test.go index e3e2f1dd69..7030764882 100644 --- a/internal/tui3/folderindex_test.go +++ b/internal/tui3/folderindex_test.go @@ -371,7 +371,7 @@ func TestAWalkWithNoBaseDoesNothingRatherThanWalkingTheDisk(t *testing.T) { // to stop only BETWEEN DIRECTORIES. The directory that ends a home walk on a // Mac is `~/Library/Caches`, which is one directory holding hundreds of // thousands of files, and a bound that is never read while those stream past is -// not a bound. Measured on the owner's laptop before this change: +// not a bound. Measured before this change: // `scanFolderRoots` took 5m58s under a three-second budget, which is what took // the folder picker's background read past the test driver's own budget and // panicked `TestThePicksLandingMidBrowseKeepTheChoicesAndThePreview`. diff --git a/internal/tui3/folderpane.go b/internal/tui3/folderpane.go index 7df62ed54e..0df6e3f7c8 100644 --- a/internal/tui3/folderpane.go +++ b/internal/tui3/folderpane.go @@ -2,7 +2,7 @@ package tui3 // THE PREVIEW PANE, AND HOW THE SHEET'S WIDTH IS DIVIDED. // -// The owner's reference is Yazi's arrangement (../reference-yazi.png): a +// The browser uses a three-column arrangement: a // readable path along the top, a NARROW ancestry column on the left, a GENEROUS // list of what you are standing in, and a LARGE preview on the right — with the // sizes right-aligned, one unmistakable selection, and compact controls at the diff --git a/internal/tui3/folderpick.go b/internal/tui3/folderpick.go index d753b7dfb2..0830610356 100644 --- a/internal/tui3/folderpick.go +++ b/internal/tui3/folderpick.go @@ -333,7 +333,7 @@ type folderGeom struct { // directory you are standing in was drawn beside the row the cursor was on — // which meant that with the cursor at the top of a folder, every sibling ABOVE // that folder was off the top of the column and the ancestry showed one name. - // The owner's reference draws the parent as what it is: a list, with the one + // The browser draws the parent as what it is: a list, with the one // you came out of marked in it. upFrom int // paneDir is the directory the preview pane is LISTING, absolute, or "" when @@ -1684,8 +1684,7 @@ var folderHintFields = []rowField{ // placeholder and [folderPick.controlLegend] under the columns — name every // chord the sheet owns exactly once, which is docs/DESIGN-LANGUAGE.md's rule // that a chord keeps a visible door beside it; naming them all in BOTH places -// was the wall of shortcuts the owner's 2026-09-08 review asked us to stop -// drawing. The fields go from the RIGHT, whole, on a frame too narrow for all of +// would draw a distracting wall of shortcuts. The fields go from the RIGHT, whole, on a frame too narrow for all of // them (rowfit.go), so the line never draws a key spelled `alt+…`. var folderBrowseHintFields = []rowField{ rowSay("search folders and files"), rowSay("or type a path"), diff --git a/internal/tui3/folderplace.go b/internal/tui3/folderplace.go index b7a3109aaa..e11d4fb558 100644 --- a/internal/tui3/folderplace.go +++ b/internal/tui3/folderplace.go @@ -11,7 +11,7 @@ package tui3 // Every road that ends in "this directory" ends in [app.referPlace] with a // [chosenPlace]: the picker's enter, `/attach` handed a folder, and — when the // forming card's ground row lands — its `g`. There is ONE such method on -// purpose. Lane P2 is what makes a chosen place PERSIST (a `Places` list on the +// purpose. The place list makes a chosen place PERSIST (a `Places` list on the // conversation's meta, feeding the ground ladder's SAID rung); it replaces the // body of that one method and does not have to go and find the doors again. // What P1 does there is real and not a placeholder: it writes the pick down for @@ -307,8 +307,7 @@ func (a *app) closeFolderSheet() tea.Cmd { // // ── WHAT THE INTENT DECIDES, AND WHAT IT DOES NOT ────────────────────────── // -// ONE SHEET SHOWS BOTH KINDS WHICHEVER DOOR OPENED IT, which is the owner's -// requirement in as many words: a person deciding what this conversation is +// ONE SHEET SHOWS BOTH KINDS WHICHEVER DOOR OPENED IT: a person deciding what this conversation is // about is looking at a repository and a log inside it at the same time, and two // browsers would have made them choose the door before they had seen the thing. // So the intent decides exactly one thing — WHETHER A FOLDER DOOR IS REQUIRED diff --git a/internal/tui3/foldertype.go b/internal/tui3/foldertype.go index ac78d06b0a..e7f689302e 100644 --- a/internal/tui3/foldertype.go +++ b/internal/tui3/foldertype.go @@ -2,7 +2,7 @@ package tui3 // WHAT KIND OF THING A ROW IS, IN ONE CELL. // -// The owner's reference (../reference-yazi.png) carries a small mark beside +// The column browser layout carries a small mark beside // every name, and the 2026-09-08 review asks for the same thing here: a compact // visual navigation aid, so a directory of forty files can be scanned for "the // picture" or "the source" without reading a single name. diff --git a/internal/tui3/home.go b/internal/tui3/home.go index 117a09163d..93876c400d 100644 --- a/internal/tui3/home.go +++ b/internal/tui3/home.go @@ -218,7 +218,7 @@ const homeGutter = 4 // of home's "while you were away": no notification, no banner, one cell of one // row saying something finished here (see [homeView.seen]). // -// ── THE TWO MARKS THE DESIGN RE-SPELLED (owner-signed, FIDELITY.md item 4) ─── +// ── THE TWO MARKS THE DESIGN RE-SPELLED ─── // // `?` AND NOT `▲` FOR A ROW THAT HAS STOPPED ON YOU. The triangle was this // screen's own invention and it was the wrong shape twice over: a warning @@ -602,8 +602,7 @@ type homeView struct { // DOOR — a panel whose heading names a place ([homeHeadAt], // [app.homeHeadDoor]) — or -1. A heading is no cursor stop, so [hover] never // lands on one; this is the one fact the pointer adds to a heading, and it - // is drawn as an underline under the word rather than a ground (owner, - // 2026-09-17: a clickable heading should say so under the mouse). + // is drawn as an underline under the word rather than a ground. headHover int // projectHover underlines a clickable project without moving the list cursor. projectHover int diff --git a/internal/tui3/home_test.go b/internal/tui3/home_test.go index 99abf76998..39559a6a13 100644 --- a/internal/tui3/home_test.go +++ b/internal/tui3/home_test.go @@ -971,8 +971,8 @@ func TestTheRestingBoxKeepsACaretOnTheCellTheFirstLetterLandsOn(t *testing.T) { // clause it deliberately leaves out (SCREEN 1a and 2b, FIDELITY.md item 3). // // It is pinned as a literal rather than against the constants alone because the -// constants are what a careless edit changes: the owner's order was "follow the -// exact design", and the only assertion that can catch a sentence drifting is +// constants are what a careless edit changes: the only assertion that can +// catch a sentence drifting is // one that carries the sentence. // // THE LAW THAT DIED IS `esc close` ON THE RESTING ROW. Home used to end every @@ -2579,8 +2579,7 @@ func TestAnEmptyHomeKeepsItsShapeAtEveryWidth(t *testing.T) { } } // AND AN EMPTY HOME HAS NO ROW TO STAND ON AT ALL: `projects` is never - // empty (DESIGN.md §4) but its rows are read and not stood on (owner, - // 2026-09-17), and every other panel whispers — a whisper names what + // empty (DESIGN.md §4) but its rows are read and not stood on, and every other panel whispers — a whisper names what // arrives rather than a thing to open. if stops := (placeHome{}).stops(a); len(stops) != 0 { t.Fatalf("at %d columns an empty home offered rows to stand on: %v", tc.width, stops) diff --git a/internal/tui3/homecard_test.go b/internal/tui3/homecard_test.go index 8409ba90d3..a014d5331c 100644 --- a/internal/tui3/homecard_test.go +++ b/internal/tui3/homecard_test.go @@ -184,8 +184,7 @@ func TestARunningCardLeadsTheRowWithItsState(t *testing.T) { // band was a list of things that happened, name first, in the index's own order, // and what that produced on the one screen a person opens twenty times a day was // three pieces of ONE run standing beside each other as three peers — with the -// order deciding which of them a reader thought was the whole story. The owner's -// instruction is that the conversation is the parent and the work under it is a +// order deciding which of them a reader thought was the whole story. The layout requires that the conversation is the parent and the work under it is a // TREE, at any depth, never a row of strangers. // // So the roster's own connectors are drawn here too (hometree.go, task.go's diff --git a/internal/tui3/homecell.go b/internal/tui3/homecell.go index 0e7e42c1af..8383a8703a 100644 --- a/internal/tui3/homecell.go +++ b/internal/tui3/homecell.go @@ -338,8 +338,7 @@ var homeDescLeadBlank = strings.Repeat(" ", homeDescLeadCells) // // A HEADING THAT OPENS NOTHING IS DIM, word and explainer alike — `projects` // and `threads` name their own panel and no place, and a heading painted like -// the five that are doors read as a door that did not work (owner, -// 2026-09-17: "make the non-clickable projects heading grey"). The ground the +// the five that are doors read as a door that did not work. The ground the // cursor's panel wears still lands on it, because that fact is about where the // cursor is and not about what the heading opens. func homeCellHead(cell *homeCell, width int, pal palette, marked, hovered bool) string { diff --git a/internal/tui3/homecell_keepnote_test.go b/internal/tui3/homecell_keepnote_test.go index 7a4638227e..333aed86e4 100644 --- a/internal/tui3/homecell_keepnote_test.go +++ b/internal/tui3/homecell_keepnote_test.go @@ -17,15 +17,15 @@ func TestMachineRowKeepsNoteBesideLongTitle(t *testing.T) { title := strings.Repeat("a", n) t.Run(fmt.Sprintf("w%d_t%d", width, n), func(t *testing.T) { cell := &homeCell{kind: cellRow, panel: panelSessions, title: title, - note: "running on studio", keepNote: true, right: "now"} + note: "running on host-a", keepNote: true, right: "now"} row := plain(homeCellBody(cell, width, pal, false)) - if !strings.Contains(row, "running on studio") || !strings.HasSuffix(row, "now") { + if !strings.Contains(row, "running on host-a") || !strings.HasSuffix(row, "now") { t.Fatalf("note or age lost: %q", row) } if got := ansi.StringWidth(row); got > width { t.Fatalf("row is %d cells in %d: %q", got, width, row) } - if whole := n <= width-len("running on studio now")-4; whole != strings.HasPrefix(row, title) { + if whole := n <= width-len("running on host-a now")-4; whole != strings.HasPrefix(row, title) { t.Fatalf("title whole=%v, row %q", whole, row) } }) diff --git a/internal/tui3/homefate_test.go b/internal/tui3/homefate_test.go index d7137aaac7..600cc5cf5f 100644 --- a/internal/tui3/homefate_test.go +++ b/internal/tui3/homefate_test.go @@ -4,9 +4,7 @@ package tui3 // it on a screen that is not a conversation, read off the row BEFORE the key is // pressed, and done by the same table that drew it (homeslash.go's [homeFate]). // -// The owner's ask, in their words: "see what other slash commands need clear -// indication like that even if it's not usable". So the two claims under test -// are that every command has a fate, and that the fate on the row is the road +// The two claims under test are that every command has a fate, and that the fate on the row is the road // the dispatch actually takes. import ( diff --git a/internal/tui3/homegrid.go b/internal/tui3/homegrid.go index 46d026e870..1f8ab1856b 100644 --- a/internal/tui3/homegrid.go +++ b/internal/tui3/homegrid.go @@ -880,8 +880,7 @@ func (p *homeGridPanel) shrink() { // at every width ([homeView.placesTop], homebridge.go): a landing that moved // with the frame is a landing nobody can build a habit on. // -// AND THE COLUMN IT NO LONGER SPILLS INTO IS THE DESCRIPTION'S (owner, -// 2026-09-15). A field that could take the middle when it ran out of room made +// AND THE COLUMN IT NO LONGER SPILLS INTO IS THE DESCRIPTION'S. A field that could take the middle when it ran out of room made // the middle a column that was a description sometimes and a panel other times; // reserving it costs a squeeze on a short frame and buys a column that means one // thing at every size ([homeDescCol]). diff --git a/internal/tui3/homegrid_test.go b/internal/tui3/homegrid_test.go index 5c2141c1aa..8251716555 100644 --- a/internal/tui3/homegrid_test.go +++ b/internal/tui3/homegrid_test.go @@ -556,7 +556,7 @@ func TestAHeadingExplainerIsDimAndGivesWayBeforeTheHeadingIsCut(t *testing.T) { // A WHISPER NEVER OUTRANKS A ROW. When a short frame has to drop a whole // panel, the panels that are only whispering go first, whatever their keep: -// a person can open a row and cannot open a whisper. Lane R2 found a 120×14 +// a person can open a row and cannot open a whisper. A 120×14 // home that was two whispers and no conversation at all. func TestAShortFrameDropsWhisperingPanelsBeforeAnyPanelWithRows(t *testing.T) { lab := newHomeLab(t) @@ -663,8 +663,7 @@ func focusedTitle(a *app) string { return "" } -// THE ARROWS STAY IN THE FIELD AND THE FOOT IS THE RESTING SENTENCE (owner, -// 2026-09-17). `→` on a field row opens that row's own strip rather than +// THE ARROWS STAY IN THE FIELD AND THE FOOT IS THE RESTING SENTENCE. `→` on a field row opens that row's own strip rather than // crossing to the rail, `←` closes it, and `ctrl+o` still opens the row's // folder without the strip. A project's row is not a stop and has no folder // door of its own any more. diff --git a/internal/tui3/homelive_test.go b/internal/tui3/homelive_test.go index 0148bcd6fa..12181c12ac 100644 --- a/internal/tui3/homelive_test.go +++ b/internal/tui3/homelive_test.go @@ -43,44 +43,44 @@ func TestHomeLiveSource(t *testing.T) { ctx := context.Background() clock := directorytest.NewFakeClock() world := directory.NewMemory(clock.Now) - studio, here := world.For("dev_studio"), world.For("dev_here") + remote, here := world.For("dev_host-a"), world.For("dev_here") key := directory.MetadataKey(bytes.Repeat([]byte{7}, 16)) - name, _ := directory.SealName(key, "studio") + name, _ := directory.SealName(key, "host-a") title, _ := directory.SealTitle(key, "Port the picker") - if err := studio.PutDevice(ctx, "dev_studio", directory.Device{V: 1, Name: name}); err != nil { + if err := remote.PutDevice(ctx, "dev_host-a", directory.Device{V: 1, Name: name}); err != nil { t.Fatal(err) } init := directory.CellInit{Head: "h1", Class: "chat", Title: title, Keys: map[string]map[string]string{}} - if _, err := studio.Create(ctx, "c-live", init); err != nil { + if _, err := remote.Create(ctx, "c-live", init); err != nil { t.Fatal(err) } live := &liveDirectory{dir: here, self: "dev_here", key: key} a := machinesHome(t, live, 200) - if text := homeText(a); !strings.Contains(text, "Port the picker") || !strings.Contains(text, "running on studio") { + if text := homeText(a); !strings.Contains(text, "Port the picker") || !strings.Contains(text, "running on host-a") { t.Fatalf("a chat running elsewhere is not listed as such:\n%s", text) } clock.Advance(directory.LeaseTTL + 1) drain(t, a, a.askMachines()) - if text := homeText(a); !strings.Contains(text, "studio offline") || strings.Contains(text, "running on studio") { + if text := homeText(a); !strings.Contains(text, "host-a offline") || strings.Contains(text, "running on host-a") { t.Fatalf("the chat went quiet and home did not follow:\n%s", text) } branch := directory.CellInit{Head: "h2", Class: "chat", ParentCell: "c-live", OrphanTurns: 2, Title: title, Keys: map[string]map[string]string{}} - if _, err := studio.Create(ctx, "c-branch", branch); err != nil { + if _, err := remote.Create(ctx, "c-branch", branch); err != nil { t.Fatal(err) } drain(t, a, a.askMachines()) - if text := homeText(a); !strings.Contains(text, "2 turns from studio") { + if text := homeText(a); !strings.Contains(text, "2 turns from host-a") { t.Fatalf("a branch that appeared is not listed:\n%s", text) } live.down = true drain(t, a, a.askMachines()) text := homeText(a) - if !strings.Contains(text, chatlist.Unreachable) || !strings.Contains(text, "studio offline") { + if !strings.Contains(text, chatlist.Unreachable) || !strings.Contains(text, "host-a offline") { t.Fatalf("with the directory out of reach the last listing must stay, under the sentence:\n%s", text) } @@ -90,7 +90,7 @@ func TestHomeLiveSource(t *testing.T) { } drain(t, a, a.askMachines()) text = homeText(a) - if strings.Contains(text, chatlist.Unreachable) || strings.Contains(text, "studio offline") { + if strings.Contains(text, chatlist.Unreachable) || strings.Contains(text, "host-a offline") { t.Fatalf("the chat is held here now and the sentences must go:\n%s", text) } } @@ -101,7 +101,7 @@ func TestHomeLiveSource(t *testing.T) { func TestReleasedChatOnAnotherMachineOffersContinue(t *testing.T) { taker := &fakeTaker{} a := continueHome(t, taker, chatlist.Static{ - {Cell: "c-idle", Title: "Notes on the migration", Device: "studio", Status: chatlist.Idle, DurableAgo: 26 * 3600e9}, + {Cell: "c-idle", Title: "Notes on the migration", Device: "host-a", Status: chatlist.Idle, DurableAgo: 26 * 3600e9}, }, 200) standOn(t, a, "c-idle") if text := homeText(a); !strings.Contains(text, chatlist.OfferContinue) { @@ -119,14 +119,14 @@ func TestReleasedChatOnAnotherMachineOffersContinue(t *testing.T) { func TestNoticeSeamSaysASentenceAtOnce(t *testing.T) { a, _ := homeTabsFixture(t) desk := NewNotices() - desk.Say("studio continued this chat; this window now only shows it") + desk.Say("host-a continued this chat; this window now only shows it") a.useNotices(desk) // a sentence said before the surface listened waits for it ringOf := func() tea.Msg { return a.noticeBell.waitRing()() } if _, ok := ringOf().(noticesMsg); !ok { t.Fatal("joining a desk that holds a sentence did not ring the door") } a.route(noticesMsg{}) - if !saidNote(a, "studio continued this chat") { + if !saidNote(a, "host-a continued this chat") { t.Fatal("the sentence said before the surface joined was lost") } diff --git a/internal/tui3/homepanel_continue_test.go b/internal/tui3/homepanel_continue_test.go index 860c35b0d3..e84325dc43 100644 --- a/internal/tui3/homepanel_continue_test.go +++ b/internal/tui3/homepanel_continue_test.go @@ -46,11 +46,11 @@ func (v *verbs) actions(merge bool) BranchActions { // continueRows is a chat that went off, one that went off with nothing left // behind it, and a branch. var continueRows = chatlist.Static{ - {Cell: "c-off", Title: "Port the picker", Device: "studio", Status: chatlist.Off, DurableAgo: 3 * time.Hour, Pending: 3}, - {Cell: "c-clean", Title: "Tidy the docs", Device: "studio", Status: chatlist.Off, DurableAgo: 90 * time.Second}, + {Cell: "c-off", Title: "Port the picker", Device: "host-a", Status: chatlist.Off, DurableAgo: 3 * time.Hour, Pending: 3}, + {Cell: "c-clean", Title: "Tidy the docs", Device: "host-a", Status: chatlist.Off, DurableAgo: 90 * time.Second}, {Cell: "c-branch", Title: "Fix the flaky test", Device: "laptop", Status: chatlist.Branch, OrphanTurns: 2, DurableAgo: 5 * time.Hour}, {Cell: "c-short", Title: "Trim logs", Device: "mac", Status: chatlist.Branch, OrphanTurns: 4, DurableAgo: 6 * time.Hour}, - {Cell: "c-run", Title: "Nightly index rebuild", Device: "studio", Status: chatlist.Running, DurableAgo: 2 * time.Minute}, + {Cell: "c-run", Title: "Nightly index rebuild", Device: "host-a", Status: chatlist.Running, DurableAgo: 2 * time.Minute}, } // standOn puts the cursor on the chat from another machine and presses enter. @@ -85,7 +85,7 @@ func TestTakeoverScreenCopy(t *testing.T) { standOn(t, a, "c-off") text := homeText(a) for _, want := range []string{ - "last saved turn 3h ago; up to 3 turns may still be on studio", + "last saved turn 3h ago; up to 3 turns may still be on host-a", chatlist.OfferContinue, } { if !strings.Contains(text, want) { @@ -106,7 +106,7 @@ func TestTakeoverScreenOffersARunningChat(t *testing.T) { taker := &fakeTaker{} a := continueHome(t, taker, continueRows, 200) standOn(t, a, "c-run") - if text := homeText(a); !strings.Contains(text, "running on studio; last saved turn 2m ago") { + if text := homeText(a); !strings.Contains(text, "running on host-a; last saved turn 2m ago") { t.Fatalf("the screen does not name where the chat runs:\n%s", text) } confirm(t, a) @@ -391,7 +391,7 @@ func TestTakeoverScreenGolden(t *testing.T) { golden := map[int][]string{ 80: { "╭─ ? Continue this chat here? ────────────────────────────────────────────────╮", - "│ last saved turn 3h ago; up to 3 turns may still be on studio │", + "│ last saved turn 3h ago; up to 3 turns may still be on host-a │", "│ │", "│ ▸ 1 continue here │", "│ 2 leave it there safe answer │", @@ -401,7 +401,7 @@ func TestTakeoverScreenGolden(t *testing.T) { 40: { "╭─ ? Continue this chat here? ────────╮", "│ last saved turn 3h ago; up to 3 │", - "│ turns may still be on studio │", + "│ turns may still be on host-a │", "│ │", "│ ▸ 1 continue here │", "│ 2 leave it there safe answer │", diff --git a/internal/tui3/homepanel_machines_test.go b/internal/tui3/homepanel_machines_test.go index 41d2c170a2..c9435f0dcc 100644 --- a/internal/tui3/homepanel_machines_test.go +++ b/internal/tui3/homepanel_machines_test.go @@ -14,10 +14,10 @@ import ( // otherMachines is one chat in each state a person can find on another machine. var otherMachines = chatlist.Static{ - {Cell: "c-run", Title: "Nightly index rebuild", Device: "studio", Status: chatlist.Running, DurableAgo: 2 * time.Minute}, - {Cell: "c-off", Title: "Port the picker", Device: "studio", Status: chatlist.Off, DurableAgo: 3 * time.Hour}, + {Cell: "c-run", Title: "Nightly index rebuild", Device: "host-a", Status: chatlist.Running, DurableAgo: 2 * time.Minute}, + {Cell: "c-off", Title: "Port the picker", Device: "host-a", Status: chatlist.Off, DurableAgo: 3 * time.Hour}, {Cell: "c-branch", Title: "Fix the flaky test", Device: "laptop", Status: chatlist.Branch, OrphanTurns: 2, DurableAgo: 5 * time.Hour}, - {Cell: "c-idle", Title: "Notes on the migration", Device: "studio", Status: chatlist.Idle, DurableAgo: 26 * time.Hour}, + {Cell: "c-idle", Title: "Notes on the migration", Device: "host-a", Status: chatlist.Idle, DurableAgo: 26 * time.Hour}, {Cell: "c-here", Title: "Held by this machine", Device: "here", Status: chatlist.Here, DurableAgo: time.Minute}, } @@ -53,8 +53,8 @@ func TestHomeRowsFromOtherMachines(t *testing.T) { a := machinesHome(t, otherMachines, 200) text := homeText(a) for _, want := range []string{ - "Nightly index rebuild", "running on studio", - "Port the picker", "studio offline", + "Nightly index rebuild", "running on host-a", + "Port the picker", "host-a offline", "Fix the flaky test", "2 turns from laptop: merge / discard", "Notes on the migration", } { @@ -68,7 +68,7 @@ func TestHomeRowsFromOtherMachines(t *testing.T) { t.Errorf("a chat held here is drawn again as another machine's") } for _, line := range linesWith(a, "Notes on the migration") { - for _, banned := range []string{"idle", "here", "studio"} { + for _, banned := range []string{"idle", "here", "host-a"} { if strings.Contains(line, banned) { t.Errorf("an idle chat says %q: %q", banned, line) } @@ -105,7 +105,7 @@ func TestUnreachableKeepsLastListing(t *testing.T) { if !strings.Contains(text, chatlist.Unreachable) { t.Fatalf("no unreachable line:\n%s", text) } - if !strings.Contains(text, "Nightly index rebuild") || !strings.Contains(text, "running on studio") { + if !strings.Contains(text, "Nightly index rebuild") || !strings.Contains(text, "running on host-a") { t.Fatalf("the last listing was dropped:\n%s", text) } } @@ -115,14 +115,14 @@ func TestUnreachableKeepsLastListing(t *testing.T) { func TestHomeMachineRowsGolden(t *testing.T) { golden := map[int][]string{ 80: { - " Nightly index rebuild running on studio 2m", - " Port the picker studio offline 3h", + " Nightly index rebuild running on host-a 2m", + " Port the picker host-a offline 3h", " Fix the flaky test 2 turns from laptop: merge / discard 5h", " Notes on the migration 1d", }, 40: { - " Nightly index... running on studio 2m", - " Port the picker studio offline 3h", + " Nightly index... running on host-a 2m", + " Port the picker host-a offline 3h", " Fix the flaky ... 2 turns · laptop 5h", " Notes on the migration 1d", }, @@ -148,7 +148,7 @@ func TestFreshHomeListsRowsFromOtherMachines(t *testing.T) { a.width, a.height = width, 40 a.taker = &fakeTaker{} a.machines = chatlist.Static{ - {Cell: "c-off", Title: "Port the picker", Device: "studio", Status: chatlist.Off, DurableAgo: 3 * time.Hour, Pending: 2}, + {Cell: "c-off", Title: "Port the picker", Device: "host-a", Status: chatlist.Off, DurableAgo: 3 * time.Hour, Pending: 2}, } a.branches = BranchActions{Merge: func(context.Context, string) error { return nil }} drain(t, a, a.openHome()) @@ -157,7 +157,7 @@ func TestFreshHomeListsRowsFromOtherMachines(t *testing.T) { t.Fatalf("a fresh home does not list the chat on another machine:\n%s", text) } standOn(t, a, "c-off") - if text := homeText(a); !strings.Contains(text, "studio") || strings.Contains(text, homeEmptyWord) { + if text := homeText(a); !strings.Contains(text, "host-a") || strings.Contains(text, homeEmptyWord) { t.Fatalf("enter on the row raised no takeover question:\n%s", text) } }) @@ -177,7 +177,7 @@ func TestAChatHeldElsewhereIsDrawnAsSuchWhicheverReadingComesFirst(t *testing.T) lab := newHomeLab(t) workspace := lab.workspace("project") id := "0000000000000001" - src := chatlist.Static{{Cell: id, Title: "Port the picker", Device: "studio", DeviceID: "dev_studio", + src := chatlist.Static{{Cell: id, Title: "Port the picker", Device: "host-a", DeviceID: "dev_host-a", Status: status, DurableAgo: time.Minute}} var a *app if readingFirst { @@ -194,7 +194,7 @@ func TestAChatHeldElsewhereIsDrawnAsSuchWhicheverReadingComesFirst(t *testing.T) a.machines = src drain(t, a, a.askMachines()) } - if got := linesWith(a, "studio"); len(got) == 0 { + if got := linesWith(a, "host-a"); len(got) == 0 { t.Fatalf("the chat is drawn as a plain local row:\n%s", homeText(a)) } }) @@ -223,12 +223,12 @@ func TestAChatThisMachineLetLapseStaysLocal(t *testing.T) { // it: the row says the device is off, not that the chat runs there now. func TestARowOnAnOfflineDeviceDoesNotSayRunning(t *testing.T) { a := machinesHome(t, chatlist.Static{ - {Cell: "c-run", Title: "Nightly index rebuild", Device: "studio", DeviceID: "dev_studio", Status: chatlist.Running, DurableAgo: time.Minute}, + {Cell: "c-run", Title: "Nightly index rebuild", Device: "host-a", DeviceID: "dev_host-a", Status: chatlist.Running, DurableAgo: time.Minute}, }, 200) - feed := &fakeFeed{state: dirwatch.State{Up: true, Online: []string{"dev_studio"}}} + feed := &fakeFeed{state: dirwatch.State{Up: true, Online: []string{"dev_host-a"}}} a.dirFeed = feed a.rebuildMachines() - if got := linesWith(a, "running on studio"); len(got) != 1 { + if got := linesWith(a, "running on host-a"); len(got) != 1 { t.Fatalf("an online device's chat should say running:\n%s", homeText(a)) } feed.state.Online = nil @@ -236,7 +236,7 @@ func TestARowOnAnOfflineDeviceDoesNotSayRunning(t *testing.T) { if got := linesWith(a, "running on"); len(got) != 0 { t.Fatalf("an offline device's chat still says running: %q", got) } - if got := linesWith(a, "studio offline"); len(got) != 1 { + if got := linesWith(a, "host-a offline"); len(got) != 1 { t.Fatalf("an offline device's chat does not say it is off:\n%s", homeText(a)) } } @@ -244,14 +244,14 @@ func TestARowOnAnOfflineDeviceDoesNotSayRunning(t *testing.T) { // A presence change redraws the rows without waiting for a new listing. func TestAPresenceSignalRedrawsTheRow(t *testing.T) { a := machinesHome(t, chatlist.Static{ - {Cell: "c-run", Title: "Nightly index rebuild", Device: "studio", DeviceID: "dev_studio", Status: chatlist.Running, DurableAgo: time.Minute}, + {Cell: "c-run", Title: "Nightly index rebuild", Device: "host-a", DeviceID: "dev_host-a", Status: chatlist.Running, DurableAgo: time.Minute}, }, 200) - feed := &fakeFeed{state: dirwatch.State{Up: true, Online: []string{"dev_studio"}}} + feed := &fakeFeed{state: dirwatch.State{Up: true, Online: []string{"dev_host-a"}}} a.dirFeed = feed a.rebuildMachines() feed.state.Online = nil a.tookWatch(dirWatchMsg{from: feed}) - if got := linesWith(a, "studio offline"); len(got) != 1 { + if got := linesWith(a, "host-a offline"); len(got) != 1 { t.Fatalf("a presence signal left the row stale:\n%s", homeText(a)) } } diff --git a/internal/tui3/homepanel_needs.go b/internal/tui3/homepanel_needs.go index 80f9f3ca46..e45c72a4f4 100644 --- a/internal/tui3/homepanel_needs.go +++ b/internal/tui3/homepanel_needs.go @@ -80,10 +80,8 @@ func (needsPanel) rows(in *homeGridInput) homePanelRows { // needsFresh is the task calls that are still worth checking, and how many are // history. // -// A YOUR-CALL OLDER THAN [homeNeedsTaskFresh] IS HISTORY, NOT A QUESTION (owner, -// 2026-09-10: a machine with twenty-four week-old landings drew twenty-four rows -// over rows nobody was going to answer, and a live question arriving under them -// would have been the twenty-fifth). They stay one press away — the fold counts +// A YOUR-CALL OLDER THAN [homeNeedsTaskFresh] IS HISTORY, NOT A QUESTION. +// They stay one press away — the fold counts // them and opening the panel shows them. The tasks place also keeps every // record. Only a task's // call ages: a conversation stopped on a question and a watch that needs diff --git a/internal/tui3/homepanel_next.go b/internal/tui3/homepanel_next.go index f2ee58479b..c1aa844d85 100644 --- a/internal/tui3/homepanel_next.go +++ b/internal/tui3/homepanel_next.go @@ -18,8 +18,7 @@ func (nextPanel) rows(in *homeGridInput) homePanelRows { shown := min(in.cap(panelNext), len(upcoming)) lines := make([]homeLine, 0, shown) for _, item := range upcoming[:shown] { - // A ROW IS THE PERSON'S OWN WORDS AND NOTHING AT ITS RIGHT (owner, - // 2026-09-15): the row's time is said ONCE, in its description, under + // A ROW IS THE PERSON'S OWN WORDS AND NOTHING AT ITS RIGHT: the row's time is said ONCE, in its description, under // the cursor — and at every width, as the second line under the cursor's // row where the description column does not fit. lines = append(lines, homeLine{kind: homeLedger, project: placeAutomationsWord, dir: item.ID, diff --git a/internal/tui3/homepanel_takedoor_test.go b/internal/tui3/homepanel_takedoor_test.go index ad5d420671..75ff81b6fc 100644 --- a/internal/tui3/homepanel_takedoor_test.go +++ b/internal/tui3/homepanel_takedoor_test.go @@ -25,7 +25,7 @@ func (f *flipping) set(s chatlist.Status) { f.mu.Lock(); f.status = s; f.mu.Unlo func (f *flipping) Rows(context.Context) ([]chatlist.Row, error) { f.mu.Lock() defer f.mu.Unlock() - return []chatlist.Row{{Cell: heldCell, Title: "Mine", Device: "studio", Status: f.status, Mine: f.mine, DurableAgo: time.Minute}}, nil + return []chatlist.Row{{Cell: heldCell, Title: "Mine", Device: "host-a", Status: f.status, Mine: f.mine, DurableAgo: time.Minute}}, nil } // heldLab opens home with a chat this machine lists and the directory says @@ -62,7 +62,7 @@ func TestListedHereAndHeldElsewhereDrawsTheDoor(t *testing.T) { if local, remote := rowsOf(a, heldCell); local != 1 || remote != 1 { t.Fatalf("local=%d remote=%d rows, want one of each:\n%s", local, remote, homeText(a)) } - if !strings.Contains(homeText(a), "running on studio") { + if !strings.Contains(homeText(a), "running on host-a") { t.Fatalf("the held chat does not say where it runs:\n%s", homeText(a)) } } diff --git a/internal/tui3/hometip_test.go b/internal/tui3/hometip_test.go index 6aa95ff8db..478a606db5 100644 --- a/internal/tui3/hometip_test.go +++ b/internal/tui3/hometip_test.go @@ -245,13 +245,8 @@ func TestEveryTipIsOnTheManualPage(t *testing.T) { } } -// The cut was thirty, /project made it thirty-one, and three reads of the whole -// list by the owner took it to twenty-two, and the way home by two spaces made -// it twenty-three on 2026-09-24. The separate conversation-budget hint makes -// twenty-four, and /helpers made it twenty-five on 2026-10-19, at the owner's -// word. There is ONE set: every hint draws -// on both boxes, a news row on neither, and a row filed under home's slot does -// not build. +// There are twenty-five hints. Every hint draws on both boxes; news rows +// draw on neither, and a hint filed under home's slot does not build. func TestTheTableIsTwentyFiveHintsAndEveryOneDrawsOnBothBoxes(t *testing.T) { hints := 0 for _, n := range notices { diff --git a/internal/tui3/hop.go b/internal/tui3/hop.go index 4caa2dddf6..87ade6d932 100644 --- a/internal/tui3/hop.go +++ b/internal/tui3/hop.go @@ -560,7 +560,7 @@ func (a *app) hopTabbed(row hopRow) bool { // look for the conversation they were just looking at. Ordered by recency the // card put it somewhere else — the third tab could be the first row — so the two // lines disagreed about the same five conversations and neither position meant -// anything. Asked for by the owner. +// anything. // // THE STRIP'S ORDER IS FIRST-ENTERED AND IT NEVER MOVES (chattabs.go says why: a // person reaches for the position, not for the word). Taking that order here @@ -1162,8 +1162,8 @@ func (a *app) hopTake() (cmd tea.Cmd) { // settings alike. The cost of that is that taking a row moved the conversation // UNDERNEATH a place and left the place in front: from home, `enter` looked like // a key that did nothing, while it had in fact quietly swapped the conversation -// behind the screen the person was reading. Reported by the owner, who pressed -// `ctrl+k` on home, chose a conversation, and stayed on home. +// behind the screen the person was reading. Pressing +// `ctrl+k` on home and choosing a conversation must leave home. // // EVERY OTHER DOOR BETWEEN CONVERSATIONS ALREADY DOES IT, and each spells it for // itself: home's own `enter` ends in [app.closeHome] (home.go's @@ -1372,7 +1372,7 @@ func (a *app) hopCardLines(width, height int, pal palette) []string { // on, it appeared on some rows and not others depending on how long that one // name was, and it cost the rows underneath it their places on a short card. // The name is worth more room on the row itself, which is what the subject - // column now takes ([hopLine]). Asked for by the owner. + // column now takes ([hopLine]). // AND THE WORD THAT SAYS WHERE THE OPEN ONES STOP, which is the fold's own // half of the head's `open` ([hopClosedLabel]). // @@ -1488,7 +1488,7 @@ func (a *app) hopFoot() string { // the head already carries — `3 of 12` says both halves of it — and then said // the same thing twice, once in a triangle nobody reads as a verb and once in // words. A foot is the one row a person looks at to find out what else they can -// press; it is worth exactly one instruction. Asked for by the owner. +// press; it is worth exactly one instruction. // // `closed` IS THE WORD FOR WHAT IS DOWN THERE. Every row behind the fold is a // conversation with no tab on the row above — one this terminal never opened, @@ -1542,9 +1542,7 @@ const hopOpenWord = "open" // The head says `open` over the rows that are tabs. `→ show closed` then adds // rows that are not, and a conversation whose tab was closed a minute ago comes // back wearing the same `○` and the same live clause it had when it was open — -// because it IS still open in every sense but the tab row's. Reported by the -// owner, who read the `✕` on the rows below as "this tab is closed" and asked -// why the ones they had closed did not have it. They do not: `✕` is +// because it IS still open in every sense but the tab row's. The closed-tab marker must remain distinct from `✕` is // [tokens.GlyphFailed] and on this card it means the row REFUSES TO OPEN — // another window is holding that conversation, or its project folder has gone. // Two different facts, and only one of them had a mark. diff --git a/internal/tui3/narrow_test.go b/internal/tui3/narrow_test.go index 36f6bc1982..b2b78550b3 100644 --- a/internal/tui3/narrow_test.go +++ b/internal/tui3/narrow_test.go @@ -305,7 +305,7 @@ func pulseLab(t *testing.T) (*app, time.Time) { return a, now } -// THE TOP LINE GIVES THINGS UP IN THE OWNER'S ORDER. +// THE TOP LINE GIVES THINGS UP IN PRIORITY ORDER. // // Every width is checked: a thing given up early may never be on the row while a thing // given up later is gone, and every rung of the ladder is met. diff --git a/internal/tui3/pages_test.go b/internal/tui3/pages_test.go index 0f164be416..5dff75c9ff 100644 --- a/internal/tui3/pages_test.go +++ b/internal/tui3/pages_test.go @@ -66,7 +66,7 @@ func TestThePlacesAreOneList(t *testing.T) { } // AND alt+0 IS NOTHING, rather than a tenth place: the eight digits are the // seven rooms and the way back to the chats, teams second and the chats - // third (the owner's order). + // third (the specified order). if _, ok := placeDigit("alt+0"); ok { t.Fatal("alt+0 reaches a place that does not exist") } diff --git a/internal/tui3/placeeverymouse_test.go b/internal/tui3/placeeverymouse_test.go index 1b26411048..88e07cca7e 100644 --- a/internal/tui3/placeeverymouse_test.go +++ b/internal/tui3/placeeverymouse_test.go @@ -126,8 +126,8 @@ func placeWalkToTop(t *testing.T, a *app) { // THE POINTER PREVIEWS AND THE CURSOR SELECTS, AND THE MOTION MESSAGE IS WHAT // CARRIES IT. homehover_test.go proves home's card follows the pointer by // calling [app.homeHover] itself, which is the right test of the law and no -// test at all of the road: the owner drove the built binary and reported that -// nothing previewed under the pointer, and a motion message swallowed on its +// test at all of the road: if nothing previews under the pointer, a motion +// message swallowed on its // way through [app.Update] would look exactly like that with every hover test // in this package still green. func TestAMotionMessageMovesHomesCardAndSelection(t *testing.T) { @@ -653,8 +653,7 @@ func homeFoldDoor(t *testing.T, a *app, panel homePanelID) int { // [placeHeadingInk]), so every heading on home THAT IS A DOOR opens with // exactly the ink [placeHeading] opens a place's section word with. A heading // that opens nothing — `projects`, `threads` — is dim instead, word and -// explainer in one ink, so it cannot be mistaken for a door (owner, -// 2026-09-17). The panel holding the cursor wears the cursor's ground over its +// explainer in one ink, so it cannot be mistaken for a door. The panel holding the cursor wears the cursor's ground over its // heading and is left out. func TestHomesHeadingsWearThePlacesHeadingInk(t *testing.T) { a := newSwitchLab(t).open(120, 45) diff --git a/internal/tui3/placejump_test.go b/internal/tui3/placejump_test.go index e57adc1131..f3c58f7a58 100644 --- a/internal/tui3/placejump_test.go +++ b/internal/tui3/placejump_test.go @@ -110,9 +110,7 @@ func TestTheRightArrowOpensTheRowsVerbsOnEveryPlace(t *testing.T) { } } -// AND NEITHER ARROW IS A TAB KEY, ON ANY OF THE SEVEN. This is the audit the -// owner's "left right does not seem to move tabs" asks for, written as the law -// it protects: `tab`/`shift+tab` walk the rooms, `shift+←→↑↓` are the time +// AND NEITHER ARROW IS A TAB KEY, ON ANY OF THE SEVEN. The navigation law is: `tab`/`shift+tab` walk the rooms, `shift+←→↑↓` are the time // window (SCREEN 3d), and the plain arrows keep whatever they already meant on // the place a person is standing on. func TestThePlainArrowsSwitchNoPlaceAnywhere(t *testing.T) { diff --git a/internal/tui3/placemouse_test.go b/internal/tui3/placemouse_test.go index f569d3648c..31cee6af15 100644 --- a/internal/tui3/placemouse_test.go +++ b/internal/tui3/placemouse_test.go @@ -8,8 +8,7 @@ import ( // THE POINTER ON A PLACE, AS A PERSON MEETS IT. // -// Three gestures, one law each, and every one of them was reported broken by -// the owner against the real binary: a tab word is a door, the wheel moves the +// Three gestures, one law each: a tab word is a door, the wheel moves the // list under the pointer, and mouse and keyboard share the selected row. // autoPlaceLab is the automations place open over three automations. diff --git a/internal/tui3/question.go b/internal/tui3/question.go index f5cbdbaf6a..27e8644700 100644 --- a/internal/tui3/question.go +++ b/internal/tui3/question.go @@ -202,8 +202,7 @@ type questionShown struct { // pointed answer as [session.Answer.Change]) or [questionAskBackKey] after // `?` (the words go to the asker with the question still open). The // block's answers row becomes a prompt saying so while it is set, because - // a box whose meaning changed silently was the owner's "typing does not - // work · is there a separate typing place?" (2026-09-10). + // a box whose meaning changes silently leaves people unsure where to type. writing string // other is the person's OWN answer: the words typed into the panel's last // row, and the answer they will travel with where they travel with one. @@ -745,8 +744,7 @@ func questionSameHoles(fresh, held questionInput) bool { // questionPointerStart is where the person's pointer stands when a question // arrives. EVERY QUESTION WITH ANSWERS HAS A POINTER — the row `↑`/`↓` walk // and `enter` takes — because a block that answered only to digits, with the -// arrows doing nothing, was one nobody could tell how to work (the owner, -// 2026-09-10: "none of my arrow keys make it obvious"). It starts on the +// arrows doing nothing, was one nobody could tell how to work. It starts on the // asker's pick when there is one, so `enter` alone still takes the // recommendation; on the first answer otherwise; and on the answer that loses // nothing for a confirmation, which is the one shape where enter must never @@ -2358,8 +2356,7 @@ func (a *app) questionRecordRow(record questionRecord, width int) string { // that is the surface's — `c change` names a key on this keyboard, so it is // never given up and never counted against the record's own words. func (a *app) questionReceiptLine(record questionRecord, width int) string { - // THE RECEIPT OPENS WITH THE SETTLED MARK AND NOT THE WORD `decided` (owner - // ruling 2026-09-11, after-you-answer pick A): `✓ → · you · + // THE RECEIPT OPENS WITH THE SETTLED MARK AND NOT THE WORD `decided`: `✓ → · you · // 14:02`. The mark is the vocabulary's ([tokens.GSettled], the same one the // ratify line wears) and it says what the word said in one cell, which is // what leaves room for the answer's own words on a narrow row. @@ -3926,7 +3923,7 @@ func (a *app) questionUndo(head questionShown) tea.Cmd { // questionDial is `D`: decide questions of this shape from now on, without // asking. // -// THE DIAL'S STORAGE IS LANE E2'S (`autonomy.json`, per project) AND THIS IS +// THE DIAL'S STORAGE IS PER PROJECT (`autonomy.json`, per project) AND THIS IS // THE SEAM ONTO IT — wired now, through [session.Agent.SetAutonomy] and the wire // frame that carries it to an engine in another process (internal/remote's // MethodSetAutonomy, which every local chat window goes through). diff --git a/internal/tui3/questionevidence_test.go b/internal/tui3/questionevidence_test.go index 1ccc285563..7a6eb4e811 100644 --- a/internal/tui3/questionevidence_test.go +++ b/internal/tui3/questionevidence_test.go @@ -2,7 +2,7 @@ package tui3 // THE EVIDENCE AN ANSWER BRINGS, WHEREVER A QUESTION IS DRAWN. // -// These hold the owner's three picks of 2026-09-11 about evidence: at a hundred +// These hold the evidence layout contract: at a hundred // columns and wider a panel whose answers brought something to look at lays // them in a list with the evidence of the one the pointer is on beside it // (preview A); narrower, that evidence unfolds under the pointer's row diff --git a/internal/tui3/questionkeys.go b/internal/tui3/questionkeys.go index b891f70467..f3bf2b21b8 100644 --- a/internal/tui3/questionkeys.go +++ b/internal/tui3/questionkeys.go @@ -60,7 +60,7 @@ type questionVerb struct { // and whatever key is the ANSWER on a shape that has one (`space` on a // checklist, `a`/`b` on a pair) — and the secondary tier is everything that // says something ABOUT the decision rather than giving it. A row that mixed - // them was the owner's "no hierarchy": answers, navigation, dismissal, four + // them had no hierarchy: answers, navigation, dismissal, four // meta-verbs and a clock at one hue and one weight. tier keyTier // giveUp is the order a narrow row surrenders its keys in — the HIGHEST @@ -537,7 +537,7 @@ func questionKeyWord(key string) string { // wherever a person is standing — the status line's chip says it, and it works // on every page. // -// The owner assigned `alt+a` to approvals and moved this door to `alt+y`. +// Approvals uses `alt+a`; pending questions use `alt+y`. // It still works on every page, including while the draft has text in it. const questionChipKey = "alt+y" @@ -835,7 +835,7 @@ func questionOwnsBox(q session.Question) bool { // // AIMING IS A KEY THAT COULD NEVER BE TEXT, or a click on the block's own rows: // `↑↓` choosing, `tab` walking, `enter` taking, `esc` putting it off, the mouse -// landing on an answer. That is the owner's own grammar for this block read in +// landing on an answer. That is the interaction order for this block, read in // the order a hand uses it — choose, then take it, then put it off // — and every one of those gestures is somebody // looking at the question rather than at the box. diff --git a/internal/tui3/questionset.go b/internal/tui3/questionset.go index 4f39ddb7a7..e1785a1a96 100644 --- a/internal/tui3/questionset.go +++ b/internal/tui3/questionset.go @@ -57,7 +57,7 @@ import ( // │ │ // ╰─ enter send · ← back · esc later ────────────────────────────────────╯ // -// ONE PERMISSION FRAME (the owner's approved "group permissions"): where every +// ONE PERMISSION FRAME: where every // question in the set is a permission with a plain grant and an answer that // loses nothing, the set is ONE decision about several calls, and it is asked // as one. `one by one` opens the same set as the tabs above. diff --git a/internal/tui3/render.go b/internal/tui3/render.go index e4aa583210..a59e859965 100644 --- a/internal/tui3/render.go +++ b/internal/tui3/render.go @@ -3246,8 +3246,7 @@ func (a *app) warmSegment() string { if !ok { return "" } - // The word rides with the rate because the rate alone was the owner's own - // stumble: two percentages share this line, and the one that means "of the + // The word rides with the rate to distinguish the two readings: two percentages share this line, and the one that means "of the // window" and the one that means "served from the cache" are told apart by // a word, not by position. It is the same word the per-turn note spends on // the same subject ("⟲ 9.8k cached"), so the running share and the turn diff --git a/internal/tui3/roommodel_test.go b/internal/tui3/roommodel_test.go index 8f98f59090..b18941badb 100644 --- a/internal/tui3/roommodel_test.go +++ b/internal/tui3/roommodel_test.go @@ -460,9 +460,8 @@ func TestARoomsFootIsTheWayOutTheNumbersAndTheKeys(t *testing.T) { // THE ROOM SAYS WHEN, AND IT SAYS THE TRUE ONE OF THE TWO. // -// The sentence it used to say was `its next turn takes it`, and on 2026-09-11 it -// was read by the owner over a task step thirteen minutes into a wait, where the -// next turn was a promise about nothing. What the engine answers now is WHEN the +// The sentence `its next turn takes it` promises nothing during a long task +// step. What the engine answers now is WHEN the // pick landed (internal/session's [session.ModelLanding]) and the room spells // exactly that — never the word "turn", because a step is one turn and can run // for twenty minutes. diff --git a/internal/tui3/salience_test.go b/internal/tui3/salience_test.go index 40b1614743..ad4aeeaa57 100644 --- a/internal/tui3/salience_test.go +++ b/internal/tui3/salience_test.go @@ -4,8 +4,7 @@ package tui3 // // THE LENS MAY LOWER SALIENCE; IT MAY NOT DROP A FACT. // -// That sentence is the whole of the implementation's Decision 4, and -// this file is the whole of its enforcement. A page may fold a fact away, dim +// This file enforces that rule. A page may fold a fact away, dim // it, put it a keypress down or draw it somewhere else entirely — what it may // not do is fail to have it. // @@ -22,7 +21,7 @@ package tui3 // does. The enumeration is read back out of internal/session's own source, so a // kind added there and forgotten here fails this test rather than shipping. // -// Lane L3 extends the same shape with the journal-role half — a steer mark, a +// Journal replay extends the same shape with the journal-role half — a steer mark, a // note, a divider replayed through both shapers — which is why the case carries // a name and a reason rather than a bare pair of event kinds. @@ -117,11 +116,11 @@ var salienceTable = []salienceCase{ "scroll", ev: session.Event{Kind: session.EventTurnDone}}, {name: "EventSteerAccepted", chatOnly: "the elbow's landing clause is the chat's today; a room's steer " + - "becomes the same elbow in lane L3", + "uses the same elbow during journal replay", ev: session.Event{Kind: session.EventSteerAccepted, Steer: &session.SteerNote{ID: 1, Words: "use the other file"}}}, - {name: "EventSteerConsumed", chatOnly: "the other half of the elbow's lifecycle, and lane L3's for the same " + + {name: "EventSteerConsumed", chatOnly: "the other half of the elbow's lifecycle, and journal replay uses it for the same " + "reason", ev: session.Event{Kind: session.EventSteerConsumed, Steer: &session.SteerNote{ID: 1, Words: "use the other file"}}}, - {name: "EventSteerFellThrough", chatOnly: "the third half of the elbow's lifecycle, and lane L3's for the " + + {name: "EventSteerFellThrough", chatOnly: "the third half of the elbow's lifecycle, and journal replay uses it for the " + "same reason", ev: session.Event{Kind: session.EventSteerFellThrough, Steer: &session.SteerNote{ID: 1, Words: "use the other file"}}}, {name: "EventConnectAsk", chatOnly: "connecting an account is a question, and a node cannot be asked one", ev: session.Event{Kind: session.EventConnectAsk, Service: "google", ServiceName: "Google"}}, diff --git a/internal/tui3/surface_test.go b/internal/tui3/surface_test.go index 57ebbf1528..9dda578e4c 100644 --- a/internal/tui3/surface_test.go +++ b/internal/tui3/surface_test.go @@ -15,7 +15,7 @@ import ( "github.com/Agent-Field/codeaf/internal/tui2/tokens" ) -// The wave-2 surface: what a resumed session shows, what ctrl+c means, how a +// The resumed-session surface: what a resumed session shows, what ctrl+c means, how a // draft is composed, recalled, completed and kept. // keyed spells the keys this file presses that the shared helper does not know. diff --git a/internal/tui3/switcher.go b/internal/tui3/switcher.go index 5b476b6e29..8edbf50267 100644 --- a/internal/tui3/switcher.go +++ b/internal/tui3/switcher.go @@ -97,8 +97,7 @@ type switcherRow struct { // [app.homeLedgerEnter]). note is the one fact such a line carries besides // its title — a task's cost, the conversation a file was made in — and it is // the line's DESCRIPTION, drawn under the cursor, and not its margin: the - // margin is when it happened, like every row of the field (owner, - // 2026-09-15). + // margin is when it happened, like every row of the field. task *session.TaskIndexEntry path string } diff --git a/internal/tui3/taskdecided_test.go b/internal/tui3/taskdecided_test.go index 48eadb0d50..683932717a 100644 --- a/internal/tui3/taskdecided_test.go +++ b/internal/tui3/taskdecided_test.go @@ -34,8 +34,8 @@ func settledCard(a *app, id uint64) (*taskCard, bool) { return nil, false } -// TestAProposalDecidedBeforeThisWindowArrivedIsDrawnAnsweredAndNotAsked is the -// owner's defect from the surface's side: approve a task, switch tab, come back, +// TestAProposalDecidedBeforeThisWindowArrivedIsDrawnAnsweredAndNotAsked is a +// repeated-approval regression: approve a task, switch tab, come back, // and be asked the same question again — every time, with the question taking // the keyboard so the conversation behind it could not be scrolled either. func TestAProposalDecidedBeforeThisWindowArrivedIsDrawnAnsweredAndNotAsked(t *testing.T) { diff --git a/internal/tui3/taskguestprogram_test.go b/internal/tui3/taskguestprogram_test.go index ec4d4ec2c9..9cc2be68de 100644 --- a/internal/tui3/taskguestprogram_test.go +++ b/internal/tui3/taskguestprogram_test.go @@ -39,7 +39,7 @@ func enterAwayPumped(t *testing.T, a *app) { drain(t, a, a.tookTaskOwner(msg)) } -// guestProgramLab is [guestLab] with the owner's task 7 handed to senior-dev: +// guestProgramLab is [guestLab] with an example task 7 handed to senior-dev: // its store answers the program's page for it. func guestProgramLab(t *testing.T) (*app, *guestDoor) { t.Helper() diff --git a/internal/tui3/teamlink.go b/internal/tui3/teamlink.go index 0d6c6b0627..be343cbbfb 100644 --- a/internal/tui3/teamlink.go +++ b/internal/tui3/teamlink.go @@ -40,7 +40,7 @@ import ( // branch… · click`. // // THE HOVER IS A GROUND, as every other control on this surface answers the -// pointer, and not the task link's brightening: the owner's bar is that +// pointer, and not the task link's brightening: the interaction rule is that // everything clickable has a hover ground. // // THE ORDINALS ARE THE TASK LINKS' OWN SPACE, offset by [teamLinkOrd]. A block diff --git a/internal/tui3/toolclamp_test.go b/internal/tui3/toolclamp_test.go index 1a8d207b2a..626d967792 100644 --- a/internal/tui3/toolclamp_test.go +++ b/internal/tui3/toolclamp_test.go @@ -44,7 +44,7 @@ func clampArgs(field, value string) string { return string(b) } -const clampCommand = `gh api "search/issues?q=repo:Agent-Field/codeaf+commenter:AbirAbbas&sort=created&order=desc&per_page=30" -q '.items[] | .number'` +const clampCommand = `gh api "search/issues?q=repo:Agent-Field/codeaf+commenter:example-user&sort=created&order=desc&per_page=30" -q '.items[] | .number'` // callRowsOf is every row the transcript draws for a tool call — the line and // whatever it hangs — plain. diff --git a/internal/tui3/topnav.go b/internal/tui3/topnav.go index fa9f7d6b42..fb72521451 100644 --- a/internal/tui3/topnav.go +++ b/internal/tui3/topnav.go @@ -43,7 +43,7 @@ import ( // // ── THE WIDTH LADDER ── // -// A row too narrow for everything gives things up IN THE OWNER'S ORDER +// A row too narrow for everything gives things up IN PRIORITY ORDER // (2026-09-24): the clock goes first (and the machine's name with it, the // other fact every terminal title already carries), then the moving count, // then the words of `2 want you`, which shortens to `2 ?` in the same amber, diff --git a/internal/tui3/wiring_test.go b/internal/tui3/wiring_test.go index 67fa5eb7f4..1d2cf6b6ad 100644 --- a/internal/tui3/wiring_test.go +++ b/internal/tui3/wiring_test.go @@ -12,13 +12,12 @@ import ( "github.com/Agent-Field/codeaf/internal/tui2/tokens" ) -// The wave-4 surface: the approval question, the session's name, ctrl+enter's +// The conversation controls: the approval question, the session's name, ctrl+enter's // queue, and the model's own thinking. -// ── the scripted agent's wave-4 methods ───────────────────────────────────── +// ── the scripted agent's control methods ───────────────────────────────────── // -// They live here rather than beside [fakeAgent] because they arrived with this -// wave: the plain fake answers them the way a session with nothing to say does, +// They live here rather than beside [fakeAgent] because they cover these controls: the plain fake answers them the way a session with nothing to say does, // and [wiredAgent] below is the one that records. func (f *fakeAgent) FollowUp(string) (<-chan session.Event, error) { diff --git a/internal/tui3/wormhole_contract_test.go b/internal/tui3/wormhole_contract_test.go index 304c258ea5..aca8b5cb21 100644 --- a/internal/tui3/wormhole_contract_test.go +++ b/internal/tui3/wormhole_contract_test.go @@ -35,7 +35,7 @@ func TestC5OnlineContinueOpensTheArrivedCopy(t *testing.T) { t.Fatal(err) } a.machines = continueRows - a.taker = &fakeTaker{taken: Taken{Transcript: transcript, Elapsed: 3 * time.Second, Resume: machine.Resume{From: "studio", Now: work, Uncommitted: []string{"README.md"}}}, on: func(id string) { + a.taker = &fakeTaker{taken: Taken{Transcript: transcript, Elapsed: 3 * time.Second, Resume: machine.Resume{From: "host-a", Now: work, Uncommitted: []string{"README.md"}}}, on: func(id string) { // This is the stale derived row read immediately after the handoff. It has // the right transcript and an old or unavailable project path. if err := session.SaveMeta(at, session.Meta{ID: id, Title: "Arrived", Workspace: filepath.Join(lab.root, "gone"), Created: time.Now(), LastUserAt: time.Now()}); err != nil { @@ -51,7 +51,7 @@ func TestC5OnlineContinueOpensTheArrivedCopy(t *testing.T) { if a.at(pageHome) || a.file != transcript || a.workspace != work { t.Fatalf("continue here stayed on home or opened %s; wanted %s\n%s", a.file, transcript, homeText(a)) } - if got := plain(lastNote(t, a)); !strings.Contains(got, "Moved from studio in 3s") { + if got := plain(lastNote(t, a)); !strings.Contains(got, "Moved from host-a in 3s") { t.Fatalf("no arrival banner: %s", got) } q := askedCard(t, a).question @@ -87,7 +87,7 @@ func TestC5OwnershipReadKeepsDisplacedChords(t *testing.T) { t.Run(chord, func(t *testing.T) { a, agent := bargeable(t, "still answering") a.file = filepath.Join(t.TempDir(), "current", ".cell", "transcript.jsonl") - a.machines = chatlist.Static{{Cell: "current", Device: "studio", Status: chatlist.Running}} + a.machines = chatlist.Static{{Cell: "current", Device: "host-a", Status: chatlist.Running}} a.taker = &fakeTaker{} typeInto(t, a, "keep my draft") drive(t, a, key(chord)) diff --git a/scripts/continuity.py b/scripts/continuity.py index 50b199e0af..f8f40feee2 100644 --- a/scripts/continuity.py +++ b/scripts/continuity.py @@ -143,14 +143,14 @@ def pair_fresh(self): def pair_once(self): """The six-digit-style code way: A shows a code, B types it, A answers y. The staging mailbox lives 4 s, so it is scripted.""" sh(f"tmux -L vcp{SUF} kill-server", check=False) - sh(f"tmux -L vcp{SUF} new-session -d -s vcp -x 150 -y 30 \"bash -lc '. {A.root}/env.sh; codeaf pair --code --name spark-a; echo EXIT=\\$?; sleep 60'\"") + sh(f"tmux -L vcp{SUF} new-session -d -s vcp -x 150 -y 30 \"bash -lc '. {A.root}/env.sh; codeaf pair --code --name host-a; echo EXIT=\\$?; sleep 60'\"") code, t0 = None, time.time() while time.time() - t0 < 90 and not code: m = re.search(r"codeaf pair (\d\d-\d\d\d-\d\d\d)", sh(f"tmux -L vcp{SUF} capture-pane -p -t vcp").stdout) code = m and m.group(1) time.sleep(0.05) assert code, "no pairing code shown" - join = subprocess.Popen(["bash", "-lc", f". {B.root}/env.sh; codeaf pair --name spark-b {code}"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True) + join = subprocess.Popen(["bash", "-lc", f". {B.root}/env.sh; codeaf pair --name host-b {code}"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True) answered = False while time.time() - t0 < 40: out = sh(f"tmux -L vcp{SUF} capture-pane -p -t vcp").stdout diff --git a/scripts/fidelity.py b/scripts/fidelity.py index e767be56ab..6379754a67 100644 --- a/scripts/fidelity.py +++ b/scripts/fidelity.py @@ -114,14 +114,14 @@ def reset(self, fixture=True): def pair(self): sh("tmux -L vfp kill-server", check=False) - sh(f"tmux -L vfp new-session -d -s vfp -x 150 -y 30 \"bash -lc '. {A_ROOT}/env.sh; codeaf pair --code --name spark-a; echo EXIT=\\$?; sleep 60'\"") + sh(f"tmux -L vfp new-session -d -s vfp -x 150 -y 30 \"bash -lc '. {A_ROOT}/env.sh; codeaf pair --code --name host-a; echo EXIT=\\$?; sleep 60'\"") code, t0 = None, time.time() while time.time() - t0 < 60 and not code: m = re.search(r"codeaf pair (\d\d-\d\d\d-\d\d\d)", sh("tmux -L vfp capture-pane -p -t vfp", check=False).stdout) code = m and m.group(1) time.sleep(0.05) assert code, "no pairing code shown" - join = subprocess.Popen(["ssh", "-o", f"ControlPath={CTL}", B_HOST, f"bash -l -c '. {B_ROOT}/env.sh; codeaf pair --name dumb-b {code}'"], + join = subprocess.Popen(["ssh", "-o", f"ControlPath={CTL}", B_HOST, f"bash -l -c '. {B_ROOT}/env.sh; codeaf pair --name host-b {code}'"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True) answered = False while time.time() - t0 < 60: diff --git a/scripts/hosted-validate.py b/scripts/hosted-validate.py index d791deed28..4c32a7d4ef 100644 --- a/scripts/hosted-validate.py +++ b/scripts/hosted-validate.py @@ -162,7 +162,7 @@ def pair(self): t0 = time.time() shown = f"{self.dir}/b-pair-invite.txt" with open(shown, "w") as f: - join = subprocess.Popen(["ssh", "-o", f"ControlPath={CTL}", B_HOST, f"bash -l -c '. {B_R}/env.sh; codeaf pair --name dumb-b'"], + join = subprocess.Popen(["ssh", "-o", f"ControlPath={CTL}", B_HOST, f"bash -l -c '. {B_R}/env.sh; codeaf pair --name host-b'"], stdout=f, stderr=subprocess.STDOUT, text=True) link, check = self.wait_for(lambda: parse_invite(open(shown).read()), 20, "B never showed a link and a check number") approve = f". {A_R}/env.sh; codeaf pair approve {shlex.quote(link)}; echo EXIT=$?; sleep 120"