@r1z4x/tezgah 1.3.0 → 1.5.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 +103 -1
- package/MANIFEST +3 -0
- package/agents/tezgah-reviewer.md +19 -10
- package/bin/tezgah-docs +3 -3
- package/bin/tezgah-doctor +75 -18
- package/bin/tezgah-route +5 -2
- package/bin/tezgah-setup +30 -18
- package/bin/tezgah-status +3 -1
- package/bin/tezgah-task +130 -37
- package/bin/tezgah-taste +74 -55
- package/bin/tezgah-triage +12 -7
- package/docs/architecture.md +4 -4
- package/docs/clarity-baseline.json +9 -9
- package/docs/contract.md +22 -12
- package/docs/environment.md +4 -1
- package/docs/evidence.md +71 -34
- package/docs/gate.md +21 -21
- package/docs/glossary.md +5 -5
- package/docs/hosts.md +8 -8
- package/docs/judge.md +93 -27
- package/docs/operations.md +69 -30
- package/docs/skills.md +2 -2
- package/docs/status-line.md +7 -7
- package/docs/vision.md +10 -5
- package/hooks/tezgah_agents.py +22 -11
- package/hooks/tezgah_attest.py +15 -22
- package/hooks/tezgah_context.py +84 -124
- package/hooks/tezgah_gate.py +26 -23
- package/hooks/tezgah_guard.py +9 -11
- package/hooks/tezgah_integrity.py +77 -178
- package/hooks/tezgah_judge.py +270 -122
- package/hooks/tezgah_lessons.py +18 -63
- package/hooks/tezgah_paths.py +183 -55
- package/hooks/tezgah_policy.py +35 -8
- package/hooks/tezgah_replay.py +57 -33
- package/hooks/tezgah_shapes.py +4 -4
- package/hooks/tezgah_skill_pick.py +6 -35
- package/hooks/tezgah_snapshot.py +25 -46
- package/hooks/tezgah_store.py +1268 -0
- package/hooks/tezgah_task.py +146 -40
- package/hooks/tezgah_taste.py +21 -18
- package/hooks/tezgah_taste_ledger.py +63 -88
- package/hooks/tezgah_update.py +6 -26
- package/hosts/codex/hook.py +5 -0
- package/hosts/cursor/hook.py +19 -31
- package/hosts/opencode/plugins/tezgah.js +309 -108
- package/package.json +1 -1
- package/skills/harness/SKILL.md +4 -0
- package/skills/plan-add/SKILL.md +23 -7
- package/skills/plan-add/render_table.py +3 -2
- package/skills/plan-status/SKILL.md +12 -4
- package/skills/plan-sync/SKILL.md +19 -9
- package/skills/tezgah-contract/SKILL.md +25 -12
package/docs/hosts.md
CHANGED
|
@@ -20,7 +20,7 @@ surface can honestly report this session; the two skill-read marks (`pony`,
|
|
|
20
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:47-55`) | 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
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:122-124`) | five tool-use (`statusline.py::observable`) |
|
|
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:
|
|
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:2349-2714`) - `tool.execute.before` carries the whole gate, `tool.execute.after` the channel on the row, the provenance label on the result and the taint notice on the next effect (`untrustedSource` `hosts/opencode/plugins/tezgah.js:1919`, `labelResult` `:1993`) | static only: generated `instructions` files, because there is no session-start injection point (`hosts/opencode/plugins/tezgah.js:3-6`, `bin/tezgah-setup::opencode_contract_text`) | chat.message pushes a synthetic part (`hosts/opencode/plugins/tezgah.js:2629-2680`) | TUI plugin `tezgah-tui.tsx` declared in `tui.json`, runs `tezgah-status` on the event bus (`hosts/opencode/tui/tezgah-tui.tsx:1-11`; wiring `bin/tezgah-setup::install_opencode`) | all seven: skill reads and kinds are classified in process (`hosts/opencode/plugins/tezgah.js:2204-2240`) |
|
|
24
24
|
| dsh | SessionStart, SubagentStart, UserPromptSubmit, PreToolUse, PostToolUse (`hosts/dsh/hooks.json:2-19`); no Stop rule, because the bridge drops every call's outcome and no check there can show a pass | hook: the same Claude-family scripts, named by `configPath` (`bin/tezgah-setup::dsh_patch_block`) | UserPromptSubmit, through the bridge (`hosts/dsh/hooks.json:9-11`) | web-profile plugin: authenticated `GET /api/tezgah.status` -> `tezgah-status` (`hosts/dsh/statusline/lib/index.js:15,66-104`) | five tool-use: the route passes `--observable=consult,research,graph,orch,judge` (`hosts/dsh/statusline/lib/index.js:16-19,84,96`) |
|
|
25
25
|
| omp | session_start, session_compact, session_switch, turn_end, before_agent_start, tool_call, tool_result, session_stop (`hosts/omp/tezgah-hook.ts.in:541,588,607,612,614,634,653,688`) | both: static `RULES.md` (`bin/tezgah-setup::STATIC_RULES`, `bin/tezgah-setup::rules_block`) and a session payload carrying only what a static file cannot know (`hosts/omp/hook.py::handle`) | before_agent_start returns a hidden message (`hosts/omp/tezgah-hook.ts.in:614-632`) | extension draws `ctx.ui.setWidget` below the editor, `setStatus` as fallback (`hosts/omp/tezgah-hook.ts.in:170-203`); ANSI line from `hosts/omp/hook.py::status_line` | all seven: the embedded runner filters skill reads before asking python (`hosts/omp/tezgah-hook.ts.in:255-262`) and no `observable` is passed (`hosts/omp/hook.py::status_line`) |
|
|
26
26
|
|
|
@@ -216,7 +216,7 @@ and say which document it came from.
|
|
|
216
216
|
|
|
217
217
|
A skill read is observable only where the host gives a channel that does not cost
|
|
218
218
|
a process per read: Claude parses its transcript (`statusline.py::claude_used`),
|
|
219
|
-
opencode classifies in process (`hosts/opencode/plugins/tezgah.js:
|
|
219
|
+
opencode classifies in process (`hosts/opencode/plugins/tezgah.js:2204-2240`),
|
|
220
220
|
omp filters in its embedded runner before it asks python
|
|
221
221
|
(`hosts/omp/tezgah-hook.ts.in:255-262`). On codex, cursor and dsh a read is not
|
|
222
222
|
observable at that price, so their surfaces pass the five tool-use measures as
|
|
@@ -339,9 +339,9 @@ In order, each step verified by the one below it:
|
|
|
339
339
|
`hosts/dsh/hooks.json` until the bridge carries outcomes.
|
|
340
340
|
- **opencode carries the untrusted-content control in the plugin**, not by
|
|
341
341
|
importing `hooks/tezgah_untrusted.py`, which a plugin cannot do in process: the
|
|
342
|
-
channel is decided in JS (`untrustedSource` `hosts/opencode/plugins/tezgah.js:
|
|
343
|
-
the tier's argv reader at `:
|
|
344
|
-
result the hook is handed (`labelResult` `:
|
|
342
|
+
channel is decided in JS (`untrustedSource` `hosts/opencode/plugins/tezgah.js:1919`,
|
|
343
|
+
the tier's argv reader at `:1902-1911`), the label and the taint notice ride the
|
|
344
|
+
result the hook is handed (`labelResult` `:1993`). A shared corpus in
|
|
345
345
|
`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
|
|
346
346
|
fails if their answers differ, so neither can move without the other.
|
|
347
347
|
- **omp carries both untrusted-content marks through the shared core.** Its
|
|
@@ -373,16 +373,16 @@ In order, each step verified by the one below it:
|
|
|
373
373
|
- **opencode asks the core for the rules it cannot port, and counts the ask.** The
|
|
374
374
|
task rule's phase and allowlist and the language rule's word list are answered by
|
|
375
375
|
`bin/tezgah-gate decide` (the recording verb; `check` is the dry run) rather than copied into JavaScript (`IDENT_CMD`
|
|
376
|
-
`hosts/opencode/plugins/tezgah.js:585`, the call site `:
|
|
376
|
+
`hosts/opencode/plugins/tezgah.js:585`, the call site `:2492-2495`) - one spawn per write
|
|
377
377
|
and per identifier-creating command. The delegation fails open by design: a missing
|
|
378
378
|
CLI, a non-zero exit, a broken pipe or a 10 s timeout refuses nothing (`collect`
|
|
379
379
|
`hosts/opencode/plugins/tezgah.js:152-178`). One `delegation` row now records the class when the core could not
|
|
380
|
-
answer (`noteDelegation` `:
|
|
380
|
+
answer (`noteDelegation` `:2304-2310`), so a silent fail-open is countable instead
|
|
381
381
|
of invisible.
|
|
382
382
|
- **opencode has no session-start hook**, so its always-on file must carry the
|
|
383
383
|
pointer every hook host appends, and the per-session index, agent and contract
|
|
384
384
|
refreshes ride the first `chat.message` instead (`bin/tezgah-setup::refresh_contract`,
|
|
385
|
-
`hosts/opencode/plugins/tezgah.js:
|
|
385
|
+
`hosts/opencode/plugins/tezgah.js:2663-2678`). It denies the native `skill`
|
|
386
386
|
tool - the generated router replaces the injected skill list
|
|
387
387
|
(`bin/tezgah-setup::install_opencode`) - and `permission.ask` may never be emitted by a
|
|
388
388
|
given build, which is why `tool.execute.before` stays the enforcing half
|
package/docs/judge.md
CHANGED
|
@@ -14,9 +14,10 @@ the kill-switch paragraph and nothing else (`CORE`,
|
|
|
14
14
|
One seam, five callers, one provider order, one egress boundary, one price, one
|
|
15
15
|
redirect guard. The module is stdlib only and total - a failure is a `None`, never
|
|
16
16
|
an exception, because a hook imports it and a hook that raises takes a session
|
|
17
|
-
down (`_request`, `hooks/tezgah_judge.py::_request`).
|
|
18
|
-
|
|
19
|
-
`hooks/tezgah_judge.py::
|
|
17
|
+
down (`_request`, `hooks/tezgah_judge.py::_request`). The carriers' endpoints are
|
|
18
|
+
module constants: `URL` (`hooks/tezgah_judge.py::URL`), `JEV_OPENROUTER_URL`
|
|
19
|
+
(`hooks/tezgah_judge.py::JEV_OPENROUTER_URL`) and `CLOUDFLARE_URL`
|
|
20
|
+
(`hooks/tezgah_judge.py::CLOUDFLARE_URL`). One opener serves every call (`OPENER`,
|
|
20
21
|
`hooks/tezgah_judge.py::OPENER`), and it refuses a redirect that leaves the
|
|
21
22
|
endpoint's host, so a `Location` cannot carry the bearer (`guarded_opener`,
|
|
22
23
|
`hooks/tezgah_paths.py`, the one copy `bin/consult` and `bin/codegen` use too). A
|
|
@@ -25,15 +26,18 @@ attempt. A 4xx, a session CLI's non-zero exit and a malformed reply never do
|
|
|
25
26
|
(`_transient`, `hooks/tezgah_judge.py::_transient`). A hook caller asks for one
|
|
26
27
|
attempt and a wall-clock `deadline` instead (`ask`, `hooks/tezgah_judge.py::ask`).
|
|
27
28
|
A session CLI answers in 4-10 s (measured 2026-10-07), so the skill hint's
|
|
28
|
-
4 s deadline usually ends first. `TEZGAH_TYPESAFE_URL`, `
|
|
29
|
-
|
|
29
|
+
4 s deadline usually ends first. `TEZGAH_TYPESAFE_URL`, `TEZGAH_JEV_OPENROUTER_URL`,
|
|
30
|
+
`TEZGAH_CLOUDFLARE_URL`, `TEZGAH_OPENROUTER_URL` and
|
|
31
|
+
`TEZGAH_OMP_BIN`/`TEZGAH_CLAUDE_BIN` are the test seams. Each URL override goes
|
|
32
|
+
through `override` (`hooks/tezgah_judge.py::override`), which refuses plain http
|
|
33
|
+
off this machine.
|
|
30
34
|
|
|
31
35
|
Every caller reads an answer through the same accessors rather than reaching
|
|
32
36
|
into the raw reply, so a Choice and a Noul are read one way for all five
|
|
33
37
|
(`choice`, `hooks/tezgah_judge.py::choice`; `noul`,
|
|
34
38
|
`hooks/tezgah_judge.py::noul`). A `text` question is the one shape that asks
|
|
35
39
|
for prose. `bin/tezgah-taste` asks it for a learning's readable line, and only
|
|
36
|
-
a generative provider answers one.
|
|
40
|
+
a generative provider answers one. No Jev carrier is sent one (`text`,
|
|
37
41
|
`hooks/tezgah_judge.py::text`). All three are total: a missing or
|
|
38
42
|
wrongly-typed answer is a `None`, never an exception.
|
|
39
43
|
|
|
@@ -44,9 +48,11 @@ are untouched, so a model answer can never license a "done" (`STEP_KINDS`,
|
|
|
44
48
|
|
|
45
49
|
The state leaves the machine. By default it goes to the session's own vendor,
|
|
46
50
|
through the session's own CLI and credential ([below](#the-providers-and-their-order)).
|
|
47
|
-
A third party
|
|
48
|
-
|
|
49
|
-
|
|
51
|
+
A third party reads it only as the `fallback` setting allows. The third parties
|
|
52
|
+
are the Jev carriers' hosts - `api.typesafe.ai`, `openrouter.ai`,
|
|
53
|
+
`api.cloudflare.com` or a user's `JEV_API_BASE_URL` - and the chat endpoint
|
|
54
|
+
(`OPENROUTER_URL`, `hooks/tezgah_judge.py::OPENROUTER_URL`).
|
|
55
|
+
For the triage the state is the screen's own text, so whoever
|
|
50
56
|
answers reads any personal data on it. For the docs fallback it is the query.
|
|
51
57
|
Nothing else goes: no session id, no
|
|
52
58
|
workspace path, no environment. The seam does not rewrite the state, because
|
|
@@ -89,18 +95,74 @@ background `learn` that armed taste starts.
|
|
|
89
95
|
|
|
90
96
|
`providers()` (`hooks/tezgah_judge.py::providers`) is the order `ask()` tries.
|
|
91
97
|
The next one is asked only when the previous failed. First comes the session's own
|
|
92
|
-
CLI (`tp.session_cli`, `hooks/tezgah_paths.py::session_cli`)
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
98
|
+
CLI (`tp.session_cli`, `hooks/tezgah_paths.py::session_cli`). The host's marker
|
|
99
|
+
names it, in this order:
|
|
100
|
+
|
|
101
|
+
| CLI | Marker | Set by |
|
|
102
|
+
|---|---|---|
|
|
103
|
+
| `omp` | `OMPCODE` | omp, which sets `CLAUDECODE` too |
|
|
104
|
+
| `claude` | `CLAUDECODE` | Claude Code |
|
|
105
|
+
| `opencode` | `OPENCODE` | opencode, in its own process |
|
|
106
|
+
| `cursor` (runs `cursor-agent`) | `CURSOR_AGENT`, `CURSOR_VERSION` | Cursor: the first in its shell tool, the second in its hooks |
|
|
107
|
+
| `codex` | `CODEX_THREAD_ID` | Codex in its shell tool; tezgah's Codex hook sets it from the session id |
|
|
108
|
+
|
|
109
|
+
A marked host whose binary is missing has no session CLI. No other CLI stands
|
|
110
|
+
in. `TEZGAH_JUDGE_CLI`, else `judge_cli` in `~/.config/tezgah/config.json`,
|
|
111
|
+
names one of the five instead. `auto`, the default, reads the marker. A named CLI
|
|
112
|
+
that is not installed is no session CLI either.
|
|
113
|
+
|
|
114
|
+
The CLI runs headless in an empty temp dir. Its flags are `SESSION_ARGV`
|
|
115
|
+
(`hooks/tezgah_judge.py::SESSION_ARGV`). The omp and claude runs drop tools,
|
|
116
|
+
rules, skills, extensions, MCP servers and settings. The other three have no flag
|
|
117
|
+
that drops their tools or takes a system prompt. They run with the least their
|
|
118
|
+
`--help` offers: opencode `--pure`, cursor-agent `--mode ask`, codex
|
|
119
|
+
`-s read-only --ephemeral`. Their prompt starts with the system prompt.
|
|
120
|
+
`opencode run` ran a shell command its prompt asked for in a probe on
|
|
121
|
+
2026-10-09. So it also gets an inline config that denies every tool
|
|
122
|
+
(`SESSION_ENV`, `hooks/tezgah_judge.py::SESSION_ENV`). With it, the same probe
|
|
123
|
+
wrote nothing. cursor-agent in ask mode refused the same shell call.
|
|
124
|
+
Measured 2026-10-07, a call cost $0.007 on claude and $0.015 on omp. Measured
|
|
125
|
+
2026-10-09 on one choice question, opencode read 28,646 input tokens ($0.004 by
|
|
126
|
+
its own report), cursor-agent 17,095 and codex 20,919. The session's own
|
|
127
|
+
credential pays, OAuth subscription included. The answer and usage come from the
|
|
128
|
+
CLI's JSON output (`_session_request`, `hooks/tezgah_judge.py::_session_request`).
|
|
129
|
+
`_cli_answer` (`hooks/tezgah_judge.py::_cli_answer`) reads the last three. Only
|
|
130
|
+
omp and claude name the model. Then come the
|
|
131
|
+
third parties: first the chosen Jev carriers, each a real System One transport
|
|
132
|
+
(`_jev_call`, `hooks/tezgah_judge.py::_jev_call`), then the OpenRouter chat
|
|
133
|
+
fallback. `JEV_CARRIERS` (`hooks/tezgah_judge.py::JEV_CARRIERS`) names the six.
|
|
134
|
+
Two of them are not Jev. Clef and Clef-flash are Cloudflare's own decision
|
|
135
|
+
models. They take the same System One request. They answer in the same typed
|
|
136
|
+
shape, so a caller that needs a typed answer takes them like Jev.
|
|
137
|
+
|
|
138
|
+
| Carrier | Endpoint | Credential (env, else file, stripped) | Body |
|
|
139
|
+
|---|---|---|---|
|
|
140
|
+
| `typesafe` | `https://api.typesafe.ai/v1/systemone` | `TYPESAFE_API_KEY`, else `~/.config/typesafe/key`, else the active omp login (`~/.omp/agent/agent.db`, read-only; `typesafe_key`, `hooks/tezgah_paths.py::typesafe_key`) | `{state, model, questions}` |
|
|
141
|
+
| `jev-openrouter` | `https://openrouter.ai/api/v1/systemone` | `OPENROUTER_API_KEY`, else `~/.config/openrouter/key` | the same, a bare `jev-*` id sent as `typesafe/<id>` |
|
|
142
|
+
| `jev-cloudflare` | `https://api.cloudflare.com/client/v4/accounts/<id>/ai/run` | `JEV_CLOUDFLARE_API_TOKEN`, else `CLOUDFLARE_API_TOKEN`, else `~/.config/cloudflare/token`; the account id from `CLOUDFLARE_ACCOUNT_ID`, else `~/.config/cloudflare/account_id` | `{model: "typesafe/jev", input: {state, questions}}`; the reply is read bare or inside `result` |
|
|
143
|
+
| `jev-compatible` | `JEV_API_BASE_URL`, used verbatim | `JEV_API_KEY` | TypeSafe's, the model from `TEZGAH_JEV_MODEL` if set |
|
|
144
|
+
| `clef`, `clef-flash` | `https://api.cloudflare.com/client/v4/accounts/<id>/ai/run/@cf/cloudflare/<name>` | the Cloudflare token and account id above | `{model: "<name>", state, questions}`; the reply read like Cloudflare's above |
|
|
145
|
+
|
|
146
|
+
`JEV_API_BASE_URL` and `JEV_API_KEY` are the names the public jev-mcp server
|
|
147
|
+
reads, so one export serves both. `TEZGAH_JEV_PROVIDER` chooses the carrier,
|
|
148
|
+
else `JEV_PROVIDER`, else `jev` in `~/.config/tezgah/config.json`, else `auto`
|
|
149
|
+
(`jev_choice`, `hooks/tezgah_paths.py::jev_choice`). The values are `auto`,
|
|
150
|
+
`typesafe`, `openrouter`, `cloudflare`, `compatible`, `clef` and `clef-flash`.
|
|
151
|
+
`tezgah-setup --jev <value>` writes the config key
|
|
152
|
+
(`set_jev`, `bin/tezgah-setup::set_jev`). `auto`
|
|
153
|
+
asks every carrier that resolves, in the table's order (`jev_carriers`,
|
|
154
|
+
`hooks/tezgah_paths.py::jev_carriers`). The seam asks a named carrier without
|
|
155
|
+
its credential nothing, and nothing stands in for it: the call records
|
|
156
|
+
`no credential (jev=<value>)` as a failure. `tezgah-status --judge` prints the
|
|
157
|
+
choice and the carriers it resolves to (`jev_summary`,
|
|
158
|
+
`hooks/tezgah_judge.py::jev_summary`). A cost row prices the carrier that
|
|
159
|
+
answered (`input_price`, `hooks/tezgah_judge.py::input_price`). Per 1M input
|
|
160
|
+
tokens: $0.042 for Jev, $0.24 for Clef, $0.09 for Clef-flash. Output is free.
|
|
161
|
+
|
|
162
|
+
OpenRouter's chat fallback reads the OpenRouter key (`openrouter_key`,
|
|
163
|
+
`hooks/tezgah_judge.py::openrouter_key`).
|
|
102
164
|
It asks the table's cheap row unless `TEZGAH_JUDGE_MODEL` names another
|
|
103
|
-
(`fallback_model`, `hooks/tezgah_judge.py::fallback_model`). The key
|
|
165
|
+
(`fallback_model`, `hooks/tezgah_judge.py::fallback_model`). The key files are
|
|
104
166
|
read because a hook runs where `~/.zshenv` never did. A chat answer is asked for in prose
|
|
105
167
|
(`CHAT_SYSTEM`, `hooks/tezgah_judge.py::CHAT_SYSTEM`) and filtered to the ids
|
|
106
168
|
asked, a wrongly typed answer dropped (`_chat_answers`,
|
|
@@ -111,8 +173,8 @@ asked, a wrongly typed answer dropped (`_chat_answers`,
|
|
|
111
173
|
|
|
112
174
|
| `fallback` | Session CLI present | No session CLI |
|
|
113
175
|
|---|---|---|
|
|
114
|
-
| `vendor` (default) | the session CLI only; when it fails, no judgement |
|
|
115
|
-
| `any` | the session CLI, then
|
|
176
|
+
| `vendor` (default) | the session CLI only; when it fails, no judgement | the Jev carriers, then the chat fallback |
|
|
177
|
+
| `any` | the session CLI, then the Jev carriers, then the chat fallback | the Jev carriers, then the chat fallback |
|
|
116
178
|
| `none` | the session CLI only | nobody: `available()` is false |
|
|
117
179
|
|
|
118
180
|
Nothing that stands in is silent. A result carries `provider`, the `model`
|
|
@@ -124,7 +186,7 @@ hint's cost row and the router's `route` row also carry `judge=<provider>/<model
|
|
|
124
186
|
|
|
125
187
|
A caller that needs one provider in particular passes `only` (`named`,
|
|
126
188
|
`hooks/tezgah_judge.py::named`). `learn`'s decision has to come from a typed
|
|
127
|
-
model, so it passes `only=("
|
|
189
|
+
model, so it passes `only=("jev",)`, every Jev carrier that resolves. It gets `None` rather than another
|
|
128
190
|
provider's answer. The caller named the provider, so the `vendor` order does not
|
|
129
191
|
apply. `none` still keeps every third party out.
|
|
130
192
|
|
|
@@ -173,8 +235,9 @@ known (`note`, `hooks/tezgah_integrity.py::note`), counted by the row's kind
|
|
|
173
235
|
costs a fraction of a cent, so a cache is not worth its state file, and a naive
|
|
174
236
|
one would make two runs of the same command disagree. Beside the last-use
|
|
175
237
|
record, the seam keeps a failure marker (`DOWN_FOR`, 300 s, in `hooks/tezgah_judge.py`). A
|
|
176
|
-
|
|
177
|
-
provider, endpoint and credential down for five minutes.
|
|
238
|
+
401, 402, 403, 5xx or a session CLI's non-zero exit marks that
|
|
239
|
+
provider, endpoint and credential down for five minutes. A 403 is OpenRouter's
|
|
240
|
+
answer to a key past its limit. Until the marker
|
|
178
241
|
expires, that provider is skipped and sent no request. A dead key or an empty account then costs
|
|
179
242
|
one refusal per five minutes, not one per prompt. A rotated key gets a new
|
|
180
243
|
marker, so the seam asks it at once.
|
|
@@ -193,8 +256,11 @@ known (`note`, `hooks/tezgah_integrity.py::note`), counted by the row's kind
|
|
|
193
256
|
|
|
194
257
|
## Source of truth
|
|
195
258
|
|
|
196
|
-
- `hooks/tezgah_judge.py` — the seam:
|
|
197
|
-
the answer accessors, the retry class and the
|
|
259
|
+
- `hooks/tezgah_judge.py` — the seam: the carriers' endpoints and bodies,
|
|
260
|
+
`available()`, `ask()`, the answer accessors, the retry class and the
|
|
261
|
+
provider-down marker.
|
|
262
|
+
- `hooks/tezgah_paths.py` — the key channels (`typesafe_key`, `jev_carriers`)
|
|
263
|
+
and the carrier choice (`jev_choice`).
|
|
198
264
|
- `bin/tezgah-triage` — the snapshot triage and the per-state matrix; prints the
|
|
199
265
|
unit it selected, the characters it saved and the cost it paid.
|
|
200
266
|
- `bin/tezgah-docs` — the page router; its index match, then its one fallback
|
package/docs/operations.md
CHANGED
|
@@ -202,8 +202,8 @@ fetch, the flip and the re-arm and runs none of the three
|
|
|
202
202
|
|
|
203
203
|
The status line says when a newer release is out. A `↑X.Y.Z` chip sits beside
|
|
204
204
|
the logo, in the version's own group, on every surface (`notice_segments()`,
|
|
205
|
-
`hooks/tezgah_update.py`). A redraw only reads
|
|
206
|
-
When that
|
|
205
|
+
`hooks/tezgah_update.py`). A redraw only reads the `update_check` row of
|
|
206
|
+
`~/.cache/tezgah/tezgah.db`. When that row is a day old, the redraw stamps it and starts one detached
|
|
207
207
|
check against the GitHub releases endpoint. An offline check writes nothing,
|
|
208
208
|
and the next day asks again. `tezgah update` is the command the chip points
|
|
209
209
|
at, and it is the only thing that moves the install. The
|
|
@@ -225,7 +225,7 @@ After a change to the contract text (`hooks/tezgah_policy.py`,
|
|
|
225
225
|
- `--refresh` does that without a reinstall, for every armed host's static
|
|
226
226
|
block and not only opencode's (`refresh_contract`). opencode has no
|
|
227
227
|
session-start hook, so its plugin runs it once per session
|
|
228
|
-
(`hosts/opencode/plugins/tezgah.js:
|
|
228
|
+
(`hosts/opencode/plugins/tezgah.js:2666-2670`). Until 2026-10-02 it
|
|
229
229
|
re-rendered opencode's files alone and reset the one shared hash, so a rule
|
|
230
230
|
edit left the other static files stale while `--report` went green (audit
|
|
231
231
|
M-5).
|
|
@@ -370,7 +370,8 @@ does not hold.
|
|
|
370
370
|
| Invocation | What it does |
|
|
371
371
|
|---|---|
|
|
372
372
|
| `tezgah-doctor` | nothing: sizes, session and event counts, whether opencode is running, the two context-hygiene settings, the `.codegraph` bytes and index presence per repository, and the three host state dirs (`bin/tezgah-doctor::collect`, `bin/tezgah-doctor::report`) |
|
|
373
|
-
| `tezgah-doctor --clean` | vacuums opencode's database, and only when opencode is not running (`bin/tezgah-doctor::vacuum_db`, `bin/tezgah-doctor::main`);
|
|
373
|
+
| `tezgah-doctor --clean` | vacuums opencode's database, and only when opencode is not running (`bin/tezgah-doctor::vacuum_db`, `bin/tezgah-doctor::main`); deletes every evidence ledger whose newest row is older than `--retention-days` (default 30), rows, import offsets and old JSONL file together (`hooks/tezgah_store.py::sweep_evidence`), and the turn stamps and used-tool marks last written before then (`hooks/tezgah_store.py::sweep_cache`); and deletes the files not modified for that long - `classify.log`, `context-drops.log`, `debug.log` and the `turns/` and `sessions/` files a running older release still writes - in the cache and its sandbox fallback, keeping the current session's (`$TEZGAH_SESSION`) ledger, stamp and marks whatever their age (`sweep_state`) |
|
|
374
|
+
| `tezgah-doctor --import-evidence` | imports every old `evidence/*.jsonl` file into the cache's `tezgah.db` now, in the cache and its sandbox fallback (`bin/tezgah-doctor::import_evidence`); session start runs the same import detached at most once a day (`hooks/tezgah_store.py::import_later`) |
|
|
374
375
|
| `tezgah-doctor --coverage` | every tracked file the codegraph index does not hold, in two classes — a supported extension the index is missing, and a shebang-only script with no `.py` twin — with the file counts it read, so an empty result cannot read as "everything is covered" |
|
|
375
376
|
| `tezgah-doctor --stack` | per host: the context files in load order, the skill dirs and which copy wins, the hook rows per event with their owner, the MCP servers, and every conflict; exits 1 on one ([hosts](hosts.md#precedence-what-each-host-loads-and-in-which-order), `hooks/tezgah_stack.py::report`) |
|
|
376
377
|
| `tezgah-doctor --prune-sessions DAYS` | deletes sessions idle longer than DAYS through `opencode session delete`, then vacuums; skipped when opencode is running or its CLI is missing (`bin/tezgah-doctor::prune_sessions`, `bin/tezgah-doctor::main`) |
|
|
@@ -398,7 +399,7 @@ writes only into a repository that already has a `.tezgah/` directory. A
|
|
|
398
399
|
(`hooks/tezgah_context.py::repo_marks`). A workspace of repo-provided data gets
|
|
399
400
|
nothing.
|
|
400
401
|
|
|
401
|
-
Rows go to `<repo>/.tezgah/taste/
|
|
402
|
+
Rows go to the `signals` table of `<repo>/.tezgah/taste/taste.db`
|
|
402
403
|
(`_write`, `hooks/tezgah_taste.py::_write`). A user turn adds a `prompt` row,
|
|
403
404
|
redacted and cut to 2000 characters (`hooks/tezgah_taste.py::note_prompt`).
|
|
404
405
|
A landed write adds an `edit` row with its redacted old and new text, cut to
|
|
@@ -412,7 +413,7 @@ ledger row. A rollback therefore never reads it as a pre-state (`capture_after`,
|
|
|
412
413
|
`hooks/tezgah_snapshot.py::capture_after`). A file outside the repository gets no
|
|
413
414
|
after blob.
|
|
414
415
|
opencode reaches the same call through a process (`noteTaste`,
|
|
415
|
-
`hosts/opencode/plugins/tezgah.js:
|
|
416
|
+
`hosts/opencode/plugins/tezgah.js:2413-2423`).
|
|
416
417
|
|
|
417
418
|
```sh
|
|
418
419
|
touch ~/.config/tezgah/taste-on # arm; rm it to disarm
|
|
@@ -434,25 +435,28 @@ builds it (`capture_signals`, `bin/tezgah-taste::capture_signals`). A signal
|
|
|
434
435
|
holds the prompt, the files written, the agent's edits, and the user's own edit
|
|
435
436
|
between two agent writes. That last one compares the after blob with the next
|
|
436
437
|
write's pre-state, or with the file on disk (`user_diff`,
|
|
437
|
-
`bin/tezgah-taste::user_diff`). `learn` asks
|
|
438
|
-
signal (`decide`, `bin/tezgah-taste::decide`, [judge](judge.md)).
|
|
438
|
+
`bin/tezgah-taste::user_diff`). `learn` asks the Jev model for one typed decision
|
|
439
|
+
per signal (`decide`, `bin/tezgah-taste::decide`, [judge](judge.md)). Any Jev
|
|
440
|
+
carrier will do: TypeSafe, OpenRouter System One, Cloudflare Workers AI or a
|
|
441
|
+
compatible endpoint. The decision
|
|
439
442
|
names preference, defect or none. It picks one of eleven fixed categories and a
|
|
440
443
|
scope. It also says how the signal relates to each learning held: supports,
|
|
441
|
-
contradicts, narrows or unrelated. When
|
|
442
|
-
the decision
|
|
443
|
-
|
|
444
|
+
contradicts, narrows or unrelated. When a provider outside the Jev family answers,
|
|
445
|
+
`learn` records the decision in the `decisions` table as `unverified`. It never
|
|
446
|
+
applies it. The report counts the typed decisions under `jev`. A
|
|
447
|
+
defect goes to the `defects` table and never into the ledger.
|
|
444
448
|
|
|
445
449
|
Armed taste also learns by itself. At session start, `learn` starts in the
|
|
446
|
-
background when
|
|
447
|
-
per repository (`learn_later`, `hooks/tezgah_taste.py::learn_later`). It runs
|
|
448
|
-
with `--no-fallback` and only when a
|
|
450
|
+
background when new signals arrived since the last start. It starts at most
|
|
451
|
+
once an hour per repository (`learn_later`, `hooks/tezgah_taste.py::learn_later`). It runs
|
|
452
|
+
with `--no-fallback` and only when a Jev carrier resolves, so it never pays a
|
|
449
453
|
generative provider for a decision it could not apply. It does not run on
|
|
450
|
-
Windows, which has no ledger lock yet. The stamp
|
|
451
|
-
cache dir, in `taste-learn/`.
|
|
454
|
+
Windows, which has no ledger lock yet. The stamp is a `taste_learn` row of the
|
|
455
|
+
cache's `tezgah.db`. The log lives under the cache dir, in `taste-learn/`.
|
|
452
456
|
|
|
453
|
-
The
|
|
454
|
-
`~/.config/tezgah/taste/
|
|
455
|
-
(
|
|
457
|
+
The repository's learnings live in the same `taste.db`. User-scope learnings live in
|
|
458
|
+
`~/.config/tezgah/taste/taste.db` (`hooks/tezgah_taste_ledger.py`). Confidence is
|
|
459
|
+
(r+1)/(r+s+2) over supporting
|
|
456
460
|
and contradicting evidence. Each piece counts at its decision probability, and
|
|
457
461
|
the ledger forgets it per day at its scope's rate (`confidence`,
|
|
458
462
|
`hooks/tezgah_taste_ledger.py::confidence`). A learning turns active in its
|
|
@@ -465,8 +469,34 @@ ledgers. `learn`, `accept`, `reject` and `edit` rewrite them one at a time on
|
|
|
465
469
|
this machine, under a lock on `~/.config/tezgah/taste/ledger.lock` (`locked`,
|
|
466
470
|
`hooks/tezgah_taste_ledger.py::locked`). The lock spans every repository,
|
|
467
471
|
because each command also rewrites the user ledger. A second one exits 2 and
|
|
468
|
-
names the holder.
|
|
469
|
-
|
|
472
|
+
names the holder.
|
|
473
|
+
|
|
474
|
+
One module owns both databases (`taste`, `hooks/tezgah_store.py::taste`). Each
|
|
475
|
+
runs SQLite in WAL mode with a 5-second busy timeout. The directory is 0700 and
|
|
476
|
+
the database file 0600. A row keeps its JSON fields as before. A hook drops a
|
|
477
|
+
row it cannot write, and the CLI's decision, defect and label rows raise
|
|
478
|
+
instead (`append`, `hooks/tezgah_taste_ledger.py::append`). An open of a
|
|
479
|
+
database imports the files it replaces (`_import`,
|
|
480
|
+
`hooks/tezgah_store.py::_import`). `ledger.json` and `gate.json` import only
|
|
481
|
+
into empty tables. The `.jsonl` row files (`signals`, `decisions`, `defects`,
|
|
482
|
+
`labels`, `injected`) import on every open that finds one, so rows an older
|
|
483
|
+
install wrote later still land. The import holds the old writer's flock on the
|
|
484
|
+
file. The `imported` table records how many bytes of each file went in, in the
|
|
485
|
+
same transaction as the rows. The next open therefore takes only the bytes a
|
|
486
|
+
writer added since, and a torn last line waits for its newline. After the commit
|
|
487
|
+
the import renames a fully read file to `<name>.imported`, or `.imported.N`
|
|
488
|
+
beside an earlier one, and never deletes one. A process killed before that
|
|
489
|
+
rename imports nothing twice. A file that does not read or parse stays in
|
|
490
|
+
place. The import retries it only once its size or mtime changes. The old
|
|
491
|
+
per-session `taste-notes/` files under the cache dir are not carried over: they
|
|
492
|
+
never named their repository. A session open during the upgrade can see one
|
|
493
|
+
write note again.
|
|
494
|
+
|
|
495
|
+
Every command that opens the store exits 2 when the repository's `.tezgah`
|
|
496
|
+
came with the clone (`store_or_exit`, `bin/tezgah-taste::store_or_exit`). It
|
|
497
|
+
then reads, imports and creates nothing there. The refusal also applies to a
|
|
498
|
+
repository whose `.tezgah` is a symlink or whose git index cannot be read. Run
|
|
499
|
+
the command from another checkout to manage the user-scope learnings.
|
|
470
500
|
|
|
471
501
|
Session start injects the active learnings (`block`,
|
|
472
502
|
`hooks/tezgah_taste_ledger.py::block`). A rule needs confidence 0.8 and a
|
|
@@ -475,11 +505,12 @@ labelled sample (`calibration`, `hooks/tezgah_taste_ledger.py::calibration`). A
|
|
|
475
505
|
hint needs 0.6. A learning whose evidence paths are all gone is withheld. A
|
|
476
506
|
write also carries the in-scope learnings it has not yet shown in the session,
|
|
477
507
|
on every host's post-tool channel (`write_note`, `hooks/tezgah_taste.py::write_note`).
|
|
508
|
+
The repository's database keeps the learnings each session has already been shown.
|
|
478
509
|
`gate` compares preference corrections per writing turn before and after the
|
|
479
510
|
first injection, by when each turn happened. Once both arms hold 980 turns and
|
|
480
511
|
the after-rate is not lower
|
|
481
|
-
at one-sided p < 0.1,
|
|
482
|
-
`hooks/tezgah_taste_ledger.py::gate`). `mine` reads omp's top-level session
|
|
512
|
+
at one-sided p < 0.1, injection stops (`gate`,
|
|
513
|
+
`hooks/tezgah_taste_ledger.py::gate`). The database keeps that stop. `mine` reads omp's top-level session
|
|
483
514
|
files and Claude's top-level transcripts whose cwd is under `--root`
|
|
484
515
|
(`cmd_mine`, `bin/tezgah-taste::cmd_mine`). `measure` and `rate` use a fixed
|
|
485
516
|
labelling rule (`RULE`, `bin/tezgah-taste::RULE`).
|
|
@@ -633,7 +664,7 @@ or its optional tools and config, on its own initiative
|
|
|
633
664
|
|
|
634
665
|
## Triage: `tezgah-triage` and the `judge-off` switch
|
|
635
666
|
|
|
636
|
-
One judgement seam serves five callers, each of them asking
|
|
667
|
+
One judgement seam serves five callers, each of them asking the judge a batched
|
|
637
668
|
question instead of paying an agent to read a page: the analyze-app loop's snapshot
|
|
638
669
|
triage (`bin/tezgah-triage`), `bin/tezgah-docs` for a query its keyword index
|
|
639
670
|
cannot place, and the prompt-path skill hint (`hooks/tezgah_skill_pick.py`), the
|
|
@@ -672,7 +703,10 @@ made - no credential, the switch below, or a failed call - and the loop reads th
|
|
|
672
703
|
tree directly instead.
|
|
673
704
|
|
|
674
705
|
The state leaves the machine. A judgement sends the state and the questions to
|
|
675
|
-
|
|
706
|
+
the host of whichever provider answers - the session's own CLI (omp, claude,
|
|
707
|
+
opencode, cursor, codex), or the chosen Jev
|
|
708
|
+
carrier's (`api.typesafe.ai`, `openrouter.ai`, `api.cloudflare.com` or a
|
|
709
|
+
`JEV_API_BASE_URL`, see [judge](judge.md)) (`ask()`, `hooks/tezgah_judge.py::ask`) - for the triage that is
|
|
676
710
|
the snapshot's own text, so a screen carrying personal data is read by a third
|
|
677
711
|
party, and for the docs fallback it is the reader's query. Nothing else goes: no
|
|
678
712
|
session id, no workspace path, no credential beyond the bearer header, and the
|
|
@@ -682,11 +716,16 @@ request cannot drift. That is why the two shell callers are on-demand - an agent
|
|
|
682
716
|
asks for a judgement, no gate does - while the skill hint asks only with its own
|
|
683
717
|
marker armed, and why the switch below is the off button for the whole path.
|
|
684
718
|
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
`
|
|
689
|
-
|
|
719
|
+
With no session CLI, the third party is the Jev model through one of its carriers.
|
|
720
|
+
TypeSafe's credential resolves from `TYPESAFE_API_KEY`, else from
|
|
721
|
+
`~/.config/typesafe/key`, else from the active omp login (`typesafe_key()`,
|
|
722
|
+
`hooks/tezgah_paths.py::typesafe_key`). `jev` chooses which of the other Jev
|
|
723
|
+
carriers the seam asks - OpenRouter's System One endpoint, Cloudflare Workers AI
|
|
724
|
+
and any System One-compatible URL (`jev_carriers()`,
|
|
725
|
+
`hooks/tezgah_paths.py::jev_carriers`). The OpenRouter chat fallback's key rides
|
|
726
|
+
`OPENROUTER_API_KEY` and then `~/.config/openrouter/key` (`openrouter_key()`,
|
|
727
|
+
`hooks/tezgah_judge.py::openrouter_key`). When no carrier answers, the same
|
|
728
|
+
questions go to that chat fallback. The file is the channel that
|
|
690
729
|
matters on a machine exporting the variable from `~/.zshenv`: a hook or a bin tool
|
|
691
730
|
runs in a non-interactive shell, where that export never ran, so the file is what a
|
|
692
731
|
judgement actually resolves. Cost is input tokens alone - $0.042 per
|
package/docs/skills.md
CHANGED
|
@@ -32,7 +32,7 @@ skill is simply not read.
|
|
|
32
32
|
|
|
33
33
|
opencode's plugin does inject prompt-time text - the builder's per-turn block,
|
|
34
34
|
the conditional rules included
|
|
35
|
-
(`hosts/opencode/plugins/tezgah.js:
|
|
35
|
+
(`hosts/opencode/plugins/tezgah.js:2629-2658`) - but that text names at most one
|
|
36
36
|
skill (the hint), never the roster, so the installer writes one:
|
|
37
37
|
`~/.config/tezgah/opencode-skills.md` always-on and
|
|
38
38
|
`opencode-skills.full.md` on demand (`bin/tezgah-setup::SKILL_ROUTER`, `bin/tezgah-setup::SKILL_ROUTER_FULL`), the first
|
|
@@ -176,7 +176,7 @@ it (`tests/test_skills.py::SkillStandards`).
|
|
|
176
176
|
| `no-ai-slop` | Edits a draft sharper and more human without losing the voice, or flags slop; fires on a draft review, on a draft in a language other than English, on text that must keep terms, identifiers or unit symbols verbatim, and before publishing English prose tezgah writes itself. |
|
|
177
177
|
| `analyze-app` | Inspects and drives a running web or mobile app from its accessibility / DOM / view tree, its rendered screen (screenshots, blur and grayscale tests, breakpoints) and measurements taken in the page; fires when a task must verify behaviour or read the UI, not just read source. |
|
|
178
178
|
| `plan-add` | Creates a plan under `.tezgah/plans/open/` with its own `allowed_paths:` scope, bootstraps the `.tezgah/plans/` layer if missing, commits it in the private `.tezgah` repository (`git -C .tezgah`, never the project's git); fires on `/tezgah:plan-add`, "track this as a plan". |
|
|
179
|
-
| `plan-status` | Reads every open plan, enriches rows with live PR state from `gh`, rewrites the README table (committed in the private `.tezgah` repository) and recommends the next plan; fires on `/tezgah:plan-status` or "what plans are open". |
|
|
179
|
+
| `plan-status` | Reads every open plan and `tezgah-task status` (each plan's Acceptance boxes ticked, the plans its `after:` waits on, the ready plans that can run at once), enriches rows with live PR state from `gh`, rewrites the README table (committed in the private `.tezgah` repository) and recommends the next plan; fires on `/tezgah:plan-status` or "what plans are open". |
|
|
180
180
|
| `plan-sync` | Closes finished plans whose PR merged or closed through `tezgah-task close`, which moves a plan to `.tezgah/plans/done/` as done only when it records an approving `review:`; commits one message per plan in the private `.tezgah` repository; fires on `/tezgah:plan-sync`, "sync plans". |
|
|
181
181
|
| `research` | Runs an auditable research line - two loops, protocol before run, six-dimension review, provenance; fires when the deliverable is evidence: a lit review, a hypothesis, a benchmark or ablation. |
|
|
182
182
|
| `product-analysis` | Runs a product analysis across five axes - value (HEART), usability (heuristics, the cognitive walkthrough, WCAG 2.2, axe-core, the running app read through `analyze-app` down to component x state, tree and image both), feasibility (a cited `path:line`), competition (a dated teardown) and triage (keep/fix/cut/bet) - with one named evidence class per finding, executed as a research line; fires on a product, feature, retention or roadmap question rather than a code change. |
|
package/docs/status-line.md
CHANGED
|
@@ -96,9 +96,9 @@ measure the surface cannot see, then armed-and-used or armed-not-used.
|
|
|
96
96
|
| `graph` | a code-graph tool call (an `mcp__…codegraph…` tool) | `.no-graph` — a missing binary is `idx`'s report, not this mark's |
|
|
97
97
|
| `orch` | a delegate call (`Task`/`Agent`/`spawn_agent`) or a subagent start | `orchestrate-off` |
|
|
98
98
|
| `judge` | a shell command really ran `tezgah-triage` or `tezgah-docs` | `judge-off`, or no credential: no `TYPESAFE_API_KEY` and no non-empty `~/.config/typesafe/key`, and neither the fallback's `OPENROUTER_API_KEY` nor its `~/.config/openrouter/key` |
|
|
99
|
-
| `gate` | never `on`: shown only as `gate✗`, right after the version, in two cases. One: the prompt hook found at least 3 gated calls in this session's transcript since its first turn: write and shell calls, every one of which the gate answers with a `began` or a `deny` row (Bash/PowerShell/Edit/Write/MultiEdit/NotebookEdit on Claude, `exec_command`/`shell`/`apply_patch` on Codex). Over the same span the ledger holds no row from the gate. A PostToolUse row is no proof: that hook keeps writing while a broken PreToolUse lets every call through. The same prompt carries the line "tezgah gate inactive on this host: ...". Two: a kill switch appeared after the session's first prompt; the prompt hook writes one `disarm` row naming it (`disarmed`, baseline `<cache>/
|
|
100
|
-
| `drift` | never `on`: shown as `drift✗` when a session start found tezgah's own hook entries changed since install (`hooks/tezgah_attest.py::run`). One mark per session and host, `<cache>/
|
|
101
|
-
| `crash` | never `on`: shown as `crash✗` when a hook of this session could not import its core and ran without tezgah (`hooks/tezgah_guard.py::import_failed`, mark `~/.cache/tezgah/
|
|
99
|
+
| `gate` | never `on`: shown only as `gate✗`, right after the version, in two cases. One: the prompt hook found at least 3 gated calls in this session's transcript since its first turn: write and shell calls, every one of which the gate answers with a `began` or a `deny` row (Bash/PowerShell/Edit/Write/MultiEdit/NotebookEdit on Claude, `exec_command`/`shell`/`apply_patch` on Codex). Over the same span the ledger holds no row from the gate. A PostToolUse row is no proof: that hook keeps writing while a broken PreToolUse lets every call through. The same prompt carries the line "tezgah gate inactive on this host: ...". Two: a kill switch appeared after the session's first prompt; the prompt hook writes one `disarm` row naming it (`disarmed`, baseline a `switches` row of `<cache>/tezgah.db`). The mark is a `gate_inactive` row of the same database (`gate_inactive`, audit Phase 1.3) | a row from the gate appears and no switch armed mid-session is still armed |
|
|
100
|
+
| `drift` | never `on`: shown as `drift✗` when a session start found tezgah's own hook entries changed since install (`hooks/tezgah_attest.py::run`). One mark per session and host, a `harness_drift` row of `<cache>/tezgah.db` keyed `<session hash>.<host>`; the session's `attest` row names what drifted | the next clean start of that host in that session |
|
|
101
|
+
| `crash` | never `on`: shown as `crash✗` when a hook of this session could not import its core and ran without tezgah (`hooks/tezgah_guard.py::import_failed`, mark an `import_crash` row of `~/.cache/tezgah/tezgah.db` keyed `<session hash>`). When `tezgah_context` itself is the module that failed, `statusline.py` prints `tezgah ✗ core import failed` instead of the line | never by itself: the mark stays for the session |
|
|
102
102
|
|
|
103
103
|
Read `on` for `consult`/`research`/`judge` as "installed and usable", not "you
|
|
104
104
|
must use it": a missing key or binary reads the same red as a kill switch
|
|
@@ -147,9 +147,9 @@ have written: a mark the literal omits reads `info` there rather than a claim.
|
|
|
147
147
|
|
|
148
148
|
## The used-kinds channel
|
|
149
149
|
|
|
150
|
-
A used measure is one
|
|
151
|
-
`<cache_dir>/sessions/<slug(session_id)>.jsonl`, one `{"kind": …}` per line
|
|
150
|
+
A used measure is one row of the session store, keyed by `slug(session_id)` and kind
|
|
152
151
|
(`record()`, `hooks/tezgah_context.py::record`; `used()` reads it back, `hooks/tezgah_context.py::used`).
|
|
152
|
+
The store is the `used` table of `<cache_dir>/tezgah.db`.
|
|
153
153
|
`cache_dir()` is `~/.cache/tezgah` with a temp fallback for sandboxed hosts
|
|
154
154
|
(`hooks/tezgah_paths.py::CACHE`, `hooks/tezgah_paths.py::fallback_cache`, `hooks/tezgah_paths.py::cache_dir`); a missing kind is not an event, so only
|
|
155
155
|
the five kinds are ever written (`hooks/tezgah_context.py::record`).
|
|
@@ -160,8 +160,8 @@ the command really ran it (34-38,84) and is the one writer that can earn `judge`
|
|
|
160
160
|
(a run of `bin/tezgah-triage` or `bin/tezgah-docs`, `shell_kind`, `hooks/tezgah_context.py::shell_kind`)
|
|
161
161
|
— plus `hooks/projects-auto-init.py::main`
|
|
162
162
|
(`orch` on subagent start), `hosts/codex/hook.py::main`,
|
|
163
|
-
`hosts/cursor/hook.py::dispatch`, hosts/opencode/plugins/tezgah.js:
|
|
164
|
-
hosts/opencode/plugins/tezgah.js:
|
|
163
|
+
`hosts/cursor/hook.py::dispatch`, hosts/opencode/plugins/tezgah.js:2144-2151,
|
|
164
|
+
hosts/opencode/plugins/tezgah.js:2702, `hosts/omp/hook.py::handle`. Both Claude (hooks/hooks.json) and dsh
|
|
165
165
|
(hosts/dsh/hooks.json) wire PostToolUse to that one shared hook, which is how dsh
|
|
166
166
|
gets any used mark at all: with no local transcript, the store is the only channel
|
|
167
167
|
its line has (hooks/projects-posttooluse.py:33-37).
|
package/docs/vision.md
CHANGED
|
@@ -22,11 +22,16 @@ the workspace's own records; every number below names where it came from.
|
|
|
22
22
|
runs, 106 minutes, 10 of them green confirmations, in one session - the rule
|
|
23
23
|
"run the full suite once, on the final tree" was already written down and
|
|
24
24
|
`AGENTS.md` said "before every commit".)
|
|
25
|
-
3. **
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
3. **One review per plan, two rounds, fanned out.** Reviewers read the plan's
|
|
26
|
+
whole diff once, in its verification phase. Round one spawns them together,
|
|
27
|
+
one per dimension. Only a confirmed critical or major finding opens round two,
|
|
28
|
+
and round two reads the fix's delta. After round two the user gets what is
|
|
29
|
+
left, or the plan lists it as fix-later. `tezgah-task review` refuses a third
|
|
30
|
+
round (`hooks/tezgah_agents.py::_reviewer_body`, `bin/tezgah-task::cmd_review`).
|
|
31
|
+
(Measured: the old loop opened a round on any finding and ended only on a
|
|
32
|
+
clean round. It reviewed one finished plan round after round. In another plan,
|
|
33
|
+
fixes brought two of four severe defects. So round two reads the fix, and a
|
|
34
|
+
severe finding left after it blocks.)
|
|
30
35
|
4. **Doubt lowers nothing and hides nothing.** An unverifiable check never
|
|
31
36
|
downgrades a finding, an unknown criterion never relaxes a cap, and a line
|
|
32
37
|
whose own criteria are unmet is reported as unanswered rather than concluded.
|
package/hooks/tezgah_agents.py
CHANGED
|
@@ -206,15 +206,22 @@ def _reviewer_body(host):
|
|
|
206
206
|
"is lowered only by evidence: a check you could not complete never\n"
|
|
207
207
|
"downgrades a confirmed finding - keep its severity and mark it\n"
|
|
208
208
|
"provisional. Disclose the order you read the files in.\n\n"
|
|
209
|
-
"Scope and budget.
|
|
210
|
-
"
|
|
211
|
-
"
|
|
212
|
-
"
|
|
213
|
-
"
|
|
214
|
-
"
|
|
215
|
-
"
|
|
216
|
-
"
|
|
217
|
-
"
|
|
209
|
+
"Scope and budget. One review per plan, over its whole diff in the\n"
|
|
210
|
+
"verification phase; at most two rounds - only a confirmed critical or\n"
|
|
211
|
+
"major finding opens round two, a minor or suggestion one is recorded\n"
|
|
212
|
+
"fix-later, and after round two what is left goes to the user or is\n"
|
|
213
|
+
"recorded fix-later, never a third round. A round may be a fan-out of\n"
|
|
214
|
+
"reviewers spawned at once, one per dimension (correctness and contract,\n"
|
|
215
|
+
"security, tests and performance) or per file group: when your brief\n"
|
|
216
|
+
"names a dimension or files, review only those - a sibling owns the rest.\n"
|
|
217
|
+
"Round two reads only the delta since the sha round one recorded\n"
|
|
218
|
+
"(`tezgah-task review` prints it), plus the round-one hunks and direct\n"
|
|
219
|
+
"callers of the symbols that delta touches, and marks every round-one\n"
|
|
220
|
+
"finding closed or open: re-reading the whole diff re-reports what was\n"
|
|
221
|
+
"already triaged. Test results are input, not a task - read the author's\n"
|
|
222
|
+
"output file; do not run the suite, and run at most one `-k` test when a\n"
|
|
223
|
+
"specific accusation needs it. The same defect family confirmed in both\n"
|
|
224
|
+
"rounds is a sign the design needs a pivot, not another patch.\n\n"
|
|
218
225
|
"%s" % (_graph_howto(host), _blast_radius(host), EMPTY_IS_NOT_PROOF,
|
|
219
226
|
_may_use(host)))
|
|
220
227
|
|
|
@@ -343,7 +350,8 @@ ROLES = (
|
|
|
343
350
|
("tezgah-reviewer",
|
|
344
351
|
"Adversarial, read-only review of a diff, branch or PR: derives the blast "
|
|
345
352
|
"radius, checks correctness, contract, security, tests and performance, and "
|
|
346
|
-
"classifies every finding confirmed/refuted/unverified."
|
|
353
|
+
"classifies every finding confirmed/refuted/unverified. One review per plan, "
|
|
354
|
+
"at most two rounds; spawn one per dimension in one message for a large diff.",
|
|
347
355
|
lambda infra: infra["caps"]["graph"], _reviewer_body, True),
|
|
348
356
|
("tezgah-cheap",
|
|
349
357
|
"Mechanical, fully specified edits on the cheapest model tier: renames, "
|
|
@@ -389,7 +397,10 @@ def _orch_body(names):
|
|
|
389
397
|
"verify; you delegate execution and read the evidence back. Available\n"
|
|
390
398
|
"specialists: %s.\n\n"
|
|
391
399
|
"Fan out by default: when the request or your todo list holds two or more\n"
|
|
392
|
-
"independent items, spawn one subagent per item in ONE
|
|
400
|
+
"independent items, count them and spawn one subagent per item in ONE\n"
|
|
401
|
+
"message - at every phase: a plan's tasks, its acceptance commands and the\n"
|
|
402
|
+
"sources of a survey are items too, and checks that share no state run as\n"
|
|
403
|
+
"tool calls in one message, never a queue. Give\n"
|
|
393
404
|
"every writing slice its own branch and checkout - `orca worktree create\n"
|
|
394
405
|
"--name <slug> --parent-worktree active --json` when the session runs\n"
|
|
395
406
|
"inside Orca, else `git worktree add -b <branch> <absolute path>` - and\n"
|