@r1z4x/tezgah 1.11.0 → 1.13.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 +333 -1
- package/MANIFEST +22 -0
- package/README.md +4 -3
- package/bin/tezgah-docs +1 -1
- package/bin/tezgah-research +39 -4
- package/bin/tezgah-setup +1229 -22
- package/bin/tezgah-task +55 -17
- package/docs/citations-baseline.json +1 -1
- package/docs/clarity-baseline.json +9 -3
- package/docs/contract.md +6 -3
- package/docs/environment.md +3 -2
- package/docs/evidence.md +81 -8
- package/docs/gate.md +6 -2
- package/docs/glossary.md +1 -1
- package/docs/hosts.md +297 -15
- package/docs/layers.md +1 -1
- package/docs/models.md +6 -0
- package/docs/operations.md +16 -5
- package/docs/research.md +36 -10
- package/docs/status-line.md +6 -0
- package/docs/testing.md +3 -1
- package/hooks/hooks.json +1 -1
- package/hooks/lang/en.json +11 -0
- package/hooks/lang/tr.json +11 -0
- package/hooks/projects-stop.py +2 -1
- package/hooks/tezgah_agents.py +27 -3
- package/hooks/tezgah_attest.py +79 -10
- package/hooks/tezgah_context.py +21 -8
- package/hooks/tezgah_gate.py +79 -23
- package/hooks/tezgah_integrity.py +212 -38
- package/hooks/tezgah_paths.py +43 -2
- package/hooks/tezgah_policy.py +35 -13
- package/hooks/tezgah_replay.py +8 -2
- package/hooks/tezgah_research.py +167 -24
- package/hooks/tezgah_stack.py +236 -1
- package/hooks/tezgah_task.py +2 -1
- package/hosts/amp/hook.py +217 -0
- package/hosts/amp/tezgah-plugin.ts.in +386 -0
- package/hosts/antigravity/hook.py +255 -0
- package/hosts/codex/hook.py +2 -1
- package/hosts/commandcode/hook.py +239 -0
- package/hosts/copilot/hook.py +284 -0
- package/hosts/cursor/hook.py +2 -1
- package/hosts/droid/hook.py +259 -0
- package/hosts/dsh/hooks.json +1 -1
- package/hosts/gemini/hook.py +213 -0
- package/hosts/goose/hook.py +145 -0
- package/hosts/omp/hook.py +4 -1
- package/hosts/omp/tezgah-hook.ts.in +30 -7
- package/hosts/opencode/plugins/tezgah.js +2 -2
- package/hosts/qwen/hook.py +206 -0
- package/output-styles/tezgah.md +10 -5
- package/package.json +1 -1
- package/skills/harness/SKILL.md +2 -0
- package/skills/i-have-adhd/SKILL.md +10 -1
- package/skills/plan-add/SKILL.md +5 -1
- package/skills/research/SKILL.md +22 -9
- package/skills/tezgah-contract/SKILL.md +19 -6
- package/statusline.py +20 -9
- package/workflows/graph-review.js +14 -2
package/bin/tezgah-task
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
tezgah-task allow GLOB ... | --any-path --why REASON replace the allowlist, recording why
|
|
7
7
|
tezgah-task stop deactivate (clear phase)
|
|
8
8
|
tezgah-task status [--json] the active task, then every open plan: progress, what it waits on, what can run at once
|
|
9
|
-
tezgah-task review <NNN|path> <reviewer> approve|changes
|
|
9
|
+
tezgah-task review <NNN|path> <reviewer> approve|changes [--no-slice WHY] record a fresh review's verdict; approve needs the reviewer's slice record, or WHY it has none
|
|
10
10
|
tezgah-task close <NNN|path> done|discarded [--resolved TEXT] move a plan to .tezgah/plans/done, deactivate it, remove merged worktrees
|
|
11
11
|
tezgah-task tick <NNN|path> <n> [--undo] tick (or untick) Acceptance item n, counted from 1
|
|
12
12
|
tezgah-task note <NNN|path> state|next "<text>" replace the body of `## State` (the newest one) or `## Next`
|
|
@@ -1082,7 +1082,7 @@ def budget_spent(path, ident):
|
|
|
1082
1082
|
% (path, REVIEW_ROUNDS, ident, say()["decision"], ident))
|
|
1083
1083
|
|
|
1084
1084
|
|
|
1085
|
-
def unfinished_review(root, reviewer, plan="", since=0):
|
|
1085
|
+
def unfinished_review(root, reviewer, plan="", since=0, no_slice="", ran_since=0):
|
|
1086
1086
|
"""The refusal of an `approve` while a slice journal record whose id or agent
|
|
1087
1087
|
is `reviewer` is open, stopped or stale: a review slice that died on a
|
|
1088
1088
|
provider limit had its approve recorded anyway, and a NameError it would have
|
|
@@ -1097,7 +1097,11 @@ def unfinished_review(root, reviewer, plan="", since=0):
|
|
|
1097
1097
|
`tt.open_slice`) counts too. Under TEZGAH_SESSION only this session's
|
|
1098
1098
|
records and unowned ones count, by id, agent or name alike: another
|
|
1099
1099
|
session's review is not this approve's. The refusal names the exact id,
|
|
1100
|
-
which `slice resume` and `slice done` take from any session.
|
|
1100
|
+
which `slice resume` and `slice done` take from any session. No finished
|
|
1101
|
+
record changed at or after `ran_since` refuses too: a requested reviewer with
|
|
1102
|
+
no status did not run, and a plan-readiness review before the checkpoint, an
|
|
1103
|
+
earlier round's review or a `resumed` record with no re-run is not this
|
|
1104
|
+
review - unless `no_slice` says why the reviewer ran outside the journal."""
|
|
1101
1105
|
from tezgah_paths import off
|
|
1102
1106
|
if off("slices-off"):
|
|
1103
1107
|
return []
|
|
@@ -1113,21 +1117,42 @@ def unfinished_review(root, reviewer, plan="", since=0):
|
|
|
1113
1117
|
return rec["plan"] == plan
|
|
1114
1118
|
return (_stamp(rec.get("updated")) or 0) >= since
|
|
1115
1119
|
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1120
|
+
found = [rec for rec in tt.slices(root) if named(rec) and ours(rec)]
|
|
1121
|
+
refusals = ["review slice %s is %s%s: let it finish, or resume it with `tezgah-task "
|
|
1122
|
+
"slice resume %s` and wait for that run; if the review was finished "
|
|
1123
|
+
"another way, record it with `tezgah-task slice done %s` - an approve "
|
|
1124
|
+
"from a review that did not finish reads nothing"
|
|
1125
|
+
% (rec["id"], rec.get("status"),
|
|
1126
|
+
(" (%s)" % rec["failure"]) if rec.get("failure") else "",
|
|
1127
|
+
rec["id"], rec["id"])
|
|
1128
|
+
for rec in found if rec.get("status") in ("open", "stopped", "stale")]
|
|
1129
|
+
ran = [rec for rec in found if rec.get("status") == "done"
|
|
1130
|
+
and (_stamp(rec.get("updated")) or 0) >= ran_since]
|
|
1131
|
+
if not ran and not refusals and not no_slice:
|
|
1132
|
+
# a requested reviewer with no finished record since the work is a
|
|
1133
|
+
# review that did not run
|
|
1134
|
+
refusals.append(
|
|
1135
|
+
"no finished review slice names %s for this plan since its work began: "
|
|
1136
|
+
"a reviewer with no record did not run, and a readiness review, an "
|
|
1137
|
+
"earlier round or a resumed record is not this review. Spawn the fresh "
|
|
1138
|
+
"reviewer and approve once its slice is done; a host that keeps no "
|
|
1139
|
+
"slice journal records why with "
|
|
1140
|
+
"`--no-slice \"<why: the reviewer ran outside the slice journal>\"`"
|
|
1141
|
+
% reviewer)
|
|
1142
|
+
return refusals
|
|
1126
1143
|
|
|
1127
1144
|
|
|
1128
1145
|
def cmd_review(args):
|
|
1146
|
+
no_slice = ""
|
|
1147
|
+
if "--no-slice" in args:
|
|
1148
|
+
i = args.index("--no-slice")
|
|
1149
|
+
no_slice = " ".join((args[i + 1:i + 2] or [""])[0].split())
|
|
1150
|
+
args = args[:i] + args[i + 2:]
|
|
1151
|
+
if not no_slice:
|
|
1152
|
+
return misuse("--no-slice takes why the reviewer ran outside the slice journal")
|
|
1129
1153
|
if len(args) != 3 or args[2] not in VERDICTS:
|
|
1130
|
-
return misuse("review takes <NNN|path> <reviewer> %s
|
|
1154
|
+
return misuse("review takes <NNN|path> <reviewer> %s [--no-slice WHY]"
|
|
1155
|
+
% "|".join(VERDICTS))
|
|
1131
1156
|
path, problem = open_plan(args[0])
|
|
1132
1157
|
if problem:
|
|
1133
1158
|
return problems([problem])
|
|
@@ -1144,9 +1169,21 @@ def cmd_review(args):
|
|
|
1144
1169
|
mark = (fields.get("checkpoint") or "").split()
|
|
1145
1170
|
since = (git_out(root, "log", "-1", "--format=%ct", mark[-1])
|
|
1146
1171
|
if mark and mark[-1] != tt.CHECKPOINT_PENDING else None)
|
|
1172
|
+
since = int(since) if (since or "").isdigit() else 0
|
|
1173
|
+
# this round's review finished after the first commit of the work it
|
|
1174
|
+
# reads: past the checkpoint in round one (a plan-readiness review ran
|
|
1175
|
+
# before it), past the commit round one read in round two (round one's
|
|
1176
|
+
# reviewer finished before it); with no such commit, after the base itself
|
|
1147
1177
|
ident = fields.get("id") or os.path.basename(path).split("-")[0]
|
|
1148
|
-
|
|
1149
|
-
|
|
1178
|
+
base = fields.get("reviewed_sha") or (
|
|
1179
|
+
mark[-1] if mark and mark[-1] != tt.CHECKPOINT_PENDING else "")
|
|
1180
|
+
first = ""
|
|
1181
|
+
if base:
|
|
1182
|
+
after = (git_out(tt.plan_holders(root).get(ident) or root, "log",
|
|
1183
|
+
"--reverse", "--format=%ct", base + "..HEAD") or "").split()
|
|
1184
|
+
first = after[0] if after else git_out(root, "log", "-1", "--format=%ct", base)
|
|
1185
|
+
ran_since = max(since, int(first) if (first or "").isdigit() else 0)
|
|
1186
|
+
bad = unfinished_review(root, args[1], ident, since, no_slice, ran_since)
|
|
1150
1187
|
if bad:
|
|
1151
1188
|
return problems(bad)
|
|
1152
1189
|
# a plan held by its own worktree is built on that branch: its HEAD is the
|
|
@@ -1154,7 +1191,8 @@ def cmd_review(args):
|
|
|
1154
1191
|
ident = fields.get("id") or os.path.basename(path).split("-")[0]
|
|
1155
1192
|
where = tt.plan_holders(root).get(ident) or root
|
|
1156
1193
|
sha = (git_out(where, "rev-parse", "HEAD") or "")[:12]
|
|
1157
|
-
record = {"review": "%s %s %s" % (args[1], args[2], today()
|
|
1194
|
+
record = {"review": "%s %s %s%s" % (args[1], args[2], today(),
|
|
1195
|
+
" no-slice: " + no_slice if no_slice else ""),
|
|
1158
1196
|
"review_round": str(int(prior) + 1 if prior.isdigit() else 1),
|
|
1159
1197
|
"updated": today()}
|
|
1160
1198
|
if sha:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"CHANGELOG.md": {
|
|
3
|
-
"long_sentences":
|
|
3
|
+
"long_sentences": 1,
|
|
4
4
|
"passive": 2,
|
|
5
5
|
"replace_words": 0,
|
|
6
6
|
"semicolons": 0
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"semicolons": 3
|
|
31
31
|
},
|
|
32
32
|
"docs/evidence.md": {
|
|
33
|
-
"long_sentences":
|
|
33
|
+
"long_sentences": 112,
|
|
34
34
|
"passive": 55,
|
|
35
35
|
"replace_words": 1,
|
|
36
36
|
"semicolons": 39
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"semicolons": 14
|
|
55
55
|
},
|
|
56
56
|
"docs/hosts.md": {
|
|
57
|
-
"long_sentences":
|
|
57
|
+
"long_sentences": 38,
|
|
58
58
|
"passive": 19,
|
|
59
59
|
"replace_words": 0,
|
|
60
60
|
"semicolons": 18
|
|
@@ -83,6 +83,12 @@
|
|
|
83
83
|
"replace_words": 3,
|
|
84
84
|
"semicolons": 42
|
|
85
85
|
},
|
|
86
|
+
"docs/orca.md": {
|
|
87
|
+
"long_sentences": 0,
|
|
88
|
+
"passive": 0,
|
|
89
|
+
"replace_words": 0,
|
|
90
|
+
"semicolons": 0
|
|
91
|
+
},
|
|
86
92
|
"docs/research.md": {
|
|
87
93
|
"long_sentences": 105,
|
|
88
94
|
"passive": 59,
|
package/docs/contract.md
CHANGED
|
@@ -208,7 +208,10 @@ default, measured on two research lines with a written flip
|
|
|
208
208
|
one also gets an OCR review spec: OCR's file selection, its rule groups and a
|
|
209
209
|
refuter pass. The spec is input to the review, never a verdict
|
|
210
210
|
([operations](operations.md#opt-in-ocr-review-spec-ocr),
|
|
211
|
-
`bin/tezgah-task::print_ocr_spec`).
|
|
211
|
+
`bin/tezgah-task::print_ocr_spec`). A verdict comes only from a fresh
|
|
212
|
+
reviewer. A host that cannot start one says so and asks the owner. The author
|
|
213
|
+
contests a confirmed finding only with a file:line the reviewer could read. It
|
|
214
|
+
never lowers a severity (`hooks/tezgah_policy.py::EXEC`).
|
|
212
215
|
|
|
213
216
|
## Composition order of the always-on core
|
|
214
217
|
|
|
@@ -256,7 +259,7 @@ applied, and it also returns the names of the switches that fired.
|
|
|
256
259
|
- **Ponytail (minimal code).** Take the laziest rung that holds (YAGNI → reuse → stdlib → platform → installed dependency → one line), never simplify away validation, error handling or security. A non-default level rides the reminder (`hooks/tezgah_context.py::_pony_level_line`).
|
|
257
260
|
- **Output shape: ADHD-friendly.** The action or answer is the first line, multi-step work is a numbered list whose position is restated, an estimate is in concrete units. Two upstream rules were rewritten rather than imported verbatim because they collided with rules already in force: the state restatement points at the todo list instead of duplicating it, and a time estimate can no longer be read as a measurement.
|
|
258
261
|
- **Deliver the whole ask; never the shortcut.** The request is a floor: no cheaper stand-in, no silent scope cut, no token gesture reported as done.
|
|
259
|
-
- **Integrity: evidence, or a named blocker.** A done/tested claim holds only if the check ran in this session and its output was seen; "unverified" (tr: "doğrulanmadı") follows a check that ran and failed; leftover work is done, or stays as one of four blockers (DECISION, ACCESS, IRREVERSIBLE, ENV; tr: KARAR, YETKİ, GERİ ALINAMAZ, ORTAM) with its evidence. The mechanical half is enforced by [gate.md](gate.md), the residual half by the Stop rule's `residual work` class ([evidence.md](evidence.md)). Its **Ask policy** says what is asked at all. Work and process are never asked. An outward step runs on a grant. A never-default step waits for an answer. Every other question goes in one batch with a recommended default. The Stop rule's `needless ask` class refuses a reply that ends on a question the agent can decide, and the gate warns on an ask-tool question with no default ([gate.md](gate.md#ask-an-ask-tool-question-with-no-recommended-option)).
|
|
262
|
+
- **Integrity: evidence, or a named blocker.** A done/tested claim holds only if the check ran in this session and its output was seen; "unverified" (tr: "doğrulanmadı") follows a check that ran and failed; leftover work is done, or stays as one of four blockers (DECISION, ACCESS, IRREVERSIBLE, ENV; tr: KARAR, YETKİ, GERİ ALINAMAZ, ORTAM) with its evidence. The mechanical half is enforced by [gate.md](gate.md), the residual half by the Stop rule's `residual work` class ([evidence.md](evidence.md)). Its **Ask policy** says what is asked at all. Work and process are never asked. An outward step runs on a grant. A never-default step waits for an answer. Plan 136 added three to that list after a session ran another session's research line under a hidden dir and wrote a `chosen:` variant the owner never picked: choosing a variant or option a research, analysis or review recommends; a way around a tezgah refusal (another folder, a hidden dir, a hatch the owner did not grant); deciding about another session's work (its line, plan or branch). A taste or intent fork still goes in the batch with a default taken this turn; these do not. A subagent's question of that kind is relayed to the user, never answered by the orchestrator or escalated to a stronger model. Every other question goes in one batch with a recommended default. The Stop rule's `needless ask` class refuses a reply that ends on a question the agent can decide, and passes one that asks the owner to pick a variant or to decide one of those steps (`hooks/tezgah_integrity.py::OWNER_CHOICE`); the gate warns on an ask-tool question with no default ([gate.md](gate.md#ask-an-ask-tool-question-with-no-recommended-option)).
|
|
260
263
|
- **Loop discipline.** Never repeat an identical failing command; three attempts is the ceiling, then report what is still unknown.
|
|
261
264
|
- **Lessons ledger: stop repeating mistakes.** `.tezgah/lessons.md` lines are standing constraints; the recent ones are injected at session start, and up to three older ones a prompt shares words with ride that turn, each once per session when the host sends a session id (a compaction resets that, because the compacted context no longer holds them; without an id every matching turn carries them). Write a line rule first, `<rule> - <incident>` (`LESSON_SEPARATOR`, `hooks/tezgah_context.py::LESSON_SEPARATOR`). The session block counts the shown lines that do not open with their rule. A line ending `|| enforced_by: <rule|test>` leaves both blocks while that gate rule or test is on (`hooks/tezgah_lessons.py::lines`). That one reader serves the context, the gate and the CLI. It cuts that clause, a `|| check: ...` clause and an `@<sha> <path>` stamp off each line (`hooks/tezgah_lessons.py::parse`). None of them changes the key, the per-turn digest, the cut or the ranking. The gate records a line written after untrusted text in the same turn as a `lesson_tainted` row ([evidence.md](evidence.md)). That line still rides both blocks, behind a per-line data label (`hooks/tezgah_context.py::LESSON_TAINTED`). ADR 010 makes it a row, never a refusal. `bin/tezgah-lessons` proposes rewrites, merges and retirements, and never writes the file.
|
|
262
265
|
- **No AI attribution, ever, on any host.** Nothing persisted or published may name a model, vendor or "AI" as author, co-author, generator or helper; one already in pushed history is reported.
|
|
@@ -543,7 +546,7 @@ brief carries the same caller-list floor (`hooks/tezgah_agents.py`), and
|
|
|
543
546
|
## When the injected text grows too large
|
|
544
547
|
|
|
545
548
|
Each event has a byte budget: `session_start` and `post_compact` 16000,
|
|
546
|
-
`user_prompt` 7000, `subagent_start`
|
|
549
|
+
`user_prompt` 7000, `subagent_start` 6100, anything else 13000
|
|
547
550
|
(`hooks/tezgah_context.py::CONTEXT_BUDGET`). The rationale and the measured
|
|
548
551
|
sizes sit beside it. In an empty fixture repo, `session_start` with the core
|
|
549
552
|
builds 9842 B before the ponytail and ADHD rules went inline and 10884 B after
|
package/docs/environment.md
CHANGED
|
@@ -28,14 +28,14 @@ the kill switches are files, not variables, and live in
|
|
|
28
28
|
| `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
29
|
| `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
30
|
| `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/*/hooks.json`, `hosts/omp/tezgah-hook.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. |
|
|
31
|
+
| `TEZGAH_PYTHON` | `hooks/tezgah_paths.py::python_cmd`, `bin/tezgah.js`, `bin/tezgah-dsh.cmd`, `hooks/hooks.json`, `hosts/*/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. |
|
|
32
32
|
|
|
33
33
|
## Where tezgah and the hosts keep state
|
|
34
34
|
|
|
35
35
|
| Variable | Read by | Default | Effect |
|
|
36
36
|
|---|---|---|---|
|
|
37
37
|
| `TEZGAH_ROOTS` | `hooks/tezgah_paths.py::roots`, `bin/tezgah-setup`, `hosts/opencode/plugins/tezgah.js` | `roots` in `~/.config/tezgah/config.json`, else `~/Projects` | The directories tezgah is armed over, `os.pathsep`-separated (`;` on Windows, the opencode plugin included); it outranks the config file. |
|
|
38
|
-
| `XDG_CONFIG_HOME` | `hooks/tezgah_paths.py::CONFIG_DIR`, `bin/tezgah-doctor`, `bin/tezgah-dsh.cmd`, `hosts/opencode/plugins/tezgah.js` | `~/.config` | Parent of tezgah's config dir (kill switches, `bin/` links, config) and of
|
|
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
39
|
| `XDG_DATA_HOME` | `bin/tezgah-setup::default_prefix`, `packaging/install.sh`, `packaging/upgrade.sh` | `~/.local/share` | Parent of the default install prefix. |
|
|
40
40
|
| `XDG_CACHE_HOME` | `hooks/tezgah_apps.py::ARTIFACTS` | `~/.cache` | Parent of the app-analysis artifact dir. |
|
|
41
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. |
|
|
@@ -122,6 +122,7 @@ the kill switches are files, not variables, and live in
|
|
|
122
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. |
|
|
123
123
|
| `CLAUDE_PROJECT_DIR` | `hooks/projects-auto-init.py::main` | the process cwd | The project dir when the hook payload carries no `cwd`. |
|
|
124
124
|
| `PLAYWRIGHT_BROWSERS_PATH`, `LOCALAPPDATA` | `hooks/tezgah_apps.py::_browser_caches`, `bin/tezgah-setup::default_prefix` (`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
|
+
| `AMP_IGNORE_GUIDANCE_FILES` | `hosts/amp/tezgah-plugin.ts.in` | set by the user for Amp | Amp's own list of guidance files to skip; when it is set the plugin asks for the session brief with the core in it, since `~/.config/amp/AGENTS.md` may be skipped. |
|
|
125
126
|
|
|
126
127
|
## Test knob
|
|
127
128
|
|
package/docs/evidence.md
CHANGED
|
@@ -125,7 +125,8 @@ a write tool is `edit`, a shell call is `verify` when its command matches the ch
|
|
|
125
125
|
| `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
126
|
| `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
127
|
| `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/hooks.json`)
|
|
128
|
+
| `subagent_end` | `stop_reason(..., subagent=True)`, from Claude's SubagentStop (`hooks/hooks.json`), Cursor's subagentStop `summary` and Droid's SubagentStop `task_result`; 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 |
|
|
129
|
+
| `reply_unread` | Droid's, Command Code's and Copilot's Stop (`hosts/droid/hook.py::main`, `hosts/commandcode/hook.py::main`, `hosts/copilot/hook.py::main`) when the transcript 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. No such Stop event carries the reply, and an empty reply judged would pass any claim as "no claim", so the turn end is recorded and not judged | nothing yet; `NOT_TOOL_HOOK` lists it |
|
|
129
130
|
| `stop_spec` | nothing since plan 101. Plan 063's shadow wrote one per reply that left a claim or a refusal row: `agree`, `disagree` or `error`, and `fold` or `selector`. The fold it compared against is gone | nothing. `NOT_TOOL_HOOK` still lists it and the replay keeps it beside its claim (`hooks/tezgah_replay.py::SIDE_KINDS`), so an old ledger reads as it did |
|
|
130
131
|
| `deny`, `nudge` | the [gate](gate.md)'s `hooks/tezgah_gate.py::_deny`, first-nudge `hooks/tezgah_gate.py::decision` | `hooks/tezgah_integrity.py::counters` |
|
|
131
132
|
| `snapshot`, `rollback` | `hooks/tezgah_snapshot.py::_capture_one`, `hooks/tezgah_snapshot.py::restore` | `hooks/tezgah_integrity.py::_snapshot_hash`; no counter |
|
|
@@ -296,12 +297,31 @@ The checker is `_stop_block` (`hooks/tezgah_integrity.py::_stop_block`), reached
|
|
|
296
297
|
(`hosts/omp/hook.py::handle`) — all with `{"decision": "block", "reason": …}`, all inert outside a
|
|
297
298
|
[root](glossary.md#root) and under the `verify-off` [kill switch](glossary.md#kill-switch)
|
|
298
299
|
(`hooks/projects-stop.py::main`). Each of these hosts judges the reply after a block again.
|
|
300
|
+
The reply the rule judges is read through `reply_prose`
|
|
301
|
+
(`hooks/tezgah_integrity.py::reply_prose`). A string passes through. A host that hands over
|
|
302
|
+
a message object yields its `text` blocks, joined the way the model wrote them. A thinking
|
|
303
|
+
or tool block is left out. omp's `session_stop` carries `last_assistant_message` as the object
|
|
304
|
+
its `getLastAssistantMessage()` returns, and the bridge passed it through, so the rule judged
|
|
305
|
+
`str(dict)`: one line, no list items, diluted prose. Measured on
|
|
306
|
+
the owner's evidence database (2026-10-29): 1643 of 1656 omp `shape` rows of 45 days carried
|
|
307
|
+
`lines: 1`. Every other host wrote 209 multi-line rows of 231. Of the three days' refusals, 24
|
|
308
|
+
were `reply language` over such a row, and every line-shaped class read a text that had no
|
|
309
|
+
lines. The bridge now sends the text too
|
|
310
|
+
(`hosts/omp/tezgah-hook.ts.in`), but the core is where a host cannot repeat it (plan 143).
|
|
299
311
|
The re-ask budget is the core's (`_record_only`, `hooks/tezgah_integrity.py::_record_only`): at
|
|
300
312
|
most `STOP_REASKS` = 2 refusals in one user turn, and never two for one reason class. The count
|
|
301
313
|
comes from the turn's `blocked:` claim and refusal rows, not from the host's payload. The omp host
|
|
302
|
-
hands a block back as a queued message. Its prompt hook writes that text as a new `turn` row.
|
|
303
|
-
|
|
304
|
-
|
|
314
|
+
hands a block back as a queued message. Its prompt hook writes that text as a new `turn` row.
|
|
315
|
+
Gemini does the same: an AfterAgent deny comes back through BeforeAgent as the next prompt
|
|
316
|
+
(`hosts/gemini/hook.py::main`). A refusal row stores the key of its text as `reask`. A `turn` row with that key starts no new user
|
|
317
|
+
turn for the count (`_turn_refusals`, `hooks/tezgah_integrity.py::_turn_refusals`). Antigravity's
|
|
318
|
+
`continue` re-enters the loop with no prompt, so its first-invocation hook writes no `turn` row
|
|
319
|
+
while the ledger's last row is that refusal (`hosts/antigravity/hook.py::reentered`). A Goose Stop
|
|
320
|
+
block keeps the turn going with no new prompt and no host count, so its turn rows alone decide
|
|
321
|
+
(`hosts/goose/hook.py::main`). Amp's plugin continues a blocked turn with a user message that
|
|
322
|
+
starts with `[tezgah:stop]`. The plugin never sends that prompt as a `user_prompt`. So it writes
|
|
323
|
+
no `turn` row and grants nothing. The Stop rule then judges that turn with `stop_hook_active`
|
|
324
|
+
(`hosts/amp/tezgah-plugin.ts.in`). The omp host
|
|
305
325
|
caps the continuations it starts itself, but never a chain of blocks, so tezgah's count bounds that
|
|
306
326
|
chain. So a reply that fixes the first class but adds a new one gets one more refusal. A blocker
|
|
307
327
|
list with no evidence is such a new class. A reply after a block that is not refused writes one
|
|
@@ -310,7 +330,7 @@ while the turn holds no refusal of this rule. That reply gets no refusal and no
|
|
|
310
330
|
Stop hook may have blocked. The host's count rules only where the ledger cannot. A session with no
|
|
311
331
|
`turn` row allows one refusal per chain. A Cursor `loop_count` of 2 ends the chain. The opencode
|
|
312
332
|
host has no Stop rule and so no budget. Its plugin API has no end-of-turn hook with an output. The
|
|
313
|
-
`event` hook of `hosts/opencode/plugins/tezgah.js` only observes.
|
|
333
|
+
`event` hook of `hosts/opencode/plugins/tezgah.js` only observes. Fifteen triggers, in order, each naming its reason class (`_stop_block`
|
|
314
334
|
`hooks/tezgah_integrity.py::_stop_block`).
|
|
315
335
|
The first four judge how the reply is written (`_shape_block`, `hooks/tezgah_integrity.py::_shape_block`); the rest judge its evidence:
|
|
316
336
|
|
|
@@ -516,18 +536,71 @@ The four shape classes share their switches with the text they enforce: `adhd-of
|
|
|
516
536
|
(`hooks/tezgah_integrity.py::QUESTION_END`). Its paragraph names no recommended default, and
|
|
517
537
|
it names no step the ask policy keeps from a default (`hooks/tezgah_integrity.py::never_default`).
|
|
518
538
|
Such steps are a force push, a delete, production, a credential, a third party, or a spend
|
|
519
|
-
over `spend_cap`.
|
|
539
|
+
over `spend_cap`. Building a plan the reply wrote is one too
|
|
540
|
+
(`hooks/tezgah_integrity.py::PLAN_BUILD`). So is a last line that asks the owner to pick a
|
|
541
|
+
variant ("V4 mü V1 mi?"), to take a way around a tezgah refusal, or to decide about another
|
|
542
|
+
session's work (`hooks/tezgah_integrity.py::OWNER_CHOICE`, plan 136). The
|
|
543
|
+
class then refuses a process question: the packs' `ask.process`
|
|
520
544
|
shapes, such as "Başlayayım mı?", "shall I proceed?" and "which first?"
|
|
521
545
|
(`hooks/tezgah_integrity.py::PROCESS_ASK`). It also refuses a question about an outward
|
|
522
546
|
step the user granted this session (`hooks/tezgah_integrity.py::grants_in_force`). A question
|
|
523
547
|
about an outward step the user has not granted is the one the policy asks, so it passes. The
|
|
524
|
-
refusal says to take the step, or to move
|
|
548
|
+
refusal says to take the step, or to move a taste or intent fork into the approval batch
|
|
549
|
+
with a default. A class-F step waits for the answer. Under
|
|
525
550
|
`tr` it opens with "Bu soruyu sorma". `_residual_block` asks it after `residual work` and
|
|
526
551
|
before `ready steps` (`hooks/tezgah_integrity.py::needless_ask`). Unlike those two, it needs
|
|
527
|
-
no work in the turn, because a
|
|
552
|
+
no work in the turn, because a reply that ends on a bare "Başlayayım mı?" is its measured case.
|
|
528
553
|
The research line counted 33 process questions among 48 question endings in the owner's omp
|
|
529
554
|
transcripts. It refuses once per turn and counts like `residual work`
|
|
530
555
|
(`hooks/tezgah_integrity.py::LIST_BLOCKS`).
|
|
556
|
+
14. **handed step** — a turn that ran a command of its own ends by telling the reader to run
|
|
557
|
+
one (plan 142). The line names a command in backticks and addresses the reader as the one
|
|
558
|
+
who runs it: the packs' `ask.hand_over` shapes, "komutunu sen çalıştır", "çalıştırman
|
|
559
|
+
gerekiyor", "senin adımın", "please run", "your step:"
|
|
560
|
+
(`hooks/tezgah_integrity.py::handed_steps`). An answer about how something is done is not
|
|
561
|
+
one of them. "you can run `git config --global ...`" answers the question the reader
|
|
562
|
+
asked, and the review of this plan caught the wider wording refusing it. The turn must
|
|
563
|
+
hold a
|
|
564
|
+
`run`/`verify*` row (`hooks/tezgah_integrity.py::COMMAND_KINDS`). A turn that ran nothing
|
|
565
|
+
could not have run this either, which keeps the class inert on a host that sends no mode. A
|
|
566
|
+
read-only mode closes it outright: there every step waits for the approval. A table row and
|
|
567
|
+
a quoted line are not the
|
|
568
|
+
reply speaking, so the class skips them. The line stays the user's when it names an
|
|
569
|
+
outward step (`hooks/tezgah_integrity.py::outward_classes`), a class-F step
|
|
570
|
+
(`hooks/tezgah_integrity.py::never_default`), the owner's consent, tezgah's own install or a
|
|
571
|
+
`brew` step (`hooks/tezgah_integrity.py::OWNER_STEP`), or when it types its own blocker. The
|
|
572
|
+
refusal says to run each one and report what it printed. `_residual_block` asks it after
|
|
573
|
+
`needless ask` and before `ready steps`. A handover under a residual label ("Sonraki adım:")
|
|
574
|
+
belongs to `residual work`, which comes first. This class is the plain sentence that one
|
|
575
|
+
never saw. It refuses once per turn and counts like `residual work`
|
|
576
|
+
(`hooks/tezgah_integrity.py::LIST_BLOCKS`). Measured on the owner's top-level omp and Claude
|
|
577
|
+
transcripts (2026-10-29): of 3307 user turns, 27 replies handed the reader a command and no
|
|
578
|
+
class refused any of them. The shipped shapes refuse 9 of those turns, 8 of them a step the
|
|
579
|
+
session could have taken. Those eight are four `tezgah-task phase` moves, a local merge,
|
|
580
|
+
two "the next step is yours" and a server command that owes an ACCESS line. The ninth
|
|
581
|
+
quotes the gate's own refusal. The wider wording of the first two review rounds refused 40
|
|
582
|
+
turns and then 14, an answer about how a command is run among them.
|
|
583
|
+
15. **plan claim** — a first-person claim that a command or check ran, in a turn the host ran
|
|
584
|
+
in a read-only mode (plan 142).
|
|
585
|
+
The host sends the mode with the Stop payload: `permission_mode` on Claude, Command Code,
|
|
586
|
+
Qwen and Droid, and omp's extension sends `plan` from `ctx.sessionModes`. `plan` and Droid's
|
|
587
|
+
`spec` are the read-only ones (`hooks/tezgah_integrity.py::READ_ONLY_MODES`). Nothing could
|
|
588
|
+
run in such a turn, so the claim says what cannot have happened. `_stop_block` asks it right
|
|
589
|
+
after the tamper classes and before the
|
|
590
|
+
admission, since "doğrulanmadı" repairs an unverified claim and here there was nothing to
|
|
591
|
+
check. A check that passed in the turn anyway clears it, like any other
|
|
592
|
+
evidence: a host may allow a read-only command in the mode. The trigger is the packs'
|
|
593
|
+
`ask.ran_it` vocabulary (`hooks/tezgah_integrity.py::RAN_IT`) - "test ettim", "i ran the
|
|
594
|
+
suite". It is read through the question and negation filter a claim is read through
|
|
595
|
+
(`hooks/tezgah_integrity.py::_asserted`). The done and verified vocabularies are not the
|
|
596
|
+
trigger: a plan states its acceptance in those words ("Done when all tests pass", "testler
|
|
597
|
+
yeşil olunca"), and the review of this plan caught both refused. It counts
|
|
598
|
+
as a false completion. The same mode exempts `needless ask` and closes `handed step`
|
|
599
|
+
(`hooks/tezgah_integrity.py::_residual_block`): the approval question is what plan mode asks
|
|
600
|
+
for, and every step it names waits for that approval. Every row of such a turn carries
|
|
601
|
+
`mode`, so a replay judges it as the mode it ran in
|
|
602
|
+
(`hooks/tezgah_replay.py::build_corpus`). A host that sends no
|
|
603
|
+
mode reads as `default`, which is what every rule did before the field.
|
|
531
604
|
|
|
532
605
|
A past-time temporal spec decides classes 5–10 (`hooks/tezgah_stopspec.py::FORMULAS`,
|
|
533
606
|
plan 063). Seven formulas read ten row atoms (`ROW_ATOMS`) and the turn marker. The module compiles
|
package/docs/gate.md
CHANGED
|
@@ -83,7 +83,7 @@ it first after the root check (`decision` `hooks/tezgah_gate.py::decision`). The
|
|
|
83
83
|
| tezgah's configuration | all of `~/.config/tezgah` (`config.json`, `bin/`, `ponytail.level`, the `hook:<host>:...` rows of `contract.sha256`, ...) | any write |
|
|
84
84
|
| 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
85
|
| 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`) |
|
|
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`, 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
87
|
| 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 |
|
|
88
88
|
| | `.husky` | a delete, a chmod that takes a permission away, or a move |
|
|
89
89
|
| | 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 |
|
|
@@ -139,7 +139,7 @@ remedy is one sentence, `hooks/tezgah_integrity.py::PIPED_REMEDY`, which the alw
|
|
|
139
139
|
|
|
140
140
|
Trigger, shell: a write command (`hooks/tezgah_gate.py::WRITE_CMD` — `git commit|merge|tag|notes`, `gh api`,
|
|
141
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
|
|
142
|
+
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
143
|
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
144
|
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
145
|
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
|
|
@@ -207,6 +207,8 @@ checkpoint that is a plain sha (the boundary is already real), a `pending` with
|
|
|
207
207
|
|
|
208
208
|
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
209
|
|
|
210
|
+
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.
|
|
211
|
+
|
|
210
212
|
### Secret — a credential on its way into a file
|
|
211
213
|
|
|
212
214
|
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`):
|
|
@@ -443,6 +445,8 @@ rule's triggers are the classes `_stop_block` returns, the four `_shape_block` h
|
|
|
443
445
|
| `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
446
|
| `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
447
|
| `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` |
|
|
448
|
+
| `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` |
|
|
449
|
+
| `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
450
|
| `placating opener` | designed | output rule 10 read at the Stop event; no incident recorded | `tests/test_integrity.py::test_sycophantic_opener_blocks` |
|
|
447
451
|
| `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
452
|
| `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` |
|
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,
|
|
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.
|