@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.
- package/CHANGELOG.md +507 -1
- package/MANIFEST +40 -0
- package/NOTICE +152 -0
- package/README.de.md +41 -20
- package/README.es.md +39 -19
- package/README.fr.md +41 -20
- package/README.md +18 -10
- package/README.tr.md +39 -21
- package/bin/codegen +9 -2
- package/bin/consult +6 -0
- package/bin/tezgah-adhd +3 -0
- package/bin/tezgah-agents +3 -0
- package/bin/tezgah-capture +3 -0
- package/bin/tezgah-design +329 -54
- package/bin/tezgah-docs +158 -42
- package/bin/tezgah-dsh +1 -1
- package/bin/tezgah-gate +7 -2
- package/bin/tezgah-import-ai-research +16 -4
- package/bin/tezgah-index +3 -0
- package/bin/tezgah-lessons +13 -4
- package/bin/tezgah-mcp +4 -0
- package/bin/tezgah-migrate +3 -0
- package/bin/tezgah-ocr-review +2 -1
- package/bin/tezgah-optimize +4 -1
- package/bin/tezgah-pony +3 -0
- package/bin/tezgah-research +51 -10
- package/bin/tezgah-rollback +3 -0
- package/bin/tezgah-route +1 -1
- package/bin/tezgah-setup +1741 -95
- package/bin/tezgah-status +11 -1
- package/bin/tezgah-task +58 -17
- package/bin/tezgah.js +10 -3
- package/commands/adhd.md +1 -1
- package/docs/README.md +19 -8
- package/docs/architecture.md +2 -2
- package/docs/citations-baseline.json +11 -17
- package/docs/clarity-baseline.json +12 -6
- package/docs/contract.md +83 -34
- package/docs/environment.md +59 -24
- package/docs/evidence.md +128 -25
- package/docs/gate.md +77 -23
- package/docs/glossary.md +1 -1
- package/docs/hosts.md +343 -48
- package/docs/layers.md +2 -2
- package/docs/models.md +6 -0
- package/docs/operations.md +83 -29
- package/docs/research.md +37 -11
- package/docs/skills.md +31 -8
- package/docs/status-line.md +39 -23
- package/docs/testing.md +50 -17
- package/docs/vision.md +6 -2
- package/hooks/hooks.json +1 -1
- package/hooks/lang/en.json +16 -2
- package/hooks/lang/tr.json +14 -2
- package/hooks/projects-posttooluse.py +11 -3
- package/hooks/projects-stop.py +3 -2
- package/hooks/tezgah_agents.py +27 -3
- package/hooks/tezgah_attest.py +79 -10
- package/hooks/tezgah_context.py +61 -31
- package/hooks/tezgah_gate.py +480 -126
- package/hooks/tezgah_integrity.py +577 -133
- package/hooks/tezgah_lessons.py +5 -2
- package/hooks/tezgah_paths.py +60 -8
- package/hooks/tezgah_policy.py +48 -18
- package/hooks/tezgah_replay.py +8 -2
- package/hooks/tezgah_research.py +167 -24
- package/hooks/tezgah_skill_pick.py +1 -1
- package/hooks/tezgah_snapshot.py +6 -3
- package/hooks/tezgah_stack.py +236 -1
- package/hooks/tezgah_stopspec.py +2 -1
- package/hooks/tezgah_task.py +2 -1
- package/hosts/amp/hook.py +217 -0
- package/hosts/amp/tezgah-plugin.ts.in +396 -0
- package/hosts/antigravity/hook.py +255 -0
- package/hosts/codex/hook.py +3 -2
- package/hosts/commandcode/hook.py +239 -0
- package/hosts/copilot/hook.py +284 -0
- package/hosts/cursor/hook.py +4 -2
- package/hosts/droid/hook.py +259 -0
- package/hosts/dsh/hooks.json +1 -1
- package/hosts/gemini/hook.py +213 -0
- package/hosts/goose/hook.py +145 -0
- package/hosts/omp/hook.py +5 -2
- package/hosts/omp/tezgah-hook.ts.in +157 -42
- package/hosts/opencode/plugins/tezgah.js +209 -63
- package/hosts/qwen/hook.py +206 -0
- package/output-styles/tezgah.md +11 -6
- package/package.json +6 -3
- package/packaging/build.sh +4 -4
- package/packaging/install.sh +11 -0
- package/packaging/upgrade.sh +3 -0
- package/skills/ai-research/LICENSE +21 -0
- package/skills/design-contract/SKILL.md +27 -6
- package/skills/design-library/LICENSE +21 -0
- package/skills/harness/SKILL.md +2 -0
- package/skills/i-have-adhd/SKILL.md +10 -1
- package/skills/plan-add/SKILL.md +5 -1
- package/skills/plan-add/render_table.py +7 -0
- package/skills/pm-frameworks/LICENSE +21 -0
- package/skills/research/SKILL.md +22 -9
- package/skills/rl-env/LICENSE +201 -0
- package/skills/tezgah-contract/SKILL.md +21 -8
- package/statusline.py +20 -9
- package/third_party/open-code-review/LICENSE +201 -0
- package/third_party/open-code-review/README.md +15 -0
- package/third_party/open-code-review/prompts/main_task_system.md +26 -0
- package/third_party/open-code-review/prompts/review_filter_task_system.md +13 -0
- package/workflows/graph-map.js +14 -5
- 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
|
|
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
|
|
@@ -17,12 +17,20 @@ surface can honestly report this session; the two skill-read marks (`pony`,
|
|
|
17
17
|
|
|
18
18
|
| Host | Hook events it fires | Always-on contract | Per-turn reminder | Status surface, how drawn | Measures |
|
|
19
19
|
|---|---|---|---|---|---|
|
|
20
|
-
| claude | SessionStart, SubagentStart, PostCompact, UserPromptSubmit, PreToolUse, PostToolUse, PostToolUseFailure, Stop, SubagentStop (record-only, `subagent_end`) - `hooks/hooks.json:2-32` | the managed block in `~/.claude/CLAUDE.md` (`CLAUDE_RULES`), Claude's global memory file; the SessionStart/PostCompact hook rows declare `TEZGAH_CORE_IN_FILE` (`hooks/hooks.json:4,10`) and the hook drops the core when the block is there, so the contract is paid for once (`hooks/projects-auto-init.py::core_is_in_a_file`; the static `output-styles/tezgah.md` mirror (`output-styles/tezgah.md:1`) is the hookless build's copy) | UserPromptSubmit -> `additionalContext` (`hooks/hooks.json:12-14`, `hooks/projects-auto-init.py::main`) | `statusLine` command in `~/.claude/settings.json` (`bin/tezgah-setup::wire_claude_statusline`), ANSI, forwards Orca first (`statusline.py:
|
|
20
|
+
| claude | SessionStart, SubagentStart, PostCompact, UserPromptSubmit, PreToolUse, PostToolUse, PostToolUseFailure, Stop, SubagentStop (record-only, `subagent_end`) - `hooks/hooks.json:2-32` | the managed block in `~/.claude/CLAUDE.md` (`CLAUDE_RULES`), Claude's global memory file; the SessionStart/PostCompact hook rows declare `TEZGAH_CORE_IN_FILE` (`hooks/hooks.json:4,10`) and the hook drops the core when the block is there, so the contract is paid for once (`hooks/projects-auto-init.py::core_is_in_a_file`; the static `output-styles/tezgah.md` mirror (`output-styles/tezgah.md:1`) is the hookless build's copy) | UserPromptSubmit -> `additionalContext` (`hooks/hooks.json:12-14`, `hooks/projects-auto-init.py::main`) | `statusLine` command in `~/.claude/settings.json` (`bin/tezgah-setup::wire_claude_statusline`), ANSI, forwards Orca first (`orca_out`, `statusline.py:52-60`) | all seven: the transcript parse covers the two skill reads, `graph`, `orch` and the three shell kinds - `consult`, `research` and the judgement callers (`statusline.py::claude_used`, `observable=None` at `statusline.py::observable`) |
|
|
21
21
|
| codex | SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, SubagentStart, PostCompact, Stop (`bin/tezgah-setup::CODEX_HOOK_EVENTS`; named in `hosts/codex/hook.py:6-7`) | hook: the normalized events inject `context_for` (`hosts/codex/hook.py::main`) | UserPromptSubmit -> `additionalContext` (`hosts/codex/hook.py::main`) | no custom footer item (`tui.status_line` is a closed enum) - the line rides `systemMessage`, plain, at SessionStart and Stop (`hosts/codex/hook.py:10-12`, `hosts/codex/hook.py::main`) | five tool-use (`TOOL_USE_MEASURES` at `hosts/codex/hook.py::main`) |
|
|
22
|
-
| cursor | 13 events: sessionStart, preToolUse, postToolUse, postToolUseFailure, subagentStart, subagentStop, beforeSubmitPrompt, stop, beforeMCPExecution, afterShellExecution, afterMCPExecution, afterFileEdit, afterAgentResponse (`bin/tezgah-setup::CURSOR_HOOK_EVENTS`) | hook: sessionStart -> `additional_context` (`hosts/cursor/hook.py::dispatch`) | beforeSubmitPrompt -> `additional_context` plus `{"continue": true}` (`hosts/cursor/hook.py::dispatch`) | `statusLine` in `~/.cursor/cli-config.json` -> `tezgah-statusline --cursor` (`bin/tezgah-setup::install_cursor`), ANSI (`statusline.py
|
|
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
|
|
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
|
|
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
|
|
22
|
+
| cursor | 13 events: sessionStart, preToolUse, postToolUse, postToolUseFailure, subagentStart, subagentStop, beforeSubmitPrompt, stop, beforeMCPExecution, afterShellExecution, afterMCPExecution, afterFileEdit, afterAgentResponse (`bin/tezgah-setup::CURSOR_HOOK_EVENTS`) | hook: sessionStart -> `additional_context` (`hosts/cursor/hook.py::dispatch`) | beforeSubmitPrompt -> `additional_context` plus `{"continue": true}` (`hosts/cursor/hook.py::dispatch`) | `statusLine` in `~/.cursor/cli-config.json` -> `tezgah-statusline --cursor` (`bin/tezgah-setup::install_cursor`), ANSI (`statusline.py::color`) | five tool-use (`statusline.py::observable`) |
|
|
23
|
+
| opencode | no lifecycle events; plugin hooks: config, permission.ask, tool.execute.before, event, shell.env, experimental.session.compacting, chat.message, tool.execute.after (the hook object `Tezgah` in `hosts/opencode/plugins/tezgah.js` returns) - `tool.execute.before` carries the whole gate, `event` writes a `spawned` row naming the parent on a child's `session.created`, `tool.execute.after` the channel on the row, the provenance label on the result and the taint notice on the next effect (`untrustedSource` and `labelResult` in the same file) | static only: generated `instructions` files, because there is no session-start injection point (the header comment of `hosts/opencode/plugins/tezgah.js`, `bin/tezgah-setup::opencode_contract_text`) | chat.message pushes a synthetic part (the `chat.message` hook of `Tezgah`) | TUI plugin `tezgah-tui.tsx` declared in `tui.json`, runs `tezgah-status` on the event bus (`TezgahBar` in `hosts/opencode/tui/tezgah-tui.tsx`; wiring `bin/tezgah-setup::install_opencode`) | all seven: skill reads and kinds are classified in process (`classify` in `hosts/opencode/plugins/tezgah.js`) |
|
|
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` (`STATUS_PATH` and `apply` in `hosts/dsh/statusline/lib/index.js`) | five tool-use: the route passes `--observable=consult,research,graph,orch,judge` (`OBSERVABLE` in `hosts/dsh/statusline/lib/index.js`) |
|
|
25
|
+
| omp | 13 events: agent_start, tool_execution_start, tool_execution_end, agent_end, session_shutdown, session_start, session_compact, session_switch, turn_end, before_agent_start, tool_call, tool_result, session_stop (the `pi.on` registrations in `hosts/omp/tezgah-hook.ts.in`); the first five drive the busy spinner, and `agent_end` also records a task subagent's end, since omp fires no `session_stop` for one | 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 (its `pi.on` handler in `hosts/omp/tezgah-hook.ts.in`) | extension draws `ctx.ui.setWidget` below the editor, `setStatus` as fallback (`draw` in `hosts/omp/tezgah-hook.ts.in`); ANSI line from `hosts/omp/hook.py::status_line` | all seven: the embedded runner filters skill reads before asking python (`skillRead` in `hosts/omp/tezgah-hook.ts.in`) 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
|
|
@@ -36,26 +44,50 @@ What each host can enforce, in the
|
|
|
36
44
|
[enforcement capability](glossary.md#enforcement-capability) words. `gate`: the
|
|
37
45
|
hook can refuse. `observe`: it records and never refuses. `partial`: only part
|
|
38
46
|
of the surface fires. `unavailable`: the host has no event for it. A cell gets
|
|
39
|
-
`unverified` when no
|
|
40
|
-
the code path there
|
|
47
|
+
`unverified` when no run has shown the host binary obeying it. Fixture payloads
|
|
48
|
+
test the code path there. `--report --live` goes one step further: it drives the
|
|
49
|
+
installed wiring, the hook command or the plugin loaded with node. No host
|
|
50
|
+
binary runs, so a live `ok` proves that the wiring answers, not that the host obeys.
|
|
41
51
|
|
|
42
52
|
| Host | Gate (PreToolUse) | Stop | Subagent |
|
|
43
53
|
|---|---|---|---|
|
|
44
|
-
| claude | gate, probed by `--report --live` (`bin/tezgah-setup::live_probe`) | gate, unverified (`hooks/projects-stop.py::main`) | observe, unverified: a brief at SubagentStart, a record-only `subagent_end` row at SubagentStop (ADR 011) |
|
|
45
|
-
| codex | gate, probed by `--report --live`; Codex runs only a trusted group | gate, unverified (`hosts/codex/hook.py::main`) | partial, unverified: a brief at SubagentStart, no subagent-end event |
|
|
46
|
-
| cursor | gate, probed by `--report --live` | gate, unverified (`hosts/cursor/hook.py::dispatch`) | observe, unverified: a brief at subagentStart, a record-only `subagent_end` row at subagentStop |
|
|
47
|
-
| opencode | gate, probed by `--report --live
|
|
48
|
-
| 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
|
-
| omp | gate, probed by `--report --live
|
|
54
|
+
| claude | gate, unverified: probed by `--report --live` (`bin/tezgah-setup::live_probe`), which runs the PreToolUse command of the plugin copy's `hooks/hooks.json` on a deny payload; no claude binary runs, so that Claude Code obeys the deny is not shown | gate, unverified (`hooks/projects-stop.py::main`) | observe, unverified: a brief at SubagentStart, a record-only `subagent_end` row at SubagentStop (ADR 011) |
|
|
55
|
+
| codex | gate, unverified: probed by `--report --live` through the PreToolUse command in `$CODEX_HOME/hooks.json`; no codex binary runs, and Codex runs only a trusted group | gate, unverified (`hosts/codex/hook.py::main`) | partial, unverified: a brief at SubagentStart, no subagent-end event |
|
|
56
|
+
| cursor | gate, unverified: probed by `--report --live` through the preToolUse command in `~/.cursor/hooks.json`; no cursor-agent binary runs | gate, unverified (`hosts/cursor/hook.py::dispatch`) | observe, unverified: a brief at subagentStart, a record-only `subagent_end` row at subagentStop |
|
|
57
|
+
| opencode | gate, unverified: probed by `--report --live`, which imports the installed plugin with node and fires its `tool.execute.before`; opencode's own loader is not what ran it | unavailable: the plugin API has no end-of-turn event | unavailable |
|
|
58
|
+
| dsh | gate, unverified: probed by `--report --live` through the bridge's PreToolUse command; no dsh binary runs | unavailable: ADR 003 removed the Stop entry, because the bridge carries no outcome | partial, unverified: a brief at SubagentStart, no SubagentStop entry |
|
|
59
|
+
| omp | gate, unverified: probed by `--report --live`, which loads the installed extension with node 22.6+; omp's own loader is not what ran it | 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 |
|
|
60
|
+
| qwen | gate, unverified: probed by `--report --live` through the hook command in `~/.qwen/settings.json`; that the qwen binary obeys the deny stays 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 |
|
|
61
|
+
| droid | gate, unverified: probed by `--report --live` through the hook command in `~/.factory/hooks.json`; that the droid binary obeys the deny stays 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 |
|
|
62
|
+
| 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 |
|
|
63
|
+
| 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 |
|
|
64
|
+
| 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` |
|
|
65
|
+
| 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 |
|
|
66
|
+
| 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 |
|
|
67
|
+
| 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
68
|
|
|
51
69
|
Every host also attests its own hook entries once per session
|
|
52
|
-
(`hooks/tezgah_attest.py::run`).
|
|
53
|
-
|
|
70
|
+
(`hooks/tezgah_attest.py::run`). Every host but opencode does it at session
|
|
71
|
+
start. opencode does it at the first message, through `oncePerSession`.
|
|
54
72
|
The `attest` row and the `drift` mark are
|
|
55
73
|
[evidence](evidence.md#the-kinds-by-what-reads-them), never a block.
|
|
56
74
|
`--report` prints the same comparison per host
|
|
57
75
|
(`bin/tezgah-setup::attestation_rows`).
|
|
58
76
|
|
|
77
|
+
Every Stop adapter hands the core the host's permission mode for the turn
|
|
78
|
+
(`hooks/tezgah_integrity.py::read_only_turn`, plan 142). Claude, Command Code,
|
|
79
|
+
Qwen, Copilot, Gemini, Codex, Goose, Droid and Cursor send it as
|
|
80
|
+
`permission_mode` on the Stop payload (Antigravity's envelope is camelCase,
|
|
81
|
+
`permissionMode`). omp has no such field, so its extension reads
|
|
82
|
+
`ctx.sessionModes.getState().plan` and sends `plan` when it is active
|
|
83
|
+
(`hosts/omp/tezgah-hook.ts.in`). A host that sends nothing reads as `default`.
|
|
84
|
+
Only `plan` and Droid's `spec` change a verdict, and only two of them: the rule
|
|
85
|
+
lets the approval question such a turn ends on through, and refuses a
|
|
86
|
+
done/tested claim in it
|
|
87
|
+
([evidence](evidence.md#the-stop-rule-end-to-end)). Claude's hooks reference
|
|
88
|
+
documents the field on its own Stop payload. Every other adapter reads whatever
|
|
89
|
+
its payload carries.
|
|
90
|
+
|
|
59
91
|
## What each host gets, and where it is written
|
|
60
92
|
|
|
61
93
|
Every host gets the skill symlinks from `SKILLS` (`bin/tezgah-setup::SKILLS`)
|
|
@@ -90,7 +122,8 @@ shared config, contract hash and the CLI symlinks every host shell can call
|
|
|
90
122
|
(`bin/tezgah-setup::write_user_agents`).
|
|
91
123
|
- **cursor** - `install_cursor` (`bin/tezgah-setup::install_cursor`): `~/.cursor/hooks.json`
|
|
92
124
|
(tezgah entries replaced, a user's Orca entries merged), `skills/*` symlinks,
|
|
93
|
-
`mcp.json` servers, and `cli-config.json` `statusLine`.
|
|
125
|
+
`mcp.json` servers, and `cli-config.json` `statusLine`. The symlinks skip a
|
|
126
|
+
skill another dir Cursor reads already links (`bin/tezgah-setup::cursor_shared_copy`).
|
|
94
127
|
- **opencode** - `install_opencode` (`bin/tezgah-setup::install_opencode`): the plugin under
|
|
95
128
|
both `plugins/` and `plugin/` (version drift), `skills/*` symlinks, the
|
|
96
129
|
generated `~/.config/tezgah/opencode-contract.md` (`bin/tezgah-setup::opencode_contract`) and
|
|
@@ -126,6 +159,131 @@ shared config, contract hash and the CLI symlinks every host shell can call
|
|
|
126
159
|
its own: orx's `codex` target writes the shim to `~/.agents/skills/orx`,
|
|
127
160
|
omp's native `agents` skill root, so `install_openresearch` maps omp onto it
|
|
128
161
|
(`ORX_AGENTS`, `bin/tezgah-setup::ORX_AGENTS`).
|
|
162
|
+
- **qwen** - `install_qwen` (`bin/tezgah-setup::install_qwen`) writes into
|
|
163
|
+
`~/.qwen/settings.json` (or `$QWEN_HOME`). It adds one hook group per event of
|
|
164
|
+
`QWEN_HOOK_EVENTS`, each `timeout` in milliseconds. The PreToolUse group
|
|
165
|
+
carries `QWEN_PRETOOL_MATCHER` over Qwen's runtime tool ids. It also sets
|
|
166
|
+
`ui.statusLine`. A status line the user had comes back at uninstall. Orca's
|
|
167
|
+
groups and the provider keys in that file stay, and
|
|
168
|
+
the backup is owner-only (`bin/tezgah-setup::backup`). The install links
|
|
169
|
+
`skills/*` into `~/.qwen/skills`. It writes the always-on block in
|
|
170
|
+
`~/.qwen/QWEN.md`. tezgah writes no subagent roles, MCP rows or orx shim for
|
|
171
|
+
Qwen yet.
|
|
172
|
+
- **droid** - `install_droid` (`bin/tezgah-setup::install_droid`) writes
|
|
173
|
+
`~/.factory/hooks.json`, keyed by event at the top. It adds one hook group per
|
|
174
|
+
event of `DROID_HOOK_EVENTS`, each `timeout` in seconds, and keeps the user's
|
|
175
|
+
own groups. The PreToolUse group carries `DROID_PRETOOL_MATCHER` over Droid's
|
|
176
|
+
tool ids. Droid reads the `hooks` key of `settings.json` only while hooks.json
|
|
177
|
+
is absent. The install then writes no hooks.json and says why
|
|
178
|
+
(`bin/tezgah-setup::droid_hooks_blocked`). Droid's settings.json holds
|
|
179
|
+
comments, so the install edits it in place and keeps them
|
|
180
|
+
(`bin/tezgah-setup::jsonc_put`). It adds the `statusLine`. It sets
|
|
181
|
+
`includeCoAuthoredByDroid` to false, the switch for Droid's co-author trailer.
|
|
182
|
+
The uninstall puts that switch back to what the install found: the user's
|
|
183
|
+
value, or no key. A file it cannot edit safely is left alone and the run names
|
|
184
|
+
both keys. The install
|
|
185
|
+
links `skills/*` into `~/.factory/skills` beside the user's own links. It
|
|
186
|
+
writes the always-on block in `~/.factory/AGENTS.md`. The uninstall deletes a
|
|
187
|
+
hooks.json that held only tezgah's groups. An empty one would still hide
|
|
188
|
+
settings.json's hooks. tezgah writes no subagent roles, MCP rows or orx
|
|
189
|
+
shim for Droid yet.
|
|
190
|
+
- **commandcode** - `install_commandcode` (`bin/tezgah-setup::install_commandcode`)
|
|
191
|
+
writes into the `hooks` key of `~/.commandcode/settings.json`. It adds one
|
|
192
|
+
hook group per event of `COMMANDCODE_HOOK_EVENTS`, each `timeout` in seconds.
|
|
193
|
+
The user's own groups stay, and the backup is owner-only. The PreToolUse group
|
|
194
|
+
carries `COMMANDCODE_PRETOOL_MATCHER`, which Command Code tests against the
|
|
195
|
+
tool's display name. The same write sets `attribution.commit` to `""`. That
|
|
196
|
+
turns off the default `Co-authored-by: CommandCodeBot` commit trailer. A value
|
|
197
|
+
the user set stays. The uninstall restores the value the install found, from
|
|
198
|
+
the install record (`bin/tezgah-setup::restore_prior`). The install links `skills/*` into `~/.commandcode/skills`. It
|
|
199
|
+
writes the always-on block in `~/.commandcode/AGENTS.md`. tezgah writes no
|
|
200
|
+
subagent roles, MCP rows or orx shim for Command Code yet.
|
|
201
|
+
- **copilot** - `install_copilot` (`bin/tezgah-setup::install_copilot`) writes
|
|
202
|
+
`$COPILOT_HOME/hooks/tezgah.json`, a `{"version": 1, "hooks": ...}` file that
|
|
203
|
+
is wholly tezgah's. `COPILOT_HOME` defaults to `~/.copilot`. The file holds
|
|
204
|
+
one entry per event of `COPILOT_HOOK_EVENTS`, each `timeoutSec` in seconds.
|
|
205
|
+
The inline `hooks` key of settings.json is never used, since one bad item
|
|
206
|
+
there rejects every hook in it. The PreToolUse entry carries
|
|
207
|
+
`COPILOT_PRETOOL_MATCHER`. settings.json is JSON with comments. So the status
|
|
208
|
+
line and `includeCoAuthoredBy: false` go in only while it parses as plain
|
|
209
|
+
JSON. A file holding comments gets a `holds comments - left untouched` line,
|
|
210
|
+
and the install still exits 0. The install refuses an unreadable, non-UTF-8
|
|
211
|
+
or non-object file and fails. That setting turns off the default `Co-authored-by` trailer on the
|
|
212
|
+
agent's commits. A value the user set stays. The install links `skills/*`
|
|
213
|
+
into `$COPILOT_HOME/skills` and writes the always-on block in
|
|
214
|
+
`$COPILOT_HOME/copilot-instructions.md`. The uninstall deletes tezgah.json.
|
|
215
|
+
Each settings key goes back to what the install found, the status line
|
|
216
|
+
too. Copilot gets no subagent roles, MCP rows or orx shim from tezgah yet.
|
|
217
|
+
- **gemini** - `install_gemini` (`bin/tezgah-setup::install_gemini`) writes
|
|
218
|
+
tezgah's own extension, `~/.gemini/extensions/tezgah/`, which is wholly
|
|
219
|
+
tezgah's. `GEMINI_CLI_HOME` moves `~/.gemini` under it. The dir holds
|
|
220
|
+
`gemini-extension.json`, `hooks/hooks.json` with one group per event of
|
|
221
|
+
`GEMINI_HOOK_EVENTS` (each `timeout` in milliseconds) and `skills/*` links.
|
|
222
|
+
The BeforeTool group carries `GEMINI_PRETOOL_MATCHER`. settings.json is never
|
|
223
|
+
written: it is JSON with comments and holds the user's keys. Gemini's default
|
|
224
|
+
prompt adds no co-author trailer and no setting names one, so there is
|
|
225
|
+
nothing to turn off. The install writes the always-on block in
|
|
226
|
+
`~/.gemini/GEMINI.md`, which Antigravity reads too. The uninstall deletes the
|
|
227
|
+
extension dir. It strips the block only while no other host of
|
|
228
|
+
`GEMINI_MD_HOSTS` stays armed (`bin/tezgah-setup::gemini_md_kept`). Gemini
|
|
229
|
+
gets no subagent roles, MCP rows or orx shim from tezgah yet.
|
|
230
|
+
- **antigravity** - `install_antigravity` (`bin/tezgah-setup::install_antigravity`)
|
|
231
|
+
writes tezgah's hook set under the top-level `tezgah` name of
|
|
232
|
+
`~/.gemini/config/hooks.json`, beside the user's other names. Each event of
|
|
233
|
+
`ANTIGRAVITY_HOOK_EVENTS` gets one item, its `timeout` in seconds. PreToolUse
|
|
234
|
+
and PostToolUse take a matcher group, the others a bare handler. Antigravity
|
|
235
|
+
never loads hooks from `antigravity-cli/settings.json`. The Antigravity App
|
|
236
|
+
and IDE read the same global hooks.json, so the install arms them too. The
|
|
237
|
+
status line goes in `~/.gemini/antigravity-cli/settings.json` only while it
|
|
238
|
+
is plain JSON. The install names a file holding comments, leaves it alone and exits 0.
|
|
239
|
+
The skill links go in `antigravity-cli/skills`. Antigravity
|
|
240
|
+
adds no co-author trailer of its own, and no setting names one. An open
|
|
241
|
+
feature request asks for one (google-antigravity/antigravity-cli#230). The
|
|
242
|
+
always-on block is the `~/.gemini/GEMINI.md` one Gemini CLI shares
|
|
243
|
+
(`bin/tezgah-setup::gemini_md_kept`). The uninstall takes out the `tezgah`
|
|
244
|
+
name and the links, and puts the user's own statusLine back. It removes a file that held only
|
|
245
|
+
tezgah's keys. Antigravity gets no subagent roles, MCP rows or orx shim from
|
|
246
|
+
tezgah yet. Antigravity can never show a passing check. Its PostToolUse
|
|
247
|
+
carries no output. A shell call that went to the background also ends with an
|
|
248
|
+
empty `error`. So the hook records each shell call as ran, never a pass
|
|
249
|
+
(`hosts/antigravity/hook.py::post_tool`). The owner chose this: a done or
|
|
250
|
+
tested claim there always stops at the Stop rule's `no verify_ok` class.
|
|
251
|
+
- **goose** - `install_goose` (`bin/tezgah-setup::install_goose`) writes
|
|
252
|
+
tezgah's own plugin, `~/.agents/plugins/tezgah/`, which is wholly tezgah's.
|
|
253
|
+
It holds `plugin.json`, `hooks/hooks.json` and `skills/*` links. The hooks
|
|
254
|
+
file has one group per event of `GOOSE_HOOK_EVENTS`, each `timeout` in
|
|
255
|
+
seconds. Goose loads the skills as `tezgah:<skill>`. Goose tests a matcher
|
|
256
|
+
unanchored, so the PreToolUse group carries the anchored
|
|
257
|
+
`GOOSE_PRETOOL_MATCHER`. No hook sets `on_failure`, so it stays at Goose's
|
|
258
|
+
default `allow`, and a hook fault lets the call through. The always-on block
|
|
259
|
+
goes in `~/.config/goose/.goosehints`. Neither Goose's docs nor its system
|
|
260
|
+
prompt add a co-author trailer or name a setting for one. The uninstall
|
|
261
|
+
deletes the plugin dir and strips the block. The rest of `~/.agents` is the
|
|
262
|
+
user's and stays. The install ignores `GOOSE_PATH_ROOT`, since the docs
|
|
263
|
+
do not say where it puts the hints file. Goose gets no subagent roles, MCP
|
|
264
|
+
rows or orx shim from tezgah yet.
|
|
265
|
+
- **amp** - `install_amp` (`bin/tezgah-setup::install_amp`) writes one file,
|
|
266
|
+
`tezgah.ts`, into Amp's system plugin dir (`hooks/tezgah_paths.py::AMP_PLUGIN`,
|
|
267
|
+
under `XDG_CONFIG_HOME` when that is set). `hooks/tezgah_attest.py::amp_plugin`
|
|
268
|
+
renders it from `hosts/amp/tezgah-plugin.ts.in`. Amp loads every `.ts` there,
|
|
269
|
+
and nothing registers it. A running Amp needs `plugins: reload`. The plugin
|
|
270
|
+
spawns `hosts/amp/hook.py` without blocking, one JSON in and one out. One
|
|
271
|
+
plugin process serves many threads, so it keys all its state by thread id.
|
|
272
|
+
The install links the skills into `~/.config/amp/skills` and writes the
|
|
273
|
+
always-on block into `~/.config/amp/AGENTS.md`. By default Amp adds a
|
|
274
|
+
`Co-authored-by: Amp` and an `Amp-Thread-ID` trailer to its commits. The
|
|
275
|
+
install sets `amp.git.commit.coauthor.enabled` and
|
|
276
|
+
`amp.git.commit.ampThread.enabled` to false in `~/.config/amp/settings.json`,
|
|
277
|
+
or in `settings.jsonc` when that is the only one, unless the user set them.
|
|
278
|
+
It writes that file only while it is plain JSON. Install, uninstall and the
|
|
279
|
+
report resolve it the same way (`bin/tezgah-setup::amp_settings`). The
|
|
280
|
+
install names a file holding comments, leaves it alone and exits 0. It
|
|
281
|
+
refuses both files at once, or an unreadable one, and exits 1. Then the run
|
|
282
|
+
prints both keys to set by hand. The uninstall removes the plugin only when
|
|
283
|
+
it is a fresh render or starts with tezgah's header line
|
|
284
|
+
(`bin/tezgah-setup::amp_plugin_ours`). A trailer key still false goes back to
|
|
285
|
+
what the install found. Amp gets no subagent roles, MCP rows or orx shim from
|
|
286
|
+
tezgah.
|
|
129
287
|
|
|
130
288
|
An adapter is responsible for its host's **envelope** and its **surface**: it
|
|
131
289
|
translates the host's event and tool names into the shared vocabulary (one call
|
|
@@ -149,6 +307,14 @@ pays for it once.
|
|
|
149
307
|
| opencode | no stdout-inject hook: `install_opencode` writes the generated `~/.config/tezgah/opencode-contract.md` and references it from `opencode.json` `instructions` |
|
|
150
308
|
| omp | the managed block in `~/.omp/agent/RULES.md` |
|
|
151
309
|
| 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) |
|
|
310
|
+
| 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`) |
|
|
311
|
+
| 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`) |
|
|
312
|
+
| 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`) |
|
|
313
|
+
| 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`) |
|
|
314
|
+
| 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`) |
|
|
315
|
+
| 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`) |
|
|
316
|
+
| 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 |
|
|
317
|
+
| 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
318
|
|
|
153
319
|
An installed host whose core line is missing is a health check's business
|
|
154
320
|
(`host_checks_<host>`), not a second copy: two delivery paths for one host is the
|
|
@@ -167,11 +333,27 @@ and drops the later ones.
|
|
|
167
333
|
| context | cursor | the `sessionStart` hook context, the repo's `AGENTS.md`, `.cursor/rules/*.mdc`, `.cursorrules` | the hook |
|
|
168
334
|
| context | opencode | `~/.config/opencode/AGENTS.md`, `opencode.json` `instructions`, the repo's `AGENTS.md` | the contract and router files in `instructions` |
|
|
169
335
|
| 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 |
|
|
336
|
+
| context | qwen | `~/.qwen/QWEN.md`, the repo's `QWEN.md` and `AGENTS.md`, then `.qwen/QWEN.local.md`; all load | block in the first |
|
|
337
|
+
| 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` |
|
|
338
|
+
| context | commandcode | `~/.commandcode/AGENTS.md`, the repo's `AGENTS.md` (else `.commandcode/AGENTS.md`), then each subdirectory's; all load | block in the first |
|
|
339
|
+
| 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 |
|
|
340
|
+
| 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 |
|
|
341
|
+
| 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` |
|
|
342
|
+
| 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` |
|
|
343
|
+
| 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
344
|
| 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
345
|
| skills | claude | `.claude/skills`, `~/.claude/skills`; plugin skills are namespaced `tezgah:<name>` | the plugin copy |
|
|
172
346
|
| 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
|
-
| 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` |
|
|
347
|
+
| 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`, only for the skills `~/.codex`, `~/.claude` or `~/.agents/skills` do not already link |
|
|
174
348
|
| 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 |
|
|
349
|
+
| skills | qwen | `~/.qwen/skills`, `.qwen/skills`, extension and bundled skills; no documented winner | links in `~/.qwen/skills` |
|
|
350
|
+
| 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 |
|
|
351
|
+
| skills | commandcode | `.commandcode/skills`, `.agents/skills`, `~/.commandcode/skills`, `~/.agents/skills`, extra locations, bundled; first wins | links in `~/.commandcode/skills` |
|
|
352
|
+
| 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` |
|
|
353
|
+
| 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 |
|
|
354
|
+
| skills | antigravity | the workspace's `.agents/skills`, `~/.gemini/antigravity-cli/skills`, plugin skills; no documented winner | links in `~/.gemini/antigravity-cli/skills` |
|
|
355
|
+
| 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>` |
|
|
356
|
+
| 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
357
|
| 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
358
|
| agents | codex | `$CODEX_HOME/agents`, `/etc/codex/agents`, `.codex/agents` (trusted repos only), read at config load | `$CODEX_HOME/agents`, written at install |
|
|
177
359
|
| 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 +363,16 @@ and drops the later ones.
|
|
|
181
363
|
| 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
364
|
| 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
365
|
| 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
|
-
|
|
|
366
|
+
| 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 |
|
|
367
|
+
| 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 |
|
|
368
|
+
| 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 |
|
|
369
|
+
| 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` |
|
|
370
|
+
| 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` |
|
|
371
|
+
| 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 |
|
|
372
|
+
| 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` |
|
|
373
|
+
| 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` |
|
|
374
|
+
| 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 |
|
|
375
|
+
| 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
376
|
|
|
186
377
|
Orca is a launcher, not a host. It starts omp with its own `--extension`. It
|
|
187
378
|
gives each Codex account its own `CODEX_HOME` under
|
|
@@ -224,6 +415,14 @@ three harness configurations while swapping the model inside one harness moved i
|
|
|
224
415
|
| 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
416
|
| 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
417
|
| 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) | - |
|
|
418
|
+
| 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 |
|
|
419
|
+
| droid | not named: tezgah writes no Droid subagent roles | `unverified`: tezgah has not mapped Droid's providers to a wire | - |
|
|
420
|
+
| commandcode | not named: tezgah writes no Command Code subagent roles | `unverified`: tezgah has not mapped Command Code's providers to a wire | - |
|
|
421
|
+
| copilot | not named: tezgah writes no Copilot subagent roles | `unverified`: tezgah has not mapped Copilot's providers to a wire | - |
|
|
422
|
+
| gemini | not named: tezgah writes no Gemini subagent roles | `unverified`: tezgah has not mapped Gemini's providers to a wire | - |
|
|
423
|
+
| antigravity | not named: tezgah writes no Antigravity subagent roles | `unverified`: tezgah has not mapped Antigravity's models to a wire | - |
|
|
424
|
+
| goose | not named: tezgah writes no Goose subagent roles | `unverified`: tezgah has not mapped Goose's providers to a wire | - |
|
|
425
|
+
| amp | not named: tezgah writes no Amp subagent roles | `unverified`: tezgah has not mapped Amp's models to a wire | - |
|
|
227
426
|
|
|
228
427
|
`unverified` is the point of the column rather than a gap in it. A wrong dialect
|
|
229
428
|
here is worse than a blank one: a reader would use it to explain a score, and the
|
|
@@ -234,15 +433,16 @@ and say which document it came from.
|
|
|
234
433
|
|
|
235
434
|
A skill read is observable only where the host gives a channel that does not cost
|
|
236
435
|
a process per read: Claude parses its transcript (`statusline.py::claude_used`),
|
|
237
|
-
opencode classifies in process (`hosts/opencode/plugins/tezgah.js
|
|
436
|
+
opencode classifies in process (`classify` in `hosts/opencode/plugins/tezgah.js`),
|
|
238
437
|
omp filters in its embedded runner before it asks python
|
|
239
|
-
(`hosts/omp/tezgah-hook.ts.in
|
|
240
|
-
|
|
241
|
-
|
|
438
|
+
(`skillRead` in `hosts/omp/tezgah-hook.ts.in`), and Amp's plugin does the same for a
|
|
439
|
+
`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
|
|
440
|
+
observable at that price. Their surfaces pass the five tool-use measures as
|
|
441
|
+
`observable`, and the two skill marks render dim (`info`, no glyph) instead of
|
|
242
442
|
claiming the skill was never opened (`hooks/tezgah_context.py::TOOL_USE_MEASURES`, and the
|
|
243
443
|
`observable` branch at `hooks/tezgah_context.py::health_segments`). Callers that pass `TOOL_USE_MEASURES`:
|
|
244
|
-
`statusline.py::observable`, `hosts/codex/hook.py::main`,
|
|
245
|
-
`hosts/dsh/statusline/lib/index.js
|
|
444
|
+
`statusline.py::observable`, `hosts/codex/hook.py::main`, `hosts/commandcode/hook.py::status`,
|
|
445
|
+
`OBSERVABLE` in `hosts/dsh/statusline/lib/index.js`. A kill switch is observable everywhere
|
|
246
446
|
and still renders `off` (`hooks/tezgah_context.py::health_segments`).
|
|
247
447
|
|
|
248
448
|
## Adding a host
|
|
@@ -252,7 +452,7 @@ In order, each step verified by the one below it:
|
|
|
252
452
|
1. `HOST_DIRS` and, when the CLI can exist without a config dir, `HOST_BINS` in
|
|
253
453
|
`hooks/tezgah_paths.py::HOST_DIRS`, `hooks/tezgah_paths.py::HOST_BINS`. These are shared by the installer, the report
|
|
254
454
|
and agent generation, so "is this host installed?" has one answer
|
|
255
|
-
(`hooks/tezgah_paths.py
|
|
455
|
+
(`hooks/tezgah_paths.py::host_installed`). Add the name to `ALL_HOSTS` in the report
|
|
256
456
|
order (`bin/tezgah-setup::ALL_HOSTS`).
|
|
257
457
|
2. The hook adapter(s) under `hosts/<host>/` (an `install_*`-independent
|
|
258
458
|
directory like `hosts/codex/hook.py`, `hosts/cursor/hook.py`) plus, for a
|
|
@@ -260,21 +460,37 @@ In order, each step verified by the one below it:
|
|
|
260
460
|
(`hosts/omp/tezgah-hook.ts.in` rendered at `hooks/tezgah_attest.py::omp_bridge`).
|
|
261
461
|
3. `install_<host>()` writing that host's files, registered in `INSTALLERS`
|
|
262
462
|
(`bin/tezgah-setup::INSTALLERS`), and `uninstall_<host>()` removing only
|
|
263
|
-
tezgah-managed links and blocks (`bin/tezgah-setup
|
|
463
|
+
tezgah-managed links and blocks, registered in `UNINSTALLERS` (`bin/tezgah-setup::UNINSTALLERS`).
|
|
264
464
|
4. `host_checks_<host>()` returning `(label, bool)` rows over what was actually
|
|
265
465
|
written, registered in `HOST_CHECKS` (`bin/tezgah-setup::HOST_CHECKS`); the
|
|
266
466
|
`--report` output is that list (`bin/tezgah-setup::report`). Give the row a home-qualified
|
|
267
467
|
label if the host's dir can be relocated (`host_checks_codex`, `bin/tezgah-setup::host_checks_codex`).
|
|
468
|
+
The first row is the host's "wired" row (`bin/tezgah-setup::hosts_row`), and a
|
|
469
|
+
row whose MISS means unarmed names a label `ARMING_ROWS` carries
|
|
470
|
+
(`bin/tezgah-setup::ARMING_ROWS`).
|
|
471
|
+
Every other table keyed by host grows with it, or the host crashes or drops
|
|
472
|
+
out there. `hooks/tezgah_stack.py::host_stack` raises on an unknown host, so
|
|
473
|
+
`tezgah-doctor --stack` fails. The other tables are
|
|
474
|
+
`hooks/tezgah_stack.py::tezgah_skill_dirs`, `hooks/tezgah_attest.py::registration`
|
|
475
|
+
and `CODE_TARGETS`, `bin/tezgah-setup::hook_entries` and
|
|
476
|
+
`bin/tezgah-setup::live_plan`. Without `live_plan` the live rows read
|
|
477
|
+
UNVERIFIED. The gate's control plane grows too: `HOOK_ENTRIES`, `HOOK_HOSTS`
|
|
478
|
+
and the shared files of `hooks/tezgah_gate.py::_registration`. The gate then
|
|
479
|
+
refuses a forged run of the new hook, and an edit of its registration, as it
|
|
480
|
+
refuses the others.
|
|
268
481
|
5. Decide the surface: a `statusLine` command, a TUI/widget plugin, or the
|
|
269
482
|
`systemMessage` fallback (`hosts/codex/hook.py:10-12`). Pass `observable` if
|
|
270
483
|
the host cannot see a skill read, and `idx_override` on any redraw that must
|
|
271
484
|
not fork git for a cosmetic glyph (`hooks/tezgah_context.py::health_segments`).
|
|
272
485
|
6. Tests, in two tiers: a per-host class in `tests/test_setup.py` pinning the
|
|
273
|
-
report rows and the written files (e.g. `OmpHost
|
|
274
|
-
`CodexHome
|
|
486
|
+
report rows and the written files (e.g. `tests/test_setup.py::OmpHost`,
|
|
487
|
+
`tests/test_setup.py::CodexHome`, `tests/test_setup.py::CursorMatcher`,
|
|
488
|
+
`tests/test_setup.py::DshStatusline`), and an
|
|
275
489
|
opt-in script under `tests/e2e_*.py` for the thing only a real host can prove
|
|
276
490
|
- not collected by `unittest discover`, it exits 0 with `SKIP` when the host
|
|
277
|
-
is absent (`tests/e2e_omp_statusline.py:13-16`).
|
|
491
|
+
is absent (`tests/e2e_omp_statusline.py:13-16`). A script that calls a model
|
|
492
|
+
also exits 0 with `SKIP` unless `TEZGAH_E2E_SPEND=1` is set
|
|
493
|
+
(`tests/e2e_qwen.py`, `tests/e2e_droid.py`).
|
|
278
494
|
|
|
279
495
|
## Host-specific limits worth knowing
|
|
280
496
|
|
|
@@ -287,6 +503,26 @@ In order, each step verified by the one below it:
|
|
|
287
503
|
`gate✗` appears only in the SessionStart/Stop status message.
|
|
288
504
|
- **cursor, dsh:** observed only if their prompt payload carries `transcript_path`
|
|
289
505
|
(unverified on a live host).
|
|
506
|
+
- **qwen:** unverified. Its prompt payload carries `transcript_path`. The counter
|
|
507
|
+
reads only the Claude, omp and Codex row shapes (`hooks/tezgah_context.py::_transcript_calls`).
|
|
508
|
+
No run has captured Qwen's transcript format yet, so a disarmed gate there likely counts 0.
|
|
509
|
+
- **droid:** unverified on a live host. Its transcript rows carry `message.content`
|
|
510
|
+
`tool_use` blocks with a `timestamp`, the Claude shape the counter reads, and
|
|
511
|
+
`GATED_TOOLS` names `Execute`, `Create` and `ApplyPatch`
|
|
512
|
+
(`hooks/tezgah_context.py::GATED_TOOLS`).
|
|
513
|
+
- **commandcode:** unavailable: Command Code has no prompt event, so nothing runs the
|
|
514
|
+
counter there.
|
|
515
|
+
- **copilot:** unavailable: Copilot drops a command hook's UserPromptSubmit output,
|
|
516
|
+
so no reminder carries the counter's warning there.
|
|
517
|
+
- **gemini:** unverified. BeforeAgent carries `transcript_path`, but Gemini's
|
|
518
|
+
session file is a format the counter does not read
|
|
519
|
+
(`hooks/tezgah_context.py::_transcript_calls`), so a disarmed gate there likely counts 0.
|
|
520
|
+
- **antigravity:** unavailable: its `transcript.jsonl` is not a format the counter
|
|
521
|
+
reads, so the hook does not hand it over. Only a switch armed mid-session shows.
|
|
522
|
+
- **goose:** unavailable: Goose ignores UserPromptSubmit output, so no reminder
|
|
523
|
+
carries the counter's warning there.
|
|
524
|
+
- **amp:** unavailable: the plugin hands the hook no transcript path, so only a
|
|
525
|
+
switch armed mid-session shows.
|
|
290
526
|
- **omp, opencode:** not applicable: the prompt hook and the gate live in one extension or
|
|
291
527
|
plugin process, so one cannot run without the other.
|
|
292
528
|
- Everywhere: a disarmed PreToolUse beside a working PostToolUse writes rows and is not
|
|
@@ -313,7 +549,7 @@ In order, each step verified by the one below it:
|
|
|
313
549
|
(`hooks/projects-auto-init.py:28-38`, `hosts/codex/hook.py::main`).
|
|
314
550
|
- **opencode**: `experimental.session.compacting` carries the builder's
|
|
315
551
|
post-compact block, so the contract survives the summary
|
|
316
|
-
(`hosts/opencode/plugins/tezgah.js
|
|
552
|
+
(the `experimental.session.compacting` hook of `Tezgah` in `hosts/opencode/plugins/tezgah.js`). It hands the builder the
|
|
317
553
|
session id. The builder then forgets the lessons and armed paragraphs
|
|
318
554
|
that session saw, and restates its pinned user constraints. The call
|
|
319
555
|
used to send `{}`, which keyed nothing on the session.
|
|
@@ -323,7 +559,7 @@ In order, each step verified by the one below it:
|
|
|
323
559
|
`hosts/omp/hook.py` for `{"event": "post_compact"}`; the block is sent with
|
|
324
560
|
`pi.sendMessage(..., {deliverAs: "nextTurn"})`, and the summary rides as
|
|
325
561
|
`compact_summary` so `remember_compaction` writes the compaction row
|
|
326
|
-
(`hosts/omp/tezgah-hook.ts.in
|
|
562
|
+
(the `session_compact` handler in `hosts/omp/tezgah-hook.ts.in`, `hosts/omp/hook.py::handle`).
|
|
327
563
|
Subagent sessions are skipped, because the post-compact block starts the
|
|
328
564
|
indexer.
|
|
329
565
|
- **cursor**: not re-injected - a limitation. Cursor's only compaction hook,
|
|
@@ -347,7 +583,7 @@ In order, each step verified by the one below it:
|
|
|
347
583
|
SubagentStop and Stop, and silently drops anything else - which is why
|
|
348
584
|
`configPath` names `hosts/dsh/hooks.json` and not the Claude manifest
|
|
349
585
|
(`bin/tezgah-setup::dsh_patch_block`). Its status route goes through `tezgah-status`
|
|
350
|
-
with the five tool-use measures (`hosts/dsh/statusline/lib/index.js
|
|
586
|
+
with the five tool-use measures (`OBSERVABLE` in `hosts/dsh/statusline/lib/index.js`), it
|
|
351
587
|
declares `TEZGAH_CALL_OUTCOME=none` because the bridge drops the outcome field
|
|
352
588
|
(`hosts/dsh/hooks.json:16-18`, `hooks/projects-posttooluse.py::main`), and its
|
|
353
589
|
statusline row lives in the web profile because the plugin injects the
|
|
@@ -357,9 +593,9 @@ In order, each step verified by the one below it:
|
|
|
357
593
|
`hosts/dsh/hooks.json` until the bridge carries outcomes.
|
|
358
594
|
- **opencode carries the untrusted-content control in the plugin**, not by
|
|
359
595
|
importing `hooks/tezgah_untrusted.py`, which a plugin cannot do in process: the
|
|
360
|
-
channel is decided in JS (`untrustedSource` `hosts/opencode/plugins/tezgah.js
|
|
361
|
-
the tier's argv reader
|
|
362
|
-
result the hook is handed (`labelResult`
|
|
596
|
+
channel is decided in JS (`untrustedSource` in `hosts/opencode/plugins/tezgah.js`,
|
|
597
|
+
the tier's argv reader `tierRead`), the label and the taint notice ride the
|
|
598
|
+
result the hook is handed (`labelResult`). A shared corpus in
|
|
363
599
|
`tests/test_opencode_plugin.py::OpenCodePlugin.test_the_classifier_agrees_with_the_python_half_on_a_shared_corpus` drives both halves over the same calls and
|
|
364
600
|
fails if their answers differ, so neither can move without the other.
|
|
365
601
|
- **omp carries both untrusted-content marks through the shared core.** Its
|
|
@@ -391,20 +627,20 @@ In order, each step verified by the one below it:
|
|
|
391
627
|
- **opencode asks the core for the rules it cannot port, and counts the ask.** The
|
|
392
628
|
task rule's phase and allowlist and the language rule's word list are answered by
|
|
393
629
|
`bin/tezgah-gate decide` (the recording verb; `check` is the dry run) rather than copied into JavaScript (`IDENT_CMD`
|
|
394
|
-
`hosts/opencode/plugins/tezgah.js
|
|
630
|
+
in `hosts/opencode/plugins/tezgah.js`, tested in the `tool.execute.before` hook) - one spawn per write
|
|
395
631
|
and per identifier-creating command. The delegation fails open by design: a missing
|
|
396
|
-
CLI, a non-zero exit, a broken pipe or a 10 s timeout refuses nothing
|
|
397
|
-
`
|
|
398
|
-
answer (`noteDelegation`
|
|
632
|
+
CLI, a non-zero exit, a broken pipe or a 10 s timeout refuses nothing
|
|
633
|
+
(`collect`). One `delegation` row now records the class when the core could not
|
|
634
|
+
answer (`noteDelegation`), so a silent fail-open is countable instead
|
|
399
635
|
of invisible.
|
|
400
636
|
- **opencode has no session-start hook**, so its always-on file must carry the
|
|
401
637
|
pointer every hook host appends, and the per-session index, agent and contract
|
|
402
638
|
refreshes ride the first `chat.message` instead (`bin/tezgah-setup::refresh_contract`,
|
|
403
|
-
`hosts/opencode/plugins/tezgah.js
|
|
639
|
+
the `oncePerSession` checks in the `chat.message` hook of `hosts/opencode/plugins/tezgah.js`). It denies the native `skill`
|
|
404
640
|
tool - the generated router replaces the injected skill list
|
|
405
641
|
(`bin/tezgah-setup::install_opencode`) - and `permission.ask` may never be emitted by a
|
|
406
642
|
given build, which is why `tool.execute.before` stays the enforcing half
|
|
407
|
-
(`hosts/opencode/plugins/tezgah.js
|
|
643
|
+
(the header comment of `hosts/opencode/plugins/tezgah.js`).
|
|
408
644
|
- **Cursor only runs a PreToolUse hook for the tool types its matcher names**, so
|
|
409
645
|
the matcher must list the write spellings or the gate's edit branches are
|
|
410
646
|
unreachable there (`hosts/cursor/hook.py:33-42`,
|
|
@@ -414,7 +650,7 @@ In order, each step verified by the one below it:
|
|
|
414
650
|
longer. A busy turn with every conditional rule armed measured 10288.
|
|
415
651
|
`fit()` cuts the tail to `CONTEXT_MAX` and marks the cut, so the reminder and
|
|
416
652
|
the first rules arrive (`hosts/cursor/hook.py::fit`, `hosts/cursor/hook.py::CONTEXT_MAX`).
|
|
417
|
-
- **Claude, dsh, omp, codex and
|
|
653
|
+
- **Claude, dsh, omp, codex, cursor, qwen, droid, commandcode, copilot, gemini, antigravity, goose and amp run the gate only for the tool names
|
|
418
654
|
their matcher carries**, so a name the PostToolUse side carries and the
|
|
419
655
|
PreToolUse side does not is recorded in the ledger and never refused.
|
|
420
656
|
`PowerShell` and `pwsh` are both `BASH_TOOLS` members
|
|
@@ -425,14 +661,65 @@ In order, each step verified by the one below it:
|
|
|
425
661
|
`hosts/cursor/hooks.json:8` - neither named them until 2026-09-19, so a
|
|
426
662
|
PowerShell call on those two reached no hook at all while the shared rules
|
|
427
663
|
already knew the spelling), and omp's `GATED` list
|
|
428
|
-
(`hosts/omp/tezgah-hook.ts.in
|
|
429
|
-
|
|
664
|
+
(`GATED` in `hosts/omp/tezgah-hook.ts.in`). Qwen Code has two shell tool ids,
|
|
665
|
+
`run_shell_command` and `monitor`, which `bin/tezgah-setup::QWEN_PRETOOL_MATCHER`
|
|
666
|
+
names beside `notebook_edit` and `mcp__.*`. Claude's and dsh's `Monitor` runs a
|
|
667
|
+
shell command too, so their matcher names it. Droid's shell is `Execute`, which `bin/tezgah-setup::DROID_PRETOOL_MATCHER`
|
|
668
|
+
names beside `Create`, `Edit`, `ApplyPatch`, `Task`, `Grep` and `mcp__.*`. The
|
|
669
|
+
adapter hands ApplyPatch's `input` to the gate as `patch`
|
|
670
|
+
(`hosts/droid/hook.py::gate_input`). Command Code's settings hooks see only
|
|
671
|
+
four tools and test the matcher against their display names (`SHELL`, `READ`,
|
|
672
|
+
`WRITE`, `EDIT`), so `bin/tezgah-setup::COMMANDCODE_PRETOOL_MATCHER` is
|
|
673
|
+
`shell|write|edit`. Its read, MCP, subagent and web tools never reach the
|
|
674
|
+
gate, and plan mode skips every hook. The adapter folds a shell call's `args`
|
|
675
|
+
into `command` and hands an edit's `old_value`/`new_value` over as
|
|
676
|
+
`old_string`/`new_string` (`hosts/commandcode/hook.py::gate_input`). Copilot's
|
|
677
|
+
PascalCase PreToolUse reports Claude tool names and anchors its matcher. So
|
|
678
|
+
`bin/tezgah-setup::COPILOT_PRETOOL_MATCHER` names `Bash`, `Write`, `Edit`,
|
|
679
|
+
`Agent`, `Task` and `Grep`, plus `.+-.+` for an MCP tool's `<server>-<tool>`.
|
|
680
|
+
The adapter maps the runtime spellings too (`bash`, `powershell`, `create`,
|
|
681
|
+
`edit`, `apply_patch`, `task`, `grep`, `rg`). An Edit that names no file but
|
|
682
|
+
carries a patch reaches the gate as `apply_patch`. `<server>-<tool>` becomes
|
|
683
|
+
`mcp__<server>__<tool>` for a server `mcp-config.json` names
|
|
684
|
+
(`hosts/copilot/hook.py::gate_name`). Gemini's BeforeTool matcher is a regex
|
|
685
|
+
over the tool name. `bin/tezgah-setup::GEMINI_PRETOOL_MATCHER` names the
|
|
686
|
+
shell, write, edit and grep tools, the subagent tools
|
|
687
|
+
(`codebase_investigator`, `generalist`, `invoke_agent`) and `mcp_.*`. A user's own subagents are not
|
|
688
|
+
named. `mcp_<server>_<tool>` marks no boundary between server and tool, so it
|
|
689
|
+
reaches the gate as `mcp__<server>_<tool>` (`hosts/gemini/hook.py::gate_name`).
|
|
690
|
+
Antigravity's matcher is a regex over the tool name, and
|
|
691
|
+
`bin/tezgah-setup::ANTIGRAVITY_PRETOOL_MATCHER` names `run_command`,
|
|
692
|
+
`write_to_file`, `replace_file_content`, `multi_replace_file_content`,
|
|
693
|
+
`invoke_subagent` and `grep_search`. The adapter renames their PascalCase
|
|
694
|
+
arguments into the gate's fields (`hosts/antigravity/hook.py::gate_input`).
|
|
695
|
+
`CommandLine` becomes `command`, `TargetFile` and `CodeContent` become
|
|
696
|
+
`file_path` and `content`. `TargetContent` and `ReplacementContent` become
|
|
697
|
+
`old_string` and `new_string`, and `ReplacementChunks` becomes `edits`. The
|
|
698
|
+
docs name no MCP tool spelling, so the matcher names no MCP tool. It names
|
|
699
|
+
neither `manage_task`'s `send_input` nor `schedule`.
|
|
700
|
+
Goose tests a matcher unanchored, so `bin/tezgah-setup::GOOSE_PRETOOL_MATCHER`
|
|
701
|
+
anchors `shell`, `write`, `edit` and `.+__.+`, an extension's
|
|
702
|
+
`<extension>__<tool>`, which reaches the gate as `mcp__<extension>__<tool>`
|
|
703
|
+
(`hosts/goose/hook.py::gate_name`). The adapter hands `write`'s and `edit`'s
|
|
704
|
+
`path` over as `file_path`, and `edit`'s `before`/`after` as
|
|
705
|
+
`old_string`/`new_string` (`hosts/goose/hook.py::gate_input`). Goose's own
|
|
706
|
+
`developer`, `analyze`, `summon` and `code_execution` tools other than these
|
|
707
|
+
three, and its subagent tool, are not named.
|
|
708
|
+
Amp's plugin sends the gate `Bash`, `shell_command`, `edit_file`,
|
|
709
|
+
`create_file`, `apply_patch` and `Task` (its `GATED` list) and an MCP effect
|
|
710
|
+
name. A shell call's text reaches the gate as `command`, from Amp's
|
|
711
|
+
`shellCommandFromToolCall` helper or the input's `cmd`
|
|
712
|
+
(`hosts/amp/hook.py::gate_input`). Read, search, web and `code_exec` calls
|
|
713
|
+
are not gated, and an MCP tool reached through `code_exec` or `tool_search`
|
|
714
|
+
is never seen.
|
|
715
|
+
A host this list does not cover is a host whose shell the gate cannot see.
|
|
430
716
|
- **An MCP effect reaches the gate on every host that can route it**. Claude
|
|
431
717
|
and dsh run it through a PreToolUse group of its own in `hooks/hooks.json` and
|
|
432
718
|
`hosts/dsh/hooks.json`. That group stays apart from the literal one, because
|
|
433
719
|
one regex character would end its exact-name dialect. Its pattern names the
|
|
434
720
|
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`)
|
|
721
|
+
tests the same pattern (`MCP_EFFECT`, `hosts/omp/tezgah-hook.ts.in`), as does
|
|
722
|
+
Amp's `tool.call` (`hosts/amp/tezgah-plugin.ts.in`). Both
|
|
436
723
|
hosts read a matcher as an unanchored JavaScript `RegExp`, and the pattern
|
|
437
724
|
uses no lookahead. A read-only MCP call, such as a code-graph query, spawns no
|
|
438
725
|
hook there. Codex's matcher carries `mcp__.*` (`hosts/codex/hooks.json`).
|
|
@@ -446,7 +733,7 @@ In order, each step verified by the one below it:
|
|
|
446
733
|
- **omp spawns the MCP `command` as one executable** and takes the rest in
|
|
447
734
|
`args`; the whole argv in `command` fails with ENOENT (`bin/tezgah-setup::install_omp`),
|
|
448
735
|
and `setStatus` strips ANSI, so only the widget path keeps the per-mark colors
|
|
449
|
-
(`hosts/omp/tezgah-hook.ts.in
|
|
736
|
+
(the comment above `draw` in `hosts/omp/tezgah-hook.ts.in`).
|
|
450
737
|
- **omp drops browser MCP servers while its native browser is on**: a server
|
|
451
738
|
named `playwright`/`puppeteer`/... or running `@playwright/mcp`/... never
|
|
452
739
|
connects there (omp 18.2.11 `/mcp` listed codegraph, mobile-mcp and tezgah
|
|
@@ -465,6 +752,14 @@ In order, each step verified by the one below it:
|
|
|
465
752
|
- `hosts/dsh/hooks.json`, `hosts/dsh/statusline/lib/index.js`
|
|
466
753
|
- `hosts/omp/hook.py`, `hosts/omp/tezgah-hook.ts.in`
|
|
467
754
|
- `hosts/opencode/plugins/tezgah.js`, `hosts/opencode/tui/tezgah-tui.tsx`
|
|
755
|
+
- `hosts/qwen/hook.py`, `tests/e2e_qwen.py` (the opt-in run of the real binary)
|
|
756
|
+
- `hosts/droid/hook.py`, `tests/e2e_droid.py` (the opt-in run of the real binary)
|
|
757
|
+
- `hosts/commandcode/hook.py`
|
|
758
|
+
- `hosts/copilot/hook.py`
|
|
759
|
+
- `hosts/gemini/hook.py`
|
|
760
|
+
- `hosts/antigravity/hook.py`
|
|
761
|
+
- `hosts/goose/hook.py`
|
|
762
|
+
- `hosts/amp/hook.py`, `hosts/amp/tezgah-plugin.ts.in`
|
|
468
763
|
- `statusline.py`, `bin/tezgah-status`, `hooks/tezgah_context.py`
|
|
469
764
|
- `hooks/tezgah_models.py` - the per-host model surface in `## The model wire`
|
|
470
765
|
- `hooks/tezgah_stack.py`, `bin/tezgah-doctor` `--stack` - the order in `## Precedence`
|