@r1z4x/tezgah 1.0.0 → 1.1.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 (56) hide show
  1. package/CHANGELOG.md +71 -1
  2. package/MANIFEST +8 -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-triage +5 -3
  12. package/docs/README.md +1 -0
  13. package/docs/architecture.md +5 -2
  14. package/docs/citations-baseline.json +1 -1
  15. package/docs/clarity-baseline.json +9 -9
  16. package/docs/contract.md +68 -26
  17. package/docs/environment.md +6 -3
  18. package/docs/evidence.md +7 -0
  19. package/docs/gate.md +13 -7
  20. package/docs/glossary.md +1 -1
  21. package/docs/hosts.md +48 -1
  22. package/docs/index.json +29 -1
  23. package/docs/judge.md +61 -55
  24. package/docs/models.md +14 -8
  25. package/docs/operations.md +10 -5
  26. package/docs/orca.md +126 -0
  27. package/docs/research.md +38 -3
  28. package/docs/skills.md +74 -0
  29. package/docs/status-line.md +3 -1
  30. package/docs/testing.md +33 -21
  31. package/docs/vision.md +12 -8
  32. package/hooks/projects-pretooluse.py +17 -5
  33. package/hooks/tezgah_agents.py +135 -13
  34. package/hooks/tezgah_components.py +3 -0
  35. package/hooks/tezgah_context.py +65 -22
  36. package/hooks/tezgah_gate.py +133 -17
  37. package/hooks/tezgah_integrity.py +60 -13
  38. package/hooks/tezgah_judge.py +239 -56
  39. package/hooks/tezgah_models.py +41 -5
  40. package/hooks/tezgah_orca.py +144 -0
  41. package/hooks/tezgah_paths.py +40 -6
  42. package/hooks/tezgah_policy.py +109 -35
  43. package/hooks/tezgah_research.py +104 -8
  44. package/hooks/tezgah_skill_pick.py +459 -8
  45. package/hooks/tezgah_stack.py +382 -0
  46. package/hooks/tezgah_stopspec.py +2 -2
  47. package/hosts/codex/hook.py +7 -1
  48. package/hosts/cursor/hook.py +7 -4
  49. package/hosts/omp/hook.py +12 -6
  50. package/hosts/omp/tezgah-hook.ts.in +20 -7
  51. package/hosts/opencode/plugins/tezgah.js +18 -9
  52. package/output-styles/tezgah.md +34 -19
  53. package/package.json +1 -1
  54. package/skills/i-have-adhd/SKILL.md +2 -2
  55. package/skills/research/SKILL.md +60 -17
  56. 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
package/bin/tezgah-triage CHANGED
@@ -90,9 +90,11 @@ MEASURED = ("100% of the control lines at a 94% read on a reproduced 356-line "
90
90
  "screen")
91
91
  CUT = 96
92
92
  USAGE = "tezgah-triage --select FILE --task TEXT | --states FILE [--component TEXT]"
93
- NO_CREDENTIAL = ("no credential (TYPESAFE_API_KEY or ~/.config/typesafe/key, or "
94
- "the OpenRouter fallback's OPENROUTER_API_KEY or "
95
- "~/.config/openrouter/key), or the judge-off switch is on")
93
+ NO_CREDENTIAL = ("no judge: no session CLI (omp or claude, from OMPCODE/CLAUDECODE), "
94
+ "and no third party the `fallback` setting allows (TYPESAFE_API_KEY "
95
+ "or ~/.config/typesafe/key, or the OpenRouter fallback's "
96
+ "OPENROUTER_API_KEY or ~/.config/openrouter/key), or the judge-off "
97
+ "switch is on")
96
98
  TRIAGE_OFF = "the triage-off switch is on"
97
99
  FAILED = ("the call failed - a timeout, a refusal, or a reply that is not the "
98
100
  "documented shape")
package/docs/README.md CHANGED
@@ -36,6 +36,7 @@ citation you can open: `path::symbol` into Python code, `path:line` elsewhere.
36
36
  | Which model runs which subagent, and how does `tezgah-route` pick one? | [models](models.md) |
37
37
  | Where does a research line live, and what does `tezgah-research check` refuse? | [research](research.md) |
38
38
  | What does each host get, and what can it observe? | [hosts](hosts.md) |
39
+ | Inside Orca, who owns worktrees and terminals, and which checkouts does Orca track? | [orca](orca.md) |
39
40
  | Which skills ship, and how does one reach a host? | [skills](skills.md) |
40
41
  | How is one feature audited across its layers, and what does a capability change need to carry? | [feature-audit](feature-audit.md) |
41
42
  | How do I check a change, and how do I test it? | [testing](testing.md) |
@@ -93,6 +93,7 @@ One session, in order. Each step names the file that handles the event on Claude
93
93
  the graph index status and the detached index spawn (`hooks/tezgah_context.py::context_for`),
94
94
  the open plans, the sibling checkouts of a `git worktree` with their open plan
95
95
  and research line counts (`sibling_line`, `hooks/tezgah_context.py::sibling_line`),
96
+ inside Orca the `Orca:` line ([orca](orca.md), `hooks/tezgah_orca.py::context_line`),
96
97
  the lessons ledger, and the active kill switches. The CORE is
