@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.
Files changed (109) hide show
  1. package/CHANGELOG.md +507 -1
  2. package/MANIFEST +40 -0
  3. package/NOTICE +152 -0
  4. package/README.de.md +41 -20
  5. package/README.es.md +39 -19
  6. package/README.fr.md +41 -20
  7. package/README.md +18 -10
  8. package/README.tr.md +39 -21
  9. package/bin/codegen +9 -2
  10. package/bin/consult +6 -0
  11. package/bin/tezgah-adhd +3 -0
  12. package/bin/tezgah-agents +3 -0
  13. package/bin/tezgah-capture +3 -0
  14. package/bin/tezgah-design +329 -54
  15. package/bin/tezgah-docs +158 -42
  16. package/bin/tezgah-dsh +1 -1
  17. package/bin/tezgah-gate +7 -2
  18. package/bin/tezgah-import-ai-research +16 -4
  19. package/bin/tezgah-index +3 -0
  20. package/bin/tezgah-lessons +13 -4
  21. package/bin/tezgah-mcp +4 -0
  22. package/bin/tezgah-migrate +3 -0
  23. package/bin/tezgah-ocr-review +2 -1
  24. package/bin/tezgah-optimize +4 -1
  25. package/bin/tezgah-pony +3 -0
  26. package/bin/tezgah-research +51 -10
  27. package/bin/tezgah-rollback +3 -0
  28. package/bin/tezgah-route +1 -1
  29. package/bin/tezgah-setup +1741 -95
  30. package/bin/tezgah-status +11 -1
  31. package/bin/tezgah-task +58 -17
  32. package/bin/tezgah.js +10 -3
  33. package/commands/adhd.md +1 -1
  34. package/docs/README.md +19 -8
  35. package/docs/architecture.md +2 -2
  36. package/docs/citations-baseline.json +11 -17
  37. package/docs/clarity-baseline.json +12 -6
  38. package/docs/contract.md +83 -34
  39. package/docs/environment.md +59 -24
  40. package/docs/evidence.md +128 -25
  41. package/docs/gate.md +77 -23
  42. package/docs/glossary.md +1 -1
  43. package/docs/hosts.md +343 -48
  44. package/docs/layers.md +2 -2
  45. package/docs/models.md +6 -0
  46. package/docs/operations.md +83 -29
  47. package/docs/research.md +37 -11
  48. package/docs/skills.md +31 -8
  49. package/docs/status-line.md +39 -23
  50. package/docs/testing.md +50 -17
  51. package/docs/vision.md +6 -2
  52. package/hooks/hooks.json +1 -1
  53. package/hooks/lang/en.json +16 -2
  54. package/hooks/lang/tr.json +14 -2
  55. package/hooks/projects-posttooluse.py +11 -3
  56. package/hooks/projects-stop.py +3 -2
  57. package/hooks/tezgah_agents.py +27 -3
  58. package/hooks/tezgah_attest.py +79 -10
  59. package/hooks/tezgah_context.py +61 -31
  60. package/hooks/tezgah_gate.py +480 -126
  61. package/hooks/tezgah_integrity.py +577 -133
  62. package/hooks/tezgah_lessons.py +5 -2
  63. package/hooks/tezgah_paths.py +60 -8
  64. package/hooks/tezgah_policy.py +48 -18
  65. package/hooks/tezgah_replay.py +8 -2
  66. package/hooks/tezgah_research.py +167 -24
  67. package/hooks/tezgah_skill_pick.py +1 -1
  68. package/hooks/tezgah_snapshot.py +6 -3
  69. package/hooks/tezgah_stack.py +236 -1
  70. package/hooks/tezgah_stopspec.py +2 -1
  71. package/hooks/tezgah_task.py +2 -1
  72. package/hosts/amp/hook.py +217 -0
  73. package/hosts/amp/tezgah-plugin.ts.in +396 -0
  74. package/hosts/antigravity/hook.py +255 -0
  75. package/hosts/codex/hook.py +3 -2
  76. package/hosts/commandcode/hook.py +239 -0
  77. package/hosts/copilot/hook.py +284 -0
  78. package/hosts/cursor/hook.py +4 -2
  79. package/hosts/droid/hook.py +259 -0
  80. package/hosts/dsh/hooks.json +1 -1
  81. package/hosts/gemini/hook.py +213 -0
  82. package/hosts/goose/hook.py +145 -0
  83. package/hosts/omp/hook.py +5 -2
  84. package/hosts/omp/tezgah-hook.ts.in +157 -42
  85. package/hosts/opencode/plugins/tezgah.js +209 -63
  86. package/hosts/qwen/hook.py +206 -0
  87. package/output-styles/tezgah.md +11 -6
  88. package/package.json +6 -3
  89. package/packaging/build.sh +4 -4
  90. package/packaging/install.sh +11 -0
  91. package/packaging/upgrade.sh +3 -0
  92. package/skills/ai-research/LICENSE +21 -0
  93. package/skills/design-contract/SKILL.md +27 -6
  94. package/skills/design-library/LICENSE +21 -0
  95. package/skills/harness/SKILL.md +2 -0
  96. package/skills/i-have-adhd/SKILL.md +10 -1
  97. package/skills/plan-add/SKILL.md +5 -1
  98. package/skills/plan-add/render_table.py +7 -0
  99. package/skills/pm-frameworks/LICENSE +21 -0
  100. package/skills/research/SKILL.md +22 -9
  101. package/skills/rl-env/LICENSE +201 -0
  102. package/skills/tezgah-contract/SKILL.md +21 -8
  103. package/statusline.py +20 -9
  104. package/third_party/open-code-review/LICENSE +201 -0
  105. package/third_party/open-code-review/README.md +15 -0
  106. package/third_party/open-code-review/prompts/main_task_system.md +26 -0
  107. package/third_party/open-code-review/prompts/review_filter_task_system.md +13 -0
  108. package/workflows/graph-map.js +14 -5
  109. package/workflows/graph-review.js +14 -2
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:2988` | `new Error(deny)` thrown at `hosts/opencode/plugins/tezgah.js:3152` |
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.
@@ -83,8 +98,9 @@ it first after the root check (`decision` `hooks/tezgah_gate.py::decision`). The
83
98
  | tezgah's configuration | all of `~/.config/tezgah` (`config.json`, `bin/`, `ponytail.level`, the `hook:<host>:...` rows of `contract.sha256`, ...) | any write |
84
99
  | evidence and session state | the cache's `tezgah.db` (with its `-wal` and `-shm` files), `evidence/`, the `sessions/`, `switches/`, `gate-inactive/`, `harness-drift/` and `import-crash/` directories that database imports once, and `workspace-index.json`, in the global cache and the temp fallback (`hooks/tezgah_gate.py::CONTROL_CACHE`) | any write |
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
- | 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` | a shell write; a write tool only when it changes an entry that is tezgah's - `statusLine`, `attribution`, `disableAllHooks`, a key or item naming tezgah (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`) |
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 the value of `cp -t`, `curl -o`, `wget -O`, `tar -C` on extraction and `unzip -d` (`OPTION_TARGETS`). It expands `~`, `$HOME` and the XDG
97
- variables first. It follows a `cd` within the line and reads past `if`/`then`/`do`/`!`/`{`. It opens `eval` and `bash -c`, and any flag cluster holding
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` (`HOOKS_KEY`/`GIT_VALUE_OPTS`/`CONFIG_READS`/`ROUGH_WORDS` `hooks/tezgah_integrity.py::HOOKS_KEY`, `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`
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 — `tail`, `head`, `grep`/`egrep`/`fgrep`, `cut`, `wc`, `sed -n`, with
129
- `|` or `|&`, a `tee` in between included — in one `&&`/`;`-separated segment of the line (`hooks/tezgah_integrity.py::piped_check`, `TRIMMER`
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:3069-3073`) and records such a line as `verify` when a pipe or a later command owns its status (`statusHidden`, `hosts/opencode/plugins/tezgah.js:505`).
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`) carrying a credit form (`hooks/tezgah_gate.py::ATTRIB` — `co-authored-by:`, `generated with`, `made with`,
142
- `built by`, `assisted by`, `authored by`, `noreply@anthropic`, a robot emoji). Trigger, edit/write: one of the content fields `EDIT_TEXT` (`hooks/tezgah_gate.py::EDIT_TEXT`)
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`,
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:632`, the call site `hosts/opencode/plugins/tezgah.js:3069-3073`).
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`
@@ -207,6 +253,8 @@ checkpoint that is a plain sha (the boundary is already real), a `pending` with
207
253
 
