@r1z4x/tezgah 1.5.0 → 1.6.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 (55) hide show
  1. package/CHANGELOG.md +96 -1
  2. package/MANIFEST +6 -1
  3. package/README.md +6 -4
  4. package/bin/consult +13 -10
  5. package/bin/tezgah-docs +1 -1
  6. package/bin/tezgah-research +53 -3
  7. package/bin/tezgah-route +8 -5
  8. package/bin/tezgah-setup +45 -25
  9. package/bin/tezgah-task +194 -41
  10. package/docs/architecture.md +1 -1
  11. package/docs/citations-baseline.json +1 -1
  12. package/docs/clarity-baseline.json +4 -4
  13. package/docs/contract.md +4 -1
  14. package/docs/feature-audit.md +24 -3
  15. package/docs/gate.md +10 -11
  16. package/docs/hosts.md +9 -0
  17. package/docs/index.json +21 -7
  18. package/docs/judge.md +3 -1
  19. package/docs/layers.md +1 -1
  20. package/docs/models.md +17 -7
  21. package/docs/operations.md +0 -1
  22. package/docs/research.md +46 -0
  23. package/docs/skills.md +23 -5
  24. package/docs/testing.md +1 -1
  25. package/hooks/tezgah_agents.py +130 -96
  26. package/hooks/tezgah_components.py +2 -1
  27. package/hooks/tezgah_context.py +67 -15
  28. package/hooks/tezgah_gate.py +62 -76
  29. package/hooks/tezgah_judge.py +15 -8
  30. package/hooks/tezgah_models.py +50 -3
  31. package/hooks/tezgah_paths.py +26 -11
  32. package/hooks/tezgah_policy.py +47 -19
  33. package/hooks/tezgah_product.py +107 -0
  34. package/hooks/tezgah_research.py +3 -3
  35. package/hooks/tezgah_skill_pick.py +58 -17
  36. package/hooks/tezgah_task.py +16 -8
  37. package/hosts/opencode/plugins/tezgah.js +17 -37
  38. package/package.json +1 -2
  39. package/skills/analyze-app/SKILL.md +57 -0
  40. package/skills/design-contract/EVALS.md +24 -1
  41. package/skills/design-contract/SKILL.md +17 -1
  42. package/skills/design-contract/data-views.md +86 -0
  43. package/skills/feature-audit/SKILL.md +32 -3
  44. package/skills/plan-add/EVALS.md +8 -8
  45. package/skills/plan-add/SKILL.md +20 -21
  46. package/skills/plan-sync/EVALS.md +2 -2
  47. package/skills/plan-sync/SKILL.md +11 -7
  48. package/skills/pm-frameworks/SKILL.md +6 -0
  49. package/skills/product-analysis/EVALS.md +23 -1
  50. package/skills/product-analysis/SKILL.md +50 -11
  51. package/skills/product-flow/EVALS.md +81 -0
  52. package/skills/product-flow/SKILL.md +91 -0
  53. package/skills/research/SKILL.md +10 -1
  54. package/skills/tezgah-contract/SKILL.md +67 -41
  55. package/agents/tezgah-reviewer.md +0 -79
