@r1z4x/tezgah 1.4.0 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/CHANGELOG.md +120 -1
  2. package/MANIFEST +0 -1
  3. package/README.md +6 -4
  4. package/bin/tezgah-docs +3 -3
  5. package/bin/tezgah-route +12 -6
  6. package/bin/tezgah-setup +67 -24
  7. package/bin/tezgah-status +3 -1
  8. package/bin/tezgah-task +132 -39
  9. package/bin/tezgah-taste +49 -31
  10. package/bin/tezgah-triage +12 -7
  11. package/docs/architecture.md +1 -1
  12. package/docs/clarity-baseline.json +7 -7
  13. package/docs/contract.md +21 -11
  14. package/docs/environment.md +3 -1
  15. package/docs/evidence.md +4 -3
  16. package/docs/glossary.md +1 -1
  17. package/docs/hosts.md +9 -0
  18. package/docs/judge.md +95 -27
  19. package/docs/layers.md +1 -1
  20. package/docs/models.md +17 -7
  21. package/docs/operations.md +23 -13
  22. package/docs/skills.md +1 -1
  23. package/docs/testing.md +1 -1
  24. package/docs/vision.md +10 -5
  25. package/hooks/tezgah_agents.py +149 -105
  26. package/hooks/tezgah_context.py +42 -7
  27. package/hooks/tezgah_judge.py +252 -79
  28. package/hooks/tezgah_models.py +50 -3
  29. package/hooks/tezgah_paths.py +180 -27
  30. package/hooks/tezgah_policy.py +41 -10
  31. package/hooks/tezgah_replay.py +26 -15
  32. package/hooks/tezgah_task.py +160 -46
  33. package/hooks/tezgah_taste.py +2 -2
  34. package/hooks/tezgah_taste_ledger.py +13 -4
  35. package/hosts/codex/hook.py +5 -0
  36. package/package.json +1 -2
  37. package/skills/harness/SKILL.md +4 -0
  38. package/skills/plan-add/SKILL.md +23 -7
  39. package/skills/plan-add/render_table.py +3 -2
  40. package/skills/plan-status/SKILL.md +12 -4
  41. package/skills/plan-sync/SKILL.md +19 -9
  42. package/skills/tezgah-contract/SKILL.md +29 -14
  43. package/agents/tezgah-reviewer.md +0 -70
@@ -56,6 +56,7 @@ the kill switches are files, not variables, and live in
56
56
  | `TEZGAH_ORCA_BIN` | `hooks/tezgah_orca.py::cli` | `ORCA_CLI_COMMAND`, then `$ORCA_CLI_BIN_DIR/orca`, then `orca` on PATH (`orca-ide` on Linux outside Orca), then the macOS app's CLI | The Orca CLI `tezgah-status --orca` and `tezgah-setup --status` ask. A missing path is no CLI, never a fallback: the test suite points it at one. |
57
57
  | `TEZGAH_OMP_BIN` | `hooks/tezgah_paths.py::omp_bin` | `omp` on PATH | The omp binary the installer, the model table, consult's session-model lookup and the judgement seam's session call run; tests point it at a stand-in. |
58
58
  | `TEZGAH_CLAUDE_BIN` | `claude_bin`, `hooks/tezgah_paths.py::claude_bin` | `claude` on PATH | The Claude Code CLI `--install` registers the plugin with (`claude plugin`) and the judgement seam asks in a Claude Code session; the test suite points it at a path that does not exist, or at a stand-in. |
59
+ | `TEZGAH_OPENCODE_BIN`, `TEZGAH_CURSOR_BIN`, `TEZGAH_CODEX_BIN` | `hooks/tezgah_paths.py::opencode_bin`, `hooks/tezgah_paths.py::cursor_bin`, `hooks/tezgah_paths.py::codex_bin` | `opencode`, `cursor-agent`, `codex` on PATH or a per-user bin dir | The CLI the judgement seam asks in that host's session; tests point them at stand-ins. |
59
60
  | `TEZGAH_DSH_BIN` | `bin/tezgah-setup::reconcile_json_servers` | the profile-local entry, then `bin/tezgah-dsh`, then npx | How the installer invokes the dsh CLI. |
60
61
  | `TEZGAH_INDEX_BIN` | `bin/tezgah-dsh:19`, `bin/tezgah-dsh.cmd` | `~/.config/tezgah/bin/tezgah-index` | The index worker `tezgah-dsh` warms before it starts dsh. |
61
62
  | `TEZGAH_STATUS_BIN` | `hosts/dsh/statusline/lib/index.js:24`, `hosts/opencode/tui/tezgah-tui.tsx` | `~/.config/tezgah/bin/tezgah-status` | The status renderer the dsh status line and the opencode TUI call. |
@@ -73,7 +74,7 @@ the kill switches are files, not variables, and live in
73
74
 
74
75
  | Variable | Read by | Default | Effect |
75
76
  |---|---|---|---|
