@r1z4x/tezgah 1.11.0 → 1.14.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.
Files changed (109) hide show
  1. package/CHANGELOG.md +507 -1
  2. package/MANIFEST +40 -0
  3. package/NOTICE +152 -0
  4. package/README.de.md +41 -20
  5. package/README.es.md +39 -19
  6. package/README.fr.md +41 -20
  7. package/README.md +18 -10
  8. package/README.tr.md +39 -21
  9. package/bin/codegen +9 -2
  10. package/bin/consult +6 -0
  11. package/bin/tezgah-adhd +3 -0
  12. package/bin/tezgah-agents +3 -0
  13. package/bin/tezgah-capture +3 -0
  14. package/bin/tezgah-design +329 -54
  15. package/bin/tezgah-docs +158 -42
  16. package/bin/tezgah-dsh +1 -1
  17. package/bin/tezgah-gate +7 -2
  18. package/bin/tezgah-import-ai-research +16 -4
  19. package/bin/tezgah-index +3 -0
  20. package/bin/tezgah-lessons +13 -4
  21. package/bin/tezgah-mcp +4 -0
  22. package/bin/tezgah-migrate +3 -0
  23. package/bin/tezgah-ocr-review +2 -1
  24. package/bin/tezgah-optimize +4 -1
  25. package/bin/tezgah-pony +3 -0
  26. package/bin/tezgah-research +51 -10
  27. package/bin/tezgah-rollback +3 -0
  28. package/bin/tezgah-route +1 -1
  29. package/bin/tezgah-setup +1741 -95
  30. package/bin/tezgah-status +11 -1
  31. package/bin/tezgah-task +58 -17
  32. package/bin/tezgah.js +10 -3
  33. package/commands/adhd.md +1 -1
  34. package/docs/README.md +19 -8
  35. package/docs/architecture.md +2 -2
  36. package/docs/citations-baseline.json +11 -17
  37. package/docs/clarity-baseline.json +12 -6
  38. package/docs/contract.md +83 -34
  39. package/docs/environment.md +59 -24
  40. package/docs/evidence.md +128 -25
  41. package/docs/gate.md +77 -23
  42. package/docs/glossary.md +1 -1
  43. package/docs/hosts.md +343 -48
  44. package/docs/layers.md +2 -2
  45. package/docs/models.md +6 -0
  46. package/docs/operations.md +83 -29
  47. package/docs/research.md +37 -11
  48. package/docs/skills.md +31 -8
  49. package/docs/status-line.md +39 -23
  50. package/docs/testing.md +50 -17
  51. package/docs/vision.md +6 -2
  52. package/hooks/hooks.json +1 -1
  53. package/hooks/lang/en.json +16 -2
  54. package/hooks/lang/tr.json +14 -2
  55. package/hooks/projects-posttooluse.py +11 -3
  56. package/hooks/projects-stop.py +3 -2
  57. package/hooks/tezgah_agents.py +27 -3
  58. package/hooks/tezgah_attest.py +79 -10
  59. package/hooks/tezgah_context.py +61 -31
  60. package/hooks/tezgah_gate.py +480 -126
  61. package/hooks/tezgah_integrity.py +577 -133
  62. package/hooks/tezgah_lessons.py +5 -2
  63. package/hooks/tezgah_paths.py +60 -8
  64. package/hooks/tezgah_policy.py +48 -18
  65. package/hooks/tezgah_replay.py +8 -2
  66. package/hooks/tezgah_research.py +167 -24
  67. package/hooks/tezgah_skill_pick.py +1 -1
  68. package/hooks/tezgah_snapshot.py +6 -3
  69. package/hooks/tezgah_stack.py +236 -1
  70. package/hooks/tezgah_stopspec.py +2 -1
  71. package/hooks/tezgah_task.py +2 -1
  72. package/hosts/amp/hook.py +217 -0
  73. package/hosts/amp/tezgah-plugin.ts.in +396 -0
  74. package/hosts/antigravity/hook.py +255 -0
  75. package/hosts/codex/hook.py +3 -2
  76. package/hosts/commandcode/hook.py +239 -0
  77. package/hosts/copilot/hook.py +284 -0
  78. package/hosts/cursor/hook.py +4 -2
  79. package/hosts/droid/hook.py +259 -0
  80. package/hosts/dsh/hooks.json +1 -1
  81. package/hosts/gemini/hook.py +213 -0
  82. package/hosts/goose/hook.py +145 -0
  83. package/hosts/omp/hook.py +5 -2
  84. package/hosts/omp/tezgah-hook.ts.in +157 -42
  85. package/hosts/opencode/plugins/tezgah.js +209 -63
  86. package/hosts/qwen/hook.py +206 -0
  87. package/output-styles/tezgah.md +11 -6
  88. package/package.json +6 -3
  89. package/packaging/build.sh +4 -4
  90. package/packaging/install.sh +11 -0
  91. package/packaging/upgrade.sh +3 -0
  92. package/skills/ai-research/LICENSE +21 -0
  93. package/skills/design-contract/SKILL.md +27 -6
  94. package/skills/design-library/LICENSE +21 -0
  95. package/skills/harness/SKILL.md +2 -0
  96. package/skills/i-have-adhd/SKILL.md +10 -1
  97. package/skills/plan-add/SKILL.md +5 -1
  98. package/skills/plan-add/render_table.py +7 -0
  99. package/skills/pm-frameworks/LICENSE +21 -0
  100. package/skills/research/SKILL.md +22 -9
  101. package/skills/rl-env/LICENSE +201 -0
  102. package/skills/tezgah-contract/SKILL.md +21 -8
  103. package/statusline.py +20 -9
  104. package/third_party/open-code-review/LICENSE +201 -0
  105. package/third_party/open-code-review/README.md +15 -0
  106. package/third_party/open-code-review/prompts/main_task_system.md +26 -0
  107. package/third_party/open-code-review/prompts/review_filter_task_system.md +13 -0
  108. package/workflows/graph-map.js +14 -5
  109. package/workflows/graph-review.js +14 -2