package/CHANGELOG.md CHANGED
@@ -6,6 +6,99 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [1.6.0] - 2026-10-10
10
+
11
+ ### Added
12
+
13
+ - **A data, metric or dashboard ask reaches the product rule.** "Bu veriye uygun bir metric dashboard tasarla" armed no rule before. A dashboard, chart or metric-choice ask now arms `product`, judged per clause. A code marker in the same clause, such as a metrics endpoint, keeps it out.
14
+ - **product-analysis chooses metrics.** It names a North Star and its input metrics. Each metric gets one record: goal, signal, definition, window, baseline and more.
15
+ - **Dashboard rules in design-contract.** The new `data-views.md` picks the chart for the question. It also sets the context, the window and the widget states.
16
+ - **feature-audit forward mode.** A feature not built yet gets its five matrices in `spec.md`. The audit after the build compares against it.
17
+ - **analyze-app writes the design measurement.** Nothing produced `measurement.json` before. The skill now gives its shape and the snippet that reads it.
18
+ - **The local skill search reaches design-library's entries.** The search now reads the entries a skill's `INDEX.md` lists, one row each.
19
+ - **The product-flow skill and `tezgah-research next`.** A new product capability now runs in a fixed order, from research to re-measure. Each step leaves a file under `.tezgah/analysis/<topic>/`: a `## <step>` note in `flow.md`, or `spec.md` for the forward spec. `tezgah-research next --topic <t>` prints each step's status from those files, and a re-measure is due once the decision's `observe after:` date has passed. It then names the first missing or due step's skill and command. The skill lists the steps, and `FLOWS` in `hooks/tezgah_product.py` holds their done rules.
20
+
21
+ ### Removed
22
+
23
+ - **The product ledger is cut before release (ADR 025).** Plan 075's measured run did not show the ledger beating the playbook alone. Gone: writing and reading `.tezgah/product/ledger.jsonl`, `tezgah-research record`, `due` and `commit --product`, the ledger part of `check`, `product:` citations and the session-start note on due decisions. An existing `ledger.jsonl` is left in place, and nothing reads it.
24
+
25
+ ### Changed
26
+
27
+ - **The session runs the plan lifecycle itself.** The gate refused a session's
28
+ own `tezgah-task start|phase|allow|stop`. It told the session to ask the user,
29
+ so every plan ended with a command handed back. That refusal is gone. A phase
30
+ or scope refusal now names the move (`tezgah-task phase implementation`,
31
+ `tezgah-task allow`). The CLI still checks Acceptance, the spike, the
32
+ checkpoint and the review on each move. The gate still refuses a write to the
33
+ plan file itself. `tezgah-task allow` now needs `--why`, and `status` prints
34
+ the reason next to the widened scope.
35
+ - **`close` cleans up.** `tezgah-task close` on the active task clears its
36
+ phase instead of refusing. It then removes each linked worktree whose branch
37
+ has landed and which holds nothing else, through Orca when Orca tracks it. A
38
+ squash merge counts as landed. A branch whose commits cancel out does not. It
39
+ prints every worktree it keeps, with the reason. Uncommitted files, unknown
40
+ ignored files and `.tezgah` data keep a worktree. So do an unmerged branch, a
41
+ branch nobody committed on, an agent at work and an Orca that does not answer.
42
+ - **No process questions.** The contract says the orchestrator picks a process
43
+ parameter the user did not set, such as a spend ceiling, and states it.
44
+ `consult` on exit 4/5 records the first member of another model family
45
+ itself instead of asking the user to choose. After two review rounds the
46
+ session fixes what is left and closes with `close NNN done --resolved`. A
47
+ missing credential sends it to a route that needs none first. A silent gate
48
+ has it run `tezgah-setup --report` itself.
49
+
50
+ ### Fixed
51
+
52
+ - **product-analysis no longer says `check` demands three compared solutions.** An analysis answers once. A choice between solutions goes under `decisions/`.
53
+
54
+ ## [1.5.1] - 2026-10-09
55
+
56
+ ### Fixed
57
+
58
+ - **Subagents load in the first session.** Claude and Codex missed every role a
59
+ session wrote into a new repo until the next session. Codex also ignored
60
+ `.codex/agents` in an untrusted repo. The install now writes the roles to
61
+ `~/.claude/agents` and `$CODEX_HOME/agents`, as it already did for omp. A
62
+ session removes the copies an older release left in a repo's `.claude/agents`
63
+ and `.codex/agents`. Cursor read 9 of 10 role descriptions as ">" and got a
64
+ model id it does not list. It now gets its own `.cursor/agents` with a
65
+ one-line description and `model: inherit`.
66
+
67
+ - **`tezgah-route` names the specialists.** It could only name a tier. The
68
+ five specialists ran 0 times in 121 delegations. The generic task agent ran 86.
69
+ The judge now also answers whether a brief is a specialist's whole
70
+ job, and the route names that agent. `--phase review` and `--phase research`
71
+ name the reviewer and the researcher. A security brief that only reviews
72
+ goes to `tezgah-security`. The routing and fan-out text say to spawn the
73
+ named agent, never the generic one.
74
+ - **Jev through OpenRouter asks a model OpenRouter has.** The carrier sent
75
+ `typesafe/jev-latest`, which OpenRouter answers with 400 (no such model). It
76
+ now sends the bare `jev-latest`, which OpenRouter maps itself.
77
+ - **The Workers AI model id picks Clef.** `tezgah-setup` refused
78
+ `--jev @cf/cloudflare/clef-flash`, and the same value in
79
+ `TEZGAH_JEV_PROVIDER` chose no carrier. Both now name `clef-flash`, and
80
+ `@cf/cloudflare/clef` names `clef`.
81
+ - **opencode's judge run denies tools at agent level too.** A user's
82
+ `agent.build.permission` beat the inline top-level deny.
83
+ - **The refusal note needs an asked session CLI.** The seam writes it only
84
+ when it asked the session CLI first. A caller that named the CLI gets it.
85
+ - **A chosen carrier reads only its own key.** A `clef` choice no longer opens
86
+ omp's login database on every status-line redraw.
87
+ - **`pnpm test` names a command.** An Acceptance item counted as a command
88
+ only when a word after the first was an option or a path. So `pnpm test`,
89
+ `cargo test` and `make test` read as missing, and the phase move refused the
90
+ plan. A known runner followed by `test` or `run` now counts. Prose such as
91
+ `run the tests` still does not.
92
+ - **Each carrier's price is now pinned by a test.** The one cost check priced a
93
+ TypeSafe answer at Jev's rate. It passed with or without per-carrier pricing.
94
+ New tests in `tezgah-taste`, `tezgah-triage` and the replay labeller price Clef
95
+ and Clef-flash answers at their own rates. A run with an unpriced answer prints
96
+ `cost unknown`.
97
+ - **Claude lists the reviewer once.** The plugin shipped its own
98
+ `agents/tezgah-reviewer.md`, which Claude shows as `tezgah:tezgah-reviewer`
99
+ beside the generated reviewer and never shadows. The plugin now ships no
100
+ agent, and its release-time renderer is gone with it.
101
+
9
102
  ## [1.5.0] - 2026-10-09
