@r1z4x/tezgah 1.3.0 → 1.5.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 +103 -1
- package/MANIFEST +3 -0
- package/agents/tezgah-reviewer.md +19 -10
- package/bin/tezgah-docs +3 -3
- package/bin/tezgah-doctor +75 -18
- package/bin/tezgah-route +5 -2
- package/bin/tezgah-setup +30 -18
- package/bin/tezgah-status +3 -1
- package/bin/tezgah-task +130 -37
- package/bin/tezgah-taste +74 -55
- package/bin/tezgah-triage +12 -7
- package/docs/architecture.md +4 -4
- package/docs/clarity-baseline.json +9 -9
- package/docs/contract.md +22 -12
- package/docs/environment.md +4 -1
- package/docs/evidence.md +71 -34
- package/docs/gate.md +21 -21
- package/docs/glossary.md +5 -5
- package/docs/hosts.md +8 -8
- package/docs/judge.md +93 -27
- package/docs/operations.md +69 -30
- package/docs/skills.md +2 -2
- package/docs/status-line.md +7 -7
- package/docs/vision.md +10 -5
- package/hooks/tezgah_agents.py +22 -11
- package/hooks/tezgah_attest.py +15 -22
- package/hooks/tezgah_context.py +84 -124
- package/hooks/tezgah_gate.py +26 -23
- package/hooks/tezgah_guard.py +9 -11
- package/hooks/tezgah_integrity.py +77 -178
- package/hooks/tezgah_judge.py +270 -122
- package/hooks/tezgah_lessons.py +18 -63
- package/hooks/tezgah_paths.py +183 -55
- package/hooks/tezgah_policy.py +35 -8
- package/hooks/tezgah_replay.py +57 -33
- package/hooks/tezgah_shapes.py +4 -4
- package/hooks/tezgah_skill_pick.py +6 -35
- package/hooks/tezgah_snapshot.py +25 -46
- package/hooks/tezgah_store.py +1268 -0
- package/hooks/tezgah_task.py +146 -40
- package/hooks/tezgah_taste.py +21 -18
- package/hooks/tezgah_taste_ledger.py +63 -88
- package/hooks/tezgah_update.py +6 -26
- package/hosts/codex/hook.py +5 -0
- package/hosts/cursor/hook.py +19 -31
- package/hosts/opencode/plugins/tezgah.js +309 -108
- package/package.json +1 -1
- package/skills/harness/SKILL.md +4 -0
- package/skills/plan-add/SKILL.md +23 -7
- package/skills/plan-add/render_table.py +3 -2
- package/skills/plan-status/SKILL.md +12 -4
- package/skills/plan-sync/SKILL.md +19 -9
- package/skills/tezgah-contract/SKILL.md +25 -12
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,106 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [1.5.0] - 2026-10-09
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- **A plan gets one review, at most two rounds.** The review reads the plan's
|
|
14
|
+
whole diff once, in its verification phase. Round one spawns its reviewers in
|
|
15
|
+
one message, one per dimension, or one reviewer for a diff under 300 lines.
|
|
16
|
+
Only a confirmed critical or major finding opens round two. Round two reads
|
|
17
|
+
the fix's delta. A minor finding goes on the fix-later list. `tezgah-task review`
|
|
18
|
+
refuses a third round. What is left after round two goes to the user. The
|
|
19
|
+
reviewer agent, the contract and plan-sync now state this one rule. Before,
|
|
20
|
+
the reviewer looped until a round confirmed nothing.
|
|
21
|
+
- **One Acceptance rule.** A plan enters a writing phase only when each
|
|
22
|
+
Acceptance item names its command or says `unverifiable` and why. Before, one
|
|
23
|
+
command-bearing item let the move through. `render_table --acceptance
|
|
24
|
+
--strict` then refused the same plan. Both now read one predicate,
|
|
25
|
+
`tezgah_task.unproven`, and the refusal names each item's line.
|
|
26
|
+
- **Typed decisions take any Jev carrier.** Taste's `learn`, `label`,
|
|
27
|
+
`calibrate`, `gate` and `measure --typed` require the Jev model, not
|
|
28
|
+
TypeSafe's address. So do taste's background start and replay's
|
|
29
|
+
`--label-model`. OpenRouter System One, Cloudflare Workers AI or a
|
|
30
|
+
compatible endpoint counts the same. Old `typesafe` rows keep counting.
|
|
31
|
+
`learn`'s report key `typesafe` is now `jev`. Replay's `--provider typesafe`
|
|
32
|
+
is now `--provider jev`, and the old name still works. The triage's no-judge
|
|
33
|
+
message names every carrier, the selection knob and the session CLIs.
|
|
34
|
+
- **The fan-out rule arms on the lists the owner writes.** Items written
|
|
35
|
+
`1 - ...`, `1- `, `1:`, `a)` or `+` now arm it. An inline `1. ... 2. ...`
|
|
36
|
+
arms it, and so does "subagentlerle". Three files named as one list arm it
|
|
37
|
+
too, and so do "First, ... Second, ..." and three imperative clauses. A later
|
|
38
|
+
list in the same session gets a line that names the action: count the items,
|
|
39
|
+
one subagent per item in one message. The rule covers every phase: a plan's
|
|
40
|
+
tasks, its acceptance commands and a survey's sources fan out too. The clause
|
|
41
|
+
that let a list of small items run one by one is gone.
|
|
42
|
+
|
|
43
|
+
### Added
|
|
44
|
+
|
|
45
|
+
- **Plan order and progress in `tezgah-task status`.** A plan can name the
|
|
46
|
+
plans it waits on with `after:`. `status` prints each open plan's ticked
|
|
47
|
+
Acceptance boxes and what it still waits on. A `parallel:` line names the
|
|
48
|
+
ready plans whose `allowed_paths:` share no prefix. Those can run at once.
|
|
49
|
+
An open plan whose `status:` is not open or blocked is a `FAIL` line, and so
|
|
50
|
+
is an `after:` id no plan carries. `--json` adds `open` and `parallel`.
|
|
51
|
+
- **The judge asks opencode, Cursor and Codex sessions too.** The judgement
|
|
52
|
+
seam's session CLI was omp or claude only. It now also runs `opencode run`,
|
|
53
|
+
`cursor-agent -p` and `codex exec` headless when their host's marker is set
|
|
54
|
+
(`OPENCODE`, `CURSOR_AGENT`/`CURSOR_VERSION`, `CODEX_THREAD_ID`).
|
|
55
|
+
`TEZGAH_JUDGE_CLI`, else `judge_cli` in `config.json`, picks one of the five
|
|
56
|
+
by name. A picked CLI that is not installed means no session CLI.
|
|
57
|
+
- **Jev through any carrier.** The judge reaches Jev through TypeSafe,
|
|
58
|
+
OpenRouter, Cloudflare Workers AI or a System One-compatible URL
|
|
59
|
+
(`JEV_API_BASE_URL`, `JEV_API_KEY`).
|
|
60
|
+
`TEZGAH_JEV_PROVIDER`, `JEV_PROVIDER` or config.json `jev` picks the carrier,
|
|
61
|
+
and `tezgah-setup --jev <value>` writes the config key. `auto` asks every
|
|
62
|
+
carrier that resolves. A named carrier without its credential gets no
|
|
63
|
+
request, and no other carrier stands in. `tezgah-status --judge` prints the
|
|
64
|
+
choice and its carriers.
|
|
65
|
+
- **The omp login is a TypeSafe key.** With no `TYPESAFE_API_KEY` and no key
|
|
66
|
+
file, the seam reads the active omp login, read-only.
|
|
67
|
+
- **Clef and Clef-flash as typed judges.** Cloudflare's own decision models
|
|
68
|
+
answer the System One API on Workers AI with the Cloudflare token and account
|
|
69
|
+
id. `--jev clef` or `--jev clef-flash` picks one, and `auto` asks them after
|
|
70
|
+
the Jev carriers. A typed decision counts them like Jev. Cost rows now price
|
|
71
|
+
the carrier that answered: $0.24 per 1M input for Clef, $0.09 for Clef-flash.
|
|
72
|
+
|
|
73
|
+
### Fixed
|
|
74
|
+
|
|
75
|
+
- **A spent key stops costing a request.** OpenRouter answers 403 to a key
|
|
76
|
+
past its limit. A 403 now marks a provider down for five minutes, like a 401.
|
|
77
|
+
|
|
78
|
+
## [1.4.0] - 2026-10-08
|
|
79
|
+
|
|
80
|
+
### Changed
|
|
81
|
+
|
|
82
|
+
- **Taste state lives in SQLite.** The repository's signals, decisions,
|
|
83
|
+
defects, labels, injections, gate state, learnings and write-note history
|
|
84
|
+
move to `.tezgah/taste/taste.db`. User-scope learnings move to
|
|
85
|
+
`~/.config/tezgah/taste/taste.db`. Opening a store imports the old JSON and
|
|
86
|
+
JSONL files and renames each to `<name>.imported`. It also takes rows an
|
|
87
|
+
older install appends to an old file later. `taste.md` stays a text file.
|
|
88
|
+
- **The evidence ledger lives in SQLite.** Session rows move from
|
|
89
|
+
`~/.cache/tezgah/evidence/<session>.jsonl` to the `evidence` table of
|
|
90
|
+
`~/.cache/tezgah/tezgah.db`. Each row is one INSERT, so no reader meets a
|
|
91
|
+
torn line. A read or write of a session imports its old JSONL file first.
|
|
92
|
+
Session start imports the rest in the background once a day, and
|
|
93
|
+
`tezgah-doctor --import-evidence` imports them now. opencode's plugin uses
|
|
94
|
+
the same database through node:sqlite. On node before 22.5 it calls the
|
|
95
|
+
store's `evidence` CLI. The old files stay in place: an opencode process
|
|
96
|
+
started before the upgrade may still append to one. `tezgah-doctor --clean`
|
|
97
|
+
deletes a session idle past the retention window.
|
|
98
|
+
- **The small cache stores live in the same database.** They move from files
|
|
99
|
+
under `~/.cache/tezgah/` to tables of `~/.cache/tezgah/tezgah.db`. These are
|
|
100
|
+
the turn stamps, switch baselines, used-tool marks and status marks. The
|
|
101
|
+
nudge marks, the judge's marks, the skill hints and the update check move
|
|
102
|
+
too. So do the lesson-taint index, Cursor's marks and replies, the snapshot
|
|
103
|
+
and replay indexes and the taste-learn stamps. The first open of the
|
|
104
|
+
database imports each store's old files once and deletes them. A later open
|
|
105
|
+
deletes, unread, a file an older release still running writes there. Snapshot
|
|
106
|
+
blobs, replay runs and logs stay files. `tezgah-doctor --clean` sweeps the
|
|
107
|
+
stamps and marks there.
|
|
108
|
+
|
|
9
109
|
## [1.3.0] - 2026-10-07
|
|
10
110
|
|
|
11
111
|
### Added
|
|
@@ -833,7 +933,9 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
833
933
|
the copy could not find `hooks/` and died on import. It now links into the
|
|
834
934
|
installed tree, and also installs the command as `tezgah`.
|
|
835
935
|
|
|
836
|
-
[Unreleased]: https://github.com/r1z4x/tezgah/compare/v1.
|
|
936
|
+
[Unreleased]: https://github.com/r1z4x/tezgah/compare/v1.5.0...HEAD
|
|
937
|
+
[1.5.0]: https://github.com/r1z4x/tezgah/releases/tag/v1.5.0
|
|
938
|
+
[1.4.0]: https://github.com/r1z4x/tezgah/releases/tag/v1.4.0
|
|
837
939
|
[1.3.0]: https://github.com/r1z4x/tezgah/releases/tag/v1.3.0
|
|
838
940
|
[1.2.0]: https://github.com/r1z4x/tezgah/releases/tag/v1.2.0
|
|
839
941
|
[1.1.0]: https://github.com/r1z4x/tezgah/releases/tag/v1.1.0
|
package/MANIFEST
CHANGED
|
@@ -142,6 +142,7 @@ hooks/tezgah_skill_pick.py
|
|
|
142
142
|
hooks/tezgah_snapshot.py
|
|
143
143
|
hooks/tezgah_stack.py
|
|
144
144
|
hooks/tezgah_stopspec.py
|
|
145
|
+
hooks/tezgah_store.py
|
|
145
146
|
hooks/tezgah_task.py
|
|
146
147
|
hooks/tezgah_taste.py
|
|
147
148
|
hooks/tezgah_taste_ledger.py
|
|
@@ -670,6 +671,7 @@ tests/test_ai_research_library.py
|
|
|
670
671
|
tests/test_apps_registry.py
|
|
671
672
|
tests/test_attest.py
|
|
672
673
|
tests/test_bash_agreement.py
|
|
674
|
+
tests/test_cache_store.py
|
|
673
675
|
tests/test_cheap_source.py
|
|
674
676
|
tests/test_clarity.py
|
|
675
677
|
tests/test_claude_adapters.py
|
|
@@ -694,6 +696,7 @@ tests/test_dsh_statusline.py
|
|
|
694
696
|
tests/test_embed.py
|
|
695
697
|
tests/test_encoding.py
|
|
696
698
|
tests/test_enforcers.py
|
|
699
|
+
tests/test_evidence_store.py
|
|
697
700
|
tests/test_fuzz_shell.py
|
|
698
701
|
tests/test_gate.py
|
|
699
702
|
tests/test_gate_vectors.py
|
|
@@ -3,7 +3,9 @@ name: tezgah-reviewer
|
|
|
3
3
|
description: >
|
|
4
4
|
Adversarial, read-only review of a diff, branch or PR: derives the blast
|
|
5
5
|
radius, checks correctness, contract, security, tests and performance, and
|
|
6
|
-
classifies every finding confirmed/refuted/unverified.
|
|
6
|
+
classifies every finding confirmed/refuted/unverified. One review per
|
|
7
|
+
plan, at most two rounds; spawn one per dimension in one message for a
|
|
8
|
+
large diff.
|
|
7
9
|
model: opus
|
|
8
10
|
effort: high
|
|
9
11
|
readonly: true
|
|
@@ -56,15 +58,22 @@ is lowered only by evidence: a check you could not complete never
|
|
|
56
58
|
downgrades a confirmed finding - keep its severity and mark it
|
|
57
59
|
provisional. Disclose the order you read the files in.
|
|
58
60
|
|
|
59
|
-
Scope and budget.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
61
|
+
Scope and budget. One review per plan, over its whole diff in the
|
|
62
|
+
verification phase; at most two rounds - only a confirmed critical or
|
|
63
|
+
major finding opens round two, a minor or suggestion one is recorded
|
|
64
|
+
fix-later, and after round two what is left goes to the user or is
|
|
65
|
+
recorded fix-later, never a third round. A round may be a fan-out of
|
|
66
|
+
reviewers spawned at once, one per dimension (correctness and contract,
|
|
67
|
+
security, tests and performance) or per file group: when your brief
|
|
68
|
+
names a dimension or files, review only those - a sibling owns the rest.
|
|
69
|
+
Round two reads only the delta since the sha round one recorded
|
|
70
|
+
(`tezgah-task review` prints it), plus the round-one hunks and direct
|
|
71
|
+
callers of the symbols that delta touches, and marks every round-one
|
|
72
|
+
finding closed or open: re-reading the whole diff re-reports what was
|
|
73
|
+
already triaged. Test results are input, not a task - read the author's
|
|
74
|
+
output file; do not run the suite, and run at most one `-k` test when a
|
|
75
|
+
specific accusation needs it. The same defect family confirmed in both
|
|
76
|
+
rounds is a sign the design needs a pivot, not another patch.
|
|
68
77
|
|
|
69
78
|
You may use: the codegraph MCP tools, read, grep and glob. Read-only:
|
|
70
79
|
no writes, no edits, no shell.
|
package/bin/tezgah-docs
CHANGED
|
@@ -24,7 +24,7 @@ every word is part of the query.
|
|
|
24
24
|
|
|
25
25
|
Matching is word-based and case-insensitive against an entry's title, its
|
|
26
26
|
questions, its sources and its path; every word must match, so a second word
|
|
27
|
-
narrows. A query that matches nothing is the last resort of one
|
|
27
|
+
narrows. A query that matches nothing is the last resort of one judge-seam Choice
|
|
28
28
|
question over the pages themselves; it answers `none` for a query no page
|
|
29
29
|
covers. When that question gives no judgement - unavailable (`docs-judge-off`,
|
|
30
30
|
the seam's `judge-off`, no credential) or a call that failed or came back
|
|
@@ -106,7 +106,7 @@ RUN = re.compile(r"((?:`[A-Za-z_][A-Za-z0-9_]*(?:\(\))?`\s*[/,]?\s*){1,5})$")
|
|
|
106
106
|
CITE = re.compile(r"`([\w.-]+(?:/[\w.-]+)+|[\w-]+\.\w+):(\d+)(?:-(\d+))?`"
|
|
107
107
|
r"|`(:(\d+)(?:-(\d+))?)`")
|
|
108
108
|
# The symbol written after the citation instead of before it, the way a table
|
|
109
|
-
# cell reads: `hooks/tezgah_policy.py:
|
|
109
|
+
# cell reads: `hooks/tezgah_policy.py:678-946` (`CORE`).
|
|
110
110
|
AFTER = re.compile(r"\s*\(`([A-Za-z_][A-Za-z0-9_]*)(?:\(\))?`\)")
|
|
111
111
|
# Any `path:line`, backticked or not: what CITE does not match of these (a
|
|
112
112
|
# bare token, a comma list `x.py:12,40`) is counted as unjudged, so a citation
|
|
@@ -1036,7 +1036,7 @@ def record(result):
|
|
|
1036
1036
|
|
|
1037
1037
|
|
|
1038
1038
|
def judge_pick(query):
|
|
1039
|
-
"""(judged, page): whether a
|
|
1039
|
+
"""(judged, page): whether a judge-seam Choice question came back with one of
|
|
1040
1040
|
its options, and the page it named (None for `none`).
|
|
1041
1041
|
|
|
1042
1042
|
The last resort for a query the index could not place: a word the index does
|
package/bin/tezgah-doctor
CHANGED
|
@@ -34,12 +34,14 @@ this tree.
|
|
|
34
34
|
tezgah-doctor --coverage the tracked files the graph index does not hold
|
|
35
35
|
tezgah-doctor --coverage --repo ~/Projects/x
|
|
36
36
|
tezgah-doctor --clean vacuum the opencode database, and delete the
|
|
37
|
-
hook state (evidence ledgers,
|
|
38
|
-
|
|
39
|
-
debug.log) idle > 30
|
|
40
|
-
(--retention-days N);
|
|
41
|
-
own
|
|
37
|
+
hook state (evidence ledgers, turn
|
|
38
|
+
stamps, used-tool marks, classify.log,
|
|
39
|
+
context-drops.log, debug.log) idle > 30
|
|
40
|
+
days (--retention-days N);
|
|
41
|
+
$TEZGAH_SESSION's own state is always kept
|
|
42
42
|
tezgah-doctor --prune-sessions 30 delete sessions idle > 30 days, then vacuum
|
|
43
|
+
tezgah-doctor --import-evidence import the legacy JSONL evidence ledgers
|
|
44
|
+
into the evidence database now
|
|
43
45
|
"""
|
|
44
46
|
import argparse
|
|
45
47
|
import importlib.machinery
|
|
@@ -79,7 +81,11 @@ GRAPH_DB = CODEGRAPH + ".db"
|
|
|
79
81
|
# The per-session state the hooks write and nothing else removes: the evidence
|
|
80
82
|
# ledgers, the per-turn stamps, the used-tool marks and the two capped logs.
|
|
81
83
|
# Audit L-11 (GAP-10) measured 2,000 of each, 400 days old, surviving a full
|
|
82
|
-
# cycle and `--clean`.
|
|
84
|
+
# cycle and `--clean`. The stamps and the marks are rows of the cache database
|
|
85
|
+
# (`tezgah_store.sweep_cache`); the evidence directory holds the legacy ledger
|
|
86
|
+
# files its import has not taken, and `turns/` and `sessions/` the files a
|
|
87
|
+
# writer from an older release still running beside this one adds there.
|
|
88
|
+
# Snapshots are not here: tezgah_snapshot caps them itself.
|
|
83
89
|
STATE_DIRS = ("evidence", "turns", "sessions")
|
|
84
90
|
STATE_LOGS = ("classify.log", "context-drops.log", "debug.log")
|
|
85
91
|
RETENTION_DAYS = 30
|
|
@@ -277,20 +283,36 @@ def prune_sessions(days):
|
|
|
277
283
|
|
|
278
284
|
def sweep_state(days, session=None):
|
|
279
285
|
"""Delete the hook state not modified for `days`, in the global cache and
|
|
280
|
-
the temp fallback; (removed, bytes freed
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
286
|
+
the temp fallback; (files removed, bytes freed, evidence sessions deleted,
|
|
287
|
+
evidence rows deleted, turn-stamp and used-mark rows deleted).
|
|
288
|
+
|
|
289
|
+
The evidence ledger is swept in its database (`tezgah_store.sweep_evidence`,
|
|
290
|
+
after importing the legacy JSONL files): every session whose newest row is
|
|
291
|
+
older than the cutoff loses its rows. The turn stamps and the used-tool
|
|
292
|
+
marks are swept there too, by the time each was last written
|
|
293
|
+
(`tezgah_store.sweep_cache`). The other files go by mtime, because
|
|
294
|
+
every writer appends or rewrites in place, so a file's mtime is its
|
|
295
|
+
session's last activity. The current session (`TEZGAH_SESSION`, the id omp
|
|
296
|
+
and the judgement CLIs export) is kept whatever its age: a session idle past
|
|
297
|
+
the window and then resumed must not lose its ledger mid-flight."""
|
|
298
|
+
import tezgah_store
|
|
287
299
|
from tezgah_context import slug
|
|
288
300
|
from tezgah_integrity import _slug
|
|
289
301
|
keep = ({_slug(session) + ".jsonl", slug(session) + ".json",
|
|
290
302
|
slug(session) + ".jsonl"} if session else set())
|
|
291
303
|
cutoff = time.time() - days * 86400
|
|
292
|
-
removed = freed = 0
|
|
304
|
+
removed = freed = sessions = rows = marks = 0
|
|
293
305
|
for base in dict.fromkeys((CACHE, fallback_cache())):
|
|
306
|
+
try:
|
|
307
|
+
swept = tezgah_store.sweep_evidence(base, cutoff,
|
|
308
|
+
_slug(session) if session else None)
|
|
309
|
+
except tezgah_store.ERRORS:
|
|
310
|
+
swept = (0, 0)
|
|
311
|
+
sessions, rows = sessions + swept[0], rows + swept[1]
|
|
312
|
+
try:
|
|
313
|
+
marks += tezgah_store.sweep_cache(base, cutoff, slug(session) if session else None)
|
|
314
|
+
except tezgah_store.ERRORS:
|
|
315
|
+
pass
|
|
294
316
|
paths = [os.path.join(base, name) for name in STATE_LOGS]
|
|
295
317
|
for sub in STATE_DIRS:
|
|
296
318
|
try:
|
|
@@ -310,7 +332,15 @@ def sweep_state(days, session=None):
|
|
|
310
332
|
continue
|
|
311
333
|
removed += 1
|
|
312
334
|
freed += st.st_size
|
|
313
|
-
return removed, freed
|
|
335
|
+
return removed, freed, sessions, rows, marks
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def import_evidence():
|
|
339
|
+
"""The bulk import of the legacy JSONL ledgers into the evidence database
|
|
340
|
+
of the cache and of the temp fallback: {cache: files seen}."""
|
|
341
|
+
import tezgah_store
|
|
342
|
+
return {base: tezgah_store.import_evidence(base)
|
|
343
|
+
for base in dict.fromkeys((CACHE, fallback_cache()))}
|
|
314
344
|
|
|
315
345
|
|
|
316
346
|
|
|
@@ -421,7 +451,7 @@ def main():
|
|
|
421
451
|
help="a saved `codegraph files -j` dump to read for --coverage")
|
|
422
452
|
ap.add_argument("--clean", action="store_true",
|
|
423
453
|
help="vacuum the opencode database and delete tezgah hook "
|
|
424
|
-
"state (ledgers,
|
|
454
|
+
"state (ledgers, turn stamps, used-tool marks, classify.log, "
|
|
425
455
|
"context-drops.log, debug.log) not "
|
|
426
456
|
"modified for --retention-days; the current session "
|
|
427
457
|
"($TEZGAH_SESSION) is kept (opt-in)")
|
|
@@ -431,6 +461,10 @@ def main():
|
|
|
431
461
|
"%(default)s days)")
|
|
432
462
|
ap.add_argument("--prune-sessions", type=int, metavar="DAYS",
|
|
433
463
|
help="delete sessions idle longer than DAYS, then vacuum")
|
|
464
|
+
ap.add_argument("--import-evidence", action="store_true",
|
|
465
|
+
help="import the legacy JSONL evidence ledgers into the "
|
|
466
|
+
"evidence database now, instead of at the next "
|
|
467
|
+
"session start's daily run")
|
|
434
468
|
args = ap.parse_args()
|
|
435
469
|
if args.retention_days < 1:
|
|
436
470
|
ap.error("--retention-days wants a positive day count")
|
|
@@ -439,6 +473,19 @@ def main():
|
|
|
439
473
|
repo = os.path.realpath(os.path.expanduser(args.repo[0]) if args.repo
|
|
440
474
|
else os.getcwd())
|
|
441
475
|
return coverage_report(repo, args.dump)
|
|
476
|
+
if args.import_evidence:
|
|
477
|
+
import tezgah_store
|
|
478
|
+
try:
|
|
479
|
+
seen = import_evidence()
|
|
480
|
+
except tezgah_store.ERRORS as exc:
|
|
481
|
+
print("evidence import failed: %s" % exc, file=sys.stderr)
|
|
482
|
+
return 1
|
|
483
|
+
if args.json:
|
|
484
|
+
print(json.dumps({"imported": seen}, indent=2))
|
|
485
|
+
else:
|
|
486
|
+
for base, n in seen.items():
|
|
487
|
+
print("%s: %d legacy ledger file(s) imported" % (base, n))
|
|
488
|
+
return 0
|
|
442
489
|
if args.stack:
|
|
443
490
|
import tezgah_stack
|
|
444
491
|
repo = os.path.realpath(os.path.expanduser(args.repo[0]) if args.repo
|
|
@@ -470,10 +517,13 @@ def main():
|
|
|
470
517
|
except sqlite3.Error as exc:
|
|
471
518
|
cleaned["vacuum"] = "failed: %s" % exc
|
|
472
519
|
if args.clean:
|
|
473
|
-
removed, freed = sweep_state(
|
|
474
|
-
|
|
520
|
+
removed, freed, sessions, rows, marks = sweep_state(
|
|
521
|
+
args.retention_days, os.environ.get("TEZGAH_SESSION"))
|
|
475
522
|
cleaned["state"] = {"removed": removed, "bytes": freed,
|
|
476
523
|
"days": args.retention_days}
|
|
524
|
+
cleaned["evidence"] = {"sessions": sessions, "rows": rows,
|
|
525
|
+
"days": args.retention_days}
|
|
526
|
+
cleaned["marks"] = {"rows": marks, "days": args.retention_days}
|
|
477
527
|
rep = collect(pairs)
|
|
478
528
|
|
|
479
529
|
if args.json:
|
|
@@ -499,6 +549,13 @@ def main():
|
|
|
499
549
|
if st:
|
|
500
550
|
print(" hook state %d file(s) older than %d days removed, %s"
|
|
501
551
|
% (st["removed"], st["days"], human(st["bytes"])))
|
|
552
|
+
ev = cleaned.get("evidence")
|
|
553
|
+
if ev:
|
|
554
|
+
print(" evidence %d session(s) idle > %d days deleted (%d rows)"
|
|
555
|
+
% (ev["sessions"], ev["days"], ev["rows"]))
|
|
556
|
+
mk = cleaned.get("marks")
|
|
557
|
+
if mk:
|
|
558
|
+
print(" stamps/marks %d row(s) idle > %d days deleted" % (mk["rows"], mk["days"]))
|
|
502
559
|
elif rep["opencode_db_bytes"] > 500 * 1024 * 1024:
|
|
503
560
|
print("\nrun with --clean to vacuum; --prune-sessions DAYS to shrink the db")
|
|
504
561
|
return 0
|
package/bin/tezgah-route
CHANGED
|
@@ -12,8 +12,11 @@ A brief naming stored data, a migration, credentials, the gate or security goes
|
|
|
12
12
|
tezgah-frontier by rule; otherwise Jev picks the tier (one Choice, ~650 input
|
|
13
13
|
tokens, ~0.4 s); with no key or `judge-off`, `--phase`
|
|
14
14
|
(explore|code|mechanical|plan|review|research) picks it from the static table, and
|
|
15
|
-
with neither the middle tier is used. The brief is sent to
|
|
16
|
-
|
|
15
|
+
with neither the middle tier is used. The brief is sent to the judge seam's
|
|
16
|
+
provider - the session's own CLI (omp, claude, opencode, cursor, codex) or the Jev
|
|
17
|
+
model through TypeSafe, OpenRouter System One, Cloudflare Workers AI or a
|
|
18
|
+
compatible endpoint (TEZGAH_JEV_PROVIDER picks one), else the OpenRouter chat
|
|
19
|
+
fallback - so write it in English, the language the judge reads best.
|
|
17
20
|
|
|
18
21
|
Every routed call writes one `route` row to the session's ledger (TEZGAH_SESSION):
|
|
19
22
|
tier, agent, what set the tier (`via`: rule|jev|phase|default), the static
|
package/bin/tezgah-setup
CHANGED
|
@@ -59,6 +59,7 @@ import tezgah_paths as tp # noqa: E402
|
|
|
59
59
|
import tezgah_policy as policy # noqa: E402
|
|
60
60
|
import tezgah_agents # noqa: E402
|
|
61
61
|
import tezgah_integrity # noqa: E402
|
|
62
|
+
import tezgah_store # noqa: E402
|
|
62
63
|
import tezgah_attest as ta # noqa: E402
|
|
63
64
|
from tezgah_policy import ( # noqa: E402
|
|
64
65
|
ADHD, CODEGRAPH_RULE, CODEGRAPH_STATIC, CONSULT, EXEC, NO_CONSULT,
|
|
@@ -883,6 +884,22 @@ def save_features(ids):
|
|
|
883
884
|
return True
|
|
884
885
|
|
|
885
886
|
|
|
887
|
+
def set_jev(value):
|
|
888
|
+
"""Persist `value` as config.json["jev"] - this key's only writer - and print
|
|
889
|
+
the carriers it resolves to now. An unchanged value writes nothing, like
|
|
890
|
+
`save_features`; a config that does not parse is refused, not replaced."""
|
|
891
|
+
import tezgah_judge
|
|
892
|
+
cfg = read_config(tp.CONFIG, {})
|
|
893
|
+
if cfg is None:
|
|
894
|
+
sys.stderr.write("tezgah-setup: %s is not valid JSON; jev not set\n" % tp.CONFIG)
|
|
895
|
+
return 1
|
|
896
|
+
if cfg.get("jev") != value:
|
|
897
|
+
cfg["jev"] = value
|
|
898
|
+
write_json(tp.CONFIG, cfg)
|
|
899
|
+
print("%s %s %s" % (OK, tp.CONFIG, tezgah_judge.jev_summary()))
|
|
900
|
+
return 0
|
|
901
|
+
|
|
902
|
+
|
|
886
903
|
def app_servers(selected=None):
|
|
887
904
|
"""The app-analysis MCP servers to wire this run (shared spec)."""
|
|
888
905
|
return tezgah_apps.servers(selection() if selected is None else selected)
|
|
@@ -4161,17 +4178,6 @@ def _denied(host, stdout):
|
|
|
4161
4178
|
return (got.get("hookSpecificOutput") or {}).get("permissionDecision") == "deny"
|
|
4162
4179
|
|
|
4163
4180
|
|
|
4164
|
-
def _live_cleanup(slug):
|
|
4165
|
-
"""Remove every cache file the synthetic session left (named by its slug)."""
|
|
4166
|
-
for dirpath, _dirs, files in os.walk(tp.cache_dir()):
|
|
4167
|
-
for name in files:
|
|
4168
|
-
if slug in name:
|
|
4169
|
-
try:
|
|
4170
|
-
os.remove(os.path.join(dirpath, name))
|
|
4171
|
-
except OSError:
|
|
4172
|
-
pass
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
4181
|
def live_probe(host, deny=False):
|
|
4176
4182
|
"""(state, line) for one host: ok, miss or unverified. `deny` sends the
|
|
4177
4183
|
PreToolUse probe: the host's deny envelope and a deny row must both appear;
|
|
@@ -4211,15 +4217,14 @@ def live_probe(host, deny=False):
|
|
|
4211
4217
|
capture_output=True, text=True, env=env,
|
|
4212
4218
|
cwd=root, timeout=LIVE_TIMEOUT)
|
|
4213
4219
|
ms = int((time.monotonic() - started) * 1000)
|
|
4214
|
-
|
|
4215
|
-
with open(ledger, encoding="utf-8") as fh:
|
|
4216
|
-
kinds = [json.loads(ln).get("kind") for ln in fh if ln.strip()]
|
|
4217
|
-
except (OSError, ValueError):
|
|
4218
|
-
kinds = []
|
|
4220
|
+
kinds = [row.get("kind") for row in tezgah_integrity.events_path(ledger)]
|
|
4219
4221
|
except (OSError, subprocess.SubprocessError) as exc:
|
|
4220
4222
|
return "miss", "live %s: the hook did not run (%s)" % (who, exc)
|
|
4221
4223
|
finally:
|
|
4222
|
-
|
|
4224
|
+
try:
|
|
4225
|
+
tezgah_store.forget_session(ledger)
|
|
4226
|
+
except tezgah_store.ERRORS:
|
|
4227
|
+
pass
|
|
4223
4228
|
shutil.rmtree(root, ignore_errors=True)
|
|
4224
4229
|
err = (proc.stderr or "").strip().splitlines()
|
|
4225
4230
|
tail = " (exit %d%s)" % (proc.returncode, ": " + err[-1][:160] if err else "")
|
|
@@ -4778,7 +4783,7 @@ def host_checks_omp():
|
|
|
4778
4783
|
tp.have_typesafe_key()),
|
|
4779
4784
|
# One row per channel: the two disagree in both directions - a key file
|
|
4780
4785
|
# alone serves the seam, an omp login-store record alone serves omp.
|
|
4781
|
-
("judgement seam key resolvable (
|
|
4786
|
+
("judgement seam key resolvable (session CLI, Jev carrier or OpenRouter)",
|
|
4782
4787
|
tp.have_judge_key()),
|
|
4783
4788
|
("status line answers", omp_hook_answers()), ("tier models in task.agentModelOverrides and modelRoles.plan/slow", tier_models_current()),
|
|
4784
4789
|
]
|
|
@@ -5690,6 +5695,11 @@ def main(argv):
|
|
|
5690
5695
|
help="with --install: the language replies are asked for and "
|
|
5691
5696
|
"the Stop rule checks (config.json `reply_lang`; `tr` "
|
|
5692
5697
|
"when unset, `any` checks none)")
|
|
5698
|
+
ap.add_argument("--jev", choices=tp.JEV_CHOICES,
|
|
5699
|
+
help="the carrier the judgement seam reaches the Jev model "
|
|
5700
|
+
"through (config.json `jev`; `auto` tries every one "
|
|
5701
|
+
"that resolves); TEZGAH_JEV_PROVIDER and JEV_PROVIDER "
|
|
5702
|
+
"still win over it")
|
|
5693
5703
|
# `--version` is argparse's print-and-exit action, so `--upgrade --version V`
|
|
5694
5704
|
# would print the current version, exit 0 and move nothing at all - the
|
|
5695
5705
|
# "reads as done, did nothing" shape this repo hunts. Refused by name here,
|
|
@@ -5742,6 +5752,8 @@ def main(argv):
|
|
|
5742
5752
|
if args.features:
|
|
5743
5753
|
features_report()
|
|
5744
5754
|
return 0
|
|
5755
|
+
if args.jev:
|
|
5756
|
+
return set_jev(args.jev)
|
|
5745
5757
|
if args.refresh:
|
|
5746
5758
|
paths = refresh_contract()
|
|
5747
5759
|
for path in paths:
|
package/bin/tezgah-status
CHANGED
|
@@ -298,7 +298,9 @@ def main():
|
|
|
298
298
|
last = tj.last_use()
|
|
299
299
|
if "--json" in flags:
|
|
300
300
|
print(json.dumps(last, sort_keys=True))
|
|
301
|
-
|
|
301
|
+
return 0
|
|
302
|
+
print(tj.jev_summary())
|
|
303
|
+
if not last:
|
|
302
304
|
print("judge: no judgement recorded")
|
|
303
305
|
elif last.get("provider"):
|
|
304
306
|
print("judge: %s answered with %s/%s (fallback=%s)%s" % (
|