76
- | `OMPCODE`, `CLAUDECODE` | `hooks/tezgah_paths.py::session_cli`, `bin/consult`, `hooks/tezgah_models.py::worker_model` | set by the host | Name the host session: the judgement seam asks that host's CLI first (`OMPCODE` wins, omp sets both). The test suite unsets both. |
77
+ | `OMPCODE`, `CLAUDECODE`, `OPENCODE`, `CURSOR_AGENT`, `CURSOR_VERSION`, `CODEX_THREAD_ID` | `hooks/tezgah_paths.py::session_cli` (all), `bin/consult`, `hooks/tezgah_models.py::worker_model` (the first two) | set by the host | Name the host session: the judgement seam asks that host's CLI first, in the order listed (omp sets both of the first two, so `OMPCODE` wins; Cursor sets `CURSOR_AGENT` in its shell tool and `CURSOR_VERSION` in its hooks; Codex sets `CODEX_THREAD_ID` in its shell tool only). The test suite unsets all of them. |
77
78
  | `TYPESAFE_API_KEY` | `hooks/tezgah_judge.py::key`, `hooks/tezgah_paths.py` | `~/.config/typesafe/key` | The judgement seam's first third-party credential, used as `fallback` in `config.json` allows ([judge](judge.md)); omp also reads it for its own judge. |
78
79
  | `OPENROUTER_API_KEY` | `hooks/tezgah_judge.py::openrouter_key`, `bin/consult`, `bin/codegen`, `hooks/tezgah_models.py` | `~/.config/openrouter/key` | The seam's chat fallback, consult's and codegen's `openrouter` provider, and omp's `any` family. |
79
80
  | `DEEPSEEK_API_KEY`, `INCEPTION_API_KEY` | `bin/consult`, `bin/codegen`, `hooks/tezgah_replay.py::label_model` | `~/.config/deepseek/key`, `~/.config/inception/key` | The `deepseek` and `inception` providers of consult and codegen; `DEEPSEEK_API_KEY` alone is also the replay sheet's `--provider deepseek` rater. |
@@ -82,6 +83,7 @@ the kill switches are files, not variables, and live in
82
83
  | `TEZGAH_OPENROUTER_URL` | `hooks/tezgah_judge.py::openrouter_url` | OpenRouter's chat-completions URL | Repoints the seam's chat fallback (tests). Plain `http` is refused unless the host is this machine. |
83
84
  | `TEZGAH_DEEPSEEK_URL` | `hooks/tezgah_replay.py::_deepseek` | `https://api.deepseek.com/chat/completions` | Repoints the replay sheet's `--provider deepseek` rater (tests). Plain `http` is refused unless the host is this machine. |
84
85
  | `TEZGAH_JUDGE_MODEL` | `hooks/tezgah_judge.py::fallback_model` | the cheap `any` row of the models table | The model the seam's chat fallback asks. |
86
+ | `TEZGAH_JUDGE_CLI` | `hooks/tezgah_paths.py::session_cli` | `judge_cli` in `config.json`, else `auto` | The session CLI the judgement seam asks: `omp`, `claude`, `opencode`, `cursor` or `codex`, or `auto` to read the host's marker. A named CLI that is not installed means no session CLI, never another one. |
85
87
 
86
88
  ## consult and codegen
87
89
 
package/docs/evidence.md CHANGED
@@ -785,11 +785,12 @@ one rater's totals would show the other rater those labels.
785
785
 
786
786
  `replay --label-model --prompt <file>` is rater 2 when that rater is a model. It sends each sheet
787
787
  row to the judge seam (`hooks/tezgah_judge.py`) as one `choice` question and writes
788
- `labels-model.jsonl` beside the sheet. It redacts the row's text first (`ti.redact`), and it runs
789
- only when TypeSafe answers. The prompt file holds each set's instructions and criteria. A reply
788
+ `labels-model.jsonl` beside the sheet. It redacts the row's text first (`ti.redact`). It runs
789
+ only when a Jev carrier answers: TypeSafe, OpenRouter System One, Cloudflare Workers AI or a
790
+ compatible endpoint. `--provider jev` names it, and the old name `typesafe` still works. The prompt file holds each set's instructions and criteria. A reply
790
791
  with no label from those criteria leaves its row unlabelled. The command writes each label as it
791
792
  arrives, so a run that stops early keeps what it paid for. A second run asks only the unlabelled rows. Every run prints the input tokens and their cost. The sheet rows leave the machine for
792
- `api.typesafe.ai`. The key and the corpus stay on it. Kappa against a model rater
793
+ the Jev carrier's endpoint. The key and the corpus stay on it. Kappa against a model rater
793
794
  measures human-model agreement, not inter-rater reliability.
794
795
 
795
796
  `--provider deepseek --model deepseek-v4-pro` asks the same questions from the same prompt file