10
103
 
11
104
  ### Changed
@@ -933,7 +1026,9 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
933
1026
  the copy could not find `hooks/` and died on import. It now links into the
934
1027
  installed tree, and also installs the command as `tezgah`.
935
1028
 
936
- [Unreleased]: https://github.com/r1z4x/tezgah/compare/v1.5.0...HEAD
1029
+ [Unreleased]: https://github.com/r1z4x/tezgah/compare/v1.6.0...HEAD
1030
+ [1.6.0]: https://github.com/r1z4x/tezgah/releases/tag/v1.6.0
1031
+ [1.5.1]: https://github.com/r1z4x/tezgah/releases/tag/v1.5.1
937
1032
  [1.5.0]: https://github.com/r1z4x/tezgah/releases/tag/v1.5.0
938
1033
  [1.4.0]: https://github.com/r1z4x/tezgah/releases/tag/v1.4.0
939
1034
  [1.3.0]: https://github.com/r1z4x/tezgah/releases/tag/v1.3.0
package/MANIFEST CHANGED
@@ -19,7 +19,6 @@ README.md
19
19
  README.tr.md
20
20
  RELEASING.md
21
21
  SECURITY.md
22
- agents/tezgah-reviewer.md
23
22
  assets/logo/README.md
24
23
  assets/logo/tezgah-logo-16.png
25
24
  assets/logo/tezgah-logo-256.png
@@ -134,6 +133,7 @@ hooks/tezgah_models.py
134
133
  hooks/tezgah_orca.py
135
134
  hooks/tezgah_paths.py
136
135
  hooks/tezgah_policy.py
136
+ hooks/tezgah_product.py
137
137
  hooks/tezgah_rank.py
138
138
  hooks/tezgah_replay.py
139
139
  hooks/tezgah_research.py
@@ -546,6 +546,7 @@ skills/analyze-app/EVALS.md
546
546
  skills/analyze-app/SKILL.md
547
547
  skills/design-contract/EVALS.md
548
548
  skills/design-contract/SKILL.md
549
+ skills/design-contract/data-views.md
549
550
  skills/design-library/EVALS.md
550
551
  skills/design-library/INDEX.md
551
552
  skills/design-library/SKILL.md
@@ -613,6 +614,8 @@ skills/ponytail/EVALS.md
613
614
  skills/ponytail/SKILL.md
614
615
  skills/product-analysis/EVALS.md
615
616
  skills/product-analysis/SKILL.md
617
+ skills/product-flow/EVALS.md
618
+ skills/product-flow/SKILL.md
616
619
  skills/research/EVALS.md
617
620
  skills/research/SKILL.md
618
621
  skills/rl-env/SKILL.md
@@ -734,6 +737,8 @@ tests/test_packaging_scripts.py
734
737
  tests/test_paths.py
735
738
  tests/test_paths_worktree.py
736
739
  tests/test_platform.py
740
+ tests/test_product_flow_e2e.py
741
+ tests/test_product_next.py
737
742
  tests/test_providers.py
738
743
  tests/test_rank.py
739
744
  tests/test_replay.py
package/README.md CHANGED
@@ -109,10 +109,12 @@ it set out to arm is not armed.
109
109
  `~/.codex/AGENTS.md`, `~/.omp/agent/RULES.md`). Claude Code's hooks ship in a
110
110
  plugin registered through `claude plugin`; with no `claude` CLI, Claude stays
111
111
  unarmed and the report says so.
