tickmarkr 1.92.1 → 1.96.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/run/merge.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { existsSync, writeFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { shq } from "../adapters/types.js";
4
- import { classifyFailureOutput, fingerprint, freshFailures } from "../gates/baseline.js";
4
+ import { ceilingKillResult, classifyFailureOutput, effectiveCeilingMs, fingerprint, freshFailures, } from "../gates/baseline.js";
5
5
  import { tickmarkrDir } from "../graph/graph.js";
6
6
  import { gitHead, linkNodeModules, resolveIntegrationBranch, sh, shGit, shGitOk, WORKTREES_DIR } from "./git.js";
7
7
  export function integrationBranch(cfg, runId) {
@@ -53,21 +53,55 @@ export async function mergeTask(intWt, taskBranch, message, gatedCommit) {
53
53
  export async function verifyIntegrationTip(intWt, commands, runDir, baseline) {
54
54
  const results = [];
55
55
  for (const [gate, cmd] of Object.entries(commands)) {
56
- const r = await sh(cmd, intWt);
56
+ const entry = baseline?.commands[gate];
57
+ // OBS-534: the ceiling is the BATTERY's, derived by effectiveCeilingMs from the same baseline entry
58
+ // this loop already reads for forgiveness two lines down — never the flat DEFAULT_SHELL_TIMEOUT_MS
59
+ // `sh` defaults to. A suite whose capture measured 600007ms carries a recorded 1800021ms ceiling;
60
+ // running it under 600000ms here SIGKILLed a green tip three times while every per-task gate passed.
61
+ const ceilingMs = effectiveCeilingMs(entry);
62
+ const r = await sh(cmd, intWt, ceilingMs);
57
63
  const raw = r.stdout + "\n" + r.stderr;
58
64
  const stripped = raw.split(intWt).join("");
59
- const entry = baseline?.commands[gate];
65
+ const artifact = join(runDir, `tip-verify-${gate}.log`);
66
+ // Battery parity on the ceiling too (baseline.ts Q24): the kill is read BEFORE the exit code is
67
+ // interpreted at all. A SIGKILLed battery never returned a verdict, so no line of its partial
68
+ // output is one — fingerprinting it is what produced the `<unrecognized failure output>` an
69
+ // operator cannot act on. The kill reader's own text (ceiling + elapsed) and cause replace it.
70
+ const killed = ceilingKillResult(gate, r, ceilingMs);
71
+ if (killed) {
72
+ writeFileSync(artifact, raw);
73
+ results.push({
74
+ gate,
75
+ cmd,
76
+ pass: false,
77
+ exitCode: r.code,
78
+ fingerprints: [],
79
+ details: killed.details,
80
+ cause: killed.meta?.classification,
81
+ artifact,
82
+ });
83
+ continue;
84
+ }
60
85
  const { failing, unreadable } = freshFailures(entry, stripped);
86
+ const cause = r.code === 0 ? undefined : classifyFailureOutput(stripped);
61
87
  // `?? 1` is the battery's own default (baseline.ts compareToBaseline): an exitCode-less legacy
62
88
  // entry reads as red-at-baseline there, so it must read the same here or old baselines silently
63
- // lose forgiveness. Battery parity on the infra rule too (T9): infrastructure-only output means
64
- // the runner never completed a suite — nothing was verified, so nothing is forgivable, however
65
- // familiar its fingerprints. Stricter-than-battery edge kept: unreadable output never forgives.
66
- const forgiven = r.code !== 0 && entry !== undefined && (entry.exitCode ?? 1) !== 0
67
- && failing.length === 0 && !unreadable && classifyFailureOutput(stripped) !== "infra";
89
+ // lose forgiveness. OBS-534 (T2): a capture killed at its ceiling now records a CAUSE and no
90
+ // verdict, and that default must not launder the missing exit code back into red-at-baseline.
91
+ // Only a recorded verdict is forgivable, so this reads the same predicate the battery does
92
+ // (baseline.ts `baselineRed`) — `infra` first, the legacy default only after it. freshFailures
93
+ // already drops the killed capture's flushed fingerprints, but it cannot close this alone: an
94
+ // output whose only shape is a diagnostic HEADING (vitest's "Unhandled Errors" banner) is
95
+ // fingerprintable yet OBS-42-exempt from rejecting, so `failing` comes back empty, `unreadable`
96
+ // false and the cause reads "regression" — every other guard satisfied, and a real red forgiven
97
+ // against a capture that never finished asking the question.
98
+ const baselineRed = entry !== undefined && entry.infra !== true && (entry.exitCode ?? 1) !== 0;
99
+ // Battery parity on the infra rule too (T9): infrastructure-only output means the runner never
100
+ // completed a suite — nothing was verified, so nothing is forgivable, however familiar its
101
+ // fingerprints. Stricter-than-battery edge kept: unreadable output never forgives.
102
+ const forgiven = r.code !== 0 && baselineRed && failing.length === 0 && !unreadable && cause !== "infra";
68
103
  const pass = r.code === 0 || forgiven;
69
- const artifact = pass ? undefined : join(runDir, `tip-verify-${gate}.log`);
70
- if (artifact)
104
+ if (!pass)
71
105
  writeFileSync(artifact, raw);
72
106
  results.push({
73
107
  gate,
@@ -79,7 +113,8 @@ export async function verifyIntegrationTip(intWt, commands, runDir, baseline) {
79
113
  : forgiven ? `exit ${r.code} but only baseline-recorded failures (forgiven vs baseline)`
80
114
  : `exit ${r.code}`,
81
115
  ...(forgiven ? { forgiven: true } : {}),
82
- ...(artifact ? { artifact } : {}),
116
+ ...(cause ? { cause } : {}),
117
+ ...(pass ? {} : { artifact }),
83
118
  });
84
119
  }
85
120
  return results;
@@ -20,7 +20,7 @@ Implement the first objective sentence. Additional prose that is not the title.
20
20
  </objective>
21
21
 
22
22
  <context>
23
- @.planning/PROJECT.md
23
+ @fixtures/gsd-sample/PROJECT.md
24
24
  @$HOME/.claude/get-shit-done/workflows/execute-plan.md
25
25
  @~/somewhere/outside.md
26
26
  </context>
@@ -0,0 +1,19 @@
1
+ # Sample project — vendored GSD fixture input
2
+
3
+ This file exists so the sibling phase's `07-01-PLAN.md` can cite a repo-relative `<context>` path that
4
+ is **part of the fixture itself**. It previously cited `.planning/PROJECT.md`, which resolved only
5
+ inside the private development checkout: the public export strips `.planning/` at any depth, so the
6
+ compiler's context-reachability refusal (v1.96 T3) correctly failed eight tests on the exported tree
7
+ and the release ritual's pre-tag proof caught it. A vendored fixture must carry its own inputs.
8
+
9
+ Nothing here is read by an assertion — the fixture only needs the path to resolve. The prose stands in
10
+ for the project brief a real GSD plan would point a worker at.
11
+
12
+ ## Objective
13
+
14
+ Ship a small feature end to end, with each plan in the phase owning one objective sentence.
15
+
16
+ ## Constraints
17
+
18
+ - One plan, one objective.
19
+ - A plan's `<context>` block promises the worker can read every repo-relative path it lists.
@@ -0,0 +1,68 @@
1
+ <!-- tickmarkr:spec -->
2
+ <!-- provenance: the shape of .planning/phases/99-arabic-coverage as compiled on 2026-08-18 into
3
+ run-20260818-185710-0000000000000011 (41 tasks). OBS-535's fix — files[] is write scope, not
4
+ payload — was measured against that graph (41 unreadable-payload lints to 0; 31 overflow lints
5
+ when the window is forced to 50k) and against nothing in this repo, because no fixture carried
6
+ its shape. This one does, at five tasks instead of forty-one.
7
+
8
+ The four shape features that made every task on that graph skip its context-window comparison,
9
+ each present below:
10
+ 1. a brace-glob write scope (`scripts/{a,b}`) — a set, not a document; unmeasurable as one file
11
+ 2. an output declared in files[] (`*-SUMMARY.md`) — absent from the base tree BY CONSTRUCTION
12
+ 3. a wave-1 artifact consumed by a TRANSITIVE dependent (T3 reads what T1 writes, via T2)
13
+ 4. the same artifact cited by a task with NO producer upstream (T4) — the actionable class,
14
+ which on the real graph was 17 tasks citing a self-gitignored tree (RULING-P99-14)
15
+ T5 adds the class no commit can ever satisfy: a ref under a gitignored directory.
16
+
17
+ Compile this fixture from a NON-REPO directory. compileNative's context reachability check
18
+ (native.ts:684) fails open when git cannot answer, and T3/T4/T5 deliberately cite paths absent
19
+ from any base tree — that absence is the fixture's whole subject. -->
20
+
21
+ ## T1: Land the instruments the later waves measure with
22
+ - goal: Write the two audit instruments and this task's own summary, so a later wave has something to read
23
+ - shape: implement
24
+ - files: scripts/{audit-strict.mjs,census.mjs}, .planning/payload-shape/T1-SUMMARY.md
25
+ - context: docs/payload-shape/PLAN.md
26
+ - complexity: 3
27
+ - acceptance:
28
+ - command: node -e "process.exit(0)"
29
+ - judge: both instruments exist and the summary records what they measured
30
+
31
+ ## T2: Rewrite the localisation sources the instruments flag
32
+ - goal: Apply the instrument's findings across the localisation sources named in the write scope
33
+ - shape: implement
34
+ - deps: T1
35
+ - files: src/i18n/{ar/common.json,en/common.json,ar/dossier.json,en/dossier.json,ar/intake.json,en/intake.json,ar/tasks.json,en/tasks.json,ar/settings.json,en/settings.json}, .planning/payload-shape/T2-SUMMARY.md
36
+ - context: docs/payload-shape/PLAN.md, scripts/audit-strict.mjs
37
+ - complexity: 5
38
+ - acceptance:
39
+ - judge: every key src/i18n/ar/common.json shares with its en counterpart carries a non-empty Arabic value, and .planning/payload-shape/T2-SUMMARY.md records the count it rewrote
40
+
41
+ ## T3: Close the census the second instrument opens
42
+ - goal: Read the census instrument written two waves back and close its remaining senses
43
+ - shape: implement
44
+ - deps: T2
45
+ - files: src/i18n/{ar/common.json,en/common.json}, .planning/payload-shape/T3-SUMMARY.md
46
+ - context: docs/payload-shape/PLAN.md, scripts/census.mjs
47
+ - complexity: 4
48
+ - acceptance:
49
+ - judge: the census reports no unresolved sense for any rewritten key
50
+
51
+ ## T4: Report on the audit without depending on the task that writes it
52
+ - goal: Summarise the audit's findings for the operator record
53
+ - shape: chore
54
+ - files: .planning/payload-shape/T4-SUMMARY.md
55
+ - context: docs/payload-shape/PLAN.md, scripts/audit-strict.mjs
56
+ - complexity: 2
57
+ - acceptance:
58
+ - judge: .planning/payload-shape/T4-SUMMARY.md states both the flagged-site count and the total key count it was measured against
59
+
60
+ ## T5: Apply the standing ruling to the rewritten sources
61
+ - goal: Enforce the ruling's terminology decisions across the sources T2 rewrote
62
+ - shape: chore
63
+ - deps: T2, T3
64
+ - files: src/i18n/{ar/common.json,en/common.json}
65
+ - context: docs/payload-shape/PLAN.md, .state/RULING-TERMINOLOGY.md
66
+ - complexity: 2
67
+ - acceptance:
68
+ - judge: no file under src/i18n/ar contains the string "Dossier" or "Engagement" in Latin script
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tickmarkr",
3
- "version": "1.92.1",
3
+ "version": "1.96.0",
4
4
  "description": "Spec in, verified work out.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -19,12 +19,13 @@ When working in a multi-agent terminal environment, decide your role before star
19
19
  - **Claude Code:** `herdr agent start orchestrator --kind claude --pane <root-pane-id> -- --permission-mode bypassPermissions`
20
20
  - **Codex:** `herdr agent start orchestrator --kind codex --pane <root-pane-id> -- --dangerously-bypass-approvals-and-sandbox` — the unsandboxed flag is REQUIRED, not optional: codex's `workspace-write` sandbox keeps `.git` refs read-only, so a sandboxed orchestrator's `tickmarkr run` dies at integration-branch creation (`git worktree add` cannot lock the ref). Do not downgrade this flag; the herdr pane and repo scope are the containment.
21
21
  - **Auxiliary agents you spawn (consultants, reviewers, scouts) follow the same forms.** Never launch a claude session in plan mode or default permission mode for autonomous work — both stall on per-command approval prompts nobody is watching; claude is always `--permission-mode bypassPermissions` (tickmarkr's own adapter uses exactly this for workers, judges, and consults). A read-only codex consultant may use `--sandbox read-only`; any codex session that must touch git needs the unsandboxed flag above.
22
+ - **Auxiliary seats run as the CLI's interactive TUI in their visible pane — never headless** (`claude -p` / `codex exec`): headless buffers output until exit so the pane renders idle for the entire run, is blind to SessionStart hook errors (a broken and a fixed hook both return green), and gives a stall watcher no midpoint — silent-time equals lifetime. Headless is for exit-code probes only (a quota check that wants `rc`), never for work anyone must watch.
22
23
 
23
24
  Outside a multi-agent terminal environment, run the loop directly.
24
25
 
25
26
  ## Stand-down (mission end and retirement)
26
27
 
27
- On each mission's terminal state, after the record commit and operator notification: the orchestrator stops every monitor and background task it started, prints one final stand-down line, and leaves nothing queued in its input box. A finished session with an armed watcher or pre-filled input is a loaded gun.
28
+ On each mission's terminal state, after the record commit and operator notification: the orchestrator stops every monitor and background task it started, sweeps the heartbeat/beat files its watchers wrote (a stale beat beside a live one reads as coverage to whoever globs the directory), prints one final stand-down line, and leaves nothing queued in its input box. A finished session with an armed watcher or pre-filled input is a loaded gun.
28
29
 
29
30
  ## Invariants
30
31
 
@@ -18,6 +18,7 @@ When working in a multi-agent terminal environment, decide your role before star
18
18
  - **Claude Code:** `herdr agent start orchestrator --kind claude --pane <root-pane-id> -- --permission-mode bypassPermissions`
19
19
  - **Codex:** `herdr agent start orchestrator --kind codex --pane <root-pane-id> -- --dangerously-bypass-approvals-and-sandbox` — the unsandboxed flag is REQUIRED, not optional: codex's `workspace-write` sandbox keeps `.git` refs read-only, so a sandboxed orchestrator's `tickmarkr run` dies at integration-branch creation (`git worktree add` cannot lock the ref). Do not downgrade this flag; the herdr pane and repo scope are the containment.
20
20
  - **Auxiliary agents you spawn (consultants, reviewers, scouts) follow the same forms.** Never launch a claude session in plan mode or default permission mode for autonomous work — both stall on per-command approval prompts nobody is watching; claude is always `--permission-mode bypassPermissions` (tickmarkr's own adapter uses exactly this for workers, judges, and consults). A read-only codex consultant may use `--sandbox read-only`; any codex session that must touch git needs the unsandboxed flag above.
21
+ - **Auxiliary seats run as the CLI's interactive TUI in their visible pane — never headless** (`claude -p` / `codex exec`): headless buffers output until exit so the pane renders idle for the entire run, is blind to SessionStart hook errors (a broken and a fixed hook both return green), and gives a stall watcher no midpoint — silent-time equals lifetime. Headless is for exit-code probes only (a quota check that wants `rc`), never for work anyone must watch.
21
22
 
22
23
  Outside a multi-agent terminal environment, run the loop directly.
23
24
 
@@ -60,7 +61,7 @@ When spawning consultants (agents gathering synthesis input for decisions like S
60
61
 
61
62
  ## Stand-down (mission end and retirement)
62
63
 
63
- - **Orchestrator, on terminal state** (green, failed, or parked), after the record commit and operator notification: stop every monitor and background task you started, print one final stand-down line, and leave NOTHING queued in your input box. A finished session with an armed watcher or pre-filled input is a loaded gun — a retired v1.40 orchestrator sat idle with "merge … tag, publish" unsent in its input; one stray Enter would have shipped a duplicate release.
64
+ - **Orchestrator, on terminal state** (green, failed, or parked), after the record commit and operator notification: stop every monitor and background task you started, sweep the heartbeat/beat files your watchers wrote (a stale beat beside a live one reads as coverage to whoever globs the directory), print one final stand-down line, and leave NOTHING queued in your input box. A finished session with an armed watcher or pre-filled input is a loaded gun — a retired v1.40 orchestrator sat idle with "merge … tag, publish" unsent in its input; one stray Enter would have shipped a duplicate release.
64
65
  - **Supervisor, when a mission completes** (and always before spawning the next orchestrator): verify the orchestrator stood down, then close its tab. Seeming input-box text in a retired pane can be the TUI's dim ghost-text suggestion, not queued input — confirm with an ANSI read (dim escape around the text) or type-one-char-and-read-back before treating it as the loaded gun; close the tab either way. The journal, execution record, OBS ledger, and memory hold the story; pane scrollback is disposable. Never leave a retired agent idle with watchers armed.
65
66
 
66
67
  ## The loop
@@ -12,6 +12,19 @@ to the user with evidence.
12
12
  The mission is the skill argument. If empty, ask the user what to run end-to-end before doing anything else.
13
13
  Requires `HERDR_ENV=1`; if unset, say so and stop.
14
14
 
15
+ **THE ENGINE IS THE DEFAULT EXECUTOR.** A mission that names a milestone, a phase, or a spec runs the
16
+ loop: `tickmarkr compile` → `plan` → `run` → `report`, and the JOURNAL is the record. `compile` ingests
17
+ GSD phase plans (`src/compile/gsd.ts`), so *"this repo uses GSD"* is not a reason to bypass it. The
18
+ supervised GSD flow (below) is the EXCEPTION: it exists only on an explicit operator order, recorded in
19
+ the brief WITH its costs named — no journal (so no per-task adapter/model record), no routing, no
20
+ enforced gate battery, no enforced cross-vendor review; each is re-implemented by hand or silently lost.
21
+ **Measured 2026-08-18 (P98):** the operator triggered this skill expecting the engine; the mission ran
22
+ as GSD legs instead — 12 seats, 11 of them one model checking that same model's work, and *"which model
23
+ ran each task"* was unanswerable from every mission artifact (no ruling, log, or brief named a model;
24
+ the answer took session-file archaeology). The flip from the engine (P89, journaled runs on disk) to
25
+ GSD legs (P92) had been RULED NOWHERE — no ledger entry decides it — and then propagated for six phases
26
+ through brief lineage. **An executor choice nobody made is still an executor choice, and it compounds.**
27
+
15
28
  ## Setup
16
29
 
17
30
  0. **Adopt before you build.** If this workspace already has a supervision hierarchy — an
@@ -22,11 +35,17 @@ Requires `HERDR_ENV=1`; if unset, say so and stop.
22
35
  status, and either ADOPT the
23
36
  existing orchestrator (updated brief, re-armed watchers) or, if the old hierarchy is dead, archive the
24
37
  stale brief and build fresh.
38
+ ⚠ **Adopting a hierarchy silently adopts its EXECUTOR CHOICE.** The P92→P98 GSD drift propagated
39
+ exactly this way: each overseer read the prior brief, reproduced "the same two-leg pattern as the
40
+ last three phases", and the unruled bypass of the engine became load-bearing through repetition.
41
+ At every adopt, re-derive the executor question — *"why is this milestone not compiled?"* — and if
42
+ the answer is not a recorded operator order, route the mission back through the engine.
25
43
  0a. **READ THE PROJECT MEMORY BEFORE YOU START — it already contains discipline you are about to re-earn.**
26
44
  `~/.claude/projects/<cwd-slug>/memory/` (slug = the absolute cwd with `/` → `-`). Read `MEMORY.md`, then
27
- `ls` the topic entries and open every one whose name concerns METHOD or DISCIPLINE rather than a shipped
28
- milestone — names like `*-discipline`, `*-drill`, `*-parity`, `*-least-permission`, `context-reset-*`,
29
- `consults-*`, `agent-*`.
45
+ `ls` the topic entries and open every one whose name concerns METHOD, DISCIPLINE, or a STANDING
46
+ OPERATOR LAYOUT/CONVENTION rather than a shipped milestone — names like `*-discipline`, `*-drill`,
47
+ `*-parity`, `*-least-permission`, `context-reset-*`, `consults-*`, `agent-*`, `*-tab-layout`,
48
+ `*-visible-*`, `*-panes*`, `user-tabs-*`.
30
49
  **Earned 2026-08-04, expensively.** That directory held `…-falsification-drill-discipline.md`, written
31
50
  three weeks earlier: *"a gate or grep-pin is assumed WRONG until a falsification drill proves it bites…
32
51
  run the drill that should redden it and SEE the red before trusting green."* That is Evidence discipline
@@ -35,8 +54,32 @@ Requires `HERDR_ENV=1`; if unset, say so and stop.
35
54
  mission-scoped brief. **A memory that exists and is never opened costs more than one that was never
36
55
  written, because everyone assumes the lesson is somewhere.** Entries may predate a project rename; search
37
56
  by concept, not by the current product name.
57
+ **And the sweep scope is itself a recorded defect: a filter limited to discipline names SKIPS the
58
+ layout canon, and that skip is paid.** The layout entry records a 2026-08-05 correction it caused —
59
+ *"widen the start-of-session read to include layout/convention entries, not only discipline ones"* —
60
+ and on 2026-08-17 the same skip put a planning seat inside the ORCH tab. A standing operator layout is
61
+ not cosmetic; it is how the operator reads the fleet, and it binds exactly like a discipline rule.
38
62
  1. Load the `herdr` skill. `herdr pane list` to map the workspace — the focused pane is yours. Rename your
39
63
  tab OVERSEER; create ONE tab ORCHESTRATOR.
64
+ **FIVE-TAB CANON (standing operator layout — corrected three times on 2026-07-27, layout approved
65
+ 2026-07-29, re-earned 2026-08-17):**
66
+ - `OVERSEER` — you. Do not add a second live run surface: the daemon self-places the shipped board
67
+ beside the supervising seat that invokes the run.
68
+ - `ORCH` — the orchestrator and the daemon-placed, run-id-pinned shipped board beside it. **Look for
69
+ that `role: "watch"` pane; never hand-place or hand-roll a live run surface. Nothing else, ever: a
70
+ work seat NEVER splits into the ORCH tab.** Operator verbatim: *"in orch tab should be the orch and
71
+ the watcher only."* Re-earned 2026-08-17: a planning seat split beside the orchestrator, and the
72
+ operator caught it, again. The daemon owns the side placement and board-first width allocation;
73
+ neither the worker-pane halving floor nor an overseer split command places this pane.
74
+ - Worker/seat tabs — tickmarkr opens ONE TAB PER TASK itself; GSD-leg seats get the same treatment
75
+ (own tab, or a shared WORKERS tab), never the ORCH tab.
76
+ - `CONSULT · <topic>` — ONE shared tab for ALL consultants of a round, side-by-side splits; never one
77
+ tab per consultant; do NOT auto-close after adjudication (operator, 2026-08-09 — keep the round's
78
+ panes until the thread is confirmed finished or a successor round supersedes them).
79
+ - `REVIEW <task>` — reviewer panes.
80
+ Never multiply beyond these without asking. **Tabs you did not create are the OPERATOR'S — provenance
81
+ decides: never close, rename, reuse, or send input to one, however idle it looks.** An "idle
82
+ stale-looking" tab an overseer once swept was the operator's in-progress thinking (2026-07-14).
40
83
  **Live tab labels (standing operator rule, 2026-07-12):** on every decision or state change (role
41
84
  handoff, task done/merged, run end) rename the affected tabs — and keep labels SHORT: the role as the
42
85
  main name plus at most ONE hot-state token. Vocabulary: ORCH carries the milestone and progress
@@ -48,8 +91,9 @@ Requires `HERDR_ENV=1`; if unset, say so and stop.
48
91
  truncated brief silently drops policy. Write the full brief to `<repo>/.tickmarkr/overseer/ORCH-BRIEF.md`
49
92
  (inside the tickmarkr state dir — already self-gitignored, no exclude step needed), then send one line:
50
93
  `herdr pane run <orch> "Read .tickmarkr/overseer/ORCH-BRIEF.md and follow it exactly."` The brief MUST contain: the
51
- mission, the pane mechanics below, rules 1–2, and require a verbatim one-sentence acknowledgment of the
52
- human-checkpoint rule before anything is dispatched.
94
+ mission, the five-tab canon from step 1, the pane mechanics below, rules 1–2, the GSD-leg rules
95
+ (below) whenever the mission dispatches `/gsd:*` legs, and require a verbatim one-sentence
96
+ acknowledgment of the human-checkpoint rule before anything is dispatched.
53
97
  **⚠ HARVEST BEFORE YOU DELETE.** At mission end the brief dir goes — but a long mission accumulates
54
98
  *method guards* in that brief (how to know a thing, not what is true of this spec), and deleting them
55
99
  re-earns each one at full price on the next mission. So before removing the dir: lift every durable,
@@ -57,7 +101,7 @@ Requires `HERDR_ENV=1`; if unset, say so and stop.
57
101
  and only then delete. A guard's home must outlive the mission that earned it. The project ledger does
58
102
  NOT count as that home — `CLAUDE.md` itself says planning records are read-only archives and current
59
103
  guidance belongs in the memory file or the shipped docs.
60
- 4. Arm the watcher (Supervision). Report the hierarchy map (pane ids + names) to the user.
104
+ 4. Arm the watcher and your own supervision beat (Supervision). Report the hierarchy map (pane ids + names) to the user.
61
105
 
62
106
  ## Supervising tickmarkr as the executor — WHO DOES WHAT
63
107
 
@@ -69,7 +113,7 @@ and the first thing to get right is that **almost none of it is yours**.
69
113
  | | ORCHESTRATOR | OVERSEER |
70
114
  |---|---|---|
71
115
  | `compile` · `plan` · `run` · `resume` | **owns** | never |
72
- | journal watchers, live surface, dialog watchers | **owns** | watches the ORCHESTRATOR, not the run |
116
+ | journal and dialog watchers; verifying the daemon's live surface | **owns** | watches the ORCHESTRATOR, not the run |
73
117
  | orphan sweeps, worker pane hygiene | **owns** | — |
74
118
  | reading a gate failure and assembling its evidence | **owns** | reads the file it writes |
75
119
  | **deciding** a gate, spend, or ship | never | **owns** |
@@ -89,10 +133,11 @@ journal tail to decide what happens next, or sweeping orphans — you have taken
89
133
 
90
134
  ### What the ORCHESTRATOR does, and what you require of it
91
135
 
92
- - **A live surface.** `tickmarkr run` is stdout-silent until run-end by design, and the run spawns its own
93
- watch board (`role: "watch"`, one per run) — **look for that pane before building anything.** Do NOT use
94
- `tickmarkr status --watch` as the surface: `status <runId>` has reported the WRONG run, so a board built
95
- on it shows a previous milestone's numbers under the current run's id.
136
+ - **The live surface arrives with the run.** `tickmarkr run` is stdout-silent until run-end by design;
137
+ its daemon self-places one shipped `role: "watch"` board beside the supervising seat and pins that
138
+ board to the daemon's run id. **Look for the matching daemon-placed pane.** If it is absent, treat that
139
+ as a daemon/run liveness fault and use the normal recovery path; never hand-place, hand-roll, or launch
140
+ a replacement live surface.
96
141
  - **The journal is the source of truth**, not panes. Watchers go on `run-end` / `task-human` /
97
142
  `task-failed` / `consult-verdict`; never sleep-poll inside an agent turn. **Never key a watcher on an
98
143
  agent's `done`** — that is turn end and fires the moment a seat finishes acknowledging you.
@@ -192,12 +237,68 @@ is a lossy summary nobody trusts while a clean session re-oriented from disk-ver
192
237
  good, not after. If your own context cannot be read by the watcher, say so to the operator and ask for the
193
238
  number — an unmeasured budget is not a small budget.
194
239
 
240
+ ## Supervising GSD legs — when the mission dispatches `/gsd:*` instead of `tickmarkr run`
241
+
242
+ Milestones that alternate GSD legs (`/gsd:plan-phase N`, `/gsd:execute-phase N`) under this hierarchy get
243
+ none of the engine's dispatcher, routing, or gates — every guarantee `tickmarkr run` provides has to be
244
+ demanded in the brief instead. **This path is the EXCEPTION and requires the recorded operator order
245
+ named in the default-executor rule at the top of this skill.** Five guarantees get dropped every time
246
+ they are left implicit:
247
+
248
+ 0. **A seats ledger stands in for the journal's assignment records.** Every seat spawn — orchestrator,
249
+ planner, checker, executor, verifier, consult — appends ONE JSON line to
250
+ `<state-dir>/overseer/seats.jsonl` in the same act as the spawn:
251
+ `{"ts":"<iso>","seat":"<name>","pane":"<id>","tab":"<label>","adapter":"<cli>","model":"<model>","role":"<role>","brief":"<path>"}`.
252
+ The journal answers *"which model ran each task"* in one line; without this file the answer is
253
+ session-file archaeology, and the model monoculture it would have exposed stays invisible (measured
254
+ 2026-08-18: 12 seats, no artifact naming any seat's model). ⚠ Interim per rule 27 — removal
255
+ condition: the engine runs the milestone and the journal is the record.
256
+
257
+ 1. **GSD's Agent-tool default is OVERRIDDEN — name the trap in every seat brief.** `/gsd:plan-phase` and
258
+ `/gsd:execute-phase` fan their work out to in-process Task subagents: invisible to every watcher tier,
259
+ billed to the seat's own context, visible only in the seat's status footer. Measured 2026-08-17 (P98
260
+ leg 1): one planning seat ran FIVE in-process subagents to ≈855k tokens — two of them an unexplained
261
+ duplicate respawn pair — and the operator caught it from the footer, not from any tier of supervision.
262
+ The violation was first recorded 2026-07-10, and that record already states the fix: *"generic
263
+ 'visible panes' wording is not enough — name the GSD trap."* The concrete mechanism is claude-code
264
+ TEAMMATES (the seat's footer roster; `Teammate @<name> finished` notices): they run INSIDE the
265
+ parent seat's turn, so the seat cannot drain its message queue until every teammate returns —
266
+ **unwatchable and unsteerable are the same defect** (the queued-message law, Pane mechanics).
267
+ Measured 2026-08-18: two freeze-class directives sat queued behind a planner's teammate fan-out
268
+ while the plan set they froze was still editable from that queue, and THE OPERATOR ended the turn
269
+ by hand (Esc, twice) because no seat owned the interrupt. Every GSD seat brief states: subagents
270
+ run as visible herdr panes in per-task tabs (`herdr agent start …`), and a seat report whose work
271
+ was produced by invisible subagents is rejected on read.
272
+ 2. **Seats are interactive TUI, never headless `-p`.** The P92–P96 exec lane —
273
+ `cat brief | claude -p … ` in a visible pane — satisfied visibility in the letter only: `claude -p`
274
+ buffers output until exit, so the pane renders idle for the entire run; it is blind to SessionStart
275
+ hook errors (a broken and a fixed hook both return green); and its silence has no midpoint for a
276
+ stall watcher to catch — silent-time equals lifetime. Standing operator rule since 2026-07-13:
277
+ consults and one-off LLM calls run as the CLI's real interactive TUI in a visible named pane.
278
+ Headless is for exit-code probes — a quota check that wants `rc`, never work anyone must watch.
279
+ 3. **Buy seat diversity from the live capability matrix, at every dispatch.** When one vendor's model
280
+ quota collapses, the reflex is to collapse every seat onto the surviving model and hold the
281
+ cross-vendor CLI back for a late probe — P97 ran planner, checker and verifier as one family that
282
+ way, and three same-family passes confirmed one wrong anchored conclusion with the refuting fact in
283
+ the room. `<state-dir>/doctor.json` already lists every installed+authed adapter and its models (nine
284
+ were authed on 2026-08-17 while every seat ran claude). Priority when independence is scarce:
285
+ **verifier > checker > planner > executors** — the independent seat goes cross-vendor
286
+ (`herdr agent start … --kind codex`), ruled at dispatch, never debated under time pressure.
287
+ 4. **Gate every exec lane with the shipped battery, not hand-rolled greps.**
288
+ `tickmarkr verify --base <ref> --criteria <file>` is the standalone form of the engine's own gates —
289
+ build/test/lint diffed against a recorded baseline, evidence, scope, plus the semantic judges — one
290
+ fail-closed verdict, no daemon, no retries. A per-lane grep gate re-implements a weaker version of
291
+ this and passes on source text the screen never renders, which is exactly the class the acceptance
292
+ judge exists to reject. One command per lane, named in the lane's own brief.
293
+
195
294
  ## Pane mechanics that bite
196
295
 
197
296
  - **Verified send protocol**: `herdr agent send` writes WITHOUT Enter, and `pane run`'s Enter can be swallowed
198
297
  by bracketed-paste on long payloads. Robust sequence: read the pane (bare prompt required) → send-text →
199
298
  sleep 2–3s → send-keys Enter → read back (input empty / agent `working`). Never report "briefed" without
200
- the read-back. Long content goes in a brief file, never pane text.
299
+ the read-back. Long content goes in a brief file, never pane text. `scripts/seat-send.sh` encodes
300
+ this whole path — size guard, atomic prompt, prompt-line read-back, optional interrupt — and never
301
+ auto-resends.
201
302
  **PROBE THE READ-BACK WITH THE SHORTEST DISTINCTIVE TOKEN — a commit hash, a pid, an OBS id — NEVER a
202
303
  sentence.** A long phrase crosses the pane's render wrap boundary, so grepping for it returns zero on a
203
304
  message that arrived intact, and **a badly-probed successful send is byte-identical to a truncated one.**
@@ -206,6 +307,35 @@ number — an unmeasured budget is not a small budget.
206
307
  (OBS-396): a grep for the full sentence returned 0 while a grep for one word of the same sentence
207
308
  returned 1. This trap lives *inside* the verification step above, which is why it survives — the rule
208
309
  that is supposed to catch dropped sends is the rule that manufactures the phantom.
310
+ **AND A CONTENT PROBE — TOKEN, TAIL, FULL TEXT — CANNOT VERIFY DELIVERY AT ALL, only presence.**
311
+ Measured 2026-08-18: `herdr pane read` includes the INPUT BOX, so an unsubmitted message renders
312
+ identically to a submitted one and every content-based probe returns the same answer in both
313
+ states. A freeze hold was "verified delivered" by three independent content methods and had never
314
+ been submitted; the receiving seat stopped 19 minutes later without ever reading it — the frozen
315
+ set was protected by nothing but the operator's manual Esc. **The prompt line is the only state
316
+ that discriminates** — text sitting on `❯` is exactly what will not run — and a delivery report
317
+ is prompt-line state alone, or nothing: a decorative check beside a real one reads as
318
+ corroboration (two greens, one of which was never capable of disagreeing).
319
+ - **A MESSAGE TO A WORKING SEAT IS A QUEUED MESSAGE, AND THE QUEUE DRAINS ONLY AT TURN BOUNDARIES.**
320
+ Delivery is not arrival: `agent prompt` to a `working` claude seat lands in its queue (`Press up to
321
+ edit queued messages` on the seat's prompt line is the tell) and is READ only when the current turn
322
+ ends — and with in-process teammates a turn runs 20–40 minutes, so steering latency equals subagent
323
+ runtime. Measured 2026-08-17/18 (P98 leg 1): a FREEZE HOLD and a checker-release directive stacked
324
+ behind a planner's teammate fan-out — the freeze forbade edits its own queue could still trigger —
325
+ and the OPERATOR ended the turn by hand. Three consequences:
326
+ - **A queued hold is not a hold.** A freeze-class or superseding directive to a `working` seat is
327
+ delivered by INTERRUPT, and the interrupt is the SUPERVISING tier's move, never left to the
328
+ operator: `send-keys esc` → re-read status → esc once more if still working (two, bounded) →
329
+ verify idle → prompt → verify. The interrupt loses the seat's in-flight step; for a
330
+ correctness-class directive that is the price, and it is cheaper than a voided verdict.
331
+ - **Never stack a correction behind a stale directive.** A queued message executes in a context that
332
+ no longer matches the one it was written in. When conditions change, do not append another
333
+ message — interrupt, then send ONE directive that NAMES the queue and overrides it (*"your queue
334
+ holds X and Y; act on neither; current state is Z"*), and require the seat to report what its
335
+ queue held before acting. The receiving seat re-validates every queued item against CURRENT state.
336
+ - Five distinct send failures in one leg — front-truncation, sitting unsubmitted, two silent losses,
337
+ a probe that mistook its own echo for a reply — is what a prose send protocol costs under load:
338
+ run `scripts/seat-send.sh` instead.
209
339
  - **Guard-before-Enter** (race-safe prompt answering): chain with `&&` — pane get shows `blocked` && pane
210
340
  read shows the expected option under the cursor && only then send-keys. If no longer `blocked`, someone
211
341
  already answered; do nothing.
@@ -235,9 +365,38 @@ number — an unmeasured budget is not a small budget.
235
365
  unaffected (one more reason to prefer them).
236
366
  - Stale typed input is unclearable via CLI — supersede it:
237
367
  `pane run "<-- disregard everything before this arrow (stale draft). ACTUAL: <message>"`.
368
+ **But DISCRIMINATE before you supersede or file it: text on an idle seat's prompt line has FOUR
369
+ authors** — the seat's own draft, an operator, another agent's `agent send` (writes WITHOUT Enter),
370
+ and claude-code's AUTOSUGGEST, which renders context-plausible ghost text BYTE-IDENTICAL to a typed
371
+ draft in text-format reads (OBS-482). The check is mechanical and only works at observation time:
372
+ `agent read --format ansi` — dim/grey SGR around the text = autosuggest ghost, NOT input. Measured
373
+ 2026-08-17 (D-206): an unattributed instruction was found in an orchestrator's box, superseded
374
+ defensively, and its origin stayed UNRESOLVED — the one probe that discriminates was not taken while
375
+ the text still sat there. An origin question you can close in ten seconds at the pane becomes
376
+ permanently open the moment anyone clears the box.
238
377
 
239
378
  ## Supervision watcher
240
379
 
380
+ **Arm your OWN tier first, in the same call chain that arms everything else.** `status` derives each
381
+ tier's state from a beat file the tier itself writes, so a seat that never beats reads `ABSENT` — and
382
+ `ABSENT` means *never armed*, which is a lie about a seat that is working the run. Measured on the P99
383
+ run: `orchestrator ARMED / overseer ABSENT / watch ABSENT` for the whole milestone, with a live overseer
384
+ watching it. Two thirds of that line were constants, not measurements.
385
+
386
+ The beat is one shipped command and the loop is yours, run from the repo root as its own
387
+ `run_in_background` Bash call:
388
+
389
+ ```bash
390
+ cd <repo> && while :; do tickmarkr beat overseer; sleep 10; done # 10s = SUPERVISION_BEAT_MS
391
+ tickmarkr beat overseer --stand-down # at stand-down, in the same act
392
+ ```
393
+
394
+ One beat per invocation, deliberately: the loop is what proves the seat is alive, so a command that
395
+ kept beating on its own would keep reporting a dead seat as healthy. Stop the loop — or die — and the
396
+ tier ages to `STALE` (never `ABSENT`) within six beats, which is the state that says *armed, then lost*.
397
+ Stand down explicitly when you hand off, or a deliberate exit reads as a death. Same rule as rule 29
398
+ below, now with a conventional path the other tier already reads: `tickmarkr status` shows it.
399
+
241
400
  Arm the bundled watcher as its OWN Bash call with `run_in_background` — chaining it after other commands
242
401
  with `&` orphans it from the wake chain. It prints one wake reason and exits; re-arm after every wake.
243
402
 
@@ -309,9 +468,9 @@ herdr agent wait <name> --until blocked --timeout <ms> # run_in_background
309
468
  ⚠ **Its exit status is not evidence.** That command exits **0 on timeout** and **0 when the pane is gone**,
310
469
  exactly as it does on a real block — so confirm every wake by READING the pane before acting on it.
311
470
 
312
- **And note what no watcher can cover:** the adopted supervision design gives this seat zero watchers and
313
- wakes it on *product-owned signals* that do not exist until the `supervision-heartbeat` work ships. Until
314
- then the seat improvises, and an improvised set is where a whole failure class hides. A **host** permission
471
+ **And note what no watcher can cover:** the supervision beat above proves this seat is ARMED and nothing
472
+ more — it is a liveness claim, not a wake signal, so the seat still improvises the wakes, and an improvised
473
+ set is where a whole failure class hides. A **host** permission
315
474
  modal is invisible to tickmarkr entirely, so no `src/**` change closes that one — it is covered here or
316
475
  nowhere.
317
476
 
@@ -324,6 +483,12 @@ nowhere.
324
483
  It wakes when every named file exists AND ends with its terminal marker, and on timeout it reports each
325
484
  file as READY / PARTIAL / ABSENT so a quiet arm still proves the watcher was alive. Tell each seat, in its
326
485
  brief, the exact marker its report must end with — you cannot watch for a marker you never demanded.
486
+ **Arm on the marker YOU demanded, verified against the FILE — never on the seat's report of its own
487
+ marker.** Measured 2026-08-17: a seat reported its sweep "ends `SWEEP-END`"; the file on disk ended
488
+ `ORDER4-END`. A watcher armed on the reported marker never fires while the artifact sits COMPLETE, and
489
+ that hang is byte-identical to a seat still working. The script prints each unfinished file's actual
490
+ last line on every timeout heartbeat — read it there, and when in doubt `tail -1` the artifact, never
491
+ the transcript's claim about it.
327
492
 
328
493
  **Arm it in the same call as the spawn, not the next one.** A watcher armed "after I finish this step"
329
494
  leaves a gap exactly as wide as however long you stay busy, and you will be busy — you just spawned work.
@@ -386,6 +551,10 @@ orchestrator turn boundary.
386
551
  safe 53 → floor 108"*), and it splits right only while `paneWidth/2 ≥ 108 + 2` (`herdr.ts:494`),
387
552
  otherwise **down**. Apply the same test by hand: `herdr pane layout --pane <id>`, halve the width,
388
553
  and if the halves fall under the floor, split `--direction down`.
554
+ - **The daemon-placed ORCH board is outside this manual split rule.** The halving bound protects
555
+ worker-pane trailers; the daemon, not the overseer, places the run-id-pinned shipped board beside
556
+ the supervising seat and owns its board-first width allocation. Look for that pane and do not split,
557
+ place, or recreate it.
389
558
  - **Binary splits cannot produce an even 3-column row at any width.** 220 goes to 110/55/55 whichever
390
559
  pane you split. **At a 220-col terminal the width-derived cap is TWO side-by-side panes**; a third
391
560
  seat goes below one of them, or into its own tab. "Three panes" is a *height* heuristic
@@ -713,6 +882,11 @@ twice.** They are mission-independent on purpose: nothing here names a task, a l
713
882
  **Write beats to a conventional path inside the repository the other tier already reads**, one file per
714
883
  tier, and state the path when you report. Then have the reader name the tiers that are ABSENT, never
715
884
  the ones present: a list of what IS armed is producible by a seat whose watchers are all dead.
885
+ **And a beat OUTLIVES its mission unless something sweeps it.** Namespace beats per phase/leg and
886
+ sweep them at stand-down: measured 2026-08-17, a beats directory held SEVEN stale beats from prior
887
+ phases beside four live tiers, and a stale beat beside live ones reads as coverage to anyone globbing
888
+ the directory — the outliving-its-trigger failure (rule 11) in file form. Only age distinguishes a
889
+ frozen beat from a live one, so the reader states ages, and the writer removes what it retires.
716
890
  30. **A JOURNAL WATCHER ON A RESUMABLE RUN MUST SCOPE TO THE CURRENT ENGAGEMENT.** A resumed run's journal
717
891
  still contains the PREVIOUS `run-end`. A watcher that greps the whole file for its terminal event finds
718
892
  that old one immediately, concludes the run is over, and exits — on every resume, which is exactly when