@r1z4x/tezgah 1.0.0 → 1.2.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 +92 -1
- package/MANIFEST +10 -0
- package/bin/tezgah-agents +10 -1
- package/bin/tezgah-docs +1 -1
- package/bin/tezgah-doctor +11 -0
- package/bin/tezgah-research +31 -14
- package/bin/tezgah-setup +154 -29
- package/bin/tezgah-skill +59 -0
- package/bin/tezgah-skill.py +59 -0
- package/bin/tezgah-status +62 -1
- package/bin/tezgah-taste +504 -5
- package/bin/tezgah-triage +5 -3
- package/docs/README.md +1 -0
- package/docs/architecture.md +5 -2
- package/docs/citations-baseline.json +1 -1
- package/docs/clarity-baseline.json +9 -9
- package/docs/contract.md +68 -26
- package/docs/environment.md +6 -3
- package/docs/evidence.md +7 -0
- package/docs/gate.md +13 -7
- package/docs/glossary.md +1 -1
- package/docs/hosts.md +48 -1
- package/docs/index.json +34 -3
- package/docs/judge.md +75 -60
- package/docs/models.md +14 -8
- package/docs/operations.md +80 -25
- package/docs/orca.md +126 -0
- package/docs/research.md +38 -3
- package/docs/skills.md +74 -0
- package/docs/status-line.md +3 -1
- package/docs/testing.md +33 -21
- package/docs/vision.md +12 -8
- package/hooks/projects-posttooluse.py +7 -0
- package/hooks/projects-pretooluse.py +17 -5
- package/hooks/tezgah_agents.py +135 -13
- package/hooks/tezgah_components.py +3 -0
- package/hooks/tezgah_context.py +84 -22
- package/hooks/tezgah_gate.py +133 -17
- package/hooks/tezgah_integrity.py +60 -13
- package/hooks/tezgah_judge.py +288 -66
- package/hooks/tezgah_models.py +41 -5
- package/hooks/tezgah_orca.py +144 -0
- package/hooks/tezgah_paths.py +40 -6
- package/hooks/tezgah_policy.py +109 -35
- package/hooks/tezgah_research.py +104 -8
- package/hooks/tezgah_skill_pick.py +459 -8
- package/hooks/tezgah_stack.py +382 -0
- package/hooks/tezgah_stopspec.py +2 -2
- package/hooks/tezgah_taste.py +35 -9
- package/hooks/tezgah_taste_ledger.py +628 -0
- package/hosts/codex/hook.py +12 -1
- package/hosts/cursor/hook.py +12 -4
- package/hosts/omp/hook.py +18 -6
- package/hosts/omp/tezgah-hook.ts.in +20 -7
- package/hosts/opencode/plugins/tezgah.js +28 -15
- package/output-styles/tezgah.md +34 -19
- package/package.json +1 -1
- package/skills/i-have-adhd/SKILL.md +2 -2
- package/skills/research/SKILL.md +60 -17
- package/skills/tezgah-contract/SKILL.md +55 -13
package/docs/research.md
CHANGED
|
@@ -65,7 +65,7 @@ nothing recorded the ask and `close --limit` accepted any string.
|
|
|
65
65
|
`state.json` `rules` is the line's format version. `init` writes `RULES`, the
|
|
66
66
|
newest set (`hooks/tezgah_research.py::RULES`). Each set keeps its own gate. Rules 2
|
|
67
67
|
are the standards rules. Rules 3 add the ask contract. Rules 4 add the protocol
|
|
68
|
-
sections below. A bump never weakens an older set.
|
|
68
|
+
sections below. Rules 5 add the scope lock. A bump never weakens an older set.
|
|
69
69
|
|
|
70
70
|
A `rules` above `RULES` means a later tezgah wrote the line. `check` then reports
|
|
71
71
|
one error that names both numbers and reads nothing else. The writers refuse the
|
|
@@ -110,14 +110,49 @@ it. The rules come from the gortex/SoL-Pi adoption's ideas 3 and 4 and from the
|
|
|
110
110
|
layer audit's P2 probe (`_check_protocol_sections`, `_token_claim_problem`).
|
|
111
111
|
Older lines keep the word rule.
|
|
112
112
|
|
|
113
|
+
## Scope lock (rules 5)
|
|
114
|
+
|
|
115
|
+
The owner reported on 2026-10-07 that research turned every question into
|
|
116
|
+
metrics. It also grew past the ask, stayed shallow, and drifted off topic. The
|
|
117
|
+
rule text pushed each of these. The injected rule sent every research task to
|
|
118
|
+
orx's experiment tree. The skill locked a metric at bootstrap. `check` refused a
|
|
119
|
+
line past `bootstrap` without one. An analysis owed three compared variants. The
|
|
120
|
+
ideation moves had no fence. Rules 5 lock a line to its question:
|
|
121
|
+
|
|
122
|
+
- `init --in "<covered>" --out "<left alone>"` (repeatable) write `state.json`
|
|
123
|
+
`scope`. The in-scope items are `S1`, `S2`, ... in order. `--method
|
|
124
|
+
qualitative|measured` writes `method`. `scope_problems`
|
|
125
|
+
(`hooks/tezgah_research.py::scope_problems`) names an empty `scope.in`, an
|
|
126
|
+
empty `scope.out` and a missing method. `check` refuses them past `bootstrap`
|
|
127
|
+
and warns at it.
|
|
128
|
+
- A claim's `trace` names the in-scope item it answers (`S<n>` or the item's
|
|
129
|
+
text). `claim_problems` and `_check_claims` both call `trace_problem`
|
|
130
|
+
(`hooks/tezgah_research.py::trace_problem`). The writer and the checker refuse
|
|
131
|
+
the same off-topic claim.
|
|
132
|
+
- A `qualitative` line owes no metric. `_check_evaluation`
|
|
133
|
+
(`hooks/tezgah_research.py::_check_evaluation`) skips it. `_check_scope`
|
|
134
|
+
refuses one that records `results.jsonl`. A measured question says `measured`
|
|
135
|
+
and locks its evaluation first.
|
|
136
|
+
- An `analysis` deliverable owes no variants (`deliverable_problems` takes the
|
|
137
|
+
line's rules). A design, plan or code still does. Older lines keep three.
|
|
138
|
+
|
|
139
|
+
The session reads the same lock in the research rule
|
|
140
|
+
(`hooks/tezgah_policy.py::RESEARCH` and the paragraph `**Research: route it to
|
|
141
|
+
OpenResearch.**`). Restate the question verbatim. Name what is in and out of
|
|
142
|
+
scope. Drop a finding that answers nothing asked. Pick the method from the
|
|
143
|
+
question. Answer a plain factual question in the reply from primary sources, with
|
|
144
|
+
no line. Cite each finding's source with a confidence. The host-subagent fallback
|
|
145
|
+
gets the question, its scope and its method in its brief. The research skill's
|
|
146
|
+
"Scope and method" section is the long form.
|
|
147
|
+
|
|
113
148
|
## One line, one directory
|
|
114
149
|
|
|
115
150
|
| Path | Holds |
|
|
116
151
|
|---|---|
|
|
117
|
-
| `state.json` | the question, the phase (`bootstrap`/`inner`/`outer`/`concluded`), the direction, the locked evaluation and its optional second gate (`capability_tolerance`, `counter_metric`), the session events (`PHASES`, `hooks/tezgah_research.py::PHASES`), `rules` (the rule set `init` opened it under, `RULES`), `deliverable` (`kind`, `path`, `ask`, `min_variants`), `supersedes` for a new version of an older line, and `closed` once `close` records a deliberate limit. The phase is the author's declaration; `derived_phase` (`hooks/tezgah_research.py::derived_phase`) reads the *other* authority beside it from the line's own artifacts, and the checker names the two disagreeing |
|
|
152
|
+
| `state.json` | the question, the phase (`bootstrap`/`inner`/`outer`/`concluded`), the direction, `method` and `scope` (rules 5, see "Scope lock"), the locked evaluation and its optional second gate (`capability_tolerance`, `counter_metric`), the session events (`PHASES`, `hooks/tezgah_research.py::PHASES`), `rules` (the rule set `init` opened it under, `RULES`), `deliverable` (`kind`, `path`, `ask`, `min_variants`), `supersedes` for a new version of an older line, and `closed` once `close` records a deliberate limit. The phase is the author's declaration; `derived_phase` (`hooks/tezgah_research.py::derived_phase`) reads the *other* authority beside it from the line's own artifacts, and the checker names the two disagreeing |
|
|
118
153
|
| `log.md` | the decision log, newest last: one line per decision, experiment, dead end or pivot, with the evidence that drove it |
|
|
119
154
|
| `findings.md` | the four sections every line answers, named by `FINDINGS_SECTIONS` (`hooks/tezgah_research.py::FINDINGS_SECTIONS`) |
|
|
120
|
-
| `claims.jsonl` | one JSON object per row: `statement`, `falsification`, `proof`, `provenance`, `status`, `kind`, `scope` (what the claim's numbers were measured on, which may not be wider than the rows it rests on), and `supersedes` when the row replaces an earlier claim |
|
|
155
|
+
| `claims.jsonl` | one JSON object per row: `statement`, `falsification`, `proof`, `provenance`, `status`, `kind`, `trace` (the in-scope item, rules 5), `scope` (what the claim's numbers were measured on, which may not be wider than the rows it rests on), and `supersedes` when the row replaces an earlier claim |
|
|
121
156
|
| `predictions.jsonl` | one JSON object per row: `commit` (a 40-character sha the row is bound to), `claim` (an id the line holds, or empty when the prediction stands alone), `metric`, `value_before`, `value_after`, `falsifier`, `components` (the keys of the components the change touches, read from `hooks/tezgah_components.py`), and `granted_by` when a human granted what the frozen paths below forbid |
|
|
122
157
|
| `experiments/<hypothesis>/protocol.md` | what the change is, what it predicts, what result would falsify it, why - committed before the run |
|
|
123
158
|
| `experiments/<hypothesis>/results.jsonl` | the rows the run produced, one JSON object per non-blank line, each carrying a non-empty `source` and, once declared, a `scope` of `real`, `fixture` or `derived`, and - when that scope is `fixture` - a `fixture` description of what was generated |
|
package/docs/skills.md
CHANGED
|
@@ -83,6 +83,66 @@ directory that host reads, and the host lists `name` + `description` itself -
|
|
|
83
83
|
which is why the installer counts that metadata as an always-on cost
|
|
84
84
|
(`bin/tezgah-setup::context_budget`).
|
|
85
85
|
|
|
86
|
+
## Searching skills, and reading part of one
|
|
87
|
+
|
|
88
|
+
A session meets the roster as names (omp's list runs to hundreds once a
|
|
89
|
+
`skills.customDirectories` corpus is mounted - 284 on this machine), and no host
|
|
90
|
+
searches bodies by topic. `bin/tezgah-skill` does: it indexes every installed
|
|
91
|
+
skill root - the checkout's, omp's user and custom-directories corpora,
|
|
92
|
+
Claude's, `~/.agents`, Codex's, opencode's - into heading-delimited sections
|
|
93
|
+
(`hooks/tezgah_skill_pick.py::sections`) and ranks them for a query with the
|
|
94
|
+
lessons ledger's BM25 (`hooks/tezgah_rank.py::rank`, heading weighted over
|
|
95
|
+
body). Each hit is a section:
|
|
96
|
+
|
|
97
|
+
tezgah-skill "jwt alg none"
|
|
98
|
+
skill://attack-jwt:26-122 /Users/.../attack-jwt/SKILL.md JWT Token Attack
|
|
99
|
+
|
|
100
|
+
The `skill://<name>:<start>-<end>` address is a range omp's read tool resolves
|
|
101
|
+
(line selectors are part of the `read` contract; `skill://<name>/<file>` reads a
|
|
102
|
+
neighbour file, and `/skill:<name>` injects the whole body - a command, not a
|
|
103
|
+
range). The absolute path beside it is for hosts without a `skill://`
|
|
104
|
+
transport, and both are printed because a name that differs between roots keeps
|
|
105
|
+
only omp's precedence - the search reports the first root's copy rather than
|
|
106
|
+
resolving the collision. Measured on this machine: 7 roots, 10 482 sections,
|
|
107
|
+
2.3-2.6 s cold. Fenced code is not indexed as headings, so a `# comment`
|
|
108
|
+
inside a block never masquerades as a section title.
|
|
109
|
+
|
|
110
|
+
The per-turn hint reuses the same ranking: when the armed judgement names a
|
|
111
|
+
skill (`skill-suggest-on`, [contract](contract.md)), the appended line points at
|
|
112
|
+
the matching SECTION of that skill - `Start at skill://ponytail:38-61` plus the
|
|
113
|
+
path - so the session opens the part that answers the turn instead of a name it
|
|
114
|
+
must skim (`hooks/tezgah_skill_pick.py::section_of`, pinned by
|
|
115
|
+
`tests/test_skill_pick.py::OmpHintPath` through omp's own dispatch). The search
|
|
116
|
+
CLI is local and unarmed: it costs nothing until it is run, and the judgement
|
|
117
|
+
stays opt-in exactly as before.
|
|
118
|
+
|
|
119
|
+
The always-on Session scope paragraph sends every session to it. Run
|
|
120
|
+
`tezgah-skill <words>` before working a topic a skill covers, then read only
|
|
121
|
+
the returned range (`hooks/tezgah_policy.py::CORE`). The installer links the
|
|
122
|
+
CLI into `~/.config/tezgah/bin` beside the other tezgah commands.
|
|
123
|
+
|
|
124
|
+
On a turn the judgement does not answer, the same search runs on its own,
|
|
125
|
+
default on and with no model (`hooks/tezgah_skill_pick.py::section_hint`). It
|
|
126
|
+
keeps its index as SQLite in the tezgah cache dir (`skill-search.sqlite`). It
|
|
127
|
+
rebuilds that index only when a SKILL.md's mtime or size moves
|
|
128
|
+
(`hooks/tezgah_skill_pick.py::_open_index`). A prompt reads its own terms'
|
|
129
|
+
postings and the length column, never the corpus.
|
|
130
|
+
|
|
131
|
+
The hook then appends one line, `A local skill search matched this request:
|
|
132
|
+
skill://<name>:<start>-<end>`, with the section title. Every host but omp also
|
|
133
|
+
gets the absolute path. The top section must carry 60% of the prompt's
|
|
134
|
+
distinct content terms that any skill contains, and at least three
|
|
135
|
+
(`hooks/tezgah_skill_pick.py::HINT_COVER`, `HINT_MIN_TERMS`). Each section
|
|
136
|
+
appears once per session.
|
|
137
|
+
|
|
138
|
+
Measured on this machine on 2026-10-07: 7 502 sections and a 9.5 MB index. The
|
|
139
|
+
one cold rebuild took 1.36 s, and a warm prompt takes 6-15 ms. On a turn it
|
|
140
|
+
fires the line costs 196-239 bytes on omp and 245-320 elsewhere. It is not a
|
|
141
|
+
judgement, so `judge-off` leaves it on. `reminder-off` drops it with the rest
|
|
142
|
+
of the per-turn text (`tests/test_skill_search.py::LocalHint`,
|
|
143
|
+
`OmpPromptPath`).
|
|
144
|
+
|
|
145
|
+
|
|
86
146
|
## How a skill reaches a host
|
|
87
147
|
|
|
88
148
|
`SKILLS` in `bin/tezgah-setup::SKILLS` is the shipped list, and it is the single
|
|
@@ -127,6 +187,17 @@ it (`tests/test_skills.py::SkillStandards`).
|
|
|
127
187
|
| `ai-research` | The vendored 98-skill library for AI/ML machinery - training and serving a model, benchmarks, interpretability, retrieval pipelines; read one entry, never the tree. |
|
|
128
188
|
| `rl-env` | The vendored FineEnvs skill set for authoring an RL environment from a description - OpenEnv, OpenReward/ORS, Verifiers and NeMo Gym, plus the orchestrator that ports one description across all four; read one entry, never the tree. |
|
|
129
189
|
|
|
190
|
+
The generated specialists load skills first (`hooks/tezgah_agents.py::SPECIALISTS`).
|
|
191
|
+
`tezgah-agents --list` prints the roster with tiers.
|
|
192
|
+
|
|
193
|
+
| Specialist | Skills it loads |
|
|
194
|
+
|---|---|
|
|
195
|
+
| `tezgah-docs` | `no-ai-slop` |
|
|
196
|
+
| `tezgah-ui` | `analyze-app`, `design-contract`, `design-library`, `feature-audit` |
|
|
197
|
+
| `tezgah-researcher` | `research`, `ai-research` |
|
|
198
|
+
| `tezgah-security` | the host's `wstg-*`, `attack-*`, `ci-assessment` and `llm-security` skills, which tezgah does not ship |
|
|
199
|
+
| `tezgah-tester` | none |
|
|
200
|
+
|
|
130
201
|
## Vendored material
|
|
131
202
|
|
|
132
203
|
`NOTICE` records every third-party skill and its terms: it opens by saying the
|
|
@@ -229,6 +300,9 @@ unlisted directory, and fails on a body that pairs 44x44 with Level AA again.
|
|
|
229
300
|
its index and its three trap cases
|
|
230
301
|
- `skills/rl-env/SOURCE` - the vendored FineEnvs environment-authoring set, its
|
|
231
302
|
upstream revision and the per-file sha256
|
|
303
|
+
- `bin/tezgah-skill` - the section search CLI over every installed skill root;
|
|
304
|
+
`hooks/tezgah_skill_pick.py` - `sections`, `search`, `skill_roots`,
|
|
305
|
+
`section_of`, the hint that carries the address
|
|
232
306
|
- `hooks/tezgah_policy.py` - the always-on rule text and the Claude skill name
|
|
233
307
|
- `tests/test_setup.py`, `tests/test_skills.py` - the router-line, name
|
|
234
308
|
resolution, frontmatter, collision and skill-standard tests
|
package/docs/status-line.md
CHANGED
|
@@ -105,7 +105,8 @@ must use it": a missing key or binary reads the same red as a kill switch
|
|
|
105
105
|
(`consult_options()`, `orx_bin()`, `have_judge_key()`: `hooks/tezgah_paths.py::consult_options`,
|
|
106
106
|
`hooks/tezgah_paths.py::orx_bin`, `hooks/tezgah_paths.py::have_judge_key`). `judge` is the seam
|
|
107
107
|
behind `bin/tezgah-triage`, `bin/tezgah-docs` and the skill picker
|
|
108
|
-
([judge](judge.md))
|
|
108
|
+
([judge](judge.md)). Its credential is the session's own CLI, else a third-party
|
|
109
|
+
key `fallback` allows, never
|
|
109
110
|
omp's login store. `graph` and `orch` are the two switch-only marks: nothing at
|
|
110
111
|
arming time asks whether their toolchain is on the machine, because neither rule
|
|
111
112
|
leaves the text when it is missing - the graph-first rule ships whether or not
|
|
@@ -229,6 +230,7 @@ tezgah-status /path/to/repo <id> --counters # the evidence ledger's counters
|
|
|
229
230
|
tezgah-status --legend # what each mark means
|
|
230
231
|
tezgah-status --rule-yield # live gate rules that stopped firing
|
|
231
232
|
tezgah-status --enforcers # what enforces each always-on rule
|
|
233
|
+
tezgah-status --judge # what the judge used last, and why
|
|
232
234
|
```
|
|
233
235
|
|
|
234
236
|
The session id is the second positional argument or `TEZGAH_SESSION`; without it
|
package/docs/testing.md
CHANGED
|
@@ -26,7 +26,7 @@ request (`CONTRIBUTING.md:15-20`).
|
|
|
26
26
|
`ruff` is not vendored: install it as a uv tool, run `uvx ruff check .` without
|
|
27
27
|
installing it, or use the version CI installs from `requirements-dev.txt:3`
|
|
28
28
|
(`ruff==0.16.7`). There is no other linter and no type checker
|
|
29
|
-
(`AGENTS.md:
|
|
29
|
+
(`AGENTS.md:51-52`).
|
|
30
30
|
|
|
31
31
|
The clarity ratchet, `bin/tezgah-docs --clarity`, counts four prose rules per
|
|
32
32
|
file in `docs/*.md` and `CHANGELOG.md`. The rules are sentences over 25 words,
|
|
@@ -41,7 +41,7 @@ baseline on the next run. Only `--clarity --update` raises it.
|
|
|
41
41
|
A pin is a claim about what ran, not a guarantee about what will. This repository
|
|
42
42
|
has two, and both are about the language rather than about dependencies: the
|
|
43
43
|
floor `py310` (`pyproject.toml:5`) and the CI matrix that proves it
|
|
44
|
-
(`.github/workflows/ci.yml:
|
|
44
|
+
(`.github/workflows/ci.yml:114`). There is no dependency lockfile to pin
|
|
45
45
|
because there is no package: tezgah is a set of scripts run in place, and no
|
|
46
46
|
`[project]` or build system is declared (`pyproject.toml:1-2`). The only
|
|
47
47
|
third-party tool the checks name is ruff, at a version in
|
|
@@ -57,30 +57,42 @@ conclude from either.
|
|
|
57
57
|
|
|
58
58
|
## Continuous integration
|
|
59
59
|
|
|
60
|
-
`.github/workflows/ci.yml` runs on
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
60
|
+
`.github/workflows/ci.yml` runs on a push to `main` and on a pull request, so
|
|
61
|
+
a branch commit runs once, through its pull request, not twice. A tag push does
|
|
62
|
+
not start it. `release.yml` calls it (`ci.yml:13`) as its `ci` job, so npm and
|
|
63
|
+
brew publish only after CI passed on the release commit. A newer push to the
|
|
64
|
+
same ref cancels the run it replaces, except on a release (`concurrency`). Every
|
|
65
|
+
job has a `timeout-minutes`, so a hung test cannot hold a runner for six hours.
|
|
66
|
+
No matrix cancels its other legs when one fails (`fail-fast: false`).
|
|
67
|
+
|
|
68
|
+
- `changes` diffs against the commit the push replaced or the
|
|
69
|
+
pull request's base. A change only under `docs/`, `assets/`, a root `*.md`
|
|
70
|
+
page or the GitHub templates is prose: it runs `docs` and nothing else. An
|
|
71
|
+
empty diff, a base it cannot read, a release or a manual run runs everything.
|
|
72
|
+
- `docs`, prose only: the citations audit and
|
|
73
|
+
`tests/impacted.py --ref <base>`, which maps a docs page to the docs modules.
|
|
74
|
+
- `test` (`ci.yml:100`) on Python 3.10 and 3.14, the floor and the ceiling.
|
|
75
|
+
It byte-compiles, lints with ruff from `requirements-dev.txt` (pip cached),
|
|
76
|
+
audits the docs citations and runs `TEZGAH_E2E_STRICT=1 python3
|
|
77
|
+
tests/e2e_plan_flow.py`. On `main` it also runs the serial suite with `-v`.
|
|
78
|
+
A pull request skips that step, and so does a release, which passes the
|
|
79
|
+
`release` input and runs one 3.10 leg.
|
|
80
|
+
- `test-sharded` (`ci.yml:145`) on Python 3.10 and 3.14 with node 22 (the
|
|
81
|
+
first line that loads the omp `.ts` hook without a flag). On `main` and a
|
|
82
|
+
release it runs `python3 tests/impacted.py --all`. On a pull request it runs
|
|
83
|
+
`--ref <base>`: the modules the change maps to, and the whole suite when the
|
|
84
|
+
map does not know a path. It is a shadow that started 2026-10-05. After four
|
|
85
|
+
weeks it replaces the serial `test` step only if it missed no failure that
|
|
86
|
+
step caught.
|
|
87
|
+
- `apps-e2e` (`ci.yml:178`) on Python 3.12 and node 20: one step,
|
|
76
88
|
`TEZGAH_E2E_STRICT=1 python3 tests/e2e_analyze_wiring.py`. It completes the
|
|
77
89
|
app-MCP handshake only - no browser, no device - so it stays deterministic;
|
|
78
90
|
strict mode makes a server that cannot start a failure rather than a skip, so
|
|
79
91
|
a broken wire cannot hide behind a skip (`e2e_analyze_wiring.py:4-12`).
|
|
80
|
-
- `artifact-install` (`ci.yml:
|
|
92
|
+
- `artifact-install` (`ci.yml:200`) on Python 3.10 and 3.12: builds the
|
|
81
93
|
tarball, installs from it under a temp prefix, and runs the artifact smoke on
|
|
82
94
|
the installed tree instead of the checkout.
|
|
83
|
-
- `artifact-install-windows` (`ci.yml:
|
|
95
|
+
- `artifact-install-windows` (`ci.yml:243`) on `windows-latest`: the same
|
|
84
96
|
build, install and smoke through `packaging/install.ps1` - the twin a Windows
|
|
85
97
|
user runs - so it is the only place the Windows claim is proven. It then runs
|
|
86
98
|
the opencode plugin's path tests natively, `tezgah update --dry-run`, the
|
|
@@ -233,7 +245,7 @@ a browser or a device) or to a `_probe_*.py` helper the tests call.
|
|
|
233
245
|
- **No network.** Loopback stubs only, as above.
|
|
234
246
|
- **Two e2e scripts stay opt-in and local**: `e2e_analyze_web.py` and
|
|
235
247
|
`e2e_analyze_mobile.py`, behind `TEZGAH_E2E_APPS=1`, with the wiring handshake
|
|
236
|
-
as their CI substitute (`AGENTS.md:
|
|
248
|
+
as their CI substitute (`AGENTS.md:119-125`).
|
|
237
249
|
|
|
238
250
|
## Adding a test
|
|
239
251
|
|
package/docs/vision.md
CHANGED
|
@@ -51,14 +51,18 @@ change that would silently run nothing.
|
|
|
51
51
|
|
|
52
52
|
## Model routing
|
|
53
53
|
|
|
54
|
-
Every omp agent gets a **
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
54
|
+
Every omp agent gets a **fallback chain** (`hooks/tezgah_models.py::_chain`, over
|
|
55
|
+
`hooks/tezgah_models.py::funded_families`). The mode's family goes first. By
|
|
56
|
+
default the rest is the same vendor's model through another funded provider, such
|
|
57
|
+
as Opus through OpenRouter on an Anthropic session. `fallback: any` in
|
|
58
|
+
`config.json` adds every other funded family, and `none` keeps the first entry
|
|
59
|
+
alone. One pin per agent made one provider's 429 every subagent's failure, because
|
|
60
|
+
the account is shared with the main thread (measured: 24 of 26 post-routing 429s hit
|
|
61
|
+
a subagent's first request, all on one Opus account). A chain that left the vendor
|
|
62
|
+
ran an Opus session's subagents on GPT and GLM, so that step is opt-in. A family
|
|
63
|
+
whose credential cannot be detected is left out of the chain, never guessed in. The
|
|
64
|
+
main thread's own model, and the provider-keyed chains, stay the user's settings.
|
|
65
|
+
tezgah writes only the keys it owns.
|
|
62
66
|
|
|
63
67
|
What tezgah cannot fix from inside: a provider that stalls on a very large
|
|
64
68
|
context (`OpenAI completions stream stalled`, five minutes without an event on a
|
|
@@ -26,6 +26,7 @@ try:
|
|
|
26
26
|
from tezgah_integrity import ( # noqa: E402
|
|
27
27
|
SUBAGENT_CHANNEL, note_tool, ran_nothing, report_bytes, untrusted_source)
|
|
28
28
|
from tezgah_paths import root_for # noqa: E402
|
|
29
|
+
from tezgah_taste import write_note # noqa: E402
|
|
29
30
|
from tezgah_untrusted import marks # noqa: E402
|
|
30
31
|
except Exception as exc:
|
|
31
32
|
import_failed(exc)
|
|
@@ -139,6 +140,12 @@ def main():
|
|
|
139
140
|
# empty-run contract (tezgah_integrity.EMPTY_RUN)
|
|
140
141
|
empty_run=ran_nothing(result),
|
|
141
142
|
agent=agent)
|
|
143
|
+
# the opt-in taste note: the learnings in scope for the first write to this
|
|
144
|
+
# file type in the session (hooks/tezgah_taste.write_note), beside the
|
|
145
|
+
# provenance line rather than instead of it
|
|
146
|
+
if event == "PostToolUse" and not failed:
|
|
147
|
+
notice = "\n".join(t for t in (notice, safe(session_id, write_note, session_id,
|
|
148
|
+
inp, cwd, tool)) if t)
|
|
142
149
|
if notice:
|
|
143
150
|
json.dump({"hookSpecificOutput": {
|
|
144
151
|
"hookEventName": event,
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
"""Claude Code PreToolUse hook (matcher Agent|Task|Grep|Bash), inert outside roots.
|
|
3
3
|
|
|
4
4
|
Claude's envelope around the shared gate (hooks/tezgah_gate.py): refuse the
|
|
5
|
-
grep-only explorer subagent, and nudge the first
|
|
6
|
-
session toward the code graph. Later searches pass
|
|
5
|
+
grep-only explorer subagent, and nudge the first code-symbol search of a
|
|
6
|
+
session toward the code graph. Later symbol searches pass with the graph named
|
|
7
|
+
beside them (`additionalContext`, which Claude reads with the tool result).
|
|
7
8
|
"""
|
|
8
9
|
import json
|
|
9
10
|
import os
|
|
@@ -12,11 +13,17 @@ import sys
|
|
|
12
13
|
sys.path.insert(0, os.path.dirname(os.path.realpath(__file__)))
|
|
13
14
|
from tezgah_guard import import_failed, safe # noqa: E402
|
|
14
15
|
try:
|
|
15
|
-
from tezgah_gate import decision # noqa: E402
|
|
16
|
+
from tezgah_gate import decision, graph_advice # noqa: E402
|
|
16
17
|
except Exception as exc:
|
|
17
18
|
import_failed(exc)
|
|
18
19
|
|
|
19
20
|
|
|
21
|
+
def verdict(tool, inp, cwd, session_id, agent):
|
|
22
|
+
"""(deny reason, graph line): the line only for a call the gate lets through."""
|
|
23
|
+
reason = decision(tool, inp, cwd, session_id, agent=agent)
|
|
24
|
+
return reason, None if reason else graph_advice(tool, inp, cwd, "claude")
|
|
25
|
+
|
|
26
|
+
|
|
20
27
|
def main():
|
|
21
28
|
try:
|
|
22
29
|
p = json.load(sys.stdin)
|
|
@@ -27,14 +34,19 @@ def main():
|
|
|
27
34
|
inp = p.get("tool_input") or {}
|
|
28
35
|
# `agent_id` is set only when the call comes from a subagent (Claude's hook
|
|
29
36
|
# reference, common input fields): the repeat ceilings key on it
|
|
30
|
-
reason = safe(p.get("session_id"),
|
|
31
|
-
|
|
37
|
+
reason, advice = safe(p.get("session_id"), verdict, tool, inp, cwd,
|
|
38
|
+
p.get("session_id"), p.get("agent_id")) or (None, None)
|
|
32
39
|
if reason:
|
|
33
40
|
json.dump({"hookSpecificOutput": {
|
|
34
41
|
"hookEventName": "PreToolUse",
|
|
35
42
|
"permissionDecision": "deny",
|
|
36
43
|
"permissionDecisionReason": reason,
|
|
37
44
|
}}, sys.stdout)
|
|
45
|
+
elif advice:
|
|
46
|
+
json.dump({"hookSpecificOutput": {
|
|
47
|
+
"hookEventName": "PreToolUse",
|
|
48
|
+
"additionalContext": advice,
|
|
49
|
+
}}, sys.stdout)
|
|
38
50
|
|
|
39
51
|
|
|
40
52
|
main()
|
package/hooks/tezgah_agents.py
CHANGED
|
@@ -79,7 +79,12 @@ EMPTY_IS_NOT_PROOF = (
|
|
|
79
79
|
STEER = (("mechanical edit (tier from `tezgah-route`)", "tezgah-cheap"),
|
|
80
80
|
("bounded code or tests", "tezgah-standard"),
|
|
81
81
|
("design, invariants, unknown-cause debugging", "tezgah-frontier"),
|
|
82
|
-
("review of a diff", "tezgah-reviewer")
|
|
82
|
+
("review of a diff", "tezgah-reviewer"),
|
|
83
|
+
("running the impacted checks", "tezgah-tester"),
|
|
84
|
+
("docs and path:line citations", "tezgah-docs"),
|
|
85
|
+
("security review", "tezgah-security"),
|
|
86
|
+
("running-app UI or design analysis", "tezgah-ui"),
|
|
87
|
+
("a research line", "tezgah-researcher"))
|
|
83
88
|
|
|
84
89
|
|
|
85
90
|
def manifest_sha():
|
|
@@ -235,6 +240,104 @@ def _worker_body(tier):
|
|
|
235
240
|
return body
|
|
236
241
|
|
|
237
242
|
|
|
243
|
+
# The specialists beside the tier workers: name -> (what it does, the skills it
|
|
244
|
+
# loads first, its tools, the role text). The tier is not here: it is
|
|
245
|
+
# tezgah_models.AGENT_SLOT, the row every host's model line is rendered from, so
|
|
246
|
+
# the description cannot name one tier while the frontmatter runs another.
|
|
247
|
+
# A skill is shipped under skills/ unless HOST_SKILLS names it: those are the
|
|
248
|
+
# security families a host installs on its own, which tezgah does not ship.
|
|
249
|
+
HOST_SKILLS = ("wstg-*", "attack-*", "ci-assessment", "llm-security")
|
|
250
|
+
SPECIALISTS = {
|
|
251
|
+
"tezgah-tester": (
|
|
252
|
+
"Runs only the checks a change reaches and reports them verbatim: the "
|
|
253
|
+
"repo's impacted-test runner or the tests that import the changed "
|
|
254
|
+
"files, never the full suite unless the brief asks.",
|
|
255
|
+
(), "shell to run checks; no source or test edits",
|
|
256
|
+
"Take the changed files from the brief or `git diff --name-only`. Run\n"
|
|
257
|
+
"the repository's own impacted-test runner if it has one (for example\n"
|
|
258
|
+
"`python3 tests/impacted.py --run <files>`), else the test files that\n"
|
|
259
|
+
"import the changed modules. Keep each check last on its line with its\n"
|
|
260
|
+
"output in a file, then read the file in a separate call: never pipe a\n"
|
|
261
|
+
"check into tail or grep, never `|| true`, never `--no-verify`, never a\n"
|
|
262
|
+
"new skip. Set the shell timeout above 300 s for a long run. Report each\n"
|
|
263
|
+
"check as passed or failed with its counts and the exact failure text.\n"
|
|
264
|
+
"Never edit source or test files; a fix belongs to the caller."),
|
|
265
|
+
"tezgah-docs": (
|
|
266
|
+
"Writes and repairs docs pages and their `path:line` / `path::symbol` "
|
|
267
|
+
"citations after a code change, under the repo's own docs checks.",
|
|
268
|
+
("no-ai-slop",), "read, edit and write docs; shell for the docs checks",
|
|
269
|
+
"Edit only documentation files the brief names. Cite code as\n"
|
|
270
|
+
"`path::symbol`; when a code change shifted a `path:line` citation,\n"
|
|
271
|
+
"re-anchor it by whole token (`:87` must never rewrite `:870`), map each\n"
|
|
272
|
+
"changed line to its replacement block, and run the repo's citation and\n"
|
|
273
|
+
"clarity checks (`bin/tezgah-docs --citations`, `--clarity` where they\n"
|
|
274
|
+
"exist) until they report zero. Plain sentences, one fact each."),
|
|
275
|
+
"tezgah-security": (
|
|
276
|
+
"Read-only security review of a change or surface: injection, authn/authz, "
|
|
277
|
+
"secrets, SSRF, deserialisation, supply chain; every finding with a "
|
|
278
|
+
"concrete exploit path and file:line.",
|
|
279
|
+
HOST_SKILLS, "read-only; shell only for read-only git and the codegraph CLI",
|
|
280
|
+
"Pick the skills that match the class under test: `wstg-injection`,\n"
|
|
281
|
+
"`wstg-auth-session`, `wstg-logic-client-api`, `wstg-recon-config`,\n"
|
|
282
|
+
"the matching `attack-*` entry (attack-ssrf, attack-jwt, ...),\n"
|
|
283
|
+
"`ci-assessment` for pipelines, `llm-security` for prompt handling.\n"
|
|
284
|
+
"Trace each untrusted input to its sink. Classify every candidate as\n"
|
|
285
|
+
"confirmed (exploit path + file:line), refuted (the guard that stops it)\n"
|
|
286
|
+
"or unverified. Never write, never run an exploit against a live system,\n"
|
|
287
|
+
"never print a secret you find - name its location only."),
|
|
288
|
+
"tezgah-ui": (
|
|
289
|
+
"Inspects a running web or mobile app and judges it against the design "
|
|
290
|
+
"floor: component x state coverage, layout, contrast and WCAG 2.2, with "
|
|
291
|
+
"the screen it read as evidence.",
|
|
292
|
+
("analyze-app", "design-contract", "design-library", "feature-audit"),
|
|
293
|
+
"shell and browser/device tools; no source edits",
|
|
294
|
+
"Read the running app, never only its source: a UI claim inferred from\n"
|
|
295
|
+
"code is not evidence. Measure against `.tezgah/design-contract.md` when\n"
|
|
296
|
+
"it exists (`tezgah-design check`). Every finding names the screen,\n"
|
|
297
|
+
"breakpoint and state it came from and a 0-4 severity. Report findings;\n"
|
|
298
|
+
"do not edit source files."),
|
|
299
|
+
"tezgah-researcher": (
|
|
300
|
+
"Runs one research line through OpenResearch: hypotheses, experiments, "
|
|
301
|
+
"literature, a report - the protocol frozen before the run.",
|
|
302
|
+
("research", "ai-research"),
|
|
303
|
+
"shell for `orx` and `tezgah-research`; writes only under .tezgah/research",
|
|
304
|
+
"Load the OpenResearch manual first (`orx skill`) and follow its\n"
|
|
305
|
+
"experiment-tree rules. Work inside the one line the brief names; never\n"
|
|
306
|
+
"open a second line while one is open (`tezgah-research status`). Freeze\n"
|
|
307
|
+
"the protocol and the criteria before any run; a correction after a run\n"
|
|
308
|
+
"goes in `amendments.md`. Run `tezgah-research check` before reporting.\n"
|
|
309
|
+
"If `orx` is not installed, say so and stop."),
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def _specialist_desc(name):
|
|
314
|
+
what, skills, tools, _role = SPECIALISTS[name]
|
|
315
|
+
named = ", ".join(s + (" (host-installed)" if s in HOST_SKILLS else "")
|
|
316
|
+
for s in skills)
|
|
317
|
+
return "%s Skills: %s. Tier: %s (tezgah-route). Tools: %s." % (
|
|
318
|
+
what, named or "none", tm.AGENT_SLOT[name], tools)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def _specialist_body(name):
|
|
322
|
+
_what, skills, _tools, role = SPECIALISTS[name]
|
|
323
|
+
|
|
324
|
+
def body(_host):
|
|
325
|
+
load = ("Load these skills before anything else, those this host has: "
|
|
326
|
+
"%s.\n" % ", ".join("`%s`" % s for s in skills)) if skills else ""
|
|
327
|
+
return ("You are %s, a specialist on the %s model tier. Do exactly the\n"
|
|
328
|
+
"brief: stay inside the files and paths it names (absolute paths\n"
|
|
329
|
+
"under the worktree it gives, never the main checkout), and\n"
|
|
330
|
+
"report what you did and what each command printed, verbatim.\n"
|
|
331
|
+
"%s%s\nIf the brief cannot be met as written, say so and stop.\n"
|
|
332
|
+
"Never spawn subagents." % (name, tm.AGENT_SLOT[name], load, role))
|
|
333
|
+
return body
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def _specialist(name, readonly=False):
|
|
337
|
+
return (name, _specialist_desc(name), lambda infra: True,
|
|
338
|
+
_specialist_body(name), readonly)
|
|
339
|
+
|
|
340
|
+
|
|
238
341
|
# name, description, capability gate, body(host), read-only?
|
|
239
342
|
ROLES = (
|
|
240
343
|
("tezgah-reviewer",
|
|
@@ -257,6 +360,11 @@ ROLES = (
|
|
|
257
360
|
"unknown-cause debugging and hard-to-reverse changes on the strongest model "
|
|
258
361
|
"tier; also where an ESCALATE from a cheaper worker is restarted.",
|
|
259
362
|
lambda infra: True, _worker_body("frontier"), False),
|
|
363
|
+
_specialist("tezgah-tester"),
|
|
364
|
+
_specialist("tezgah-docs"),
|
|
365
|
+
_specialist("tezgah-security", readonly=True),
|
|
366
|
+
_specialist("tezgah-ui"),
|
|
367
|
+
_specialist("tezgah-researcher"),
|
|
260
368
|
)
|
|
261
369
|
|
|
262
370
|
# The roles gated on the code graph, which a repo's `.no-graph` turns off.
|
|
@@ -264,25 +372,38 @@ GRAPH_ROLES = ("tezgah-reviewer",)
|
|
|
264
372
|
# Every agent name tezgah ever generated: the live roles, the orchestrator and
|
|
265
373
|
# the retired roles. omp's files carry no MARKER, so this list - not the
|
|
266
374
|
# `tezgah-` prefix - is what makes a file there tezgah's to sweep.
|
|
267
|
-
|
|
375
|
+
# tezgah-researcher was retired and came back as the OpenResearch specialist.
|
|
376
|
+
RETIRED_ROLES = ("tezgah-explorer", "tezgah-verifier")
|
|
268
377
|
OWNED_NAMES = tuple(r[0] for r in ROLES) + ("tezgah-orchestrator",) + RETIRED_ROLES
|
|
269
378
|
|
|
270
379
|
ORCH_DESC = ("Route work in this repo to the generated tezgah-* subagents. The "
|
|
271
|
-
"main thread decides and verifies;
|
|
272
|
-
"
|
|
380
|
+
"main thread decides and verifies; two or more independent items "
|
|
381
|
+
"fan out to parallel subagents in one message, one git worktree per "
|
|
382
|
+
"writing slice, and no subagent orchestrates another.")
|
|
273
383
|
|
|
274
384
|
|
|
275
385
|
def _orch_body(names):
|
|
276
386
|
listed = ", ".join(names) if names else "(none active yet)"
|
|
277
387
|
return (
|
|
278
388
|
"You are the tezgah orchestrator for this repository. You decide and\n"
|
|
279
|
-
"verify; you delegate
|
|
280
|
-
"
|
|
389
|
+
"verify; you delegate execution and read the evidence back. Available\n"
|
|
390
|
+
"specialists: %s.\n\n"
|
|
391
|
+
"Fan out by default: when the request or your todo list holds two or more\n"
|
|
392
|
+
"independent items, spawn one subagent per item in ONE message. Give\n"
|
|
393
|
+
"every writing slice its own branch and checkout - `orca worktree create\n"
|
|
394
|
+
"--name <slug> --parent-worktree active --json` when the session runs\n"
|
|
395
|
+
"inside Orca, else `git worktree add -b <branch> <absolute path>` - and\n"
|
|
396
|
+
"brief it with absolute paths under that checkout only, never the main\n"
|
|
397
|
+
"checkout. Serialize only a real\n"
|
|
398
|
+
"dependency, and never wait idle while independent work remains; you\n"
|
|
399
|
+
"integrate the branches and run the checks once on the merged tree.\n\n"
|
|
281
400
|
"Route reviews to tezgah-reviewer, with the changed files and symbols in\n"
|
|
282
|
-
"the brief: where it has no shell it cannot run `git diff` itself.
|
|
283
|
-
"
|
|
284
|
-
"
|
|
285
|
-
"
|
|
401
|
+
"the brief: where it has no shell it cannot run `git diff` itself. Checks\n"
|
|
402
|
+
"go to tezgah-tester, docs and citations to tezgah-docs, a security review\n"
|
|
403
|
+
"to tezgah-security, a running app to tezgah-ui, a research line to\n"
|
|
404
|
+
"tezgah-researcher. Brief a code-discovery task to a tier worker with the\n"
|
|
405
|
+
"codegraph tools; a second opinion (`consult`) stays with the main\n"
|
|
406
|
+
"thread. Other work goes to the tier worker\n"
|
|
286
407
|
"`tezgah-route \"<brief>\"` names (tezgah-cheap, tezgah-standard,\n"
|
|
287
408
|
"tezgah-frontier); an ESCALATE answer is restarted on tezgah-frontier with\n"
|
|
288
409
|
"the same brief. Never delegate a task a specialist is not\n"
|
|
@@ -407,10 +528,11 @@ def omp_user_agents(root="~"):
|
|
|
407
528
|
names = [r[0] for r in active]
|
|
408
529
|
if not names:
|
|
409
530
|
return {}
|
|
531
|
+
# A role that writes gets every tool the session has; a read-only one gets
|
|
532
|
+
# the read tools plus the shell its body bounds to read-only commands.
|
|
410
533
|
out = {name + ".md": render_md_omp(
|
|
411
|
-
name, desc, body("omp"),
|
|
412
|
-
|
|
413
|
-
for name, desc, _cap, body, _readonly in active}
|
|
534
|
+
name, desc, body("omp"), **({} if readonly else {"tools": None}))
|
|
535
|
+
for name, desc, _cap, body, readonly in active}
|
|
414
536
|
out["tezgah-orchestrator.md"] = render_md_omp(
|
|
415
537
|
"tezgah-orchestrator", ORCH_DESC, _orch_body(names),
|
|
416
538
|
tools=("read", "grep", "glob", "bash", "task"))
|
|
@@ -114,6 +114,9 @@ COMPONENTS = [
|
|
|
114
114
|
{"key": "workspace", "label": "workspace",
|
|
115
115
|
"files": ["hooks/tezgah_policy.py", "hooks/tezgah_gate.py"],
|
|
116
116
|
"switch": "workspace-off", "editable": False, "revert": "git"},
|
|
117
|
+
{"key": "fanout", "label": "fanout",
|
|
118
|
+
"files": ["hooks/tezgah_policy.py", "hooks/tezgah_agents.py"],
|
|
119
|
+
"switch": "orchestrate-off", "editable": True, "revert": "git"},
|
|
117
120
|
{"key": "skills", "label": "skills",
|
|
118
121
|
"files": ["skills/"],
|
|
119
122
|
"switch": "none", "editable": True, "revert": "git"},
|