@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.
- package/CHANGELOG.md +333 -1
- package/MANIFEST +22 -0
- package/README.md +4 -3
- package/bin/tezgah-docs +1 -1
- package/bin/tezgah-research +39 -4
- package/bin/tezgah-setup +1229 -22
- package/bin/tezgah-task +55 -17
- package/docs/citations-baseline.json +1 -1
- package/docs/clarity-baseline.json +9 -3
- package/docs/contract.md +6 -3
- package/docs/environment.md +3 -2
- package/docs/evidence.md +81 -8
- package/docs/gate.md +6 -2
- package/docs/glossary.md +1 -1
- package/docs/hosts.md +297 -15
- package/docs/layers.md +1 -1
- package/docs/models.md +6 -0
- package/docs/operations.md +16 -5
- package/docs/research.md +36 -10
- package/docs/status-line.md +6 -0
- package/docs/testing.md +3 -1
- package/hooks/hooks.json +1 -1
- package/hooks/lang/en.json +11 -0
- package/hooks/lang/tr.json +11 -0
- package/hooks/projects-stop.py +2 -1
- package/hooks/tezgah_agents.py +27 -3
- package/hooks/tezgah_attest.py +79 -10
- package/hooks/tezgah_context.py +21 -8
- package/hooks/tezgah_gate.py +79 -23
- package/hooks/tezgah_integrity.py +212 -38
- package/hooks/tezgah_paths.py +43 -2
- package/hooks/tezgah_policy.py +35 -13
- package/hooks/tezgah_replay.py +8 -2
- package/hooks/tezgah_research.py +167 -24
- package/hooks/tezgah_stack.py +236 -1
- package/hooks/tezgah_task.py +2 -1
- package/hosts/amp/hook.py +217 -0
- package/hosts/amp/tezgah-plugin.ts.in +386 -0
- package/hosts/antigravity/hook.py +255 -0
- package/hosts/codex/hook.py +2 -1
- package/hosts/commandcode/hook.py +239 -0
- package/hosts/copilot/hook.py +284 -0
- package/hosts/cursor/hook.py +2 -1
- 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 +4 -1
- package/hosts/omp/tezgah-hook.ts.in +30 -7
- package/hosts/opencode/plugins/tezgah.js +2 -2
- package/hosts/qwen/hook.py +206 -0
- package/output-styles/tezgah.md +10 -5
- package/package.json +1 -1
- 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/research/SKILL.md +22 -9
- package/skills/tezgah-contract/SKILL.md +19 -6
- package/statusline.py +20 -9
- package/workflows/graph-review.js +14 -2
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,335 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [1.13.0] - 2026-10-29
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- **omp handed the Stop rule a message object, not the reply (plan 143).**
|
|
14
|
+
omp's `session_stop` carries `last_assistant_message` as the object its
|
|
15
|
+
`getLastAssistantMessage()` returns. The bridge passed it through, so the
|
|
16
|
+
rule judged `str(dict)`: one line, no list items, diluted prose.
|
|
17
|
+
- Every line-shaped class read a text with no lines: the list cap, the
|
|
18
|
+
residual sections, the question the reply ends on, the handed step. The
|
|
19
|
+
language check read a share that was not the reply's.
|
|
20
|
+
- Measured on the owner's evidence database: 1643 of 1656 omp `shape` rows
|
|
21
|
+
of 45 days carried `lines: 1`. Every other host wrote 209 multi-line of
|
|
22
|
+
231. Of three days' refusals, 24 were `reply language` over such a row.
|
|
23
|
+
- `stop_reason` reads every reply through `reply_prose` now, so no host can
|
|
24
|
+
repeat it. The omp bridge sends the text blocks.
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
- **The Stop rule knows the host's plan mode (plan 142).** Every Stop adapter
|
|
29
|
+
passes the host's `permission_mode` to the core now. omp's extension sends
|
|
30
|
+
`plan` from `ctx.sessionModes.getState()`. `plan` and Droid's `spec` are the
|
|
31
|
+
read-only ones.
|
|
32
|
+
- In such a turn the rule lets the approval question through. It used to
|
|
33
|
+
refuse it as a `needless ask` - the one question that mode allows.
|
|
34
|
+
- A first-person claim that a command or check ran, made in such a turn, is
|
|
35
|
+
the new `plan claim` class. Nothing could run, so the claim says what
|
|
36
|
+
cannot have happened. A check that passed anyway clears it, and a plan's
|
|
37
|
+
own acceptance line ("Done when all tests pass") is no claim.
|
|
38
|
+
- The new `handed step` class refuses a reply that tells the reader to run a
|
|
39
|
+
command. It asks only of a turn that ran commands of its own, outside plan
|
|
40
|
+
mode. An outward step, one no default may take, the owner's consent,
|
|
41
|
+
tezgah's own install and a `brew` step stay the user's. So does a line that
|
|
42
|
+
types its blocker, and an answer about how a command is run.
|
|
43
|
+
- Measured on the owner's transcripts: of 3307 user turns, 27 replies handed
|
|
44
|
+
back a command and no class refused any of them. The shipped shapes refuse
|
|
45
|
+
9 turns, 8 of them a step the session could have taken.
|
|
46
|
+
|
|
47
|
+
## [1.12.0] - 2026-10-29
|
|
48
|
+
|
|
49
|
+
### Added
|
|
50
|
+
|
|
51
|
+
- **Amp is a host (`amp`, plan 140).** `tezgah-setup --install --hosts amp`
|
|
52
|
+
writes one TypeScript plugin, `tezgah.ts`, into Amp's system plugin dir.
|
|
53
|
+
That dir is `$XDG_CONFIG_HOME/amp/plugins/`, else `~/.config/amp/plugins/`.
|
|
54
|
+
The install renders it from `hosts/amp/tezgah-plugin.ts.in`. Amp loads every
|
|
55
|
+
`.ts` there, and a running Amp needs `plugins: reload`. The install links the
|
|
56
|
+
skills into `~/.config/amp/skills` and writes the always-on block into
|
|
57
|
+
`~/.config/amp/AGENTS.md`. It sets Amp's two commit trailers
|
|
58
|
+
(`amp.git.commit.coauthor.enabled`, `amp.git.commit.ampThread.enabled`) to
|
|
59
|
+
false in `settings.json` unless the user set them. It writes that file only
|
|
60
|
+
while it is plain JSON with no `settings.jsonc` beside it. The uninstall
|
|
61
|
+
removes the plugin only when it is tezgah's render. It drops a trailer key
|
|
62
|
+
only while that key is still false.
|
|
63
|
+
- The plugin spawns `hosts/amp/hook.py` without blocking. One plugin process
|
|
64
|
+
serves many threads, so it keys its state by thread id. A dead, slow or
|
|
65
|
+
crashing hook allows every call, and the plugin tells each thread once.
|
|
66
|
+
- `tool.call` refuses with `{action: "reject-and-continue"}`. It asks the gate
|
|
67
|
+
for `Bash`, `shell_command`, the edit tools, `Task` and MCP effect names. A
|
|
68
|
+
shell call's text reaches the gate as `command`, also when Amp sends `cmd`.
|
|
69
|
+
- A tool result counts as passed only with an integer `exitCode` on its
|
|
70
|
+
output object. `error` and `cancelled` are failures. A bare `done` shell
|
|
71
|
+
result is a run, never a pass.
|
|
72
|
+
- `agent.end` judges a `done` turn and continues it with `[tezgah:stop]
|
|
73
|
+
<reason>`. The plugin never sends that prompt as the user's. So it writes no
|
|
74
|
+
turn row, grants nothing and cannot reset the re-ask budget. A thread with
|
|
75
|
+
a parent thread is a subagent. It gets the subagent brief, and its end is a
|
|
76
|
+
record-only row.
|
|
77
|
+
- The session brief rides the thread's first `agent.start`. The status line
|
|
78
|
+
goes on Amp's experimental status item for the active thread.
|
|
79
|
+
- `--report` has Amp rows. `--report --live` loads the plugin with node and
|
|
80
|
+
drives its `tool.call` and `tool.result`. The row stays UNVERIFIED, since
|
|
81
|
+
Amp's own loader did not run it. `tezgah-doctor --stack` reads Amp's
|
|
82
|
+
guidance files, skills, plugins and `amp.mcpServers`. `read_web_page` is a
|
|
83
|
+
web channel for the untrusted label. README and docs name all fourteen
|
|
84
|
+
hosts.
|
|
85
|
+
- **Goose is a host (`goose`, plan 139 slice 5).** `tezgah-setup --install
|
|
86
|
+
--hosts goose` writes tezgah's own plugin dir, `~/.agents/plugins/tezgah/`.
|
|
87
|
+
It holds `plugin.json`, `hooks/hooks.json` and `skills/` links, and it is
|
|
88
|
+
wholly tezgah's. Goose loads the skills as `tezgah:<skill>`. The timeouts are in seconds: Stop 30, the
|
|
89
|
+
rest 5. `on_failure` stays at Goose's default `allow`, so the gate fails open
|
|
90
|
+
on a hook fault. The always-on block goes in `~/.config/goose/.goosehints`.
|
|
91
|
+
Goose adds no co-author trailer and names no setting for one, so there is
|
|
92
|
+
nothing to turn off. The uninstall deletes the plugin dir and the block and
|
|
93
|
+
leaves the rest of `~/.agents` alone.
|
|
94
|
+
- `hosts/goose/hook.py` handles SessionStart (attestation only),
|
|
95
|
+
UserPromptSubmit (the turn row only), PreToolUse, PostToolUse,
|
|
96
|
+
PostToolUseFailure and Stop. The event is the payload's `event`, the cwd its
|
|
97
|
+
`working_dir`, else the hook's own. `shell`, `write` and `edit` map to Bash,
|
|
98
|
+
Write and Edit, with `path` as `file_path` and `before`/`after` as
|
|
99
|
+
`old_string`/`new_string`. `<extension>__<tool>` becomes
|
|
100
|
+
`mcp__<extension>__<tool>`.
|
|
101
|
+
- PreToolUse and Stop block with `{"decision": "block", "reason": ...}` on
|
|
102
|
+
stdout. The outcome is the event: PostToolUse is a pass, PostToolUseFailure
|
|
103
|
+
a failure. Stop judges `last_assistant_message`, or writes a
|
|
104
|
+
`reply_unread` row. With no host block count, the turn rows bound the
|
|
105
|
+
re-asks.
|
|
106
|
+
- `--report` has Goose rows, including one for `disabledPlugins`. `--report
|
|
107
|
+
--live` probes the hook command with Goose's payload and block shape.
|
|
108
|
+
`tezgah-doctor --stack` reads Goose's hints, skills, plugins and the
|
|
109
|
+
`extensions:` map of `config.yaml`. No run has shown the binary obeying the
|
|
110
|
+
block. README and docs/layers.md now name all thirteen hosts.
|
|
111
|
+
- **Antigravity CLI is a host (`antigravity`, plan 139 slice 4).**
|
|
112
|
+
`tezgah-setup --install --hosts antigravity` writes tezgah's hook set under
|
|
113
|
+
the `tezgah` name of `~/.gemini/config/hooks.json`. The user's other names
|
|
114
|
+
stay. The Antigravity App and IDE load that file too. The timeouts are in
|
|
115
|
+
seconds: Stop 30, the rest 5. The status line goes in
|
|
116
|
+
`~/.gemini/antigravity-cli/settings.json` while it is plain JSON, and the skill
|
|
117
|
+
links go in `antigravity-cli/skills`. Antigravity adds no co-author
|
|
118
|
+
trailer of its own, so there is nothing to turn off. The always-on block is
|
|
119
|
+
the `~/.gemini/GEMINI.md` one Gemini CLI shares. An uninstall now keeps it only
|
|
120
|
+
while another armed host reads the same file, so with `GEMINI_CLI_HOME` set
|
|
121
|
+
the two blocks come and go apart.
|
|
122
|
+
- `hosts/antigravity/hook.py` handles SessionStart, PreInvocation,
|
|
123
|
+
PreToolUse, PostToolUse and Stop. The payload names no event, so each
|
|
124
|
+
command passes it as its first argument. `conversationId` is the session
|
|
125
|
+
and `workspacePaths[0]` the cwd. The adapter renames the PascalCase tool
|
|
126
|
+
arguments into the gate's fields: `CommandLine`, `TargetFile`, `CodeContent`,
|
|
127
|
+
`TargetContent`/`ReplacementContent` and `ReplacementChunks`.
|
|
128
|
+
- PreToolUse denies with a top-level `decision: deny`. An allowed call
|
|
129
|
+
answers `decision: allow`, since Antigravity requires the field. PostToolUse reads
|
|
130
|
+
the outcome off `error`: empty is a pass, anything else a failure. The first
|
|
131
|
+
invocation of a turn writes the turn row and carries the reminder as an
|
|
132
|
+
`ephemeralMessage`. A loop that a refusal re-entered writes no turn row, so
|
|
133
|
+
the refusal budget holds.
|
|
134
|
+
- Stop reads the reply from `transcriptPath`, or writes a `reply_unread` row.
|
|
135
|
+
It judges only an idle `model_stop`, and `decision: continue` sends the
|
|
136
|
+
model back into the loop with the reason.
|
|
137
|
+
- `--report` has Antigravity rows, including one for `enabled: false` on the
|
|
138
|
+
`tezgah` name. `--report --live` probes the hook command with Antigravity's
|
|
139
|
+
camelCase payload and deny shape. No run has shown the binary obeying the
|
|
140
|
+
deny.
|
|
141
|
+
- **Gemini CLI is a host (`gemini`, plan 139 slice 3).** `tezgah-setup
|
|
142
|
+
--install --hosts gemini` writes tezgah's own extension dir,
|
|
143
|
+
`~/.gemini/extensions/tezgah/`. It holds `gemini-extension.json`,
|
|
144
|
+
`hooks/hooks.json` and links to the skills.
|
|
145
|
+
`GEMINI_CLI_HOME` moves `~/.gemini` under it. The timeouts are in
|
|
146
|
+
milliseconds: AfterAgent 30000, the rest 5000. settings.json is never
|
|
147
|
+
written, since it is JSON with comments and holds the user's keys. Gemini's
|
|
148
|
+
default prompt adds no co-author trailer and no setting names one. The
|
|
149
|
+
always-on block goes in `~/.gemini/GEMINI.md`, which Antigravity reads too.
|
|
150
|
+
`--uninstall` deletes the extension dir and strips the block only while no
|
|
151
|
+
other host that reads it stays armed.
|
|
152
|
+
- `hosts/gemini/hook.py` handles SessionStart, BeforeAgent, BeforeTool,
|
|
153
|
+
AfterTool and AfterAgent. BeforeAgent writes the turn row and carries the
|
|
154
|
+
per-turn reminder. BeforeTool maps `run_shell_command`, `write_file`,
|
|
155
|
+
`replace`, the grep tools and the subagent tools into the gate's names, and
|
|
156
|
+
`mcp_<server>_<tool>` into `mcp__<server>_<tool>`. It denies with a
|
|
157
|
+
top-level `decision: deny` on exit 0.
|
|
158
|
+
- AfterTool reads the outcome off `tool_response`: an `error` is a failure,
|
|
159
|
+
and a shell result's `Exit Code: N` names a non-zero code. A shell run that
|
|
160
|
+
printed its `Output:` with no exit-code line is a pass, since Gemini writes
|
|
161
|
+
that line only for a non-zero code. AfterAgent judges `prompt_response` and
|
|
162
|
+
denies with `decision: deny`. Gemini sends the reason back through
|
|
163
|
+
BeforeAgent as the next prompt, and that turn row does not reset the
|
|
164
|
+
refusal budget. The status segment rides `systemMessage`.
|
|
165
|
+
- `--report` has Gemini rows, including one for `hooksConfig.enabled: false`
|
|
166
|
+
in settings.json, which runs no hook at all. `--report --live` probes the
|
|
167
|
+
hook command with Gemini's own payload and deny shape. No run has shown the
|
|
168
|
+
binary loading the extension or obeying the deny.
|
|
169
|
+
- **GitHub Copilot CLI is a host (`copilot`, plan 139 slice 2).** `tezgah-setup
|
|
170
|
+
--install --hosts copilot` writes `$COPILOT_HOME/hooks/tezgah.json`, a hooks
|
|
171
|
+
file that is wholly tezgah's. `COPILOT_HOME` defaults to `~/.copilot`. The
|
|
172
|
+
timeouts are in seconds: PreToolUse 10, Stop and SubagentStop 30, the rest 5.
|
|
173
|
+
The inline `hooks` key of settings.json is never used, since one bad item
|
|
174
|
+
there rejects every hook in it. The install sets two keys in settings.json
|
|
175
|
+
while it is plain JSON. The status line runs `tezgah-statusline --copilot`.
|
|
176
|
+
`includeCoAuthoredBy` is `false`, so the agent's commits carry no co-author
|
|
177
|
+
trailer. A value the user set stays. The install links the skills into
|
|
178
|
+
`$COPILOT_HOME/skills` and writes the always-on block in
|
|
179
|
+
`copilot-instructions.md`. `--uninstall` deletes tezgah.json and takes back
|
|
180
|
+
only tezgah's settings keys, links and block.
|
|
181
|
+
- `hosts/copilot/hook.py` uses the PascalCase events, so payloads carry
|
|
182
|
+
snake_case fields and Claude tool names. It maps the runtime spellings too.
|
|
183
|
+
An Edit that names no file but carries a patch reaches the gate as
|
|
184
|
+
`apply_patch`. An MCP tool's `<server>-<tool>` becomes
|
|
185
|
+
`mcp__<server>__<tool>` for a server `mcp-config.json` names. It denies
|
|
186
|
+
with a top-level `permissionDecision: deny` on exit 0.
|
|
187
|
+
- A PreToolUse hook that times out lets the call through, by Copilot's own
|
|
188
|
+
rule. One that crashes denies the call. A repo's `.claude/settings.json`
|
|
189
|
+
hooks run there too.
|
|
190
|
+
- PostToolUse records a pass and PostToolUseFailure a failure. A shell
|
|
191
|
+
result's `exit code N` overrides either. Stop carries no reply, so the hook
|
|
192
|
+
reads the last `assistant.message` from `transcript_path`. A turn end with
|
|
193
|
+
none there becomes a `reply_unread` row. Copilot drops a command hook's
|
|
194
|
+
UserPromptSubmit output, so there is no per-turn reminder. The event still
|
|
195
|
+
writes the turn row. subagentStart gives a delegate the brief. A
|
|
196
|
+
SubagentStop only records its verdict.
|
|
197
|
+
- `--report` has Copilot rows, and `--report --live` probes the hook command
|
|
198
|
+
with Copilot's own payload and deny shape. No run has shown the binary
|
|
199
|
+
obeying the deny.
|
|
200
|
+
- **Command Code is a host (`commandcode`, plan 139 slice 1).** `tezgah-setup
|
|
201
|
+
--install --hosts commandcode` merges tezgah's hook groups into the `hooks`
|
|
202
|
+
key of `~/.commandcode/settings.json` beside the user's. Their timeouts are in
|
|
203
|
+
seconds. The same write sets `attribution.commit` to `""`, so commits carry no
|
|
204
|
+
`CommandCodeBot` co-author trailer. A value the user set stays. The install
|
|
205
|
+
links the skills into `~/.commandcode/skills`. It writes the always-on block
|
|
206
|
+
in `~/.commandcode/AGENTS.md`. `--uninstall` takes back only tezgah's groups,
|
|
207
|
+
links and block. It drops the attribution key only while it holds what the
|
|
208
|
+
install wrote.
|
|
209
|
+
- Command Code's settings hooks see only shell, read, write and edit. So the
|
|
210
|
+
PreToolUse matcher is `shell|write|edit`. The gate there never sees read,
|
|
211
|
+
MCP, subagent or web calls, and plan mode skips every hook.
|
|
212
|
+
- `hosts/commandcode/hook.py` maps `shell_command`, `write_file` and
|
|
213
|
+
`edit_file` onto the gate. It folds a shell call's `args` into `command`.
|
|
214
|
+
It hands `old_value`/`new_value` over as `old_string`/`new_string`. It
|
|
215
|
+
denies with `permissionDecision: deny` on exit 0. It reads a shell call's
|
|
216
|
+
outcome off an `exit code N` in its result. A result with no code counts
|
|
217
|
+
as a run, never a pass.
|
|
218
|
+
- Stop carries no reply, so the hook reads it from `transcript_path`. A turn
|
|
219
|
+
end with none there becomes a `reply_unread` row. The status segment rides
|
|
220
|
+
`systemMessage` at SessionStart and Stop. There is no prompt event, so no
|
|
221
|
+
per-turn reminder.
|
|
222
|
+
- `--report` has Command Code rows, and `--report --live` probes the hook
|
|
223
|
+
command with Command Code's own payload. No run has shown the binary
|
|
224
|
+
obeying the deny.
|
|
225
|
+
- **Factory Droid is a host (`droid`, plan 138 wave 1).** `tezgah-setup --install
|
|
226
|
+
--hosts droid` merges tezgah's hook groups into `~/.factory/hooks.json` beside
|
|
227
|
+
the user's. Their timeouts are in seconds, as Droid reads them. The install
|
|
228
|
+
also links the skills into `~/.factory/skills` beside the user's own links.
|
|
229
|
+
It writes the always-on block in `~/.factory/AGENTS.md`. It sets the
|
|
230
|
+
`statusLine` command and `includeCoAuthoredByDroid: false` in `settings.json`.
|
|
231
|
+
The install edits a settings.json with comments in place and keeps its
|
|
232
|
+
comments (plan 141). `--uninstall` takes back only tezgah's groups, links and keys.
|
|
233
|
+
- Droid reads the `hooks` key of settings.json only while hooks.json is
|
|
234
|
+
absent. So the install creates no hooks.json beside such a key, and the
|
|
235
|
+
report row says why. Then settings.json stays untouched.
|
|
236
|
+
- `hosts/droid/hook.py` maps Droid's tool ids (`Execute`, `Create`,
|
|
237
|
+
`ApplyPatch`, `FetchUrl`) onto the gate and hands ApplyPatch's `input` over
|
|
238
|
+
as `patch`. It denies with `permissionDecision: deny` on exit 0 and reads a
|
|
239
|
+
shell call's outcome off its `[Process exited with code N]` line.
|
|
240
|
+
- Droid's Stop carries no reply, so the hook reads the reply from
|
|
241
|
+
`transcript_path`. A turn end whose reply is not there yet becomes a
|
|
242
|
+
`reply_unread` row and is not judged. SubagentStop records `task_result`
|
|
243
|
+
as a `subagent_end` row.
|
|
244
|
+
- `--report` has Droid rows, and `--report --live` probes the hook command
|
|
245
|
+
with Droid's own payload. `tests/e2e_droid.py` runs the real binary only
|
|
246
|
+
under `TEZGAH_E2E_SPEND=1`, because it spends a model call.
|
|
247
|
+
|
|
248
|
+
- **Qwen Code is a host (`qwen`, plan 138 wave 1).** `tezgah-setup --install
|
|
249
|
+
--hosts qwen` merges tezgah's hook groups into `~/.qwen/settings.json` beside
|
|
250
|
+
Orca's. Their timeouts are in milliseconds, as Qwen reads them. The install
|
|
251
|
+
also adds the `ui.statusLine` command, the skill links and the always-on
|
|
252
|
+
block in `~/.qwen/QWEN.md`. `--uninstall` takes back only tezgah's groups and keys.
|
|
253
|
+
- `hosts/qwen/hook.py` maps Qwen's tool ids (`run_shell_command`,
|
|
254
|
+
`write_file`, `edit`, `agent`, `grep_search`) onto the gate. It denies with
|
|
255
|
+
`permissionDecision: deny` on exit 0, blocks Stop on
|
|
256
|
+
`last_assistant_message`, briefs a subagent at SubagentStart and records
|
|
257
|
+
its end at SubagentStop.
|
|
258
|
+
- `--report` has Qwen rows, and `--report --live` probes the hook command
|
|
259
|
+
with Qwen's own payload. `tests/e2e_qwen.py` runs the real binary only
|
|
260
|
+
under `TEZGAH_E2E_SPEND=1`, because it spends a model call.
|
|
261
|
+
- `tezgah-doctor --stack` and the session attestation know the host. An
|
|
262
|
+
unknown host no longer crashes the attestation.
|
|
263
|
+
- A host-file backup is now owner-only, and a rewritten host config keeps
|
|
264
|
+
its file mode, because Qwen's settings.json holds provider keys.
|
|
265
|
+
|
|
266
|
+
- **Review rules taken from google/elephant-goldfish-model (plan 137).** The
|
|
267
|
+
owner chose them from research line `egm-lessons`. No EGM file ships:
|
|
268
|
+
- `tezgah-task review NNN <r> approve` refuses a plan with no review slice
|
|
269
|
+
record. A host with no slice journal passes `--no-slice "<why>"`, and the
|
|
270
|
+
reason lands on the review line (`bin/tezgah-task`).
|
|
271
|
+
- The contract says that no fresh subagent means no review verdict. It also
|
|
272
|
+
says the author contests a finding only with a file:line and never lowers
|
|
273
|
+
a severity (`hooks/tezgah_policy.py`).
|
|
274
|
+
- tezgah-reviewer has a plan-readiness mode for a plan file. A design choice
|
|
275
|
+
left to the implementer is a major finding, and so is a section it cannot
|
|
276
|
+
paraphrase. plan-add runs it before `phase implementation`
|
|
277
|
+
(`hooks/tezgah_agents.py`).
|
|
278
|
+
- graph-review merges findings with the same file, line and title before its
|
|
279
|
+
verification cap, and reports `caps.deduplicated`.
|
|
280
|
+
- `tests/test_links.py` resolves every relative markdown link the repository
|
|
281
|
+
owns.
|
|
282
|
+
|
|
283
|
+
### Changed
|
|
284
|
+
|
|
285
|
+
- **The gate refuses a credit verb with "with" or "using" too.** The attribution
|
|
286
|
+
rule read `written by` and `built by`, so `Drafted with Claude` passed. The
|
|
287
|
+
credit verbs now include `drafted`, `produced` and `composed`, and each takes
|
|
288
|
+
`by`, `with` or `using` before a model or vendor name. Prose such as
|
|
289
|
+
`drafted with the owner` still passes (`hooks/tezgah_gate.py::_CREDIT`, plan
|
|
290
|
+
137).
|
|
291
|
+
|
|
292
|
+
- **A release tag must sit on main.** A new `on-main` job in `release.yml`
|
|
293
|
+
fails the run on a tag off `origin/main`. Then no job builds or uploads
|
|
294
|
+
anything (plan 137).
|
|
295
|
+
|
|
296
|
+
- **An owner-only choice waits for the owner.** Class F now names three more
|
|
297
|
+
steps. One is picking a variant a research, analysis or review recommends.
|
|
298
|
+
The others are a way around a tezgah refusal and deciding about another
|
|
299
|
+
session's work. The core
|
|
300
|
+
Ask policy, the per-turn reminder and the ask warning carry them too. The Stop
|
|
301
|
+
rule's `needless ask` lets a question that asks the owner to pick between
|
|
302
|
+
options through (`OWNER_CHOICE`, `hooks/tezgah_integrity.py`). A worker returns
|
|
303
|
+
such a question as `ESCALATE: owner`, and the orchestrator relays it to the
|
|
304
|
+
user instead of answering it (plan 136).
|
|
305
|
+
|
|
306
|
+
- **A research line records the owner's pick apart from its own.** A rules-9
|
|
307
|
+
line writes its pick as `recommended:` in `decision.md`. A `chosen:` line
|
|
308
|
+
needs the owner's words, which `tezgah-research choose <slug> <decision>
|
|
309
|
+
<variant> --ack "<words>"` writes. The line's rules judge its own pick, never
|
|
310
|
+
the owner's, so an owner may take a dominated variant. `conclude` says when a
|
|
311
|
+
recommendation still waits for the owner (`hooks/tezgah_research.py`).
|
|
312
|
+
|
|
313
|
+
- **A line in flight no longer blocks `init --allow-open`.** A line whose only
|
|
314
|
+
`check` errors are pending verdicts is in flight, not broken. Both `init`
|
|
315
|
+
refusals now say that another session's line goes to the owner.
|
|
316
|
+
|
|
317
|
+
### Fixed
|
|
318
|
+
|
|
319
|
+
- **Three new hosts no longer record a check that did not pass as a pass.**
|
|
320
|
+
Antigravity's PostToolUse has no output. A command that went to the
|
|
321
|
+
background also ends with an empty `error`. So the hook records each
|
|
322
|
+
Antigravity shell call as ran, never a pass, and Antigravity can never show
|
|
323
|
+
a passing check. A Copilot shell success with no `exit code N` text (a
|
|
324
|
+
background launch) is ran, never a pass. A Gemini result with a `Signal:`
|
|
325
|
+
line (a runner killed by a signal) is a failure.
|
|
326
|
+
- **The gate guards Gemini's and Goose's hook-off keys.** These are Gemini's
|
|
327
|
+
`hooksConfig` and Goose's `disabledPlugins`. Claude's `disableAllHooks` had
|
|
328
|
+
the same guard.
|
|
329
|
+
- **The Command Code uninstall removes tezgah's `attribution.commit: ""`
|
|
330
|
+
beside the user's other attribution keys**, not only when it stood alone.
|
|
331
|
+
|
|
332
|
+
- **A hidden research dir is no longer invisible.** A dot dir under
|
|
333
|
+
`.tezgah/research/` is an error in `status` and in `check` with no slug. The
|
|
334
|
+
gate refuses a write there (`research_hidden_reason`, `hooks/tezgah_gate.py`).
|
|
335
|
+
A session had run a whole line under `.tezgah/research/.staging/` after an
|
|
336
|
+
`init` refusal.
|
|
337
|
+
|
|
9
338
|
## [1.11.0] - 2026-10-23
|
|
10
339
|
|
|
11
340
|
### Added
|
|
@@ -1299,7 +1628,10 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
1299
1628
|
the copy could not find `hooks/` and died on import. It now links into the
|
|
1300
1629
|
installed tree, and also installs the command as `tezgah`.
|
|
1301
1630
|
|
|
1302
|
-
[Unreleased]: https://github.com/r1z4x/tezgah/compare/v1.
|
|
1631
|
+
[Unreleased]: https://github.com/r1z4x/tezgah/compare/v1.13.0...HEAD
|
|
1632
|
+
[1.13.0]: https://github.com/r1z4x/tezgah/releases/tag/v1.13.0
|
|
1633
|
+
[1.12.0]: https://github.com/r1z4x/tezgah/releases/tag/v1.12.0
|
|
1634
|
+
[1.11.0]: https://github.com/r1z4x/tezgah/releases/tag/v1.11.0
|
|
1303
1635
|
[1.10.1]: https://github.com/r1z4x/tezgah/releases/tag/v1.10.1
|
|
1304
1636
|
[1.10.0]: https://github.com/r1z4x/tezgah/releases/tag/v1.10.0
|
|
1305
1637
|
[1.9.2]: https://github.com/r1z4x/tezgah/releases/tag/v1.9.2
|
package/MANIFEST
CHANGED
|
@@ -161,19 +161,28 @@ hooks/tezgah_taste.py
|
|
|
161
161
|
hooks/tezgah_taste_ledger.py
|
|
162
162
|
hooks/tezgah_untrusted.py
|
|
163
163
|
hooks/tezgah_update.py
|
|
164
|
+
hosts/amp/hook.py
|
|
165
|
+
hosts/amp/tezgah-plugin.ts.in
|
|
166
|
+
hosts/antigravity/hook.py
|
|
164
167
|
hosts/codex/hook.py
|
|
165
168
|
hosts/codex/hooks.json
|
|
169
|
+
hosts/commandcode/hook.py
|
|
170
|
+
hosts/copilot/hook.py
|
|
166
171
|
hosts/cursor/hook.py
|
|
167
172
|
hosts/cursor/hooks.json
|
|
173
|
+
hosts/droid/hook.py
|
|
168
174
|
hosts/dsh/hooks.json
|
|
169
175
|
hosts/dsh/statusline/lib/client.js
|
|
170
176
|
hosts/dsh/statusline/lib/index.js
|
|
171
177
|
hosts/dsh/statusline/package.json
|
|
178
|
+
hosts/gemini/hook.py
|
|
179
|
+
hosts/goose/hook.py
|
|
172
180
|
hosts/omp/hook.py
|
|
173
181
|
hosts/omp/tezgah-hook.ts.in
|
|
174
182
|
hosts/opencode/plugins/tezgah.js
|
|
175
183
|
hosts/opencode/tui/cli/tui.tsx
|
|
176
184
|
hosts/opencode/tui/tezgah-tui.tsx
|
|
185
|
+
hosts/qwen/hook.py
|
|
177
186
|
output-styles/tezgah.md
|
|
178
187
|
package.json
|
|
179
188
|
packaging/build.sh
|
|
@@ -830,6 +839,7 @@ skills/rl-env/rl-env-from-description/references/verifiers.md
|
|
|
830
839
|
skills/tezgah-contract/EVALS.md
|
|
831
840
|
skills/tezgah-contract/SKILL.md
|
|
832
841
|
statusline.py
|
|
842
|
+
tests/_amp_plugin_harness.mjs
|
|
833
843
|
tests/_fuzz_shell_reader.mjs
|
|
834
844
|
tests/_mcp_stdio.py
|
|
835
845
|
tests/_omp_extension_harness.mjs
|
|
@@ -849,10 +859,12 @@ tests/e2e_analyze_mobile.py
|
|
|
849
859
|
tests/e2e_analyze_web.py
|
|
850
860
|
tests/e2e_analyze_wiring.py
|
|
851
861
|
tests/e2e_docker_cycle.py
|
|
862
|
+
tests/e2e_droid.py
|
|
852
863
|
tests/e2e_dsh_statusline.py
|
|
853
864
|
tests/e2e_omp_statusline.py
|
|
854
865
|
tests/e2e_packaged_install.py
|
|
855
866
|
tests/e2e_plan_flow.py
|
|
867
|
+
tests/e2e_qwen.py
|
|
856
868
|
tests/e2e_tezgah_mcp.py
|
|
857
869
|
tests/fixtures/rules8-known-truth/bodies/arxiv-1.xml
|
|
858
870
|
tests/fixtures/rules8-known-truth/bodies/arxiv-10.xml
|
|
@@ -906,6 +918,9 @@ tests/stop_spec_eval.py
|
|
|
906
918
|
tests/support.py
|
|
907
919
|
tests/test_agents.py
|
|
908
920
|
tests/test_ai_research_library.py
|
|
921
|
+
tests/test_amp_hook.py
|
|
922
|
+
tests/test_amp_plugin.py
|
|
923
|
+
tests/test_antigravity_hook.py
|
|
909
924
|
tests/test_apps_registry.py
|
|
910
925
|
tests/test_attest.py
|
|
911
926
|
tests/test_bash_agreement.py
|
|
@@ -916,12 +931,14 @@ tests/test_claude_adapters.py
|
|
|
916
931
|
tests/test_claude_registration.py
|
|
917
932
|
tests/test_codegen.py
|
|
918
933
|
tests/test_codex_hook.py
|
|
934
|
+
tests/test_commandcode_hook.py
|
|
919
935
|
tests/test_components.py
|
|
920
936
|
tests/test_constraint_pinning.py
|
|
921
937
|
tests/test_consult_arena.py
|
|
922
938
|
tests/test_context.py
|
|
923
939
|
tests/test_context_diet.py
|
|
924
940
|
tests/test_control_plane.py
|
|
941
|
+
tests/test_copilot_hook.py
|
|
925
942
|
tests/test_cursor_cap.py
|
|
926
943
|
tests/test_cursor_hook.py
|
|
927
944
|
tests/test_decisions.py
|
|
@@ -929,6 +946,7 @@ tests/test_design_cli.py
|
|
|
929
946
|
tests/test_docs.py
|
|
930
947
|
tests/test_docs_router.py
|
|
931
948
|
tests/test_doctor.py
|
|
949
|
+
tests/test_droid_hook.py
|
|
932
950
|
tests/test_dsh_hooks.py
|
|
933
951
|
tests/test_dsh_launcher.py
|
|
934
952
|
tests/test_dsh_statusline.py
|
|
@@ -940,6 +958,8 @@ tests/test_extension.py
|
|
|
940
958
|
tests/test_fuzz_shell.py
|
|
941
959
|
tests/test_gate.py
|
|
942
960
|
tests/test_gate_vectors.py
|
|
961
|
+
tests/test_gemini_hook.py
|
|
962
|
+
tests/test_goose_hook.py
|
|
943
963
|
tests/test_graph_verbs.py
|
|
944
964
|
tests/test_graph_workflows.py
|
|
945
965
|
tests/test_guard.py
|
|
@@ -961,6 +981,7 @@ tests/test_lesson_lineage.py
|
|
|
961
981
|
tests/test_lessons.py
|
|
962
982
|
tests/test_lessons_compile.py
|
|
963
983
|
tests/test_lifecycle_sweep.py
|
|
984
|
+
tests/test_links.py
|
|
964
985
|
tests/test_live_probe.py
|
|
965
986
|
tests/test_mcp_features.py
|
|
966
987
|
tests/test_mcp_server.py
|
|
@@ -981,6 +1002,7 @@ tests/test_platform.py
|
|
|
981
1002
|
tests/test_product_flow_e2e.py
|
|
982
1003
|
tests/test_product_next.py
|
|
983
1004
|
tests/test_providers.py
|
|
1005
|
+
tests/test_qwen_hook.py
|
|
984
1006
|
tests/test_rank.py
|
|
985
1007
|
tests/test_replay.py
|
|
986
1008
|
tests/test_replay_context.py
|
package/README.md
CHANGED
|
@@ -35,8 +35,9 @@ reaches every host the same way.
|
|
|
35
35
|
|
|
36
36
|
## What tezgah enforces and records
|
|
37
37
|
|
|
38
|
-
- **One contract,
|
|
39
|
-
dsh
|
|
38
|
+
- **One contract, fourteen hosts.** omp, Claude Code, Codex, Cursor, opencode,
|
|
39
|
+
dsh, Qwen Code, Factory Droid, Command Code, GitHub Copilot CLI, Gemini CLI,
|
|
40
|
+
Antigravity CLI, Goose and Amp see the same rules, because every host is a thin adapter over one shared
|
|
40
41
|
core — change a rule once and all of them pick it up.
|
|
41
42
|
- **A done claim needs a passing check on record.** A gate of 15 refusals stops
|
|
42
43
|
the neutered check — `--no-verify`, `|| true`, a test piped into `tail`, a
|
|
@@ -154,7 +155,7 @@ and the optional integrations are in the [docs](docs/README.md).
|
|
|
154
155
|
|
|
155
156
|
## Supported hosts
|
|
156
157
|
|
|
157
|
-
**omp** (the primary host, developed and verified against), **Claude Code**, **Codex**, **Cursor**, **opencode**, **dsh** — thin adapters over one shared core.
|
|
158
|
+
**omp** (the primary host, developed and verified against), **Claude Code**, **Codex**, **Cursor**, **opencode**, **dsh**, **Qwen Code**, **Factory Droid**, **Command Code**, **GitHub Copilot CLI**, **Gemini CLI**, **Antigravity CLI**, **Goose**, **Amp** — thin adapters over one shared core. The last six are wired from their vendors' docs alone: no run of their binaries has shown them obeying a deny yet ([hosts](docs/hosts.md)).
|
|
158
159
|
|
|
159
160
|
## Where the depth lives
|
|
160
161
|
|
package/bin/tezgah-docs
CHANGED
|
@@ -106,7 +106,7 @@ RUN = re.compile(r"((?:`[A-Za-z_][A-Za-z0-9_]*(?:\(\))?`\s*[/,]?\s*){1,5})$")
|
|
|
106
106
|
CITE = re.compile(r"`([\w.-]+(?:/[\w.-]+)+|[\w-]+\.\w+):(\d+)(?:-(\d+))?`"
|
|
107
107
|
r"|`(:(\d+)(?:-(\d+))?)`")
|
|
108
108
|
# The symbol written after the citation instead of before it, the way a table
|
|
109
|
-
# cell reads: `hooks/tezgah_policy.py:
|
|
109
|
+
# cell reads: `hooks/tezgah_policy.py:819-1158` (`CORE`).
|
|
110
110
|
AFTER = re.compile(r"\s*\(`([A-Za-z_][A-Za-z0-9_]*)(?:\(\))?`\)")
|
|
111
111
|
# Any `path:line`, backticked or not: what CITE does not match of these (a
|
|
112
112
|
# bare token, a comma list `x.py:12,40`) is counted as unjudged, so a citation
|
package/bin/tezgah-research
CHANGED
|
@@ -25,6 +25,8 @@
|
|
|
25
25
|
tezgah-research verdict <slug> <id> met|not-met|unanswerable --evidence "<ref>"
|
|
26
26
|
record one success criterion's verdict
|
|
27
27
|
tezgah-research conclude <slug> conclude a line whose criteria all carry a verdict
|
|
28
|
+
tezgah-research choose <slug> <decision> <variant> --ack "<the owner's words>"
|
|
29
|
+
record the owner's pick of a recommended variant
|
|
28
30
|
tezgah-research migrate-layout move flat research/<slug> into open/ or done/
|
|
29
31
|
tezgah-research close <slug> --limit "<reason>" conclude a line as a deliberate limit
|
|
30
32
|
tezgah-research seal <slug> [--history-lost] --ack "<owner decision>"
|
|
@@ -121,14 +123,20 @@ no status, a design/plan/analysis/code deliverable with no decision comparing it
|
|
|
121
123
|
variants - printing every open slug with its reasons. `--allow-open "<reason>"`
|
|
122
124
|
opens the new line anyway and writes the reason into its `log.md` as the first
|
|
123
125
|
entry; an empty reason is misuse, and the hatch is refused while any open line has
|
|
124
|
-
`check` errors
|
|
126
|
+
`check` errors other than a criterion whose verdict is still `pending` (a line in
|
|
127
|
+
flight). Both refusals say that a line another session is running goes to the
|
|
128
|
+
owner, not to this session. `close <slug> --limit "<reason>"` concludes a line on purpose,
|
|
125
129
|
writing the reasons it was still open into `state.json` `closed` and `log.md`.
|
|
126
130
|
`--supersedes <old>` opens a new version of an existing line, which then carries
|
|
127
131
|
the old deliverable as a variant; a line asking an existing line's exact question
|
|
128
132
|
without it is refused. `compare` appends one cell to
|
|
129
133
|
`decisions/<decision>/comparison.jsonl` under the rule `check` applies to it.
|
|
134
|
+
A rules-9 decision.md writes the line's pick as `recommended:`; `choose` records
|
|
135
|
+
the owner's `chosen:` with an `owner:` line holding their words, and `conclude`
|
|
136
|
+
names each recommendation still waiting for it.
|
|
130
137
|
`check` prints the same list of open lines after the per-line report, and so does
|
|
131
|
-
`status`, so neither state has to be discovered by being refused.
|
|
138
|
+
`status`, so neither state has to be discovered by being refused. Both also fail
|
|
139
|
+
a hidden dir under `research/`, which no line may live in.
|
|
132
140
|
|
|
133
141
|
`init --method exploratory` (rules 7) opens a campaign instead of a question:
|
|
134
142
|
`--arena` and `--budget` (and, for a measurable arena, `--evaluator` with its
|
|
@@ -286,6 +294,7 @@ def cmd_init(args):
|
|
|
286
294
|
print(" %s: %s" % (name, "; ".join(reasons)))
|
|
287
295
|
print('to open %s anyway, say why: tezgah-research init %s '
|
|
288
296
|
'--allow-open "<reason>"' % (slug, slug))
|
|
297
|
+
print(tr.OWNER_ASK)
|
|
289
298
|
return 1
|
|
290
299
|
broken = tr.broken_open_lines(repo) if unfinished else []
|
|
291
300
|
if broken:
|
|
@@ -297,6 +306,7 @@ def cmd_init(args):
|
|
|
297
306
|
print(" %s: %d error(s), first: %s" % (name, len(errors), errors[0]))
|
|
298
307
|
print('fix them, or record what is left: tezgah-research close <slug> '
|
|
299
308
|
'--limit "<reason>"')
|
|
309
|
+
print(tr.OWNER_ASK)
|
|
300
310
|
return 1
|
|
301
311
|
twins = [t for t in tr.serial_twins(repo, slug, {"question": question})
|
|
302
312
|
if t != supersedes]
|
|
@@ -490,7 +500,7 @@ def cmd_check(args):
|
|
|
490
500
|
return 1
|
|
491
501
|
problems = 0
|
|
492
502
|
report = {}
|
|
493
|
-
if known:
|
|
503
|
+
if known or tr.stray_dirs(repo):
|
|
494
504
|
report = tr.check(repo, rest[0] if rest else None, strict=strict,
|
|
495
505
|
all_lines=all_lines, code=checkout_root(os.getcwd()),
|
|
496
506
|
flags={"online": online, "orx": ask_orx})
|
|
@@ -1067,6 +1077,28 @@ def cmd_conclude(args):
|
|
|
1067
1077
|
print("FAIL conclude: %s" % problem)
|
|
1068
1078
|
return 1
|
|
1069
1079
|
print("concluded %s; it moved to research/done/%s" % (slug, slug))
|
|
1080
|
+
for decision, variant in tr.owner_waits(repo, slug):
|
|
1081
|
+
print("report to the owner: %s is recommended, not chosen; building it waits "
|
|
1082
|
+
"for the owner's answer (tezgah-research choose %s %s %s --ack "
|
|
1083
|
+
"\"<their words>\")" % (variant, slug, decision, variant))
|
|
1084
|
+
return 0
|
|
1085
|
+
|
|
1086
|
+
|
|
1087
|
+
def cmd_choose(args):
|
|
1088
|
+
"""Record the owner's pick among a decision's produced variants."""
|
|
1089
|
+
usage = 'choose <slug> <decision> <variant> --ack "<the owner\'s words>"'
|
|
1090
|
+
if len(args) != 5 or args[3] != "--ack":
|
|
1091
|
+
return misuse(usage)
|
|
1092
|
+
slug, decision, variant, _flag, ack = args
|
|
1093
|
+
repo = repo_root(os.getcwd())
|
|
1094
|
+
if slug not in tr.slugs(repo):
|
|
1095
|
+
return misuse("no such research line: %s" % slug)
|
|
1096
|
+
problem = tr.choose_line(repo, slug, decision, variant, ack,
|
|
1097
|
+
time.strftime("%Y-%m-%d"))
|
|
1098
|
+
if problem:
|
|
1099
|
+
print("FAIL choose: %s" % problem)
|
|
1100
|
+
return 1
|
|
1101
|
+
print("decisions/%s: the owner chose %s" % (decision, variant))
|
|
1070
1102
|
return 0
|
|
1071
1103
|
|
|
1072
1104
|
|
|
@@ -1212,6 +1244,8 @@ def main(argv):
|
|
|
1212
1244
|
return cmd_verdict(rest)
|
|
1213
1245
|
if cmd == "conclude":
|
|
1214
1246
|
return cmd_conclude(rest)
|
|
1247
|
+
if cmd == "choose":
|
|
1248
|
+
return cmd_choose(rest)
|
|
1215
1249
|
if cmd == "migrate-layout":
|
|
1216
1250
|
return cmd_migrate_layout(rest)
|
|
1217
1251
|
if cmd == "close":
|
|
@@ -1237,7 +1271,8 @@ def main(argv):
|
|
|
1237
1271
|
if cmd in commands:
|
|
1238
1272
|
return commands[cmd](repo_root(os.getcwd()), rest)
|
|
1239
1273
|
return misuse("unknown command %r (init, commit, check, status, --all, claim, predict, "
|
|
1240
|
-
"components, migrate, source, compare, verdict, conclude,
|
|
1274
|
+
"components, migrate, source, compare, verdict, conclude, choose, "
|
|
1275
|
+
"migrate-layout, "
|
|
1241
1276
|
"close, seal, import, next, idea, stage, match, board, dedup, yield, cite, "
|
|
1242
1277
|
"fetch, search, instrument, gold, label, switch, release-check, "
|
|
1243
1278
|
"calibration)" % cmd)
|