112
- - **Agent files in your repositories.** Inside the configured roots, a session
113
- writes subagent files to `.claude/agents/`, `.codex/agents/` and
114
- `.opencode/agents/`, and lists those directories in the clone's own
115
- `.git/info/exclude` (`TEZGAH_NO_EXCLUDE=1` stops that).
112
+ - **Agent files.** The install writes tezgah's subagent roles to
113
+ `~/.claude/agents/`, `$CODEX_HOME/agents/` (default `~/.codex/agents/`) and
114
+ `~/.omp/agent/agents/`. Inside the configured roots, a session writes them to
115
+ the repository's `.cursor/agents/` and `.opencode/agents/`, and lists those
116
+ directories in the clone's own `.git/info/exclude` (`TEZGAH_NO_EXCLUDE=1`
117
+ stops that).
116
118
  - **A daily update check.** The status line asks for a newer release at most
117
119
  once a day; `~/.config/tezgah/update-check-off` or `TEZGAH_UPDATE_CHECK=0`
118
120
  turns it off.
package/bin/consult CHANGED
@@ -56,7 +56,8 @@ suffix); 2 no key for the chosen provider, or no member available at all; 3 the
56
56
  --provider/--models run got no answer; 4 no recorded choice - one "offer:" line
57
57
  per available member, nothing was asked; 5 every recorded member (and the
58
58
  fallback) failed - one "reoffer:" line per member still available that did not