97
98
  dropped when the host's own file already carries it (`TEZGAH_CORE_IN_FILE`,
98
99
  declared by that manifest row: `~/.claude/CLAUDE.md` on Claude). Outside a
@@ -110,9 +111,11 @@ One session, in order. Each step names the file that handles the event on Claude
110
111
  session block's last five, see
111
112
  [operations.md](operations.md#opt-in-embedding-relevance); each once per
112
113
  session, remembered in the turn stamp and forgotten at a compaction),
113
- and the stale-index notice (`hooks/tezgah_context.py::index_notice`).
114
+ and the stale-index notice, which also starts the incremental re-index
115
+ (`hooks/tezgah_context.py::index_notice`).
114
116
  3. **PreToolUse** — `hooks/projects-pretooluse.py::main` calls `decision` and emits
115
- the deny envelope (`hooks/projects-pretooluse.py::main`). The gate is the same
117
+ the deny envelope, or, for a code-symbol search it lets through, the graph line
118
+ as `additionalContext` (`hooks/tezgah_gate.py::graph_advice`). The gate is the same
116
119
  object on every host: `hosts/omp/hook.py::handle`, `hosts/codex/hook.py::gate_reason`.
117
120
  4. **PostToolUse** — `hooks/projects-posttooluse.py::main` writes the evidence row
118
121
  through `note_tool` (`hooks/projects-posttooluse.py::main`), records the used
@@ -42,7 +42,7 @@
42
42
  "unjudged": 9
43
43
  },
44
44
  "docs/testing.md": {
45
- "unjudged": 42
45
+ "unjudged": 41
46
46
  },
47
47
  "hooks/tezgah_gate.py": {
48
48
  "unjudged": 1
@@ -18,10 +18,10 @@
18
18
  "semicolons": 9
19
19
  },
20
20
  "docs/contract.md": {
21
- "long_sentences": 40,
21
+ "long_sentences": 43,
22
22
  "passive": 26,
23
23
  "replace_words": 0,
24
- "semicolons": 30
24
+ "semicolons": 31
25
25
  },
26
26
  "docs/environment.md": {
27
27
  "long_sentences": 3,
@@ -45,7 +45,7 @@
45
45
  "long_sentences": 67,
46
46
  "passive": 32,
47
47
  "replace_words": 1,
48
- "semicolons": 46
48
+ "semicolons": 45
49
49
  },
50
50
  "docs/glossary.md": {
51
51
  "long_sentences": 51,
@@ -60,10 +60,10 @@
60
60
  "semicolons": 18
61
61
  },
62
62
  "docs/judge.md": {
63
- "long_sentences": 28,
63
+ "long_sentences": 23,
64
64
  "passive": 15,
65
65
  "replace_words": 0,
66
- "semicolons": 17
66
+ "semicolons": 14
67
67
  },
68
68
  "docs/layers.md": {
69
69
  "long_sentences": 20,
@@ -90,10 +90,10 @@
90
90
  "semicolons": 77
91
91
  },
92
92
  "docs/skills.md": {
93
- "long_sentences": 31,
94
- "passive": 16,
93
+ "long_sentences": 36,
94
+ "passive": 18,
95
95
  "replace_words": 0,
96
- "semicolons": 14
96
+ "semicolons": 16
97
97
  },
98
98
  "docs/status-line.md": {
99
99
  "long_sentences": 44,
@@ -111,6 +111,6 @@
111
111
  "long_sentences": 9,
112
112
  "passive": 7,
113
113
  "replace_words": 1,
114
- "semicolons": 12
114
+ "semicolons": 11
115
115
  }
116
116
  }
package/docs/contract.md CHANGED
@@ -17,12 +17,24 @@ host says the same thing (`hooks/tezgah_policy.py:14-15`); path placeholders
17
17
 
18
18
  | Surface | Text | Paid |
19
19
  |---|---|---|
20
- | always-on core | `CORE` (`hooks/tezgah_policy.py::CORE`) minus the five conditional paragraphs, plus the pointer line | once per session: `session_start` and `post_compact` |
21
- | conditional paragraph | one of the five keyed by `CONDITIONAL_KEYS` (`hooks/tezgah_policy.py::CONDITIONAL_KEYS`) | in full on the first turn of a session whose prompt matches its task class; a later match pays one line (`hooks/tezgah_context.py::armed_again`) until a compaction or `ARMED_RESURFACE` turns (`hooks/tezgah_context.py::ARMED_RESURFACE`) bring the full text back |
20
+ | always-on core | `CORE` (`hooks/tezgah_policy.py::CORE`) minus the six conditional paragraphs, plus the pointer line | once per session: `session_start` and `post_compact` |
21
+ | conditional paragraph | one of the six keyed by `CONDITIONAL_KEYS` (`hooks/tezgah_policy.py::CONDITIONAL_KEYS`) | in full on the first turn of a session whose prompt matches its task class; a later match pays one line (`hooks/tezgah_context.py::armed_again`) until a compaction or `ARMED_RESURFACE` turns (`hooks/tezgah_context.py::ARMED_RESURFACE`) bring the full text back |
22
22
  | per-turn reminder | `PROMPT_REMINDER` (`hooks/tezgah_policy.py::PROMPT_REMINDER`) | every user prompt |