208
254
  Trigger: a write tool, or a shell redirect/`tee` (`write_paths`), whose target relative to the repo root starts with `plans/`, `research/` or `analysis/` while the project tracks no file under that directory (`workspace_reason`, `hooks/tezgah_gate.py`). The refusal names `.tezgah/<kind>/` as the place to write. It also names the commit form for the workspace's shape (`hooks/tezgah_research.py::folded`). An own repository gets `git -C .tezgah` (`hooks/tezgah_gate.py::WORKSPACE_OWN`). A folded one the project tracks gets its own `git add`/`git commit` (`hooks/tezgah_gate.py::WORKSPACE_FOLDED`). A directory the project already tracks is its own and passes. `mkdir` and positional shell targets (`cp`, `mv`) are not read - the same ceiling as `write_paths`. Off: `workspace-off`.
209
255
 
256
+ A second trigger under the same switch: a write under a dot dir directly below `.tezgah/research/`, such as `.tezgah/research/.staging/x/state.json` (`hooks/tezgah_gate.py::research_hidden_reason`). It is read against the main checkout and against the session's own root. `slugs()` skips dot dirs, so a line there is invisible to `status` and `check`. The refusal says a refused line goes to the owner (`hooks/tezgah_gate.py::RESEARCH_HIDDEN_DENY`). `open/`, `done/` and a dot file directly in `research/` pass.
257
+
210
258
  ### Secret — a credential on its way into a file