package/docs/glossary.md CHANGED
@@ -98,7 +98,7 @@ A mark consumed by its own first use, so the thing it protects happens once: the
98
98
  An opt-out file in the repo tree - `.no-ponytail`, `.no-adhd`, `.no-graph`, `.no-lessons`, `.no-taste` - collected by `repo_marks()` walking up to the enclosing root (`hooks/tezgah_context.py::repo_marks`). Not a [kill switch](#kill-switch): a mark travels with the repo, a switch with the machine.
99
99
 
100
100
  ### plan
101
- A piece of work spanning sessions, one markdown file with frontmatter under `<repo root>/.tezgah/plans/open/`, moved to `.tezgah/plans/done/` when closed, up to three of them injected by `open_plans()` (`hooks/tezgah_context.py::open_plans`) and its work happening on a `plan/NNN-slug` branch. Not a TODO in code.
101
+ A piece of work spanning sessions, one markdown file with frontmatter under `<repo root>/.tezgah/plans/open/`, moved to `.tezgah/plans/done/` when closed, up to three of them injected by `open_plans()` (`hooks/tezgah_context.py::open_plans`) and its work happening on a `plan/NNN-slug` branch. An optional `after:` names the plans it waits on, and `tezgah-task status` reads it with the ticked Acceptance boxes (`hooks/tezgah_task.py::queue`). Not a TODO in code, and not the host's session todo list, which no reader syncs with it.
102
102
 
103
103
  ### plan table
104
104
  The status table in `.tezgah/plans/README.md` (`.tezgah/plans/README.md:9`), owned by the `plan-status` skill (`skills/plan-status/SKILL.md:1`) and among the first blocks the [budget](#budget) gives up because it lives on disk (`hooks/tezgah_context.py::DROP_ORDER`). Not the injected list of open plans.
package/docs/hosts.md CHANGED
@@ -71,6 +71,8 @@ shared config, contract hash and the CLI symlinks every host shell can call
71
71
  `~/.claude/settings.json` - `statusLine` (`bin/tezgah-setup::wire_claude_statusline`) and the attribution
72
72
  off-switch (`bin/tezgah-setup::wire_claude_attribution`) - and the always-on block in
73
73
  `~/.claude/CLAUDE.md` (`CLAUDE_RULES`), Claude's own user-level memory file.
74
+ The subagent roles go to `~/.claude/agents/tezgah-*.md`
75
+ (`bin/tezgah-setup::write_user_agents`).
74
76
  Its hook manifest is the plugin's own
75
77
  `hooks/hooks.json`; no `hooks.json` is written into `~/.claude`. Claude runs a
76
78
  **copy** of the checkout under `~/.claude/plugins/cache`, refreshed by
@@ -84,6 +86,8 @@ shared config, contract hash and the CLI symlinks every host shell can call
84
86
  (one group per event, PreToolUse carrying `CODEX_PRETOOL_MATCHER` at `bin/tezgah-setup::CODEX_PRETOOL_MATCHER`),
85
87
  `skills/*` symlinks, `~/.codex/bin/consult`, and `mcp_servers.*` tables in
86
88
  `config.toml` (`ensure_toml_mcp`, `bin/tezgah-setup::ensure_toml_mcp`).
89
+ The subagent roles go to `$CODEX_HOME/agents/tezgah-*.toml`
90
+ (`bin/tezgah-setup::write_user_agents`).
87
91
  - **cursor** - `install_cursor` (`bin/tezgah-setup::install_cursor`): `~/.cursor/hooks.json`
88
92
  (tezgah entries replaced, a user's Orca entries merged), `skills/*` symlinks,
89
93
  `mcp.json` servers, and `cli-config.json` `statusLine`.
@@ -159,6 +163,11 @@ and drops the later ones.
159
163
  | skills | codex | `.agents/skills` from the cwd up to the repo root, `~/.agents/skills`, `$CODEX_HOME/skills`; one name in two dirs is listed twice, not merged | links in `$CODEX_HOME/skills` |
160
164
  | skills | cursor | `.agents/skills`, `.cursor/skills`, `~/.agents/skills`, `~/.cursor/skills`, then Claude's and Codex's project and user dirs; no documented winner | links in `~/.cursor/skills` |
161
165
  | skills | opencode | its own skill tool is denied; the router lists `~/.config/opencode/skills`, `~/.claude/skills`, `~/.agents/skills`, first wins (`bin/tezgah-setup::skill_groups`) | links and the router |
166
+ | agents | claude | managed, the `claude --agents` flag, `.claude/agents`, `~/.claude/agents`, plugin agents namespaced `plugin:name`; a dir absent at start is not watched | `~/.claude/agents`, written at install |
167
+ | agents | codex | `$CODEX_HOME/agents`, `/etc/codex/agents`, `.codex/agents` (trusted repos only), read at config load | `$CODEX_HOME/agents`, written at install |
168
+ | agents | cursor | `cursor-agent`: `.cursor/agents`, then `.claude/agents`, `.grok/agents`, plugin agents; first name wins; no user dir; read at process start. The editor's docs add `~/.cursor/agents` and `~/.claude/agents` | `.cursor/agents` per repo at session start, in the CLI's line format (`hooks/tezgah_agents.py::render_md_cursor`) |
169
+ | agents | opencode | `~/.config/opencode/agents`, `.opencode/agents`, merged per field | `config` hook injection, `.opencode/agents` as fallback |
170
+ | agents | omp | `.omp/agents`, `~/.omp/agent/agents`, extensions, bundled | `~/.omp/agent/agents`, written at install |
162
171
  | hooks | claude | user `settings.json` rows (Orca's), project rows, the plugin's `hooks/hooks.json` (tezgah's); every matching row runs, none is ordered before another | plugin rows |
163
172
  | hooks | codex, cursor | `hooks.json` array order, then the repo's `.codex/` or `.cursor/hooks.json`; Codex runs a row only once its hash is trusted | own rows after Orca's |
164
173
  | hooks | opencode | every `*.js`/`*.ts` in `plugins/` and `plugin/`; `tezgah.js` is linked into both, and its `globalThis.__tezgahPluginLoaded` guard makes the second load return `{}` | the plugin |
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`). One endpoint and one key path
18
- are module constants (`URL`, `hooks/tezgah_judge.py::URL`; `KEY_FILE`,
19
- `hooks/tezgah_judge.py::KEY_FILE`), one opener is shared by every call (`OPENER`,
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`, `TEZGAH_OPENROUTER_URL`
29
- and `TEZGAH_OMP_BIN`/`TEZGAH_CLAUDE_BIN` are the test seams.
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. TypeSafe is never sent one (`text`,
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 - `api.typesafe.ai` or `openrouter.ai` (`OPENROUTER_URL`,
48
- `hooks/tezgah_judge.py::OPENROUTER_URL`) - reads it only as the `fallback`
49
- setting allows. For the triage the state is the screen's own text, so whoever
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,76 @@ 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`): `omp` when
93
- `OMPCODE` is set, else `claude` when `CLAUDECODE` is. It runs headless with no
94
- tools, rules, skills, extensions, MCP servers or settings (`SESSION_ARGV`,
95
- `hooks/tezgah_judge.py::SESSION_ARGV`). Measured 2026-10-07, a call cost $0.007
96
- on claude and $0.015 on omp. The session's own credential pays, OAuth subscription
97
- included. The model and usage are read from the CLI's JSON output
98
- (`_session_request`, `hooks/tezgah_judge.py::_session_request`). Then come the
99
- third parties. TypeSafe reads `TYPESAFE_API_KEY`, else `~/.config/typesafe/key`
100
- (`key`, `hooks/tezgah_judge.py::key`). OpenRouter reads `OPENROUTER_API_KEY`, else
101
- `~/.config/openrouter/key` (`openrouter_key`, `hooks/tezgah_judge.py::openrouter_key`).
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 is sent as it is, and OpenRouter maps it onto `typesafe/` |
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
+ The Workers AI ids `@cf/cloudflare/clef` and `@cf/cloudflare/clef-flash` name
152
+ the same two (`jev_name`, `hooks/tezgah_paths.py::jev_name`).
153
+ `tezgah-setup --jev <value>` writes the config key
154
+ (`set_jev`, `bin/tezgah-setup::set_jev`). `auto`
155
+ asks every carrier that resolves, in the table's order (`jev_carriers`,
156
+ `hooks/tezgah_paths.py::jev_carriers`). The seam asks a named carrier without
157
+ its credential nothing, and nothing stands in for it: the call records
158
+ `no credential (jev=<value>)` as a failure. `tezgah-status --judge` prints the
159
+ choice and the carriers it resolves to (`jev_summary`,
160
+ `hooks/tezgah_judge.py::jev_summary`). A cost row prices the carrier that
161
+ answered (`input_price`, `hooks/tezgah_judge.py::input_price`). Per 1M input
162
+ tokens: $0.042 for Jev, $0.24 for Clef, $0.09 for Clef-flash. Output is free.
163
+
164
+ OpenRouter's chat fallback reads the OpenRouter key (`openrouter_key`,
165
+ `hooks/tezgah_judge.py::openrouter_key`).
102
166
  It asks the table's cheap row unless `TEZGAH_JUDGE_MODEL` names another
103
- (`fallback_model`, `hooks/tezgah_judge.py::fallback_model`). The key file is
167
+ (`fallback_model`, `hooks/tezgah_judge.py::fallback_model`). The key files are
104
168
  read because a hook runs where `~/.zshenv` never did. A chat answer is asked for in prose
105
169
  (`CHAT_SYSTEM`, `hooks/tezgah_judge.py::CHAT_SYSTEM`) and filtered to the ids
106
170
  asked, a wrongly typed answer dropped (`_chat_answers`,
@@ -111,8 +175,8 @@ asked, a wrongly typed answer dropped (`_chat_answers`,
111
175
 
112
176
  | `fallback` | Session CLI present | No session CLI |
113
177
  |---|---|---|
114
- | `vendor` (default) | the session CLI only; when it fails, no judgement | TypeSafe, then OpenRouter |
115
- | `any` | the session CLI, then TypeSafe, then OpenRouter | TypeSafe, then OpenRouter |
178
+ | `vendor` (default) | the session CLI only; when it fails, no judgement | the Jev carriers, then the chat fallback |
179
+ | `any` | the session CLI, then the Jev carriers, then the chat fallback | the Jev carriers, then the chat fallback |
116
180
  | `none` | the session CLI only | nobody: `available()` is false |
117
181
 
118
182
  Nothing that stands in is silent. A result carries `provider`, the `model`
@@ -124,7 +188,7 @@ hint's cost row and the router's `route` row also carry `judge=<provider>/<model
124
188
 
125
189
  A caller that needs one provider in particular passes `only` (`named`,
126
190
  `hooks/tezgah_judge.py::named`). `learn`'s decision has to come from a typed
127
- model, so it passes `only=("typesafe",)`. It gets `None` rather than another
191
+ model, so it passes `only=("jev",)`, every Jev carrier that resolves. It gets `None` rather than another
128
192
  provider's answer. The caller named the provider, so the `vendor` order does not
129
193
  apply. `none` still keeps every third party out.
130
194
 
@@ -173,8 +237,9 @@ known (`note`, `hooks/tezgah_integrity.py::note`), counted by the row's kind
173
237
  costs a fraction of a cent, so a cache is not worth its state file, and a naive
174
238
  one would make two runs of the same command disagree. Beside the last-use
175
239
  record, the seam keeps a failure marker (`DOWN_FOR`, 300 s, in `hooks/tezgah_judge.py`). A
176
- call that ends on a 401, 402, 5xx or a session CLI's non-zero exit marks that
177
- provider, endpoint and credential down for five minutes. Until the marker
240
+ 401, 402, 403, 5xx or a session CLI's non-zero exit marks that
241
+ provider, endpoint and credential down for five minutes. A 403 is OpenRouter's
242
+ answer to a key past its limit. Until the marker
178
243
  expires, that provider is skipped and sent no request. A dead key or an empty account then costs
179
244
  one refusal per five minutes, not one per prompt. A rotated key gets a new
180
245
  marker, so the seam asks it at once.
@@ -193,8 +258,11 @@ known (`note`, `hooks/tezgah_integrity.py::note`), counted by the row's kind
193
258
 
194
259
  ## Source of truth
195
260
 
196
- - `hooks/tezgah_judge.py` — the seam: endpoint, key path, `available()`, `ask()`,
197
- the answer accessors, the retry class and the provider-down marker.
261
+ - `hooks/tezgah_judge.py` — the seam: the carriers' endpoints and bodies,
262
+ `available()`, `ask()`, the answer accessors, the retry class and the
263
+ provider-down marker.
264
+ - `hooks/tezgah_paths.py` — the key channels (`typesafe_key`, `jev_carriers`)
265
+ and the carrier choice (`jev_choice`).
198
266
  - `bin/tezgah-triage` — the snapshot triage and the per-state matrix; prints the
199
267
  unit it selected, the characters it saved and the cost it paid.
200
268
  - `bin/tezgah-docs` — the page router; its index match, then its one fallback
package/docs/layers.md CHANGED
@@ -76,7 +76,7 @@ lessons and research lines.
76
76
  Must not own the loop, the model, the tools or the network. tezgah never runs
77
77
  the work and never rolls back on its own - rollback is the explicit
78
78
  `bin/tezgah-rollback` - and the subagents it defines are rendered into a host's
79
- own agent surface for that host to run (`hooks/tezgah_agents.py:2-22`).
79
+ own agent surface for that host to run (`hooks/tezgah_agents.py:2-30`).
80
80
 
81
81
  ## The boundary, one term at a time
82
82
 
package/docs/models.md CHANGED
@@ -59,21 +59,21 @@ invoices).
59
59
  | Host | Family | Where the model lands |
60
60
  |---|---|---|
61
61
  | Claude Code | anthropic | the agent file's alias and `effort:` (`_model_lines`): an alias follows the provider and keeps the main session's variant, where a full id breaks on Bedrock, Vertex and a gateway (code.claude.com/docs/en/sub-agents) |
62
- | Codex | openai | `model` and `model_reasoning_effort` in `.codex/agents/*.toml` |
62
+ | Codex | openai | `model` and `model_reasoning_effort` in `$CODEX_HOME/agents/*.toml` |
63
63
  | opencode | any | a selector `--refresh` found in `opencode models` for the slot's model under any configured provider (`resolve_opencode`); none found, no model line, so the agent inherits. The slot's effort rides the JSON agent config as `reasoningEffort` (opencode.ai/docs/agents) |
64
64
  | omp | anthropic when the session default is an Anthropic model, any otherwise (`omp_mode`); a non-Anthropic default is `any`, never a silent fall back to the Anthropic column | `task.agentModelOverrides`, which omp resolves before the agent or bundled model (omp's own docs: `omp://task-agent-discovery.md`, *Model and structured-output precedence*); every slot is written, the frontier row included, so no agent in the row runs on the session default; each override is a **fallback chain** (`omp_overrides`, `_chain`): the mode's family first, then what `fallback` in `~/.config/tezgah/config.json` allows (`tp.fallback_policy`) over the families this machine holds a credential for (`funded_families` reads omp's auth store and the provider env keys; a family it cannot see is left out, never guessed in) - `vendor`, the default, keeps only the session vendor's model (`openrouter/anthropic/claude-opus-5.5:<effort>` behind an Anthropic session, `OPENROUTER_ID`), so a 429 never moves an Opus session's subagent onto GPT or GLM; `any` adds every other funded family, rotated per agent; `none` writes the first entry alone - omp splits the chain into per-spawn patterns (omp's own docs: `omp://settings.md (its `retry.fallbackChains` section)`), so one provider's 429 falls through instead of killing the spawn, measured before this: 24 of 26 post-routing 429s hit a subagent's first request, all on one Opus account every slot shared; `apply_omp` also writes `modelRoles.plan` and `.slow` on that frontier row (same record, same ownership) so plan mode designs on it, and their fallbacks go to `retry.fallbackChains.<role>` under the same `fallback` rule, the only chain keys tezgah owns (`default` and provider-keyed chains stay the user's); `apply_omp` writes through the installer's own binary and agent dir, from HOME so a repository's `.omp/config.yml` never leaks machine-wide, and touches only the entries tezgah wrote (recorded in `~/.config/tezgah/models.json` as `omp_written`; a machine written before that record is adopted by its value, `_ours_by_shape`); a changed `fallback` reaches omp on the next `tezgah-route --refresh` or `tezgah-setup --install` |
65
65
  | dsh | - | none: no model setting was found to write |
66
66
 
67
67
  The orchestrator agent keeps `model: inherit`: it is the main thread's own agent.
68
68
 
69
- | Cursor | - | reads `.claude/agents/` today; Cursor wants its own model ids with the effort **inside** the id (`claude-opus-5[effort=high]`, cursor.com/docs/subagents), and no id this table names was verified against Cursor's models page - so a `.cursor/agents/` rendering that pins a model is not generated yet |
69
+ | Cursor | - | `model: inherit` in `.cursor/agents/*.md` (`render_md_cursor`): `cursor-agent` passes the value through as a raw id, an alias such as `opus` is not in its model list, and the effort belongs **inside** the id (`claude-opus-5[effort=high]`, cursor.com/docs/subagents). No id this table names was verified against Cursor's models page, so no tier model is pinned there yet |
70
70
 
71
71
  omp's bundled agents are routed through the same record (omp's own docs: `omp://task-agent-discovery.md`, *Bundled agents*): `scout` on the explore slot, `sonic` on the cheap slot, `task` on the standard slot, and `reviewer`/`security-reviewer` left to inherit, because a review is frontier work. A user's own value for any of them wins (see the ownership rule above). Those two are the gap this leaves: the record carries no entry for them, so a session on a cheap model reviews on that model - routing them is one more `BUNDLED` entry, not decided here.
72
72
 
73
73
  ## What is not routed yet
74
74
 
75
75
  - **Claude Code's built-in subagents** (Explore, general-purpose, Plan) run on the session model. `CLAUDE_CODE_SUBAGENT_MODEL` and a settings `env` block exist but carry no effort, and whether a project agent with a built-in's name replaces it is not stated where it can be cited - so nothing is generated on a guess. What would close it: a documented per-agent effort for built-ins, or a confirmed name-collision rule.
76
- - **Codex's default subagent** (spawned with no agent file): only `.codex/agents/*.toml` carries a model today. What would close it: a documented `[agents]` default settable from a repo-scoped file tezgah already writes.
76
+ - **Codex's default subagent** (spawned with no agent file): only `$CODEX_HOME/agents/*.toml` carries a model today. What would close it: a documented `[agents]` default settable from a file tezgah already writes.
77
77
  - **opencode's built-in agents**: the injected JSON could carry `model`/`reasoningEffort` for them, but the merge semantics for a built-in entry are not documented where they can be cited, and a wrong merge would replace a built-in prompt instead of adding a model.
78
78
  - **dsh**: the model is a plugin config, not an agent field. The composed profile carries `agent-default-model` with `config: {provider: deepseek-official, model: deepseek-flash}` (`dsh --profile web --dump-config`, 2026-09-30) and tezgah's managed patch block already patches plugin config by id (its `llm-pi-ai` row), so a `- id: agent-default-model` row would set the profile's model. Not written, because that knob is one model for the whole session - no `subagent*` entry in the composed tree carries a model - and a single session-wide model is the main thread's setting, which this design deliberately leaves alone ([why the main thread keeps one model](#the-shape)). What would close it: a per-agent model in dsh's config surface.
79
79
 
@@ -91,7 +91,9 @@ fixed:
91
91
  (`OVERRIDE`, `hooks/tezgah_models.py::OVERRIDE`) - the class the judge
92
92
  under-routed in its measurement, and the classes a wrong call cannot be
93
93
  undone on. A 12-brief fixture pins it: 8 high-stakes briefs route to
94
- frontier, 4 near-miss controls do not (`tests/test_models.py`).
94
+ frontier, 4 near-miss controls do not (`tests/test_models.py`). Such a brief
95
+ that only reviews goes to `tezgah-security` instead (`REVIEW_ONLY`,
96
+ `hooks/tezgah_models.py::REVIEW_ONLY`).
95
97
  2. Otherwise the brief - redacted with the ledger's own reader
96
98
  (`redact`, `hooks/tezgah_integrity.py::redact`) - goes to the judge as one Choice over three tiers (`TIER_QUESTION`,
97
99
  `hooks/tezgah_models.py::TIER_QUESTION`). The judge asks the session's own model first ([judge](judge.md)).
@@ -99,9 +101,17 @@ fixed:
99
101
  the same session that wrote the rubric (2026-09-30, twice): under-route 0.025,
100
102
  accuracy 0.925 and 0.900, 392 ms median, about 656 input tokens per call; a
101
103
  keyword rule on the same set under-routed 0.100. The session model was not measured on that set.
102
- 3. With no judgement (`judge-off`, no key, a failed call) `--phase` picks the tier
103
- from the static table (`PHASE_TIER`, `hooks/tezgah_models.py::PHASE_TIER`), and
104
- with no phase the middle tier is used.
104
+ The same call asks a second Choice (`ROLE_QUESTION`,
105
+ `hooks/tezgah_models.py::ROLE_QUESTION`): is the brief the whole job of a
106
+ specialist? A yes names that agent on its own tier (`AGENT_SLOT`,
107
+ `hooks/tezgah_models.py::AGENT_SLOT`). Before this question, sessions
108
+ spawned the five specialists 0 times in 121 delegations (2026-10-07 to 10-09).
109
+ Clef agreed on 9 of 10 briefs this session labelled (2026-10-09).
110
+ 3. With no judgement (`judge-off`, no key, a failed call) `--phase review` and
111
+ `--phase research` name the reviewer and the researcher (`PHASE_AGENT`,
112
+ `hooks/tezgah_models.py::PHASE_AGENT`). Another phase picks the tier from the
113
+ static table (`PHASE_TIER`, `hooks/tezgah_models.py::PHASE_TIER`). With no
114
+ phase the middle tier is used.
105
115
 
106
116
  The brief leaves the machine for the judge, like every judgement ([judge](judge.md)).
107
117
  Every routed call writes one `route` row to the session's ledger. The row names the
@@ -28,7 +28,6 @@ before an install is considered.
28
28
  | `--status [PATH]` | Print the armed/used checklist for PATH (default cwd) and stop — the same line as `bin/tezgah-status` (`bin/tezgah-setup::main`, `hooks/tezgah_context.py::health_lines`). It also prints the report's `config.json hosts (...) match the hosts wired (...)` row when the recorded list and the wiring on disk disagree (`hosts_row`), and nothing when they agree: install health is checked here, not only in a full report. |
29
29
  | `--agents [PATH]` | Regenerate PATH's per-repo subagent set (default cwd); outside a [root](glossary.md#root) it prints `no agents generated` (`bin/tezgah-setup::main`). |
30
30
  | `--write-manifest` | Regenerate the tracked `MANIFEST` from `git ls-files` and stop — the release step, its only writer, and it says so on a tree with no `.git` rather than writing an empty listing (`bin/tezgah-setup::write_manifest`, `bin/tezgah-setup::plugin_files`). |
31
- | `--write-plugin-agents` | Re-render the plugin's tracked `agents/*.md` from the role bodies `hooks/tezgah_agents.py` generates per repo, and stop — the release step that keeps the shipped reviewer from drifting from the generated one; `tests/test_agents.py` fails until it has run (`write_plugin_agents`, `plugin_agents`). |
32
31
  | `--deps` | Install the missing optional tools and stop; with `--install` the install already does it. Like `--install` and `--enable`, it exits 1 when a requested tool is still missing (`bin/tezgah-setup::main`). |
33
32
  | `--host-deps` | With `--install` or `--deps`, also install the missing third-party host CLIs (cursor-agent, pnpm, dsh). They are opt-in because each one is a vendor `curl \| sh` that uninstall does not take back. Every installer runs with a 600 s timeout, each `curl` with `--max-time 300` and each pipeline under `pipefail`, so a stalled or refused download fails instead of hanging or reading as success (audit M-9). |
34
33
  | `--mcp-schemas` | Measure the MCP tool-schema band by asking each registered server (`initialize` + `tools/list`) and stop (`bin/tezgah-setup::main`, `bin/tezgah-setup::mcp_schema_report`). |
@@ -435,18 +434,21 @@ builds it (`capture_signals`, `bin/tezgah-taste::capture_signals`). A signal
435
434
  holds the prompt, the files written, the agent's edits, and the user's own edit
436
435
  between two agent writes. That last one compares the after blob with the next
437
436
  write's pre-state, or with the file on disk (`user_diff`,
438
- `bin/tezgah-taste::user_diff`). `learn` asks TypeSafe for one typed decision per
439
- signal (`decide`, `bin/tezgah-taste::decide`, [judge](judge.md)). The decision
437
+ `bin/tezgah-taste::user_diff`). `learn` asks the Jev model for one typed decision
438
+ per signal (`decide`, `bin/tezgah-taste::decide`, [judge](judge.md)). Any Jev
439
+ carrier will do: TypeSafe, OpenRouter System One, Cloudflare Workers AI or a
440
+ compatible endpoint. The decision
440
441
  names preference, defect or none. It picks one of eleven fixed categories and a
441
442
  scope. It also says how the signal relates to each learning held: supports,
442
- contradicts, narrows or unrelated. When another provider answers, `learn` records
443
- the decision in the `decisions` table as `unverified` and never applies it. A
443
+ contradicts, narrows or unrelated. When a provider outside the Jev family answers,
444
+ `learn` records the decision in the `decisions` table as `unverified`. It never
445
+ applies it. The report counts the typed decisions under `jev`. A
444
446
  defect goes to the `defects` table and never into the ledger.
445
447
 
446
448
  Armed taste also learns by itself. At session start, `learn` starts in the
447
449
  background when new signals arrived since the last start. It starts at most
448
450
  once an hour per repository (`learn_later`, `hooks/tezgah_taste.py::learn_later`). It runs
449
- with `--no-fallback` and only when a TypeSafe key resolves, so it never pays a
451
+ with `--no-fallback` and only when a Jev carrier resolves, so it never pays a
450
452
  generative provider for a decision it could not apply. It does not run on
451
453
  Windows, which has no ledger lock yet. The stamp is a `taste_learn` row of the
452
454
  cache's `tezgah.db`. The log lives under the cache dir, in `taste-learn/`.
@@ -661,7 +663,7 @@ or its optional tools and config, on its own initiative
661
663
 
662
664
  ## Triage: `tezgah-triage` and the `judge-off` switch
663
665
 
664
- One judgement seam serves five callers, each of them asking TypeSafe a batched
666
+ One judgement seam serves five callers, each of them asking the judge a batched
665
667
  question instead of paying an agent to read a page: the analyze-app loop's snapshot
666
668
  triage (`bin/tezgah-triage`), `bin/tezgah-docs` for a query its keyword index
667
669
  cannot place, and the prompt-path skill hint (`hooks/tezgah_skill_pick.py`), the
@@ -700,7 +702,10 @@ made - no credential, the switch below, or a failed call - and the loop reads th
700
702
  tree directly instead.
701
703
 
702
704
  The state leaves the machine. A judgement sends the state and the questions to
703
- `api.typesafe.ai` (`ask()`, `hooks/tezgah_judge.py::ask`) - for the triage that is
705
+ the host of whichever provider answers - the session's own CLI (omp, claude,
706
+ opencode, cursor, codex), or the chosen Jev
707
+ carrier's (`api.typesafe.ai`, `openrouter.ai`, `api.cloudflare.com` or a
708
+ `JEV_API_BASE_URL`, see [judge](judge.md)) (`ask()`, `hooks/tezgah_judge.py::ask`) - for the triage that is
704
709
  the snapshot's own text, so a screen carrying personal data is read by a third
705
710
  party, and for the docs fallback it is the reader's query. Nothing else goes: no
706
711
  session id, no workspace path, no credential beyond the bearer header, and the
@@ -710,11 +715,16 @@ request cannot drift. That is why the two shell callers are on-demand - an agent
710
715
  asks for a judgement, no gate does - while the skill hint asks only with its own
711
716
  marker armed, and why the switch below is the off button for the whole path.
712
717
 
713
- The credential resolves from `TYPESAFE_API_KEY`, else from
714
- `~/.config/typesafe/key` (`credential()`, `hooks/tezgah_judge.py::credential`), and when
715
- neither resolves the same questions go to the OpenRouter fallback, whose key rides
716
- `OPENROUTER_API_KEY` and then `~/.config/openrouter/key`
717
- (`openrouter_key()`, `hooks/tezgah_judge.py::openrouter_key`). The file is the channel that
718
+ With no session CLI, the third party is the Jev model through one of its carriers.
719
+ TypeSafe's credential resolves from `TYPESAFE_API_KEY`, else from
720
+ `~/.config/typesafe/key`, else from the active omp login (`typesafe_key()`,
721
+ `hooks/tezgah_paths.py::typesafe_key`). `jev` chooses which of the other Jev
722
+ carriers the seam asks - OpenRouter's System One endpoint, Cloudflare Workers AI
723
+ and any System One-compatible URL (`jev_carriers()`,
724
+ `hooks/tezgah_paths.py::jev_carriers`). The OpenRouter chat fallback's key rides
725
+ `OPENROUTER_API_KEY` and then `~/.config/openrouter/key` (`openrouter_key()`,
726
+ `hooks/tezgah_judge.py::openrouter_key`). When no carrier answers, the same
727
+ questions go to that chat fallback. The file is the channel that
718
728
  matters on a machine exporting the variable from `~/.zshenv`: a hook or a bin tool
719
729
  runs in a non-interactive shell, where that export never ran, so the file is what a
720
730
  judgement actually resolves. Cost is input tokens alone - $0.042 per
package/docs/skills.md CHANGED
@@ -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/testing.md CHANGED
@@ -171,7 +171,7 @@ into the test process would answer about the developer's real machine.
171
171
  | `tests/_probe_paths.py` | argv op, no payload (`:11`) | `roots`, `root_for`, `off`, `default_root`, `config`, `off_dirs`, `cache_dir`, `which_user`, `orx_bin`, `consult_options`, `have_typesafe_key`, `codegraph_bin` |
172
172
  | `tests/_probe_context.py` | JSON on stdin, `{"fn": ...}` (`:4-5`) | `context_for`, `health_lines`, `health_segments`, `record` |
173
173
  | `tests/_probe_integrity.py` | JSON on stdin (`:4-6`) | `note`, `note_tool`, `note_turn`, `kinds`, `events`, `prior_calls`, `counters`, `shortcut_command`, `shortcut_edit`, `stop_reason`, `verify_command` |
174
- | `tests/_probe_agents.py` | JSON on stdin (`:3-4`) | `sync_root`, `opencode_json`, `cleanup`, `detect` |
174
+ | `tests/_probe_agents.py` | JSON on stdin (`:3-4`) | `sync_root`, `opencode_json`, `cleanup`, `detect`, `omp`, `user`, `steering` |
175
175
  | `tests/_probe_gate.py` | JSON on stdin (`:4-8`) | the shared gate `decision(tool, input, cwd, session_id)`; `capture_log` plants a stub `tezgah_snapshot` in `sys.modules`, so the snapshot call site is pinned whether or not that module has landed |
176
176
 
177
177
  A probe is invoked through `support.run`/`run_json` with its path constant
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. **A review loop reads every fix.** A round that confirms a defect gets a
26
- round over the fix. The loop ends when a round confirms none
27
- (`hooks/tezgah_agents.py::_reviewer_body`). The same defect family three times means the
28
- design changes. (Measured: two of four severe defects in one internal plan came from
29
- fixes, after round two.)
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.