@r1z4x/tezgah 1.13.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 +175 -1
- package/MANIFEST +18 -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 +15 -8
- 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 +157 -41
- 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 +12 -6
- package/bin/tezgah-rollback +3 -0
- package/bin/tezgah-route +1 -1
- package/bin/tezgah-setup +596 -157
- package/bin/tezgah-status +11 -1
- package/bin/tezgah-task +3 -0
- 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 +3 -3
- package/docs/contract.md +77 -31
- package/docs/environment.md +58 -24
- package/docs/evidence.md +49 -19
- package/docs/gate.md +71 -21
- package/docs/hosts.md +65 -52
- package/docs/layers.md +1 -1
- package/docs/operations.md +67 -24
- package/docs/research.md +1 -1
- package/docs/skills.md +31 -8
- package/docs/status-line.md +33 -23
- package/docs/testing.md +48 -17
- package/docs/vision.md +6 -2
- package/hooks/lang/en.json +5 -2
- package/hooks/lang/tr.json +3 -2
- package/hooks/projects-posttooluse.py +11 -3
- package/hooks/projects-stop.py +1 -1
- package/hooks/tezgah_context.py +40 -23
- package/hooks/tezgah_gate.py +404 -106
- package/hooks/tezgah_integrity.py +367 -97
- package/hooks/tezgah_lessons.py +5 -2
- package/hooks/tezgah_paths.py +17 -6
- package/hooks/tezgah_policy.py +13 -5
- package/hooks/tezgah_skill_pick.py +1 -1
- package/hooks/tezgah_snapshot.py +6 -3
- package/hooks/tezgah_stopspec.py +2 -1
- package/hosts/amp/hook.py +1 -1
- package/hosts/amp/tezgah-plugin.ts.in +13 -3
- package/hosts/antigravity/hook.py +1 -1
- package/hosts/codex/hook.py +1 -1
- package/hosts/commandcode/hook.py +1 -1
- package/hosts/copilot/hook.py +1 -1
- package/hosts/cursor/hook.py +2 -1
- package/hosts/droid/hook.py +1 -1
- package/hosts/gemini/hook.py +1 -1
- package/hosts/goose/hook.py +1 -1
- package/hosts/omp/hook.py +1 -1
- package/hosts/omp/tezgah-hook.ts.in +127 -35
- package/hosts/opencode/plugins/tezgah.js +209 -63
- package/hosts/qwen/hook.py +1 -1
- package/output-styles/tezgah.md +1 -1
- 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/plan-add/render_table.py +7 -0
- package/skills/pm-frameworks/LICENSE +21 -0
- package/skills/rl-env/LICENSE +201 -0
- package/skills/tezgah-contract/SKILL.md +2 -2
- 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/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`, `hooks/tezgah_paths.py::AMP_PLUGIN`, `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`). |
|
|
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,16 +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. |
|
|
125
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. |
|
|
126
153
|
|
|
127
154
|
## Test knob
|
|
128
155
|
|
|
129
156
|
| Variable | Read by | Default | Effect |
|
|
130
157
|
|---|---|---|---|
|
|
131
|
-
| `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. |
|
|
132
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. |
|
|
133
160
|
|
|
134
161
|
## Debugging
|
|
@@ -136,17 +163,24 @@ the kill switches are files, not variables, and live in
|
|
|
136
163
|
| Variable | Read by | Default | Effect |
|
|
137
164
|
|---|---|---|---|
|
|
138
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. |
|
|
139
167
|
|
|
140
168
|
## Source of truth
|
|
141
169
|
|
|
142
170
|
`hooks/tezgah_paths.py`, `hooks/tezgah_judge.py`, `hooks/tezgah_models.py`,
|
|
143
171
|
`hooks/tezgah_index.py`, `hooks/tezgah_apps.py`, `hooks/tezgah_agents.py`,
|
|
144
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`,
|
|
145
177
|
`hooks/projects-auto-init.py`, `hooks/projects-posttooluse.py`, `statusline.py`,
|
|
146
178
|
`bin/tezgah-setup`, `bin/consult`, `bin/codegen`, `bin/tezgah-status`,
|
|
147
|
-
`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`,
|
|
148
181
|
`packaging/upgrade.sh`, `packaging/install.ps1`, `packaging/build.sh`,
|
|
149
|
-
`hosts/omp/tezgah-hook.ts.in`, `hosts/
|
|
150
|
-
`hosts/dsh/statusline/lib/index.js`. The
|
|
151
|
-
code
|
|
152
|
-
|
|
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,15 +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/
|
|
129
|
-
| `reply_unread` | Droid's, Command Code's and
|
|
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 |
|
|
130
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 |
|
|
131
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` |
|
|
132
|
-
| `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 |
|
|
133
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) |
|
|
134
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 |
|
|
135
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 |
|
|
@@ -139,6 +153,16 @@ a write tool is `edit`, a shell call is `verify` when its command matches the ch
|
|
|
139
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 |
|
|
140
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 |
|
|
141
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`) |
|
|
142
166
|
|
|
143
167
|
**`compact` is what a compaction kept, from the record.** When the host hands the PostCompact
|
|
144
168
|
payload the text the model is about to receive — Claude's `compact_summary` — the shared path
|
|
@@ -359,8 +383,9 @@ The four shape classes share their switches with the text they enforce: `adhd-of
|
|
|
359
383
|
are read at a command position on the masked text, `re.M` so that a command on its own later
|
|
360
384
|
line of the same call counts. A component turn owes one thing more: when a changed UI source is
|
|
361
385
|
a component rather than a screen (`hooks/tezgah_integrity.py::DESIGN_COMPONENT` - a file
|
|
362
|
-
under a component/views/widgets
|
|
363
|
-
|
|
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
|
|
364
389
|
enough by itself and a `tezgah-design check` row has to be newer than the component write it
|
|
365
390
|
judges (`hooks/tezgah_integrity.py::_design_evidence`, branch `hooks/tezgah_stopspec.py::verdict`) - and it has
|
|
366
391
|
to be a check whose pass was seen (`passing_check`, `hooks/tezgah_integrity.py::passing_check`), because
|
|
@@ -396,8 +421,12 @@ The four shape classes share their switches with the text they enforce: `adhd-of
|
|
|
396
421
|
A prose edit is no step and no change (`hooks/tezgah_integrity.py::_prose_edit`,
|
|
397
422
|
`hooks/tezgah_integrity.py::_work_row`). Such an `edit` row has a real `target` ending in
|
|
398
423
|
`.md`, `.markdown`, `.rst` or `.adoc`. It owes no check and does not stale a pass. An agent
|
|
399
|
-
instruction file is not prose
|
|
400
|
-
`
|
|
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
|
|
401
430
|
claims a test ran ("tested", "tüm testler geçti") still needs a pass after a prose-only turn.
|
|
402
431
|
A comment-only edit to code is still a change: the row carries no text to tell it apart.
|
|
403
432
|
10. **no external read** — the reply states the state of a system tezgah does not own — a registry,
|
|
@@ -477,7 +506,8 @@ The four shape classes share their switches with the text they enforce: `adhd-of
|
|
|
477
506
|
refused. A turn with no step, a ledger with no turn marker and a nested session skip it. A
|
|
478
507
|
subagent's end is never refused (ADR 011). Its `subagent_end` row carries the count of its
|
|
479
508
|
untyped items as `residual`. The parent's Stop then refuses a reply that types no item of its
|
|
480
|
-
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
|
|
481
511
|
task session. Its bridge sends the child's last reply from `agent_end`. The row lands in the
|
|
482
512
|
ledger of the session that spawned the child, read from the child's own session header, and
|
|
483
513
|
counts once per child. Codex, opencode and dsh write no such row. On omp the reply after a
|
|
@@ -883,9 +913,9 @@ Cursor (`hosts/cursor/hook.py::dispatch`), omp (`hosts/omp/hook.py::handle`, whi
|
|
|
883
913
|
empty stand-in for the result body its bridge never sends, so a subagent's report keeps its label), and opencode, whose plugin
|
|
884
914
|
cannot import the core in process and mirrors the control in JavaScript instead — the channel on the
|
|
885
915
|
call's own row, the label and the taint notice in front of the result the hook is handed, including
|
|
886
|
-
the `external` row an MCP answer or a fetched page earns (`untrustedSource
|
|
887
|
-
`hosts/opencode/plugins/tezgah.js
|
|
888
|
-
`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
|
|
889
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:
|
|
890
920
|
opencode's plugin writes that `external` row itself and carries the channel alone, since the UI rule
|
|
891
921
|
`_screen_read` belongs to the Python half and does not run there.
|
package/docs/gate.md
CHANGED
|
@@ -37,7 +37,7 @@ writes stays under `~/.cache/tezgah/replay`. The corpus, the exclusions and the
|
|
|
37
37
|
|---|---|---|
|
|
38
38
|
| claude, dsh | `hooks/projects-pretooluse.py::main` (`hosts/dsh/hooks.json:13-14`) | `hookSpecificOutput.permissionDecision: "deny"` |
|
|
39
39
|
| codex, cursor, omp | `hosts/codex/hook.py::main`, `hosts/cursor/hook.py::dispatch`, `hosts/omp/hook.py::handle` | that host's own envelope — [hosts.md](hosts.md) |
|
|
40
|
-
| opencode | `hosts/opencode/plugins/tezgah.js:
|
|
40
|
+
| opencode | `Tezgah` `hosts/opencode/plugins/tezgah.js:3249` | `throw new Error(deny)` `hosts/opencode/plugins/tezgah.js:3455` |
|
|
41
41
|
|
|
42
42
|
Every refusal is also recorded before it is returned: `_deny` appends a ledger row `deny` whose `detail` is `"<rule>: <reason, first 80 chars>"`, plus any
|
|
43
43
|
`extra` (`hooks/tezgah_gate.py::_deny`). That row, not the reason wording, is where "why was this denied" is answered, and `<rule>` is the name used below.
|
|
@@ -55,7 +55,22 @@ Its verb class only picks which existing content rules read its payload. An effe
|
|
|
55
55
|
like the host matcher (`hooks/tezgah_integrity.py::mcp_class`, `hooks/tezgah_integrity.py::MCP_VERBS`). The tool part is what follows the last `__`.
|
|
56
56
|
A clause of it that a read verb leads (`get_commit`) is a read. An effect verb in another clause still decides (`get_or_create_issue`).
|
|
57
57
|
omp's `mcp__<server>_<tool>` has no server boundary, so there an effect verb anywhere decides.
|
|
58
|
+
On omp, an MCP call goes through the device tool, as a `write` to `xd://mcp__<name>` with the arguments as JSON.
|
|
59
|
+
Its bridge sends the gate that `write` and the `mcp__<name>` call it carries.
|
|
60
|
+
It records the result as the carried call (`hosts/omp/tezgah-hook.ts.in`).
|
|
61
|
+
Then omp emits the carried call's own event under the same id.
|
|
62
|
+
The bridge skips that one event once (`CARRIED` in `hosts/omp/tezgah-hook.ts.in`).
|
|
63
|
+
A reused id, an empty id or a refused call reaches the gate again.
|
|
64
|
+
The gate writes no `began` row for such a `write` (`hooks/tezgah_gate.py::ROUTED_DEVICE`).
|
|
65
|
+
An omp `ast_edit` call reaches the gate as one `edit` per `paths` item, marked `via: "ast_edit"`.
|
|
66
|
+
Only such an item is read as a glob or a directory (`hooks/tezgah_gate.py::_tree_label`).
|
|
67
|
+
It meets the control rule for every protected directory under it.
|
|
68
|
+
It also meets it for every hook file, shared registration and git config file it covers (`hooks/tezgah_gate.py::_control_files`).
|
|
58
69
|
A `write` call such as `write_file` meets the shortcut, attribution and secret rules.
|
|
70
|
+
Its `path`, `file_path` and `destination` fields meet the control rule (`hooks/tezgah_gate.py::control_reason`).
|
|
71
|
+
A move's `source` meets it too, read as a removal.
|
|
72
|
+
A forge server's tool (`mcp__github__`, `mcp__gitlab__`, `mcp__gitea__`, `mcp__bitbucket__`) writes into a remote repository (`hooks/tezgah_gate.py::REMOTE_MCP`).
|
|
73
|
+
So the gate reads its `path` as remote, not against the local checkout. A `repo` field on any other server's call changes nothing.
|
|
59
74
|
A `publish` call such as `send_message` meets attribution and secret. An `act` call such as `click` meets none of them.
|
|
60
75
|
Every class counts as an effect for the drift re-statement (`hooks/tezgah_gate.py::effectful`) and the taint notice
|
|
61
76
|
(`hooks/tezgah_untrusted.py::effectful`). So an `act` call can now draw the drift re-statement, once per long turn.
|
|
@@ -85,6 +100,7 @@ it first after the root check (`decision` `hooks/tezgah_gate.py::decision`). The
|
|
|
85
100
|
| hook wiring | the opencode plugin file, the omp hook, the Claude plugin copy, the install prefix, and the tree the hooks run from unless the session works inside it (`hooks/tezgah_gate.py::_registration`, `hooks/tezgah_gate.py::_install_trees`) | any write |
|
|
86
101
|
| shared registration files | `.claude/settings.json` and `settings.local.json`, `~/.claude/plugins/installed_plugins.json` and `known_marketplaces.json`, Codex's and Cursor's `hooks.json`, Codex's `config.toml`, Gemini's `settings.json`, Goose's `~/.config/goose/settings.json` | a shell write; a write tool only when it changes an entry that is tezgah's - `statusLine`, `attribution`, a key that turns hooks off (Claude's `disableAllHooks`, Gemini's `hooksConfig`; `hooks/tezgah_gate.py::OWNED_KEYS`), a key or item naming tezgah, such as `tezgah` in Goose's `disabledPlugins` (the user's own hook beside tezgah's in one matcher group stays theirs), or any `[hooks...]` line of `config.toml`, where the trust entries live (`hooks/tezgah_gate.py::_shared_change`) |
|
|
87
102
|
| repository marks and hooks | a `REPO_MARKS` name under a root, the session checkout's `.git/hooks` (a `.sample` file and a clone elsewhere pass), `git config core.hooksPath` set or unset there or `--global` | any write |
|
|
103
|
+
| | the session checkout's `.git/config` and `.git/config.worktree` (and a linked worktree's `.git/worktrees/*/config.worktree`), `~/.gitconfig` and `$XDG_CONFIG_HOME/git/config` (`hooks/tezgah_gate.py::_git_config`) | a shell write, and a write tool only when it adds, changes or removes a `core.hooksPath`, `include.path`, `includeIf.*.path` or `extensions.worktreeConfig` entry (`hooks/tezgah_gate.py::_git_hook_keys`). `git config` setting one of those keys in the session checkout or `--global`/`--system` is refused the same way, and with `-f`/`--file` the named file is judged, wherever the cwd is (`hooks/tezgah_gate.py::_hooks_setting`, `hooks/tezgah_gate.py::_git_change`) |
|
|
88
104
|
| | `.husky` | a delete, a chmod that takes a permission away, or a move |
|
|
89
105
|
| | a directory holding any protected path (`rm -rf ~/.cache`, `mv .git /tmp/x`), also through a link (`rm -rf link/`), including `.tezgah/plans/open/` itself, every ancestor of it (`rm -rf .tezgah`, `mv .tezgah/plans /tmp/x`) and a glob or brace directly under it (`rm -rf .tezgah/plans/open/*`) | a delete or move |
|
|
90
106
|
| evidence only | a delete or move of one named plan under `.tezgah/plans/open/`; a glob or the directory is refused (row above); `git add -f` or `--force` of a `.tezgah/` path (`_git_change` `hooks/tezgah_gate.py::_git_change`) | never: the call goes on to the other rules and, if they let it through, leaves a `disarm` row whose `detail` is `control: <what>` (`CONTROL_EVIDENCE` `hooks/tezgah_gate.py::CONTROL_EVIDENCE`); `git rm --cached` leaves none (plan-sync's move) |
|
|
@@ -93,8 +109,10 @@ it first after the root check (`decision` `hooks/tezgah_gate.py::decision`). The
|
|
|
93
109
|
|
|
94
110
|
The rule reads the shell word by word, one simple command at a time (`shell_control` `hooks/tezgah_gate.py::shell_control`). It reads each redirect target.
|
|
95
111
|
It reads the arguments of `touch`, `rm`, `rmdir`, `unlink`, `mv`, `chmod`, `mkdir`, `tee`, `truncate`, `sqlite3`, the copiers' destination and `sed -i`/`perl -i`.
|
|
96
|
-
It reads
|
|
97
|
-
|
|
112
|
+
It reads `mkfifo`, `mknod` and the `ditto` copier the same way.
|
|
113
|
+
It reads the value of `cp -t`, `curl -o`, `wget -O`, `tar -C` on extraction and `unzip -d` (`OPTION_TARGETS`). It expands `~`, `$HOME`, the XDG
|
|
114
|
+
variables and `${VAR:-default}` first (`hooks/tezgah_gate.py::_expanded`). It follows a `cd` within the line and reads past `if`/`then`/`do`/`!`/`{`.
|
|
115
|
+
It reads past the shortcut rule's wrappers too, with their options (`hooks/tezgah_integrity.py::_past_wrappers`). It opens `eval`, `eval --`, `builtin eval` and `bash -c`, and any flag cluster holding
|
|
98
116
|
`c` is `-c`. A path counts as written and with its last link resolved. A quoted word is a word, so a grep or a commit message that names a switch passes. `SHELL_WRITE` stays as it was: it feeds the
|
|
99
117
|
task phase rule and the opencode mirror. The task CLI is not a control-plane target: the session runs it itself (see the task rule below). Told: what the call would
|
|
100
118
|
change, and that the user changes it in their own terminal (`CONTROL_DENY` `hooks/tezgah_gate.py::CONTROL_DENY`). The text names no command. Standing. No switch
|
|
@@ -111,8 +129,27 @@ request is refused.
|
|
|
111
129
|
|
|
112
130
|
### Shortcut — a check made unable to fail, or a test disabled
|
|
113
131
|
|
|
114
|
-
Trigger, shell: `--no-verify`, or `SKIP=`/`HUSKY_SKIP_HOOKS=`/`HUSKY=0`, next to a git/hook word; or a `core.hooksPath` assignment (`git -c core.hooksPath=...`, `git config core.hooksPath <dir>` with a quoted value too, `--config-env`, `GIT_CONFIG_KEY_n`/`GIT_CONFIG_PARAMETERS`) in the same command as a `git commit`/`git push` (`
|
|
132
|
+
Trigger, shell: `--no-verify`, or `SKIP=`/`HUSKY_SKIP_HOOKS=`/`HUSKY=0`, next to a git/hook word; or a `core.hooksPath`, `include.path` or `includeIf.*.path` assignment (`git -c core.hooksPath=...`, `git config core.hooksPath <dir>` with a quoted value too, `--config-env`, `GIT_CONFIG_KEY_n`/`GIT_CONFIG_PARAMETERS`) in the same command as a `git commit`/`git push` (`HOOKS_KEYS`/`GIT_VALUE_OPTS`/`CONFIG_READS`/`ROUGH_WORDS` `hooks/tezgah_integrity.py::HOOKS_KEYS`, `hooks/tezgah_integrity.py::GIT_VALUE_OPTS`, `hooks/tezgah_integrity.py::CONFIG_READS`, `hooks/tezgah_integrity.py::ROUGH_WORDS`, `hooks/tezgah_integrity.py::_shell_segments` reads the line word by word the way shlex does - an unquoted backtick ends a command (`hooks/tezgah_integrity.py::_unquoted_backticks`; one inside double quotes is read as data, the blind spot `mask` has) and a line shlex cannot read is read roughly rather than dropped - `hooks/tezgah_integrity.py::_hooks_redirect`; husky's standalone setup, a read or `--unset`, a hook install naming `pre-push`, and a commit message naming the key or the env var pass); or a verification command chained with `|| true`/`; true`
|
|
115
133
|
(`hooks/tezgah_integrity.py::_shortcut_command`, `hooks/tezgah_integrity.py::NO_VERIFY`, `hooks/tezgah_integrity.py::SKIP_ENV`, `hooks/tezgah_integrity.py::NEUTER`, `hooks/tezgah_integrity.py::GITISH`).
|
|
134
|
+
The reader finds the program past env assignments, compound keywords and a wrapper (`hooks/tezgah_integrity.py::_past_wrappers`).
|
|
135
|
+
The wrappers are `sudo`, `env`, `nice`, `nohup`, `exec`, `command`, `builtin`, `time`, `timeout`, `stdbuf`, `ionice` and `caffeinate` (`hooks/tezgah_integrity.py::WRAPPER_VALUE_OPTS`).
|
|
136
|
+
It skips their options and the value an option takes as the next word, short or long: `sudo --user root`, `time --format x`, `timeout`'s duration.
|
|
137
|
+
It splits `env -S '...'` into the words it runs.
|
|
138
|
+
The control rule and `VERIFY` read the same table.
|
|
139
|
+
So `nice -n 5 git commit -n` and `timeout 60 pytest || true` meet this rule, and `nice -n 5 pytest | tail` meets the piped one.
|
|
140
|
+
A wrapper outside the table (`setsid`, `chrt`, `taskset`, `doas`, `xargs`, `flock`) still hides the program behind it.
|
|
141
|
+
The rule reads the script of an `eval`, `eval --`, `builtin eval` or `bash -c` the same way (`hooks/tezgah_integrity.py::_shell_scripts`).
|
|
142
|
+
A trap whose action runs `exit 0` is a neuter on any signal (`hooks/tezgah_integrity.py::exit_trap_swallows`).
|
|
143
|
+
On EXIT it replaces the check's status, on ERR it fires when the check fails, and on DEBUG before the check runs.
|
|
144
|
+
Any other trap passes the gate, and an allowlist decides what it records (`hooks/tezgah_integrity.py::trap_owns_status`, `hooks/tezgah_integrity.py::TRAP_CLEANUP`).
|
|
145
|
+
Only an EXIT trap whose every command is `rm`, `echo`, `printf` or `:` keeps the check's status, so `trap 'rm -f x' EXIT; pytest` records as passed.
|
|
146
|
+
Every other trap records the line as ran, never as passed: an ERR, DEBUG or RETURN trap, `exec true`, `ex""it 0`, `exit $((0))` and a function.
|
|
147
|
+
So does a `trap` word the line's own commands do not set, such as one inside an `eval` string or a commit message.
|
|
148
|
+
`t''rap` and `t\rap` count as traps too.
|
|
149
|
+
A `$'...'` on a trap line counts as an action of its own.
|
|
150
|
+
An `exit`, or an `exec` that runs a command, before the check records the line as ran (`hooks/tezgah_integrity.py::status_hidden`). A bare `exec >log` only redirects.
|
|
151
|
+
A wrapper option cluster that ends in a value option takes the next word, as getopt reads it (`sudo -Hu deploy`, `timeout -vk 5 60`, `hooks/tezgah_integrity.py::_option_value`).
|
|
152
|
+
An `env` cluster holding `S` splits its string (`env -iS '...'`), and BSD env's `-P`, `-L` and `-U` take a value.
|
|
116
153
|
These shell predicates read the line through `mask()` (`hooks/tezgah_integrity.py::mask`), which blanks quotes and comments the way bash reads them. A URL's `//`, a word's `#`, a `/* */` glob pair and `'x\'` are words there, so they do not hide the command after them. The word reader takes a `$'...'` as one word and a `$( )` or backtick body as a command of its own (`hooks/tezgah_integrity.py::_for_shlex`). Every shell reader asks one rule for a comment and a line continuation (`hooks/tezgah_integrity.py::_opens_comment`, `hooks/tezgah_integrity.py::CONTINUATION`). A `#` opens a comment only at a word's start, so `x\ #`, `x\<newline>#` and `x\r#` hide nothing. A `)` that closes a substitution or an array is no word end either (`$(:)#`, `<(:)#`, `x=(a)#`, `hooks/tezgah_integrity.py::_kept`). A subshell's `(true)#` does open a comment. The readers drop a backslash-newline where bash drops it, so `-de\<newline>lete` reads as `-delete`. `mask()` moves it to its word's start, so its rules read `tr\<newline>ue` as `true` and `|\<newline>|` as `||`. A rule that slices the raw line reads it at the same offsets (`hooks/tezgah_integrity.py::joined`). The opencode mirror asks its port, `opensComment`. Trigger, edit/write: a skip marker newly
|
|
117
154
|
introduced into a test file — the path must match `tests?/`, `test_*`, `*_test`, `*.test.*` (`hooks/tezgah_integrity.py::TEST_PATH`), the marker must survive `mask_source()` so one
|
|
118
155
|
inside a string or comment does not count, and `_added` compares against the file's own text on disk for a `Write` (`hooks/tezgah_integrity.py::_added`, `hooks/tezgah_integrity.py::SKIP_TEST`). Every write dialect is read
|
|
@@ -125,32 +162,38 @@ heredoc bodies by design). An MCP `write` call meets the same predicate over its
|
|
|
125
162
|
|
|
126
163
|
### Piped — a check whose status a trimmer owns
|
|
127
164
|
|
|
128
|
-
Trigger: a verification command (integrity's `VERIFY`) piped into a trimmer or filter
|
|
129
|
-
`|` or `|&`,
|
|
165
|
+
Trigger: a verification command (integrity's `VERIFY`) piped into a trimmer or filter, with
|
|
166
|
+
`|` or `|&`, in one `&&`/`;`-separated segment of the line. The filters are `tail`, `head`, `grep`/`egrep`/`fgrep`, `rg`, `awk`, `cut`, `wc`, `sed`, `sort`, `cat`, `less`, `more` and `tee` (`hooks/tezgah_integrity.py::piped_check`, `TRIMMER`
|
|
130
167
|
`hooks/tezgah_integrity.py::TRIMMER`). The line's exit status is the trimmer's, so the ledger can only record the check as ran, never as passed, and the Stop rule
|
|
131
168
|
then refuses every claim the run was meant to carry — observed in a real omp session where 13 checks ran piped and 3 of 6 completion claims were blocked.
|
|
132
169
|
Passes: a line that opens with `set -o pipefail` (`hooks/tezgah_integrity.py::PIPEFAIL`), which integrity then records as decisive
|
|
133
170
|
(`verify_ok`/`verify_fail`, [evidence.md](evidence.md)); a redirect to a file; a pipe of anything that is not a check (`git log | head`). The
|
|
134
171
|
remedy is one sentence, `hooks/tezgah_integrity.py::PIPED_REMEDY`, which the always-on core and the contract skill quote too. Told: keep the check last with its output in a file, then read the file in a separate call, or open the line with `set -o pipefail;`. A `; tail` on the same line hands the status to `tail`. The refusal names the check as it was typed. Standing, under `verify-off`
|
|
135
172
|
(`hooks/tezgah_gate.py::decision`). opencode asks the core for this rule on any shell line that pipes a check
|
|
136
|
-
(`hosts/opencode/plugins/tezgah.js
|
|
173
|
+
(`hosts/opencode/plugins/tezgah.js::verifyCommand`) and records such a line as `verify` when a pipe or a later command owns its status (`hosts/opencode/plugins/tezgah.js::statusHidden`).
|
|
137
174
|
|
|
138
175
|
### Attribution — an AI/model credit on its way into an artifact
|
|
139
176
|
|
|
140
177
|
Trigger, shell: a write command (`hooks/tezgah_gate.py::WRITE_CMD` — `git commit|merge|tag|notes`, `gh api`,
|
|
141
|
-
`gh pr|issue|release create|edit|comment|review|merge|close
|
|
178
|
+
`gh pr|issue|release create|edit|comment|review|merge|close`, git or gh named by its path too) carrying a credit form (`hooks/tezgah_gate.py::ATTRIB` — `co-authored-by:` or `co-authored-by=` (a `--trailer`), `generated with`, `made with`,
|
|
142
179
|
a credit verb, `noreply@anthropic`, a robot emoji). A credit verb is `built`, `written`, `drafted` or a like verb, then `by`, `with` or `using`, then a model or vendor name. Trigger, edit/write: one of the content fields `EDIT_TEXT` (`hooks/tezgah_gate.py::EDIT_TEXT`)
|
|
143
180
|
containing a line that *starts* with a credit (`hooks/tezgah_gate.py::ATTRIB_LINE`). Told: remove it and re-run; naming a tool in order to use it is fine, crediting it
|
|
144
181
|
as author is not (`hooks/tezgah_gate.py::ATTRIB_DENY`). Standing. The line anchor is why prose that merely names the banned forms passes (`hooks/tezgah_gate.py::_CREDIT`).
|
|
145
182
|
The same twin as the shortcut rule's: a credit a heredoc writes into a file is refused from the body (`hooks/tezgah_gate.py::decision`), and no command has to be
|
|
146
183
|
a `git`/`gh` write for it to be this rule's. An MCP `write` or `publish` call meets `ATTRIB_LINE` over its payload text ([MCP](#mcp-a-servers-effect-tool)).
|
|
184
|
+
The script of a `bash -c` or an `eval` is read the same way (`hooks/tezgah_gate.py::attribution`).
|
|
185
|
+
A quoted or backslashed program (`"/usr/bin/git" commit`, `\git commit`) is still git, and a `-c trailer.<x>.key=Co-authored-by` on the line names the credit its `--trailer` carries (`hooks/tezgah_gate.py::TRAILER_CREDIT`).
|
|
186
|
+
The rule refuses `git config trailer.<x>.key Co-authored-by` too, since the stored key credits every later `--trailer` (`hooks/tezgah_gate.py::CONFIG_CMD`).
|
|
147
187
|
A folded `.tezgah` (`hooks/tezgah_research.py::folded`) makes `tezgah-research commit` write the project's own history with a message the gate never reads in the command. The CLI runs it through this rule's reader, `hooks/tezgah_gate.py::ATTRIB`, and the language rule's (`hooks/tezgah_lang.py::refusal`, under `lang-off`) before committing. It prints the same refusal, with exit 2 and nothing committed (`bin/tezgah-research::message_refusal`).
|
|
148
188
|
|
|
149
189
|
### Language — an identifier or message that is not English
|
|
150
190
|
|
|
151
191
|
Trigger, shell: a command that would create an identifier - `git checkout -b`/`git switch -c`, `git branch` with a new name, a commit subject (`git commit -m`/`--message`/`-F`), or a `gh pr|issue create --title` (the language section `hooks/tezgah_gate.py::lang_reason`, `hooks/tezgah_gate.py::BRANCH_NEW`, `hooks/tezgah_gate.py::COMMIT_MSG`, `hooks/tezgah_gate.py::GH_NEW`, `hooks/tezgah_gate.py::created_texts`; the text is read from the raw command, because `mask()` blanks quoted strings and a commit subject is one) - whose name or subject is **not English**: a letter outside ASCII anywhere (`hooks/tezgah_lang.py::non_english` - a predicate rather than one language's letter list, so Russian, Greek, Arabic, Chinese and accented Latin hit it exactly as Turkish does) or an ASCII-folded Turkish word from the curated list (`hooks/tezgah_lang.py::ENGLISH`, wi…
|
|
192
|
+
The identifier shapes also include `git worktree add -b|-B` (before or after the path, stuck as `-bname` too), `git checkout -bname`, the branch `git worktree add <path>` names after the path, and the new name of `git branch -m|-M|-c|-C`, flags between included.
|
|
193
|
+
They include `gh pr|issue create -t`, a tag `git tag [-a|-s|-f|-m <msg>] <name>` makes, and the remote branch of a push refspec (`hooks/tezgah_gate.py::TAG_NEW`, `hooks/tezgah_gate.py::_created_by_words`, `hooks/tezgah_gate.py::PUSH_CMD`).
|
|
194
|
+
A push's first positional word is its remote (`origin`, `git@github.com:u/repo.git`, `myhost:u/repo.git`), never a refspec (`hooks/tezgah_gate.py::PUSH_VALUE_OPTS`).
|
|
152
195
|
|
|
153
|
-
The rule is a **heuristic** and its own text says so: a word list is not a language detector, so it cannot tell a proper noun or a product name in another language from a word that ought to be English, and a listed word may be the user's own term. A command that only names the words - `echo`, `grep`, `git log --grep` - creates nothing and passes. An identifier outlives the session that typed it, which is the reason the rule exists at all: `plan/004-admin-durum-onarimi` is in a public history from the moment it exists. On **opencode** the rule is not ported: the plugin asks the core for it through `bin/tezgah-gate check`, one spawn for a command its pre-test reads as identifier-creating (`IDENT_CMD` `hosts/opencode/plugins/tezgah.js:
|
|
196
|
+
The rule is a **heuristic** and its own text says so: a word list is not a language detector, so it cannot tell a proper noun or a product name in another language from a word that ought to be English, and a listed word may be the user's own term. A command that only names the words - `echo`, `grep`, `git log --grep` - creates nothing and passes. An identifier outlives the session that typed it, which is the reason the rule exists at all: `plan/004-admin-durum-onarimi` is in a public history from the moment it exists. On **opencode** the rule is not ported: the plugin asks the core for it through `bin/tezgah-gate check`, one spawn for a command its pre-test reads as identifier-creating (`IDENT_CMD` `hosts/opencode/plugins/tezgah.js:769`, the call site `IDENT_CMD.test` `hosts/opencode/plugins/tezgah.js:3387`).
|
|
154
197
|
|
|
155
198
|
Off: `lang-off`, the rule's own switch (`hooks/tezgah_gate.py::decision`), and `pretooluse-off` drops it with the whole gate.
|
|
156
199
|
|
|
@@ -159,6 +202,9 @@ Off: `lang-off`, the rule's own switch (`hooks/tezgah_gate.py::decision`), and `
|
|
|
159
202
|
Trigger: another session recorded a write of this call's file inside `RACE_WINDOW_MIN = 10` minutes (`hooks/tezgah_gate.py::RACE_WINDOW_MIN`). The call's files are
|
|
160
203
|
its path field, its `apply_patch` headers, or a shell redirect or `tee` target (`hooks/tezgah_gate.py::write_paths`, `hooks/tezgah_gate.py::race_reason`).
|
|
161
204
|
The reader counts an `edit` row and a shell `run` row that carries a `target` (`hooks/tezgah_integrity.py::writers_elsewhere`).
|
|
205
|
+
It also counts another session's `began` row of the file that no outcome row answered yet, a write in flight.
|
|
206
|
+
For its first 60 seconds such a row counts outright (`hooks/tezgah_integrity.py::FRESH_BEGAN_SECONDS`), so two sessions on their first write of one file meet.
|
|
207
|
+
After that it holds only for a tool the session's host answers in the window. The gate writes the `target` on the `began` row (`hooks/tezgah_gate.py::decision`).
|
|
162
208
|
Scratch is skipped: a temp file or a device (`hooks/tezgah_integrity.py::scratch_target`). A file a git checkout holds is never scratch, even when the checkout lives under the temp dir (`hooks/tezgah_gate.py::_scratch`). A `run` row that writes only scratch carries no `target`. The rule also skips the harness's own channels (`hooks/tezgah_gate.py::URI_CHANNEL`), such as `agent://`, `xd://` and `conflict://`. It skips a shell target with an unknown variable too.
|
|
163
209
|
It skips this session's own subagent once its slice record has closed after the subagent's newest write of the file (`hooks/tezgah_gate.py::ended_child`). The writer ledger's `spawned` row names the child, and the record is read after `pending` closes an ended omp child. A child with no record, an open one, or a write newer than the close stays refused.
|
|
164
210
|
Told: re-read the file and re-apply the change to what is on disk now (`RACE_DENY`
|
|
@@ -230,7 +276,8 @@ Three triggers, one per shape a credential takes on its way into a file (`hooks/
|
|
|
230
276
|
A name an earlier command on the line set to a token stays carried. A printer that reads it into a file still denies, as in `API_KEY=x; echo $API_KEY > f` (`hooks/tezgah_gate.py::_printer_owns_sink`).
|
|
231
277
|
That check reads the pipeline stage that owns the sink, so `echo "$TOKEN" | docker login --password-stdin > log` passes. A `> /dev/null` is not a file there.
|
|
232
278
|
The rule reads token shapes, not file names: `echo "$KEY" > f` and `cat key > f` carry no token and pass.
|
|
233
|
-
- **A credential file staged.** A `git add` that names a credential file (`hooks/
|
|
279
|
+
- **A credential file staged.** A `git add`, `git stage`, a `git commit` pathspec or a `git update-index --add` that names a credential file (`hooks/tezgah_gate.py::STAGING`).
|
|
280
|
+
The names are the snapshot store's (`hooks/tezgah_snapshot.py::SECRET_FILE`: `.env`, `.env.local`, a key, `id_rsa`, `credentials.json`). A commit's option value (`-m .env`) is no pathspec.
|
|
234
281
|
The command is read word by word (`hooks/tezgah_gate.py::_secret_add`). `.env.example`, `.env.sample`, `.env.template`, `.env.dist` and `.env.defaults` carry names without values and pass (`hooks/tezgah_gate.py::SECRET_TEMPLATES`).
|
|
235
282
|
The rule does not read `git add -A` or `git add .` (`hooks/tezgah_gate.py::SECRET_ADD_DENY`).
|
|
236
283
|
- **The text a write lands.** The text holds a token with a known credential prefix. It is read on every route: a write tool in every dialect, an `apply_patch` file by file, a heredoc body.
|
|
@@ -384,21 +431,24 @@ widen a list a regex already covers.
|
|
|
384
431
|
## The mirror: the opencode plugin
|
|
385
432
|
|
|
386
433
|
opencode cannot run Python hooks, so `hosts/opencode/plugins/tezgah.js` is an independent JavaScript re-implementation of the same rules, dispatched from
|
|
387
|
-
`tool.execute.before`
|
|
388
|
-
`SKIP_ENV` hosts/opencode/plugins/tezgah.js:
|
|
434
|
+
`tool.execute.before` `hosts/opencode/plugins/tezgah.js:3302` (exported by `Tezgah` `hosts/opencode/plugins/tezgah.js:3249`) in the gate's own order, up to its `throw new Error(deny)` `hosts/opencode/plugins/tezgah.js:3455`; its shortcut constants restate the same regexes (`NEUTER` `hosts/opencode/plugins/tezgah.js:376`,
|
|
435
|
+
`SKIP_ENV` `hosts/opencode/plugins/tezgah.js:384`, `NO_VERIFY` `hosts/opencode/plugins/tezgah.js:385`, `GITISH` `hosts/opencode/plugins/tezgah.js:386`, `SKIP_TEST` `hosts/opencode/plugins/tezgah.js:707`). What keeps the two halves honest is a test, not a shared module:
|
|
389
436
|
`tests/test_opencode_plugin.py` drives the plugin's hooks through a node harness with a throwaway HOME, and imports the Python `tezgah_integrity.call_id` so a
|
|
390
437
|
separator or canonical-form drift in the action id fails there instead of silently in a session (`tests/test_opencode_plugin.py:1-7`, `tests/test_opencode_plugin.py:27-30`, `tests/test_opencode_plugin.py::OpenCodePlugin.test_the_id_is_the_hash_the_python_writer_computes`).
|
|
391
438
|
|
|
392
|
-
The shell rule kinds mirrored last are each pinned in both suites: the shell's write body (`SHELL_WRITE` `hosts/opencode/plugins/tezgah.js:
|
|
393
|
-
`heredocBodies` `hosts/opencode/plugins/tezgah.js:
|
|
394
|
-
`hosts/opencode/plugins/tezgah.js:
|
|
395
|
-
ordering obligation (`COMMIT_CMD` `hosts/opencode/plugins/tezgah.js:
|
|
396
|
-
`hosts/opencode/plugins/tezgah.js:
|
|
397
|
-
`hosts/opencode/plugins/tezgah.js:
|
|
439
|
+
The shell rule kinds mirrored last are each pinned in both suites: the shell's write body (`SHELL_WRITE` `hosts/opencode/plugins/tezgah.js:1210`,
|
|
440
|
+
`heredocBodies` `hosts/opencode/plugins/tezgah.js:1216`, `shellWriteBody` `hosts/opencode/plugins/tezgah.js:1249`, the shortcut and attribution twins at
|
|
441
|
+
`shortcutEdit(body)` `hosts/opencode/plugins/tezgah.js:3362` and `shellAttribution` `hosts/opencode/plugins/tezgah.js:2216`, the credential twin at `landsSecret(body)` `hosts/opencode/plugins/tezgah.js:2177`) and the
|
|
442
|
+
ordering obligation (`COMMIT_CMD` `hosts/opencode/plugins/tezgah.js:2070`, `ORDER_DENY`
|
|
443
|
+
`hosts/opencode/plugins/tezgah.js:2072`, `lastVerify` `hosts/opencode/plugins/tezgah.js:2082`, `orderReason` `hosts/opencode/plugins/tezgah.js:2107`, dispatched at
|
|
444
|
+
`orderReason(args` `hosts/opencode/plugins/tezgah.js:3403`). `lastVerify` folds the tail exactly as `_last_verify` does, minus the `passing_check` narrowing - this rule reads only
|
|
398
445
|
`fail`, and a row that narrowing would refuse reads `ok`/`ran` on both sides, so the outcome is identical.
|
|
399
446
|
|
|
400
|
-
The control rule has no JavaScript copy.
|
|
401
|
-
|
|
447
|
+
The control rule has no JavaScript copy. Neither have the credential rule's `git add` reader and the workspace rule. The plugin's `CONTROL_CMD` pre-test
|
|
448
|
+
picks every shell line with a write shape the core reads. That is a redirect, a program that changes a path, a git staging command, `git config`, or a tezgah CLI or hook
|
|
449
|
+
entry. `NEUTER` and `SHORTCUT_CMD` pick the lines that could swallow a check's status or skip the hooks. The plugin puts each such line to the core before any
|
|
450
|
+
rule of its own. It returns the core's answer verbatim. The pre-test only bounds the asking, so it is a superset of the core's shapes. A write tool already
|
|
451
|
+
goes to the core. A refusal the plugin decides itself writes the call's `began` row ahead of its `deny`, as the core does. The plugin's own `off()`
|
|
402
452
|
answers only for a name in its `SWITCHES` list, and `tests/test_paths.py` holds that list equal to `tezgah_paths.SWITCHES`.
|
|
403
453
|
|
|
404
454
|
## Rule provenance
|
|
@@ -491,7 +541,7 @@ The opencode classifier does not read the manifest's `checks`, so a declared che
|
|
|
491
541
|
`hooks/tezgah_gate.py::RETRY_CEILING`, `hooks/tezgah_gate.py::retry_reason`; `hooks/tezgah_gate.py::secret_command`, `hooks/tezgah_gate.py::race_reason`,
|
|
492
542
|
`hooks/tezgah_gate.py::drift_reason`, `hooks/tezgah_gate.py::effectful`, `hooks/tezgah_gate.py::_deny`; the plan rule's own section `hooks/tezgah_gate.py::PLAN_TRUNKS`, `hooks/tezgah_gate.py::PLAN_SKIP`, `hooks/tezgah_gate.py::PLAN_AGENT_HOSTS`, `hooks/tezgah_gate.py::PLAN_WRITES`, `hooks/tezgah_gate.py::PLAN_MERGING`, `hooks/tezgah_gate.py::PLAN_BIND`, `hooks/tezgah_gate.py::PLAN_BIND_TRUNK`, `hooks/tezgah_gate.py::_gitdir`, `hooks/tezgah_gate.py::_head`, `hooks/tezgah_gate.py::_merging`, `hooks/tezgah_gate.py::_unplanned` — `hooks/tezgah_gate.py::plan_reason`, `hooks/tezgah_gate.py::_branch`, `hooks/tezgah_gate.py::_checkout`, `hooks/tezgah_gate.py::_product`, `hooks/tezgah_gate.py::PLAN_DENY`; `hooks/tezgah_gate.py::write_paths`, `hooks/tezgah_gate.py::shell_target`, `hooks/tezgah_gate.py::SHELL_AS_WRITE`; the `capture` call `hooks/tezgah_gate.py::decision` (a write tool's target, and a shell write's)
|
|
493
543
|
- `hooks/projects-pretooluse.py` — the Claude and dsh envelope; `hooks/tezgah_paths.py` — `off`, `root_for`, `cache_dir`
|
|
494
|
-
- `hooks/tezgah_integrity.py` — `hooks/tezgah_integrity.py::WRITE_TOOLS`, `hooks/tezgah_integrity.py::BASH_TOOLS`; `NEUTER`/`SKIP_ENV`/`NO_VERIFY`/`GITISH` `hooks/tezgah_integrity.py::NEUTER`, `hooks/tezgah_integrity.py::SKIP_ENV`, `hooks/tezgah_integrity.py::NO_VERIFY`, `hooks/tezgah_integrity.py::GITISH`; `
|
|
544
|
+
- `hooks/tezgah_integrity.py` — `hooks/tezgah_integrity.py::WRITE_TOOLS`, `hooks/tezgah_integrity.py::BASH_TOOLS`; `NEUTER`/`SKIP_ENV`/`NO_VERIFY`/`GITISH` `hooks/tezgah_integrity.py::NEUTER`, `hooks/tezgah_integrity.py::SKIP_ENV`, `hooks/tezgah_integrity.py::NO_VERIFY`, `hooks/tezgah_integrity.py::GITISH`; `HOOKS_KEYS`/`GIT_VALUE_OPTS`/`CONFIG_READS`/`ROUGH_WORDS` `hooks/tezgah_integrity.py::HOOKS_KEYS`, `hooks/tezgah_integrity.py::GIT_VALUE_OPTS`, `hooks/tezgah_integrity.py::CONFIG_READS`, `hooks/tezgah_integrity.py::ROUGH_WORDS`; `hooks/tezgah_integrity.py::SKIP_TEST`; `TEST_PATH`
|
|
495
545
|
`hooks/tezgah_integrity.py::TEST_PATH`; `hooks/tezgah_integrity.py::call_id`; `hooks/tezgah_integrity.py::_path`, `hooks/tezgah_integrity.py::note`, `hooks/tezgah_integrity.py::events`, `hooks/tezgah_integrity.py::writers_elsewhere`, `hooks/tezgah_integrity.py::mask`, `hooks/tezgah_integrity.py::shortcut_command`,
|
|
496
546
|
`hooks/tezgah_integrity.py::shortcut_edit`
|
|
497
547
|
- `hosts/opencode/plugins/tezgah.js` — the JavaScript mirror
|