23
- | skill suggestion | one `<skill_relevance>` line naming at most one installed skill, written by `suggest` (`hooks/tezgah_skill_pick.py::suggest`); off unless `skill-suggest-on` is armed | only on a turn the judgement answers with a skill |
23
+ | skill suggestion | one `<skill_relevance>` line naming at most one installed skill and the SECTION of it that matches the turn (`skill://<name>:<start>-<end>` plus the absolute path), written by `suggest` (`hooks/tezgah_skill_pick.py::suggest`) when `skill-suggest-on` is armed, else by the model-free section search (`hooks/tezgah_skill_pick.py::section_hint`), default on | only on a turn the judgement answers with a skill, or whose prompt one section covers (each section once per session) |
24
24
  | on-demand full contract | the long-form blocks of `hooks/tezgah_policy.py` (`EXEC`, `CONSULT`, `RESEARCH`, …), shipped hand-kept as `skills/tezgah-contract/SKILL.md` | only when the session loads that skill |
25
25
 
26
+ Two paragraphs of the core are complete statements of their rule, not
27
+ summaries the session must upgrade: **Ponytail** and **Output shape:
28
+ ADHD-friendly** carry every operative clause of their skills (the runnable-check
29
+ rule, the lazy-version-and-question reply, the evidence-gated cause, the
30
+ three-still-broken stop, the pre-send delete pass) from the first turn of every
31
+ session, because "on the first non-trivial task, read the full skill" armed a
32
+ rule only when the model chose to load it. What the skills still hold is the
33
+ level table, the reasoning and the worked examples (`skills/ponytail/SKILL.md`,
34
+ `skills/i-have-adhd/SKILL.md`), and the pointer says so. The kill switches drop
35
+ the paragraphs whole (`ponytail-auto.off`/`.no-ponytail`, `adhd-off`/`.no-adhd`),
36
+ rules and all.
37
+
26
38
  The skill suggestion is the one surface a judgement writes rather than a constant.
27
39
  The roster reaches a session as an index of host-truncated one-liners, so which
28
40
  entry to look at first is the turn's own weak spot; one batched judgement request
@@ -33,8 +45,10 @@ asking whether the turn wants a skill at all - and the winner is appended after
33
45
  the armed paragraphs as a hint to look at first, never as an instruction to load
34
46
  (`hooks/tezgah_skill_pick.py:41-75` is the whole configuration, wired into
35
47
  `context_for` at `hooks/tezgah_context.py::context_for`). Measured cost of the line
36
- itself: 305-317 characters, about 78 tokens of prompt. Measured cost of the
37
- judgement: one call per unanswered prompt, 909-915 input tokens, 0.77-0.81 s,
48
+ itself: 455-523 characters, about 115-130 tokens of prompt (the section address
49
+ added ~150 characters over the name-only line; `bin/tezgah-skill` is the same
50
+ ranking without the call). Measured cost of the judgement: one call per
51
+ unanswered prompt, 909-915 input tokens, 0.77-0.81 s,
38
52
  $0.000038 at $0.042/1M on three live judgements. It is OFF UNLESS ARMED -
39
53
  `skill-suggest-on` in `~/.config/tezgah`, the same place the kill switches live -
40
54
  because a labelled set of 28 prompts through an independent chooser was measured
@@ -47,6 +61,19 @@ session is never paid for twice (`tests/test_skill_pick.py`). The host's own
47
61
  roster text is left untouched: that is what a session matches on and what its
48
62
  prefix cache covers, which is the whole reason the upstream recipe keeps it.
49
63
 
64
+ When the judgement gives no line, the local section search may write one. It
65
+ uses no model and is on by default (`hooks/tezgah_skill_pick.py::section_hint`,
66
+ [skills](skills.md)). Its SQLite index lives in the tezgah cache dir and
67
+ rebuilds only when a SKILL.md's stat moves. The line needs a top section that
68
+ carries 60% of the prompt's distinct content terms, and at least three. It
69
+ names each section once per session and drops the absolute path on omp, whose
70
+ read tool resolves `skill://`. Measured on this machine: 6-15 ms warm, and
71
+ 1.36 s for the one cold rebuild over 7 502 sections. The line costs 196-239
72
+ bytes on omp and 245-320 elsewhere. It is not a judgement, so `judge-off` leaves it on,
73
+ and `reminder-off` drops it with the per-turn text. The always-on core also
74
+ sends every session to the same search by hand. Run `tezgah-skill <words>`
75
+ before working a topic a skill covers, then read only the returned range.
76
+
50
77
  `context_for(event, …)` is the one dispatcher (`hooks/tezgah_context.py::context_for`):
51
78
  `session_start`/`post_compact` build core plus live state, `user_prompt` builds
52
79
  the reminder, whatever was armed and any skill hint, `subagent_start` builds a short brief
@@ -142,23 +169,26 @@ research lines with a written flip; everything else keeps one fresh reviewer
142
169
  Paragraphs are concatenated in the order they appear in `CORE` and identified by
143
170
  the bold label each starts with (`CORE_RULES`, `hooks/tezgah_context.py::CORE_RULES`).
