@r1z4x/tezgah 1.11.0 → 1.13.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 (60) hide show
  1. package/CHANGELOG.md +333 -1
  2. package/MANIFEST +22 -0
  3. package/README.md +4 -3
  4. package/bin/tezgah-docs +1 -1
  5. package/bin/tezgah-research +39 -4
  6. package/bin/tezgah-setup +1229 -22
  7. package/bin/tezgah-task +55 -17
  8. package/docs/citations-baseline.json +1 -1
  9. package/docs/clarity-baseline.json +9 -3
  10. package/docs/contract.md +6 -3
  11. package/docs/environment.md +3 -2
  12. package/docs/evidence.md +81 -8
  13. package/docs/gate.md +6 -2
  14. package/docs/glossary.md +1 -1
  15. package/docs/hosts.md +297 -15
  16. package/docs/layers.md +1 -1
  17. package/docs/models.md +6 -0
  18. package/docs/operations.md +16 -5
  19. package/docs/research.md +36 -10
  20. package/docs/status-line.md +6 -0
  21. package/docs/testing.md +3 -1
  22. package/hooks/hooks.json +1 -1
  23. package/hooks/lang/en.json +11 -0
  24. package/hooks/lang/tr.json +11 -0
  25. package/hooks/projects-stop.py +2 -1
  26. package/hooks/tezgah_agents.py +27 -3
  27. package/hooks/tezgah_attest.py +79 -10
  28. package/hooks/tezgah_context.py +21 -8
  29. package/hooks/tezgah_gate.py +79 -23
  30. package/hooks/tezgah_integrity.py +212 -38
  31. package/hooks/tezgah_paths.py +43 -2
  32. package/hooks/tezgah_policy.py +35 -13
  33. package/hooks/tezgah_replay.py +8 -2
  34. package/hooks/tezgah_research.py +167 -24
  35. package/hooks/tezgah_stack.py +236 -1
  36. package/hooks/tezgah_task.py +2 -1
  37. package/hosts/amp/hook.py +217 -0
  38. package/hosts/amp/tezgah-plugin.ts.in +386 -0
  39. package/hosts/antigravity/hook.py +255 -0
  40. package/hosts/codex/hook.py +2 -1
  41. package/hosts/commandcode/hook.py +239 -0
  42. package/hosts/copilot/hook.py +284 -0
  43. package/hosts/cursor/hook.py +2 -1
  44. package/hosts/droid/hook.py +259 -0
  45. package/hosts/dsh/hooks.json +1 -1
  46. package/hosts/gemini/hook.py +213 -0
  47. package/hosts/goose/hook.py +145 -0
  48. package/hosts/omp/hook.py +4 -1
  49. package/hosts/omp/tezgah-hook.ts.in +30 -7
  50. package/hosts/opencode/plugins/tezgah.js +2 -2
  51. package/hosts/qwen/hook.py +206 -0
  52. package/output-styles/tezgah.md +10 -5
  53. package/package.json +1 -1
  54. package/skills/harness/SKILL.md +2 -0
  55. package/skills/i-have-adhd/SKILL.md +10 -1
  56. package/skills/plan-add/SKILL.md +5 -1
  57. package/skills/research/SKILL.md +22 -9
  58. package/skills/tezgah-contract/SKILL.md +19 -6
  59. package/statusline.py +20 -9
  60. package/workflows/graph-review.js +14 -2
package/docs/hosts.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Hosts
2
2
 
3
- tezgah is one Python core with six host adapters around it: `claude`, `codex`,
4
- `cursor`, `opencode`, `dsh`, `omp`. This page is for the maintainer wiring a new
3
+ tezgah is one Python core with fourteen host adapters around it: `claude`, `codex`,
4
+ `cursor`, `opencode`, `dsh`, `omp`, `qwen`, `droid`, `commandcode`, `copilot`, `gemini`, `antigravity`, `goose`, `amp`. This page is for the maintainer wiring a new
5
5
  host, adding a mark, or debugging a status line that claims something a host
6
6
  cannot see. Read it when a question starts with "does this host get X?" or "can
7
7
  this host observe Y?" - the answer is not uniform, and guessing it produces a
