@r1z4x/tezgah 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/CHANGELOG.md +92 -1
  2. package/MANIFEST +10 -0
  3. package/bin/tezgah-agents +10 -1
  4. package/bin/tezgah-docs +1 -1
  5. package/bin/tezgah-doctor +11 -0
  6. package/bin/tezgah-research +31 -14
  7. package/bin/tezgah-setup +154 -29
  8. package/bin/tezgah-skill +59 -0
  9. package/bin/tezgah-skill.py +59 -0
  10. package/bin/tezgah-status +62 -1
  11. package/bin/tezgah-taste +504 -5
  12. package/bin/tezgah-triage +5 -3
  13. package/docs/README.md +1 -0
  14. package/docs/architecture.md +5 -2
  15. package/docs/citations-baseline.json +1 -1
  16. package/docs/clarity-baseline.json +9 -9
  17. package/docs/contract.md +68 -26
  18. package/docs/environment.md +6 -3
  19. package/docs/evidence.md +7 -0
  20. package/docs/gate.md +13 -7
  21. package/docs/glossary.md +1 -1
  22. package/docs/hosts.md +48 -1
  23. package/docs/index.json +34 -3
  24. package/docs/judge.md +75 -60
  25. package/docs/models.md +14 -8
  26. package/docs/operations.md +80 -25
  27. package/docs/orca.md +126 -0
  28. package/docs/research.md +38 -3
  29. package/docs/skills.md +74 -0
  30. package/docs/status-line.md +3 -1
  31. package/docs/testing.md +33 -21
  32. package/docs/vision.md +12 -8
  33. package/hooks/projects-posttooluse.py +7 -0
  34. package/hooks/projects-pretooluse.py +17 -5
  35. package/hooks/tezgah_agents.py +135 -13
  36. package/hooks/tezgah_components.py +3 -0
  37. package/hooks/tezgah_context.py +84 -22
  38. package/hooks/tezgah_gate.py +133 -17
  39. package/hooks/tezgah_integrity.py +60 -13
  40. package/hooks/tezgah_judge.py +288 -66
  41. package/hooks/tezgah_models.py +41 -5
  42. package/hooks/tezgah_orca.py +144 -0
  43. package/hooks/tezgah_paths.py +40 -6
  44. package/hooks/tezgah_policy.py +109 -35
  45. package/hooks/tezgah_research.py +104 -8
  46. package/hooks/tezgah_skill_pick.py +459 -8
  47. package/hooks/tezgah_stack.py +382 -0
  48. package/hooks/tezgah_stopspec.py +2 -2
  49. package/hooks/tezgah_taste.py +35 -9
  50. package/hooks/tezgah_taste_ledger.py +628 -0
  51. package/hosts/codex/hook.py +12 -1
  52. package/hosts/cursor/hook.py +12 -4
  53. package/hosts/omp/hook.py +18 -6
  54. package/hosts/omp/tezgah-hook.ts.in +20 -7
  55. package/hosts/opencode/plugins/tezgah.js +28 -15
  56. package/output-styles/tezgah.md +34 -19
  57. package/package.json +1 -1
  58. package/skills/i-have-adhd/SKILL.md +2 -2
  59. package/skills/research/SKILL.md +60 -17
  60. package/skills/tezgah-contract/SKILL.md +55 -13
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env python3
2
+ """tezgah-skill [-k N] [--json] [--root DIR] QUERY... - find the skill section
3
+ that answers a topic.
4
+
5
+ The roster a session meets is hundreds of one-line names, so the skill that
6
+ holds the answer is a guess unless something searches the bodies. This is that
7
+ search: every installed skill root on the machine (the plugin's own, omp's user
8
+ and custom-directories corpora, Claude/Codex/Agents/opencode), indexed by
9
+ heading, ranked by the same BM25 the per-turn lessons block uses. Each hit is a
10
+ section, not a file:
11
+
12
+ skill://attack-jwt:26-122 /abs/SKILL.md JWT Token Attack
13
+
14
+ The `skill://<name>:<start>-<end>` address is what omp's read tool resolves
15
+ (line selectors included); the absolute path is for the hosts that have no
16
+ skill:// transport, and is also the tiebreaker where two roots hold different
17
+ skills under one name (the search reports the first root's copy, omp's own
18
+ precedence order - it does not resolve the collision for you).
19
+
20
+ --root DIR adds a directory to the discovered roots (a project's vendored
21
+ skills); --json prints the machine shape. Exit 0 hits, 1 none, 2 usage.
22
+ """
23
+ import argparse
24
+ import json
25
+ import os
26
+ import sys
27
+
28
+ HERE = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
29
+ sys.path.insert(0, os.path.join(HERE, "hooks"))
30
+ import tezgah_skill_pick as sp # noqa: E402
31
+
32
+
33
+ def main(argv=None):
34
+ parser = argparse.ArgumentParser(
35
+ prog="tezgah-skill", description=__doc__.splitlines()[0])
36
+ parser.add_argument("query", nargs="+", help="the topic to search for")
37
+ parser.add_argument("-k", type=int, default=5, metavar="N",
38
+ help="sections to print (default 5)")
39
+ parser.add_argument("--root", action="append", default=[], metavar="DIR",
40
+ help="an extra skills directory to index (repeatable)")
41
+ parser.add_argument("--json", action="store_true",
42
+ help="print [{skill, uri, path, title, start, end}]")
43
+ args = parser.parse_args(argv)
44
+ query = " ".join(args.query)
45
+ hits = sp.search(query, k=max(1, args.k),
46
+ roots=(sp.skill_roots(args.root) if args.root else None))
47
+ if args.json:
48
+ print(json.dumps([{"skill": h[0],
49
+ "uri": "skill://%s:%d-%d" % (h[0], h[3], h[4]),
50
+ "path": h[1], "title": h[2],
51
+ "start": h[3], "end": h[4]} for h in hits], indent=1))
52
+ else:
53
+ for skill, path, title, start, end in hits:
54
+ print("skill://%s:%d-%d\t%s\t%s" % (skill, start, end, path, title))
55
+ return 0 if hits else 1
56
+
57
+
58
+ if __name__ == "__main__":
59
+ sys.exit(main())
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env python3
2
+ """tezgah-skill [-k N] [--json] [--root DIR] QUERY... - find the skill section
3
+ that answers a topic.
4
+
5
+ The roster a session meets is hundreds of one-line names, so the skill that
6
+ holds the answer is a guess unless something searches the bodies. This is that
7
+ search: every installed skill root on the machine (the plugin's own, omp's user
8
+ and custom-directories corpora, Claude/Codex/Agents/opencode), indexed by
9
+ heading, ranked by the same BM25 the per-turn lessons block uses. Each hit is a
10
+ section, not a file:
11
+
12
+ skill://attack-jwt:26-122 /abs/SKILL.md JWT Token Attack
13
+
14
+ The `skill://<name>:<start>-<end>` address is what omp's read tool resolves
15
+ (line selectors included); the absolute path is for the hosts that have no
16
+ skill:// transport, and is also the tiebreaker where two roots hold different
17
+ skills under one name (the search reports the first root's copy, omp's own
18
+ precedence order - it does not resolve the collision for you).
19
+
20
+ --root DIR adds a directory to the discovered roots (a project's vendored
21
+ skills); --json prints the machine shape. Exit 0 hits, 1 none, 2 usage.
22
+ """
23
+ import argparse
24
+ import json
25
+ import os
26
+ import sys
27
+
28
+ HERE = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
29
+ sys.path.insert(0, os.path.join(HERE, "hooks"))
30
+ import tezgah_skill_pick as sp # noqa: E402
31
+
32
+
33
+ def main(argv=None):
34
+ parser = argparse.ArgumentParser(
35
+ prog="tezgah-skill", description=__doc__.splitlines()[0])
36
+ parser.add_argument("query", nargs="+", help="the topic to search for")
37
+ parser.add_argument("-k", type=int, default=5, metavar="N",
38
+ help="sections to print (default 5)")
39
+ parser.add_argument("--root", action="append", default=[], metavar="DIR",
40
+ help="an extra skills directory to index (repeatable)")
41
+ parser.add_argument("--json", action="store_true",
42
+ help="print [{skill, uri, path, title, start, end}]")
43
+ args = parser.parse_args(argv)
44
+ query = " ".join(args.query)
45
+ hits = sp.search(query, k=max(1, args.k),
46
+ roots=(sp.skill_roots(args.root) if args.root else None))
47
+ if args.json:
48
+ print(json.dumps([{"skill": h[0],
49
+ "uri": "skill://%s:%d-%d" % (h[0], h[3], h[4]),
50
+ "path": h[1], "title": h[2],
51
+ "start": h[3], "end": h[4]} for h in hits], indent=1))
52
+ else:
53
+ for skill, path, title, start, end in hits:
54
+ print("skill://%s:%d-%d\t%s\t%s" % (skill, start, end, path, title))
55
+ return 0 if hits else 1
56
+
57
+
58
+ if __name__ == "__main__":
59
+ sys.exit(main())
package/bin/tezgah-status CHANGED
@@ -62,7 +62,17 @@ their status line; this CLI is how every host (and a human) gets it:
62
62
  records, prose-only) and the record from