144
171
  That order, with the line each label sits on in `hooks/tezgah_policy.py`:
145
- `**{REPLY_LANG}, BLUF.**` :637, `**Ponytail (minimal code).**` :645, `**Output shape:
146
- ADHD-friendly.**` :656, `**Deliver the whole ask; never the shortcut.**` :670,
147
- `**Integrity: evidence, or "doğrulanmadı".**` :684, `**Loop discipline.**` :700,
148
- `**Spec before building.**` :709 *(conditional)*, `**Lessons ledger: stop
149
- repeating mistakes.**` :723, `**Code discovery: graph first.**` :731
150
- *(conditional)*, `**Consult before irreversible.**` :740 *(conditional)*,
151
- `**Research: route it to OpenResearch.**` :756 *(conditional)*, `**Product
152
- analysis: five axes, one evidence class per finding.**` :772 *(conditional)*,
153
- `**No AI attribution, ever, on any host.**` :805, `**Identifiers and messages
154
- stay English.**` :815, `**Session scope: the user's repo, not tezgah.**` :821,
155
- `**Kill switches:**` :837. The first label renders as `**Turkish, BLUF.**` under
172
+ `**{REPLY_LANG}, BLUF.**` :649, `**Ponytail (minimal code).**` :657, `**Output shape:
173
+ ADHD-friendly.**` :668, `**Deliver the whole ask; never the shortcut.**` :682,
174
+ `**Integrity: evidence, or "doğrulanmadı".**` :696, `**Loop discipline.**` :713,
175
+ `**Spec before building.**` :722 *(conditional)*, `**Lessons ledger: stop
176
+ repeating mistakes.**` :736, `**Code discovery: graph first.**` :744
177
+ *(conditional)*, `**Consult before irreversible.**` :753 *(conditional)*,
178
+ `**Research: route it to OpenResearch.**` :769 *(conditional)*, `**Product
179
+ analysis: five axes, one evidence class per finding.**` :785 *(conditional)*,
180
+ `**No AI attribution, ever, on any host.**` :818, `**Identifiers and messages
181
+ stay English.**` :828, `**Session scope: the user's repo, not tezgah.**` :834,
182
+ ``**Workspace: `.tezgah/` only.**`` :843, `**Parallel by default: fan out
183
+ independent items.**` :850 *(conditional)*,
184
+ `**Kill switches:**` :865. The first label renders as `**Turkish, BLUF.**` under
156
185
  the default `reply_lang`.
157
186
 
158
- `always_on_core()` (`hooks/tezgah_context.py::always_on_core`) drops the five
187
+ `always_on_core()` (`hooks/tezgah_context.py::always_on_core`) drops the six
159
188
  conditional paragraphs and appends `POINTERS` (`hooks/tezgah_policy.py::POINTERS`):
160
189
  one line each saying the rule exists and where its full text lives — spec-first,