@@ -23,6 +23,14 @@ surface can honestly report this session; the two skill-read marks (`pony`,
23
23
  | opencode | no lifecycle events; plugin hooks: config, permission.ask, tool.execute.before, shell.env, experimental.session.compacting, chat.message, tool.execute.after (`hosts/opencode/plugins/tezgah.js:2372-2737`) - `tool.execute.before` carries the whole gate, `tool.execute.after` the channel on the row, the provenance label on the result and the taint notice on the next effect (`untrustedSource` `hosts/opencode/plugins/tezgah.js:1942`, `labelResult` `:1993`) | static only: generated `instructions` files, because there is no session-start injection point (`hosts/opencode/plugins/tezgah.js:3-6`, `bin/tezgah-setup::opencode_contract_text`) | chat.message pushes a synthetic part (`hosts/opencode/plugins/tezgah.js:2652-2703`) | TUI plugin `tezgah-tui.tsx` declared in `tui.json`, runs `tezgah-status` on the event bus (`hosts/opencode/tui/tezgah-tui.tsx:1-11`; wiring `bin/tezgah-setup::install_opencode`) | all seven: skill reads and kinds are classified in process (`hosts/opencode/plugins/tezgah.js:2227-2263`) |
24
24
  | dsh | SessionStart, SubagentStart, UserPromptSubmit, PreToolUse, PostToolUse (`hosts/dsh/hooks.json:2-19`); no Stop rule, because the bridge drops every call's outcome and no check there can show a pass | hook: the same Claude-family scripts, named by `configPath` (`bin/tezgah-setup::dsh_patch_block`) | UserPromptSubmit, through the bridge (`hosts/dsh/hooks.json:9-11`) | web-profile plugin: authenticated `GET /api/tezgah.status` -> `tezgah-status` (`hosts/dsh/statusline/lib/index.js:15,66-104`) | five tool-use: the route passes `--observable=consult,research,graph,orch,judge` (`hosts/dsh/statusline/lib/index.js:16-19,84,96`) |
25
25
  | omp | session_start, session_compact, session_switch, turn_end, before_agent_start, tool_call, tool_result, session_stop (`hosts/omp/tezgah-hook.ts.in:596,655,674,679,681,701,720,755`) | both: static `RULES.md` (`bin/tezgah-setup::STATIC_RULES`, `bin/tezgah-setup::rules_block`) and a session payload carrying only what a static file cannot know (`hosts/omp/hook.py::handle`) | before_agent_start returns a hidden message (`hosts/omp/tezgah-hook.ts.in:681-699`) | extension draws `ctx.ui.setWidget` below the editor, `setStatus` as fallback (`hosts/omp/tezgah-hook.ts.in:170-203`); ANSI line from `hosts/omp/hook.py::status_line` | all seven: the embedded runner filters skill reads before asking python (`hosts/omp/tezgah-hook.ts.in:257-264`) and no `observable` is passed (`hosts/omp/hook.py::status_line`) |
26
+ | qwen | SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, PostToolUseFailure, SubagentStart, SubagentStop (record-only, `subagent_end`), Stop (`bin/tezgah-setup::QWEN_HOOK_EVENTS`), all in `~/.qwen/settings.json` beside Orca's rows | the managed block in `~/.qwen/QWEN.md` (`bin/tezgah-setup::STATIC_RULES`); SessionStart drops the core while the block is there (`hosts/qwen/hook.py::rules_carry_core`) | UserPromptSubmit -> `additionalContext` (`hosts/qwen/hook.py::main`) | `ui.statusLine` command in `~/.qwen/settings.json` -> `tezgah-statusline --qwen`, ANSI kept by `respectUserColors` (`bin/tezgah-setup::install_qwen`) | five tool-use (`statusline.py::observable`) |
27
+ | droid | SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, SubagentStop (record-only, `subagent_end`), Stop (`bin/tezgah-setup::DROID_HOOK_EVENTS`), all in `~/.factory/hooks.json` | the managed block in `~/.factory/AGENTS.md` (`bin/tezgah-setup::STATIC_RULES`); SessionStart drops the core while the block is there (`hosts/droid/hook.py::rules_carry_core`) | UserPromptSubmit -> `additionalContext` (`hosts/droid/hook.py::main`) | `statusLine` command in `~/.factory/settings.json` -> `tezgah-statusline --droid`, set in place with the file's comments kept (`bin/tezgah-setup::install_droid`, `bin/tezgah-setup::jsonc_put`) | five tool-use (`statusline.py::observable`) |
28
+ | commandcode | SessionStart, PreToolUse (matcher `shell\|write\|edit`), PostToolUse, Stop (`bin/tezgah-setup::COMMANDCODE_HOOK_EVENTS`), all in the `hooks` key of `~/.commandcode/settings.json` | the managed block in `~/.commandcode/AGENTS.md` (`bin/tezgah-setup::STATIC_RULES`); SessionStart drops the core while the block is there (`hosts/commandcode/hook.py::rules_carry_core`) | unavailable: Command Code has no prompt event | no custom status line - the segment rides `systemMessage`, plain, TUI only, at SessionStart and Stop (`hosts/commandcode/hook.py::status`) | five tool-use (`TOOL_USE_MEASURES` at `hosts/commandcode/hook.py::status`) |
29
+ | copilot | SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, PostToolUseFailure, subagentStart, SubagentStop (record-only, `subagent_end`), Stop (`bin/tezgah-setup::COPILOT_HOOK_EVENTS`), all in tezgah's own `$COPILOT_HOME/hooks/tezgah.json` | the managed block in `$COPILOT_HOME/copilot-instructions.md` (`bin/tezgah-setup::STATIC_RULES`); SessionStart drops the core while the block is there (`hosts/copilot/hook.py::rules_carry_core`) | unavailable: Copilot drops a command hook's UserPromptSubmit output; the event still writes the turn row (`hosts/copilot/hook.py::main`) | `statusLine` command in `$COPILOT_HOME/settings.json` -> `tezgah-statusline --copilot`, written only while that file is plain JSON (`bin/tezgah-setup::install_copilot`) | five tool-use (`statusline.py::observable`) |
30
+ | gemini | SessionStart, BeforeAgent, BeforeTool, AfterTool, AfterAgent (`bin/tezgah-setup::GEMINI_HOOK_EVENTS`), all in tezgah's own extension `~/.gemini/extensions/tezgah/hooks/hooks.json` | the managed block in `~/.gemini/GEMINI.md` (`bin/tezgah-setup::STATIC_RULES`), shared with Antigravity; SessionStart drops the core while the block is there (`hosts/gemini/hook.py::rules_carry_core`) | BeforeAgent -> `hookSpecificOutput.additionalContext`, appended to the prompt for that turn (`hosts/gemini/hook.py::main`) | no custom status line (`ui.footer.items` is a closed list) - the segment rides `systemMessage`, plain, at SessionStart and AfterAgent (`hosts/gemini/hook.py::status`) | five tool-use (`TOOL_USE_MEASURES` at `hosts/gemini/hook.py::status`) |
31
+ | antigravity | SessionStart, PreInvocation, PreToolUse, PostToolUse, Stop (`bin/tezgah-setup::ANTIGRAVITY_HOOK_EVENTS`), all under the `tezgah` name of `~/.gemini/config/hooks.json`; the payload names no event, so each command passes it as its first argument | the managed block in `~/.gemini/GEMINI.md` (`bin/tezgah-setup::STATIC_RULES`), shared with Gemini CLI; SessionStart injects the core as a system message while the block is missing (`hosts/antigravity/hook.py::rules_carry_core`) | PreInvocation with `invocationNum` 0 -> an `injectSteps` `ephemeralMessage`; whether that number restarts each user turn is undocumented (`hosts/antigravity/hook.py::main`) | `statusLine` command in `~/.gemini/antigravity-cli/settings.json` -> `tezgah-statusline --antigravity`, written only while that file is plain JSON (`bin/tezgah-setup::install_antigravity`) | five tool-use (`statusline.py::observable`) |
32
+ | goose | SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, PostToolUseFailure, Stop (`bin/tezgah-setup::GOOSE_HOOK_EVENTS`), all in tezgah's own plugin `~/.agents/plugins/tezgah/hooks/hooks.json`; the payload names the event in `event` | the managed block in `~/.config/goose/.goosehints` (`bin/tezgah-setup::STATIC_RULES`), which Goose adds to the system prompt of every request; no hook can inject it, because SessionStart is observation only | unavailable: Goose ignores UserPromptSubmit output; the event still writes the turn row (`hosts/goose/hook.py::main`) | unavailable: Goose has no status line and shows no hook output to the user | none drawn; the PostToolUse rows still record the five tool-use marks for `tezgah-status` (`hosts/goose/hook.py::post_tool`) |
33
+ | amp | no hook file: tezgah's TypeScript plugin `$XDG_CONFIG_HOME/amp/plugins/tezgah.ts` (else `~/.config/amp/plugins/tezgah.ts`) subscribes to `session.start`, `agent.start`, `tool.call`, `tool.result` and `agent.end` and forwards each to `hosts/amp/hook.py` (`hosts/amp/tezgah-plugin.ts.in`) | the managed block in `~/.config/amp/AGENTS.md` (`bin/tezgah-setup::STATIC_RULES`), which Amp always includes; `session.start` cannot inject, so the session brief rides the thread's next `agent.start`, with the core only while that block is missing or `AMP_IGNORE_GUIDANCE_FILES` is set (`hosts/amp/hook.py::handle`) | hook: `agent.start` returns the reminder as a hidden message (`display: false`); a prompt that starts with `[tezgah:stop]` is tezgah's own continue and sends no `user_prompt` | the experimental `createStatusItem`, plain text, for the thread the user is looking at only (owner-approved, plan 140 A-D15); without it, no status surface | none drawn beyond the status item; the `tool.result` rows record the five tool-use marks and a skill read |
26
34
 
27
35
  The middle five marks (`consult`, `research`, `graph`, `orch`, `judge`) are tool-use marks:
28
36
  any adapter that sees its host's tool calls can light them by calling
@@ -47,15 +55,37 @@ the code path there, but no run has shown the host obeying it.
47
55
  | opencode | gate, probed by `--report --live` through a node driver | unavailable: the plugin API has no end-of-turn event | unavailable |
48
56
  | dsh | gate, probed by `--report --live` through the bridge | unavailable: ADR 003 removed the Stop entry, because the bridge carries no outcome | partial, unverified: a brief at SubagentStart, no SubagentStop entry |
49
57
  | omp | gate, probed by `--report --live` (node 22.6+) | gate, unverified (`hosts/omp/hook.py::handle`) | partial, unverified: a brief at a task session's start; `session_stop` does not fire for task sessions |
58
+ | qwen | gate, probed by `--report --live` through the hook command in `~/.qwen/settings.json`; that the qwen binary obeys the deny is unverified until `TEZGAH_E2E_SPEND=1 python3 tests/e2e_qwen.py` runs (it spends a model call) | gate, unverified (`hosts/qwen/hook.py::main`, judged on `last_assistant_message`) | observe, unverified: a brief at SubagentStart, a record-only `subagent_end` row at SubagentStop |
59
+ | droid | gate, probed by `--report --live` through the hook command in `~/.factory/hooks.json`; that the droid binary obeys the deny is unverified until `TEZGAH_E2E_SPEND=1 python3 tests/e2e_droid.py` runs (it spends a model call) | gate, unverified: Droid's Stop carries no reply, so the reply is read from `transcript_path` (`hosts/droid/hook.py::last_reply`), and a turn end whose reply is not there yet is a `reply_unread` row, never judged | partial, unverified: a record-only `subagent_end` row from SubagentStop's `task_result`; no SubagentStart, so no brief |
60
+ | commandcode | gate, unverified: probed by `--report --live` through the hook command in `~/.commandcode/settings.json`, but no run of the binary has shown it obeying the deny; only shell, write and edit reach the hook (read, MCP, subagent and web tools do not), and plan mode skips every hook | gate, unverified: Stop carries no reply, so the reply is read from `transcript_path` (`hosts/commandcode/hook.py::last_reply`) and a turn end with none there is a `reply_unread` row, never judged; the host caps a block at three retries | unavailable: the settings hooks have no subagent event |
61
+ | copilot | gate, unverified: probed by `--report --live` through the hook command in `$COPILOT_HOME/hooks/tezgah.json`, which answers with a top-level `permissionDecision: deny`, but no run of the binary has shown it obeying the deny. A PreToolUse hook that times out (10 s) lets the call through, by Copilot's own rule; one that crashes denies it. A repo's `.claude/settings.json` hooks run there too | gate, unverified: Stop carries no reply, so the reply is the last `assistant.message` in `transcript_path` (`hosts/copilot/hook.py::last_reply`), and a turn end with none there is a `reply_unread` row, never judged; the host ends the turn anyway after 8 blocks in a row | observe, unverified: a brief at subagentStart, a record-only `subagent_end` row at SubagentStop; the built-in `general-purpose` agent fires neither |
62
+ | gemini | gate, unverified: probed by `--report --live` through the hook command in `~/.gemini/extensions/tezgah/hooks/hooks.json`, which answers with a top-level `decision: deny`, but no run of the binary has shown it obeying the deny, and no run has shown Gemini loading an extension dir written without `gemini extensions install` (its loader reads every subdir; a non-empty `security.allowedExtensions` asks for install metadata tezgah does not write). A hook that crashes or prints bad JSON is a warning and the call goes on | gate, unverified: AfterAgent carries the reply as `prompt_response` and `stop_hook_active` (`hosts/gemini/hook.py::main`); a deny sends the reason back as a new prompt | unavailable: no subagent event; a subagent is a tool call (`codebase_investigator`, `generalist`, `invoke_agent`) the gate sees as `Task` |
63
+ | antigravity | gate, unverified: probed by `--report --live` through the hook command under the `tezgah` name of `~/.gemini/config/hooks.json`, fed Antigravity's camelCase envelope with the event as argv, which answers with a top-level `decision: deny` (an allow answers `decision: allow`, since the field is required), but no run of the binary has shown it obeying the deny. What Antigravity does when a hook crashes or times out is undocumented. The same file arms the Antigravity App and IDE | gate, unverified: Stop carries no reply, so the reply is read from `transcriptPath` (`hosts/antigravity/hook.py::last_reply`), and a turn end with none there is a `reply_unread` row, never judged; only an idle `model_stop` is judged, and `decision: continue` sends the model back into the loop with the reason as a system message. No block counter: the turn's refusal rows bound the re-asks | unavailable: no subagent event; a child conversation keeps its own ledger, and `invoke_subagent` reaches the gate as Task |
64
+ | goose | gate, unverified: probed by `--report --live` through the hook command in `~/.agents/plugins/tezgah/hooks/hooks.json`, which answers `{"decision": "block"}` on stdout, but no run of the binary has shown it obeying the block, nor loading a plugin dir written by hand rather than by `goose plugin install` (the plugins guide says both are discovered). `on_failure` is left at its default `allow`, so a hook that crashes, times out or prints no decision lets the call through: the gate fails open on a hook fault. Only `shell`, `write`, `edit` and `<extension>__<tool>` reach the gate | gate, unverified: Stop carries the reply as `last_assistant_message` (`hosts/goose/hook.py::main`), and a Stop with none is a `reply_unread` row, never judged. It carries no count of earlier blocks, so the turn's own refusal rows bound the re-asks; Goose ends the turn anyway after `GOOSE_STOP_HOOK_BLOCK_CAP` blocks in a row | unavailable: SubagentStart and SubagentStop are not emitted; the subagent tool is not mapped |
65
+ | amp | gate, unverified: `--report --live` loads the installed plugin with node 22.6+ (`--experimental-strip-types`) and fires its `tool.call` on a `Bash` call carrying `cmd`; the plugin answers `{action: "reject-and-continue"}`, but Amp's own Bun loader is not what ran it, so the row stays UNVERIFIED. A hook that is missing, crashes, times out or prints no JSON answers `{action: "allow"}`: the gate fails open. Only `Bash`, `shell_command`, `edit_file`, `create_file`, `apply_patch`, `Task` and an MCP effect name reach the gate | gate, unverified: `agent.end` with status `done` asks the Stop rule on the last assistant message and continues the turn with `[tezgah:stop] <reason>`; a turn that marker started is judged as the re-ask (`stop_hook_active`), and Amp's own cap of 5 continues backs the core's budget. Error and cancelled turns are not judged. A shell result passes only on an integer `exitCode` read off an output object (`hosts/amp/hook.py::call_failed`); Amp's docs do not show that its Bash result carries one, so until a run shows it no Amp check counts as passed | partial, unverified: a thread with a parent (`ctx.thread.parentThreadID()`) gets the subagent brief and a record-only `subagent_end` row at its `agent.end`; whether a Task subagent's events reach plugins is undocumented |
50
66
 
51
67
  Every host also attests its own hook entries once per session
52
- (`hooks/tezgah_attest.py::run`). Claude, dsh, Codex, Cursor and omp do it at
53
- session start. opencode does it at the first message, through `oncePerSession`.
68
+ (`hooks/tezgah_attest.py::run`). Every host but opencode does it at session
69
+ start. opencode does it at the first message, through `oncePerSession`.
54
70
  The `attest` row and the `drift` mark are
55
71
  [evidence](evidence.md#the-kinds-by-what-reads-them), never a block.
56
72
  `--report` prints the same comparison per host
57
73
  (`bin/tezgah-setup::attestation_rows`).
58
74
 
75
+ Every Stop adapter hands the core the host's permission mode for the turn
76
+ (`hooks/tezgah_integrity.py::read_only_turn`, plan 142). Claude, Command Code,
77
+ Qwen, Copilot, Gemini, Codex, Goose, Droid and Cursor send it as
78
+ `permission_mode` on the Stop payload (Antigravity's envelope is camelCase,
79
+ `permissionMode`). omp has no such field, so its extension reads
80
+ `ctx.sessionModes.getState().plan` and sends `plan` when it is active
81
+ (`hosts/omp/tezgah-hook.ts.in`). A host that sends nothing reads as `default`.
82
+ Only `plan` and Droid's `spec` change a verdict, and only two of them: the rule
83
+ lets the approval question such a turn ends on through, and refuses a
84
+ done/tested claim in it
85
+ ([evidence](evidence.md#the-stop-rule-end-to-end)). Claude's hooks reference
86
+ documents the field on its own Stop payload. Every other adapter reads whatever
87
+ its payload carries.
88
+
59
89
  ## What each host gets, and where it is written
60
90
 
61
91
  Every host gets the skill symlinks from `SKILLS` (`bin/tezgah-setup::SKILLS`)
@@ -126,6 +156,122 @@ shared config, contract hash and the CLI symlinks every host shell can call
126
156
  its own: orx's `codex` target writes the shim to `~/.agents/skills/orx`,
127
157
  omp's native `agents` skill root, so `install_openresearch` maps omp onto it
128
158
  (`ORX_AGENTS`, `bin/tezgah-setup::ORX_AGENTS`).
159
+ - **qwen** - `install_qwen` (`bin/tezgah-setup::install_qwen`) writes into
160
+ `~/.qwen/settings.json` (or `$QWEN_HOME`). It adds one hook group per event of
161
+ `QWEN_HOOK_EVENTS`, each `timeout` in milliseconds. The PreToolUse group
162
+ carries `QWEN_PRETOOL_MATCHER` over Qwen's runtime tool ids. It also sets
163
+ `ui.statusLine`. Orca's groups and the provider keys in that file stay, and
164
+ the backup is owner-only (`bin/tezgah-setup::backup`). The install links
165
+ `skills/*` into `~/.qwen/skills`. It writes the always-on block in
166
+ `~/.qwen/QWEN.md`. tezgah writes no subagent roles, MCP rows or orx shim for
167
+ Qwen yet.
168
+ - **droid** - `install_droid` (`bin/tezgah-setup::install_droid`) writes
169
+ `~/.factory/hooks.json`, keyed by event at the top. It adds one hook group per
170
+ event of `DROID_HOOK_EVENTS`, each `timeout` in seconds, and keeps the user's
171
+ own groups. The PreToolUse group carries `DROID_PRETOOL_MATCHER` over Droid's
172
+ tool ids. Droid reads the `hooks` key of `settings.json` only while hooks.json
173
+ is absent. The install then writes no hooks.json and says why
174
+ (`bin/tezgah-setup::droid_hooks_blocked`). Droid's settings.json holds
175
+ comments, so the install edits it in place and keeps them
176
+ (`bin/tezgah-setup::jsonc_put`). It adds the `statusLine`. It sets
177
+ `includeCoAuthoredByDroid` to false, the switch for Droid's co-author trailer.
178
+ A file it cannot edit safely is left alone and the run names both keys. The install
179
+ links `skills/*` into `~/.factory/skills` beside the user's own links. It
180
+ writes the always-on block in `~/.factory/AGENTS.md`. The uninstall deletes a
181
+ hooks.json that held only tezgah's groups. An empty one would still hide
182
+ settings.json's hooks. tezgah writes no subagent roles, MCP rows or orx
183
+ shim for Droid yet.
184
+ - **commandcode** - `install_commandcode` (`bin/tezgah-setup::install_commandcode`)
185
+ writes into the `hooks` key of `~/.commandcode/settings.json`. It adds one
186
+ hook group per event of `COMMANDCODE_HOOK_EVENTS`, each `timeout` in seconds.
187
+ The user's own groups stay, and the backup is owner-only. The PreToolUse group
188
+ carries `COMMANDCODE_PRETOOL_MATCHER`, which Command Code tests against the
189
+ tool's display name. The same write sets `attribution.commit` to `""`. That
190
+ turns off the default `Co-authored-by: CommandCodeBot` commit trailer. A value
191
+ the user set stays. The uninstall removes the key only while it holds what the
192
+ install wrote. The install links `skills/*` into `~/.commandcode/skills`. It
193
+ writes the always-on block in `~/.commandcode/AGENTS.md`. tezgah writes no
194
+ subagent roles, MCP rows or orx shim for Command Code yet.
195
+ - **copilot** - `install_copilot` (`bin/tezgah-setup::install_copilot`) writes
196
+ `$COPILOT_HOME/hooks/tezgah.json`, a `{"version": 1, "hooks": ...}` file that
197
+ is wholly tezgah's. `COPILOT_HOME` defaults to `~/.copilot`. The file holds
198
+ one entry per event of `COPILOT_HOOK_EVENTS`, each `timeoutSec` in seconds.
199
+ The inline `hooks` key of settings.json is never used, since one bad item
200
+ there rejects every hook in it. The PreToolUse entry carries
201
+ `COPILOT_PRETOOL_MATCHER`. settings.json is JSON with comments. So the status
202
+ line and `includeCoAuthoredBy: false` go in only while it parses as plain
203
+ JSON. That setting turns off the default `Co-authored-by` trailer on the
204
+ agent's commits. A value the user set stays. The install links `skills/*`
205
+ into `$COPILOT_HOME/skills` and writes the always-on block in
206
+ `$COPILOT_HOME/copilot-instructions.md`. The uninstall deletes tezgah.json
207
+ and takes back only tezgah's settings keys. tezgah writes no subagent roles,
208
+ MCP rows or orx shim for Copilot yet.
209
+ - **gemini** - `install_gemini` (`bin/tezgah-setup::install_gemini`) writes
210
+ tezgah's own extension, `~/.gemini/extensions/tezgah/`, which is wholly
211
+ tezgah's. `GEMINI_CLI_HOME` moves `~/.gemini` under it. The dir holds
212
+ `gemini-extension.json`, `hooks/hooks.json` with one group per event of
213
+ `GEMINI_HOOK_EVENTS` (each `timeout` in milliseconds) and `skills/*` links.
214
+ The BeforeTool group carries `GEMINI_PRETOOL_MATCHER`. settings.json is never
215
+ written: it is JSON with comments and holds the user's keys. Gemini's default
216
+ prompt adds no co-author trailer and no setting names one, so there is
217
+ nothing to turn off. The install writes the always-on block in
218
+ `~/.gemini/GEMINI.md`, which Antigravity reads too. The uninstall deletes the
219
+ extension dir. It strips the block only while no other host of
220
+ `GEMINI_MD_HOSTS` stays armed (`bin/tezgah-setup::gemini_md_kept`). Gemini
221
+ gets no subagent roles, MCP rows or orx shim from tezgah yet.
222
+ - **antigravity** - `install_antigravity` (`bin/tezgah-setup::install_antigravity`)
223
+ writes tezgah's hook set under the top-level `tezgah` name of
224
+ `~/.gemini/config/hooks.json`, beside the user's other names. Each event of
225
+ `ANTIGRAVITY_HOOK_EVENTS` gets one item, its `timeout` in seconds. PreToolUse
226
+ and PostToolUse take a matcher group, the others a bare handler. Antigravity
227
+ never loads hooks from `antigravity-cli/settings.json`. The Antigravity App
228
+ and IDE read the same global hooks.json, so the install arms them too. The
229
+ status line goes in `~/.gemini/antigravity-cli/settings.json` only while it
230
+ is plain JSON. The skill links go in `antigravity-cli/skills`. Antigravity
231
+ adds no co-author trailer of its own, and no setting names one. An open
232
+ feature request asks for one (google-antigravity/antigravity-cli#230). The
233
+ always-on block is the `~/.gemini/GEMINI.md` one Gemini CLI shares
234
+ (`bin/tezgah-setup::gemini_md_kept`). The uninstall takes out the `tezgah`
235
+ name, the statusLine and the links. It removes a file that held only
236
+ tezgah's keys. Antigravity gets no subagent roles, MCP rows or orx shim from
237
+ tezgah yet. Antigravity can never show a passing check. Its PostToolUse
238
+ carries no output. A shell call that went to the background also ends with an
239
+ empty `error`. So the hook records each shell call as ran, never a pass
240
+ (`hosts/antigravity/hook.py::post_tool`). The owner chose this: a done or
241
+ tested claim there always stops at the Stop rule's `no verify_ok` class.
242
+ - **goose** - `install_goose` (`bin/tezgah-setup::install_goose`) writes
243
+ tezgah's own plugin, `~/.agents/plugins/tezgah/`, which is wholly tezgah's.
244
+ It holds `plugin.json`, `hooks/hooks.json` and `skills/*` links. The hooks
245
+ file has one group per event of `GOOSE_HOOK_EVENTS`, each `timeout` in
246
+ seconds. Goose loads the skills as `tezgah:<skill>`. Goose tests a matcher
247
+ unanchored, so the PreToolUse group carries the anchored
248
+ `GOOSE_PRETOOL_MATCHER`. No hook sets `on_failure`, so it stays at Goose's
249
+ default `allow`, and a hook fault lets the call through. The always-on block
250
+ goes in `~/.config/goose/.goosehints`. Neither Goose's docs nor its system
251
+ prompt add a co-author trailer or name a setting for one. The uninstall
252
+ deletes the plugin dir and strips the block. The rest of `~/.agents` is the
253
+ user's and stays. The install ignores `GOOSE_PATH_ROOT`, since the docs
254
+ do not say where it puts the hints file. Goose gets no subagent roles, MCP
255
+ rows or orx shim from tezgah yet.
256
+ - **amp** - `install_amp` (`bin/tezgah-setup::install_amp`) writes one file,
257
+ `tezgah.ts`, into Amp's system plugin dir (`hooks/tezgah_paths.py::AMP_PLUGIN`,
258
+ under `XDG_CONFIG_HOME` when that is set). `hooks/tezgah_attest.py::amp_plugin`
259
+ renders it from `hosts/amp/tezgah-plugin.ts.in`. Amp loads every `.ts` there,
260
+ and nothing registers it. A running Amp needs `plugins: reload`. The plugin
261
+ spawns `hosts/amp/hook.py` without blocking, one JSON in and one out. One
262
+ plugin process serves many threads, so it keys all its state by thread id.
263
+ The install links the skills into `~/.config/amp/skills` and writes the
264
+ always-on block into `~/.config/amp/AGENTS.md`. By default Amp adds a
265
+ `Co-authored-by: Amp` and an `Amp-Thread-ID` trailer to its commits. The
266
+ install sets `amp.git.commit.coauthor.enabled` and
267
+ `amp.git.commit.ampThread.enabled` to false in `~/.config/amp/settings.json`
268
+ unless the user set them. It writes that file only while it is plain JSON
269
+ with no `settings.jsonc` beside it (`bin/tezgah-setup::amp_settings`). Else it
270
+ prints both keys to set by hand. The uninstall removes the plugin only when
271
+ it is a fresh render or starts with tezgah's header line
272
+ (`bin/tezgah-setup::amp_plugin_ours`). It drops a trailer key only while that
273
+ key is still false. Amp gets no subagent roles, MCP rows or orx shim from
274
+ tezgah.
129
275
 
130
276
  An adapter is responsible for its host's **envelope** and its **surface**: it
131
277
  translates the host's event and tool names into the shared vocabulary (one call
@@ -149,6 +295,14 @@ pays for it once.
149
295
  | opencode | no stdout-inject hook: `install_opencode` writes the generated `~/.config/tezgah/opencode-contract.md` and references it from `opencode.json` `instructions` |
150
296
  | omp | the managed block in `~/.omp/agent/RULES.md` |
151
297
  | dsh | the session's own hook context: `hosts/dsh/hooks.json` runs the Claude-family scripts through the bridge, and no dsh file carries the core, so dsh's `SessionStart` row declares nothing and still gets it here (`~/.dsh/cordis.patch.yml` mounts that bridge) |
298
+ | qwen | the managed block in `~/.qwen/QWEN.md`, which Qwen loads into every session; its `SessionStart` hook repeats the core only while that block is missing (`hosts/qwen/hook.py::rules_carry_core`) |
299
+ | droid | the managed block in `~/.factory/AGENTS.md`, which Droid loads as coding guidelines at startup; its `SessionStart` hook repeats the core only while that block is missing (`hosts/droid/hook.py::rules_carry_core`) |
300
+ | commandcode | the managed block in `~/.commandcode/AGENTS.md`, the user memory file Command Code puts in the system prompt; its `SessionStart` hook repeats the core only while that block is missing (`hosts/commandcode/hook.py::rules_carry_core`) |
301
+ | copilot | the managed block in `$COPILOT_HOME/copilot-instructions.md`, Copilot's personal instructions file, applied to every session; its `SessionStart` hook repeats the core only while that block is missing (`hosts/copilot/hook.py::rules_carry_core`) |
302
+ | gemini | the managed block in `~/.gemini/GEMINI.md`, the global context file Gemini puts in the system prompt and Antigravity reads too; its `SessionStart` hook repeats the core only while that block is missing (`hosts/gemini/hook.py::rules_carry_core`) |
303
+ | antigravity | the same managed block in `~/.gemini/GEMINI.md`, a standalone global rule Antigravity keeps always on (24,000-byte file limit); its `SessionStart` hook injects the core as a system message only while that block is missing (`hosts/antigravity/hook.py::rules_carry_core`) |
304
+ | goose | the managed block in `~/.config/goose/.goosehints`, the global hints file Goose adds to the system prompt of every request; no hook repeats it, because no Goose hook output reaches the model outside a block |
305
+ | amp | the managed block in `~/.config/amp/AGENTS.md`, which Amp always includes; the session brief on the thread's first turn repeats it only while that block is missing or `AMP_IGNORE_GUIDANCE_FILES` is set (`hosts/amp/hook.py::rules_carry_core`) |
152
306
 
153
307
  An installed host whose core line is missing is a health check's business
154
308
  (`host_checks_<host>`), not a second copy: two delivery paths for one host is the
@@ -167,11 +321,27 @@ and drops the later ones.
167
321
  | context | cursor | the `sessionStart` hook context, the repo's `AGENTS.md`, `.cursor/rules/*.mdc`, `.cursorrules` | the hook |
168
322
  | context | opencode | `~/.config/opencode/AGENTS.md`, `opencode.json` `instructions`, the repo's `AGENTS.md` | the contract and router files in `instructions` |
169
323
  | context | omp | `SYSTEM.md`, `APPEND_SYSTEM.md`, `RULES.md` (sticky), the repo's `.omp/AGENTS.md`, `AGENTS.md`, `CLAUDE.md`; another host's user files only with `enabledProviders` | block in `RULES.md`, the extension at session start |
324
+ | context | qwen | `~/.qwen/QWEN.md`, the repo's `QWEN.md` and `AGENTS.md`, then `.qwen/QWEN.local.md`; all load | block in the first |
325
+ | context | droid | from the cwd up to the git root, `AGENTS.md` (or `CLAUDE.md`) in each dir and its `.factory/`, `.agents/`, `.agent/`; then `~/.factory/AGENTS.md` and the other personal dirs; project files override personal ones | block in `~/.factory/AGENTS.md` |
326
+ | context | commandcode | `~/.commandcode/AGENTS.md`, the repo's `AGENTS.md` (else `.commandcode/AGENTS.md`), then each subdirectory's; all load | block in the first |
327
+ | context | copilot | `$COPILOT_HOME/copilot-instructions.md` and `instructions/`, then in the git root and the cwd `.github/copilot-instructions.md`, `.github/instructions/`, `AGENTS.md`, `CLAUDE.md`, `GEMINI.md` and `.claude/rules/`; all merge | block in the first |
328
+ | context | gemini | `~/.gemini/GEMINI.md`, then from the cwd up to the git root, then subdirectories (`context.fileName` renames the file); all concatenate | block in the first |
329
+ | context | antigravity | `~/.gemini/AGENTS.md` and `GEMINI.md`, `~/.gemini/config/rules/*.md`, `~/.gemini/antigravity-cli/rules/*.md`, then the workspace's `AGENTS.md`, `GEMINI.md` and `.agents/rules/`, and plugin rules; all combine, more specific rules win a conflict, and over 20,000 tokens the largest files become pointers | block in `~/.gemini/GEMINI.md` |
330
+ | context | goose | `CONTEXT_FILE_NAMES` (default `AGENTS.md`, `.goosehints`) in `~/.config/goose/`, then in the project root and each directory goose reads; all load, and a local hint wins over a global one | block in `~/.config/goose/.goosehints` |
331
+ | context | amp | `AGENTS.md` in the cwd and its parents, subtree files on a read, then `~/.config/amp/AGENTS.md` and `~/.config/AGENTS.md`; all load, and `AMP_IGNORE_GUIDANCE_FILES` skips matching files | block in `~/.config/amp/AGENTS.md` |
170
332
  | skills | omp | `skills.customDirectories`, then `.omp/skills` and `~/.omp/agent/skills`, Claude's project dir (user dir opt-in), `.agents/skills` and `~/.agents/skills`; first wins, identical copies collapse, a differing copy is renamed `<namespace>/<name>` | links in `~/.omp/agent/skills` |
171
333
  | skills | claude | `.claude/skills`, `~/.claude/skills`; plugin skills are namespaced `tezgah:<name>` | the plugin copy |
172
334
  | skills | codex | `.agents/skills` from the cwd up to the repo root, `~/.agents/skills`, `$CODEX_HOME/skills`; one name in two dirs is listed twice, not merged | links in `$CODEX_HOME/skills` |
173
335
  | skills | cursor | `.agents/skills`, `.cursor/skills`, `~/.agents/skills`, `~/.cursor/skills`, then Claude's and Codex's project and user dirs; no documented winner | links in `~/.cursor/skills` |
174
336
  | skills | opencode | its own skill tool is denied; the router lists `~/.config/opencode/skills`, `~/.claude/skills`, `~/.agents/skills`, first wins (`bin/tezgah-setup::skill_groups`) | links and the router |
337
+ | skills | qwen | `~/.qwen/skills`, `.qwen/skills`, extension and bundled skills; no documented winner | links in `~/.qwen/skills` |
338
+ | skills | droid | `.factory/skills` (project and folder), `~/.factory/skills`, `.agents/skills` and `~/.agents/skills`, plugin and built-in skills; no documented winner | links in `~/.factory/skills`, beside the user's own |
339
+ | skills | commandcode | `.commandcode/skills`, `.agents/skills`, `~/.commandcode/skills`, `~/.agents/skills`, extra locations, bundled; first wins | links in `~/.commandcode/skills` |
340
+ | skills | copilot | `.github/skills`, `.agents/skills`, `.claude/skills`, a parent's `.github/skills`, `$COPILOT_HOME/skills`, `~/.agents/skills`, plugins, `COPILOT_SKILLS_DIRS`, bundled; first wins | links in `$COPILOT_HOME/skills` |
341
+ | skills | gemini | workspace `.agents/skills` and `.gemini/skills`, user `~/.agents/skills` and `~/.gemini/skills`, extension skills, built-in; higher tier wins, `.agents/skills` wins within one | links in the extension's `skills/`, the lowest tier |
342
+ | skills | antigravity | the workspace's `.agents/skills`, `~/.gemini/antigravity-cli/skills`, plugin skills; no documented winner | links in `~/.gemini/antigravity-cli/skills` |
343
+ | skills | goose | `~/.agents/skills`, `.agents/skills`, plugin skills (namespaced `<plugin>:<name>`), then `.goose/skills`, `.claude/skills` and `~/.claude/skills` for backward compatibility; no documented winner | links in the plugin's `skills/`, loaded as `tezgah:<name>` |
344
+ | skills | amp | `~/.config/agents/skills`, `~/.agents/skills`, `~/.config/amp/skills`, `.agents/skills`, `.claude/skills`, `~/.claude/skills`, then plugin and configured paths; first name wins | links in `~/.config/amp/skills` |
175
345
  | agents | claude | managed, the `claude --agents` flag, `.claude/agents`, `~/.claude/agents`, plugin agents namespaced `plugin:name`; a dir absent at start is not watched | `~/.claude/agents`, written at install |
176
346
  | agents | codex | `$CODEX_HOME/agents`, `/etc/codex/agents`, `.codex/agents` (trusted repos only), read at config load | `$CODEX_HOME/agents`, written at install |
177
347
  | agents | cursor | `cursor-agent`: `.cursor/agents`, then `.claude/agents`, `.grok/agents`, plugin agents; first name wins; no user dir; read at process start. The editor's docs add `~/.cursor/agents` and `~/.claude/agents` | `.cursor/agents` per repo at session start, in the CLI's line format (`hooks/tezgah_agents.py::render_md_cursor`) |
@@ -181,7 +351,16 @@ and drops the later ones.
181
351
  | hooks | codex, cursor | `hooks.json` array order, then the repo's `.codex/` or `.cursor/hooks.json`; Codex runs a row only once its hash is trusted | own rows after Orca's |
182
352
  | hooks | opencode | every `*.js`/`*.ts` in `plugins/` and `plugin/`; `tezgah.js` is linked into both, and its `globalThis.__tezgahPluginLoaded` guard makes the second load return `{}` | the plugin |
183
353
  | hooks | omp | `~/.omp/agent/extensions/` first, then `config.yml` `extensions:`, one load per absolute path; per event in that order, and the first `block` ends a tool call | the bridge in `hooks/pre/` |
184
- | mcp | each | omp: project `.omp/mcp.json`, then `~/.omp/agent/mcp.json`, first name wins; claude: `~/.claude.json`, the repo's `.mcp.json`, the plugin's `.mcp.json`; codex: `$CODEX_HOME/config.toml`; cursor: `~/.cursor/mcp.json`; opencode: `opencode.json` `mcp` | one `tezgah` and one `codegraph` row per host |
354
+ | hooks | qwen | user `settings.json` rows (Orca's and tezgah's), then project `.qwen/settings.json` rows in trusted folders; groups run in parallel unless `sequential` | own rows after Orca's |
355
+ | hooks | droid | org-managed hooks, user `~/.factory/hooks.json`, project `.factory/hooks.json` and enabled plugins' `hooks/hooks.json`, merged with no documented order; a scope's settings.json `hooks` key counts only while its hooks.json is absent | own rows in hooks.json |
356
+ | hooks | commandcode | project `.commandcode/settings.json` rows, then user `~/.commandcode/settings.json` rows; PreToolUse runs them in order and stops at the first deny, the other events run in parallel | own rows in the user settings.json |
357
+ | hooks | copilot | policy files, then the repo's `.github/hooks/*.json`, user `$COPILOT_HOME/hooks/*.json`, the inline `hooks` of the repo's and the user's settings.json (and the repo's `.claude/settings.json`), then plugins; every entry runs, and any PreToolUse deny blocks | own file `hooks/tezgah.json` |
358
+ | hooks | gemini | project `.gemini/settings.json`, user `~/.gemini/settings.json`, system settings, then extensions' `hooks/hooks.json`; `hooksConfig.enabled: false` runs none | own extension `extensions/tezgah/hooks/hooks.json` |
359
+ | hooks | antigravity | workspace `.agents/hooks.json`, global `~/.gemini/config/hooks.json`, then installed plugins' `hooks.json`; each top-level name is a hook set, and `enabled: false` on a name loads none of it | the `tezgah` name in the global hooks.json |
360
+ | hooks | goose | user `~/.agents/plugins/<name>/hooks/hooks.json`, project `.agents/plugins/<name>/hooks/hooks.json` and installed plugins; a name in `disabledPlugins` of `~/.config/goose/settings.json` loads none of it; any PreToolUse block denies | own plugin `~/.agents/plugins/tezgah/hooks/hooks.json` |
361
+ | hooks | amp | plugins: personal and workspace ones from Amp's settings, the project's `.amp/plugins/`, the system `$XDG_CONFIG_HOME/amp/plugins/`; a project plugin wins over a system one of the same name, and every plugin gets every event in no defined order | the system plugin `tezgah.ts` |
362
+ | mcp | each | omp: project `.omp/mcp.json`, then `~/.omp/agent/mcp.json`, first name wins; claude: `~/.claude.json`, the repo's `.mcp.json`, the plugin's `.mcp.json`; codex: `$CODEX_HOME/config.toml`; cursor: `~/.cursor/mcp.json`; opencode: `opencode.json` `mcp`; qwen: `settings.json` `mcpServers`; droid: `~/.factory/mcp.json`; commandcode: `settings.json` `mcp.servers`, `~/.commandcode/mcp.json`, the repo's `.mcp.json`; copilot: `$COPILOT_HOME/mcp-config.json`, the repo's `.mcp.json` and `.github/mcp.json`, project first; gemini: `settings.json` `mcpServers` (user, project) and each extension's `gemini-extension.json`, settings.json first; antigravity: `~/.gemini/config/mcp_config.json`, the workspace's `.agents/mcp_config.json` and plugins, the running server keeps a shared name; goose: the `extensions:` map of `~/.config/goose/config.yaml`; tezgah writes no row for qwen, droid, commandcode, copilot, gemini, antigravity or goose yet | one `tezgah` and one `codegraph` row per host but qwen, droid, commandcode, copilot, gemini, antigravity and goose |
363
+ | mcp | amp | `amp.mcpServers` in `~/.config/amp/settings.json`, then the workspace's `.amp/settings.json`, which wins | none: tezgah writes no MCP row for Amp |
185
364
 
186
365
  Orca is a launcher, not a host. It starts omp with its own `--extension`. It
187
366
  gives each Codex account its own `CODEX_HOME` under
@@ -224,6 +403,14 @@ three harness configurations while swapping the model inside one harness moved i
224
403
  | opencode | a selector read from `opencode models` (`hooks/tezgah_models.py:12`) | the AI SDK: `@ai-sdk/openai-compatible` for `/v1/chat/completions`, `@ai-sdk/openai` for `/v1/responses`, beside its own Anthropic and Bedrock providers (opencode's provider docs) | vendor documentation |
225
404
  | omp | the session default's family, otherwise `task.agentModelOverrides` and `modelRoles.plan` (`hooks/tezgah_models.py:13-16`) | whichever provider the selected model names - Anthropic Messages, OpenAI Responses, OpenAI chat completions, Google, Bedrock - with text-based dialects where a native tool API is unavailable; the OpenRouter route dispatches to Responses unless `PI_OPENROUTER_RESPONSES=0` (omp's own `provider-compat-reference` documentation) | the tool's own documentation |
226
405
  | dsh | the Claude-family scripts through the bridge (`hosts/dsh/hooks.json:2-18`); `README.md:27-28` calls it "DeepSeek's dsh harness" | `unverified`: its own `--help` describes it as "an ordered stack of plugin-bundle patch layers" and names no model API, and its profile directory names a provider (DeepSeek) rather than a wire (checked 2026-10-01) | - |
406
+ | qwen | not named: tezgah writes no Qwen subagent roles | the provider `settings.json` `modelProviders` selects, keyed by auth type - `openai` (OpenAI-compatible), `anthropic`, `gemini`, `vertex-ai` - each with its own `baseUrl` (Qwen Code 0.21.13 `configuration/model-providers.md`) | the tool's own documentation |
407
+ | droid | not named: tezgah writes no Droid subagent roles | `unverified`: tezgah has not mapped Droid's providers to a wire | - |
408
+ | commandcode | not named: tezgah writes no Command Code subagent roles | `unverified`: tezgah has not mapped Command Code's providers to a wire | - |
409
+ | copilot | not named: tezgah writes no Copilot subagent roles | `unverified`: tezgah has not mapped Copilot's providers to a wire | - |
410
+ | gemini | not named: tezgah writes no Gemini subagent roles | `unverified`: tezgah has not mapped Gemini's providers to a wire | - |
411
+ | antigravity | not named: tezgah writes no Antigravity subagent roles | `unverified`: tezgah has not mapped Antigravity's models to a wire | - |
412
+ | goose | not named: tezgah writes no Goose subagent roles | `unverified`: tezgah has not mapped Goose's providers to a wire | - |
413
+ | amp | not named: tezgah writes no Amp subagent roles | `unverified`: tezgah has not mapped Amp's models to a wire | - |
227
414
 
228
415
  `unverified` is the point of the column rather than a gap in it. A wrong dialect
229
416
  here is worse than a blank one: a reader would use it to explain a score, and the
@@ -236,12 +423,13 @@ A skill read is observable only where the host gives a channel that does not cos
236
423
  a process per read: Claude parses its transcript (`statusline.py::claude_used`),
237
424
  opencode classifies in process (`hosts/opencode/plugins/tezgah.js:2227-2263`),
238
425
  omp filters in its embedded runner before it asks python
239
- (`hosts/omp/tezgah-hook.ts.in:257-264`). On codex, cursor and dsh a read is not
240
- observable at that price, so their surfaces pass the five tool-use measures as
241
- `observable` and the two skill marks render dim (`info`, no glyph) instead of
426
+ (`hosts/omp/tezgah-hook.ts.in:257-264`), and Amp's plugin does the same for a
427
+ `Read` of a shipped skill (`hosts/amp/tezgah-plugin.ts.in`). On codex, cursor, qwen, droid, commandcode, copilot, gemini, antigravity, goose and dsh a read is not
428
+ observable at that price. Their surfaces pass the five tool-use measures as
429
+ `observable`, and the two skill marks render dim (`info`, no glyph) instead of
242
430
  claiming the skill was never opened (`hooks/tezgah_context.py::TOOL_USE_MEASURES`, and the
243
431
  `observable` branch at `hooks/tezgah_context.py::health_segments`). Callers that pass `TOOL_USE_MEASURES`:
244
- `statusline.py::observable`, `hosts/codex/hook.py::main`,
432
+ `statusline.py::observable`, `hosts/codex/hook.py::main`, `hosts/commandcode/hook.py::status`,
245
433
  `hosts/dsh/statusline/lib/index.js:16-19`. A kill switch is observable everywhere
246
434
  and still renders `off` (`hooks/tezgah_context.py::health_segments`).
247
435
 
@@ -260,11 +448,24 @@ In order, each step verified by the one below it:
260
448
  (`hosts/omp/tezgah-hook.ts.in` rendered at `hooks/tezgah_attest.py::omp_bridge`).
261
449
  3. `install_<host>()` writing that host's files, registered in `INSTALLERS`
262
450
  (`bin/tezgah-setup::INSTALLERS`), and `uninstall_<host>()` removing only
263
- tezgah-managed links and blocks (`bin/tezgah-setup:3331-3463`).
451
+ tezgah-managed links and blocks, registered in `UNINSTALLERS` (`bin/tezgah-setup::UNINSTALLERS`).
264
452
  4. `host_checks_<host>()` returning `(label, bool)` rows over what was actually
265
453
  written, registered in `HOST_CHECKS` (`bin/tezgah-setup::HOST_CHECKS`); the
266
454
  `--report` output is that list (`bin/tezgah-setup::report`). Give the row a home-qualified
267
455
  label if the host's dir can be relocated (`host_checks_codex`, `bin/tezgah-setup::host_checks_codex`).
456
+ The first row is the host's "wired" row (`bin/tezgah-setup::hosts_row`), and a
457
+ row whose MISS means unarmed names a label `ARMING_ROWS` carries
458
+ (`bin/tezgah-setup::ARMING_ROWS`).
459
+ Every other table keyed by host grows with it, or the host crashes or drops
460
+ out there. `hooks/tezgah_stack.py::host_stack` raises on an unknown host, so
461
+ `tezgah-doctor --stack` fails. The other tables are
462
+ `hooks/tezgah_stack.py::tezgah_skill_dirs`, `hooks/tezgah_attest.py::registration`
463
+ and `CODE_TARGETS`, `bin/tezgah-setup::hook_entries` and
464
+ `bin/tezgah-setup::live_plan`. Without `live_plan` the live rows read
465
+ UNVERIFIED. The gate's control plane grows too: `HOOK_ENTRIES`, `HOOK_HOSTS`
466
+ and the shared files of `hooks/tezgah_gate.py::_registration`. The gate then
467
+ refuses a forged run of the new hook, and an edit of its registration, as it
468
+ refuses the others.
268
469
  5. Decide the surface: a `statusLine` command, a TUI/widget plugin, or the
269
470
  `systemMessage` fallback (`hosts/codex/hook.py:10-12`). Pass `observable` if
270
471
  the host cannot see a skill read, and `idx_override` on any redraw that must
@@ -274,7 +475,9 @@ In order, each step verified by the one below it:
274
475
  `CodexHome:657-702`, `CursorMatcher:705-722`, `DshStatusline:872-957`), and an
275
476
  opt-in script under `tests/e2e_*.py` for the thing only a real host can prove
276
477
  - not collected by `unittest discover`, it exits 0 with `SKIP` when the host
277
- is absent (`tests/e2e_omp_statusline.py:13-16`).
478
+ is absent (`tests/e2e_omp_statusline.py:13-16`). A script that calls a model
479
+ also exits 0 with `SKIP` unless `TEZGAH_E2E_SPEND=1` is set
480
+ (`tests/e2e_qwen.py`, `tests/e2e_droid.py`).
278
481
 
279
482
  ## Host-specific limits worth knowing
280
483
 
@@ -287,6 +490,26 @@ In order, each step verified by the one below it:
287
490
  `gate✗` appears only in the SessionStart/Stop status message.
288
491
  - **cursor, dsh:** observed only if their prompt payload carries `transcript_path`
289
492
  (unverified on a live host).
493
+ - **qwen:** unverified. Its prompt payload carries `transcript_path`. The counter
494
+ reads only the Claude, omp and Codex row shapes (`hooks/tezgah_context.py::_transcript_calls`).
495
+ No run has captured Qwen's transcript format yet, so a disarmed gate there likely counts 0.
496
+ - **droid:** unverified on a live host. Its transcript rows carry `message.content`
497
+ `tool_use` blocks with a `timestamp`, the Claude shape the counter reads, and
498
+ `GATED_TOOLS` names `Execute`, `Create` and `ApplyPatch`
499
+ (`hooks/tezgah_context.py::GATED_TOOLS`).
500
+ - **commandcode:** unavailable: Command Code has no prompt event, so nothing runs the
501
+ counter there.
502
+ - **copilot:** unavailable: Copilot drops a command hook's UserPromptSubmit output,
503
+ so no reminder carries the counter's warning there.
504
+ - **gemini:** unverified. BeforeAgent carries `transcript_path`, but Gemini's
505
+ session file is a format the counter does not read
506
+ (`hooks/tezgah_context.py::_transcript_calls`), so a disarmed gate there likely counts 0.
507
+ - **antigravity:** unavailable: its `transcript.jsonl` is not a format the counter
508
+ reads, so the hook does not hand it over. Only a switch armed mid-session shows.
509
+ - **goose:** unavailable: Goose ignores UserPromptSubmit output, so no reminder
510
+ carries the counter's warning there.
511
+ - **amp:** unavailable: the plugin hands the hook no transcript path, so only a
512
+ switch armed mid-session shows.
290
513
  - **omp, opencode:** not applicable: the prompt hook and the gate live in one extension or
291
514
  plugin process, so one cannot run without the other.
292
515
  - Everywhere: a disarmed PreToolUse beside a working PostToolUse writes rows and is not
@@ -414,7 +637,7 @@ In order, each step verified by the one below it:
414
637
  longer. A busy turn with every conditional rule armed measured 10288.
415
638
  `fit()` cuts the tail to `CONTEXT_MAX` and marks the cut, so the reminder and
416
639
  the first rules arrive (`hosts/cursor/hook.py::fit`, `hosts/cursor/hook.py::CONTEXT_MAX`).
417
- - **Claude, dsh, omp, codex and cursor run the gate only for the tool names
640
+ - **Claude, dsh, omp, codex, cursor, qwen, droid, commandcode, copilot, gemini, antigravity, goose and amp run the gate only for the tool names
418
641
  their matcher carries**, so a name the PostToolUse side carries and the
419
642
  PreToolUse side does not is recorded in the ledger and never refused.
420
643
  `PowerShell` and `pwsh` are both `BASH_TOOLS` members
@@ -425,14 +648,65 @@ In order, each step verified by the one below it:
425
648
  `hosts/cursor/hooks.json:8` - neither named them until 2026-09-19, so a
426
649
  PowerShell call on those two reached no hook at all while the shared rules
427
650
  already knew the spelling), and omp's `GATED` list
428
- (`hosts/omp/tezgah-hook.ts.in:270-273`). A host this list does not cover is a
429
- host whose shell the gate cannot see.
651
+ (`hosts/omp/tezgah-hook.ts.in:270-273`). Qwen Code has two shell tool ids,
652
+ `run_shell_command` and `monitor`, which `bin/tezgah-setup::QWEN_PRETOOL_MATCHER`
653
+ names beside `notebook_edit` and `mcp__.*`. Claude's and dsh's `Monitor` runs a
654
+ shell command too, so their matcher names it. Droid's shell is `Execute`, which `bin/tezgah-setup::DROID_PRETOOL_MATCHER`
655
+ names beside `Create`, `Edit`, `ApplyPatch`, `Task`, `Grep` and `mcp__.*`. The
656
+ adapter hands ApplyPatch's `input` to the gate as `patch`
657
+ (`hosts/droid/hook.py::gate_input`). Command Code's settings hooks see only
658
+ four tools and test the matcher against their display names (`SHELL`, `READ`,
659
+ `WRITE`, `EDIT`), so `bin/tezgah-setup::COMMANDCODE_PRETOOL_MATCHER` is
660
+ `shell|write|edit`. Its read, MCP, subagent and web tools never reach the
661
+ gate, and plan mode skips every hook. The adapter folds a shell call's `args`
662
+ into `command` and hands an edit's `old_value`/`new_value` over as
663
+ `old_string`/`new_string` (`hosts/commandcode/hook.py::gate_input`). Copilot's
664
+ PascalCase PreToolUse reports Claude tool names and anchors its matcher. So
665
+ `bin/tezgah-setup::COPILOT_PRETOOL_MATCHER` names `Bash`, `Write`, `Edit`,
666
+ `Agent`, `Task` and `Grep`, plus `.+-.+` for an MCP tool's `<server>-<tool>`.
667
+ The adapter maps the runtime spellings too (`bash`, `powershell`, `create`,
668
+ `edit`, `apply_patch`, `task`, `grep`, `rg`). An Edit that names no file but
669
+ carries a patch reaches the gate as `apply_patch`. `<server>-<tool>` becomes
670
+ `mcp__<server>__<tool>` for a server `mcp-config.json` names
671
+ (`hosts/copilot/hook.py::gate_name`). Gemini's BeforeTool matcher is a regex
672
+ over the tool name. `bin/tezgah-setup::GEMINI_PRETOOL_MATCHER` names the
673
+ shell, write, edit and grep tools, the subagent tools
674
+ (`codebase_investigator`, `generalist`, `invoke_agent`) and `mcp_.*`. A user's own subagents are not
675
+ named. `mcp_<server>_<tool>` marks no boundary between server and tool, so it
676
+ reaches the gate as `mcp__<server>_<tool>` (`hosts/gemini/hook.py::gate_name`).
677
+ Antigravity's matcher is a regex over the tool name, and
678
+ `bin/tezgah-setup::ANTIGRAVITY_PRETOOL_MATCHER` names `run_command`,
679
+ `write_to_file`, `replace_file_content`, `multi_replace_file_content`,
680
+ `invoke_subagent` and `grep_search`. The adapter renames their PascalCase
681
+ arguments into the gate's fields (`hosts/antigravity/hook.py::gate_input`).
682
+ `CommandLine` becomes `command`, `TargetFile` and `CodeContent` become
683
+ `file_path` and `content`. `TargetContent` and `ReplacementContent` become
684
+ `old_string` and `new_string`, and `ReplacementChunks` becomes `edits`. The
685
+ docs name no MCP tool spelling, so the matcher names no MCP tool. It names
686
+ neither `manage_task`'s `send_input` nor `schedule`.
687
+ Goose tests a matcher unanchored, so `bin/tezgah-setup::GOOSE_PRETOOL_MATCHER`
688
+ anchors `shell`, `write`, `edit` and `.+__.+`, an extension's
689
+ `<extension>__<tool>`, which reaches the gate as `mcp__<extension>__<tool>`
690
+ (`hosts/goose/hook.py::gate_name`). The adapter hands `write`'s and `edit`'s
691
+ `path` over as `file_path`, and `edit`'s `before`/`after` as
692
+ `old_string`/`new_string` (`hosts/goose/hook.py::gate_input`). Goose's own
693
+ `developer`, `analyze`, `summon` and `code_execution` tools other than these
694
+ three, and its subagent tool, are not named.
695
+ Amp's plugin sends the gate `Bash`, `shell_command`, `edit_file`,
696
+ `create_file`, `apply_patch` and `Task` (its `GATED` list) and an MCP effect
697
+ name. A shell call's text reaches the gate as `command`, from Amp's
698
+ `shellCommandFromToolCall` helper or the input's `cmd`
699
+ (`hosts/amp/hook.py::gate_input`). Read, search, web and `code_exec` calls
700
+ are not gated, and an MCP tool reached through `code_exec` or `tool_search`
701
+ is never seen.
702
+ A host this list does not cover is a host whose shell the gate cannot see.
430
703
  - **An MCP effect reaches the gate on every host that can route it**. Claude
431
704
  and dsh run it through a PreToolUse group of its own in `hooks/hooks.json` and
432
705
  `hosts/dsh/hooks.json`. That group stays apart from the literal one, because
433
706
  one regex character would end its exact-name dialect. Its pattern names the
434
707
  effect verbs of `hooks/tezgah_integrity.py::MCP_VERBS`, and omp's `tool_call`
435
- tests the same pattern (`MCP_EFFECT`, `hosts/omp/tezgah-hook.ts.in`). Both
708
+ tests the same pattern (`MCP_EFFECT`, `hosts/omp/tezgah-hook.ts.in`), as does
709
+ Amp's `tool.call` (`hosts/amp/tezgah-plugin.ts.in`). Both
436
710
  hosts read a matcher as an unanchored JavaScript `RegExp`, and the pattern
437
711
  uses no lookahead. A read-only MCP call, such as a code-graph query, spawns no
438
712
  hook there. Codex's matcher carries `mcp__.*` (`hosts/codex/hooks.json`).
@@ -465,6 +739,14 @@ In order, each step verified by the one below it:
465
739
  - `hosts/dsh/hooks.json`, `hosts/dsh/statusline/lib/index.js`
466
740
  - `hosts/omp/hook.py`, `hosts/omp/tezgah-hook.ts.in`
467
741
  - `hosts/opencode/plugins/tezgah.js`, `hosts/opencode/tui/tezgah-tui.tsx`
742
+ - `hosts/qwen/hook.py`, `tests/e2e_qwen.py` (the opt-in run of the real binary)
743
+ - `hosts/droid/hook.py`, `tests/e2e_droid.py` (the opt-in run of the real binary)
744
+ - `hosts/commandcode/hook.py`
745
+ - `hosts/copilot/hook.py`
746
+ - `hosts/gemini/hook.py`
747
+ - `hosts/antigravity/hook.py`
748
+ - `hosts/goose/hook.py`
749
+ - `hosts/amp/hook.py`, `hosts/amp/tezgah-plugin.ts.in`
468
750
  - `statusline.py`, `bin/tezgah-status`, `hooks/tezgah_context.py`
469
751
  - `hooks/tezgah_models.py` - the per-host model surface in `## The model wire`
470
752
  - `hooks/tezgah_stack.py`, `bin/tezgah-doctor` `--stack` - the order in `## Precedence`
package/docs/layers.md CHANGED
@@ -56,7 +56,7 @@ Must not own the user's rules, the evidence of the turn or the refusal. A
56
56
  runtime runs what the model asks; nothing inside it stops the model, which is
57
57
  the whole reason tezgah exists ([architecture](architecture.md)).
58
58
 
59
- Here the six hosts are the runtimes, one config dir per host
59
+ Here the fourteen hosts (claude, codex, cursor, opencode, dsh, omp, qwen, droid, commandcode, copilot, gemini, antigravity, goose, amp) are the runtimes, one config dir per host
60
60
  (`hooks/tezgah_paths.py::HOST_DIRS`) with one presence test (`host_installed`
61
61
  `hooks/tezgah_paths.py::host_installed`), and the adapter under `hosts/<name>/` holds the
62
62
  host's envelope and nothing else ([hosts](hosts.md)). The README's own intro
package/docs/models.md CHANGED
@@ -40,6 +40,12 @@ field or judgement, and only a second miss restarts the task on `tezgah-frontier
40
40
  with the original brief rather than handing the failed trajectory up: continuing a
41
41
  cheap trajectory on a frontier model was the most expensive option measured
42
42
  (arXiv 2608.24358, Table 1: 1.61 cost units against 0.72 for frontier-only).
43
+ A question only the owner can answer climbs past every tier, frontier included.
44
+ It is choosing a variant or option a research, analysis or review recommends, a
45
+ way around a tezgah refusal, or deciding about another session's work. The
46
+ worker answers `ESCALATE: owner` with the options and a recommended default
47
+ (`hooks/tezgah_agents.py::OWNER_ESCALATE`). The orchestrator puts it to the
48
+ user in the approval batch and does not re-brief it.
43
49
 
44
50
  ## The table (snapshot 2026-09-30)
45
51
 
@@ -19,8 +19,8 @@ before an install is considered.
19
19
  | `--install` | Arm the `--hosts` (default: every detected host): writes `~/.config/tezgah/config.json`, the contract hash, the `~/.config/tezgah/bin` symlinks and every host file, then prints the report. Installs the missing optional tools first unless `--no-deps`; third-party host CLIs (cursor-agent, pnpm, dsh) only with `--host-deps`, otherwise their exact install command is printed. Exits 1 when a requested tool is still missing, naming the cause (`installer failed with exit N`, or `exited 0 but not on PATH`) (`bin/tezgah-setup::main`, `bin/tezgah-setup::install_all`, `bin/tezgah-setup::install_deps`). It also exits 1 when a planned host is left unarmed, and names it under `not armed:`. Only the arming rows count: hooks wired, the contract current, and the row a host's own CLI arms where that CLI exists: omp's extension row with an omp CLI, Claude's `plugin copy current` with a `claude` CLI. Codex `hooks trusted` and the provider-key rows stay out, because a correct fresh install leaves them MISS (`ARMING_ROWS`, `arming_miss()`). A host config that does not parse as a JSON object is refused: the run names the file and the parse error, never writes it, and exits 1 (`read_config()`); tezgah's own `config.json` is refused the same way. Claude is registered through its own CLI (`register_claude_plugin()`): the run renders `.claude-plugin/{plugin,marketplace}.json` from the release version into the tree it runs from (untracked; a pair under another marketplace name is kept), runs `claude plugin marketplace add <tree>` (a directory source pinned to that tree) and `claude plugin install tezgah@tezgah-local`, and skips both when any `tezgah@*` row is already in `installed_plugins.json`, because a second row fires every hook twice. `--reply-lang tr|en|any` stores `reply_lang` in `config.json`; without it the stored value stays, and a config with none reads as `tr`. |
20
20
  | `--wizard` | Ask the install questions and write only after the final yes; the other flags supply the defaults, so `--wizard --hosts omp` asks only the rest (`bin/tezgah-setup::wizard`). |
21
21
  | `--report` | Print what is armed, per host, and stop (`bin/tezgah-setup::main`, `bin/tezgah-setup::report`). |
22
- | `--report --live` | Proves the hooks run, not only that they are written (audit Phase 1.2). For each host, one synthetic PostToolUse (`true`) goes through that host's own wiring: the command in `~/.codex/hooks.json`, `~/.cursor/hooks.json`, the enabled Claude plugin copy's `hooks/hooks.json` (with `CLAUDE_PLUGIN_ROOT`), the hooks.json named by dsh's `cordis.patch.yml`; the opencode plugin is imported with node the way opencode imports it; the omp extension `.ts` is loaded with node `--experimental-strip-types` after checking it is in omp's `extensions` list. A ledger row for a dedicated `tezgah-live-<pid>-<ms>` session must appear; the event runs against a throwaway root (`TEZGAH_ROOTS`) and every cache file named for that session is deleted afterwards. A second probe per host proves the gate: one PreToolUse for `git commit --no-verify -m x` through the same wiring must come back as that host's deny (Claude, Codex and dsh `permissionDecision: deny`, Cursor `permission: deny`, omp `block: true`, opencode a throw) and leave a `deny` row in the ledger; under `pretooluse-off` or `verify-off` it reads UNVERIFIED and names the switch (`live_probe()`, `LIVE_DENY_COMMAND`). The omp bridge must also match a fresh render byte for byte, from the renderer the install writes with (`omp_bridge()`). Rows read `ok live <host>` / `ok live <host> deny`, `MISS live <host>: <cause>` (no tezgah command in the host file, the command wrote no row, no deny envelope or no deny row, a hand-edited omp bridge, Codex does not trust tezgah's group) or `UNVERIFIED live <host>: <why>` (node missing or older than 22.6, the omp CLI did not answer, a switch that stands the refusal down). It exits 1 when any host is MISS; UNVERIFIED never fails the run and is never shown as ok. No model is called. |
23
- | `--refresh` | Re-render every always-on contract artifact of the armed hosts - the managed blocks of `CLAUDE.md`, Codex's `AGENTS.md` and omp's `RULES.md`, opencode's contract and skill routers - after a policy edit or a kill switch; prints the paths it refreshed, else `contract is current`. Before the first install there is no hash record, so it changes nothing (`refresh_contract`, `bin/tezgah-setup::refresh_contract`). |
22
+ | `--report --live` | Proves the hooks run, not only that they are written (audit Phase 1.2). For each host, one synthetic PostToolUse (`true`) goes through that host's own wiring: the command in `~/.codex/hooks.json`, `~/.cursor/hooks.json`, `~/.qwen/settings.json` (a `run_shell_command` payload), `~/.factory/hooks.json` (an `Execute` payload), `~/.commandcode/settings.json` (a `shell_command` payload), `$COPILOT_HOME/hooks/tezgah.json` (a `Bash` payload), `~/.gemini/extensions/tezgah/hooks/hooks.json` (a `run_shell_command` payload), the `tezgah` name of `~/.gemini/config/hooks.json` (a camelCase `run_command` payload, the event on the command line), `~/.agents/plugins/tezgah/hooks/hooks.json` (a `shell` payload, the event in `event`), the enabled Claude plugin copy's `hooks/hooks.json` (with `CLAUDE_PLUGIN_ROOT`), the hooks.json named by dsh's `cordis.patch.yml`; the opencode plugin is imported with node the way opencode imports it; the omp extension `.ts` is loaded with node `--experimental-strip-types` after checking it is in omp's `extensions` list; Amp's plugin `tezgah.ts` is loaded the same way and fed Amp's own `tool.call`/`tool.result` events (a `Bash` call carrying `cmd`). A ledger row for a dedicated `tezgah-live-<pid>-<ms>` session must appear; the event runs against a throwaway root (`TEZGAH_ROOTS`) and every cache file named for that session is deleted afterwards. A second probe per host proves the gate: one PreToolUse for `git commit --no-verify -m x` through the same wiring must come back as that host's deny (Claude, Codex, dsh, Qwen, Droid and Command Code `permissionDecision: deny` under `hookSpecificOutput`, Copilot the same at the top level, Gemini and Antigravity a top-level `decision: deny`, Goose a top-level `decision: block`, Cursor `permission: deny`, omp `block: true`, Amp `action: reject-and-continue`, opencode a throw) and leave a `deny` row in the ledger; under `pretooluse-off` or `verify-off` it reads UNVERIFIED and names the switch (`live_probe()`, `LIVE_DENY_COMMAND`). The omp bridge and the Amp plugin must also match a fresh render byte for byte, from the renderer the install writes with (`omp_bridge()`, `hooks/tezgah_attest.py::amp_plugin`). Rows read `ok live <host>` / `ok live <host> deny`, `MISS live <host>: <cause>` (no tezgah command in the host file, the command wrote no row, no deny envelope or no deny row, a hand-edited omp bridge or Amp plugin, Codex does not trust tezgah's group) or `UNVERIFIED live <host>: <why>` (node missing or older than 22.6, the omp CLI did not answer, a switch that stands the refusal down, and always for Amp, whose own Bun loader is not what ran the plugin). It exits 1 when any host is MISS; UNVERIFIED never fails the run and is never shown as ok. No model is called, so the probe proves the hook command, not that the host binary obeys it: for Qwen that is `TEZGAH_E2E_SPEND=1 python3 tests/e2e_qwen.py` and for Droid `TEZGAH_E2E_SPEND=1 python3 tests/e2e_droid.py`, each spending a model call; Command Code, Copilot, Gemini, Antigravity, Goose and Amp have no such script yet, so their obedience is unverified. |
23
+ | `--refresh` | Re-render every always-on contract artifact of the armed hosts - the managed blocks of `CLAUDE.md`, Codex's `AGENTS.md`, omp's `RULES.md`, Qwen's `QWEN.md`, Droid's, Command Code's and Amp's `AGENTS.md`, Copilot's `copilot-instructions.md`, the `GEMINI.md` Gemini and Antigravity share, Goose's `.goosehints`, opencode's contract and skill routers - after a policy edit or a kill switch; prints the paths it refreshed, else `contract is current`. Before the first install there is no hash record, so it changes nothing (`refresh_contract`, `bin/tezgah-setup::refresh_contract`). |
24
24
  | `--uninstall` | Remove everything tezgah installed — per host the wiring it wrote, plus (a full run) the Claude plugin copy with its registry rows, the generated config state, the kill switches, the caches and the versioned install tree — then verify the removal; nonzero exit while anything tezgah wrote survives (`bin/tezgah-setup::verify_uninstall`, `bin/tezgah-setup::uninstall`). |
25
25
  | `--adopt` | Move pre-tezgah wiring aside instead of deleting it; alone it stops there, with `--install` it runs first (`bin/tezgah-setup::main`, `bin/tezgah-setup::adopt`). |
26
26
  | `--sync` | Copy this checkout over the Claude plugin [copy](glossary.md#plugin-copy) `installed_plugins.json` names, or over every copy when it names none (`bin/tezgah-setup::sync`, `bin/tezgah-setup::live_plugin_copies`). An older version dir is Claude's cache to collect, so it is left alone. Each copy is built in a staging tree beside the plugin cache and swapped in with two renames, its `.git` carried over, so a failed or killed sync leaves the previous copy whole (`bin/tezgah-setup::_swap`). Every copied file is 0644, or 0755 when the tree marks it executable, whatever mode the source has (`bin/tezgah-setup::_copy_normalised`). |
@@ -41,7 +41,7 @@ before an install is considered.
41
41
  | `update` | `tezgah update [--dry-run]` moves this install to the newest release through the channel it came from, then re-arms the hosts from the new tree. A release prefix goes through `--upgrade`. A Homebrew keg runs `brew upgrade r1z4x/tezgah/tezgah`, an npm tree runs `npm install -g @r1z4x/tezgah@latest`, and a git checkout runs `git pull --ff-only` (`update()`, `hooks/tezgah_update.py`). Before re-arming it prints every tezgah hook entry the new tree adds, removes or changes, per host. Paths compare with the tree root taken out, so a move to the new release's own path is no change. A real change on a terminal waits for a yes; Enter is no. A piped run goes on without asking (decision 12; `hooks/tezgah_update.py::confirm_rearm`). A no exits 1 after the fetch and says what it left. A checkout or an npm package is replaced in place, and the prefix's `current` already points at the new tree, so there the hooks run the new code and only the changed entries wait. A Homebrew keg is new beside the old one, so hooks wired to the old keg's path run the old code until the re-arm (`NOT_REARMED`, `hooks/tezgah_update.py::NOT_REARMED`). |
42
42
  | `--version` | Print the plugin version and exit (`bin/tezgah-setup::main`, `hooks/tezgah_context.py::version`). |
43
43
  | `--roots R` | Set the roots for this install, `os.pathsep`-separated. Without `--install` it exits 1: `--roots only means something with --install` (`bin/tezgah-setup::main`). |
44
- | `--hosts H` | Comma-separated subset of `claude,codex,opencode,cursor,dsh,omp`; an unknown name exits 1 before anything is written, and naming hosts switches off the re-detection that follows a tool install (`bin/tezgah-setup::main`, `bin/tezgah-setup::install_all`). `--install --hosts X` adds X to the hosts recorded in `config.json` and keeps the others. |
44
+ | `--hosts H` | Comma-separated subset of `claude,codex,opencode,cursor,dsh,omp,qwen,droid,commandcode,copilot,gemini,antigravity,goose,amp`; an unknown name exits 1 before anything is written, and naming hosts switches off the re-detection that follows a tool install (`bin/tezgah-setup::main`, `bin/tezgah-setup::install_all`). `--install --hosts X` adds X to the hosts recorded in `config.json` and keeps the others. |
45
45
  | `-h`, `--help` | Usage and the list above (`bin/tezgah-setup::main`). |
46
46
 
47
47
  A green `--report` says the wiring is on disk; `--report --live` says it ran. What it cannot cover is the host binary itself: it runs the command the host would run, not the host, so a host that refuses to load its own config (a Claude plugin disabled in the UI after the check, a dsh build whose bridge drops the event) still needs a real session to show.
@@ -186,7 +186,7 @@ too, from the same `hosts_row`, so the mismatch reaches the surface a session
186
186
  checks its own health with. Two places deliberately do not: the status line's
187
187
  marks are the armed/used checklist and are redrawn by a fresh process on every
188
188
  host (omp re-renders per tool result), while "wired" is `HOST_CHECKS`, the
189
- installer's own reading of six host files — a second reading of it in `hooks/`
189
+ installer's own reading of eight host files — a second reading of it in `hooks/`
190
190
  would be the drift `AGENTS.md` names as the report's source of truth; and
191
191
  `bin/tezgah-doctor` accounts for the bytes the hosts' stores hold and never reads
192
192
  a host's config, so a wiring row there would belong to a different tool.
@@ -233,7 +233,7 @@ After a change to the contract text (`hooks/tezgah_policy.py`,
233
233
  - `--install` re-renders every host file, the opencode contract and the skill
234
234
  router, and rewrites `~/.config/tezgah/contract.sha256`, which holds one
235
235
  `<sha> <path>` line per rendered artifact - the managed blocks of
236
- `CLAUDE.md`, `AGENTS.md` and `RULES.md`, `opencode-contract.md` and the
236
+ `CLAUDE.md`, Codex's, Droid's, Command Code's and Amp's `AGENTS.md`, `RULES.md`, `QWEN.md`, `copilot-instructions.md`, `GEMINI.md` and `.goosehints`, `opencode-contract.md` and the
237
237
  `opencode-skills*.md` routers - rather than one hash over the sources
238
238
  (`bin/tezgah-setup::record_shas`, `bin/tezgah-setup::contract_artifacts`).
239
239
  - `--refresh` does that without a reinstall, for every armed host's static
@@ -687,6 +687,13 @@ The reviewer brief carries the matching rule
687
687
  accounts for every listed file as reviewed or skipped with a reason. It still
688
688
  applies the rest of its brief.
689
689
 
690
+ The reviewer also has a plan-readiness mode for a plan file instead of a diff
691
+ (`hooks/tezgah_agents.py::_reviewer_body`). It restates each section's intent in
692
+ one sentence. A section it cannot restate is a major finding. A build choice the
693
+ plan leaves to the implementer is a major finding too. A file the plan will
694
+ create is not a missing reference. `skills/plan-add/SKILL.md` runs this review
695
+ once before the `implementation` phase.
696
+
690
697
  `tezgah-review-quote --from BASE [--to HEAD] FINDINGS.json` places each finding by
691
698
  its quoted code (`bin/tezgah-review-quote::main`). Each finding needs `path` and
692
699
  `quote`, and may carry the `start_line` it cites. The tool adds a `status`.
@@ -849,6 +856,10 @@ that was active when it opened, and only that plan's records count. A record
849
856
  with no plan counts when it changed after the plan's checkpoint commit. A
850
857
  `resumed` record hands the guard to its re-dispatch's own record
851
858
  (`bin/tezgah-task::unfinished_review`).
859
+ `tezgah-task review` also refuses an approve with no record of that reviewer for the plan. A
860
+ reviewer with no status did not run. A host that keeps no slice journal passes
861
+ `--no-slice "<why>"`, and the plan's `review:` line keeps that reason
862
+ (`bin/tezgah-task::cmd_review`). A `changes` verdict needs no record.
852
863
  The lever map is a closed set. `rate_limit` and `quota` point at the
853
864
  `hooks/tezgah_models.py` fallback chain. `cut_off` and `timeout` point at a
854
865
  per-slice deadline, `aborted` at the `wip:` commit cadence, a duplicate job at