211
259
 
212
260
  Three triggers, one per shape a credential takes on its way into a file (`hooks/tezgah_gate.py::secret_command`, `hooks/tezgah_gate.py::secret_edit`, read at `hooks/tezgah_gate.py::decision`):
@@ -228,7 +276,8 @@ Three triggers, one per shape a credential takes on its way into a file (`hooks/
228
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`).
229
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.
230
278
  The rule reads token shapes, not file names: `echo "$KEY" > f` and `cat key > f` carry no token and pass.
231
- - **A credential file staged.** A `git add` that names a credential file (`hooks/tezgah_snapshot.py::SECRET_FILE`: `.env`, `.env.local`, a key, `id_rsa`, `credentials.json`).
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.
232
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`).
233
282
  The rule does not read `git add -A` or `git add .` (`hooks/tezgah_gate.py::SECRET_ADD_DENY`).
234
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.
@@ -382,21 +431,24 @@ widen a list a regex already covers.
382
431
  ## The mirror: the opencode plugin
383
432
 
384
433
  opencode cannot run Python hooks, so `hosts/opencode/plugins/tezgah.js` is an independent JavaScript re-implementation of the same rules, dispatched from
385
- `tool.execute.before` (hosts/opencode/plugins/tezgah.js:2988, exported `hosts/opencode/plugins/tezgah.js:2936`) in the gate's own order (`hosts/opencode/plugins/tezgah.js:2988-3153`); its shortcut constants restate the same regexes (`NEUTER` `hosts/opencode/plugins/tezgah.js:314`,
386
- `SKIP_ENV` hosts/opencode/plugins/tezgah.js:315, `NO_VERIFY` hosts/opencode/plugins/tezgah.js:316, `GITISH` hosts/opencode/plugins/tezgah.js:317, `SKIP_TEST` hosts/opencode/plugins/tezgah.js:575). What keeps the two halves honest is a test, not a shared module:
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:
387
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
388
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`).
389
438
 