161
- a second opinion, OpenResearch routing, product analysis, the code graph, and the
190
+ a second opinion, OpenResearch routing, product analysis, the code graph, parallel
191
+ fan-out, and the
162
192
  per-repo design contract `bin/tezgah-design` checks a UI change against (the one
163
193
  pointer with no conditional paragraph behind it, because the Stop rule's `no
164
194
  ui_ok` class is what asks for the check). That is
@@ -234,13 +264,17 @@ sentence of the rule.
234
264
  | consult | The owner removed the consent rule on 2026-09-26 ([gate.md](gate.md#what-the-gate-deliberately-does-not-catch)). The per-turn reminder carries the clause instead. |
235
265
  | research | Owner decision ADR 010 (2026-10-05): no new research rule until plan 062 reports. |
236
266
  | product | Owner decision ADR 010 (2026-10-05): no new research rule until plan 062 reports. The product rule is the research route. |
267
+ | fanout | A refusal cannot see a serial run that should have been parallel: nothing is called that a gate could deny. The prompt-shape arming (`hooks/tezgah_context.py::PROMPT_HINTS`) and the generated specialists line are the mechanism. |
237
268
  | scope | no decision |
238
269
 
239
270
  ## Arming the conditional paragraphs, and the per-turn reminder
240
271
 
241
272
  `PROMPT_HINTS` (`hooks/tezgah_context.py::PROMPT_HINTS`) is one compiled pattern per key
242
- — `spec`, `consult`, `research`, `product`, `graph` — and `classify_prompt()` returns the keys
243
- a prompt matches (`hooks/tezgah_context.py::classify_prompt`). On that turn only, the
273
+ — `spec`, `consult`, `research`, `product`, `graph`, `fanout` — and `classify_prompt()` returns the keys
274
+ a prompt matches (`hooks/tezgah_context.py::classify_prompt`). `fanout` arms on the
275
+ shape of the request, not a word. Two or more list entries on their own lines
276
+ arm it, and so does an explicit subagent, parallel or worktree ask. The owner
277
+ no longer has to say "do it with subagents, in parallel". On that turn only, the
244
278
  matching paragraphs are appended after the reminder
245
279
  (`hooks/tezgah_context.py::context_for`); a session that never asks such a question
246
280
  pays the one-line pointer instead. The patterns carry Turkish stems because the
@@ -300,7 +334,7 @@ a keyword match, not intent: "don't touch `verify-off`" names the switch too.
300
334
  | `verify-off` | `**Integrity: evidence…**`, and the per-turn evidence-scope line with it | `hooks/tezgah_context.py::switches`, `hooks/tezgah_context.py::context_for` |
301
335
  | `consult-off` | `**Consult before irreversible.**` | `hooks/tezgah_context.py::switches` |
302
336
  | `research-off` | `**Research: route it to OpenResearch.**` | `hooks/tezgah_context.py::switches` |
303
- | `orchestrate-off` | the orchestration section of the on-demand skill (there is no core paragraph) | `hooks/tezgah_context.py::switches` disables it, `hooks/tezgah_context.py::context_for` injects "Orchestration is off", and the skill-ignore note is `hooks/tezgah_context.py::context_for` |
337
+ | `orchestrate-off` | `**Parallel by default: fan out independent items.**`, and the orchestration section of the on-demand skill | `hooks/tezgah_context.py::switches` drops the paragraph, `hooks/tezgah_context.py::context_for` injects "Orchestration is off" and drops the specialists line, and the skill-ignore note is `hooks/tezgah_context.py::context_for` |
304
338
  | `reminder-off` | the per-turn reminder | `hooks/tezgah_context.py::context_for` returns `None` |
305
339
  | `judge-off` | the judgement seam: the snapshot triage, the docs page fallback and the skill hint | `hooks/tezgah_judge.py::available` — `available()` is asked before any call, so an armed switch makes no request at all |
306
340
  | `triage-off` | the snapshot triage alone (`bin/tezgah-triage`), leaving the docs fallback and the skill hint armed | `bin/tezgah-triage::off_reason` — `off_reason()` answers this switch before the seam's, so the analyze-app loop reads the tree instead of paying for a judgement |
@@ -378,11 +412,19 @@ brief carries the same caller-list floor (`hooks/tezgah_agents.py`), and
378
412
 
379
413
  ## When the injected text grows too large
380
414
 
381
- Each event has a byte budget: `session_start` and `post_compact` 12000,
382
- `user_prompt` 6000, `subagent_start` 5500, anything else 12000
415
+ Each event has a byte budget: `session_start` and `post_compact` 13000,
416
+ `user_prompt` 6000, `subagent_start` 5500, anything else 13000
383
417
  (`hooks/tezgah_context.py::CONTEXT_BUDGET`). The rationale and the measured
384
- sizes sit beside it. A fixture repo builds 10066 B at `session_start` and about
385
- 5150 B at `subagent_start`. A `user_prompt` that arms all five conditional rules
418
+ sizes sit beside it. In an empty fixture repo, `session_start` with the core
419
+ builds 9842 B before the ponytail and ADHD rules went inline and 10884 B after
420
+ (+1042 B). The fan-out pointer adds 112 B and the `tezgah-skill` sentence
421
+ 113 B, so the always-on band is 10499 B (`tests/test_setup.py`). The healthy
422
+ fixture with a lesson and a plan then built 12059 B. At the old 12000 B budget
423
+ it lost its lessons and sibling checkouts to pay for core rules, so the budget
424
+ moved to 13000 B. The subagent brief and the reminder stay the same size.
425
+ `SkillRulesInForceAtSessionStart` (`tests/test_context.py`) pins the rules
426
+ themselves, not the size. A
427
+ `user_prompt` that arms all five conditional rules
386
428
  for the first time builds 7503 B. That is over its budget on purpose: an armed paragraph
387
429
  is never dropped, and every later match in the session pays one line. The
388
430
  budget is a byte count, not a token estimate.
@@ -390,7 +432,7 @@ budget is a byte count, not a token estimate.
390
432
  Over budget, `budgeted()` gives up blocks in `DROP_ORDER`, lowest value
391
433
  first — the text another surface already carries (project knowledge, the
392
434
  sibling-checkout line, lessons), the skill hint (a suggestion, never an
393
- instruction), the per-turn relevant lessons and the plan table first, then the tooling-availability lines and the live graph
435
+ instruction), the per-turn relevant lessons and the plan table first, then the Orca line, the tooling-availability lines and the live graph
394
436
  glance, then the resume state (it outlives those because it is the only one that
395
437
  says what this session was doing, and it still yields to a rule), then the
396
438
  evidence-scope warning, the task phase, the delta, and the skill pointer last
@@ -44,6 +44,7 @@ the kill switches are files, not variables, and live in
44
44
  | `TEZGAH_NO_EXCLUDE` | `hooks/tezgah_agents.py::ensure_exclude` | unset | `1` stops tezgah adding its generated-agent dirs to the repository's `.git/info/exclude`. |
45
45
  | `CODEX_HOME` | `hooks/tezgah_paths.py::HOST_DIRS` | `~/.codex` | The Codex home tezgah installs into and checks; the same variable Codex itself reads. |
46
46
  | `DSH_HOME` | `hooks/tezgah_paths.py::HOST_DIRS`, `bin/tezgah-dsh`, `bin/tezgah-dsh.cmd` | `~/.dsh` | The dsh home tezgah installs into, and where `tezgah-dsh` looks for the CLI. |
47
+ | `ORCA_WORKTREE_ID`, `ORCA_TERMINAL_HANDLE`, `ORCA_PANE_KEY`, `TERM_PROGRAM=Orca` | `hooks/tezgah_orca.py::session` | unset | Exported by an Orca terminal. Any one marks the session as inside Orca: the session-start block gains the `Orca:` line and `tezgah-setup --status` an `orca:` line ([orca](orca.md)). tezgah never writes them. |
47
48
  | `TEZGAH_OPENCODE_DATA` | `bin/tezgah-setup::OPENCODE_DATA`, `bin/tezgah-doctor` | `~/.local/share/opencode` | opencode's data dir: its session database for `tezgah-doctor` and the installer. |
48
49
 
49
50
  ## Which binary runs
@@ -52,8 +53,9 @@ the kill switches are files, not variables, and live in
52
53
  |---|---|---|---|
53
54
  | `TEZGAH_CODEGRAPH_BIN` | `hooks/tezgah_paths.py::codegraph_bin` | the config file, then `codegraph` on PATH | The codegraph executable the index and the graph marks use. |
54
55
  | `TEZGAH_ORX_BIN` | `hooks/tezgah_paths.py::orx_bin` | `orx` on PATH | The OpenResearch CLI the research rule routes to. |
55
- | `TEZGAH_OMP_BIN` | `hooks/tezgah_paths.py::omp_bin` | `omp` on PATH | The omp binary the installer, the model table and consult's session-model lookup run. |
56
- | `TEZGAH_CLAUDE_BIN` | `claude_bin`, `hooks/tezgah_paths.py::claude_bin` | `claude` on PATH | The Claude Code CLI `--install` registers the plugin with (`claude plugin`); the test suite points it at a path that does not exist. |
56
+ | `TEZGAH_ORCA_BIN` | `hooks/tezgah_orca.py::cli` | `ORCA_CLI_COMMAND`, then `$ORCA_CLI_BIN_DIR/orca`, then `orca` on PATH (`orca-ide` on Linux outside Orca), then the macOS app's CLI | The Orca CLI `tezgah-status --orca` and `tezgah-setup --status` ask. A missing path is no CLI, never a fallback: the test suite points it at one. |
57
+ | `TEZGAH_OMP_BIN` | `hooks/tezgah_paths.py::omp_bin` | `omp` on PATH | The omp binary the installer, the model table, consult's session-model lookup and the judgement seam's session call run; tests point it at a stand-in. |
58
+ | `TEZGAH_CLAUDE_BIN` | `claude_bin`, `hooks/tezgah_paths.py::claude_bin` | `claude` on PATH | The Claude Code CLI `--install` registers the plugin with (`claude plugin`) and the judgement seam asks in a Claude Code session; the test suite points it at a path that does not exist, or at a stand-in. |
57
59
  | `TEZGAH_DSH_BIN` | `bin/tezgah-setup::reconcile_json_servers` | the profile-local entry, then `bin/tezgah-dsh`, then npx | How the installer invokes the dsh CLI. |
58
60
  | `TEZGAH_INDEX_BIN` | `bin/tezgah-dsh:19`, `bin/tezgah-dsh.cmd` | `~/.config/tezgah/bin/tezgah-index` | The index worker `tezgah-dsh` warms before it starts dsh. |
59
61
  | `TEZGAH_STATUS_BIN` | `hosts/dsh/statusline/lib/index.js:24`, `hosts/opencode/tui/tezgah-tui.tsx` | `~/.config/tezgah/bin/tezgah-status` | The status renderer the dsh status line and the opencode TUI call. |
@@ -71,7 +73,8 @@ the kill switches are files, not variables, and live in
71
73
 
72
74
  | Variable | Read by | Default | Effect |
73
75
  |---|---|---|---|
74
- | `TYPESAFE_API_KEY` | `hooks/tezgah_judge.py::key`, `hooks/tezgah_paths.py` | `~/.config/typesafe/key` | The judgement seam's first credential; omp also reads it for its own judge. |
76
+ | `OMPCODE`, `CLAUDECODE` | `hooks/tezgah_paths.py::session_cli`, `bin/consult`, `hooks/tezgah_models.py::worker_model` | set by the host | Name the host session: the judgement seam asks that host's CLI first (`OMPCODE` wins, omp sets both). The test suite unsets both. |
77
+ | `TYPESAFE_API_KEY` | `hooks/tezgah_judge.py::key`, `hooks/tezgah_paths.py` | `~/.config/typesafe/key` | The judgement seam's first third-party credential, used as `fallback` in `config.json` allows ([judge](judge.md)); omp also reads it for its own judge. |
75
78
  | `OPENROUTER_API_KEY` | `hooks/tezgah_judge.py::openrouter_key`, `bin/consult`, `bin/codegen`, `hooks/tezgah_models.py` | `~/.config/openrouter/key` | The seam's chat fallback, consult's and codegen's `openrouter` provider, and omp's `any` family. |
76
79
  | `DEEPSEEK_API_KEY`, `INCEPTION_API_KEY` | `bin/consult`, `bin/codegen`, `hooks/tezgah_replay.py::label_model` | `~/.config/deepseek/key`, `~/.config/inception/key` | The `deepseek` and `inception` providers of consult and codegen; `DEEPSEEK_API_KEY` alone is also the replay sheet's `--provider deepseek` rater. |
77
80
  | `ANTHROPIC_OAUTH_TOKEN`, `ANTHROPIC_API_KEY`, `ZAI_API_KEY` | `hooks/tezgah_models.py::OMP_AUTH` | omp's own auth store | Read as presence only: whether omp can run the `anthropic` or `zai` model family, so the model table can pick a row omp will run. |
package/docs/evidence.md CHANGED
@@ -313,6 +313,13 @@ The four shape classes share their switches with the text they enforce: `adhd-of
313
313
  has only its words as the trigger. There a claim word inside a question or under a negation in
314
314
  its own clause is no claim (`asserted_claims`). Examples: "testler geçti mi?", "is it done?",
315
315
  "not tested yet", "tamamlandı değil". "Tamamlandı, push edeyim mi?" still claims.
316
+ A prose edit is no step and no change (`hooks/tezgah_integrity.py::_prose_edit`,
317
+ `hooks/tezgah_integrity.py::_work_row`). Such an `edit` row has a real `target` ending in
318
+ `.md`, `.markdown`, `.rst` or `.adoc`. It owes no check and does not stale a pass. An agent
319
+ instruction file is not prose: `SKILL.md`, `AGENTS.md`, `CLAUDE.md` and anything under
320
+ `skills/`, `prompts/`, `agents/`, `commands/` or a host directory stay a change. A reply that
321
+ claims a test ran ("tested", "tüm testler geçti") still needs a pass after a prose-only turn.
322
+ A comment-only edit to code is still a change: the row carries no text to tell it apart.
316
323
  10. **no external read** — the reply states the state of a system tezgah does not own — a registry,
317
324
  a release, a tag, a formula, a CI run — and no read of that system ran in the same turn
318
325
  (`hooks/tezgah_integrity.py::_external_claim`, over `EXTERNAL_SYSTEM`/`EXTERNAL_STATE`
package/docs/gate.md CHANGED
@@ -244,13 +244,19 @@ failures. Under `verify-off` (`hooks/tezgah_gate.py::decision`).
244
244
  Trigger: the same call has been attempted more than `RETRY_CEILING = 3` times in the session, whatever those attempts returned (`hooks/tezgah_gate.py::RETRY_CEILING`, `retry_reason`
245
245
  `hooks/tezgah_gate.py::retry_reason`). Told: an unchanged repeat is not a retry (`hooks/tezgah_gate.py::loop_reason`). Standing. Under `verify-off` (`hooks/tezgah_gate.py::decision`). Both guards read only the ledger tail.
246
246
 
247
- ### Nudge — the first identifier-shaped search of a session
248
-
249
- Trigger: a `Grep` whose pattern, or a `grep`/`rg` argument (`hooks/tezgah_gate.py::BASH_SEARCH`), matches `IDENT` (`^[A-Za-z_][A-Za-z0-9_]{2,}$`, `hooks/tezgah_gate.py::IDENT`), in a repo whose
250
- codegraph index exists (`<repo>/.codegraph/codegraph.db`; `searched_identifier`
251
- `hooks/tezgah_gate.py::searched_identifier`). Told: the runnable command built from the denied search (`codegraph explore <X>`, `codegraph callers <X>`, `codegraph impact <X>`), and that an omp subagent uses this CLI because omp's MCP device refuses concurrent writes
252
- (`hooks/tezgah_gate.py::nudge_reason`).
247
+ ### Nudge — the first code-symbol search of a session, then a line beside every later one
248
+
249
+ Trigger: a search for a code symbol, in a repo whose codegraph index exists (`<repo>/.codegraph/codegraph.db`) and that has no `.no-graph` (`hooks/tezgah_gate.py::index_slug`).
250
+ A code symbol has an inner underscore or a lower-to-upper step, or sits in a `def x` or `x\(` pattern (`hooks/tezgah_gate.py::symbol_in`).
251
+ A plain word such as `TODO` is literal text and never matches.
252
+ The search is the pattern of a `grep`, `search` or `ast_grep` tool, a term of omp's `find`, or a `grep`/`rg` argument (`hooks/tezgah_gate.py::searched_symbol`, `hooks/tezgah_gate.py::BASH_SEARCH`).
253
+ omp's bridge sends `find`, `search` and `ast_grep` to the gate, because omp's own prompt sends code discovery to `find` first (`hosts/omp/tezgah-hook.ts.in`).
254
+ Told: the host's graph tool (`hooks/tezgah_gate.py::graph_tool`) and the runnable command built from the denied search (`codegraph explore <X>`, `codegraph callers <X>`, `codegraph impact <X>`).
255
+ An omp subagent uses this CLI, because omp's MCP device refuses concurrent writes (`hooks/tezgah_gate.py::nudge_reason`).
253
256
  Once-only: the mark in `cache_dir()/nudged/<sha1(session)[:16]>` is written *before* the refusal, so re-issuing the search passes (`hooks/tezgah_gate.py::first_nudge`). opencode's `oncePerSession` writes the same name (`hooks/tezgah_gate.py::nudge_mark`).
257
+ After that, every later symbol search passes with one line naming the graph tool, while the index stamp equals HEAD (`hooks/tezgah_gate.py::graph_advice`).
258
+ Claude reads the line as PreToolUse `additionalContext` (`hooks/projects-pretooluse.py`), and omp reads it with the call's `tool_result` (`hosts/omp/tezgah-hook.ts.in`).
259
+ Codex and Cursor read it with their PostToolUse result (`hosts/codex/hook.py`, `hosts/cursor/hook.py`).
254
260
 
255
261
  ### Drift — a long turn loses the rules it started with
256
262
 
@@ -395,7 +401,7 @@ rule's triggers are the classes `_stop_block` returns, the four `_shape_block` a
395
401
  ## Source of truth
396
402
 
397
403
  - `hooks/tezgah_gate.py` — `hooks/tezgah_gate.py::decision`; every rule constant and refusal text `hooks/tezgah_gate.py:162-2157`; `attribution`/`attribution_edit` `hooks/tezgah_gate.py::attribution`, `hooks/tezgah_gate.py::attribution_edit`; `explored`
398
- `hooks/tezgah_gate.py::explored`; `hooks/tezgah_gate.py::searched_identifier`, `hooks/tezgah_gate.py::index_slug`, `hooks/tezgah_gate.py::first_nudge`, `hooks/tezgah_gate.py::nudge_reason`; `hooks/tezgah_gate.py::LOOP_ATTEMPTS`, `hooks/tezgah_gate.py::loop_reason`,
404
+ `hooks/tezgah_gate.py::explored`; `hooks/tezgah_gate.py::searched_symbol`, `hooks/tezgah_gate.py::index_slug`, `hooks/tezgah_gate.py::first_nudge`, `hooks/tezgah_gate.py::nudge_reason`, `hooks/tezgah_gate.py::graph_advice`; `hooks/tezgah_gate.py::LOOP_ATTEMPTS`, `hooks/tezgah_gate.py::loop_reason`,
399
405
  `hooks/tezgah_gate.py::RETRY_CEILING`, `hooks/tezgah_gate.py::retry_reason`; `hooks/tezgah_gate.py::secret_command`, `hooks/tezgah_gate.py::race_reason`,
400
406
  `hooks/tezgah_gate.py::drift_reason`, `hooks/tezgah_gate.py::effectful`, `hooks/tezgah_gate.py::_deny`; the plan rule's own section `hooks/tezgah_gate.py:1391-1588` — `hooks/tezgah_gate.py::plan_reason`, `hooks/tezgah_gate.py::_branch`, `hooks/tezgah_gate.py::_checkout`, `hooks/tezgah_gate.py::_product`, `hooks/tezgah_gate.py::PLAN_DENY`; `hooks/tezgah_gate.py::write_paths`, `hooks/tezgah_gate.py::shell_target`, `hooks/tezgah_gate.py::SHELL_AS_WRITE`; the `capture` call `hooks/tezgah_gate.py::decision` (a write tool's target, and a shell write's)
401
407
  - `hooks/projects-pretooluse.py` — the Claude and dsh envelope; `hooks/tezgah_paths.py` — `off`, `root_for`, `cache_dir`
package/docs/glossary.md CHANGED
@@ -86,7 +86,7 @@ One measured row of always-on context the installer reports in bytes: the core c
86
86
  `output-styles/tezgah.md`, a by-hand duplicate of `always_on_core()` kept for the host path that is hookless, which a test holds to the generator (`tests/test_context.py::OutputStyleMirrorsCore`). Not generated: an edit to [CORE](#core) must be carried into it by hand - a lesson the local `.tezgah/lessons.md` ledger holds; that file is untracked by design, so it carries no `path:line` here.
87
87
 
88
88
  ### nudge
89
- The one-time hint that sends a first identifier-shaped search to the code graph, `first_nudge()` consuming a per-session mark before the grep is denied (`hooks/tezgah_gate.py::first_nudge`) with the text naming the graph tools (`nudge_reason()`, `hooks/tezgah_gate.py::nudge_reason`). Not a deny: it is spent by being delivered, and the identical call passes on the retry.
89
+ The one-time hint that sends a first code-symbol search to the code graph, `first_nudge()` consuming a per-session mark before the grep is denied (`hooks/tezgah_gate.py::first_nudge`) with the text naming the graph tools (`nudge_reason()`, `hooks/tezgah_gate.py::nudge_reason`). Not a deny: it is spent by being delivered, and the identical call passes on the retry. Every later symbol search gets a non-blocking line naming the graph tool (`hooks/tezgah_gate.py::graph_advice`).
90
90
 
91
91
  ### observable measure
92
92
  A used-mark a surface is able to see at all, so it can say "armed, not used yet" without inventing it: `health_segments(observable=...)` (`hooks/tezgah_context.py::health_segments`) with the five tool-use measures in `TOOL_USE_MEASURES` (`hooks/tezgah_context.py::TOOL_USE_MEASURES`), a measure outside the set rendering as `info` rather than `ready`. Which host can see a skill read is [hosts](hosts.md)'s.