From b80f6933191fefc4df062652fc0e20165da9d47a Mon Sep 17 00:00:00 2001 From: Hugh Francis Date: Sat, 14 Nov 2026 04:20:43 +0000 Subject: [PATCH] check-ticket: say how to clear "outside touches" after ticket set #196 named the fix for a ticket added since the log last landed. The same holds for a ticket the supervisor changed with ticket set (touches, done-whens): the forge checks it as main's log has it, so widened touches fail "changes files outside touches" until log-sync and merge --chore log land the change. A supervisor recorded this twice (stacks-117, stacks-213). The error now says so, and supervisor.md's Merging bullet covers ticket set as well as ticket add. Co-Authored-By: Claude Opus 5.5 --- docs/devteam/process/supervisor.md | 2 +- docs/devteam/scripts/devteam.mjs | 2 +- test/fixtures/g1/check-ticket.txt | 2 +- test/fixtures/g1/merge-dry-run.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/devteam/process/supervisor.md b/docs/devteam/process/supervisor.md index 7645e1d2..1147a568 100644 --- a/docs/devteam/process/supervisor.md +++ b/docs/devteam/process/supervisor.md @@ -124,7 +124,7 @@ Production is watched by **sensors**, and they are yours: you add, change and st - `devteam.mjs ready` lists tickets whose dependencies are merged and that have no worker; `--plan-ahead` adds those that can spec and plan now. To keep a ready ticket unscheduled on purpose (it may duplicate other work, it waits on the owner), `ticket set --json '{"held": ""}'`; `ready` and the idle-capacity finding then leave it out, and `{"held": null}` releases it. Caps: `max_active_tickets` (your review capacity is the real limit) and `max_active_per_component`. The machine is shared with other projects' fleets, so a new worker or planner doesn't start while the machine's memory is at `machine_memory_ceiling` (default 0.8 of its cap, from 0.5 to 0.95); a resume of a session that already ran always starts (`--resume` on one that never ran is a new start). When a start is refused for memory, or the `memory-full` finding shows in place of idle capacity, wait for sessions to finish rather than passing `--force`: forcing it is how the box runs out and restarts every fleet on it. If you keep hitting the ceiling, lower your own `max_active_tickets`; it is your setting, not a question for the owner. Prefer the critical path, then contract tickets, then tickets that close a story. - `devteam.mjs worker `; `worker --resume` relaunches a crashed one in the same window and worktree with `resume_command`. -- **Merging.** A `merge-request` message names the exact head. Read the diff, the final review record and `verification.md` yourself, then `devteam.mjs merge `. It runs `check-ticket` at that head, the log rule, and every gate in the settings in a clean worktree at that head, merges with `--match-head-commit` (or, with `forge: "git"`, merges that sha and pushes), and logs the act. `--dry-run` does everything but the merge (and works on a branch with no PR yet). A ticket you added since the log last landed fails the forge's checks (`project/tickets// has no ticket`) until its line is on main, because the forge reads only the landed log: land the log first (`log-sync`, then `merge --chore log`) and rerun them. If anything fails it says what; reply to the worker with `--kind ruling --re `. A head that moved after the request is a new request. You never merge any other way, and workers never merge. +- **Merging.** A `merge-request` message names the exact head. Read the diff, the final review record and `verification.md` yourself, then `devteam.mjs merge `. It runs `check-ticket` at that head, the log rule, and every gate in the settings in a clean worktree at that head, merges with `--match-head-commit` (or, with `forge: "git"`, merges that sha and pushes), and logs the act. `--dry-run` does everything but the merge (and works on a branch with no PR yet). A ticket you added or changed (`ticket add`, or `ticket set` on touches or done-whens) since the log last landed is checked as main's log has it: a new one fails (`project/tickets// has no ticket`), widened touches fail (`changes files outside touches`), until its line is on main, because the forge reads only the landed log: land the log first (`log-sync`, then `merge --chore log`) and rerun them. If anything fails it says what; reply to the worker with `--kind ruling --re `. A head that moved after the request is a new request. You never merge any other way, and workers never merge. - **Other repos.** `merge` works per repo; the ticket says which (its component's `link`). `fleet status` lists each repo link with its path, and a sibling whose origin it can't fetch is a blocker. A ticket whose repo has no checkout on this machine waits as "repo unreachable"; `link-unreachable` says what to clone where. - **Chore PRs** (`merge --chore `: log, retros, designs, the architecture doc) may not touch `project/log` (except `chore-log`, which touches nothing else) or `project/tickets`. One that changes anything outside `project/` (`.claude/`, `.github/`, the kit) needs the owner's answer, `merge --chore --approved `; one that changes a real kit file (under the kit's own path, other than `project/` and `run/`) needs it in a `governance-edit`, `process-edit` or `gate-change` question, however it was given (chat counts). Kit files merged verbatim from upstream need no question (`chore-devteam-sync-` under `upstream_sync` `"adopt"` or a standing rule, ยง9), and a secret chore (`chore-secret-`) lands the encrypted stores with the answer that posted them. - **The forge's checks.** `merge` refuses while any check on the PR's head is pending or failing, whether or not branch protection requires it (a deploy pipeline's status counts too). Wait and merge again. A check that's broken or irrelevant is the owner's to skip, in `ignored_checks`. diff --git a/docs/devteam/scripts/devteam.mjs b/docs/devteam/scripts/devteam.mjs index 03d8e7c2..89ccf390 100644 --- a/docs/devteam/scripts/devteam.mjs +++ b/docs/devteam/scripts/devteam.mjs @@ -3722,7 +3722,7 @@ function checkTicket(id, { sha: shaArg, title, body, approvals = true }) { const own = R.projectInRepo ? `${R.projectRel}/tickets/${id}/` : null; const mineOwn = (f) => !!own && f.startsWith(own); const outside = changed.filter((f) => !mineOwn(f) && !(t.touches ?? []).some((g) => pathMatches(g, f))); - if (outside.length) fails.push(`changes files outside touches: ${outside.slice(0, 10).join(', ')}${outside.length > 10 ? ` (+${outside.length - 10} more)` : ''}`); + if (outside.length) fails.push(`changes files outside touches: ${outside.slice(0, 10).join(', ')}${outside.length > 10 ? ` (+${outside.length - 10} more)` : ''}; if the supervisor widened touches with ticket set since the log last landed, land the log first (log-sync, then merge --chore log) and check again`); // Component lines: a changed file is also inside the ticket's component's line or // a touches_components member's; the kit's own line is closed but for the ticket's own folder. // Judged by main's committed settings, so neither a line widened in a working tree nor the diff --git a/test/fixtures/g1/check-ticket.txt b/test/fixtures/g1/check-ticket.txt index 268d8988..217d3477 100644 --- a/test/fixtures/g1/check-ticket.txt +++ b/test/fixtures/g1/check-ticket.txt @@ -1,5 +1,5 @@ exit 1 --- stdout -fail: changes files outside touches: OUTSIDE.md +fail: changes files outside touches: OUTSIDE.md; if the supervisor widened touches with ticket set since the log last landed, land the log first (log-sync, then merge --chore log) and check again --- stderr diff --git a/test/fixtures/g1/merge-dry-run.txt b/test/fixtures/g1/merge-dry-run.txt index 6a27cf3f..19c38ee4 100644 --- a/test/fixtures/g1/merge-dry-run.txt +++ b/test/fixtures/g1/merge-dry-run.txt @@ -1,6 +1,6 @@ exit 1 --- stdout -fail: changes files outside touches: OUTSIDE.md +fail: changes files outside touches: OUTSIDE.md; if the supervisor widened touches with ticket set since the log last landed, land the log first (log-sync, then merge --chore log) and check again fail: no gates in settings (gates: {name: command}); pass --no-gates to merge without running any --- stderr