59
- fail. On 4 or 5, ask the user which to use and record it with --use.
59
+ fail. On 4 or 5, the agent records the first listed member of another model
60
+ family with --use itself and asks again; the user is not asked to choose.
60
61
  """
61
62
  import concurrent.futures
62
63
  import json
@@ -504,11 +505,11 @@ def retry_hint(classes):
504
505
  return "the referee answered outside its five headings; re-run, or judge the panel yourself"
505
506
  if c == "credit":
506
507
  return ("the provider account has no credit (HTTP 402) - the key is fine; "
507
- "top it up, or ask the user for another member and record it with --use")
508
+ "top it up, or record another member with --use")
508
509
  if c == "key":
509
- return "set the provider's key, or ask the user for another member and record it with --use"
510
+ return "set the provider's key, or record another member with --use"
510
511
  if c == "missing":
511
- return "the agent CLI is not installed; ask the user for another member and record it with --use"
512
+ return "the agent CLI is not installed; record another member with --use"
512
513
  if c.startswith("cli-exit"):
513
514
  return ("the agent CLI failed (not logged in, or a model name it does not "
514
515
  "know); run it once by hand, or record another member with --use")
@@ -645,8 +646,9 @@ def main():
645
646
  print("consult: no recorded choice, so nothing was asked.")
646
647
  for opt in found:
647
648
  print(offer_line("offer", opt))
648
- print("next: ask the user which of these to use (two or more make a "
649
- "panel), then run: consult --use <member>[,<member>] and ask again")
649
+ print("next: record the first of these from a model family other than "
650
+ "yours (two make a panel) with: consult --use <member>[,<member>], "
651
+ "then ask again - do not ask the user to choose")
650
652
  sys.stdout.flush()
651
653
  os._exit(4)
652
654
 
@@ -775,14 +777,15 @@ def main():
775
777
  print("retry: %s" % hint)
776
778
  code = 0 if answers else 3
777
779
  if not answers and not explicit:
778
- # The recorded choice is the user's, so consult never swaps it on its
779
- # own: it names what is left and the agent asks again.
780
+ # consult never swaps the recorded choice on its own: it names what is
781
+ # left, and the agent records the next member and asks again.
780
782
  lost = {c[1] for c, _cls in failures} | skip_opts
781
783
  left = [o for o in options() if o not in lost]
782
784
  for opt in left:
783
785
  print(offer_line("reoffer", opt))
784
- print("next: ask the user which of these to use instead, then run: "
785
- "consult --use <member>[,<member>] and ask again" if left else
786
+ print("next: record the first of these from another model family with: "
787
+ "consult --use <member>[,<member>], then ask again - do not ask the "
788
+ "user to choose" if left else
786
789
  "next: nothing else can answer here; tell the user the second "
787
790
  "opinion was skipped (install an agent CLI or add a provider key)")
788
791
  code = 5
package/bin/tezgah-docs CHANGED
@@ -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:678-946` (`CORE`).
109
+ # cell reads: `hooks/tezgah_policy.py:697-972` (`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
@@ -16,11 +16,17 @@
16
16
  tezgah-research source <slug> <hypothesis> --run <orxRunId> [--command "<cmd>"]
17
17
  [--scope real|fixture|derived] [--fixture "<what was generated>"]
18
18
  tezgah-research compare <slug> <decision> record one variants x criteria cell from stdin
19
+ tezgah-research verdict <slug> <id> met|not-met|unanswerable --evidence "<ref>"
20
+ record one success criterion's verdict
21
+ tezgah-research conclude <slug> conclude a line whose criteria all carry a verdict
22
+ tezgah-research migrate-layout move flat research/<slug> into open/ or done/
19
23
  tezgah-research close <slug> --limit "<reason>" conclude a line as a deliberate limit
20
24
  tezgah-research seal <slug> [--history-lost] --ack "<owner decision>"
21
25
  seal a line concluded before seals existed
22
26
  tezgah-research import <checkout> [<slug>] [--allow-open "<why>"]
23
27
  bring lines from another checkout, with history
28
+ tezgah-research next --topic <t> [--flow metric-dashboard|feature] [--json]
29
+ the product-flow steps of a topic and the next one
24
30
 
25
31
  A research line lives at <repo>/.tezgah/research/<slug>/ and holds the state an
26
32
  auditable investigation needs: the question, the decision log, the findings, the
@@ -103,6 +109,7 @@ import time
103
109
  HERE = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
104
110
  sys.path.insert(0, os.path.join(HERE, "hooks"))
105
111
  import tezgah_paths as tp # noqa: E402
112
+ import tezgah_product as tp_product # noqa: E402
106
113
  import tezgah_research as tr # noqa: E402
107
114
 
108
115
 
@@ -245,12 +252,17 @@ def cmd_commit(args):
245
252
  known = tr.slugs(repo)
246
253
  if slug not in known:
247
254
  return misuse("no such research line (have: %s)" % (", ".join(known) or "none"))
255
+ return commit_path(repo, tr.line_dir(repo, slug), message)
256
+
257
+
258
+ def commit_path(repo, path, message):
259
+ """Stage and commit `path` alone in the private repository of `repo`."""
248
260
  ws = tp.ensure_workspace(repo)
249
261
  if ws is None:
250
262
  print("commit refused: %s is not a git work tree (or .tezgah could not be "
251
263
  "set up), so there is no private repository to commit into" % repo)
252
264
  return 1
253
- rel = os.path.relpath(tr.line_dir(repo, slug), ws)
265
+ rel = os.path.relpath(path, ws)
254
266
  try:
255
267
  done = tp.ws_git(repo, "add", "-A", "--", rel)
256
268
  if not done.returncode:
@@ -298,6 +310,7 @@ def cmd_check(args):
298
310
  print("warn %s: %s" % (slug, warn))
299
311
  else:
300
312
  print("no research line under %s" % tr.root(repo))
313
+ lines = len(report)
301
314
  if ask_orx:
302
315
  # The registry is the repository's, not a line's, so this runs whatever the
303
316
  # lines look like - which is also why it is only asked for when --orx says so.
@@ -313,7 +326,7 @@ def cmd_check(args):
313
326
  print(json.dumps(report, indent=2))
314
327
  return 1 if problems else 0
315
328
  if not problems:
316
- print("research: %d line(s) ok" % len(report))
329
+ print("research: %d line(s) ok" % lines)
317
330
  if not rest and not all_lines:
318
331
  done = [slug for slug in known if tr.sealed(repo, slug)]
319
332
  if done:
@@ -576,6 +589,40 @@ def cmd_compare(args):
576
589
  return 0
577
590
 
578
591
 
592
+ def cmd_next(args):
593
+ """The product-flow steps of one topic with their status, then the first
594
+ missing or due step's skill and command; a report, so exit 0."""
595
+ usage = "next --topic <t> [--flow %s] [--json]" % "|".join(tp_product.FLOWS)
596
+ opts, as_json, rest = {}, False, list(args)
597
+ while rest:
598
+ arg = rest.pop(0)
599
+ if arg == "--json":
600
+ as_json = True
601
+ elif arg in ("--topic", "--flow") and rest and arg not in opts:
602
+ opts[arg] = rest.pop(0)
603
+ else:
604
+ return misuse(usage)
605
+ flow = opts.get("--flow", "metric-dashboard")
606
+ if "--topic" not in opts:
607
+ return misuse(usage)
608
+ if flow not in tp_product.FLOWS:
609
+ return misuse("unknown flow %r (%s)" % (flow, ", ".join(tp_product.FLOWS)))
610
+ topic = opts["--topic"]
611
+ steps = tp_product.next_steps(repo_root(os.getcwd()), topic, flow)
612
+ if as_json:
613
+ # the step list next_steps returns; the next step is its first
614
+ # missing or due entry
615
+ print(json.dumps(steps, indent=2))
616
+ return 0
617
+ first = next((s for s in steps if s["status"] in ("missing", "due")), None)
618
+ for n, step in enumerate(steps, 1):
619
+ print("%d. %s [%s] %s - %s" % (n, step["id"], step["status"], step["skill"],
620
+ step["detail"]))
621
+ print("next: %s - %s" % (first["skill"], first["command"]) if first
622
+ else "next: nothing left")
623
+ return 0
624
+
625
+
579
626
  def cmd_verdict(args):
