@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.
Files changed (53) hide show
  1. package/CHANGELOG.md +103 -1
  2. package/MANIFEST +3 -0
  3. package/agents/tezgah-reviewer.md +19 -10
  4. package/bin/tezgah-docs +3 -3
  5. package/bin/tezgah-doctor +75 -18
  6. package/bin/tezgah-route +5 -2
  7. package/bin/tezgah-setup +30 -18
  8. package/bin/tezgah-status +3 -1
  9. package/bin/tezgah-task +130 -37
  10. package/bin/tezgah-taste +74 -55
  11. package/bin/tezgah-triage +12 -7
  12. package/docs/architecture.md +4 -4
  13. package/docs/clarity-baseline.json +9 -9
  14. package/docs/contract.md +22 -12
  15. package/docs/environment.md +4 -1
  16. package/docs/evidence.md +71 -34
  17. package/docs/gate.md +21 -21
  18. package/docs/glossary.md +5 -5
  19. package/docs/hosts.md +8 -8
  20. package/docs/judge.md +93 -27
  21. package/docs/operations.md +69 -30
  22. package/docs/skills.md +2 -2
  23. package/docs/status-line.md +7 -7
  24. package/docs/vision.md +10 -5
  25. package/hooks/tezgah_agents.py +22 -11
  26. package/hooks/tezgah_attest.py +15 -22
  27. package/hooks/tezgah_context.py +84 -124
  28. package/hooks/tezgah_gate.py +26 -23
  29. package/hooks/tezgah_guard.py +9 -11
  30. package/hooks/tezgah_integrity.py +77 -178
  31. package/hooks/tezgah_judge.py +270 -122
  32. package/hooks/tezgah_lessons.py +18 -63
  33. package/hooks/tezgah_paths.py +183 -55
  34. package/hooks/tezgah_policy.py +35 -8
  35. package/hooks/tezgah_replay.py +57 -33
  36. package/hooks/tezgah_shapes.py +4 -4
  37. package/hooks/tezgah_skill_pick.py +6 -35
  38. package/hooks/tezgah_snapshot.py +25 -46
  39. package/hooks/tezgah_store.py +1268 -0
  40. package/hooks/tezgah_task.py +146 -40
  41. package/hooks/tezgah_taste.py +21 -18
  42. package/hooks/tezgah_taste_ledger.py +63 -88
  43. package/hooks/tezgah_update.py +6 -26
  44. package/hosts/codex/hook.py +5 -0
  45. package/hosts/cursor/hook.py +19 -31
  46. package/hosts/opencode/plugins/tezgah.js +309 -108
  47. package/package.json +1 -1
  48. package/skills/harness/SKILL.md +4 -0
  49. package/skills/plan-add/SKILL.md +23 -7
  50. package/skills/plan-add/render_table.py +3 -2
  51. package/skills/plan-status/SKILL.md +12 -4
  52. package/skills/plan-sync/SKILL.md +19 -9
  53. 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.3.0...HEAD
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. Every round that confirms a defect is followed by a
60
- round over that fix's delta; the review ends when a round confirms none.
61
- A later round reads only the delta since the sha the previous round
62
- recorded (`tezgah-task review` prints it): re-reading the whole diff
63
- re-reports what was already triaged. Test results are input, not a task
64
- - read the author's output file; do not run the suite, and run at most one
65
- `-k` test when a specific accusation needs it. The same defect family
66
- confirmed three times is a sign the design needs a pivot, not another
67
- patch.
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 TypeSafe Choice
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:667-928` (`CORE`).
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 TypeSafe Choice question came back with one of
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, turns/,
38
- sessions/, classify.log, context-drops.log,
39
- debug.log) idle > 30 days
40
- (--retention-days N); $TEZGAH_SESSION's
41
- own files are always kept
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`. Snapshots are not here: tezgah_snapshot caps them itself.
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
- By mtime because every writer appends or rewrites in place, so a file's
283
- mtime is its session's last activity. The current session's files
284
- (`TEZGAH_SESSION`, the id omp and the judgement CLIs export) are kept
285
- whatever their age: a session idle past the window and then resumed must
286
- not lose its ledger mid-flight."""
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, turns/, sessions/, classify.log, "
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(args.retention_days,
474
- os.environ.get("TEZGAH_SESSION"))
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 api.typesafe.ai (or the
16
- OpenRouter fallback) - write it in English, the language the judge reads best.
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
- try:
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
- _live_cleanup(slug)
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 (env or key file)",
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
- elif not last:
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" % (