390
- The shell rule kinds mirrored last are each pinned in both suites: the shell's write body (`SHELL_WRITE` `hosts/opencode/plugins/tezgah.js:1039`,
391
- `heredocBodies` `hosts/opencode/plugins/tezgah.js:1045`, `shellWriteBody` `hosts/opencode/plugins/tezgah.js:1078`, the shortcut and attribution twins at
392
- `hosts/opencode/plugins/tezgah.js:3043-3044` and `hosts/opencode/plugins/tezgah.js:2010`, the credential twin at `hosts/opencode/plugins/tezgah.js:1970-1974`) and the
393
- ordering obligation (`COMMIT_CMD` `hosts/opencode/plugins/tezgah.js:1899`, `ORDER_DENY`
394
- `hosts/opencode/plugins/tezgah.js:1901`, `lastVerify` `hosts/opencode/plugins/tezgah.js:1911`, `orderReason` `hosts/opencode/plugins/tezgah.js:1936`, dispatched at
395
- `hosts/opencode/plugins/tezgah.js:3084-3087`). `lastVerify` folds the tail exactly as `_last_verify` does, minus the `passing_check` narrowing - this rule reads only
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
396
445
  `fail`, and a row that narrowing would refuse reads `ok`/`ran` on both sides, so the outcome is identical.
397
446
 
398
- The control rule has no JavaScript copy. The plugin's `CONTROL_CMD` pre-test picks the shell lines that could touch the control plane. The plugin puts each
399
- such line to the core before any rule of its own, and returns the core's answer verbatim. A write tool already goes to the core. The plugin's own `off()`
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()`
400
452
  answers only for a name in its `SWITCHES` list, and `tests/test_paths.py` holds that list equal to `tezgah_paths.SWITCHES`.
401
453
 
402
454
  ## Rule provenance
@@ -443,6 +495,8 @@ rule's triggers are the classes `_stop_block` returns, the four `_shape_block` h
443
495
  | `open todo` | the owner's report (2026-10-12, plan 097 item 2): `ready_left` counted only `pending` items, so a done claim over an `in_progress` todo item ended the turn | `hooks/tezgah_integrity.py::open_left` | `tests/test_integrity.py::test_a_done_claim_over_an_in_progress_todo_is_refused_once` |
444
496
  | `needless ask` | research line `orchestrator-flow-audit` S3: 33 of 48 final replies that ended on a question in the owner's omp transcripts were process questions ("Başlayayım mı?"), and 9 of 47 next prompts were a bare "devam" or "evet" | `hooks/tezgah_integrity.py::needless_ask` | `tests/test_integrity.py::test_a_process_question_at_the_end_is_refused_once` |
445
497
  | `stopped slice` | measured 2026-10-11 (research line `subagent-resume-orchestration`): 118 of 1413 omp subagent runs ended stopped, and in the newest 20 the parent absorbed the work inline 16 times instead of resuming it | `hooks/tezgah_integrity.py::_stopped_slice` | `tests/test_integrity.py::test_a_stopped_slice_of_this_session_refuses_the_turn_once` |
498
+ | `handed step` | measured 2026-10-29 on the owner's top-level omp and Claude transcripts: of 3307 user turns, 27 replies handed the reader a command in backticks and no class refused any of them; the `needless ask` refusal pushes the same step out of a question and into prose. The shipped shapes refuse 9 of those turns, 8 a real handover; the two review rounds of plan 142 cut the wordings that refused an answer about how a command is run | `hooks/tezgah_integrity.py::handed_steps` | `tests/test_integrity.py::test_a_handed_step_is_refused_once` |
499
+ | `plan claim` | probe 2026-10-29: a read-only plan turn whose reply claimed "test ettim" ended the turn, because every evidence class needs a work row and plan mode produces none; the same mode's approval question was refused as `needless ask`. The trigger is the first-person run claim alone: review round 2 found the done and verified vocabularies refusing a plan's own acceptance line | `hooks/tezgah_integrity.py::RAN_IT` | `tests/test_integrity.py::test_a_done_claim_in_plan_mode_is_refused` |
446
500
  | `placating opener` | designed | output rule 10 read at the Stop event; no incident recorded | `tests/test_integrity.py::test_sycophantic_opener_blocks` |
447
501
  | `forbidden closer` | designed | output rule 10 read at the Stop event; no incident recorded | `tests/test_integrity.py::test_a_closer_from_the_skills_own_list_blocks` |
448
502
  | `list cap` | designed | output rule 8 read at the Stop event; no incident recorded | `tests/test_integrity.py::test_a_list_over_the_cap_blocks_with_its_size` |
@@ -487,7 +541,7 @@ The opencode classifier does not read the manifest's `checks`, so a declared che
487
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`,
488
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)
489
543
  - `hooks/projects-pretooluse.py` — the Claude and dsh envelope; `hooks/tezgah_paths.py` — `off`, `root_for`, `cache_dir`