580
627
  """Record one success criterion's verdict, with the evidence it rests on."""
581
628
  if len(args) < 3:
@@ -769,6 +816,8 @@ def main(argv):
769
816
  return cmd_source(rest)
770
817
  if cmd == "compare":
771
818
  return cmd_compare(rest)
819
+ if cmd == "next":
820
+ return cmd_next(rest)
772
821
  if cmd == "verdict":
773
822
  return cmd_verdict(rest)
774
823
  if cmd == "conclude":
@@ -782,7 +831,8 @@ def main(argv):
782
831
  if cmd == "import":
783
832
  return cmd_import(rest)
784
833
  return misuse("unknown command %r (init, commit, check, status, --all, claim, predict, "
785
- "components, migrate, source, compare, close, seal, import)" % cmd)
834
+ "components, migrate, source, compare, verdict, conclude, migrate-layout, "
835
+ "close, seal, import, next)" % cmd)
786
836
 
787
837
 
788
838
  if __name__ == "__main__":
package/bin/tezgah-route CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env python3
2
- """tezgah-route - which tier worker a delegated task goes to, and the model table.
2
+ """tezgah-route - which worker a delegated task goes to, and the model table.
3
3
 
4
4
  tezgah-route "<brief>" [--phase P] [--json] the agent to spawn, and why
5
5
  tezgah-route --check the table's age and refresh flags
@@ -9,10 +9,13 @@
9
9
  tezgah-route --report each route's first check, by tier
10
10
 
11
11
  A brief naming stored data, a migration, credentials, the gate or security goes to
12
- tezgah-frontier by rule; otherwise Jev picks the tier (one Choice, ~650 input
13
- tokens, ~0.4 s); with no key or `judge-off`, `--phase`
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 the judge seam's
12
+ tezgah-frontier by rule - or to tezgah-security when it only reviews; otherwise
13
+ Jev answers two Choices in one call (~700 input tokens, ~0.4 s): the tier, and
14
+ whether the brief is the whole job of a specialist (tezgah-tester, -docs,
15
+ -security, -ui, -researcher, -reviewer), which is then the agent named. With no
16
+ key or `judge-off`, `--phase review|research` names the reviewer or the
17
+ researcher, another `--phase` (explore|code|mechanical|plan) picks the tier from
18
+ the static table, and with neither the middle tier is used. The brief is sent to the judge seam's
16
19
  provider - the session's own CLI (omp, claude, opencode, cursor, codex) or the Jev
17
20
  model through TypeSafe, OpenRouter System One, Cloudflare Workers AI or a
18
21
  compatible endpoint (TEZGAH_JEV_PROVIDER picks one), else the OpenRouter chat
package/bin/tezgah-setup CHANGED
@@ -13,7 +13,6 @@
13
13
  tezgah-setup --adopt move the predecessor wiring aside
14
14
  tezgah-setup --sync dev: copy this checkout over the Claude plugin copy
15
15
  tezgah-setup --write-manifest release: regenerate the tracked MANIFEST from git
16
- tezgah-setup --write-plugin-agents release: re-render the plugin's agents/ from the role source
17
16
  tezgah-setup --status [PATH] print the armed/used checklist
18
17
  tezgah-setup --agents [PATH] regenerate this repo's subagent set
19
18
  tezgah-setup --deps [--dry-run] install missing optional tools only
