@r1z4x/tezgah 1.5.1 → 1.7.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 +126 -1
- package/MANIFEST +11 -0
- package/README.md +6 -3
- package/bin/consult +13 -10
- package/bin/tezgah-docs +18 -16
- package/bin/tezgah-doctor +152 -0
- package/bin/tezgah-research +286 -12
- package/bin/tezgah-route +48 -1
- package/bin/tezgah-setup +27 -14
- package/bin/tezgah-task +771 -46
- package/bin/tezgah-taste +48 -10
- package/docs/architecture.md +7 -4
- package/docs/citations-baseline.json +1 -1
- package/docs/clarity-baseline.json +5 -5
- package/docs/contract.md +112 -25
- package/docs/evidence.md +201 -28
- package/docs/feature-audit.md +25 -4
- package/docs/gate.md +70 -33
- package/docs/glossary.md +4 -4
- package/docs/hosts.md +8 -8
- package/docs/index.json +30 -10
- package/docs/judge.md +9 -3
- package/docs/models.md +35 -2
- package/docs/operations.md +103 -3
- package/docs/research.md +392 -13
- package/docs/skills.md +24 -6
- package/docs/status-line.md +5 -5
- package/docs/testing.md +2 -2
- package/docs/vision.md +12 -4
- package/hooks/hooks.json +2 -2
- package/hooks/lang/en.json +323 -0
- package/hooks/lang/tr.json +344 -0
- package/hooks/projects-posttooluse.py +24 -5
- package/hooks/projects-pretooluse.py +9 -4
- package/hooks/projects-stop.py +28 -16
- package/hooks/tezgah_agents.py +29 -9
- package/hooks/tezgah_clarity.py +7 -3
- package/hooks/tezgah_components.py +2 -1
- package/hooks/tezgah_context.py +460 -390
- package/hooks/tezgah_gate.py +640 -104
- package/hooks/tezgah_integrity.py +1480 -197
- package/hooks/tezgah_models.py +337 -15
- package/hooks/tezgah_paths.py +103 -10
- package/hooks/tezgah_policy.py +286 -130
- package/hooks/tezgah_product.py +107 -0
- package/hooks/tezgah_rank.py +29 -27
- package/hooks/tezgah_replay.py +200 -30
- package/hooks/tezgah_research.py +1297 -12
- package/hooks/tezgah_skill_pick.py +58 -17
- package/hooks/tezgah_stopspec.py +7 -0
- package/hooks/tezgah_store.py +74 -27
- package/hooks/tezgah_task.py +917 -8
- package/hosts/codex/hook.py +9 -10
- package/hosts/cursor/hook.py +21 -14
- package/hosts/dsh/hooks.json +2 -2
- package/hosts/omp/hook.py +44 -23
- package/hosts/omp/tezgah-hook.ts.in +93 -7
- package/hosts/opencode/plugins/tezgah.js +119 -39
- package/output-styles/tezgah.md +76 -42
- package/package.json +1 -1
- package/skills/analyze-app/SKILL.md +57 -0
- package/skills/design-contract/EVALS.md +24 -1
- package/skills/design-contract/SKILL.md +17 -1
- package/skills/design-contract/data-views.md +86 -0
- package/skills/feature-audit/SKILL.md +32 -3
- package/skills/i-have-adhd/EVALS.md +22 -2
- package/skills/i-have-adhd/SKILL.md +12 -5
- package/skills/plan-add/EVALS.md +8 -8
- package/skills/plan-add/SKILL.md +22 -21
- package/skills/plan-sync/EVALS.md +2 -2
- package/skills/plan-sync/SKILL.md +14 -8
- package/skills/pm-frameworks/SKILL.md +6 -0
- package/skills/ponytail/EVALS.md +21 -2
- package/skills/ponytail/SKILL.md +4 -4
- package/skills/product-analysis/EVALS.md +23 -1
- package/skills/product-analysis/SKILL.md +50 -11
- package/skills/product-flow/EVALS.md +81 -0
- package/skills/product-flow/SKILL.md +91 -0
- package/skills/research/SKILL.md +99 -2
- package/skills/tezgah-contract/EVALS.md +52 -7
- package/skills/tezgah-contract/SKILL.md +204 -85
package/docs/evidence.md
CHANGED
|
@@ -36,7 +36,7 @@ family lose theirs (`hooks/tezgah_integrity.py::REDACTIONS`; audit L-6, which fo
|
|
|
36
36
|
(`hooks/tezgah_integrity.py::LEDGER_FIELDS`) — `id`, `exit`, `out_bytes`, `fail_class`, `workspace`, `source`, `hash`, `changed`, `tool`,
|
|
37
37
|
`target`, plus the reply-shape names `lines`, `chars`, `items`, `longest_list`, `tr_share`, `answer_first` and the compaction
|
|
38
38
|
row's `summary_chars`, `summary_hash`, `constraint_found`, `constraint_expected`, the `attest`
|
|
39
|
-
row's `host` and `switches` and a `
|
|
39
|
+
row's `host` and `switches`, a `claim` row's `harness` and a Stop refusal row's `reask` (the key of its own text, which the `turn` row of omp's continuation carries, `hooks/tezgah_integrity.py::_turn_refusals`) — and
|
|
40
40
|
a key outside that set is dropped, a `None` value left out, because every reader treats a missing key
|
|
41
41
|
as `None` (`hooks/tezgah_integrity.py::note_path`). `tool` is the call's own name (`Bash`, `Write`, `mcp__codegen__status`),
|
|
42
42
|
written by `note_tool` (`hooks/tezgah_integrity.py::note_tool`) because `classify` folds the name into
|
|
@@ -49,7 +49,7 @@ written path as an absolute real path, resolved against the call's cwd (`_abs_ta
|
|
|
49
49
|
now compares `target`, and a row without one (written before the field landed) is ignored by that
|
|
50
50
|
guard rather than matched by its spelling (audit M-6). Both writers write `tool` — `note_tool`, and opencode's
|
|
51
51
|
plugin, whose row carries the same field under the same empty-means-absent rule
|
|
52
|
-
(`hosts/opencode/plugins/tezgah.js:
|
|
52
|
+
(`hosts/opencode/plugins/tezgah.js:1253-1264`) — so no live host leaves it out. A row written *before*
|
|
53
53
|
the field landed names its tool only where the name survived in `detail` — an `unknown` row and an
|
|
54
54
|
MCP `external` row — so the one cause of an under-count is the corpus's age, and it shrinks as
|
|
55
55
|
sessions run. The append is one INSERT in autocommit (`hooks/tezgah_integrity.py::_append`,
|
|
@@ -63,7 +63,14 @@ audit L-6 found the old files `0644`.
|
|
|
63
63
|
**The JSONL files it replaced.** Every read or write of a session first imports its old
|
|
64
64
|
`<cache>/evidence/<slug>.jsonl` file (`hooks/tezgah_store.py::_sync`). The import costs one stat
|
|
65
65
|
when nothing is new. It takes the database's write lock first, then the file's flock if it is free.
|
|
66
|
-
It takes only the complete lines past the bytes the `imported` table already counts
|
|
66
|
+
It takes only the complete lines past the bytes the `imported` table already counts for the file.
|
|
67
|
+
A volume remount can give the file a new device id. A store from before the fix then took the
|
|
68
|
+
file again from its start under the new id. So every `imported` row of one name and inode is one
|
|
69
|
+
import stream. The import resumes from the longest stream under any id, even when this id holds a
|
|
70
|
+
shorter one (`hooks/tezgah_store.py::_recorded`). That row and this id's own row swap ids, so each
|
|
71
|
+
stream keeps one row (`hooks/tezgah_store.py::_record`). A file cut below what went in goes on as
|
|
72
|
+
one stream. `tezgah-doctor --repair-evidence` counts the copies by those rows and removes them
|
|
73
|
+
([operations](operations.md#health-pass-tezgah-doctor)). It never
|
|
67
74
|
renames the file aside. An opencode process started before the store may still run the old plugin,
|
|
68
75
|
which appends to it, and a refused row lands there. Session start launches the bulk import
|
|
69
76
|
of every other file, detached and at most once a day
|
|
@@ -108,12 +115,16 @@ a write tool is `edit`, a shell call is `verify` when its command matches the ch
|
|
|
108
115
|
| Kind | Written by | Read by |
|
|
109
116
|
|---|---|---|
|
|
110
117
|
| `turn` | `hooks/tezgah_integrity.py::note_turn`, from the prompt path | `hooks/tezgah_integrity.py::_turn_start`, scoping every turn rule; `hooks/tezgah_integrity.py::_claim_key` |
|
|
111
|
-
| `run`, `edit`, `verify`, `verify_ok`, `verify_fail`, `interrupted` | `hooks/tezgah_integrity.py::note_tool`, the call's own name in `tool`, on opencode the plugin writes these same kinds and the same field (`hosts/opencode/plugins/tezgah.js:
|
|
118
|
+
| `run`, `edit`, `verify`, `verify_ok`, `verify_fail`, `interrupted` | `hooks/tezgah_integrity.py::note_tool`, the call's own name in `tool`, on opencode the plugin writes these same kinds and the same field (`hosts/opencode/plugins/tezgah.js:1215-1293`) | the Stop rule's `worked` set `hooks/tezgah_integrity.py::WORK_KINDS`; `counters.steps` `hooks/tezgah_integrity.py::_counts`; `last_verify`/`partial_state`; the `--trend` tool histogram |
|
|
112
119
|
| `external`, `unknown` | `hooks/tezgah_integrity.py::note_tool` | the taint notice, via `source`; the `--trend` tool histogram; nothing counts them as work |
|
|
113
|
-
| `
|
|
120
|
+
| `todo` | `hooks/tezgah_integrity.py::note_todo`, from `note_tool` for a todo call: omp `todo` (the bridge sends the list from the result's `details`), Claude and dsh `TodoWrite` (their PostToolUse matcher), opencode `todowrite` (the plugin's own `recordTodo`), Cursor `todo_write` and Codex `update_plan`, which arrive with no matcher. `open`, `ready` and `blocked` count the list, and `detail` holds the ready items' heads, one per line | the `ready steps` Stop class (`hooks/tezgah_integrity.py::ready_left`) and the idle-wait warning (`hooks/tezgah_gate.py::ready_advice`) |
|
|
121
|
+
| `wait` | `hooks/tezgah_integrity.py::note_tool`, for omp's `wait` | `hooks/tezgah_gate.py::ready_advice`, which counts it as idle; nothing counts it as work |
|
|
122
|
+
| `ask` | `hooks/tezgah_gate.py::ask_advice`, for an omp `ask` or Claude AskUserQuestion call: `detail` holds the questions, cut; `questions` counts them and `defaults` the ones with a recommended option | nothing refuses on it; the row is the record of what the session asked |
|
|
123
|
+
| `grant` | `hooks/tezgah_context.py::context_for`, from each prompt's `user_grants` (`hooks/tezgah_context.py::user_grants`): `detail` holds the user's clause, `target` the outward class it covers (`push`, `release`, `publish`, `pr` or `all`), or `lift:<class>` when the prompt took it back | `hooks/tezgah_integrity.py::grants_in_force`, read by the `needless ask` Stop class and the ask-tool warning |
|
|
124
|
+
| `began` | the gate's allow path for a write or shell call `hooks/tezgah_gate.py::decision`; on opencode the plugin's before-hook when the core was not asked (`hosts/opencode/plugins/tezgah.js:2572-2588`) | `hooks/tezgah_integrity.py::unanswered`, behind a claim in the Stop rule and in `counters.unanswered` |
|
|
114
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` |
|
|
115
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 |
|
|
116
|
-
| `after_block` | `hooks/tezgah_integrity.py::stop_reason`, on
|
|
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 |
|
|
117
128
|
| `subagent_end` | `stop_reason(..., subagent=True)`, from Claude's SubagentStop (`hooks/hooks.json`) and Cursor's subagentStop `summary`; 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 |
|
|
118
129
|
| `stop_spec` | `hooks/tezgah_stopspec.py::shadow`, from `stop_reason`, one per reply that leaves a claim or a refusal row, keyed like the claim row. The `detail` is `agree <where>: <class>`, `disagree <where>: <imperative> -> <spec>` (`ok` for an allow) or `error: <type>`. `<where>` is `fold` when the selector reached the fold and `selector` when it decided alone | `tests/stop_spec_eval.py --shadow-rows`, for plan 063's live agreement bar, read on the `fold` rows. It never feeds the verdict. `NOT_TOOL_HOOK` lists it |
|
|
119
130
|
| `deny`, `nudge` | the [gate](gate.md)'s `hooks/tezgah_gate.py::_deny`, first-nudge `hooks/tezgah_gate.py::decision` | `hooks/tezgah_integrity.py::counters` |
|
|
@@ -121,6 +132,7 @@ a write tool is `edit`, a shell call is `verify` when its command matches the ch
|
|
|
121
132
|
| `compact` | `hooks/tezgah_integrity.py::note_compaction`, from the post-compaction path (`tezgah_context.remember_compaction` `hooks/tezgah_context.py::remember_compaction`) | `hooks/tezgah_integrity.py::_counts` (what `counters` folds with) |
|
|
122
133
|
| `lesson` | `hooks/tezgah_context.py::note_lesson`, one row per lesson the budget left in a session block or a per-turn block, with its 8-hex `key` and `block` (`session` or `turn`), inside the host's `safe()` like the rest of the prompt path | no counter; `hooks/tezgah_context.py::NOT_TOOL_HOOK` keeps it out of `hooks/tezgah_context.py::_ledger_since`, so a session of lesson rows still reads as a gate that never ran |
|
|
123
134
|
| `lesson_tainted` | the gate's allow path for a write to a `.tezgah/lessons.md` (`hooks/tezgah_gate.py::lesson_taint`, from `decision` after every refusal) in a user turn that already holds a row carrying `source`, folded over `turn_rows` (that subagent's rows when the host names one). One row per lesson line the ledger will hold after the write and does not hold now, read off the post-write text, so a partial-line edit names the whole line; its 8-hex `key`, the channel in `source` and `detail`, the ledger in `target`. One row with no `key` when the gate cannot name the line (an `echo` append, an edit whose old text is not there, a removal). The keyed rows also go to the repository's index, which keeps only the ledger's current lines (`hooks/tezgah_lessons.py::write_taint`). Never a refusal (ADR 010); the dry run writes neither | the per-line data label on that lesson in both lesson blocks (`hooks/tezgah_context.py::LESSON_TAINTED`, read once per prompt through `hooks/tezgah_lessons.py::tainted`); no counter |
|
|
135
|
+
| `lesson_changed` | the same allow path (`hooks/tezgah_gate.py::lesson_taint`), in any turn whatever it read: one row per `.tezgah/lessons.md` line the write rewrites or removes, read off the post-write text, an `apply_patch` hunk's `-` lines (a `Delete File` drops every line), or a heredoc or plain `tee` that overwrites the ledger (an append, `>>` or `tee -a`, drops nothing and writes none); the old line's 8-hex `key`, its old text in `detail` (redacted and cut like every `detail`), the ledger in `target`. At most `LESSON_CHANGED_MAX` = 10 rows per call; the rest go into one keyless row whose `items` counts them, so a whole-ledger rewrite cannot push the refusal rules' fixed tails out of their window. No `new_key`: the row contract has no field for it, so a rewritten line's new key rides only its `lesson_tainted` row. A shell write the gate cannot read (`sed -i`, an `echo >` body) leaves none. Never a refusal (ADR 019) | nothing yet: the history a person or a replay reads to see which standing constraints were edited away |
|
|
124
136
|
| `lesson_hit` | nothing yet: reserved for plan 061 Phase B, a compiled lesson check that matched a call in its observe stage, with the lesson's `key` and the call's `id` | nothing yet |
|
|
125
137
|
| `ledger_damage` | `hooks/tezgah_integrity.py::_note_damage`, from `_parse`, once per damaged line, with its 12-hex `key` | the Stop rule's first evidence class, "evidence tampered" (`hooks/tezgah_integrity.py::_stop_block`) |
|
|
126
138
|
| `disarm` | `hooks/tezgah_context.py::disarmed`, on the prompt a switch first appears on after the session's first prompt; `detail` is the switch name. Also the gate (`hooks/tezgah_gate.py::decision`), for a call it lets through that changes a `CONTROL_EVIDENCE` target - an open plan's delete or move, a forced `git add` of a `.tezgah/` path - with the call's `id` and `detail` `control: <what>` | the status line's `gate` mark ([status-line.md](status-line.md)) for the switch rows, set by the same check that writes them; the gate's rows are read by a person or a replay; no counter |
|
|
@@ -208,7 +220,7 @@ done` exits 0 whatever pytest found. `pytest && echo ok` and `cd x && pytest` ke
|
|
|
208
220
|
trimmed form before it runs ([gate.md](gate.md), the `piped` rule). The `out_bytes` half
|
|
209
221
|
bites only where the host reported a result size — Codex (`hosts/codex/hook.py::main`), Cursor
|
|
210
222
|
(`hosts/cursor/hook.py::remember_answer,275`) and omp, whose bridge measures it and sends `result_len`
|
|
211
|
-
(`hosts/omp/tezgah-hook.ts.in:
|
|
223
|
+
(`hosts/omp/tezgah-hook.ts.in:737-743`), now do — and 6 of 1423 `verify_ok` rows across 1454 local ledgers carry the field
|
|
212
224
|
(measured 2026-09-19), so an absent field still passes: the guard narrows a check whose
|
|
213
225
|
result was measured empty, it does not require the measurement. `last_verify`
|
|
214
226
|
(`hooks/tezgah_integrity.py::last_verify`) folds the ordered rows to `ok`/`fail`/`ran`/`None`, because a set cannot tell a failure
|
|
@@ -283,17 +295,29 @@ The checker is `_stop_block` (`hooks/tezgah_integrity.py::_stop_block`), reached
|
|
|
283
295
|
(`hosts/codex/hook.py::main`), Cursor (`hosts/cursor/hook.py::dispatch`), omp
|
|
284
296
|
(`hosts/omp/hook.py::handle`) — all with `{"decision": "block", "reason": …}`, all inert outside a
|
|
285
297
|
[root](glossary.md#root) and under the `verify-off` [kill switch](glossary.md#kill-switch)
|
|
286
|
-
(`hooks/projects-stop.py::main`).
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
298
|
+
(`hooks/projects-stop.py::main`). Each of these hosts judges the reply after a block again.
|
|
299
|
+
The re-ask budget is the core's (`_record_only`, `hooks/tezgah_integrity.py::_record_only`): at
|
|
300
|
+
most `STOP_REASKS` = 2 refusals in one user turn, and never two for one reason class. The count
|
|
301
|
+
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. A
|
|
303
|
+
refusal row stores the key of its text as `reask`. A `turn` row with that key starts no new user
|
|
304
|
+
turn for the count (`_turn_refusals`, `hooks/tezgah_integrity.py::_turn_refusals`). The omp host
|
|
305
|
+
caps the continuations it starts itself, but never a chain of blocks, so tezgah's count bounds that
|
|
306
|
+
chain. So a reply that fixes the first class but adds a new one gets one more refusal. A blocker
|
|
307
|
+
list with no evidence is such a new class. A reply after a block that is not refused writes one
|
|
308
|
+
`after_block` row. A host may say a block came before (`stop_hook_active`, Cursor's `loop_count`)
|
|
309
|
+
while the turn holds no refusal of this rule. That reply gets no refusal and no row, since another
|
|
310
|
+
Stop hook may have blocked. The host's count rules only where the ledger cannot. A session with no
|
|
311
|
+
`turn` row allows one refusal per chain. A Cursor `loop_count` of 2 ends the chain. The opencode
|
|
312
|
+
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. Twelve triggers, in order, each naming its reason class (`_stop_block`
|
|
290
314
|
`hooks/tezgah_integrity.py::_stop_block`).
|
|
291
315
|
The first four judge how the reply is written (`_shape_block`, `hooks/tezgah_integrity.py::_shape_block`); the rest judge its evidence:
|
|
292
316
|
|
|
293
317
|
1. **placating opener** — the reply opens by agreeing or apologising (`hooks/tezgah_integrity.py::SYCOPHANT`).
|
|
294
318
|
2. **forbidden closer** — the reply's last prose line is one of the sign-offs the output contract bans (`CLOSER`, `hooks/tezgah_integrity.py`): the same rule read at the other end, with its own class so the ledger says which end fired.
|
|
295
319
|
3. **list cap** — a contiguous list runs past `LIST_CAP` = 5 items (rule 8 of the `i-have-adhd` skill). `longest_list` (`hooks/tezgah_integrity.py::longest_list`) counts per run of column-0 items of one kind: a heading, a prose line, a table row, a fence, a marker switch or a numbered list restarting at 1 ends the run, and blank or indented continuation lines keep it. Two headed groups of four pass; a 5+ enumeration that must stay whole goes under headings or into a table, which the block text says.
|
|
296
|
-
4. **reply language** — the reply's prose is not
|
|
320
|
+
4. **reply language** — the reply's prose is not in the configured language. That means at least `LANG_MIN_WORDS` = 25 prose words, under `LANG_MIN_SHARE` = 6% of them marked as that language. `prose_words` drops fences, table rows, inline code, URLs, paths and identifiers first; `turkish_share` counted a word with a Turkish letter or one of `TR_WORDS`. `reply_share` counts the language pack's marker letters and words now (`hooks/tezgah_integrity.py::reply_share`). Only `tr` ships markers. They no longer hold de, da, en, ne, ya, her or mi, the words Spanish, French and Dutch share with Turkish.
|
|
297
321
|
|
|
298
322
|
The four shape classes share their switches with the text they enforce: `adhd-off` or a repo's `.no-adhd` lifts 1–3 (`_adhd_armed`, `hooks/tezgah_integrity.py::_adhd_armed`), `exec-mode.off` lifts 4, and a session whose environment carries `TEZGAH_NESTED` (an agent CLI consult started, whose English answer is read by code) is not judged for shape at all. A subagent's reply is never refused: omp's `session_stop` does not fire for task sessions, and a Claude SubagentStop or Cursor subagentStop leaves a record-only `subagent_end` row judged on the evidence half alone (ADR 011). The thresholds were set on the owner's omp transcripts (2026-09-27: 1,495 assistant replies with at least 25 prose words): English prose scored 0.000–0.026, the most English-heavy Turkish reply 0.077 and the bulk 0.3–0.7, and 24 of the 1,495 fall under the cut, all English or Chinese prose. The trade-off is on the Turkish side: a Turkish reply that is mostly quoted English outside backticks can fall under 6%, and the block text tells the model to put the English in backticks or a fence. `tests/test_integrity.py` `ReplyShapeCorpus` pins which realistic replies pass and which block. A lead that announces what follows (`preamble-open`) stays report-only: "Sonuç:" over a list is an answer label and reads like a preamble to any regex.
|
|
299
323
|
|
|
@@ -368,7 +392,7 @@ The four shape classes share their switches with the text they enforce: `adhd-of
|
|
|
368
392
|
about what a registry publishes. The read is the evidence, and it is taken by the row's command
|
|
369
393
|
and not by `passing_check` — `npm view` exiting non-zero is the answer for "this version is
|
|
370
394
|
missing" (`hooks/tezgah_integrity.py::_external_read_row`) — so a turn whose only work was the read ends. It is
|
|
371
|
-
last of the
|
|
395
|
+
last of the evidence classes by the branch order above and in its strongest form: it is asked only where the
|
|
372
396
|
fold would otherwise let the turn end, so it turns an allow into a refusal and never changes the
|
|
373
397
|
class another branch refused the same turn under. It is the one class a turn with no work in it
|
|
374
398
|
can make, which is why the "no work, no claim word" exit exempts it (`hooks/tezgah_integrity.py::_stop_block`) — the two turns it
|
|
@@ -377,6 +401,121 @@ The four shape classes share their switches with the text they enforce: `adhd-of
|
|
|
377
401
|
fold returned `(None, None)` over both and neither was judged at all. Measured over this machine's
|
|
378
402
|
own 2,092 final assistant replies, 2 are read as claims, both in review text about someone else's
|
|
379
403
|
tooling.
|
|
404
|
+
11. **residual work** — a turn that did work ends on a list of what is left. Any step row counts as
|
|
405
|
+
work, a prose edit included. The list starts at a line that opens with a residual label. That
|
|
406
|
+
is a markdown heading, a bold or italic lead, or a few words before a colon. A line of up to four
|
|
407
|
+
words right over a list needs no colon. Up to three words, a quote marker or an emoji may lead it.
|
|
408
|
+
Examples are "Açık kalanlar", "Doğrulanmadı:", "Sıradaki adım", "Bekleyen tek iş", "Kapsam
|
|
409
|
+
dışı", "TODO" and "Next steps". First-person negatives ("Yapamadım", "Test etmedim"), "Yarım
|
|
410
|
+
kalanlar", "Henüz kontrol edilmeyen", "Unresolved" and "Still open" count too (plan 086, S4).
|
|
411
|
+
`hooks/tezgah_integrity.py::RESIDUAL_LABEL` holds the words, and
|
|
412
|
+
`hooks/tezgah_integrity.py::residual_sections` reads them. "Kalan" is also an adjective, as in
|
|
413
|
+
"Kalan riskler". It opens a list only as the first word, on the work it names: "Kalan:", "Kalan
|
|
414
|
+
iş", "Kalan adımlar" (`hooks/tezgah_integrity.py::RESIDUAL_FIRST`). A heading holds the lines up
|
|
415
|
+
to the next heading of its level, or up to prose after a blank line. Some labels start no list.
|
|
416
|
+
Their own words close it ("Düzeltilen eksik", "Doğrulanmayan kalmadı"), or a few words say that
|
|
417
|
+
nothing is left ("Eksik yok"). "Doğrulanmadı, kanıt yok" still starts one. The class refuses an item that nothing accounts
|
|
418
|
+
for (`hooks/tezgah_integrity.py::unblocked_items`). Two things do. One is a capitalised KARAR or
|
|
419
|
+
GERİ ALINAMAZ token outside code, a Turkish suffix allowed and a negation not: a choice or a
|
|
420
|
+
consent leaves no row to check. A KARAR item also names its options or a recommended default
|
|
421
|
+
(`hooks/tezgah_integrity.py::OPTIONS_MARK`, `hooks/tezgah_integrity.py::DEFAULT_MARK`): a
|
|
422
|
+
choice with neither is one the agent can make (plan 086, S3). The other is a capitalised YETKİ or ORTAM token with its
|
|
423
|
+
evidence (`hooks/tezgah_integrity.py::_tried_holds`). The item names an error of the type's
|
|
424
|
+
kind. For ORTAM that is a missing tool, file, module, setting or quota. For YETKİ it is a denied
|
|
425
|
+
permission, a failed login or a 401/403. The item also quotes, in backticks or a fenced line, a
|
|
426
|
+
command that failed in the turn. A quote cut with "…" counts when its words open that command.
|
|
427
|
+
A grep alone that matched nothing and a failed write tool call are no failed command
|
|
428
|
+
(`hooks/tezgah_integrity.py::_failed_commands`). For an item that names a subagent, an agent, a
|
|
429
|
+
provider or a model, a failed spawn call can stand in for the quote. Its row names a provider
|
|
430
|
+
failure (`hooks/tezgah_integrity.py::_spawn_failed`). omp sends a spawn's outcome as a
|
|
431
|
+
flag with no text. So its row carries no class, and the rule refuses such an item there.
|
|
432
|
+
A YETKİ or ORTAM item that quotes a model provider's quota or rate limit is refused while
|
|
433
|
+
another family is up that the routing lets a chain fall back to. The item names a family by
|
|
434
|
+
its provider id or model stem, or the turn's spawn failed at the provider. The words "agent"
|
|
435
|
+
and "provider" alone tie nothing, so a `user-agent` or a registry's rate limit stays a blocker.
|
|
436
|
+
The journal first closes ended omp children, so a child's limit already holds its family
|
|
437
|
+
(`hooks/tezgah_integrity.py::_settle_slices`). The refusal names the family to re-dispatch to
|
|
438
|
+
(`hooks/tezgah_integrity.py::_limit_move`), never one the item names.
|
|
439
|
+
Neither the type nor the quote counts alone. "Doğrulanmadı: `pytest` ..." with no type is still
|
|
440
|
+
refused, and so is "ORTAM" with no quote. The rule refuses "ORTAM: `pytest` AssertionError" too,
|
|
441
|
+
because an assertion is work, not a missing tool. "YETKİ yok" is the type, not its negation. Only
|
|
442
|
+
"değil" negates it. A fragment of a command does not count. A type in the heading covers the
|
|
443
|
+
items under it. "Engeller: yok" and an empty "Engeller" heading hold no item. An item under a
|
|
444
|
+
blocker label that reports the blocker gone holds none either. It has a sentence in the past,
|
|
445
|
+
then a last sentence that opens on "Artık" with a present verb ("… yoktu. Artık var."). A
|
|
446
|
+
negative past ("güncellenmedi") or a last sentence with a residual word or a future keeps it
|
|
447
|
+
open (`hooks/tezgah_integrity.py::_now_closed`). The refusal names the blocker tokens of the
|
|
448
|
+
configured language. Under `tr` it opens with "Bu maddeleri şimdi yap veya engel türü ve
|
|
449
|
+
kanıtla yaz.", and under `en` it opens on the English instruction. It also says that a step
|
|
450
|
+
that cannot be undone never clears the list.
|
|
451
|
+
`_stop_block` asks this class at every exit that would end the turn
|
|
452
|
+
(`hooks/tezgah_integrity.py::_residual_block`). So it never takes a turn that another class
|
|
453
|
+
refused. A turn with no step, a ledger with no turn marker and a nested session skip it. A
|
|
454
|
+
subagent's end is never refused (ADR 011). Its `subagent_end` row carries the count of its
|
|
455
|
+
untyped items as `residual`. The parent's Stop then refuses a reply that types no item of its
|
|
456
|
+
own. Claude and Cursor write that row from their subagent-stop events. omp fires none for a
|
|
457
|
+
task session. Its bridge sends the child's last reply from `agent_end`. The row lands in the
|
|
458
|
+
ledger of the session that spawned the child, read from the child's own session header, and
|
|
459
|
+
counts once per child. Codex, opencode and dsh write no such row. On omp the reply after a
|
|
460
|
+
refusal sits in a turn of its own.
|
|
461
|
+
`hooks/tezgah_integrity.py::_turn_refusals` hands the class the rows of every turn in that
|
|
462
|
+
re-ask chain, so a failure quoted from the refused turn
|
|
463
|
+
counts. On Cursor, whose store keeps a long reply's head and tail, the sections of the
|
|
464
|
+
cut middle are kept between them (`hooks/tezgah_integrity.py::residual_text`). The words leave
|
|
465
|
+
out the labels other rules ask for: a spec's assumptions and alternatives, a measurement's
|
|
466
|
+
limits, risks and a bare recommendation. Suggested improvements are work left and stay in.
|
|
467
|
+
Before the class, 503 of 1876 final replies carried a residual label. They came from the owner's
|
|
468
|
+
omp and Claude transcripts, and 467 of them from turns that called a non-read tool. The word list
|
|
469
|
+
is precise rather than complete. A doable item typed KARAR with options still passes.
|
|
470
|
+
`tezgah-gate replay` counts the replies that end on a question to watch for that, beside the
|
|
471
|
+
residual sections per turn ([the replay corpus](#the-replay-corpus)). A residual refusal counts in `blocked_claims`
|
|
472
|
+
but not as a false completion: a check may back the claim beside the list.
|
|
473
|
+
12. **ready steps** — a turn that did work ends while the newest todo list still holds a ready
|
|
474
|
+
step. The host's todo list is the orchestrator's step queue, and a todo call leaves a `todo` row
|
|
475
|
+
(`hooks/tezgah_integrity.py::note_todo`). A `pending` item is ready. Someone holds an
|
|
476
|
+
`in_progress` item. An item omp marks `blocked`, or whose own text types a blocker, waits on
|
|
477
|
+
something named. Done items leave the list. The row keeps every ready head in `heads`. The
|
|
478
|
+
class reads the newest `todo` row of the session's own list. A subagent's row carries its
|
|
479
|
+
`agent`, and the class skips it (`hooks/tezgah_integrity.py::newest_todo`). The row may come from an
|
|
480
|
+
earlier turn on purpose: the owner asked that unfinished steps carry over to the next turn
|
|
481
|
+
(`hooks/tezgah_integrity.py::ready_left`). It scans the rows back by their serialized shape and
|
|
482
|
+
parses them until the newest parent row. An open slice of this session takes its step off the
|
|
483
|
+
ready list (`hooks/tezgah_integrity.py::ready_heads`). The slice's id names the head, or the
|
|
484
|
+
slice's own `task` holds the whole head as words. An omp batch's shared context does not count.
|
|
485
|
+
On omp, `todo start` puts every other started item
|
|
486
|
+
back to pending, so a running slice's item would read as ready without this. A ready item
|
|
487
|
+
passes when one line of the reply names its first 24 characters, inline code kept, beside a
|
|
488
|
+
blocker token (any pack's spelling: DECISION, ACCESS, IRREVERSIBLE, ENV, or KARAR, YETKİ, GERİ
|
|
489
|
+
ALINAMAZ, ORTAM) outside code. Under `tr` the refusal opens with "Hazır adımlar bekliyor." It
|
|
490
|
+
names up to three heads and tells the turn to dispatch them or mark them blocked.
|
|
491
|
+
`_residual_block` asks it after `residual work` and before
|
|
492
|
+
`stopped slice`. The scope matches `residual work`. A turn with no step skips it. So do a
|
|
493
|
+
ledger with no turn marker, a subagent's end, a nested session and `orchestrate-off`. The rule
|
|
494
|
+
refuses it once per turn (`hooks/tezgah_integrity.py::_record_only`). It counts in
|
|
495
|
+
`blocked_claims` but not as a false completion (`hooks/tezgah_integrity.py::LIST_BLOCKS`).
|
|
496
|
+
The research line `orchestrator-flow-audit` measured the gap before the class. Of 451 omp user
|
|
497
|
+
turns, 13.1% were a bare "devam". Of the 98 continue or status prompts, 18 came after a reply
|
|
498
|
+
that handed back a step the agent could take. On omp, `wait` gets a warning, never a refusal
|
|
499
|
+
(`hooks/tezgah_gate.py::ready_advice`). The warning needs a spawn with nothing after it but
|
|
500
|
+
waits and todo updates, and a ready item no running slice names. It rides back with the wait's
|
|
501
|
+
result.
|
|
502
|
+
13. **needless ask** — the reply ends on a question the agent can decide (plan 086, S3). The
|
|
503
|
+
last line asks: it ends on "?" or a pack's question particle
|
|
504
|
+
(`hooks/tezgah_integrity.py::QUESTION_END`). Its paragraph names no recommended default, and
|
|
505
|
+
it names no step the ask policy keeps from a default (`hooks/tezgah_integrity.py::never_default`).
|
|
506
|
+
Such steps are a force push, a delete, production, a credential, a third party, or a spend
|
|
507
|
+
over `spend_cap`. The class then refuses a process question: the packs' `ask.process`
|
|
508
|
+
shapes, such as "Başlayayım mı?", "shall I proceed?" and "which first?"
|
|
509
|
+
(`hooks/tezgah_integrity.py::PROCESS_ASK`). It also refuses a question about an outward
|
|
510
|
+
step the user granted this session (`hooks/tezgah_integrity.py::grants_in_force`). A question
|
|
511
|
+
about an outward step the user has not granted is the one the policy asks, so it passes. The
|
|
512
|
+
refusal says to take the step, or to move it into the approval batch with a default. Under
|
|
513
|
+
`tr` it opens with "Bu soruyu sorma". `_residual_block` asks it after `residual work` and
|
|
514
|
+
before `ready steps` (`hooks/tezgah_integrity.py::needless_ask`). Unlike those two, it needs
|
|
515
|
+
no work in the turn, because a plan proposal that ends "Başlayayım mı?" is its measured case.
|
|
516
|
+
The research line counted 33 process questions among 48 question endings in the owner's omp
|
|
517
|
+
transcripts. It refuses once per turn and counts like `residual work`
|
|
518
|
+
(`hooks/tezgah_integrity.py::LIST_BLOCKS`).
|
|
380
519
|
|
|
381
520
|
Classes 5–10 also exist as a past-time temporal spec (`hooks/tezgah_stopspec.py::FORMULAS`,
|
|
382
521
|
plan 063). Seven formulas read ten row atoms (`ROW_ATOMS`) and the turn marker. The module compiles
|
|
@@ -386,7 +525,8 @@ fold, inside its own `try`, when the reply leaves a claim or a refusal row. It w
|
|
|
386
525
|
per reply. The row says `agree`, `disagree` or `error`. It also says `fold` when the selector reached
|
|
387
526
|
the fold and `selector` when it decided alone. The verdict stays the imperative one. Both folds share one selector,
|
|
388
527
|
`_stop_block`, through its `fold` argument. It picks the rows, runs the lost-began path and
|
|
389
|
-
refuses "evidence tampered".
|
|
528
|
+
refuses "evidence tampered". The admission below and class 11 belong to the selector too. The
|
|
529
|
+
spec's verdict passes through them. `ESCAPES` names the three predicates that read beyond one row: the
|
|
390
530
|
began-row join, the repository binding and the lost-began set. Plan 063 fixed that list before
|
|
391
531
|
the first shadow row. `TEZGAH_STOPSPEC_STRICT=1` makes a disagreement raise, for the tests.
|
|
392
532
|
`tests/stop_spec_eval.py` measures the bet: agreement on plan 055's replayed Stop events and on
|
|
@@ -403,6 +543,13 @@ whole session instead of the turn:
|
|
|
403
543
|
A session that did no work at all still ends, and a turn that ran its own passing check is
|
|
404
544
|
judged by the turn fold alone. Before this, the claim could be moved to the turn
|
|
405
545
|
after the work, where the turn fold saw nothing to judge (audit M-2, INT-01).
|
|
546
|
+
- **A turn of reads.** The rule treats a turn of known reads as a turn with no work (plan 086,
|
|
547
|
+
S4). Each command in it is a known read: a program in `READ_ONLY_PROGRAMS` or a git read in
|
|
548
|
+
`GIT_READS`, with no option in `WRITING_OPTIONS`. `sed`, `awk`, `uniq`, `gh` and `codegraph`
|
|
549
|
+
count with no writing script, one operand or a read verb. A `sqlite3` read-only open counts,
|
|
550
|
+
from the option or the database URI, and so does an `ssh` whose remote command is
|
|
551
|
+
such a read (`hooks/tezgah_integrity.py::_bookkeeping_command`). Any other command, a commit included,
|
|
552
|
+
keeps the turn worked and owes the check.
|
|
406
553
|
- **A bookkeeping turn after a pass.** A turn whose only rows are read-only or VCS-bookkeeping
|
|
407
554
|
commands ends without a fresh check, as long as nothing but bookkeeping ran after the session's
|
|
408
555
|
newest pass, the session fold above allows it, and the reply makes no external-state claim
|
|
@@ -425,8 +572,21 @@ a fresh screen proof stands in the same
|
|
|
425
572
|
place as a unit pass; when that write was a UI source, the passing row has to be a UI proof — a
|
|
426
573
|
check that renders, or a read of the screen — and when it was a component, the design check has to be
|
|
427
574
|
newer than that write as well. An explicit admission (`doğrulanmadı`,
|
|
428
|
-
`unverified`, `not verified`, `couldn't verify`; `hooks/tezgah_integrity.py::NEGATED`) clears
|
|
429
|
-
|
|
575
|
+
`unverified`, `not verified`, `couldn't verify`; `hooks/tezgah_integrity.py::NEGATED`) clears a
|
|
576
|
+
class whose repair is a check (`ADMITTED_CLASSES`). Those are `check failed`, `partial failure`,
|
|
577
|
+
`stale evidence` and `no verify_ok`. It clears them only when a check ran and failed after the
|
|
578
|
+
turn's newest change (`hooks/tezgah_integrity.py::_failed_since_change`, applied by
|
|
579
|
+
`hooks/tezgah_integrity.py::_admitted`). Until plan 080 the word alone cleared every evidence class.
|
|
580
|
+
Writing it was cheaper than verifying. The word is read on the reply's prose, so a test selector
|
|
581
|
+
in code is no admission. After it clears the failure, the fold runs again without the failed
|
|
582
|
+
checks. A screen or design proof the turn still owes comes back as `no ui_ok`, and an external
|
|
583
|
+
claim with no read comes back as `no external read`. Otherwise a failed unit check would end a
|
|
584
|
+
turn that its passing twin cannot. One failure is that proof's own attempt. When the check that
|
|
585
|
+
failed is the one that sees the screen, after the newest UI write, the word clears `no ui_ok` too
|
|
586
|
+
(`hooks/tezgah_integrity.py::_proof_attempt_failed`). That covers the design check as well, which
|
|
587
|
+
measures the screen that did not render. A failed design check does the same for a component with
|
|
588
|
+
a proven screen. The selector refuses `evidence tampered` first, because the
|
|
589
|
+
admission now rests on the ledger's own rows. The block text is the string the
|
|
430
590
|
refusing branch returns; it names the failed
|
|
431
591
|
command (`hooks/tezgah_integrity.py::_failed_check`) and tells the model to report the failure with its exact error
|
|
432
592
|
line, or fix it and re-run.
|
|
@@ -480,9 +640,9 @@ therefore invisible to the line and its legend - no mark's measure is spelled th
|
|
|
480
640
|
line never asked. Three surfaces recognise a read, and they have to agree on the two forms
|
|
481
641
|
(`skills/<name>/SKILL.md` and `skill://<name>`) and on which name is shipped: the Python hook omp's
|
|
482
642
|
bridge calls (`hosts/omp/hook.py::classify`), omp's own filter, which decides whether python hears about the
|
|
483
|
-
read at all (`hosts/omp/tezgah-hook.ts.in:
|
|
643
|
+
read at all (`hosts/omp/tezgah-hook.ts.in:227-264`, its catalogue the checkout `@HOOK@` names), and
|
|
484
644
|
opencode's plugin, which classifies in process because that host has no Python hook
|
|
485
|
-
(`hosts/opencode/plugins/tezgah.js:
|
|
645
|
+
(`hosts/opencode/plugins/tezgah.js:2185-2242`, its catalogue the host's own installed skill dir
|
|
486
646
|
(`dirname(CONFIG)/opencode/skills`), which `tezgah-setup` fills with every shipped skill).
|
|
487
647
|
|
|
488
648
|
Claude loads a skill through its `Skill` tool, never a read, so a Claude load used to leave no row.
|
|
@@ -503,8 +663,8 @@ as having opened a skill when it recorded that skill's kind, so reads recorded b
|
|
|
503
663
|
still count through the two marks; and only the hosts that classify a read in process record one
|
|
504
664
|
here, so the count is a floor, not a census. Which host records what, since this store is the
|
|
505
665
|
report's only source: **omp** (the extension's filter lets the read through,
|
|
506
|
-
`hosts/omp/tezgah-hook.ts.in:
|
|
507
|
-
**opencode** (the plugin classifies it, `hosts/opencode/plugins/tezgah.js:
|
|
666
|
+
`hosts/omp/tezgah-hook.ts.in:257-264`, and the hook classifies it, `hosts/omp/hook.py::classify`) and
|
|
667
|
+
**opencode** (the plugin classifies it, `hosts/opencode/plugins/tezgah.js:2227-2242`) record the mark
|
|
508
668
|
or `skill:<name>` for every shipped skill, in either form; **Claude** sees a read, but only in its
|
|
509
669
|
transcript, which feeds the status line and writes no row here (`statusline.py::claude_used`), so a Claude
|
|
510
670
|
session reads in this report as one that opened nothing - marks included; **codex**, **cursor** and
|
|
@@ -621,7 +781,7 @@ empty stand-in for the result body its bridge never sends, so a subagent's repor
|
|
|
621
781
|
cannot import the core in process and mirrors the control in JavaScript instead — the channel on the
|
|
622
782
|
call's own row, the label and the taint notice in front of the result the hook is handed, including
|
|
623
783
|
the `external` row an MCP answer or a fetched page earns (`untrustedSource`
|
|
624
|
-
`hosts/opencode/plugins/tezgah.js:
|
|
784
|
+
`hosts/opencode/plugins/tezgah.js:1942`, `labelResult` `:1993`, with the tier's argv reader
|
|
625
785
|
`tierRead` at `:1902`). The two halves are pinned against each other over a shared corpus, so neither
|
|
626
786
|
can move without failing the other's test (`tests/test_opencode_plugin.py::OpenCodePlugin.test_the_classifier_agrees_with_the_python_half_on_a_shared_corpus`). One difference is deliberate:
|
|
627
787
|
opencode's plugin writes that `external` row itself and carries the channel alone, since the UI rule
|
|
@@ -648,14 +808,16 @@ Each key, as both readers produce it:
|
|
|
648
808
|
- `tool_error_rate` — non-zero `exit` values over every row carrying an `exit` (`hooks/tezgah_integrity.py::_counts`);
|
|
649
809
|
a host reporting no outcome contributes to neither half, so every row with an `exit` counts.
|
|
650
810
|
- `claims` and `false_completion` — the `claim` rows, which are replies in the claim vocabulary,
|
|
651
|
-
and those whose detail starts with `blocked` (`hooks/tezgah_integrity.py::_counts`).
|
|
652
|
-
the exception. A reply
|
|
653
|
-
as `shape_blocked` instead.
|
|
811
|
+
and those whose detail starts with `blocked` (`hooks/tezgah_integrity.py::_counts`). Two classes
|
|
812
|
+
are the exception. A shape class (`SHAPE_BLOCKS`) refuses a reply for its list or its language,
|
|
813
|
+
which made no false claim, so it counts as `shape_blocked` instead. `residual work`
|
|
814
|
+
(`LIST_BLOCKS`) refuses a reply for the work it leaves, not for its claim.
|
|
654
815
|
- `refusals` — the `refusal` rows: refused replies that claimed nothing, work-only or shape. A
|
|
655
816
|
shape one also counts in `shape_blocked`. Before row version 3 these were `claim` rows, so a
|
|
656
817
|
corpus folded across the boundary counts them in `claims`.
|
|
657
|
-
- `blocked_claims` —
|
|
818
|
+
- `blocked_claims` — the refused claims split by their Stop class, the text after `blocked: `
|
|
658
819
|
(`no verify_ok`, `stale evidence`, `no external read`, …), so each class has its own count.
|
|
820
|
+
Every class but `residual work` (`LIST_BLOCKS`) is also a `false_completion`.
|
|
659
821
|
- `replies` and `shape` — the `shape` rows, written for every judged reply, and those carrying a
|
|
660
822
|
report-only flag (`hooks/tezgah_integrity.py::_counts`).
|
|
661
823
|
- `denies` — `deny` rows grouped by the text before the first colon (`hooks/tezgah_integrity.py::_counts`), which is the rule
|
|
@@ -702,7 +864,7 @@ disagree with the number it is drawn from; both are off unless the flag asks, so
|
|
|
702
864
|
name at all - `classify` folded it into a kind - so an old corpus under-counts, which is the
|
|
703
865
|
honest ceiling and the reason the field exists; every row written since carries it, because both
|
|
704
866
|
writers write it (`hooks/tezgah_integrity.py::note_tool`, and opencode's plugin
|
|
705
|
-
`hosts/opencode/plugins/tezgah.js:
|
|
867
|
+
`hosts/opencode/plugins/tezgah.js:1253-1264` - before that second one did, an opencode work row was
|
|
706
868
|
the other half of this under-count, with its name unrecoverable in the command `detail`). A read
|
|
707
869
|
tool is not in it either: reads record no row by design.
|
|
708
870
|
- **The shipped programs** — `programs`, the `bin/` programs a command row really ran, read with the
|
|
@@ -763,6 +925,16 @@ only, and never leaves the machine. The run's summary is its row of the cache's
|
|
|
763
925
|
- **Denial budget** (`tezgah_shapes.deny_runs`) = per rule, runs of 3 or more consecutive denies
|
|
764
926
|
inside one turn, and sessions with 20 or more denies. These are Claude Code auto mode's
|
|
765
927
|
thresholds, not a tezgah measurement.
|
|
928
|
+
- **Ask policy** (`hooks/tezgah_replay.py::residual_turns`, plan 086 S3) = four counts over the
|
|
929
|
+
top-level omp and Claude transcripts, beside the residual sections. `process_questions` counts
|
|
930
|
+
final replies that the `needless ask` class reads as a process question. `cheap_answers` counts
|
|
931
|
+
prompts that only approve ("devam", "evet", "go on") right after a reply that ended on a
|
|
932
|
+
question. `asks_without_default` counts ask-tool questions with no recommended option.
|
|
933
|
+
`outward_without_grant` counts turns that ran `git push`, a package publish or a `gh` release
|
|
934
|
+
or PR write. Such a turn counts only when no earlier prompt of the session granted or named the
|
|
935
|
+
step. The transcripts
|
|
936
|
+
hold no grant rows, so the first count misses a re-ask after a grant. One outward turn without
|
|
937
|
+
a grant per 100 work turns over 14 days is the trigger for an outward-action gate (S3's V3).
|
|
766
938
|
|
|
767
939
|
`replay --sheet` draws the blind label sample with a fixed seed. It fills per-stratum quotas,
|
|
768
940
|
shuffles them, and writes `tezgah-taste measure`'s `{set, n, text}` format. No item shows its
|
|
@@ -841,8 +1013,9 @@ matched NEGATED and read as an admission:
|
|
|
841
1013
|
The reader measures and does not decide. `claims` feeds only the benchmarks'
|
|
842
1014
|
false-done counts (`benchmarks/arm-bench/bench.py::false_completion`,
|
|
843
1015
|
`benchmarks/arm-bench/swe.py`), never the Stop decision. The Stop verdict and
|
|
844
|
-
its `claim` row read the plain DONE/VERIFIED vocabulary
|
|
845
|
-
|
|
1016
|
+
its `claim` row read the plain DONE/VERIFIED vocabulary. Their admission reads
|
|
1017
|
+
NEGATED after a check that failed since the newest change (plan 080). So the
|
|
1018
|
+
reader's change moved no refusal. NEGATED and ADMITTED match anywhere in the reply,
|
|
846
1019
|
so a done claim beside an unrelated "no contradictions" or "never pass secrets"
|
|
847
1020
|
is not counted. A false-done count taken before commit 9cb4d1d is not
|
|
848
1021
|
comparable with one taken after it.
|
package/docs/feature-audit.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# Feature audit: one feature, every layer
|
|
2
2
|
|
|
3
3
|
Audience: the agent and the maintainer answering "why does this screen do that"
|
|
4
|
-
or "make this feature better" on an existing product surface.
|
|
4
|
+
or "make this feature better" on an existing product surface. It also serves
|
|
5
|
+
whoever specifies a feature before the build.
|
|
5
6
|
|
|
6
7
|
A screen review judges what is on the screen. Most of what goes wrong in a real
|
|
7
8
|
feature is not on the screen: it is a disagreement between two layers - the surface
|
|
@@ -20,8 +21,8 @@ sourced to WAI-ARIA APG and WCAG 2.2.
|
|
|
20
21
|
It is reached the same way `product-analysis` is: the `product` task class arms the
|
|
21
22
|
rule (`hooks/tezgah_context.py::PROMPT_HINTS`), whose coherence paragraph
|
|
22
23
|
(`hooks/tezgah_policy.py::CORE`) names the feature unit and the matrices, and
|
|
23
|
-
`skills/product-analysis/SKILL.md:
|
|
24
|
-
`skills/tezgah-contract/SKILL.md:
|
|
24
|
+
`skills/product-analysis/SKILL.md:74` runs the coherence pass before its axes.
|
|
25
|
+
`skills/tezgah-contract/SKILL.md:738-750` carries the long form for a session that
|
|
25
26
|
reads the contract.
|
|
26
27
|
|
|
27
28
|
The unit is the feature, not the screen, and every interface element and every data
|
|
@@ -38,6 +39,24 @@ step flow, a notification. It is not a form-only method.
|
|
|
38
39
|
| Interaction dependency | the controls whose change alters another's valid set | dependents, required action on change, state owner, announcement | a stale dependent value; a silent reset; a filter showing a state the view no longer has |
|
|
39
40
|
| Surface pattern | the controls, data views, flows and notifications | the decidable rule, with its source | a combobox with no expanded state; a results count announced nowhere; a destructive confirmation that hides what it destroys |
|
|
40
41
|
|
|
42
|
+
## Forward mode: the spec before the build
|
|
43
|
+
|
|
44
|
+
For a feature that does not exist yet, fill the five matrices from the decision
|
|
45
|
+
first. They go into `.tezgah/analysis/<feature>/spec.md` before the build. Mark a
|
|
46
|
+
cell the decision leaves open, and ask it. Never guess it.
|
|
47
|
+
|
|
48
|
+
After the build, run the normal audit and compare it with `spec.md` row by row.
|
|
49
|
+
Every disagreement is a finding. Examples: a capability no layer delivers, a field
|
|
50
|
+
no view renders, a missing state, an unasked layer. The method lives
|
|
51
|
+
in `skills/feature-audit/SKILL.md`, "Forward mode".
|
|
52
|
+
|
|
53
|
+
The decision the spec builds from is the one written under `## decision` in
|
|
54
|
+
`.tezgah/analysis/<topic>/flow.md`. `spec.md` names it, and so does the audit
|
|
55
|
+
after the build. The `product-flow` skill orders the steps around the audit:
|
|
56
|
+
`tezgah-research next --topic <t>` reads the forward spec as done once `spec.md`
|
|
57
|
+
exists (`hooks/tezgah_product.py::next_steps`). See "The next step of a product
|
|
58
|
+
flow" in `docs/research.md`.
|
|
59
|
+
|
|
41
60
|
## The gate that makes it more than advice
|
|
42
61
|
|
|
43
62
|
A finding whose fix names a layer that does not exist yet is not a UI finding. It
|
|
@@ -128,7 +147,7 @@ rubric, then three passes of the feature-audit skill:
|
|
|
128
147
|
## Source of truth
|
|
129
148
|
|
|
130
149
|
- `skills/feature-audit/SKILL.md` - the method: the five matrices, the proposal
|
|
131
|
-
gate, the per-surface rule table and the hand-check list
|
|
150
|
+
gate, the forward-mode spec, the per-surface rule table and the hand-check list
|
|
132
151
|
- `skills/product-analysis/SKILL.md` - the coherence pass and the artifact element
|
|
133
152
|
that carries the matrices
|
|
134
153
|
- `hooks/tezgah_context.py` - `PROMPT_HINTS`, whose `product` alternation carries
|
|
@@ -137,3 +156,5 @@ rubric, then three passes of the feature-audit skill:
|
|
|
137
156
|
`skills/tezgah-contract/SKILL.md` as its long form
|
|
138
157
|
- `tests/test_context.py`, `tests/test_setup.py` - the arming table and the router
|
|
139
158
|
line that pin the wiring
|
|
159
|
+
- `skills/product-flow/SKILL.md` and `hooks/tezgah_product.py::FLOWS` - the step
|
|
160
|
+
order around the forward spec and the audit, printed by `tezgah-research next`
|