63
63
  docs/contract.md. A report, not a gate: it always
64
64
  exits 0. `--json` prints the rows.
65
+ tezgah-status --judge what the judgement seam used last: the provider and
66
+ model that answered, and why it was not the session's
67
+ own CLI when it was not, or the refusal and the
68
+ failures that left no judgement. `--json` prints it.
65
69
  tezgah-status --legend explain the marks
70
+ tezgah-status --orca [PATH]
71
+ this repository's checkouts as Orca sees them: each
72
+ one Orca tracks with its card status, live terminal
73
+ count and agent states (`orca worktree ps`), and each
74
+ git worktree Orca does not list. Exits 0 when Orca is
75
+ absent or down and says so. `--json` prints the fold.
66
76
  tezgah-status --observable=consult,research,graph,orch
67
77
  the measures this surface can see; a measure outside
68
78
  the set renders as dim "no state" instead of claiming
@@ -200,6 +210,37 @@ def print_enforcers(as_json):
200
210
  print("unrecorded: %s" % failure)
201
211
 
202
212
 
213
+ def print_orca(cwd, as_json):
214
+ """`--orca`: this repository's git checkouts against Orca's listing. A
215
+ checkout Orca does not list has no card, terminal tracking or agent status
216
+ in Orca: create the next one with `orca worktree create`, or show the
217
+ existing ones with `orca repo set --external-worktree-visibility show`."""
218
+ import tezgah_orca
219
+ from tezgah_paths import worktrees
220
+ if not tezgah_orca.cli():
221
+ print("orca CLI not found: nothing to track")
222
+ return 0
223
+ found = tezgah_orca.checkouts(worktrees(cwd) or [cwd])
224
+ if found is None:
225
+ print("orca runtime did not answer `orca worktree ps --json` "
226
+ "(start it with `orca open`)")
227
+ return 0
228
+ if as_json:
229
+ print(json.dumps(found))
230
+ return 0
231
+ print("Orca tracks %d checkout(s) of this repository:" % len(found["tracked"]))
232
+ for w in found["tracked"]:
233
+ print(" %s [%s/%s] %d terminal(s) agents: %s" % (
234
+ w["path"], w["card"] or "-", w["status"] or "-", w["terminals"],
235
+ ", ".join(w["agents"]) or "-"))
236
+ if found["untracked"]:
237
+ print("not listed by Orca (raw `git worktree`; no card, terminals or "
238
+ "agent status there): %d" % len(found["untracked"]))
239
+ for path in found["untracked"]:
240
+ print(" %s" % path)
241
+ return 0
242
+
243
+
203
244
  def main():
