@r1z4x/tezgah 1.8.2 → 1.9.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 +48 -1
- package/MANIFEST +6 -0
- package/bin/tezgah-docs +5 -4
- package/bin/tezgah-optimize +338 -0
- package/bin/tezgah-research +2 -2
- package/bin/tezgah-route +6 -2
- package/bin/tezgah-setup +15 -1
- package/bin/tezgah-task +239 -27
- package/bin/tezgah-taste +3 -1
- package/docs/architecture.md +11 -0
- package/docs/clarity-baseline.json +1 -1
- package/docs/environment.md +1 -1
- package/docs/evidence.md +23 -20
- package/docs/gate.md +19 -14
- package/docs/models.md +9 -2
- package/docs/operations.md +9 -5
- package/docs/testing.md +18 -14
- package/hooks/lang/en.json +3 -0
- package/hooks/lang/tr.json +3 -0
- package/hooks/tezgah_context.py +36 -20
- package/hooks/tezgah_gate.py +75 -36
- package/hooks/tezgah_integrity.py +90 -134
- package/hooks/tezgah_models.py +62 -26
- package/hooks/tezgah_paths.py +107 -23
- package/hooks/tezgah_policy.py +10 -4
- package/hooks/tezgah_research.py +14 -8
- package/hooks/tezgah_stopspec.py +36 -83
- package/hooks/tezgah_store.py +7 -0
- package/hooks/tezgah_task.py +104 -35
- package/hosts/opencode/plugins/tezgah.js +136 -0
- package/output-styles/tezgah.md +5 -2
- package/package.json +1 -1
- package/skills/plan-add/SKILL.md +8 -4
- package/skills/tezgah-contract/SKILL.md +14 -0
package/docs/gate.md
CHANGED
|
@@ -183,7 +183,7 @@ Trigger: a write tool, or a shell command that writes a file, while an active ta
|
|
|
183
183
|
against `hooks/tezgah_gate.py::SHELL_WRITE`); the allowlist refuses a target outside it (`hooks/tezgah_gate.py::TASK_SCOPE_DENY`) — a write tool's path, and a shell command's redirect or `tee` target (`hooks/tezgah_gate.py::write_paths`); and a write whose target IS the record, by a write tool or a shell redirect/`tee`, is refused
|
|
184
184
|
(`hooks/tezgah_gate.py::TASK_RECORD_DENY`) - the active task's record and any other open plan that carries a phase (`hooks/tezgah_task.py::phased_record`), matched without case and read against the main checkout, so a session in a worktree meets the same rule - because only `bin/tezgah-task` writes it and that CLI makes the checks a retyped frontmatter would skip. Told:
|
|
185
185
|
the move that lifts it, made by the session itself — `tezgah-task phase implementation` or `tezgah-task allow <glob> ... --why "<reason>"` — since the orchestrator owns the plan lifecycle and the CLI refuses
|
|
186
|
-
the move until the plan's Acceptance, spike and checkpoint are in order, and records the reason for a widened scope (`allowed_why:`). The record's body has its own writers, on any open plan, run from the main checkout or from any of its worktrees. `tezgah-task tick NNN <n> [--undo]` ticks Acceptance item n (`bin/tezgah-task::cmd_tick`). `tezgah-task note NNN state|next "<text>"` replaces the newest `## State` or the `## Next` body (`bin/tezgah-task::cmd_note`). `tezgah-task spike NNN "<answer>"` writes `spike_recorded:`, the answer `implementation` waits for (`bin/tezgah-task::cmd_spike`). None of them commits. Standing: only the record changing lifts it.
|
|
186
|
+
the move until the plan's Acceptance, spike and checkpoint are in order, and records the reason for a widened scope (`allowed_why:`). The record's body has its own writers, on any open plan, run from the main checkout or from any of its worktrees. `tezgah-task tick NNN <n> [--undo]` ticks Acceptance item n (`bin/tezgah-task::cmd_tick`). `tezgah-task note NNN state|next "<text>"` replaces the newest `## State` or the `## Next` body (`bin/tezgah-task::cmd_note`). `tezgah-task spike NNN "<answer>"` writes `spike_recorded:`, the answer `implementation` waits for (`bin/tezgah-task::cmd_spike`). `tezgah-task unmet <NNN> <n> "<reason, who accepted it>"` annotates Acceptance item n with `UNMET:`, the form `close NNN done` accepts in place of a ticked box (`bin/tezgah-task::cmd_unmet`). `tezgah-task sweep` makes one idempotent pass over what the last session left behind (`bin/tezgah-task::cmd_sweep`). It lists every pending slice as one line. It removes the landed worktrees and branches, and clears a `phase:` whose branch is gone. It prints the pending slices instead of closing them, because resuming one is the session's move. None of them commits. Standing: only the record changing lifts it.
|
|
187
187
|
Scratch meets no allowlist: a temp file or a device, such as `pytest > /tmp/check.log` (`hooks/tezgah_gate.py::task_reason`). Build output inside the repo stays held. So does a file in the repo or its worktrees when the checkout lives under the temp dir, as CI's does.
|
|
188
188
|
The task rule holds repository paths only. A target under no git checkout and no configured root, such as `~/.config/runpod/key`, is no plan's work (`hooks/tezgah_gate.py::_no_repo`). Neither the phase nor the allowlist refuses it, while every other rule, the secret rule among them, still reads that write. The gate reads a relative target in the call's own directory. Any doubt keeps the target held. A line that moves the directory stays held, however it spells the `cd`, `pushd` or `popd`. So does a line that reads `$PWD` back. A line that makes a link stays held: `ln`, `install`, `cp -s`, `cp -l`. So does a target whose parent directory does not exist yet. So does a file with a second hard link. Host agent and instruction files and the shell rc files stay held too, in any letter case (`hooks/tezgah_gate.py::HOME_HELD`). Under `~/.claude` those are `agents/**`, `hooks/**`, `commands/**`, `skills/**` and `CLAUDE.md`. Elsewhere they are `~/.omp/agent/**`, `~/.codex/AGENTS.md`, `~/.codex/prompts/**`, `~/.cursor/**` and `~/.config/opencode/**`. The rc files are `~/.zshrc`, `~/.zshenv`, `~/.zprofile`, `~/.zlogin`, `~/.bashrc`, `~/.bash_profile`, `~/.bash_login` and `~/.profile`. A target in another repository stays refused as outside this one.
|
|
189
189
|
Three targets are no project file, so neither the phase nor the allowlist refuses them (`hooks/tezgah_gate.py::unnamed_target`). A URI is one: a tool device (`xd://<tool>`), an agent message (`agent://X`) or a conflict resolution (`conflict://2`). A `file://` URI names a checkout file and stays held. A shell target with an unknown variable is another. `write_paths` first expands a variable from an assignment earlier on the line. So the allowlist holds `B=src; git diff > $B/x.patch` as `src/x.patch`. A variable the same line set to the checkout, such as `R=$(git rev-parse --show-toplevel)`, is read as the checkout (`hooks/tezgah_gate.py::known_tail`). The third is tezgah's own workspace under `.tezgah/`, such as research lines, lessons and briefs (`hooks/tezgah_gate.py::workspace_free`). The slice journal stays held. So does every shell write into `.tezgah/plans/open/`, because such a file could make itself the active task. A plan file stays held too when its text, once the write lands, carries a `phase:` line (`hooks/tezgah_gate.py::_text_after`). The gate judges the whole text, so two edits cannot each pass on their own. The record rule still refuses the active record there.
|
|
@@ -240,17 +240,22 @@ Three triggers, one per shape a credential takes on its way into a file (`hooks/
|
|
|
240
240
|
|
|
241
241
|
Standing, no escape hatch, and outside `verify-off`.
|
|
242
242
|
|
|
243
|
-
### Plan — a turn's third product file on
|
|
244
|
-
|
|
245
|
-
Trigger: a write tool, or a shell redirect/`tee` (`hooks/tezgah_gate.py::write_paths`), whose target is a product path, while the checkout holding that target is on
|
|
246
|
-
already written two other product files into the same checkout (`hooks/tezgah_gate.py::plan_reason`, `hooks/tezgah_gate.py::_branch`, `hooks/tezgah_gate.py::_checkout`,
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
243
|
+
### Plan — a turn's third product file on a branch no plan binds
|
|
244
|
+
|
|
245
|
+
Trigger: a write tool, or a shell redirect/`tee` (`hooks/tezgah_gate.py::write_paths`), whose target is a product path, while the checkout holding that target is on a branch that binds no open plan
|
|
246
|
+
and this turn has already written two other product files into the same checkout (`hooks/tezgah_gate.py::plan_reason`, `hooks/tezgah_gate.py::_branch`, `hooks/tezgah_gate.py::_checkout`,
|
|
247
|
+
`hooks/tezgah_gate.py::_product`). The branch comes from that checkout, not from the session's cwd. A branch binds a plan when it is `plan/NNN-slug` for an open plan NNN, or when an open plan
|
|
248
|
+
declares it in its `branch:` frontmatter (`hooks/tezgah_gate.py::_unplanned`, `hooks/tezgah_task.py::plan_of_branch`, `hooks/tezgah_task.py::declared_branches`). So `plan/999-nope` binds
|
|
249
|
+
nothing, and Orca's `r1z4x/<slug>` branches bind by declaration. The task rule holds a bound checkout, by its plan's phase and allowlist. `main` and `master` never bind a plan, whatever a plan
|
|
250
|
+
declares. The count folds the turn's own write rows (`hooks/tezgah_integrity.py::turn_rows`) together with the files the call in hand names, so it needs no state of its own, and it counts DISTINCT
|
|
251
|
+
paths - a turn that rewrites one file three times is still one file's work. A product path is any path in the checkout, whatever the project's layout (`apps/`, `packages/`, `.maestro/` count as
|
|
252
|
+
`hooks/` does). Tezgah's and git's own state does not count: `.tezgah/`, `.codegraph/` and `.git/` (`hooks/tezgah_gate.py::PLAN_SKIP`). Neither does a host's generated `agents/` set
|
|
253
|
+
(`hooks/tezgah_gate.py::PLAN_AGENT_HOSTS`). A path outside any git repository never counts. Told: the branch it read, how many files the turn has written, and
|
|
254
|
+
the command that opens a plan - the `plan-add` skill, `/tezgah:plan-add <description>` (`hooks/tezgah_gate.py::PLAN_DENY`). It also names the bind route: `tezgah-task start NNN --phase P --branch
|
|
255
|
+
<branch>` writes the plan's `branch:` (`bin/tezgah-task::cmd_start`). The record rule refuses a hand edit of a phased record. Standing: only changing the tree's state changes it - the
|
|
256
|
+
work moves to the `plan/NNN-slug` branch the skill creates or to a bound branch (a one- or two-file fix is free either way), or the repository carries a `.no-plan-gate` mark (the shape of
|
|
257
|
+
`.no-graph` and `.no-lessons`). It passes while a merge or rebase is in progress (`MERGE_HEAD`, `rebase-merge/` or `rebase-apply/` in the checkout's gitdir, `hooks/tezgah_gate.py::_merging`):
|
|
258
|
+
resolving conflicts is not new work. It passes wherever the question cannot be answered: no session or ledger, a directory that is not a git repository, and a detached HEAD with no branch to name.
|
|
254
259
|
|
|
255
260
|
### Ordering — a commit while the newest check failed
|
|
256
261
|
|
|
@@ -391,7 +396,7 @@ A rule is a refusal with a cause: something happened, the check was written, and
|
|
|
391
396
|
only as a sentence inside each rule's own paragraph above. It is a table here instead, so it is one place and a machine reads it: `bin/tezgah-docs --citations`
|
|
392
397
|
opens `docs/gate.md`, reads this table, and refuses a rule name the source can still produce with no row, and a row whose `pin` names a test that does not exist.
|
|
393
398
|
The names are read off the source, not restated: the gate's rules are the `_deny(session_id, "<rule>", ...)` calls in `hooks/tezgah_gate.py`, and the Stop
|
|
394
|
-
rule's triggers are the classes `_stop_block` returns, the four `_shape_block` and the six `
|
|
399
|
+
rule's triggers are the classes `_stop_block` returns, the four `_shape_block` hands back and the six `_evidence_text` words for the temporal spec included.
|
|
395
400
|
|
|
396
401
|
- `incident` — what produced the rule; `designed` where none is recorded. The ratio of incident-derived to designed rules is the maturity signal the research
|
|
397
402
|
entry names, which is why the distinction is written down rather than smoothed over.
|
|
@@ -419,7 +424,7 @@ rule's triggers are the classes `_stop_block` returns, the four `_shape_block` a
|
|
|
419
424
|
| `evidence tampered` | a terminated garbage line made `_parse` raise, and the guard then failed the Stop rule open for the turn; corrupting a `verify_fail` row was an allow route. A `verify_ok` appended with `python3 -c` licensed a claim (security-03), so a pass with no `began` row is an orphan (`hooks/tezgah_integrity.py::_pair`) | `hooks/tezgah_integrity.py::_parse` | `tests/test_integrity.py::test_a_damaged_ledger_blocks_a_done_claim_as_evidence_tampered` |
|
|
420
425
|
| `check failed` | designed | the newest check failing is its own class; no incident recorded | `tests/test_integrity.py::test_failed_check_blocks` |
|
|
421
426
|
| `partial failure` | designed | a failure the turn never resolved; no incident recorded | `tests/test_integrity.py::test_an_unresolved_failure_blocks_even_after_an_earlier_pass` |
|
|
422
|
-
| `no ui_ok` | reading the fold the old way refused honest turns and named a screen check the turn had in fact run | `hooks/
|
|
427
|
+
| `no ui_ok` | reading the fold the old way refused honest turns and named a screen check the turn had in fact run | `hooks/tezgah_stopspec.py::FORMULAS` | `tests/test_integrity.py::test_a_green_unit_run_does_not_license_a_ui_change` |
|
|
423
428
|
| `stale evidence` | measured 2026-09-19: reading a rewrite as a change refused honest turns | `hooks/tezgah_integrity.py::_post_write` | `tests/test_integrity.py::test_a_write_after_the_check_makes_the_check_stale` |
|
|
424
429
|
| `no verify_ok` | E2: 0 of 10 description-shaped claims refused before the `worked` trigger | `hooks/tezgah_integrity.py::_stop_block` | `tests/test_integrity.py::test_work_with_no_passing_check_is_refused_without_a_claim_word` |
|
|
425
430
|
| `no external read` | two turns: "npm 0.22.0 is missing" off a stale client, and "make NPM_TOKEN an automation token" when it already was | `hooks/tezgah_integrity.py::_external_read_row` | `tests/test_integrity.py::test_an_external_claim_with_no_read_is_refused` |
|
package/docs/models.md
CHANGED
|
@@ -215,13 +215,20 @@ moved against the snapshot (`SNAPSHOT`, `hooks/tezgah_models.py::SNAPSHOT`;
|
|
|
215
215
|
re-applies omp's overrides. Scores are not re-read - they need a key - so
|
|
216
216
|
`tezgah-route --check` reports the snapshot's age and exits 1 past 60 days or with
|
|
217
217
|
a flag (`check`, `hooks/tezgah_models.py::check`); that is the moment to re-read
|
|
218
|
-
the leaderboards and edit `SLOTS`. `tezgah-route --mode anthropic|zai|any|auto|off` pins
|
|
218
|
+
the leaderboards and edit `SLOTS`. `tezgah-route --mode anthropic|zai|any|openai|auto|off` pins
|
|
219
219
|
omp's family instead of following the session default; `off` removes every entry
|
|
220
220
|
and role the table wrote and writes nothing, which is the way back when the
|
|
221
221
|
provider those selectors need has no budget - a measured case, not a
|
|
222
|
-
hypothetical one.
|
|
222
|
+
hypothetical one. The switch reaches every routed agent in one command. It writes
|
|
223
|
+
the table over a selector set by hand and names each one it replaced
|
|
224
|
+
(`apply_omp`, `hooks/tezgah_models.py::apply_omp`). The automatic re-apply at
|
|
225
|
+
session start leaves a hand-set selector alone. `tezgah-setup --install`
|
|
223
226
|
writes omp's overrides and `--uninstall` removes only the `tezgah-*` entries; the
|
|
224
227
|
omp status report carries a row for them.
|
|
228
|
+
`openai` runs every routed agent on omp's `openai-codex` provider, a ChatGPT
|
|
229
|
+
login (`OMP_OPENAI`, `hooks/tezgah_models.py::OMP_OPENAI`). Its ids are not the
|
|
230
|
+
Codex host's `openai` column, which omp does not list. The mode is never a
|
|
231
|
+
fallback for the others, and with no `openai-codex` login it writes nothing.
|
|
225
232
|
|
|
226
233
|
## Source of truth
|
|
227
234
|
|
package/docs/operations.md
CHANGED
|
@@ -701,11 +701,14 @@ background child. That child's SubagentStop closes it as `done`
|
|
|
701
701
|
record settles from the newest child transcript in the parent's session dir.
|
|
702
702
|
omp names a same-name re-dispatch `<name>-2.jsonl`. The last 64 KB of that file
|
|
703
703
|
decide `stopped` or `done` (`hooks/tezgah_task.py::pending`). A record
|
|
704
|
-
nothing moved for six hours closes `
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
704
|
+
nothing moved for six hours closes `stopped` with no failure class. It stays
|
|
705
|
+
owed, because an end this machine cannot see is not a finished slice. A
|
|
706
|
+
background `job` past omp's 3600 s deadline closes `stale`, since the parent
|
|
707
|
+
transcript already holds its result. Codex reports no child end tezgah can see,
|
|
708
|
+
so its records take the `stopped` path too. Every prompt lists up to three
|
|
709
|
+
pending records, newest first, each with the command that resumes it
|
|
710
|
+
(`hooks/tezgah_task.py::slice_lines`). They are the records this session spawned
|
|
711
|
+
or nobody owns, such as one opened by hand. The resume block at
|
|
709
712
|
session start and after a compaction adds one line that counts the other
|
|
710
713
|
sessions' records. `tezgah-task slice reconcile` lists them all.
|
|
711
714
|
|
|
@@ -718,6 +721,7 @@ sessions' records. `tezgah-task slice reconcile` lists them all.
|
|
|
718
721
|
| `tezgah-task slice stats [--window DAYS] [--json]` | counts records per status, failure class and host. A `class+host` signature with 3 or more events is an RSI lever candidate, and so is one with 5% of a window that holds 20 or more slices. The line names the lever |
|
|
719
722
|
| `tezgah-task slice board [--parent P]` | lists the open slices one parent spawned - the session's own, or for a subagent its parent's - newest first: the `Owns:` globs, worktree, branch and last commit of each, bounded to 12 lines (`hooks/tezgah_task.py::board`) |
|
|
720
723
|
| `tezgah-task slice merge-plan [--base REF]` | forecasts every done slice branch that carries unmerged work against REF (HEAD) and against the others with `git merge-tree`, and leaves out a `merge=union` file such as CHANGELOG.md. A branch touching `MANIFEST` or the citation baseline comes last, the rest fewest conflicts first. A branch merge-tree cannot forecast is printed as `not judged` and the exit is 1 (`bin/tezgah-task::merge_plan`) |
|
|
724
|
+
| `tezgah-task sweep` | one idempotent pass over what the last session left behind: every pending slice as one line with its resume command, the landed worktrees and branches `reconcile --prune` removes, and a `phase:` whose branch is gone while the record carries a `checkpoint` (`bin/tezgah-task::cmd_sweep`). It prints the pending slices rather than closing them, and a second run reports `0 change(s)` |
|
|
721
725
|
|
|
722
726
|
`tezgah-task review NNN <reviewer> approve` refuses while an open, stopped or
|
|
723
727
|
stale record names that reviewer as its id, agent or spawned name. Under
|
package/docs/testing.md
CHANGED
|
@@ -17,6 +17,12 @@ ruff check . # lint; config in pypr
|
|
|
17
17
|
[`CONTRIBUTING.md`](../CONTRIBUTING.md) repeats the same three before a pull
|
|
18
18
|
request (`CONTRIBUTING.md:15-20`).
|
|
19
19
|
|
|
20
|
+
`bin/tezgah-optimize next` decides which checks a change owes, and when
|
|
21
|
+
(`AGENTS.md`, "Checks, by tier"). It spends a green row for the same change on a
|
|
22
|
+
clean tree. It defers a command whose recorded median is at or above 105 s to the
|
|
23
|
+
merge step. Everything else runs now. `bin/tezgah-optimize push` coalesces the CI
|
|
24
|
+
run: one push per change.
|
|
25
|
+
|
|
20
26
|
| Check | What it catches that the other two do not |
|
|
21
27
|
|---|---|
|
|
22
28
|
| `compileall` | A syntax error in any `.py` under `hooks/` and `hosts/`, plus `statusline.py`, without running a line of it and with no environment set up. The `bin/` CLIs are Python sources with no `.py` suffix, so only running one compiles it (`test_setup.py::SetupBase.setup`). |
|
|
@@ -109,11 +115,13 @@ generates the gate half: one `gate-<rule>` mutant per rule `decision` refuses wi
|
|
|
109
115
|
It reads HEAD with the AST reader of the `--citations` rule check. Each mutant turns
|
|
110
116
|
every `return _deny(...)` of its rule into `pass` at its line span.
|
|
111
117
|
The script generates the Stop half too. Each `return ("<class>", ...)` site of
|
|
112
|
-
`_stop_block`, `_shape_block` and `
|
|
113
|
-
rows cover the guards around those classes:
|
|
114
|
-
read and the "doğrulanmadı" clear. The lost
|
|
115
|
-
|
|
116
|
-
|
|
118
|
+
`_stop_block`, `_shape_block`, `_evidence_text` and `_residual_block` gets one
|
|
119
|
+
`stop-<class>` mutant. Hand-written rows cover the guards around those classes:
|
|
120
|
+
the pass predicate, the empty-run read and the "doğrulanmadı" clear. The lost
|
|
121
|
+
call, the unseen pass, the bookkeeping and idle-turn fallbacks and the refusal
|
|
122
|
+
row have rows too. So do the two guards of the spec's decision list. The
|
|
123
|
+
shortcut half is still hand-written (`MUTANTS`), so a new guard there needs a
|
|
124
|
+
row.
|
|
117
125
|
Each mutant first runs alone the test that killed it last time, read from
|
|
118
126
|
`tests/neuter_killers.json`. When that test passes or is gone, the mutant runs
|
|
119
127
|
every module in its own HOME and TMPDIR. The module that reads the mutated file
|
|
@@ -132,15 +140,11 @@ opencode plugin's ports. That step runs even when the core step failed.
|
|
|
132
140
|
(`tests/fuzz_shell.py::main`). So a reader that misreads a bash 5 line fails
|
|
133
141
|
the leg instead of printing a count. `tests/test_packaging.py` pins the job.
|
|
134
142
|
|
|
135
|
-
The Stop rule's temporal spec (`hooks/tezgah_stopspec.py`, plan 063)
|
|
136
|
-
local script, `tests/stop_spec_eval.py`.
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
`stop_reason`, shadow on and off, on copies of the real ledgers. `--mutants`, also reached through
|
|
141
|
-
`tests/neuter_matrix.py --stop-generated`, runs seeded mutants of the formula
|
|
142
|
-
table through the in-process Stop tests of `tests/test_integrity.py`. On any
|
|
143
|
-
suite run, `TEZGAH_STOPSPEC_STRICT=1` fails the test that reached a disagreement.
|
|
143
|
+
The Stop rule's temporal spec (`hooks/tezgah_stopspec.py`, plan 063) decides
|
|
144
|
+
classes 5-10. Its formulas have their own local script, `tests/stop_spec_eval.py`.
|
|
145
|
+
`--mutants`, also reached through `tests/neuter_matrix.py --stop-generated`, runs
|
|
146
|
+
seeded mutants of the formula table through the in-process Stop tests of
|
|
147
|
+
`tests/test_integrity.py`.
|
|
144
148
|
|
|
145
149
|
The plan report (`render_table.py --acceptance --strict`) is not in CI: it reads
|
|
146
150
|
the gitignored `.tezgah/`, so on a runner it reads nothing and cannot fail. Run
|
package/hooks/lang/en.json
CHANGED
|
@@ -211,6 +211,9 @@
|
|
|
211
211
|
"never_default": [
|
|
212
212
|
"force[- ]?push\\w*|--force\\b|rewrit\\w* (?:the )?history|reset --hard|\\bdelet\\w*|\\bdrop (?:the )?(?:table|database|volume)s?|unpublish\\w*|deprecat\\w*|production|\\bprod\\b|migrat\\w*|credential\\w*|password\\w*|secret\\w*|api key|\\baccount\\w*|e-?mail|send (?:it |this |them )?to\\b"
|
|
213
213
|
],
|
|
214
|
+
"plan_build": [
|
|
215
|
+
"\\bplans?\\b[^.?!\\n]{0,40}\\b(?:is ready|ready|written|drafted)\\b|\\b(?:wrote|opened|drafted|added|created) (?:the |a |this )?plans?\\b|\\b(?:build|implement|execute|start on|start) (?:the |this |that )?plans?\\b"
|
|
216
|
+
],
|
|
214
217
|
"spend": [
|
|
215
218
|
"spend\\w*|\\bpaid\\b|billing|\\bcost\\w*|\\bbudget\\w*|\\busd\\b|\\$\\s?\\d"
|
|
216
219
|
],
|
package/hooks/lang/tr.json
CHANGED
|
@@ -255,6 +255,9 @@
|
|
|
255
255
|
"never_default": [
|
|
256
256
|
"zorla|geçmiş\\w* (?:yeniden yaz|değiştir)\\w*|\\bsil\\w*|kaldır\\w*|yok et\\w*|üretim\\w*|\\bcanlı\\w*|\\bprod\\w*|göç\\w*|kimlik bilgi\\w*|şifre\\w*|parola\\w*|\\bhesa[bp]\\w*|e-?posta\\w*|gönder\\w*"
|
|
257
257
|
],
|
|
258
|
+
"plan_build": [
|
|
259
|
+
"\\bplan\\w*[^.?!\\n]{0,40}(?:hazır|yazıldı|yazdım|oluşturdum|oluşturuldu|açtım|açıldı|eklendi|uygula\\w*|başla\\w*|kur\\w*)|(?:yazdığım|açtığım|hazırladığım|oluşturduğum) plan\\w*"
|
|
260
|
+
],
|
|
258
261
|
"spend": [
|
|
259
262
|
"ücret\\w*|ödeme\\w*|harca\\w*|bütçe\\w*|maliyet\\w*|\\bpara\\b|dolar\\w*|\\$\\s?\\d"
|
|
260
263
|
],
|
package/hooks/tezgah_context.py
CHANGED
|
@@ -33,9 +33,11 @@ from tezgah_paths import (CACHE, CONTEXT_NUDGE_TOKENS, REPO_MARKS, SWITCHES,
|
|
|
33
33
|
ai_research_dir, alts, armed,
|
|
34
34
|
cache_dir, codegraph_bin, consult_options,
|
|
35
35
|
ensure_workspace, fallback_cache, have_judge_key, language,
|
|
36
|
+
link_workspace,
|
|
36
37
|
off, orx_bin, packs, pony_level, project_top, root_for,
|
|
37
38
|
roots, say, tool,
|
|
38
|
-
workspace, workspace_from_repo, worktrees,
|
|
39
|
+
work_tree, workspace, workspace_from_repo, worktrees,
|
|
40
|
+
writable_dir)
|
|
39
41
|
|
|
40
42
|
try: # The task record is the active plan's frontmatter (see tezgah_task), read
|
|
41
43
|
# once per user prompt for the phase line. The module is newer than some
|
|
@@ -537,18 +539,22 @@ def open_plans(root):
|
|
|
537
539
|
def sibling_line(root):
|
|
538
540
|
"""One line naming every checkout of this repository, or "" when it has one.
|
|
539
541
|
The main checkout's `.tezgah` is the repository's one workspace (plan 097),
|
|
540
|
-
named with its open plan and research line counts
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
the
|
|
542
|
+
named with its open plan and research line counts; a bare repository's git
|
|
543
|
+
directory holds it in the main checkout's place (plan 098), and is named as
|
|
544
|
+
that, not counted as a checkout. A linked checkout whose own `.tezgah` still
|
|
545
|
+
holds data is named with what it holds, because `git worktree remove`
|
|
546
|
+
deletes it with the checkout. Fork-free (the pinned session-start git
|
|
547
|
+
budget): `worktrees` reads `.git/worktrees/*/gitdir`, and the counts are
|
|
548
|
+
directory listings."""
|
|
545
549
|
checkouts = worktrees(root)
|
|
546
550
|
here = os.path.realpath(root)
|
|
547
551
|
if here not in checkouts:
|
|
548
552
|
checkouts.append(here)
|
|
549
|
-
if len(checkouts) < 2:
|
|
550
|
-
return ""
|
|
551
553
|
main = checkouts[0]
|
|
554
|
+
bare = not os.path.lexists(os.path.join(main, ".git"))
|
|
555
|
+
if len(checkouts) - bare < 2:
|
|
556
|
+
return ""
|
|
557
|
+
owner = "the bare repository" if bare else "the main checkout"
|
|
552
558
|
items = []
|
|
553
559
|
for checkout in checkouts:
|
|
554
560
|
ws = workspace(checkout)
|
|
@@ -566,19 +572,20 @@ def sibling_line(root):
|
|
|
566
572
|
except OSError:
|
|
567
573
|
held = []
|
|
568
574
|
lines = tezgah_research.slugs(checkout) if held else []
|
|
569
|
-
state = ("uses
|
|
575
|
+
state = ("uses %s's `.tezgah`" % owner if not held else
|
|
570
576
|
"its own `.tezgah` still holds %s, which `git worktree remove` "
|
|
571
577
|
"deletes: move each research line with `%s import %s <slug>` "
|
|
572
|
-
"from
|
|
573
|
-
% (", ".join(held), tool("tezgah-research"), checkout
|
|
578
|
+
"from %s, and the rest by hand"
|
|
579
|
+
% (", ".join(held), tool("tezgah-research"), checkout,
|
|
580
|
+
"another worktree" if bare else "the main checkout"))
|
|
574
581
|
if lines:
|
|
575
582
|
state += " (lines: %s)" % ", ".join(lines)
|
|
576
583
|
items.append("%s (%s%s)" % (checkout, "this checkout, " if checkout == here
|
|
577
584
|
else "", state))
|
|
578
|
-
return ("Worktrees: %d checkouts of this repository share
|
|
579
|
-
"
|
|
580
|
-
"
|
|
581
|
-
% (len(checkouts), "; ".join(items), tool("tezgah-research")))
|
|
585
|
+
return ("Worktrees: %d checkouts of this repository share %s's `.tezgah` - "
|
|
586
|
+
"plans, lessons, research and analysis are written there, never into a "
|
|
587
|
+
"worktree's own: %s. Detail: `%s --all`."
|
|
588
|
+
% (len(checkouts) - bare, owner, "; ".join(items), tool("tezgah-research")))
|
|
582
589
|
|
|
583
590
|
|
|
584
591
|
def task_line(task):
|
|
@@ -1274,11 +1281,14 @@ def session_of(payload):
|
|
|
1274
1281
|
# Moved again on 2026-10-12 (plan 086, S3): the integrity paragraph carries
|
|
1275
1282
|
# the Ask policy (+913 B of core), and at 14500 B the healthy fixture gave up
|
|
1276
1283
|
# its lessons, plans or knowledge block (235-338 B), so the session budgets
|
|
1277
|
-
# move to 15500 B.
|
|
1284
|
+
# move to 15500 B. Moved again on 2026-10-13 (plan 105): the fidelity rule says
|
|
1285
|
+
# a research, analysis, review or plan ask ends at the plan (+105 B of core), and
|
|
1286
|
+
# the fixture's subagent brief reached 5500 B exactly, so subagent_start moves to
|
|
1287
|
+
# 5800 B. A
|
|
1278
1288
|
# budget in bytes, not tokens: this file has no tokenizer and a wrong estimate
|
|
1279
1289
|
# would be worse than a bound.
|
|
1280
1290
|
CONTEXT_BUDGET = {"session_start": 15500, "post_compact": 15500,
|
|
1281
|
-
"subagent_start":
|
|
1291
|
+
"subagent_start": 5800, "user_prompt": 6500}
|
|
1282
1292
|
DEFAULT_BUDGET = 13000
|
|
1283
1293
|
# The blocks in the order they are given up when the budget is exceeded, lowest
|
|
1284
1294
|
# value first: text another surface already carries (the plan table lives in the
|
|
@@ -1569,7 +1579,8 @@ def context_nudge(payload, session_id):
|
|
|
1569
1579
|
|
|
1570
1580
|
# The ledger kinds a hook other than the tool hooks writes: the prompt hook's
|
|
1571
1581
|
# turn marker, judge row, lesson rows and disarm row, the Stop hook's claim,
|
|
1572
|
-
# refusal, after_block
|
|
1582
|
+
# refusal, after_block and shape rows and the stop_spec rows of plan 063's shadow
|
|
1583
|
+
# (old ledgers only), the subagent-end hook's subagent_end row,
|
|
1573
1584
|
# compaction, the subagent mark and the `subagent_start` lineage row (both from
|
|
1574
1585
|
# SubagentStart), the omp fork's `forked` row, the guard's crash
|
|
1575
1586
|
# row (any hook), the damage row any reader writes and the session start's
|
|
@@ -2254,7 +2265,10 @@ def context_for(event, cwd, payload=None, with_core=True):
|
|
|
2254
2265
|
# the project and given its private git before anything writes there.
|
|
2255
2266
|
# A root is not a project, and the helper returns None outside a work tree.
|
|
2256
2267
|
if ws not in roots():
|
|
2257
|
-
ensure_workspace
|
|
2268
|
+
# never link to a `.tezgah` ensure_workspace refused: one the
|
|
2269
|
+
# project tracks or a link the clone placed (review of plan 100)
|
|
2270
|
+
if ensure_workspace(ws) and ws != root:
|
|
2271
|
+
link_workspace(root, ws)
|
|
2258
2272
|
# The live turn state, first in this region: on a compacted or resumed
|
|
2259
2273
|
# session it is the one thing the rest of the block cannot re-derive.
|
|
2260
2274
|
resume = resume_state(root, session_of(payload))
|
|
@@ -2329,7 +2343,9 @@ def context_for(event, cwd, payload=None, with_core=True):
|
|
|
2329
2343
|
# bulk import at most daily (tezgah_store.import_later).
|
|
2330
2344
|
if event == "session_start":
|
|
2331
2345
|
tezgah_store.import_later(cache_dir())
|
|
2332
|
-
|
|
2346
|
+
# the line's cited files are the session checkout's in a bare repository
|
|
2347
|
+
broken = (tezgah_research.failing(ws, code=work_tree(ws, root))
|
|
2348
|
+
if not off("research-off") else [])
|
|
2333
2349
|
if broken:
|
|
2334
2350
|
line_slug, err = broken[0]
|
|
2335
2351
|
parts.append(("research_broken",
|
package/hooks/tezgah_gate.py
CHANGED
|
@@ -1549,7 +1549,8 @@ TASK_SCOPE_OUTSIDE = (
|
|
|
1549
1549
|
TASK_RECORD_DENY = (
|
|
1550
1550
|
"Task gate: %s is the active task's own record; only `tezgah-task` writes it, checking the plan's "
|
|
1551
1551
|
"Acceptance, spike and review on the way. The phase and allowlist: `tezgah-task phase|allow`; an "
|
|
1552
|
-
"Acceptance box: `tezgah-task tick NNN <n>`;
|
|
1552
|
+
"Acceptance box: `tezgah-task tick NNN <n>`; an item the owner accepted unmet: "
|
|
1553
|
+
"`tezgah-task unmet NNN <n> \"<reason>\"`; State/Next: `tezgah-task note NNN state|next \"...\"`.")
|
|
1553
1554
|
# The shapes that make a shell command a write, for the phase rule below. The
|
|
1554
1555
|
# tools are not the only way to change a file, and E7b measured the other one:
|
|
1555
1556
|
# with `edit` and `write` refused twelve and six times, the armed arm wrote the
|
|
@@ -2222,7 +2223,7 @@ def shell_write_body(command, cwd=None):
|
|
|
2222
2223
|
# reported done unless the output was seen - given a mechanical half at the one
|
|
2223
2224
|
# moment a claim is written into the repository's history.
|
|
2224
2225
|
#
|
|
2225
|
-
# The reader is the Stop rule's
|
|
2226
|
+
# The reader is the newest-check fold the Stop rule's `check failed` class asks (`_last_verify` on the
|
|
2226
2227
|
# last ORDER_TAIL rows), and the rule is structurally incapable of firing in
|
|
2227
2228
|
# either direction that would punish honest work: no check at all folds to None
|
|
2228
2229
|
# and a passing newest check folds to "ok", so a commit in a session that ran no
|
|
@@ -2412,15 +2413,20 @@ def ready_advice(tool, session_id, cwd=None):
|
|
|
2412
2413
|
return READY_WAIT % (len(heads), "; ".join(cut(h, 48) for h in heads[:3]))
|
|
2413
2414
|
|
|
2414
2415
|
|
|
2415
|
-
# --- plan required: work that spans files on
|
|
2416
|
+
# --- plan required: work that spans files on an unplanned branch -------------
|
|
2416
2417
|
# What this closes: a turn that wrote four product files - a hook, its tests, its
|
|
2417
|
-
# docs and the changelog - straight onto `main`, with the plan opened afterwards
|
|
2418
|
-
#
|
|
2419
|
-
#
|
|
2420
|
-
#
|
|
2421
|
-
#
|
|
2422
|
-
#
|
|
2423
|
-
#
|
|
2418
|
+
# docs and the changelog - straight onto `main`, with the plan opened afterwards;
|
|
2419
|
+
# and Orca worktrees on `r1z4x/<name>` that built five branches of work with no
|
|
2420
|
+
# plan at all (plan 105). The task rule constrains a write only through a record
|
|
2421
|
+
# the user made, so with no active task every product write passes and "work for
|
|
2422
|
+
# a plan happens on its branch" is advice rather than a mechanism. Here the
|
|
2423
|
+
# checkout is the record: on a branch that binds no open plan the third distinct
|
|
2424
|
+
# product file a single turn writes is refused, while a one- or two-file fix
|
|
2425
|
+
# stays free - a plan is what makes work that spans files reviewable and
|
|
2426
|
+
# closeable. A branch binds a plan when it is `plan/NNN-*` or an open plan
|
|
2427
|
+
# declares it in `branch:` (`tezgah_task.plan_of_branch`); such a checkout is
|
|
2428
|
+
# the task rule's to hold. `main`/`master` never bind one, whatever a plan
|
|
2429
|
+
# declares: the trunk is where unplanned work lands.
|
|
2424
2430
|
#
|
|
2425
2431
|
# The count is a fold over the turn's own write rows (`turn_rows`), so it needs
|
|
2426
2432
|
# no state of its own, and the call in hand contributes the files it names before
|
|
@@ -2428,32 +2434,41 @@ def ready_advice(tool, session_id, cwd=None):
|
|
|
2428
2434
|
# itself.
|
|
2429
2435
|
#
|
|
2430
2436
|
# Fail-open wherever the question cannot be answered: no session, no ledger, a
|
|
2431
|
-
# checkout that is not a git repository, a detached HEAD (no branch to name),
|
|
2432
|
-
#
|
|
2433
|
-
#
|
|
2437
|
+
# checkout that is not a git repository, a detached HEAD (no branch to name), a
|
|
2438
|
+
# missing task module (no binding to read), and a repo carrying the
|
|
2439
|
+
# `.no-plan-gate` mark all pass - the direction every rule here takes, because a
|
|
2440
|
+
# refusal has to rest on something a reader can check. A merge or rebase in
|
|
2441
|
+
# progress passes too: resolving its conflicts is not new work that spans files,
|
|
2442
|
+
# and neither way out the refusal names fits it.
|
|
2434
2443
|
#
|
|
2435
2444
|
# ponytail: a PRIOR write recorded through the shell is a `run` row whose detail
|
|
2436
2445
|
# is the command, so it is not counted; only the call in hand contributes a shell
|
|
2437
2446
|
# target (`write_paths` reads the redirect). The turn this rule answers is the
|
|
2438
2447
|
# write-tool turn, and reading a shell command's target off a stored command line
|
|
2439
2448
|
# would be a second reader of the same table, not a new fact.
|
|
2440
|
-
|
|
2449
|
+
PLAN_TRUNKS = ("main", "master")
|
|
2441
2450
|
# The product paths the count reads, relative to the checkout holding the write:
|
|
2442
|
-
#
|
|
2443
|
-
#
|
|
2444
|
-
# or
|
|
2445
|
-
|
|
2446
|
-
|
|
2451
|
+
# every path of the project, whatever its layout (ServisTek's unplanned merges
|
|
2452
|
+
# wrote `apps/`, `packages/` and `.maestro/`, review of plan 105), except what
|
|
2453
|
+
# tezgah or git keeps there - the workspace, the graph index, git's own
|
|
2454
|
+
# directory and the generated agent sets. Neither is a path outside any git
|
|
2455
|
+
# checkout or a checkout outside every configured root (a scratch fixture repo).
|
|
2456
|
+
PLAN_SKIP = (".tezgah", ".codegraph", ".git")
|
|
2457
|
+
PLAN_AGENT_HOSTS = (".claude", ".codex", ".cursor", ".opencode") # their agents/
|
|
2447
2458
|
# The third file: two writes in a turn are a fix, three are work that spans files.
|
|
2448
2459
|
PLAN_WRITES = 3
|
|
2460
|
+
# git's marks of an operation in progress, in the checkout's own gitdir
|
|
2461
|
+
PLAN_MERGING = ("MERGE_HEAD", "rebase-merge", "rebase-apply")
|
|
2449
2462
|
PLAN_DENY = (
|
|
2450
|
-
"This turn has written %d distinct product files while the checkout is
|
|
2451
|
-
"`%s
|
|
2452
|
-
"close, so the third product file in one turn is
|
|
2453
|
-
"the `plan-add` skill (`/tezgah:plan-add
|
|
2454
|
-
"the `plan/NNN-slug` branch it creates
|
|
2455
|
-
"
|
|
2456
|
-
"`
|
|
2463
|
+
"This turn has written %(count)d distinct product files while the checkout is "
|
|
2464
|
+
"on `%(branch)s`, a branch that binds no open plan. Work that spans files there "
|
|
2465
|
+
"has no plan to review or close, so the third product file in one turn is "
|
|
2466
|
+
"refused: open a plan with the `plan-add` skill (`/tezgah:plan-add "
|
|
2467
|
+
"<description>`) and do the work on the `plan/NNN-slug` branch it creates, or "
|
|
2468
|
+
"bind this branch to the open plan with `tezgah-task start NNN --phase P "
|
|
2469
|
+
"--branch %(branch)s` (it writes the plan's `branch:`). A one- or two-file fix "
|
|
2470
|
+
"stays free. Every path in the checkout counts except `.tezgah/`, "
|
|
2471
|
+
"`.codegraph/`, `.git/` and the generated `<host>/agents/` sets.")
|
|
2457
2472
|
|
|
2458
2473
|
|
|
2459
2474
|
def _gitdir(root):
|
|
@@ -2566,7 +2581,8 @@ def _checkout(path, cwd):
|
|
|
2566
2581
|
session on `main` writing into a linked worktree on a plan branch is plan
|
|
2567
2582
|
work, and one on a plan branch writing into a `main` checkout is not. A
|
|
2568
2583
|
relative path is resolved against the call's own `cwd`."""
|
|
2569
|
-
|
|
2584
|
+
# `~/x` is a home path, not one under the cwd (plan 106)
|
|
2585
|
+
text = os.path.expanduser(str(path or "").strip())
|
|
2570
2586
|
if not text or not cwd and not os.path.isabs(text):
|
|
2571
2587
|
return None
|
|
2572
2588
|
full = os.path.realpath(os.path.join(cwd or "", text))
|
|
@@ -2582,15 +2598,38 @@ def _checkout(path, cwd):
|
|
|
2582
2598
|
|
|
2583
2599
|
|
|
2584
2600
|
def _product(rel):
|
|
2585
|
-
"""True when a checkout-relative path is
|
|
2601
|
+
"""True when a checkout-relative path is project work a plan exists to
|
|
2602
|
+
scope: anything but `PLAN_SKIP` and a host's generated `agents/` set."""
|
|
2586
2603
|
parts = rel.split(os.sep)
|
|
2587
|
-
return parts[0] in
|
|
2604
|
+
return parts[0] not in PLAN_SKIP and not (
|
|
2605
|
+
parts[0] in PLAN_AGENT_HOSTS and len(parts) > 2 and parts[1] == "agents")
|
|
2606
|
+
|
|
2607
|
+
|
|
2608
|
+
def _merging(top):
|
|
2609
|
+
"""True while a merge or rebase is in progress in the checkout at `top`:
|
|
2610
|
+
git's mark of it stands in the checkout's own gitdir."""
|
|
2611
|
+
gitdir = _gitdir(top)
|
|
2612
|
+
return bool(gitdir) and any(os.path.exists(os.path.join(gitdir, mark))
|
|
2613
|
+
for mark in PLAN_MERGING)
|
|
2614
|
+
|
|
2615
|
+
|
|
2616
|
+
def _unplanned(branch, top):
|
|
2617
|
+
"""True when `branch`, checked out at `top`, binds no open plan of its
|
|
2618
|
+
repository: a trunk always, else neither `plan/NNN-*` nor a branch an open
|
|
2619
|
+
plan declares. An empty branch or a missing task module cannot be read, and
|
|
2620
|
+
reads as planned (fail-open)."""
|
|
2621
|
+
if branch in PLAN_TRUNKS:
|
|
2622
|
+
return True
|
|
2623
|
+
if not branch or tezgah_task is None:
|
|
2624
|
+
return False
|
|
2625
|
+
return tezgah_task.plan_of_branch(branch, linked_main(top) or top) is None
|
|
2588
2626
|
|
|
2589
2627
|
|
|
2590
2628
|
def plan_reason(inp, cwd, session_id):
|
|
2591
2629
|
"""A deny reason when this write would be a turn's third distinct product
|
|
2592
|
-
file in a checkout on
|
|
2593
|
-
the trigger, the counted paths and the fail-open
|
|
2630
|
+
file in a checkout on a branch that binds no open plan, else None. The
|
|
2631
|
+
section above carries the trigger, the counted paths and the fail-open
|
|
2632
|
+
direction."""
|
|
2594
2633
|
if not session_id or turn_rows is None:
|
|
2595
2634
|
return None
|
|
2596
2635
|
targets = {}
|
|
@@ -2603,8 +2642,8 @@ def plan_reason(inp, cwd, session_id):
|
|
|
2603
2642
|
rows = None
|
|
2604
2643
|
for top, written in targets.items():
|
|
2605
2644
|
branch = _branch(top)
|
|
2606
|
-
if
|
|
2607
|
-
or
|
|
2645
|
+
if os.path.exists(os.path.join(top, ".no-plan-gate")) \
|
|
2646
|
+
or not _unplanned(branch, top) or _merging(top):
|
|
2608
2647
|
continue
|
|
2609
2648
|
if rows is None:
|
|
2610
2649
|
# a row's `target` is already absolute against the cwd it was
|
|
@@ -2614,7 +2653,7 @@ def plan_reason(inp, cwd, session_id):
|
|
|
2614
2653
|
written |= {rel for hit in rows if hit and hit[0] == top
|
|
2615
2654
|
for rel in (hit[1],) if _product(rel)}
|
|
2616
2655
|
if len(written) >= PLAN_WRITES:
|
|
2617
|
-
return PLAN_DENY %
|
|
2656
|
+
return PLAN_DENY % {"count": len(written), "branch": branch}
|
|
2618
2657
|
return None
|
|
2619
2658
|
|
|
2620
2659
|
|
|
@@ -3577,8 +3616,8 @@ def decision(tool, inp, cwd, session_id=None, record=True, agent=None):
|
|
|
3577
3616
|
body = inp if t in WRITE_TOOLS else shell_body
|
|
3578
3617
|
if body and secret_edit(body, cwd):
|
|
3579
3618
|
return _deny(session_id, "secret", SECRET_EDIT_DENY, tool, inp, base)
|
|
3580
|
-
# Plan required: the turn's third product file while the checkout is on
|
|
3581
|
-
#
|
|
3619
|
+
# Plan required: the turn's third product file while the checkout is on a
|
|
3620
|
+
# branch no open plan binds (see plan_reason). Below the task, workspace and secret rules,
|
|
3582
3621
|
# because each names a more specific fault in the same write, and above the
|
|
3583
3622
|
# repeat guards, because it is the call that is refused and not a repeat of
|
|
3584
3623
|
# one. Standing: only changing the tree's state changes it.
|