@@ -188,7 +187,7 @@ OMP_LESSONS = ("Read `.tezgah/lessons.md` in the repository if it exists and tre
188
187
  LOCAL_BIN = os.path.join(HOME, ".local", "bin")
189
188
  SKILLS = ["harness", "tezgah-contract", "ponytail", "i-have-adhd", "no-ai-slop",
190
189
  "analyze-app", "plan-add", "plan-status", "plan-sync", "research",
191
- "product-analysis", "feature-audit", "design-contract",
190
+ "product-analysis", "product-flow", "feature-audit", "design-contract",
192
191
  "design-library", "pm-frameworks", "ai-research", "rl-env"]
193
192
 
194
193
 
@@ -1163,9 +1162,33 @@ def install_claude():
1163
1162
  # (`TEZGAH_CORE_IN_FILE`, hooks/projects-auto-init.py), so a session pays for
1164
1163
  # the contract once.
1165
1164
  print("%s %s (always-on contract)" % (OK, write_rules("claude")))
1165
+ write_user_agents("claude")
1166
1166
  register_claude_plugin()
1167
1167
 
1168
1168
 
1169
+ def write_user_agents(host):
1170
+ """The host's tezgah roles in its user agent dir, where it loads them from
1171
+ the first session (a repo dir written at session start is missed there),
1172
+ then tezgah's MARKER files no longer wanted - `agents-off`, a lost
1173
+ capability, a retired role - are removed. The user's own files stay."""
1174
+ agents_dir = tezgah_agents.user_agent_dir(host)
1175
+ wanted = tezgah_agents.user_agents(host)
1176
+ for name, text in sorted(wanted.items()):
1177
+ path = os.path.join(agents_dir, name)
1178
+ if write_if_changed(path, text):
1179
+ print("%s %s" % (OK, path))
1180
+ for path in tezgah_agents.remove_stale(agents_dir, wanted):
1181
+ print("%s removed %s" % (OK, path))
1182
+
1183
+
1184
+ def user_agent_files(host):
1185
+ """{filename: text} of tezgah's MARKER files in the host's user agent dir."""
1186
+ d = tezgah_agents.user_agent_dir(host)
1187
+ return {n: t for n in (os.listdir(d) if os.path.isdir(d) else [])
1188
+ if n.startswith("tezgah-")
1189
+ and tezgah_agents.MARKER in (t := tezgah_agents._read(os.path.join(d, n)) or "")}
1190
+
1191
+
1169
1192
  # The marketplace a manifest rendered by this installer carries, so Claude
1170
1193
  # installs the plugin as `tezgah@tezgah-local`. A manifest the tree already has
1171
1194
  # under another name (the maintainer's) is kept, name included.
@@ -1358,6 +1381,7 @@ def install_codex():
1358
1381
  # instructions Codex keeps for the whole session. Global, like omp's
1359
1382
  # RULES.md, because a per-project file would be a tracked project file.
1360
1383
  print("%s %s (always-on contract)" % (OK, write_rules("codex")))
1384
+ write_user_agents("codex")
1361
1385
 
1362
1386
 
1363
1387
  def _toml_str(value):
@@ -3293,6 +3317,9 @@ def verify_uninstall(hosts, full):
3293
3317
  # the user's own CLAUDE.md may name tezgah; only the block is ours
3294
3318
  clean("claude global CLAUDE.md block gone", [CLAUDE_RULES],
3295
3319
  needle="<!-- tezgah:start")
3320
+ claim("claude subagents gone",
3321
+ [os.path.join(tezgah_agents.user_agent_dir("claude"), n)
3322
+ for n in user_agent_files("claude")])
3296
3323
  if "codex" in hosts:
3297
3324
  clean("codex hooks.json unwired", [os.path.join(CODEX, "hooks.json")])
3298
3325
  claim("codex skills unlinked",
@@ -3304,6 +3331,9 @@ def verify_uninstall(hosts, full):
3304
3331
  clean("codex global AGENTS.md block gone",
3305
3332
  [os.path.join(CODEX, n) for n in CODEX_RULES],
3306
3333
  needle="<!-- tezgah:start")
3334
+ claim("codex subagents gone",
3335
+ [os.path.join(tezgah_agents.user_agent_dir("codex"), n)
3336
+ for n in user_agent_files("codex")])
3307
3337
  if "opencode" in hosts:
3308
3338
  claim("opencode plugin unlinked",
3309
3339
  [os.path.join(OPENCODE, d, "tezgah.js") for d in ("plugins", "plugin")]
@@ -3454,6 +3484,13 @@ def uninstall_claude():
3454
3484
  unwire_claude_settings()
3455
3485
  drop_claude_mcp_row()
3456
3486
  strip_managed_md(CLAUDE_RULES)
3487
+ remove_user_agents("claude")
3488
+
3489
+
3490
+ def remove_user_agents(host):
3491
+ """Every tezgah MARKER file in the host's user agent dir; the user's stay."""
3492
+ for path in tezgah_agents.remove_stale(tezgah_agents.user_agent_dir(host), ()):
3493
+ print("%s removed %s" % (OK, path))
3457
3494
 
3458
3495
 
3459
3496
  def drop_claude_mcp_row():
@@ -3488,6 +3525,7 @@ def uninstall_codex():
3488
3525
  # both names: the override may have appeared, or gone, since the install
3489
3526
  for name in CODEX_RULES:
3490
3527
  strip_managed_md(os.path.join(CODEX, name))
3528
+ remove_user_agents("codex")
3491
3529
 
3492
3530
 
3493
3531
  def uninstall_opencode():
@@ -4429,6 +4467,8 @@ def host_checks_claude():
4429
4467
  bool(copies) and all(plugin_copy_current(t) for t in copies)),
4430
4468
  ("CLAUDE.md carries the contract (matches a fresh render)",
4431
4469
  rules_current("claude")),
4470
+ ("subagents in %s (match a fresh render)" % tezgah_agents.user_agent_dir("claude"),
4471
+ user_agent_files("claude") == tezgah_agents.user_agents("claude")),
4432
4472
  ] + claude_mcp_rows(copies)
4433
4473
 
4434
4474
 
@@ -4617,6 +4657,8 @@ def host_checks_codex():
4617
4657
  ("MCP in config.toml", "mcp_servers.codegraph" in toml),
4618
4658
  ("%s carries the contract (matches a fresh render)"
4619
4659
  % os.path.basename(codex_rules()), rules_current("codex")),
4660
+ ("subagents in %s (match a fresh render)" % tezgah_agents.user_agent_dir("codex"),
4661
+ user_agent_files("codex") == tezgah_agents.user_agents("codex")),
4620
4662
  ] + mcp_rows(toml_servers(toml_path), "config.toml") + [
4621
4663
  mcp_row(TEZGAH_MCP_NAME, toml_mcp_entry(toml_path, TEZGAH_MCP_NAME),
4622
4664
  "config.toml", tezgah_mcp_command()),
@@ -4929,24 +4971,6 @@ def write_manifest():
4929
4971
  return 0
4930
4972
 
4931
4973
 
4932
- def write_plugin_agents():
4933
- """Re-render the plugin's tracked `agents/*.md` from tezgah_agents' role
4934
- bodies, the source the per-repo agents are generated from. A plain write,
4935
- not write_if_changed: a tracked file needs no `.tezgah-bak` beside it."""
4936
- for name, text in tezgah_agents.plugin_agents().items():
4937
- path = os.path.join(HERE, "agents", name)
4938
- try:
4939
- with open(path, encoding="utf-8") as fh:
4940
- if fh.read() == text:
4941
- continue
4942
- except OSError:
4943
- pass
4944
- with open(path, "w", encoding="utf-8") as fh:
4945
- fh.write(text)
4946
- print("%s %s" % (OK, path))
4947
- return 0
4948
-
4949
-
4950
4974
  def tree_sha(root, files):
4951
4975
  """sha256 over each relpath and its bytes, or None when one is unreadable."""
4952
4976
  h = hashlib.sha256()
@@ -5647,8 +5671,6 @@ def main(argv):
5647
5671
  help="regenerate this repo's subagent set (default: cwd)")