204
245
  # `--` ends the options: what follows is PATH and SESSION_ID even when it
205
246
  # starts with a dash. The dsh route and the MCP tool hand caller-chosen
@@ -219,7 +260,7 @@ def main():
219
260
  pos = [a for a in args if not a.startswith("--")] + rest
220
261
  known = {"--legend", "--json", "--color", "--no-color", "--counters",
221
262
  "--all", "--trend", "--skill-fitness", "--failure-shapes",
222
- "--rule-yield", "--enforcers", "--help"}
263
+ "--rule-yield", "--enforcers", "--orca", "--judge", "--help"}
223
264
  unknown = sorted(f for f in flags
224
265
  if f not in known and not f.startswith("--observable="))
225
266
  if unknown:
@@ -252,6 +293,23 @@ def main():
252
293
  sys.stderr.write("tezgah-status: %s only means something with "
253
294
  "--counters\n" % modifier)
254
295
  return 2
296
+ if "--judge" in flags:
297
+ import tezgah_judge as tj
298
+ last = tj.last_use()
299
+ if "--json" in flags:
300
+ print(json.dumps(last, sort_keys=True))
301
+ elif not last:
302
+ print("judge: no judgement recorded")
303
+ elif last.get("provider"):
304
+ print("judge: %s answered with %s/%s (fallback=%s)%s" % (
305
+ last.get("at"), last["provider"], last.get("model"),
306
+ last.get("policy"), "; not the session: " + last["fallback"]
307
+ if last.get("fallback") else ""))
308
+ else:
309
+ print("judge: %s no judgement (fallback=%s): %s; failed: %s" % (
310
+ last.get("at"), last.get("policy"),
311
+ last.get("refused") or "every provider failed", last.get("failed")))
312
+ return 0
255
313
  if "--skill-fitness" in flags:
256
314
  fit = skill_fitness()
257
315
  if "--json" in flags:
@@ -362,6 +420,9 @@ def main():
362
420
  if "--enforcers" in flags:
363
421
  print_enforcers("--json" in flags)
364
422
  return 0
423
+ if "--orca" in flags:
424
+ return print_orca(os.path.realpath(pos[0] if pos else os.getcwd()),
425
+ "--json" in flags)
365
426
  cwd = os.path.realpath(pos[0] if pos else os.getcwd())
366
427
  session = pos[1] if len(pos) > 1 else os.environ.get("TEZGAH_SESSION")
367
428
  spec = next((f.split("=", 1)[1] for f in flags