@@ -15,8 +15,8 @@ host-neutrally by `health_segments()` (`hooks/tezgah_context.py::health_segments
15
15
  groups (`hooks/tezgah_context.py::render_line`). The group is *data on each segment*: a renderer building its
16
16
  own line from `--json` separates on `seg.group` and matches `render_line()`
17
17
  without a second copy of the partition (`hooks/tezgah_context.py::health_segments`) — opencode and dsh do exactly
18
- that (hosts/opencode/tui/tezgah-tui.tsx:108-111,
19
- hosts/dsh/statusline/lib/client.js:86-90).
18
+ that (`TezgahBar` in `hosts/opencode/tui/tezgah-tui.tsx`,
19
+ `TezgahStatusLine` in `hosts/dsh/statusline/lib/client.js`).
20
20
 
21
21
  | group | members | why together |
22
22
  |---|---|---|
@@ -173,7 +173,12 @@ group separators stay identical for every extension. An extension never has to
173
173
  know the gutter. It must name `text` and `state`. tezgah fills a missing `glyph`
174
174
  from the state's own mark, so a chip reads like a built-in. The per-extension cap
175
175
  (`MAX_SEGMENTS`) and the hard timeout (`timeout_ms`, default
176
- `DEFAULT_TIMEOUT_MS`) bound one row.
176
+ `DEFAULT_TIMEOUT_MS`) bound one row. The command prints one JSON object whose
177
+ `segments` key holds that list. tezgah reads a bare list on stdout as malformed output:
178
+
179
+ ```json
180
+ {"segments": [{"key": "cyber-eng", "state": "on", "text": "eng"}]}
181
+ ```
177
182
 
178
183
  It is a subprocess and not an import on purpose. The extension is the user's
179
184
  repo, in whatever language, and may be slow or broken. A bounded child keeps a
@@ -229,10 +234,13 @@ python is asked (`hooks/tezgah_context.py::skill_read_kind`). A surface that can
229
234
  was never opened", so it declares what it can see and those marks render `info`
230
235
  (dim, no glyph) instead of `ready` (`observable`, `hooks/tezgah_context.py::health_segments`; the set constant
231
236
  `TOOL_USE_MEASURES`, `hooks/tezgah_context.py::TOOL_USE_MEASURES`). The `--observable=` flag carries it on the CLI
232
- (`bin/tezgah-status::main`). Two callers pass the tool-use set: Cursor's
233
- status line (statusline.py:117-121) and Codex, which renders the line plain into
234
- `systemMessage` (`hosts/codex/hook.py::main`); dsh passes the literal flag
235
- string (hosts/dsh/statusline/lib/index.js:16-19,84,96), which names the five marks its
237
+ (`bin/tezgah-status::main`). Every host whose line reads the store and cannot see
238
+ a skill read passes the tool-use set: the status line for Cursor, Qwen, Droid, Copilot and Antigravity
239
+ (`statusline.py::HOST`, `statusline.py::observable`), and Codex, Command Code
240
+ and Gemini, which render the line plain into `systemMessage`
241
+ (`hosts/codex/hook.py::main`, `hosts/commandcode/hook.py::status`,
242
+ `hosts/gemini/hook.py::status`); dsh passes the literal flag
243
+ string (`OBSERVABLE` in `hosts/dsh/statusline/lib/index.js`), which names the five marks its
236
244
  own hook can record - `judge` included, since dsh wires the same PostToolUse hook
237
245
  every other host does. The set is what a host can write, not what it happens to
238
246
  have written: a mark the literal omits reads `info` there rather than a claim.
@@ -248,15 +256,15 @@ the five kinds are ever written (`hooks/tezgah_context.py::record`).
248
256
 
249
257
  Writers: the shared PostToolUse hook `hooks/projects-posttooluse.py` — `graph` from
250
258
  an MCP tool name, else the shell tokenizer's answer, which counts a tool only when
251
- the command really ran it (34-38,84) and is the one writer that can earn `judge`
259
+ the command really ran it (`hooks/projects-posttooluse.py::used_kind`) and is the one writer that can earn `judge`
252
260
  (a run of `bin/tezgah-triage` or `bin/tezgah-docs`, `shell_kind`, `hooks/tezgah_context.py::shell_kind`)
253
261
  — plus `hooks/projects-auto-init.py::main`
254
262
  (`orch` on subagent start), `hosts/codex/hook.py::main`,
255
- `hosts/cursor/hook.py::dispatch`, hosts/opencode/plugins/tezgah.js:2167-2174,
256
- hosts/opencode/plugins/tezgah.js:2725, `hosts/omp/hook.py::handle`. Both Claude (hooks/hooks.json) and dsh
263
+ `hosts/cursor/hook.py::dispatch`, `record` in `hosts/opencode/plugins/tezgah.js`,
264
+ `hosts/omp/hook.py::handle`. Both Claude (hooks/hooks.json) and dsh
257
265
  (hosts/dsh/hooks.json) wire PostToolUse to that one shared hook, which is how dsh
258
266
  gets any used mark at all: with no local transcript, the store is the only channel
259
- its line has (hooks/projects-posttooluse.py:45-49).
267
+ its line has (`hooks/projects-posttooluse.py::used_kind`).
260
268
 
261
269
  Claude's line never reads the store: it parses `tool_use` blocks out of
262
270
  `payload["transcript_path"]` plus the subagent transcripts under it, which is how
@@ -269,15 +277,15 @@ Every other surface reads the store.
269
277
  picks the host from `--cursor` or `TEZGAH_STATUS_HOST`, renders
270
278
  `render_line(segs, color=color_default())`, and joins Orca's own status line to
271
279
  the tezgah segment with ` | `; `TEZGAH_STATUS_LEGEND=1` appends the legend
272
- (31-34,40-48,116-122). ANSI is dropped under `NO_COLOR` or
280
+ (`statusline.py::HOST`, `statusline.py::seg`, `statusline.py::out`). ANSI is dropped under `NO_COLOR` or
273
281
  `TEZGAH_STATUS_COLOR=0` (`color_default()`, `hooks/tezgah_context.py::color_default`).
274
282
  - **omp** — hosts/omp/tezgah-hook.ts.in: `draw()` prefers
275
283
  `ctx.ui.setWidget("tezgah", [line], {placement: "belowEditor"})`, which renders
276
284
  the colored string as-is; `setStatus` is the fallback for a build without
277
- `setWidget`, and it is plain because omp strips ANSI (12-29). `status_line()`
285
+ `setWidget`, and it is plain because omp strips ANSI (`draw` in `hosts/omp/tezgah-hook.ts.in`). `status_line()`
278
286
  builds the line and carries `idx_override`, so a per-tool redraw forks no git
279
287
  (`hosts/omp/hook.py::status_line`); it redraws on session start, session switch, turn end
280
- and each watched tool result (hosts/omp/tezgah-hook.ts.in:596-636,674-679,720-745).
288
+ and each watched tool result (`refresh` and `WATCHED` in `hosts/omp/tezgah-hook.ts.in`).
281
289
  **Motion:** while the agent runs (`agent_start` until an `agent_end` whose
282
290
  `isTerminal` is not `false`) the widget leads with a braille spinner, the tool
283
291
  in flight (`tool_execution_start`/`_end`, "thinking" between tools) and the
@@ -297,20 +305,26 @@ Every other surface reads the store.
297
305
  (`tui.json`'s `plugin` array, not the server plugin) registering an `app_bottom`
298
306
  slot, shelling out to `tezgah-status <dir> --json <sessionID>`, coloring each
299
307
  segment from the active theme and rebuilding the separator from `seg.group`
300
- (56-88,hosts/opencode/tui/tezgah-tui.tsx:108-111). Its `tezgah: status legend` command opens a dialog running
301
- `--legend` (141-147).
308
+ (`TezgahBar` in `hosts/opencode/tui/tezgah-tui.tsx`). Its `tezgah: status legend` command opens a dialog running
309
+ `--legend` (`LegendDialog` in `hosts/opencode/tui/tezgah-tui.tsx`).
302
310
  On opencode 2 the same line comes from hosts/opencode/tui/cli/tui.tsx, a
303
311
  `Plugin.define` CLI plugin listed in `cli.json`. It reuses the v1 file's `run`.
304
312
  - **dsh** — hosts/dsh/statusline/lib/index.js serves `GET /api/tezgah.status`
305
313
  behind the Web UI's own authenticated fetch fence, so the browser fetches it
306
- same-origin with no extra token handling (14,34-38); it always passes
307
- `--observable=consult,research,graph,orch,judge` (18,47-48) and returns `{segments,
308
- legend}` for `?format=json`, plain text otherwise (49-56,63). Its client half
314
+ same-origin with no extra token handling (`apply` in `hosts/dsh/statusline/lib/index.js`); it always passes
315
+ `--observable=consult,research,graph,orch,judge` (`OBSERVABLE` in `hosts/dsh/statusline/lib/index.js`) and returns `{segments,
316
+ legend}` for `?format=json`, plain text otherwise (`statusText` in `hosts/dsh/statusline/lib/index.js`). Its client half
309
317
  (hosts/dsh/statusline/lib/client.js) renders the marks in the session header in
310
318
  fixed light/dark colors, polls every 10 s while the tab is visible, and shows the
311
- legend on hover, pins it on click (16-19,35,47-52,63-64).
319
+ legend on hover, pins it on click (`TezgahStatusLine` in `hosts/dsh/statusline/lib/client.js`).
312
320
  - **Codex** — hosts/codex/hook.py puts the plain line in `systemMessage`
313
- (182-184,195-198).
321
+ (`hosts/codex/hook.py::main`).
322
+ - **Amp** — the plugin draws the widest plain tier
323
+ (`hosts/amp/hook.py::status_line`) on Amp's experimental status item
324
+ (`createStatusItem`, TUI only, plain text). It draws only for the thread the
325
+ user is looking at. It redraws on `session.start`, `agent.end` and each
326
+ watched tool result, and shows `hook✗` while the hook fails. A build without
327
+ the API has no status surface (`hosts/amp/tezgah-plugin.ts.in`).
314
328
 
315
329
  ## Seeing and debugging the line
316
330
 
@@ -330,7 +344,8 @@ tezgah-status --judge # what the judge used last, and why
330
344
 
331
345
  The session id is the second positional argument or `TEZGAH_SESSION`; without it
332
346
  the used marks cannot light up (`bin/tezgah-status::main`). `--color`/`--no-color` force ANSI or plain,
333
- `--observable=` narrows the measures as above, and an unknown flag exits 2 (`bin/tezgah-status::main`).
347
+ `--observable=` narrows the measures as above, and an unknown flag exits 2. So does
348
+ an `--observable=` measure outside `TOOL_USE_MEASURES` plus `pony` and `adhd` (`bin/tezgah-status::main`).
334
349
 
335
350
  `--counters` is not the marks: it prints `tezgah_integrity.counters(session)` —
336
351
  `steps`, `tool_error_rate`, `claims`, `false_completion`, `shape_blocked`,
@@ -407,7 +422,8 @@ could not be read - so the mark refuses to say either fresh or stale, and
407
422
  `index_notice()` says on the turn that the graph's age is unknown. On a host that
408
423
  sandboxes hook writes (dsh) the stamp is never written, and before that state
409
424
  existed the mark read green there for good. That is why `observable` exists (`hooks/tezgah_context.py::health_segments`),
410
- why the skill-read check is documented as unavailable on Codex, Cursor and dsh
425
+ why the skill-read check is documented as unavailable on every host that passes
426
+ the tool-use set or the dsh literal (see `--observable=` above)
411
427
  (`hooks/tezgah_context.py::skill_read_kind`), why omp ignores any `idx` value that is not one of
412
428
  its five glyphs (`hosts/omp/hook.py::status_line`) - a value outside that set re-probes
413
429
  instead of being reused, one fork and never a wrong mark - and
package/docs/testing.md CHANGED
@@ -47,7 +47,7 @@ baseline on the next run. Only `--clarity --update` raises it.
47
47
  A pin is a claim about what ran, not a guarantee about what will. This repository
48
48
  has two, and both are about the language rather than about dependencies: the
49
49
  floor `py310` (`pyproject.toml:5`) and the CI matrix that proves it
50
- (`.github/workflows/ci.yml:114`). There is no dependency lockfile to pin
50
+ (the `test` job's `python-version` matrix in `.github/workflows/ci.yml`). There is no dependency lockfile to pin
51
51
  because there is no package: tezgah is a set of scripts run in place, and no
52
52
  `[project]` or build system is declared (`pyproject.toml:1-2`). The only
53
53
  third-party tool the checks name is ruff, at a version in
@@ -65,8 +65,15 @@ conclude from either.
65
65
 
66
66
  `.github/workflows/ci.yml` runs on a push to `main` and on a pull request, so
67
67
  a branch commit runs once, through its pull request, not twice. A tag push does
68
- not start it. `release.yml` calls it (`ci.yml:13`) as its `ci` job, so npm and
69
- brew publish only after CI passed on the release commit. A newer push to the
68
+ not start it. `release.yml` calls it (its `workflow_call` trigger) as its `ci` job, so npm and
69
+ brew publish only after CI passed on the release commit. Its first job,
70
+ `on-main`, fails the release when the tag's commit is not on `origin/main`.
71
+ Then no job builds or uploads anything. Next, `green` looks for a finished push
72
+ run of `ci.yml` on the same commit. It waits for a running one and counts only
73
+ `success`, and then `ci` does not run: a release does not run the suite twice. No
74
+ such run, or any other outcome, runs `ci` as before. `npm-publish` and
75
+ `brew-formula` need `green` and `ci`, and publish when either answered green.
76
+ A newer push to the
70
77
  same ref cancels the run it replaces, except on a release (`concurrency`). Every
71
78
  job has a `timeout-minutes`, so a hung test cannot hold a runner for six hours.
72
79
  No matrix cancels its other legs when one fails (`fail-fast: false`).
@@ -77,33 +84,58 @@ No matrix cancels its other legs when one fails (`fail-fast: false`).
77
84
  empty diff, a base it cannot read, a release or a manual run runs everything.
78
85
  - `docs`, prose only: the citations audit, the clarity audit and
79
86
  `tests/impacted.py --ref <base>`, which maps a docs page to the docs modules.
80
- - `test` (`ci.yml:100`) on Python 3.10 and 3.14, the floor and the ceiling.
81
- It byte-compiles, lints with ruff from `requirements-dev.txt` (pip cached),
82
- audits the docs citations and clarity and runs `TEZGAH_E2E_STRICT=1 python3
83
- tests/e2e_plan_flow.py`. A release passes the `release` input and runs one
84
- 3.10 leg.
85
- - `test-sharded` (`ci.yml:142`) on Python 3.10 and 3.14 with node 22 (the
87
+ - `test` runs on Python 3.10 and 3.14, the floor and the ceiling. Its first steps
88
+ are "Byte-compile every script", "Install ruff" (from `requirements-dev.txt`,
89
+ pip cached) and "Lint". Then come "Every docs citation lands in the symbol it
90
+ names" and "The docs prose audit". The last two are "The plan and decision
91
+ rules, in a real project" (`TEZGAH_E2E_STRICT=1 python3 tests/e2e_plan_flow.py`)
92
+ and "The research checks against their known-truth set" (`python3
93
+ bin/tezgah-research release-check`). A release passes the `release` input and
94
+ runs one 3.10 leg.
95
+ - `test-sharded` on Python 3.10 and 3.14 with node 22 (the
86
96
  first line that loads the omp `.ts` hook without a flag). On `main` and a
87
- release it runs `python3 tests/impacted.py --all`. On a pull request it runs
88
- `--ref <base>`: the modules the change maps to, and the whole suite when the
89
- map does not know a path. It replaced the serial `unittest discover` step on
97
+ release its step "Run the suite, sharded" runs `python3 tests/impacted.py --all`.
98
+ On a pull request "Run the impacted modules, sharded (pull request)" runs
99
+ `--ref <base>` instead. That runs the modules the change maps to, or the whole
100
+ suite when the map does not know a path. It replaced the serial `unittest discover` step on
90
101
  2026-10-13: both caught the same failures, and the serial step took about 30
91
102
  minutes.
92
- - `apps-e2e` (`ci.yml:175`) on Python 3.12 and node 20: one step,
103
+ - `apps-e2e` on Python 3.12 and node 20: one step, "App MCP handshake
104
+ (playwright + mobile-mcp)",
93
105
  `TEZGAH_E2E_STRICT=1 python3 tests/e2e_analyze_wiring.py`. It completes the
94
106
  app-MCP handshake only - no browser, no device - so it stays deterministic;
95
107
  strict mode makes a server that cannot start a failure rather than a skip, so
96
108
  a broken wire cannot hide behind a skip (`e2e_analyze_wiring.py:4-12`).
97
- - `artifact-install` (`ci.yml:200`) on Python 3.10 and 3.12: builds the
109
+ - `artifact-install` on Python 3.10 and 3.12: builds the
98
110
  tarball, installs from it under a temp prefix, and runs the artifact smoke on
99
111
  the installed tree instead of the checkout.
100
- - `artifact-install-windows` (`ci.yml:243`) on `windows-latest`: the same
112
+ - `artifact-install-windows` on `windows-latest`: the same
101
113
  build, install and smoke through `packaging/install.ps1` - the twin a Windows
102
114
  user runs - so it is the only place the Windows claim is proven. It then runs
103
115
  the opencode plugin's path tests natively, `tezgah update --dry-run`, the
104
116
  opencode live probe and `--uninstall` from the installed tree. Any residue
105
117
  under the prefix fails it.
106
118
 
119
+ Four node harnesses run JavaScript and TypeScript the suite cannot import. Their
120
+ Python modules drive them, so they run wherever those modules run: in
121
+ `test-sharded`, which installs node 22, and locally under `tests/impacted.py`.
122
+
123
+ - `tests/_opencode_plugin_harness.mjs` loads the opencode plugin
124
+ (`tests/test_opencode_plugin.py`).
125
+ - `tests/_omp_extension_harness.mjs` loads the omp extension
126
+ (`tests/test_omp_extension.py`) and `tests/_amp_plugin_harness.mjs` the Amp
127
+ plugin (`tests/test_amp_plugin.py`). Both need a node that strips types
128
+ (22.18+).
129
+ - `tests/_workflow_harness.mjs` is a stub of the Workflow runtime. It runs
130
+ `workflows/graph-review.js`, `workflows/graph-impact.js` and
131
+ `workflows/graph-map.js` as async function
132
+ bodies. It answers each `agent()` from a fixture, and `null` is a failed agent.
133
+ `python3 -m pytest tests/test_graph_workflows.py` runs it.
134
+
135
+ Without node, or with a node too old for `.ts`, these modules skip rather than
136
+ fail, so a leg with no node passes them silently. `test-sharded` is the leg
137
+ that proves them.
138
+
107
139
  `.github/workflows/neuter.yml` runs two jobs weekly, on demand, and on a push to
108
140
  `main` that changes a guard, the matrix or a test module it runs. The matrix is
109
141
  CI's, not the local loop's. The first job, `neuter`, asks the question the suite
@@ -125,10 +157,11 @@ row.
125
157
  Each mutant first runs alone the test that killed it last time, read from
126
158
  `tests/neuter_killers.json`. When that test passes or is gone, the mutant runs
127
159
  every module in its own HOME and TMPDIR. The module that reads the mutated file
128
- goes first. The first failing test becomes its new entry. The
160
+ goes first. With `--update-killers`, the first failing test becomes its new
161
+ entry. Without it, the tracked cache is left as it was. The
129
162
  control runs every module, then every cached killer alone, so a killer that
130
163
  fails without a mutant voids the run. A session that adds a deny rule checks its
131
- one mutant with `python3 tests/neuter_matrix.py --only <name>`.
164
+ one mutant with `python3 tests/neuter_matrix.py --only <name> --update-killers`.
132
165
 
133
166
  The second, `fuzz-shell`, runs the bash-oracle fuzzer (`tests/fuzz_shell.py`)
134
167
  under the ubuntu runner's bash 5.x. Locally, macOS ships bash 3.2. A first step
package/docs/vision.md CHANGED
@@ -117,8 +117,12 @@ conflicting SWE-bench split, the agent cheated in 0 of 132 runs with tezgah and
117
117
  no headroom. Hand labels of final messages on those impossible tasks found
118
118
  false done claims in 3 of 30 runs with tezgah and 6 of 30 without. The CI of
119
119
  that difference includes 0. The Stop rule's claim reader
120
- flagged far more runs than that. On Turkish replies it counts `doğrulandı`
121
- (verified) on a partial fix as a claim, a recorded weakness not yet fixed. The
120
+ flagged far more runs than that. On Turkish replies it counted `doğrulandı`
121
+ (verified) on a partial fix as a claim, even beside a `doğrulanmadı`. Commit
122
+ 9cb4d1d fixed that shape. Its held-out recall is 0.857, below the 0.9 bar. A
123
+ narrower gap remains. A Turkish partial fix with no admission word
124
+ (`hâlâ başarısız`, `kısmen`) still reads as done and verified. Its English twin
125
+ reads as verification only. The
122
126
  numbers, the per-task counts and the instrument flaws are in
123
127
  [the results bundle](results/paired-outcome-2026-10.md).
124
128
 
package/hooks/hooks.json CHANGED
@@ -13,7 +13,7 @@
13
13
  { "hooks": [ { "type": "command", "command": "TEZGAH_HOST=claude \"${TEZGAH_PYTHON:-python3}\" \"${CLAUDE_PLUGIN_ROOT}/hooks/projects-auto-init.py\"", "timeout": 10 } ] }
14
14
  ],
15
15
  "PreToolUse": [
16
- { "matcher": "Agent|agent|Task|task|Grep|grep|Bash|bash|PowerShell|pwsh|Shell|shell|subagent|Edit|Write|MultiEdit|NotebookEdit|AskUserQuestion", "hooks": [ { "type": "command", "command": "\"${TEZGAH_PYTHON:-python3}\" \"${CLAUDE_PLUGIN_ROOT}/hooks/projects-pretooluse.py\"", "timeout": 5 } ] },
16
+ { "matcher": "Agent|agent|Task|task|Grep|grep|Bash|bash|PowerShell|pwsh|Shell|shell|Monitor|monitor|subagent|Edit|Write|MultiEdit|NotebookEdit|AskUserQuestion", "hooks": [ { "type": "command", "command": "\"${TEZGAH_PYTHON:-python3}\" \"${CLAUDE_PLUGIN_ROOT}/hooks/projects-pretooluse.py\"", "timeout": 5 } ] },
17
17
  { "matcher": "^mcp__(?:.*[^A-Za-z0-9])?(?:append|cancel|clear|click|close|comment|commit|copy|create|delete|deploy|drag|edit|email|evaluate|exec|execute|fill|insert|install|kill|launch|merge|message|mkdir|move|navigate|notify|patch|pause|post|press|print|publish|push|put|release|remove|rename|replace|reply|resume|review|run|save|send|set|share|skip|start|stop|submit|swipe|tag|tap|terminate|type|uninstall|update|upload|write)(?:[^A-Za-z0-9].*)?$", "hooks": [ { "type": "command", "command": "\"${TEZGAH_PYTHON:-python3}\" \"${CLAUDE_PLUGIN_ROOT}/hooks/projects-pretooluse.py\"", "timeout": 5 } ] }
18
18
  ],
19
19
  "PostToolUse": [
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "English",
3
3
  "done": [
4
- "done|complete[d]?|finished|implemented|fixed|shipped|landed|merged|wired up|wired into|all tests? pass|all checks? pass|(?:tests?|checks?|suite) (?:is |are )?(?:green|passing)|build (?:passes|is green)"
4
+ "done|complete[d]?|finished|implemented|fixed|(?<!\\bis )(?<!\\bare )(?<!\\bget )(?<!\\bgets )(?<!\\bbe )(?<!\\bbeing )(?<!-)updated(?!-)|shipped|landed|merged|wired up|wired into|(?:all )?(?:tests?|checks?|suite|build) passed|(?:all )?(?:tests|checks) pass|(?:suite|build) passes|(?:tests?|checks?|suite) (?:is |are )?(?:green|passing)|build is green"
5
5
  ],
6
6
  "verified": [
7
7
  "tested|verified|i ran|ran the (?:tests?|suite|build|lint|checks?)"
8
8
  ],
9
9
  "negated": [
10
- "unverified|not verified|could ?n[o']t verify|verification (?:was )?skipped"
10
+ "unverified|not verified|could ?n[o']t verify|verification (?:was )?skipped|^\\s*(?:untested|not (?:yet )?(?:been )?(?:tested|checked))\\b|(?:^|[.!;]\\s+)(?:(?:this|the) (?:change|fix|patch)|it|i)\\b[^.\\n]{0,24}?(?:not|n't) (?:yet )?(?:been )?tested|not checked yet"
11
11
  ],
12
12
  "admitted": [
13
13
  "contradict|unsatisfiable|(?:can ?not|can't|can ?never|never) pass|impossible"
@@ -36,6 +36,9 @@
36
36
  "negation_before": [
37
37
  "\\bnot|n't|\\bnever"
38
38
  ],
39
+ "condition_before": [
40
+ "\\b(?:if|when|once|unless|until|whether)\\b"
41
+ ],
39
42
  "question_particle": [],
40
43
  "question_end": [],
41
44
  "clause_join": [
@@ -224,6 +227,9 @@
224
227
  "plan_build": [
225
228
  "\\bplans?\\b[^.?!\\n]{0,40}\\b(?:is ready|ready|written|drafted)\\b|\\b(?:wrote|opened|drafted|added|created) (?:the |a |this )?plans?\\b|\\b(?:build|implement|execute|start on|start) (?:the |this |that )?plans?\\b"
226
229
  ],
230
+ "owner_choice": [
231
+ "\\bwhich (?:variant|option|approach|design)s?\\b|\\b(?:v\\d+|option [a-h1-9]|variant [a-z0-9]+),? or (?:v\\d+|option [a-h1-9]|variant [a-z0-9]+)\\b|\\b(?:another|other|the other) session\\w*|\\bsession'?s (?:line|plan|branch)\\b|\\bclos\\w*\\b[^?\\n]{0,40}\\bline\\b|--allow-open|--limit\\b|\\.staging\\b|\\bhidden (?:dir|directory|folder)\\b|\\b(?:another|a different) (?:folder|dir|directory)\\b|\\b(?:work ?)?around\\b[^?\\n]{0,40}\\brefusal\\b"
232
+ ],
227
233
  "spend": [
228
234
  "spend\\w*|\\bpaid\\b|billing|\\bcost\\w*|\\bbudget\\w*|\\busd\\b|\\$\\s?\\d"
229
235
  ],
@@ -236,6 +242,12 @@
236
242
  "cheap_answer": [
237
243
  "yes|yep|yeah|ok(?:ay)?|sure|go(?: on| ahead)?|continue|proceed|do it|carry on|keep going|approved"
238
244
  ],
245
+ "hand_over": [
246
+ "\\bplease\\s+(?:run|execute)\\b|\\byour\\s+(?:step|turn|move)\\b(?=\\s*[:.;,!]|\\s*$)|\\bover to you\\b"
247
+ ],
248
+ "ran_it": [
249
+ "\\bi (?:ran|tested|verified|checked|executed)\\b|\\bran the (?:tests?|suite|build|lint|checks?)\\b|\\bwe ran\\b"
250
+ ],
239
251
  "outward": {
240
252
  "push": "\\bpush\\w*",
241
253
  "release": "\\brelease\\w*|\\btag(?:s|ged|ging)?\\b|version bump|bump the version",
@@ -329,6 +341,8 @@
329
341
  "slice_lead": "",
330
342
  "ready_lead": "",
331
343
  "ask_lead": "",
344
+ "handed_lead": "",
345
+ "plan_lead": "",
332
346
  "approval_heading": "Approval questions",
333
347
  "recommended": "Recommended",
334
348
  "grant_examples": "\"push without asking\", \"I approve\""
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "Turkish",
3
3
  "done": [
4
- "yaptım|tamamladım|tamamlandı|bitirdim|bitirildi|ekledim|eklendi|düzelttim|düzeltildi|hallettim|güncellendi|kuruldu|uygulandı|yayına girdi|kapatıldı|yüklendi|tüm testler geçti|testler geçti|testler yeşil|çalışıyor"
4
+ "yaptım|tamamladım|tamamlandı|bitirdim|bitirildi|ekledim|eklendi|düzelttim|düzeltildi|hallettim|güncellendi|kuruldu|uygulandı|yayına girdi|kapatıldı|yüklendi|tüm testler geçti|testler geçti|testler geçiyor|testler yeşil|çalışıyor"
5
5
  ],
6
6
  "verified": [
7
7
  "doğruladım|test ettim|kontrol ettim|denetledim|doğrulandı|test edildi"
@@ -89,6 +89,7 @@
89
89
  "negation_before": [
90
90
  "\\bdeğil"
91
91
  ],
92
+ "condition_before": [],
92
93
  "question_particle": [
93
94
  "m[ıiuü]|değil"
94
95
  ],
@@ -200,7 +201,7 @@
200
201
  "ürün(?!\\w*_)\\w*|yol harita\\w*|kuzey yıldız\\w*|aktivasyon\\w*|dönüşüm\\w*|fiyatlandır\\w*|önceliklendir\\w*|kullanıcı araştırma\\w*|ürün keşf\\w*|müşteri geri bildirim\\w*|benimsen\\w*|konumlandır\\w*|segmentasyon\\w*|hedef kitle\\w*|(kullanıcı|müşteri) discovery|keşif (görüşme|çalışma)\\w*|fırsat\\w*|memnuniyet\\w*|anket\\w*|(kullanıcı|müşteri)[ -]?(feedback|geri bildirim)\\w*|(customer|user)[ -]?geri bildirim\\w*|şikayet\\w*|rakip\\w*|rekabet\\w*|pazar pay\\w*|terk oran\\w*|elde tutma\\w*|abonelik\\w*|gelir model\\w*|özellik\\w*|sayfa(?! numara)\\w*|ekran(?! kart)(?!a\\b[^.!?\\n]{0,40}\\b(?:yaz|bas)\\w*)\\w*|arayüz\\w*|arama kutu\\w*|filtre\\w*|tablo(?![^.!?\\n]{0,60}\\b(?:sütun|kolon|sql|index|indeks)\\w*)\\w*|adım(?! sayı)\\w*|kullanıcı liste\\w*|form(u|un|da|daki|lar|ları|unu)\\w*[^.!?\\n]{0,40}\\b(?:incele|denetle|analiz|de[ğg]erlendir|iyile[şs]tir|sadele[şs]tir|kontrol)\\w*"
201
202
  ],
202
203
  "design": [
203
- "(?:(?<![\\w'’] )|(?<=\\bbu )|(?<=\\bşu ))tasar[ıi]m\\w*|(?:dashboard|pano|g[öo]sterge panel|ekran|sayfa|aray[üu]z|landing|logo|ikon)\\w* (?:\\w+ ){0,2}(?:tasarla|tasar[ıi]m|redesign)\\w*|(?:web[ -]?)?site\\w* tasar[ıi]m\\w*|(?:mobil |web )?uygulama\\w* tasar[ıi]m\\w*|(?:web|mobil) tasar[ıi]m\\w*|tasar[ıi]m (?:sistem|token|dil|rehber)\\w*|yeniden tasarla\\w*|ba[şs]tan tasarla\\w*|design [çc]al[ıi][şs]ma\\w*|aray[üu]z\\w*|(?:ui|aray[üu]z|form|buton|d[üu][ğg]me|kart|men[üu]|sayfa|ekran|react|vue)\\w* (?:\\w+ )?bile[şs]en\\w*|bile[şs]en (?:k[üu]t[üu]phane|sistem)\\w*|tipografi\\w*|renk\\w*|tema(?:s[ıi]|y[ıi]|lar\\w*)?|kullan[ıi]labilirlik\\w*|eri[şs]ilebilirli[kğg]\\w*|g[öo]rsel (?:hiyerar[şs]i|tutarl[ıi]l[ıi]k|denetim|ele[şs]tiri)\\w*",
204
+ "(?:(?<![\\w'’] )|(?<=\\bbu )|(?<=\\bşu ))tasar[ıi]m\\w*|(?:dashboard|pano|g[öo]sterge panel|ekran|sayfa|aray[üu]z|landing|logo|ikon)\\w* (?:\\w+ ){0,2}(?:tasarla|tasar[ıi]m|redesign)\\w*|(?:uygulama(?:y[ıi]|s[ıi]n[ıi])?|site(?:yi|sini)?) redesign\\w*|(?:web[ -]?)?site\\w* tasar[ıi]m\\w*|(?:mobil |web )?uygulama\\w* tasar[ıi]m\\w*|(?:web|mobil) tasar[ıi]m\\w*|tasar[ıi]m (?:sistem|token|dil|rehber)\\w*|yeniden tasarla\\w*|ba[şs]tan tasarla\\w*|design [çc]al[ıi][şs]ma\\w*|aray[üu]z\\w*|(?:ui|aray[üu]z|form|buton|d[üu][ğg]me|kart|men[üu]|sayfa|ekran|react|vue)\\w* (?:\\w+ )?bile[şs]en\\w*|bile[şs]en (?:k[üu]t[üu]phane|sistem)\\w*|tipografi\\w*|renk\\w*|tema(?:s[ıi]|y[ıi]|lar\\w*)?|kullan[ıi]labilirlik\\w*|eri[şs]ilebilirli[kğg]\\w*|g[öo]rsel (?:hiyerar[şs]i|tutarl[ıi]l[ıi]k|denetim|ele[şs]tiri)\\w*",
204
205
  {
205
206
  "ui_ask": "iyile[şs]tir\\w*|g[üu]zelle[şs]tir\\w*|d[üu]zenle\\w*|kar[ıi][şs][ıi]k\\w*|tutars[ıi]z\\w*|da[ğg][ıi]n[ıi]k\\w*|[çc]irkin\\w*"
206
207
  },
@@ -268,6 +269,9 @@
268
269
  "plan_build": [
269
270
  "\\bplan\\w*[^.?!\\n]{0,40}(?:hazır|yazıldı|yazdım|oluşturdum|oluşturuldu|açtım|açıldı|eklendi|uygula\\w*|başla\\w*|kur\\w*)|(?:yazdığım|açtığım|hazırladığım|oluşturduğum) plan\\w*"
270
271
  ],
272
+ "owner_choice": [
273
+ "\\b(?:v\\d+|seçenek \\w+|varyant \\w+)(?:'\\w+)? m[ıiuü]\\b,? (?:v\\d+|(?:seçenek |varyant )?\\w+)(?:'\\w+)? m[ıiuü]\\b|\\bhangi (?:varyant|seçenek|opsiyon|yaklaşım|tasarım)\\w*|\\bhangisini seç\\w*|\\b(?:başka|diğer) (?:bir )?oturum\\w*|\\bhatt[ıi]\\w* kapat\\w*|--allow-open|--limit\\b|\\.staging\\b|\\bgizli (?:dizin|klasör)\\w*|\\b(?:başka|farklı) (?:bir )?(?:klasör|dizin)\\w*|\\bred\\w* (?:aş|etrafından)\\w*|\\bengel\\w* (?:aş|etrafından)\\w*"
274
+ ],
271
275
  "spend": [
272
276
  "ücret\\w*|ödeme\\w*|harca\\w*|bütçe\\w*|maliyet\\w*|\\bpara\\b|dolar\\w*|\\$\\s?\\d"
273
277
  ],
@@ -280,6 +284,12 @@
280
284
  "cheap_answer": [
281
285
  "evet|tamam|olur|devam(?: et)?|yap|başla|ilerle|okey|onay(?:lıyorum)?"
282
286
  ],
287
+ "hand_over": [
288
+ "\\b(?:sen|siz)\\b[^.!?\\n]{0,32}?\\bçalıştır(?:ın|ınız)?\\b|\\bsenin adımın\\b|\\badım(?:ı)? sende\\b|\\bsıradaki adım(?:ı)? sen\\b"
289
+ ],
290
+ "ran_it": [
291
+ "\\b(?:test(?:ler)?i|suite'?i|komutu|kontrolü|denetimi)?\\s*(?:çalıştırdım|koştum|çalıştırdık)\\b|\\btest ettim\\b|\\bdoğruladım\\b|\\bkontrol ettim\\b|\\bdenetledim\\b"
292
+ ],
283
293
  "outward": {
284
294
  "push": "\\bpush\\w*",
285
295
  "release": "versiyon\\w*|sürüm\\w*|\\brelease\\w*|etiket\\w*",
@@ -350,6 +360,8 @@
350
360
  "slice_lead": "Durmuş bir dilim var. ",
351
361
  "ready_lead": "Hazır adımlar bekliyor. ",
352
362
  "ask_lead": "Bu soruyu sorma: kararı ver ya da önerili onay sorusuna çevir. ",
363
+ "handed_lead": "Bu adımı sen çalıştır, kullanıcıya bırakma. ",
364
+ "plan_lead": "Plan modunda hiçbir şey koşmadı. ",
353
365
  "approval_heading": "Onay için sorular",
354
366
  "recommended": "Öneri",
355
367
  "grant_examples": "\"bana sormadan pushla\", \"onay veriyorum\""
@@ -69,11 +69,19 @@ def result_size(result):
69
69
  so a container is measured by its top-level length (O(1) for str, bytes,
70
70
  list, dict, set) instead of re-serializing it: `json.dumps` on every
71
71
  PostToolUse copied the whole tool response and broke the hook's cost bound
72
- ("a sha1 over a short string and a few fields; nothing else"). ponytail: a
73
- dict result therefore reports its field count, not its byte length - the
74
- host reports no size of its own, and no reader compares the two."""
72
+ ("a sha1 over a short string and a few fields; nothing else"). A shell
73
+ result is the exception: Claude's Bash answers `{"stdout", "stderr",
74
+ "interrupted", ...}`, so its field count is never 0 and a check that
75
+ printed nothing would read as output (`passing_check`'s silent failure,
76
+ B-005); its stream strings (`ran_nothing`'s keys) are measured instead.
77
+ ponytail: any other dict reports its field count, not its byte length."""
75
78
  if result is None:
76
79
  return None
80
+ if isinstance(result, dict):
81
+ streams = [result[k] for k in ("stdout", "stderr", "output")
82
+ if isinstance(result.get(k), str)]
83
+ if streams:
84
+ return sum(map(len, streams))
77
85
  if isinstance(result, (str, bytes, bytearray, list, tuple, dict, set,
78
86
  frozenset)):
79
87
  return len(result)
@@ -48,7 +48,7 @@ def main():
48
48
  if not isinstance(p, dict):
49
49
  return
50
50
  session_id = p.get("session_id")
51
- safe(session_id, bind_session, session_id) # the switch latch (tezgah_paths.off)
51
+ safe(session_id, bind_session, session_id, p.get("parent")) # the switch latch
52
52
  cwd = p.get("cwd") or os.getcwd()
53
53
  if p.get("hook_event_name") == "SubagentStop" and p.get("agent_id") \
54
54
  and root_for(cwd) and not off("slices-off"):
@@ -64,7 +64,8 @@ def main():
64
64
  # a core that cannot answer has refused nothing, so the turn ends: the rule
65
65
  # never traps a session, and that holds for its own failure too
66
66
  reason = safe(session_id, stop_reason, p.get("last_assistant_message"),
67
- session_id, cwd=cwd, after_block=bool(p.get("stop_hook_active")))
67
+ session_id, cwd=cwd, after_block=bool(p.get("stop_hook_active")),
68
+ mode=p.get("permission_mode"))
68
69
  if reason:
69
70
  json.dump({"decision": "block", "reason": reason}, sys.stdout)
70
71
 
@@ -245,6 +245,15 @@ def _reviewer_body(host):
245
245
  "for those files: account for every listed file as reviewed or skipped\n"
246
246
  "with a reason, and still apply everything above - OCR output is input,\n"
247
247
  "never a verdict.\n\n"
248
+ "Plan readiness. When the target is a plan file\n"
249
+ "(`.tezgah/plans/open/NNN-*.md`) rather than a diff, review whether it\n"
250
+ "can be built without a question. First restate each section's intent\n"
251
+ "in one sentence of your own; a section you cannot restate lacks\n"
252
+ "context, which is a major finding. Then check that every choice the\n"
253
+ "build needs is made in the plan - schema, API path, dependency,\n"
254
+ "edge-case handling, the files to change; a choice left to the\n"
255
+ "implementer is a major finding. A file the plan will create is not a\n"
256
+ "missing reference. Classify and report exactly as for a diff.\n\n"
248
257
  "%s" % (_graph_howto(host), _blast_radius(host), EMPTY_IS_NOT_PROOF,
249
258
  decision, decision, _may_use(host)))
250
259
 
@@ -265,14 +274,26 @@ def _worker_body(tier):
265
274
  "report what changed and what each check printed, verbatim. Report\n"
266
275
  "progress, but never redefine the work. Never widen or narrow the brief's\n"
267
276
  "scope, budget or question. If the brief cannot be met as written, say\n"
268
- "so and stop rather than answering a different question.\n%s\n"
277
+ "so and stop rather than answering a different question.\n%s\n%s"
269
278
  "A brief that builds or changes UI: read the `design-contract` skill\n"
270
279
  "(and `design-library` for critique) and derive/check with\n"
271
280
  "`~/.config/tezgah/bin/tezgah-design` before editing.\n"
272
- "Never spawn subagents.\n%s" % (tier, tier, scope, WORKER_OUTPUT))
281
+ "Never spawn subagents.\n%s" % (tier, tier, scope, OWNER_ESCALATE,
282
+ WORKER_OUTPUT))
273
283
  return body
274
284
 
275
285
 
286
+ # A question only the owner can answer climbs past every tier to the user: a
287
+ # worker that settles it itself, frontier included, decided for the owner
288
+ # (plan 136: a line ran under a hidden dir and wrote `chosen:` with no owner
289
+ # choice). One text on every tier.
290
+ OWNER_ESCALATE = ("A question only the owner can answer - choosing a variant or option a\n"
291
+ "research, analysis or review recommends; a way around a tezgah\n"
292
+ "refusal; deciding about another session's work (its line, plan or\n"
293
+ "branch) - is never yours to decide, on any tier: stop and answer\n"
294
+ "`ESCALATE: owner` with the options and a recommended default.\n")
295
+
296
+
276
297
  # The report a consumer reads: the orchestrator re-briefs a report missing one
277
298
  # of these fields (_orch_body), so only a role with a reader gets them. One
278
299
  # text on every host, since a worker body ignores its host.
@@ -282,7 +303,8 @@ WORKER_OUTPUT = ("End the report, after any other shape asked for, with `Output
282
303
  "- commit: the sha\n"
283
304
  "- checks: each command, its exit code and the last lines of its\n"
284
305
  " output file, verbatim\n"
285
- "- escalate: the `ESCALATE:` reason (always `none` on tezgah-frontier)")
306
+ "- escalate: the `ESCALATE:` reason (on tezgah-frontier only\n"
307
+ " `ESCALATE: owner`, else `none`)")
286
308
 
287
309
 
288
310
  # The specialists beside the tier workers: name -> (what it does, the skills it
@@ -483,6 +505,8 @@ def _orch_body(names):
483
505
  "or an ESCALATE, gets one narrower re-brief of the same role naming the\n"
484
506
  "missing field or the escalated judgement; only a second miss is\n"
485
507
  "restarted on tezgah-frontier with the original brief, never continued.\n"
508
+ "An `ESCALATE: owner` is neither: its options and default go to the user\n"
509
+ "in the approval batch, and the orchestrator never answers it itself.\n"
486
510
  "Never delegate a task a specialist is not\n"
487
511
  "listed for, and never let a subagent spawn its own subagents. Verify each\n"
488
512
  "returned claim against the code before acting." % (listed, say()["blocker_tokens"]))