5648
5672
  ap.add_argument("--write-manifest", action="store_true",
5649
5673
  help="release: regenerate the tracked MANIFEST from git")
5650
- ap.add_argument("--write-plugin-agents", action="store_true",
5651
- help="release: re-render the plugin's agents/ from the role source")
5652
5674
  ap.add_argument("--deps", action="store_true")
5653
5675
  ap.add_argument("--mcp-schemas", action="store_true",
5654
5676
  help="measure the MCP tool-schema band by asking each server")
@@ -5695,7 +5717,7 @@ def main(argv):
5695
5717
  help="with --install: the language replies are asked for and "
5696
5718
  "the Stop rule checks (config.json `reply_lang`; `tr` "
5697
5719
  "when unset, `any` checks none)")
5698
- ap.add_argument("--jev", choices=tp.JEV_CHOICES,
5720
+ ap.add_argument("--jev", type=tp.jev_name, choices=tp.JEV_CHOICES,
5699
5721
  help="the carrier the judgement seam reaches the Jev model "
5700
5722
  "through (config.json `jev`; `auto` tries every one "
5701
5723
  "that resolves); TEZGAH_JEV_PROVIDER and JEV_PROVIDER "
@@ -5763,8 +5785,6 @@ def main(argv):
5763
5785
  return 0
5764
5786
  if args.write_manifest:
5765
5787
  return write_manifest()
5766
- if args.write_plugin_agents:
5767
- return write_plugin_agents()
5768
5788
  if args.status:
5769
5789
  print(tezgah_context.health_lines(os.path.realpath(expand(args.status))))
5770
5790
  # The install-health half of this report, from the same function the