@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.
- package/CHANGELOG.md +71 -1
- package/MANIFEST +8 -0
- package/bin/tezgah-agents +10 -1
- package/bin/tezgah-docs +1 -1
- package/bin/tezgah-doctor +11 -0
- package/bin/tezgah-research +31 -14
- package/bin/tezgah-setup +154 -29
- package/bin/tezgah-skill +59 -0
- package/bin/tezgah-skill.py +59 -0
- package/bin/tezgah-status +62 -1
- package/bin/tezgah-triage +5 -3
- package/docs/README.md +1 -0
- package/docs/architecture.md +5 -2
- package/docs/citations-baseline.json +1 -1
- package/docs/clarity-baseline.json +9 -9
- package/docs/contract.md +68 -26
- package/docs/environment.md +6 -3
- package/docs/evidence.md +7 -0
- package/docs/gate.md +13 -7
- package/docs/glossary.md +1 -1
- package/docs/hosts.md +48 -1
- package/docs/index.json +29 -1
- package/docs/judge.md +61 -55
- package/docs/models.md +14 -8
- package/docs/operations.md +10 -5
- package/docs/orca.md +126 -0
- package/docs/research.md +38 -3
- package/docs/skills.md +74 -0
- package/docs/status-line.md +3 -1
- package/docs/testing.md +33 -21
- package/docs/vision.md +12 -8
- package/hooks/projects-pretooluse.py +17 -5
- package/hooks/tezgah_agents.py +135 -13
- package/hooks/tezgah_components.py +3 -0
- package/hooks/tezgah_context.py +65 -22
- package/hooks/tezgah_gate.py +133 -17
- package/hooks/tezgah_integrity.py +60 -13
- package/hooks/tezgah_judge.py +239 -56
- package/hooks/tezgah_models.py +41 -5
- package/hooks/tezgah_orca.py +144 -0
- package/hooks/tezgah_paths.py +40 -6
- package/hooks/tezgah_policy.py +109 -35
- package/hooks/tezgah_research.py +104 -8
- package/hooks/tezgah_skill_pick.py +459 -8
- package/hooks/tezgah_stack.py +382 -0
- package/hooks/tezgah_stopspec.py +2 -2
- package/hosts/codex/hook.py +7 -1
- package/hosts/cursor/hook.py +7 -4
- package/hosts/omp/hook.py +12 -6
- package/hosts/omp/tezgah-hook.ts.in +20 -7
- package/hosts/opencode/plugins/tezgah.js +18 -9
- package/output-styles/tezgah.md +34 -19
- package/package.json +1 -1
- package/skills/i-have-adhd/SKILL.md +2 -2
- package/skills/research/SKILL.md +60 -17
- package/skills/tezgah-contract/SKILL.md +55 -13
package/bin/tezgah-skill
ADDED
|
@@ -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
|
|
94
|
-
"the
|
|
95
|
-
"~/.config/
|
|
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) |
|
package/docs/architecture.md
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
@@ -18,10 +18,10 @@
|
|
|
18
18
|
"semicolons": 9
|
|
19
19
|
},
|
|
20
20
|
"docs/contract.md": {
|
|
21
|
-
"long_sentences":
|
|
21
|
+
"long_sentences": 43,
|
|
22
22
|
"passive": 26,
|
|
23
23
|
"replace_words": 0,
|
|
24
|
-
"semicolons":
|
|
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":
|
|
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":
|
|
63
|
+
"long_sentences": 23,
|
|
64
64
|
"passive": 15,
|
|
65
65
|
"replace_words": 0,
|
|
66
|
-
"semicolons":
|
|
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":
|
|
94
|
-
"passive":
|
|
93
|
+
"long_sentences": 36,
|
|
94
|
+
"passive": 18,
|
|
95
95
|
"replace_words": 0,
|
|
96
|
-
"semicolons":
|
|
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":
|
|
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
|
|
21
|
-
| conditional paragraph | one of the
|
|
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`)
|
|
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:
|
|
37
|
-
|
|
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.**` :
|
|
146
|
-
ADHD-friendly.**` :
|
|
147
|
-
`**Integrity: evidence, or "doğrulanmadı".**` :
|
|
148
|
-
`**Spec before building.**` :
|
|
149
|
-
repeating mistakes.**` :
|
|
150
|
-
*(conditional)*, `**Consult before irreversible.**` :
|
|
151
|
-
`**Research: route it to OpenResearch.**` :
|
|
152
|
-
analysis: five axes, one evidence class per finding.**` :
|
|
153
|
-
`**No AI attribution, ever, on any host.**` :
|
|
154
|
-
stay English.**` :
|
|
155
|
-
|
|
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
|
|
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,
|
|
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`).
|
|
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
|
|
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`
|
|
382
|
-
`user_prompt` 6000, `subagent_start` 5500, anything else
|
|
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.
|
|
385
|
-
|
|
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
|
package/docs/environment.md
CHANGED
|
@@ -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
|
-
| `
|
|
56
|
-
| `
|
|
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
|
-
| `
|
|
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
|
|
248
|
-
|
|
249
|
-
Trigger: a
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
(`hooks/tezgah_gate.py::
|
|
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::
|
|
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
|
|
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.
|