@r1z4x/tezgah 1.11.0 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +507 -1
- package/MANIFEST +40 -0
- package/NOTICE +152 -0
- package/README.de.md +41 -20
- package/README.es.md +39 -19
- package/README.fr.md +41 -20
- package/README.md +18 -10
- package/README.tr.md +39 -21
- package/bin/codegen +9 -2
- package/bin/consult +6 -0
- package/bin/tezgah-adhd +3 -0
- package/bin/tezgah-agents +3 -0
- package/bin/tezgah-capture +3 -0
- package/bin/tezgah-design +329 -54
- package/bin/tezgah-docs +158 -42
- package/bin/tezgah-dsh +1 -1
- package/bin/tezgah-gate +7 -2
- package/bin/tezgah-import-ai-research +16 -4
- package/bin/tezgah-index +3 -0
- package/bin/tezgah-lessons +13 -4
- package/bin/tezgah-mcp +4 -0
- package/bin/tezgah-migrate +3 -0
- package/bin/tezgah-ocr-review +2 -1
- package/bin/tezgah-optimize +4 -1
- package/bin/tezgah-pony +3 -0
- package/bin/tezgah-research +51 -10
- package/bin/tezgah-rollback +3 -0
- package/bin/tezgah-route +1 -1
- package/bin/tezgah-setup +1741 -95
- package/bin/tezgah-status +11 -1
- package/bin/tezgah-task +58 -17
- package/bin/tezgah.js +10 -3
- package/commands/adhd.md +1 -1
- package/docs/README.md +19 -8
- package/docs/architecture.md +2 -2
- package/docs/citations-baseline.json +11 -17
- package/docs/clarity-baseline.json +12 -6
- package/docs/contract.md +83 -34
- package/docs/environment.md +59 -24
- package/docs/evidence.md +128 -25
- package/docs/gate.md +77 -23
- package/docs/glossary.md +1 -1
- package/docs/hosts.md +343 -48
- package/docs/layers.md +2 -2
- package/docs/models.md +6 -0
- package/docs/operations.md +83 -29
- package/docs/research.md +37 -11
- package/docs/skills.md +31 -8
- package/docs/status-line.md +39 -23
- package/docs/testing.md +50 -17
- package/docs/vision.md +6 -2
- package/hooks/hooks.json +1 -1
- package/hooks/lang/en.json +16 -2
- package/hooks/lang/tr.json +14 -2
- package/hooks/projects-posttooluse.py +11 -3
- package/hooks/projects-stop.py +3 -2
- package/hooks/tezgah_agents.py +27 -3
- package/hooks/tezgah_attest.py +79 -10
- package/hooks/tezgah_context.py +61 -31
- package/hooks/tezgah_gate.py +480 -126
- package/hooks/tezgah_integrity.py +577 -133
- package/hooks/tezgah_lessons.py +5 -2
- package/hooks/tezgah_paths.py +60 -8
- package/hooks/tezgah_policy.py +48 -18
- package/hooks/tezgah_replay.py +8 -2
- package/hooks/tezgah_research.py +167 -24
- package/hooks/tezgah_skill_pick.py +1 -1
- package/hooks/tezgah_snapshot.py +6 -3
- package/hooks/tezgah_stack.py +236 -1
- package/hooks/tezgah_stopspec.py +2 -1
- package/hooks/tezgah_task.py +2 -1
- package/hosts/amp/hook.py +217 -0
- package/hosts/amp/tezgah-plugin.ts.in +396 -0
- package/hosts/antigravity/hook.py +255 -0
- package/hosts/codex/hook.py +3 -2
- package/hosts/commandcode/hook.py +239 -0
- package/hosts/copilot/hook.py +284 -0
- package/hosts/cursor/hook.py +4 -2
- package/hosts/droid/hook.py +259 -0
- package/hosts/dsh/hooks.json +1 -1
- package/hosts/gemini/hook.py +213 -0
- package/hosts/goose/hook.py +145 -0
- package/hosts/omp/hook.py +5 -2
- package/hosts/omp/tezgah-hook.ts.in +157 -42
- package/hosts/opencode/plugins/tezgah.js +209 -63
- package/hosts/qwen/hook.py +206 -0
- package/output-styles/tezgah.md +11 -6
- package/package.json +6 -3
- package/packaging/build.sh +4 -4
- package/packaging/install.sh +11 -0
- package/packaging/upgrade.sh +3 -0
- package/skills/ai-research/LICENSE +21 -0
- package/skills/design-contract/SKILL.md +27 -6
- package/skills/design-library/LICENSE +21 -0
- package/skills/harness/SKILL.md +2 -0
- package/skills/i-have-adhd/SKILL.md +10 -1
- package/skills/plan-add/SKILL.md +5 -1
- package/skills/plan-add/render_table.py +7 -0
- package/skills/pm-frameworks/LICENSE +21 -0
- package/skills/research/SKILL.md +22 -9
- package/skills/rl-env/LICENSE +201 -0
- package/skills/tezgah-contract/SKILL.md +21 -8
- package/statusline.py +20 -9
- package/third_party/open-code-review/LICENSE +201 -0
- package/third_party/open-code-review/README.md +15 -0
- package/third_party/open-code-review/prompts/main_task_system.md +26 -0
- package/third_party/open-code-review/prompts/review_filter_task_system.md +13 -0
- package/workflows/graph-map.js +14 -5
- package/workflows/graph-review.js +14 -2
package/docs/environment.md
CHANGED
|
@@ -4,7 +4,9 @@ Every environment variable tezgah reads, in one table per concern: what reads it
|
|
|
4
4
|
what it defaults to, and what setting it changes. For someone pinning an install
|
|
5
5
|
in CI, repointing an endpoint in a test, or wondering why a hook ignores the
|
|
6
6
|
Python on PATH. A variable not on this page is not read by tezgah; one read by a
|
|
7
|
-
host and only passed through (such as `HOME` or `PATH`) is left out.
|
|
7
|
+
host and only passed through (such as `HOME` or `PATH`) is left out. Vendored
|
|
8
|
+
upstream skill scripts (under `skills/`, such as hallmark's) are out of scope:
|
|
9
|
+
the variables they read are theirs, not tezgah's.
|
|
8
10
|
|
|
9
11
|
Nothing loads a `.env` file: a variable reaches tezgah only through the
|
|
10
12
|
environment of the process that started it - the shell, the host, or the host's
|
|
@@ -19,32 +21,40 @@ the kill switches are files, not variables, and live in
|
|
|
19
21
|
|
|
20
22
|
| Variable | Read by | Default | Effect |
|
|
21
23
|
|---|---|---|---|
|
|
22
|
-
| `TEZGAH_PREFIX` | `packaging/install.sh
|
|
23
|
-
| `TEZGAH_VERSION` | `packaging/
|
|
24
|
-
| `TEZGAH_REPO` | `packaging/install.sh:13`, `packaging/upgrade.sh`, `packaging/install.ps1` | `r1z4x/tezgah` | The GitHub repository releases are fetched from. |
|
|
24
|
+
| `TEZGAH_PREFIX` | `packaging/install.sh`, `packaging/upgrade.sh`, `packaging/install.ps1`, `bin/tezgah-setup::default_prefix`, `hooks/tezgah_gate.py::_install_trees` | `$XDG_DATA_HOME/tezgah`, else `%LOCALAPPDATA%\tezgah` on Windows, else `~/.local/share/tezgah` | Where the artifact install unpacks its versions and the `current` link; `tezgah-setup` treats it as the install prefix. |
|
|
25
|
+
| `TEZGAH_VERSION` | `packaging/upgrade.sh`, `packaging/install.ps1`, `packaging/build.sh:70` | the newest GitHub release; `build.sh` falls back to `VERSION`/the CHANGELOG | The version to install, or the version `build.sh` stamps when `--version` is not given. `install.sh` does not read it: the `upgrade.sh` it runs does. |
|
|
26
|
+
| `TEZGAH_REPO` | `packaging/install.sh:13`, `packaging/upgrade.sh`, `packaging/install.ps1`, `hooks/tezgah_update.py::REPO` | `r1z4x/tezgah` | The GitHub repository releases are fetched from, and the one the daily release check asks. |
|
|
27
|
+
| `TEZGAH_GITHUB_TOKEN`, `GITHUB_TOKEN`, `GH_TOKEN` | `packaging/install.sh`, `packaging/upgrade.sh`, `packaging/install.ps1` | unset | The first one set is sent as the bearer token for a private repository's release download; unset, the download is anonymous. |
|
|
25
28
|
| `TEZGAH_UPDATE_URL` | `hooks/tezgah_update.py` | `https://github.com/<TEZGAH_REPO>/releases/latest` | Where the daily release check reads the newest tag: from the redirect, or from a JSON body's `tag_name`. Tests point it at a `file://` fixture. |
|
|
26
29
|
| `TEZGAH_UPDATE_CHECK` | `hooks/tezgah_update.py` | unset | `0` turns off the update chip and the daily check. The test suite sets it, so no test reaches the network. |
|
|
27
|
-
| `TEZGAH_DIST` | `packaging/upgrade.sh:
|
|
30
|
+
| `TEZGAH_DIST` | `packaging/upgrade.sh:109`, `packaging/install.ps1` | unset: the release URL | A directory holding `tezgah-<V>.tar.gz` and its `.sha256` (what `build.sh --out` writes) used instead of the release download; CI exercises the real fetch-verify-unpack path with it. |
|
|
28
31
|
| `TEZGAH_GH` | `packaging/upgrade.sh` | `gh` on PATH | The `gh` that checks the tarball's build provenance (`gh attestation verify`). A path that does not exist reads as no `gh`: the upgrade prints `provenance not checked` and goes on. Tests point it at a stand-in. |
|
|
29
32
|
| `TEZGAH_NO_DEPS` | `bin/tezgah-setup::main` | unset | Any value but empty or `0` is `--no-deps`: the optional tools (codegraph, orx, host CLIs) are not installed. |
|
|
30
33
|
| `TEZGAH_NO_SYMLINK` | `bin/tezgah-setup::symlinks_refused` | unset | Any value but empty or `0` writes the copy layout a machine without symlinks gets, on a machine that has them. |
|
|
31
|
-
| `TEZGAH_PYTHON` | `hooks/tezgah_paths.py::python_cmd`, `bin/tezgah.js`, `bin/tezgah-dsh.cmd`, `hooks/hooks.json`, `hosts
|
|
34
|
+
| `TEZGAH_PYTHON` | `hooks/tezgah_paths.py::python_cmd`, `bin/tezgah.js`, `bin/tezgah-dsh.cmd`, `hooks/hooks.json`, `hosts/cursor/hooks.json`, `hosts/dsh/hooks.json`, `hosts/omp/tezgah-hook.ts.in`, `hosts/amp/tezgah-plugin.ts.in`, `hosts/opencode/plugins/tezgah.js`, `packaging/build.sh` | this process's interpreter, then `python3`/`python`/`py` on PATH | The interpreter every host hook and the npm shim start; pin it when the right Python is not first on PATH. Codex's hook command is rendered at install (`bin/tezgah-setup::codex_hook_groups`) rather than read from a shipped file. |
|
|
32
35
|
|
|
33
36
|
## Where tezgah and the hosts keep state
|
|
34
37
|
|
|
35
38
|
| Variable | Read by | Default | Effect |
|
|
36
39
|
|---|---|---|---|
|
|
37
40
|
| `TEZGAH_ROOTS` | `hooks/tezgah_paths.py::roots`, `bin/tezgah-setup`, `hosts/opencode/plugins/tezgah.js` | `roots` in `~/.config/tezgah/config.json`, else `~/Projects` | The directories tezgah is armed over, `os.pathsep`-separated (`;` on Windows, the opencode plugin included); it outranks the config file. |
|
|
38
|
-
| `XDG_CONFIG_HOME` | `hooks/tezgah_paths.py::CONFIG_DIR`, `bin/tezgah-doctor`, `bin/tezgah-dsh.cmd`, `hosts/opencode/plugins/tezgah.js` | `~/.config` | Parent of tezgah's config dir (kill switches, `bin/` links, config) and of
|
|
39
|
-
| `XDG_DATA_HOME` | `bin/tezgah-setup::default_prefix`, `packaging/install.sh`, `packaging/upgrade.sh` | `~/.local/share` | Parent of the default install prefix. |
|
|
41
|
+
| `XDG_CONFIG_HOME` | `hooks/tezgah_paths.py::CONFIG_DIR`, `hooks/tezgah_paths.py::AMP_PLUGIN`, `hooks/tezgah_skill_pick.py::_host_roots`, `bin/tezgah-doctor`, `bin/tezgah-dsh.cmd`, `hosts/opencode/plugins/tezgah.js` | `~/.config` | Parent of tezgah's config dir (kill switches, `bin/` links, config), of opencode's, and of Amp's system plugin dir (`amp/plugins/tezgah.ts`). |
|
|
42
|
+
| `XDG_DATA_HOME` | `bin/tezgah-setup::default_prefix`, `packaging/install.sh`, `packaging/upgrade.sh`, `hooks/tezgah_gate.py::_install_trees` | `~/.local/share` | Parent of the default install prefix. |
|
|
40
43
|
| `XDG_CACHE_HOME` | `hooks/tezgah_apps.py::ARTIFACTS` | `~/.cache` | Parent of the app-analysis artifact dir. |
|
|
41
|
-
| `TEZGAH_DEBUG` | `hooks/tezgah_guard.py` | unset | `1` makes every hook process append one line to `<cache>/debug.log` (host, script, each guarded call's outcome or exception class, elapsed ms; no prompt or tool text), created 0600 and swept by `tezgah-doctor --clean`. Off, it costs one environment read. |
|
|
42
44
|
| `TEZGAH_FALLBACK_CACHE` | `hooks/tezgah_paths.py::fallback_cache` | `<tempdir>/tezgah` | The cache a sandboxed hook writes when its normal cache dir is denied; a test knob. |
|
|
43
45
|
| `TEZGAH_ARTIFACTS` | `hooks/tezgah_apps.py::ARTIFACTS` | `$XDG_CACHE_HOME/tezgah/apps` | Where `analyze-app` screenshots, traces and tree dumps land. |
|
|
44
46
|
| `TEZGAH_NO_EXCLUDE` | `hooks/tezgah_agents.py::ensure_exclude` | unset | `1` stops tezgah adding its generated-agent dirs to the repository's `.git/info/exclude`. |
|
|
45
|
-
| `CODEX_HOME` | `hooks/tezgah_paths.py::HOST_DIRS` | `~/.codex` | The Codex home tezgah installs into and checks; the same variable Codex itself reads. |
|
|
47
|
+
| `CODEX_HOME` | `hooks/tezgah_paths.py::HOST_DIRS`, `hooks/tezgah_skill_pick.py::_host_roots` | `~/.codex` | The Codex home tezgah installs into and checks; the same variable Codex itself reads. |
|
|
46
48
|
| `DSH_HOME` | `hooks/tezgah_paths.py::HOST_DIRS`, `bin/tezgah-dsh`, `bin/tezgah-dsh.cmd` | `~/.dsh` | The dsh home tezgah installs into, and where `tezgah-dsh` looks for the CLI. |
|
|
49
|
+
| `QWEN_HOME`, `COPILOT_HOME` | `hooks/tezgah_paths.py::HOST_DIRS` | `~/.qwen`, `~/.copilot` | The Qwen Code and Copilot CLI homes tezgah installs into and checks; the same variables those CLIs read to relocate their home. |
|
|
50
|
+
| `GEMINI_CLI_HOME` | `hooks/tezgah_paths.py::HOST_DIRS` | the home dir | The directory holding Gemini CLI's `.gemini`, where tezgah installs its extension; the variable Gemini CLI itself reads. |
|
|
51
|
+
| `CLAUDE_CONFIG_DIR` | `hooks/tezgah_stack.py::host_stack` | unset | Set (Claude Code's own relocation variable), omp's skill stack counts `~/.claude/skills` as loaded even when omp's `enabledProviders` does not list `claude`. The test suite unsets it. |
|
|
47
52
|
| `ORCA_WORKTREE_ID`, `ORCA_TERMINAL_HANDLE`, `ORCA_PANE_KEY`, `TERM_PROGRAM=Orca` | `hooks/tezgah_orca.py::session` | unset | Exported by an Orca terminal. Any one marks the session as inside Orca: the session-start block gains the `Orca:` line and `tezgah-setup --status` an `orca:` line ([orca](orca.md)). tezgah never writes them. |
|
|
53
|
+
| `ORCA_APP_VERSION` | `hooks/tezgah_orca.py::session` | unset | Exported by an Orca terminal: the Orca version the `Orca:` line reports. |
|
|
54
|
+
| `ORCA_CLI_COMMAND`, `ORCA_CLI_BIN_DIR` | `hooks/tezgah_orca.py::cli` | unset | Exported by Orca: the command (WSL) or the bin dir of its CLI, tried after `TEZGAH_ORCA_BIN` and before PATH. The test suite unsets both. |
|
|
55
|
+
| `TEZGAH_ENGAGEMENTS_ROOT` | `hooks/tezgah_extension.py::ENGAGEMENTS_ENV` | unset | A domain repository's engagement root: a session anywhere under `<root>/<slug>` reads that engagement's `.tezgah/extension.json` even when the walk up ran out first. |
|
|
56
|
+
| `GIT_DIR`, `GIT_WORK_TREE` | `hooks/tezgah_agents.py::_exclude_path` | unset | Either one set moves the repository away from `.git`, so the generated-agent excludes are written where git says, not to `.git/info/exclude`. |
|
|
57
|
+
| `LANGUAGE`, `LC_ALL`, `LC_MESSAGES`, `LANG` | `hooks/tezgah_paths.py::env_language` | unset | A first install takes the reply language from the first one set and not empty (`LANGUAGE`'s first `:` entry); `en` when it names no shipped pack. |
|
|
48
58
|
| `TEZGAH_OPENCODE_DATA` | `bin/tezgah-setup::OPENCODE_DATA`, `bin/tezgah-doctor` | `~/.local/share/opencode` | opencode's data dir: its session database for `tezgah-doctor` and the installer. |
|
|
49
59
|
|
|
50
60
|
## Which binary runs
|
|
@@ -54,9 +64,11 @@ the kill switches are files, not variables, and live in
|
|
|
54
64
|
| `TEZGAH_CODEGRAPH_BIN` | `hooks/tezgah_paths.py::codegraph_bin` | the config file, then `codegraph` on PATH | The codegraph executable the index and the graph marks use. |
|
|
55
65
|
| `TEZGAH_ORX_BIN` | `hooks/tezgah_paths.py::orx_bin` | `orx` on PATH | The OpenResearch CLI the research rule routes to. |
|
|
56
66
|
| `TEZGAH_OCR_BIN` | `hooks/tezgah_paths.py::ocr_bin` | `ocr` on PATH | The open-code-review CLI the opt-in OCR review spec runs ([operations](operations.md#opt-in-ocr-review-spec-ocr)). |
|
|
67
|
+
| `TEZGAH_OCR_REVIEW_READY_TIMEOUT` | `bin/tezgah-ocr-review::run` | `30` (seconds) | How long `tezgah-ocr-review` waits for its model gateway to answer before it reports a setup failure. Must be a number. |
|
|
68
|
+
| `GIT_CONFIG_GLOBAL`, `GIT_CONFIG_COUNT` | `bin/tezgah-ocr-review::run` | unset | OCR runs with a private HOME, so `tezgah-ocr-review` pins git to the user's `~/.gitconfig` and global excludes: an explicit `GIT_CONFIG_GLOBAL` wins over that pin, and the excludes entry is appended after the `GIT_CONFIG_COUNT` entries already set. |
|
|
57
69
|
| `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. |
|
|
58
70
|
| `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. |
|
|
59
|
-
| `TEZGAH_CLAUDE_BIN` | `
|
|
71
|
+
| `TEZGAH_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. |
|
|
60
72
|
| `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. |
|
|
61
73
|
| `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. |
|
|
62
74
|
| `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. |
|
|
@@ -75,11 +87,18 @@ the kill switches are files, not variables, and live in
|
|
|
75
87
|
|
|
76
88
|
| Variable | Read by | Default | Effect |
|
|
77
89
|
|---|---|---|---|
|
|
78
|
-
| `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. |
|
|
90
|
+
| `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), `bin/tezgah-task::print_ocr_spec` (the first) | 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. |
|
|
79
91
|
| `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. |
|
|
80
|
-
| `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,
|
|
92
|
+
| `OPENROUTER_API_KEY` | `hooks/tezgah_judge.py::openrouter_key`, `bin/consult`, `bin/codegen`, `hooks/tezgah_models.py`, `hooks/tezgah_research_evidence.py::cmd_label` | `~/.config/openrouter/key` | The seam's chat fallback, consult's and codegen's `openrouter` provider, omp's `any` family, and the `--provider openrouter` rater of `tezgah-research label`. |
|
|
81
93
|
| `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
|
-
| `ANTHROPIC_OAUTH_TOKEN`, `ANTHROPIC_API_KEY`, `ZAI_API_KEY`, `OPENAI_CODEX_OAUTH_TOKEN` | `hooks/tezgah_models.py::OMP_AUTH` | omp's own auth store | Read as presence only: whether omp can run the `anthropic`, `zai` or `
|
|
94
|
+
| `ANTHROPIC_OAUTH_TOKEN`, `ANTHROPIC_API_KEY`, `ZAI_API_KEY`, `OPENAI_CODEX_OAUTH_TOKEN`, `EXPERIMENTALLABS_API_KEY` | `hooks/tezgah_models.py::OMP_AUTH` | omp's own auth store | Read as presence only: whether omp can run the `anthropic`, `zai`, `openai` or `experientiallabs` model family, so the model table can pick a row omp will run. |
|
|
95
|
+
| `TEZGAH_HEALTH_CHECK` | `hooks/tezgah_models.py::health_due`, `hooks/tezgah_models.py::note_limit`, `hooks/tezgah_models.py::other_family_up` | unset | `0` turns off the provider health probe and the limit notes that read credentials; the test suite sets it, so no test asks a model or reads this machine's keys. |
|
|
96
|
+
| `TEZGAH_JEV_PROVIDER`, `JEV_PROVIDER` | `hooks/tezgah_paths.py::jev_choice` | `jev` in `config.json`, else `auto` | The Jev carrier the judgement seam asks (`JEV_PROVIDER` is jev-mcp's own name); `auto` asks every carrier whose credential resolves, an unknown name resolves to none ([judge](judge.md)). |
|
|
97
|
+
| `JEV_API_BASE_URL`, `JEV_API_KEY` | `hooks/tezgah_paths.py::jev_carriers`, `hooks/tezgah_judge.py::_jev_call` | unset | The `jev-compatible` carrier: any System One-compatible endpoint and its key. Plain `http` is refused unless the host is this machine. |
|
|
98
|
+
| `TEZGAH_JEV_MODEL` | `hooks/tezgah_judge.py::_jev_call` | the seam's model | The model the `jev-compatible` carrier asks. |
|
|
99
|
+
| `CLOUDFLARE_ACCOUNT_ID` | `hooks/tezgah_paths.py::cloudflare_account` | `~/.config/cloudflare/account_id` | The Workers AI account the `jev-cloudflare` and `clef` carriers call. |
|
|
100
|
+
| `JEV_CLOUDFLARE_API_TOKEN`, `CLOUDFLARE_API_TOKEN` | `hooks/tezgah_paths.py::cloudflare_token` | `~/.config/cloudflare/token` | The Workers AI token, the first one set. |
|
|
101
|
+
| `TEZGAH_CLOUDFLARE_URL`, `TEZGAH_JEV_OPENROUTER_URL` | `hooks/tezgah_judge.py::_jev_call` | Cloudflare's API base, OpenRouter's System One URL | Repoint the Workers AI and `jev-openrouter` carriers (tests). Plain `http` is refused unless the host is this machine. |
|
|
83
102
|
| `TEZGAH_TYPESAFE_URL` | `hooks/tezgah_judge.py::endpoint` | `https://api.typesafe.ai/v1/systemone` | Repoints the seam's TypeSafe endpoint (tests). Plain `http` is refused unless the host is this machine; a cross-host redirect is refused. |
|
|
84
103
|
| `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. |
|
|
85
104
|
| `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. |
|
|
@@ -99,6 +118,13 @@ the kill switches are files, not variables, and live in
|
|
|
99
118
|
| `CODEGEN_URL` | `bin/codegen::ask` | the provider's URL | Repoints codegen's endpoint. Plain `http` is refused unless the host is this machine (`127.0.0.1`, `localhost`); a redirect to another host is refused. |
|
|
100
119
|
| `CODEGEN_MODEL` | `bin/codegen::MODEL` | the cheap `any` row of the models table | The model codegen drafts with. |
|
|
101
120
|
|
|
121
|
+
## Research capture
|
|
122
|
+
|
|
123
|
+
| Variable | Read by | Default | Effect |
|
|
124
|
+
|---|---|---|---|
|
|
125
|
+
| `TEZGAH_SCOPUS_KEY` | `hooks/tezgah_research_capture.py::cmd_search` | unset | The Elsevier API key the `scopus` search connector sends; without it that connector refuses. |
|
|
126
|
+
| `TEZGAH_CAPTURE_MAP` | `hooks/tezgah_research_capture.py::fetch_url` | unset | A JSON map that answers the capture commands' fetches instead of the network; tests set it, and `hooks/tezgah_research_evidence.py::cmd_release_check` sets it to its fixtures' `capture-map.json`. |
|
|
127
|
+
|
|
102
128
|
## Status line
|
|
103
129
|
|
|
104
130
|
| Variable | Read by | Default | Effect |
|
|
@@ -106,8 +132,9 @@ the kill switches are files, not variables, and live in
|
|
|
106
132
|
| `NO_COLOR` | `hooks/tezgah_context.py::color_default`, `hosts/omp/tezgah-hook.ts.in` | unset | Any value renders the status line without ANSI colour. |
|
|
107
133
|
| `TEZGAH_STATUS_COLOR` | `hooks/tezgah_context.py::color_default`, `hosts/omp/tezgah-hook.ts.in` | unset | `0` is the same opt-out as `NO_COLOR`, for tezgah alone. |
|
|
108
134
|
| `TEZGAH_STATUS_ANIMATE` | `hosts/omp/tezgah-hook.ts.in:65` | unset (animated) | `0` keeps omp's status colours and drops the motion. |
|
|
109
|
-
| `TEZGAH_STATUS_HOST` | `statusline.py::HOST` | `claude` | `cursor` renders for
|
|
110
|
-
| `TEZGAH_STATUS_LEGEND` | `statusline.py:
|
|
135
|
+
| `TEZGAH_STATUS_HOST` | `statusline.py::HOST` | `claude` | `cursor`, `qwen`, `droid`, `copilot` or `antigravity` renders for that host (the same as `--cursor` and the like). |
|
|
136
|
+
| `TEZGAH_STATUS_LEGEND` | `statusline.py:138` | unset | `1` prints the mark legend under the line. |
|
|
137
|
+
| `COLORFGBG` | `hooks/tezgah_context.py::light_background` | unset (dark) | The terminal's reported colours: a light background picks the light palette. |
|
|
111
138
|
|
|
112
139
|
## Set by tezgah or the host, not by a user
|
|
113
140
|
|
|
@@ -119,15 +146,16 @@ the kill switches are files, not variables, and live in
|
|
|
119
146
|
| `TEZGAH_CORE_IN_FILE` | `hooks/projects-auto-init.py::core_is_in_a_file`, `hooks/hooks.json` | unset | `1` (set by the Claude plugin's hook rows) drops the core rules from the injection when the host's static file really carries the tezgah block; a file without it falls back to the hook. |
|
|
120
147
|
| `TEZGAH_HOST` | `hooks/tezgah_context.py::context_for`, `hooks/hooks.json` | unset | `claude` (set by the Claude plugin's UserPromptSubmit row; the payload names no host) makes the skill hint name the Skill tool's `tezgah:<name>` beside the path instead of a `skill://` address (`hooks/tezgah_skill_pick.py::_emit`). A host that sends `host` in the payload (omp, opencode) is not read from here. |
|
|
121
148
|
| `TEZGAH_NESTED` | `hooks/tezgah_integrity.py::_shape_block`, `bin/consult` | unset | Set by consult on the agent CLIs it runs: that session's Stop rule does not fire. |
|
|
122
|
-
| `TEZGAH_CALL_OUTCOME` | `hooks/projects-posttooluse.py::main`, `hosts/dsh/hooks.json` | unset | `none` (dsh's Task rows) records the call's outcome as unknown instead of as a pass. |
|
|
149
|
+
| `TEZGAH_CALL_OUTCOME` | `hooks/projects-posttooluse.py::main`, `hooks/tezgah_integrity.py::_hook_host`, `hosts/dsh/hooks.json` | unset | `none` (dsh's Task rows) records the call's outcome as unknown instead of as a pass. |
|
|
123
150
|
| `CLAUDE_PROJECT_DIR` | `hooks/projects-auto-init.py::main` | the process cwd | The project dir when the hook payload carries no `cwd`. |
|
|
124
|
-
| `PLAYWRIGHT_BROWSERS_PATH`, `LOCALAPPDATA` | `hooks/tezgah_apps.py::_browser_caches`, `bin/tezgah-setup::default_prefix`
|
|
151
|
+
| `PLAYWRIGHT_BROWSERS_PATH`, `LOCALAPPDATA` | `hooks/tezgah_apps.py::_browser_caches`, `bin/tezgah-setup::default_prefix` and `packaging/install.ps1` (LOCALAPPDATA only) | Playwright's own cache dirs | Where `analyze-app` looks for an installed browser before it reports one missing; on Windows `LOCALAPPDATA` is also the parent of the default install prefix. |
|
|
152
|
+
| `AMP_IGNORE_GUIDANCE_FILES` | `hosts/amp/tezgah-plugin.ts.in` | set by the user for Amp | Amp's own list of guidance files to skip; when it is set the plugin asks for the session brief with the core in it, since `~/.config/amp/AGENTS.md` may be skipped. |
|
|
125
153
|
|
|
126
154
|
## Test knob
|
|
127
155
|
|
|
128
156
|
| Variable | Read by | Default | Effect |
|
|
129
157
|
|---|---|---|---|
|
|
130
|
-
| `TEZGAH_E2E_STRICT` | `tests/
|
|
158
|
+
| `TEZGAH_E2E_STRICT` | `tests/e2e_analyze_wiring.py`, `tests/e2e_docker_cycle.py`, `tests/e2e_packaged_install.py`, `tests/e2e_plan_flow.py`, `tests/e2e_tezgah_mcp.py`, `tests/neuter_matrix.py`, `.github/workflows/ci.yml`, `.github/workflows/neuter.yml` | unset (a missing prerequisite is a SKIP) | `1` turns a SKIP into a FAIL in the six scripts listed. CI sets it on `e2e_plan_flow`, `e2e_analyze_wiring` and `e2e_packaged_install` (ci.yml) and on `neuter_matrix` (neuter.yml). `e2e_docker_cycle` and `e2e_tezgah_mcp` read it but are opt-in: set it by hand to make their missing prerequisite a failure. The other end-to-end scripts (`e2e_qwen`, `e2e_droid`, `e2e_omp_statusline`, `e2e_dsh_statusline`, `e2e_analyze_web`, `e2e_analyze_mobile`) ignore it: they need a host CLI or paid model calls a CI run does not have, and still SKIP. |
|
|
131
159
|
| `TEZGAH_OPENCODE_NO_SQLITE` | `hosts/opencode/plugins/tezgah.js` | unset (node:sqlite when the runtime has it) | `1` makes the opencode plugin read and write the evidence ledger through `python3 hooks/tezgah_store.py evidence ...`, the path a runtime without node:sqlite takes; a test sets it. |
|
|
132
160
|
|
|
133
161
|
## Debugging
|
|
@@ -135,17 +163,24 @@ the kill switches are files, not variables, and live in
|
|
|
135
163
|
| Variable | Read by | Default | Effect |
|
|
136
164
|
|---|---|---|---|
|
|
137
165
|
| `TEZGAH_DEBUG` | `hooks/tezgah_guard.py` | unset (off) | Any value but empty or `0` makes every hook process append one line to `<cache>/debug.log` (created 0600): `<epoch> host=<codex\|cursor\|omp\|claude/dsh> script=<entry script> calls=<core fn>:<answer\|none\|ExceptionClass>,... ms=<elapsed>`. `answer` means the core returned something (a deny reason, a context block, a block decision). No prompt or tool text is written. Off, it costs one environment read. `tezgah-doctor --clean` removes the file once it has gone untouched for `--retention-days` (default 30). |
|
|
166
|
+
| `TEZGAH_REPO_RULE` | `hooks/tezgah_gate.py::REPO_RULE_VALVE`, `hosts/opencode/plugins/tezgah.js` | unset | `0` skips a repository's own `.tezgah/gate.py` rule ([gate](gate.md#a-repositorys-own-rule)): the off valve for a refusal the repository, not tezgah, wrote. |
|
|
138
167
|
|
|
139
168
|
## Source of truth
|
|
140
169
|
|
|
141
170
|
`hooks/tezgah_paths.py`, `hooks/tezgah_judge.py`, `hooks/tezgah_models.py`,
|
|
142
171
|
`hooks/tezgah_index.py`, `hooks/tezgah_apps.py`, `hooks/tezgah_agents.py`,
|
|
143
172
|
`hooks/tezgah_context.py`, `hooks/tezgah_integrity.py`,
|
|
173
|
+
`hooks/tezgah_orca.py`, `hooks/tezgah_extension.py`, `hooks/tezgah_stack.py`,
|
|
174
|
+
`hooks/tezgah_update.py`, `hooks/tezgah_guard.py`, `hooks/tezgah_gate.py`,
|
|
175
|
+
`hooks/tezgah_skill_pick.py`, `hooks/tezgah_research_capture.py`,
|
|
176
|
+
`hooks/tezgah_research_evidence.py`,
|
|
144
177
|
`hooks/projects-auto-init.py`, `hooks/projects-posttooluse.py`, `statusline.py`,
|
|
145
178
|
`bin/tezgah-setup`, `bin/consult`, `bin/codegen`, `bin/tezgah-status`,
|
|
146
|
-
`bin/tezgah-doctor`, `bin/tezgah-dsh`, `bin/tezgah
|
|
179
|
+
`bin/tezgah-doctor`, `bin/tezgah-dsh`, `bin/tezgah-ocr-review`, `bin/tezgah-task`,
|
|
180
|
+
`bin/tezgah.js`, `packaging/install.sh`,
|
|
147
181
|
`packaging/upgrade.sh`, `packaging/install.ps1`, `packaging/build.sh`,
|
|
148
|
-
`hosts/omp/tezgah-hook.ts.in`, `hosts/
|
|
149
|
-
`hosts/dsh/statusline/lib/index.js`. The
|
|
150
|
-
code
|
|
151
|
-
|
|
182
|
+
`hosts/omp/tezgah-hook.ts.in`, `hosts/amp/tezgah-plugin.ts.in`,
|
|
183
|
+
`hosts/opencode/plugins/tezgah.js`, `hosts/dsh/statusline/lib/index.js`. The
|
|
184
|
+
list covers the shipped code's `os.environ`, `getenv`, `process.env`, `$env:` and
|
|
185
|
+
`${...}` reads. `tests/test_docs.py::ContractAndEnvironment` re-runs that search
|
|
186
|
+
and fails on a name with no row here.
|
package/docs/evidence.md
CHANGED
|
@@ -11,8 +11,12 @@ One append-only ledger per session, stored as the `evidence` rows of `<cache>/te
|
|
|
11
11
|
(`hooks/tezgah_store.py::EVIDENCE_SCHEMA`). A row keeps the JSON text its writer produced, with the
|
|
12
12
|
session, `kind` and `ts` in columns beside it. A caller names a ledger by its old file path,
|
|
13
13
|
`<cache>/evidence/<slug>.jsonl`. The stem is the session key: a readable prefix plus a hash of the
|
|
14
|
-
session id, which nothing can turn back into the id (`hooks/tezgah_integrity.py::_slug`). Every writer goes through `note_path` (`hooks/tezgah_integrity.py::note_path`): `note()`
|
|
15
|
-
derives the path from the session id (`hooks/tezgah_integrity.py::note`). The
|
|
14
|
+
session id, which nothing can turn back into the id (`hooks/tezgah_integrity.py::_slug`). Every Python writer but one goes through `note_path` (`hooks/tezgah_integrity.py::note_path`): `note()`
|
|
15
|
+
derives the path from the session id (`hooks/tezgah_integrity.py::note`). The exception is the pre-write snapshot writer
|
|
16
|
+
(`hooks/tezgah_snapshot.py::_append`). Its `snapshot` and `rollback` rows keep a path longer than `DETAIL_MAX` and a `forced`
|
|
17
|
+
key, which `note_path` would cut and drop. So they go straight to the same `_append` and the same ledger. They carry the same
|
|
18
|
+
`v`, but their `detail` is neither redacted nor cut. The opencode plugin builds its rows in JavaScript and inserts them itself
|
|
19
|
+
(`appendRow` in `hosts/opencode/plugins/tezgah.js`), with no `v`. Every other row is built in one place (`hooks/tezgah_integrity.py::note_path`). Its shape is
|
|
16
20
|
`{"kind": …, "ts": …, "v": …, "detail": …}` plus whatever `LEDGER_FIELDS` keys the writer knew — for a check
|
|
17
21
|
the host reported passing, `{"kind": "verify_ok", "ts": 1758000000, "v": 3, "detail": "pytest -q",
|
|
18
22
|
"id": "a1b2c3d4e5f6", "exit": 0, "out_bytes": 4312, "tool": "Bash", "repo": "/home/u/proj"}`. `v` names the shape the row was written under
|
|
@@ -33,11 +37,21 @@ JSON key (`"password": "x"`) and as a flag with its value after a space (`--pass
|
|
|
33
37
|
`--api-key X`, `mysql -pPASS`) - an `Authorization` scheme (`Bearer`, `Basic`, `Token`, `Digest`),
|
|
34
38
|
the password of URL userinfo (`scheme://user:pass@`) and of `-u user:pass`, and a prefixed token
|
|
35
39
|
family lose theirs (`hooks/tezgah_integrity.py::REDACTIONS`; audit L-6, which found each of those stored verbatim). The optional fields are exactly `LEDGER_FIELDS`
|
|
36
|
-
(`hooks/tezgah_integrity.py::LEDGER_FIELDS`)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
(`hooks/tezgah_integrity.py::LEDGER_FIELDS`):
|
|
41
|
+
|
|
42
|
+
- a call's `id`, `exit`, `out_bytes`, `fail_class`, `workspace`, `source`, `hash`, `changed`, `tool`, `target`, `repo`,
|
|
43
|
+
`empty_run`, and the began row's `check`
|
|
44
|
+
- the reply-shape names `lines`, `chars`, `items`, `longest_list`, `tr_share`, `answer_first`, and a read-only turn's `mode`
|
|
45
|
+
- the compaction row's `summary_chars`, `summary_hash`, `constraint_found` and `constraint_expected`
|
|
46
|
+
- the attest row's `host` and `switches`, a claim row's `harness`, and a refusal's `cause`
|
|
47
|
+
- a Stop refusal row's `reask`: the key of its own text, which the turn row of omp's continuation carries
|
|
48
|
+
(`hooks/tezgah_integrity.py::_turn_refusals`)
|
|
49
|
+
- the lineage names `parent`, `child` and `agent`, on the spawned, forked, spawn, subagent_start and subagent_end rows
|
|
50
|
+
- a lesson row's `key` and `block`, the authorized row's `authorized`, and a turn row's `inflight`
|
|
51
|
+
- a todo row's `open`, `ready`, `blocked`, `heads`, `taken` and `partial`
|
|
52
|
+
- a subagent_end row's `residual`, and an ask row's `questions` and `defaults`
|
|
53
|
+
|
|
54
|
+
Any key outside that set is dropped, a `None` value left out, because every reader treats a missing key
|
|
41
55
|
as `None` (`hooks/tezgah_integrity.py::note_path`). `tool` is the call's own name (`Bash`, `Write`, `mcp__codegen__status`),
|
|
42
56
|
written by `note_tool` (`hooks/tezgah_integrity.py::note_tool`) because `classify` folds the name into
|
|
43
57
|
a kind and drops it; it is what `_counts`' tool histogram counts and what the retirement report on
|
|
@@ -121,14 +135,15 @@ a write tool is `edit`, a shell call is `verify` when its command matches the ch
|
|
|
121
135
|
| `wait` | `hooks/tezgah_integrity.py::note_tool`, for omp's `wait` | `hooks/tezgah_gate.py::ready_advice`, which counts it as idle; nothing counts it as work |
|
|
122
136
|
| `ask` | `hooks/tezgah_gate.py::ask_advice`, for an omp `ask` or Claude AskUserQuestion call: `detail` holds the questions, cut; `questions` counts them and `defaults` the ones with a recommended option | nothing refuses on it; the row is the record of what the session asked |
|
|
123
137
|
| `grant` | `hooks/tezgah_context.py::context_for`, from each prompt's `user_grants` (`hooks/tezgah_context.py::user_grants`): `detail` holds the user's clause, `target` the outward class it covers (`push`, `release`, `publish`, `pr` or `all`), or `lift:<class>` when the prompt took it back | `hooks/tezgah_integrity.py::grants_in_force`, read by the `needless ask` Stop class and the ask-tool warning |
|
|
124
|
-
| `began` | the gate's allow path for a write or shell call `hooks/tezgah_gate.py::decision`; on opencode the plugin's before-hook
|
|
138
|
+
| `began` | the gate's allow path for a write or shell call `hooks/tezgah_gate.py::decision`; on opencode the plugin's before-hook too, ahead of its own refusals and on its allow path, once per call: a call the core already wrote a row for keeps that one (`hosts/opencode/plugins/tezgah.js::noteBegan`) | `hooks/tezgah_integrity.py::unanswered`, behind a claim in the Stop rule and in `counters.unanswered` |
|
|
125
139
|
| `claim` | `hooks/tezgah_integrity.py::stop_reason`, only for a reply in the claim vocabulary - a completion or verification word (`DONE`/`VERIFIED`), blocked or allowed, or a refused claim about an external system's state (`_external_claim`) | `hooks/tezgah_integrity.py::counters` |
|
|
126
140
|
| `refusal` | `stop_reason`, for a blocked reply that claimed nothing - a work-only or a shape refusal - with the same `blocked: <class>` detail (row version 3) | `counters.refusals`, and `shape_blocked` for a shape class; never a claim; `NOT_TOOL_HOOK` lists it |
|
|
127
141
|
| `after_block` | `hooks/tezgah_integrity.py::stop_reason`, on a reply after this rule's own block (a turn holding a `blocked:` claim or refusal row) that is not refused again (`_record_only`) on Claude, Codex, Cursor and omp; `detail` is `would block: <class>`, `ok` or `no claim` | nothing yet: a record for the after-block observation window, not a claim and not a second `shape` row, so `counters` counts it neither as a claim nor as a reply; `NOT_TOOL_HOOK` lists it |
|
|
128
|
-
| `subagent_end` | `stop_reason(..., subagent=True)`, from Claude's SubagentStop (`hooks/
|
|
142
|
+
| `subagent_end` | `stop_reason(..., subagent=True)`, from Claude's SubagentStop (`hooks/projects-stop.py::main`), Cursor's subagentStop `summary` (`hosts/cursor/hook.py::dispatch`), Droid's SubagentStop `task_result` (`hosts/droid/hook.py::main`), Qwen's and Copilot's SubagentStop (`hosts/qwen/hook.py::main`, `hosts/copilot/hook.py::main`), and Amp's and omp's `subagent_stop`, which their bridges send from the child's end with its `parent` (`hosts/amp/hook.py::handle`, `hosts/omp/hook.py::handle`); the evidence half only, never a block (ADR 011); `detail` as `after_block`'s | nothing yet: the rows plan 055 replays before a subagent end may block; `NOT_TOOL_HOOK` lists it. On Claude a subagent's tool rows sit in the parent's ledger, so until the agent key (plan 050) lands the row judges the parent's turn |
|
|
143
|
+
| `reply_unread` | Droid's, Command Code's, Copilot's, Antigravity's and Goose's Stop (`hosts/droid/hook.py::main`, `hosts/commandcode/hook.py::main`, `hosts/copilot/hook.py::main`, `hosts/antigravity/hook.py::main`, `hosts/goose/hook.py::main`) when the transcript or payload yields no closing assistant text (`hosts/droid/hook.py::last_reply`, `hosts/copilot/hook.py::last_reply`); `detail` names the transcript, or says there was none. Such a Stop event carries no reply, or carries it empty, and an empty reply judged would pass any claim as "no claim", so the turn end is recorded and not judged | nothing yet; `NOT_TOOL_HOOK` lists it |
|
|
129
144
|
| `stop_spec` | nothing since plan 101. Plan 063's shadow wrote one per reply that left a claim or a refusal row: `agree`, `disagree` or `error`, and `fold` or `selector`. The fold it compared against is gone | nothing. `NOT_TOOL_HOOK` still lists it and the replay keeps it beside its claim (`hooks/tezgah_replay.py::SIDE_KINDS`), so an old ledger reads as it did |
|
|
130
145
|
| `deny`, `nudge` | the [gate](gate.md)'s `hooks/tezgah_gate.py::_deny`, first-nudge `hooks/tezgah_gate.py::decision` | `hooks/tezgah_integrity.py::counters` |
|
|
131
|
-
| `snapshot`, `rollback` | `hooks/tezgah_snapshot.py::_capture_one`, `hooks/tezgah_snapshot.py::restore` | `hooks/tezgah_integrity.py::_snapshot_hash`; no counter |
|
|
146
|
+
| `snapshot`, `rollback` | `hooks/tezgah_snapshot.py::_capture_one`, `hooks/tezgah_snapshot.py::restore`, through the module's own `hooks/tezgah_snapshot.py::_append` rather than `note_path` (the Python exception, above): the row carries `v` but keeps its full path, and a forced rollback's `forced` | `hooks/tezgah_integrity.py::_snapshot_hash`; no counter |
|
|
132
147
|
| `compact` | `hooks/tezgah_integrity.py::note_compaction`, from the post-compaction path (`tezgah_context.remember_compaction` `hooks/tezgah_context.py::remember_compaction`) | `hooks/tezgah_integrity.py::_counts` (what `counters` folds with) |
|
|
133
148
|
| `lesson` | `hooks/tezgah_context.py::note_lesson`, one row per lesson the budget left in a session block or a per-turn block, with its 8-hex `key` and `block` (`session` or `turn`), inside the host's `safe()` like the rest of the prompt path | no counter; `hooks/tezgah_context.py::NOT_TOOL_HOOK` keeps it out of `hooks/tezgah_context.py::_ledger_since`, so a session of lesson rows still reads as a gate that never ran |
|
|
134
149
|
| `lesson_tainted` | the gate's allow path for a write to a `.tezgah/lessons.md` (`hooks/tezgah_gate.py::lesson_taint`, from `decision` after every refusal) in a user turn that already holds a row carrying `source`, folded over `turn_rows` (that subagent's rows when the host names one). One row per lesson line the ledger will hold after the write and does not hold now, read off the post-write text, so a partial-line edit names the whole line; its 8-hex `key`, the channel in `source` and `detail`, the ledger in `target`. One row with no `key` when the gate cannot name the line (an `echo` append, an edit whose old text is not there, a removal). The keyed rows also go to the repository's index, which keeps only the ledger's current lines (`hooks/tezgah_lessons.py::write_taint`). Never a refusal (ADR 010); the dry run writes neither | the per-line data label on that lesson in both lesson blocks (`hooks/tezgah_context.py::LESSON_TAINTED`, read once per prompt through `hooks/tezgah_lessons.py::tainted`); no counter |
|
|
@@ -138,6 +153,16 @@ a write tool is `edit`, a shell call is `verify` when its command matches the ch
|
|
|
138
153
|
| `disarm` | `hooks/tezgah_context.py::disarmed`, on the prompt a switch first appears on after the session's first prompt; `detail` is the switch name. Also the gate (`hooks/tezgah_gate.py::decision`), for a call it lets through that changes a `CONTROL_EVIDENCE` target - an open plan's delete or move, a forced `git add` of a `.tezgah/` path - with the call's `id` and `detail` `control: <what>` | the status line's `gate` mark ([status-line.md](status-line.md)) for the switch rows, set by the same check that writes them; the gate's rows are read by a person or a replay; no counter |
|
|
139
154
|
| `attest` | `hooks/tezgah_attest.py::run`, once per session start: Claude and dsh (`hooks/projects-auto-init.py::main`), Codex, Cursor and omp from their session-start events, opencode at the first message through `oncePerSession` (`bin/tezgah-context::main`). `detail` is `ok`, `drifted: <entries>` (an entry added, removed or changed since install, a hook file any user can write, a stale omp bridge, a changed entry script on a release install) or `unverified: <why>`; `host` names the host, `switches` the kill switches present. The module is imported only inside that call, so a broken one costs the row and never a gate | the `drift` status mark and the `harness` field of the session's later `claim` rows, read from one `harness_drift` row of `<cache>/tezgah.db` per session and host (`hooks/tezgah_attest.py::drift_key`, so one host's clean start cannot clear another's) - an annotation, never a Stop block; `NOT_TOOL_HOOK` lists it, so a session start after a compaction does not read as a gate that ran |
|
|
140
155
|
| `crash` | `hooks/tezgah_guard.py::safe` for a core call that raised, and `hooks/tezgah_guard.py::import_failed` for an entry point whose core imports raised (`detail` `import: <class>: <message>`). An import failure also writes one stderr line and the status line's `crash` mark, always; when `tezgah_integrity` or `tezgah_paths` is the module that failed, the row is the part that cannot be written. Hooks fail open with exit 0; `tezgah-gate check` exits 3 | `NOT_TOOL_HOOK` lists it; no counter |
|
|
156
|
+
| `shape` | `hooks/tezgah_integrity.py::stop_reason`, one row per judged reply, keyed like the claim row: `detail` is the report-only shape flags or `ok`, with the reply-shape fields and, in a read-only turn, `mode` | `hooks/tezgah_integrity.py::_counts` (`replies`, `shape`); `hooks/tezgah_integrity.py::_in_flight` |
|
|
157
|
+
| `authorized` | `hooks/tezgah_integrity.py::note_turn`, before the `turn` row of a prompt that names a kill switch while no call is in flight; `authorized` lists the names | the switch latch (`hooks/tezgah_paths.py::_authorized`), which honors a switch made mid-session only when such a row names it |
|
|
158
|
+
| `drift` | `hooks/tezgah_gate.py::drift_reason`, once per turn, before the refusal that re-states the rules once the turn passed `DRIFT_STEPS` steps; `detail` is the step count | `drift_reason` itself (one per turn); `hooks/tezgah_shapes.py::GATE_SIDE_KINDS` and the replay's `hooks/tezgah_replay.py::SIDE_KINDS` |
|
|
159
|
+
| `spawned`, `forked` | the child's session start, in the child's ledger: `spawned` on omp and Amp for a subagent whose bridge names its `parent` (`hosts/omp/hook.py::handle`, `hosts/amp/hook.py::handle`), on Cursor's subagentStart (`hosts/cursor/hook.py::dispatch`) and on opencode's `session.created` of a child (the plugin's `event` hook in `hosts/opencode/plugins/tezgah.js`), with its `agent`; `forked` on omp for a fork, `parent` holding the session it was forked from | the race rule's relation (`hooks/tezgah_integrity.py::_relation`, `hooks/tezgah_integrity.py::spawn_of`); `tezgah-route --report` (`hooks/tezgah_models.py::route_report`); the replay corpus (`hooks/tezgah_replay.py::build_corpus`) |
|
|
160
|
+
| `spawn` | omp's post-tool path for a `task` call (`hosts/omp/hook.py::handle`), one row per named task in the parent's ledger: `child` is the task name, `agent` the agent it runs (`task` when none is given) | `tezgah-route --report`, joining a route to its worker (`hooks/tezgah_models.py::route_report`) |
|
|
161
|
+
| `subagent_start` | the SubagentStart event in the parent's ledger: Claude's (`hooks/projects-auto-init.py::note_child`), Codex's (`hosts/codex/hook.py::main`) and Qwen's (`hosts/qwen/hook.py::main`); `detail` is the agent type, `agent` the agent id | `tezgah-route --report` (`hooks/tezgah_models.py::route_report`) |
|
|
162
|
+
| `orch` | `hooks/tezgah_context.py::record`, for a subagent event (Claude's and dsh's SubagentStart, Codex, Cursor, Droid, Qwen and Copilot subagent events) or a call a host classifies as a delegation | `counters.fanout` (`hooks/tezgah_integrity.py::_counts`) |
|
|
163
|
+
| `judge` | each model judgement's caller: the skill pick (`hooks/tezgah_skill_pick.py::judge`), `tezgah-route` (`bin/tezgah-route.py::main`), `tezgah-docs` and `tezgah-triage` (`bin/tezgah-docs.py::record`, `bin/tezgah-triage.py::record`); `detail` names the caller, the model and the token counts | `counters.judge` (`hooks/tezgah_integrity.py::_counts`) |
|
|
164
|
+
| `route` | `bin/tezgah-route.py::main`, when the call knows its session: the routing verdict (`hooks/tezgah_models.py::route_detail`) | `tezgah-route --report` (`hooks/tezgah_models.py::route_fields`, `hooks/tezgah_models.py::route_report`); the replay's `hooks/tezgah_replay.py::TEST_ROW_KINDS` |
|
|
165
|
+
| `delegation` | opencode's plugin only (`noteDelegation` in `hosts/opencode/plugins/tezgah.js`), when the Python core could not be asked about a call: `detail` is `gate: <failure>`, `id` the session. The call still passes | no reader by name; `counters`' kind histogram counts it (`hooks/tezgah_integrity.py::_counts`) |
|
|
141
166
|
|
|
142
167
|
**`compact` is what a compaction kept, from the record.** When the host hands the PostCompact
|
|
143
168
|
payload the text the model is about to receive — Claude's `compact_summary` — the shared path
|
|
@@ -296,12 +321,31 @@ The checker is `_stop_block` (`hooks/tezgah_integrity.py::_stop_block`), reached
|
|
|
296
321
|
(`hosts/omp/hook.py::handle`) — all with `{"decision": "block", "reason": …}`, all inert outside a
|
|
297
322
|
[root](glossary.md#root) and under the `verify-off` [kill switch](glossary.md#kill-switch)
|
|
298
323
|
(`hooks/projects-stop.py::main`). Each of these hosts judges the reply after a block again.
|
|
324
|
+
The reply the rule judges is read through `reply_prose`
|
|
325
|
+
(`hooks/tezgah_integrity.py::reply_prose`). A string passes through. A host that hands over
|
|
326
|
+
a message object yields its `text` blocks, joined the way the model wrote them. A thinking
|
|
327
|
+
or tool block is left out. omp's `session_stop` carries `last_assistant_message` as the object
|
|
328
|
+
its `getLastAssistantMessage()` returns, and the bridge passed it through, so the rule judged
|
|
329
|
+
`str(dict)`: one line, no list items, diluted prose. Measured on
|
|
330
|
+
the owner's evidence database (2026-10-29): 1643 of 1656 omp `shape` rows of 45 days carried
|
|
331
|
+
`lines: 1`. Every other host wrote 209 multi-line rows of 231. Of the three days' refusals, 24
|
|
332
|
+
were `reply language` over such a row, and every line-shaped class read a text that had no
|
|
333
|
+
lines. The bridge now sends the text too
|
|
334
|
+
(`hosts/omp/tezgah-hook.ts.in`), but the core is where a host cannot repeat it (plan 143).
|
|
299
335
|
The re-ask budget is the core's (`_record_only`, `hooks/tezgah_integrity.py::_record_only`): at
|
|
300
336
|
most `STOP_REASKS` = 2 refusals in one user turn, and never two for one reason class. The count
|
|
301
337
|
comes from the turn's `blocked:` claim and refusal rows, not from the host's payload. The omp host
|
|
302
|
-
hands a block back as a queued message. Its prompt hook writes that text as a new `turn` row.
|
|
303
|
-
|
|
304
|
-
|
|
338
|
+
hands a block back as a queued message. Its prompt hook writes that text as a new `turn` row.
|
|
339
|
+
Gemini does the same: an AfterAgent deny comes back through BeforeAgent as the next prompt
|
|
340
|
+
(`hosts/gemini/hook.py::main`). A refusal row stores the key of its text as `reask`. A `turn` row with that key starts no new user
|
|
341
|
+
turn for the count (`_turn_refusals`, `hooks/tezgah_integrity.py::_turn_refusals`). Antigravity's
|
|
342
|
+
`continue` re-enters the loop with no prompt, so its first-invocation hook writes no `turn` row
|
|
343
|
+
while the ledger's last row is that refusal (`hosts/antigravity/hook.py::reentered`). A Goose Stop
|
|
344
|
+
block keeps the turn going with no new prompt and no host count, so its turn rows alone decide
|
|
345
|
+
(`hosts/goose/hook.py::main`). Amp's plugin continues a blocked turn with a user message that
|
|
346
|
+
starts with `[tezgah:stop]`. The plugin never sends that prompt as a `user_prompt`. So it writes
|
|
347
|
+
no `turn` row and grants nothing. The Stop rule then judges that turn with `stop_hook_active`
|
|
348
|
+
(`hosts/amp/tezgah-plugin.ts.in`). The omp host
|
|
305
349
|
caps the continuations it starts itself, but never a chain of blocks, so tezgah's count bounds that
|
|
306
350
|
chain. So a reply that fixes the first class but adds a new one gets one more refusal. A blocker
|
|
307
351
|
list with no evidence is such a new class. A reply after a block that is not refused writes one
|
|
@@ -310,7 +354,7 @@ while the turn holds no refusal of this rule. That reply gets no refusal and no
|
|
|
310
354
|
Stop hook may have blocked. The host's count rules only where the ledger cannot. A session with no
|
|
311
355
|
`turn` row allows one refusal per chain. A Cursor `loop_count` of 2 ends the chain. The opencode
|
|
312
356
|
host has no Stop rule and so no budget. Its plugin API has no end-of-turn hook with an output. The
|
|
313
|
-
`event` hook of `hosts/opencode/plugins/tezgah.js` only observes.
|
|
357
|
+
`event` hook of `hosts/opencode/plugins/tezgah.js` only observes. Fifteen triggers, in order, each naming its reason class (`_stop_block`
|
|
314
358
|
`hooks/tezgah_integrity.py::_stop_block`).
|
|
315
359
|
The first four judge how the reply is written (`_shape_block`, `hooks/tezgah_integrity.py::_shape_block`); the rest judge its evidence:
|
|
316
360
|
|
|
@@ -339,8 +383,9 @@ The four shape classes share their switches with the text they enforce: `adhd-of
|
|
|
339
383
|
are read at a command position on the masked text, `re.M` so that a command on its own later
|
|
340
384
|
line of the same call counts. A component turn owes one thing more: when a changed UI source is
|
|
341
385
|
a component rather than a screen (`hooks/tezgah_integrity.py::DESIGN_COMPONENT` - a file
|
|
342
|
-
under a component/views/widgets
|
|
343
|
-
|
|
386
|
+
under a component/views/widgets/templates directory, a name that carries the convention on its
|
|
387
|
+
own, a single-file `.vue`/`.svelte`/`.astro` component, or the route file a framework with no
|
|
388
|
+
`components/` convention renders from: `page.tsx`, `layout.tsx`, `+page.svelte`), a fresh read of the screen is not
|
|
344
389
|
enough by itself and a `tezgah-design check` row has to be newer than the component write it
|
|
345
390
|
judges (`hooks/tezgah_integrity.py::_design_evidence`, branch `hooks/tezgah_stopspec.py::verdict`) - and it has
|
|
346
391
|
to be a check whose pass was seen (`passing_check`, `hooks/tezgah_integrity.py::passing_check`), because
|
|
@@ -376,8 +421,12 @@ The four shape classes share their switches with the text they enforce: `adhd-of
|
|
|
376
421
|
A prose edit is no step and no change (`hooks/tezgah_integrity.py::_prose_edit`,
|
|
377
422
|
`hooks/tezgah_integrity.py::_work_row`). Such an `edit` row has a real `target` ending in
|
|
378
423
|
`.md`, `.markdown`, `.rst` or `.adoc`. It owes no check and does not stale a pass. An agent
|
|
379
|
-
instruction file is not prose
|
|
380
|
-
`
|
|
424
|
+
instruction file is not prose (`hooks/tezgah_integrity.py::INSTRUCTION_NAMES`,
|
|
425
|
+
`hooks/tezgah_integrity.py::INSTRUCTION_DIRS`, matched without case): a file named `SKILL.md`, `AGENTS.md`,
|
|
426
|
+
`CLAUDE.md`, `GEMINI.md`, `copilot-instructions.md` or `rules.md`, and anything with `skills/`,
|
|
427
|
+
`prompts/`, `templates/`, `agents/`, `commands/`, `output-styles/`, `hooks/`, `.claude/`,
|
|
428
|
+
`.cursor/`, `.codex/`, `.omp/` or `.github/` among its directories, stay a change. A repo's own
|
|
429
|
+
`hosts/<host>` directory is not in the set. A reply that
|
|
381
430
|
claims a test ran ("tested", "tüm testler geçti") still needs a pass after a prose-only turn.
|
|
382
431
|
A comment-only edit to code is still a change: the row carries no text to tell it apart.
|
|
383
432
|
10. **no external read** — the reply states the state of a system tezgah does not own — a registry,
|
|
@@ -457,7 +506,8 @@ The four shape classes share their switches with the text they enforce: `adhd-of
|
|
|
457
506
|
refused. A turn with no step, a ledger with no turn marker and a nested session skip it. A
|
|
458
507
|
subagent's end is never refused (ADR 011). Its `subagent_end` row carries the count of its
|
|
459
508
|
untyped items as `residual`. The parent's Stop then refuses a reply that types no item of its
|
|
460
|
-
own. Claude and
|
|
509
|
+
own. Claude, Cursor, Droid, Qwen and Copilot write that row from their subagent-stop events,
|
|
510
|
+
and Amp's bridge from the child's end. omp fires none for a
|
|
461
511
|
task session. Its bridge sends the child's last reply from `agent_end`. The row lands in the
|
|
462
512
|
ledger of the session that spawned the child, read from the child's own session header, and
|
|
463
513
|
counts once per child. Codex, opencode and dsh write no such row. On omp the reply after a
|
|
@@ -516,18 +566,71 @@ The four shape classes share their switches with the text they enforce: `adhd-of
|
|
|
516
566
|
(`hooks/tezgah_integrity.py::QUESTION_END`). Its paragraph names no recommended default, and
|
|
517
567
|
it names no step the ask policy keeps from a default (`hooks/tezgah_integrity.py::never_default`).
|
|
518
568
|
Such steps are a force push, a delete, production, a credential, a third party, or a spend
|
|
519
|
-
over `spend_cap`.
|
|
569
|
+
over `spend_cap`. Building a plan the reply wrote is one too
|
|
570
|
+
(`hooks/tezgah_integrity.py::PLAN_BUILD`). So is a last line that asks the owner to pick a
|
|
571
|
+
variant ("V4 mü V1 mi?"), to take a way around a tezgah refusal, or to decide about another
|
|
572
|
+
session's work (`hooks/tezgah_integrity.py::OWNER_CHOICE`, plan 136). The
|
|
573
|
+
class then refuses a process question: the packs' `ask.process`
|
|
520
574
|
shapes, such as "Başlayayım mı?", "shall I proceed?" and "which first?"
|
|
521
575
|
(`hooks/tezgah_integrity.py::PROCESS_ASK`). It also refuses a question about an outward
|
|
522
576
|
step the user granted this session (`hooks/tezgah_integrity.py::grants_in_force`). A question
|
|
523
577
|
about an outward step the user has not granted is the one the policy asks, so it passes. The
|
|
524
|
-
refusal says to take the step, or to move
|
|
578
|
+
refusal says to take the step, or to move a taste or intent fork into the approval batch
|
|
579
|
+
with a default. A class-F step waits for the answer. Under
|
|
525
580
|
`tr` it opens with "Bu soruyu sorma". `_residual_block` asks it after `residual work` and
|
|
526
581
|
before `ready steps` (`hooks/tezgah_integrity.py::needless_ask`). Unlike those two, it needs
|
|
527
|
-
no work in the turn, because a
|
|
582
|
+
no work in the turn, because a reply that ends on a bare "Başlayayım mı?" is its measured case.
|
|
528
583
|
The research line counted 33 process questions among 48 question endings in the owner's omp
|
|
529
584
|
transcripts. It refuses once per turn and counts like `residual work`
|
|
530
585
|
(`hooks/tezgah_integrity.py::LIST_BLOCKS`).
|
|
586
|
+
14. **handed step** — a turn that ran a command of its own ends by telling the reader to run
|
|
587
|
+
one (plan 142). The line names a command in backticks and addresses the reader as the one
|
|
588
|
+
who runs it: the packs' `ask.hand_over` shapes, "komutunu sen çalıştır", "çalıştırman
|
|
589
|
+
gerekiyor", "senin adımın", "please run", "your step:"
|
|
590
|
+
(`hooks/tezgah_integrity.py::handed_steps`). An answer about how something is done is not
|
|
591
|
+
one of them. "you can run `git config --global ...`" answers the question the reader
|
|
592
|
+
asked, and the review of this plan caught the wider wording refusing it. The turn must
|
|
593
|
+
hold a
|
|
594
|
+
`run`/`verify*` row (`hooks/tezgah_integrity.py::COMMAND_KINDS`). A turn that ran nothing
|
|
595
|
+
could not have run this either, which keeps the class inert on a host that sends no mode. A
|
|
596
|
+
read-only mode closes it outright: there every step waits for the approval. A table row and
|
|
597
|
+
a quoted line are not the
|
|
598
|
+
reply speaking, so the class skips them. The line stays the user's when it names an
|
|
599
|
+
outward step (`hooks/tezgah_integrity.py::outward_classes`), a class-F step
|
|
600
|
+
(`hooks/tezgah_integrity.py::never_default`), the owner's consent, tezgah's own install or a
|
|
601
|
+
`brew` step (`hooks/tezgah_integrity.py::OWNER_STEP`), or when it types its own blocker. The
|
|
602
|
+
refusal says to run each one and report what it printed. `_residual_block` asks it after
|
|
603
|
+
`needless ask` and before `ready steps`. A handover under a residual label ("Sonraki adım:")
|
|
604
|
+
belongs to `residual work`, which comes first. This class is the plain sentence that one
|
|
605
|
+
never saw. It refuses once per turn and counts like `residual work`
|
|
606
|
+
(`hooks/tezgah_integrity.py::LIST_BLOCKS`). Measured on the owner's top-level omp and Claude
|
|
607
|
+
transcripts (2026-10-29): of 3307 user turns, 27 replies handed the reader a command and no
|
|
608
|
+
class refused any of them. The shipped shapes refuse 9 of those turns, 8 of them a step the
|
|
609
|
+
session could have taken. Those eight are four `tezgah-task phase` moves, a local merge,
|
|
610
|
+
two "the next step is yours" and a server command that owes an ACCESS line. The ninth
|
|
611
|
+
quotes the gate's own refusal. The wider wording of the first two review rounds refused 40
|
|
612
|
+
turns and then 14, an answer about how a command is run among them.
|
|
613
|
+
15. **plan claim** — a first-person claim that a command or check ran, in a turn the host ran
|
|
614
|
+
in a read-only mode (plan 142).
|
|
615
|
+
The host sends the mode with the Stop payload: `permission_mode` on Claude, Command Code,
|
|
616
|
+
Qwen and Droid, and omp's extension sends `plan` from `ctx.sessionModes`. `plan` and Droid's
|
|
617
|
+
`spec` are the read-only ones (`hooks/tezgah_integrity.py::READ_ONLY_MODES`). Nothing could
|
|
618
|
+
run in such a turn, so the claim says what cannot have happened. `_stop_block` asks it right
|
|
619
|
+
after the tamper classes and before the
|
|
620
|
+
admission, since "doğrulanmadı" repairs an unverified claim and here there was nothing to
|
|
621
|
+
check. A check that passed in the turn anyway clears it, like any other
|
|
622
|
+
evidence: a host may allow a read-only command in the mode. The trigger is the packs'
|
|
623
|
+
`ask.ran_it` vocabulary (`hooks/tezgah_integrity.py::RAN_IT`) - "test ettim", "i ran the
|
|
624
|
+
suite". It is read through the question and negation filter a claim is read through
|
|
625
|
+
(`hooks/tezgah_integrity.py::_asserted`). The done and verified vocabularies are not the
|
|
626
|
+
trigger: a plan states its acceptance in those words ("Done when all tests pass", "testler
|
|
627
|
+
yeşil olunca"), and the review of this plan caught both refused. It counts
|
|
628
|
+
as a false completion. The same mode exempts `needless ask` and closes `handed step`
|
|
629
|
+
(`hooks/tezgah_integrity.py::_residual_block`): the approval question is what plan mode asks
|
|
630
|
+
for, and every step it names waits for that approval. Every row of such a turn carries
|
|
631
|
+
`mode`, so a replay judges it as the mode it ran in
|
|
632
|
+
(`hooks/tezgah_replay.py::build_corpus`). A host that sends no
|
|
633
|
+
mode reads as `default`, which is what every rule did before the field.
|
|
531
634
|
|
|
532
635
|
A past-time temporal spec decides classes 5–10 (`hooks/tezgah_stopspec.py::FORMULAS`,
|
|
533
636
|
plan 063). Seven formulas read ten row atoms (`ROW_ATOMS`) and the turn marker. The module compiles
|
|
@@ -810,9 +913,9 @@ Cursor (`hosts/cursor/hook.py::dispatch`), omp (`hosts/omp/hook.py::handle`, whi
|
|
|
810
913
|
empty stand-in for the result body its bridge never sends, so a subagent's report keeps its label), and opencode, whose plugin
|
|
811
914
|
cannot import the core in process and mirrors the control in JavaScript instead — the channel on the
|
|
812
915
|
call's own row, the label and the taint notice in front of the result the hook is handed, including
|
|
813
|
-
the `external` row an MCP answer or a fetched page earns (`untrustedSource
|
|
814
|
-
`hosts/opencode/plugins/tezgah.js
|
|
815
|
-
`tierRead`
|
|
916
|
+
the `external` row an MCP answer or a fetched page earns (`hosts/opencode/plugins/tezgah.js::untrustedSource`,
|
|
917
|
+
`hosts/opencode/plugins/tezgah.js::labelResult`, with the tier's argv reader
|
|
918
|
+
`hosts/opencode/plugins/tezgah.js::tierRead`). The two halves are pinned against each other over a shared corpus, so neither
|
|
816
919
|
can move without failing the other's test (`tests/test_opencode_plugin.py::OpenCodePlugin.test_the_classifier_agrees_with_the_python_half_on_a_shared_corpus`). One difference is deliberate:
|
|
817
920
|
opencode's plugin writes that `external` row itself and carries the channel alone, since the UI rule
|
|
818
921
|
`_screen_read` belongs to the Python half and does not run there.
|