490
- - `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_KEY`/`GIT_VALUE_OPTS`/`CONFIG_READS`/`ROUGH_WORDS` `hooks/tezgah_integrity.py::HOOKS_KEY`, `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`
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`
491
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`,
492
546
  `hooks/tezgah_integrity.py::shortcut_edit`
493
547
  - `hosts/opencode/plugins/tezgah.js` — the JavaScript mirror
package/docs/glossary.md CHANGED
@@ -164,7 +164,7 @@ The value of one status [mark](#mark): `on`, `ready`, `off` or `info` (`hooks/te
164
164
  One of the row kinds that count as a step of work - `run`, `edit`, `verify`, `verify_ok`, `verify_fail`, `interrupted` (`STEP_KINDS`, `hooks/tezgah_integrity.py::STEP_KINDS`), summed into `steps` and into the drift threshold, while `deny`, `nudge`, `claim` and `turn` are the machinery around the work (`hooks/tezgah_integrity.py:1420-1422`). `interrupted` is the one that is not a failure: the host said the call was stopped, so the row carries no `exit` and no `fail_class`, no reader folds it as a failed check and no attempt is spent on it (`hooks/tezgah_integrity.py:1426-1429`). Not any row.
165
165
 
166
166
  ### Stop rule
167
- The end-of-turn refusal, `stop_reason()` (`hooks/tezgah_integrity.py::stop_reason`), run by the Stop hooks of Claude, Codex, Cursor and omp (`hooks/projects-stop.py::main`, `hosts/codex/hook.py::main`, `hosts/cursor/hook.py::dispatch`, `hosts/omp/hook.py::handle`): it blocks on a placating opener or a banned sign-off on the last prose line, on a completion claim the newest check does not support, on a check that passed before the newest write the gate saw change the tree, on a turn that recorded work with no passing check, or on a working turn that ends on a list of what is left with an item no blocker accounts for (`_stop_block()`, `hooks/tezgah_integrity.py::_stop_block`). An explicit "doğrulanmadı" clears a check-repair refusal only after a check that ran and failed since the newest change (`hooks/tezgah_integrity.py::_admitted`). A claim in a turn with no work is judged against the whole session, and a read-only or VCS-bookkeeping turn after a pass on an unchanged tree is exempt ([evidence](evidence.md#the-stop-rule-end-to-end)). Not the [gate](#gate), which runs before a call ([evidence](evidence.md)).
167
+ The end-of-turn refusal, `stop_reason()` (`hooks/tezgah_integrity.py::stop_reason`), run by the Stop hooks of Claude, Codex, Cursor and omp (`hooks/projects-stop.py::main`, `hosts/codex/hook.py::main`, `hosts/cursor/hook.py::dispatch`, `hosts/omp/hook.py::handle`): it blocks on a placating opener or a banned sign-off on the last prose line, on a completion claim the newest check does not support, on a check that passed before the newest write the gate saw change the tree, on a turn that recorded work with no passing check, on a working turn that ends on a list of what is left with an item no blocker accounts for, or on a reply that hands the reader a command the turn ran commands beside (`handed_steps()`, `hooks/tezgah_integrity.py::handed_steps`) (`_stop_block()`, `hooks/tezgah_integrity.py::_stop_block`). An explicit "doğrulanmadı" clears a check-repair refusal only after a check that ran and failed since the newest change (`hooks/tezgah_integrity.py::_admitted`). A claim in a turn with no work is judged against the whole session, and a read-only or VCS-bookkeeping turn after a pass on an unchanged tree is exempt ([evidence](evidence.md#the-stop-rule-end-to-end)). In a host plan mode (`read_only_turn()`, `hooks/tezgah_integrity.py::read_only_turn`) the approval question is not refused and a done/tested claim is. Not the [gate](#gate), which runs before a call ([evidence](evidence.md)).
168
168
 
169
169
  ### surface
170
170
  A place a host can show tezgah's output - a status line, a TUI widget, a `systemMessage` - and therefore the reason the same [marks](#mark) render differently per host (`hooks/tezgah_context.py::health_segments`, `hooks/tezgah_context.py::render_line`); each host's surfaces are [hosts](hosts.md)'s. Not the host itself.