@r1z4x/tezgah 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/CHANGELOG.md +92 -1
  2. package/MANIFEST +10 -0
  3. package/bin/tezgah-agents +10 -1
  4. package/bin/tezgah-docs +1 -1
  5. package/bin/tezgah-doctor +11 -0
  6. package/bin/tezgah-research +31 -14
  7. package/bin/tezgah-setup +154 -29
  8. package/bin/tezgah-skill +59 -0
  9. package/bin/tezgah-skill.py +59 -0
  10. package/bin/tezgah-status +62 -1
  11. package/bin/tezgah-taste +504 -5
  12. package/bin/tezgah-triage +5 -3
  13. package/docs/README.md +1 -0
  14. package/docs/architecture.md +5 -2
  15. package/docs/citations-baseline.json +1 -1
  16. package/docs/clarity-baseline.json +9 -9
  17. package/docs/contract.md +68 -26
  18. package/docs/environment.md +6 -3
  19. package/docs/evidence.md +7 -0
  20. package/docs/gate.md +13 -7
  21. package/docs/glossary.md +1 -1
  22. package/docs/hosts.md +48 -1
  23. package/docs/index.json +34 -3
  24. package/docs/judge.md +75 -60
  25. package/docs/models.md +14 -8
  26. package/docs/operations.md +80 -25
  27. package/docs/orca.md +126 -0
  28. package/docs/research.md +38 -3
  29. package/docs/skills.md +74 -0
  30. package/docs/status-line.md +3 -1
  31. package/docs/testing.md +33 -21
  32. package/docs/vision.md +12 -8
  33. package/hooks/projects-posttooluse.py +7 -0
  34. package/hooks/projects-pretooluse.py +17 -5
  35. package/hooks/tezgah_agents.py +135 -13
  36. package/hooks/tezgah_components.py +3 -0
  37. package/hooks/tezgah_context.py +84 -22
  38. package/hooks/tezgah_gate.py +133 -17
  39. package/hooks/tezgah_integrity.py +60 -13
  40. package/hooks/tezgah_judge.py +288 -66
  41. package/hooks/tezgah_models.py +41 -5
  42. package/hooks/tezgah_orca.py +144 -0
  43. package/hooks/tezgah_paths.py +40 -6
  44. package/hooks/tezgah_policy.py +109 -35
  45. package/hooks/tezgah_research.py +104 -8
  46. package/hooks/tezgah_skill_pick.py +459 -8
  47. package/hooks/tezgah_stack.py +382 -0
  48. package/hooks/tezgah_stopspec.py +2 -2
  49. package/hooks/tezgah_taste.py +35 -9
  50. package/hooks/tezgah_taste_ledger.py +628 -0
  51. package/hosts/codex/hook.py +12 -1
  52. package/hosts/cursor/hook.py +12 -4
  53. package/hosts/omp/hook.py +18 -6
  54. package/hosts/omp/tezgah-hook.ts.in +20 -7
  55. package/hosts/opencode/plugins/tezgah.js +28 -15
  56. package/output-styles/tezgah.md +34 -19
  57. package/package.json +1 -1
  58. package/skills/i-have-adhd/SKILL.md +2 -2
  59. package/skills/research/SKILL.md +60 -17
  60. package/skills/tezgah-contract/SKILL.md +55 -13
@@ -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.
package/docs/hosts.md CHANGED
@@ -141,6 +141,52 @@ An installed host whose core line is missing is a health check's business
141
141
  (`host_checks_<host>`), not a second copy: two delivery paths for one host is the
142
142
  same rules paid twice per session.
143
143
 
144
+ ## Precedence: what each host loads, and in which order
145
+
146
+ Several host dirs are read by more than one host. The table gives each host's
147
+ documented order. "First wins" means the host keeps the first copy of a name
148
+ and drops the later ones.
149
+
150
+ | concern | host | order | tezgah's part |
151
+ |---|---|---|---|
152
+ | context | claude | `~/.claude/CLAUDE.md`, then the repo's `CLAUDE.md`, `.claude/CLAUDE.md`, `CLAUDE.local.md`; all load | block in the first |
153
+ | context | codex | `$CODEX_HOME/AGENTS.override.md`, else `AGENTS.md`, then the repo's `AGENTS.md` | block in the first |
154
+ | context | cursor | the `sessionStart` hook context, the repo's `AGENTS.md`, `.cursor/rules/*.mdc`, `.cursorrules` | the hook |
155
+ | context | opencode | `~/.config/opencode/AGENTS.md`, `opencode.json` `instructions`, the repo's `AGENTS.md` | the contract and router files in `instructions` |
156
+ | context | omp | `SYSTEM.md`, `APPEND_SYSTEM.md`, `RULES.md` (sticky), the repo's `.omp/AGENTS.md`, `AGENTS.md`, `CLAUDE.md`; another host's user files only with `enabledProviders` | block in `RULES.md`, the extension at session start |
157
+ | skills | omp | `skills.customDirectories`, then `.omp/skills` and `~/.omp/agent/skills`, Claude's project dir (user dir opt-in), `.agents/skills` and `~/.agents/skills`; first wins, identical copies collapse, a differing copy is renamed `<namespace>/<name>` | links in `~/.omp/agent/skills` |
158
+ | skills | claude | `.claude/skills`, `~/.claude/skills`; plugin skills are namespaced `tezgah:<name>` | the plugin copy |
159
+ | skills | codex | `.agents/skills` from the cwd up to the repo root, `~/.agents/skills`, `$CODEX_HOME/skills`; one name in two dirs is listed twice, not merged | links in `$CODEX_HOME/skills` |
160
+ | skills | cursor | `.agents/skills`, `.cursor/skills`, `~/.agents/skills`, `~/.cursor/skills`, then Claude's and Codex's project and user dirs; no documented winner | links in `~/.cursor/skills` |
161
+ | skills | opencode | its own skill tool is denied; the router lists `~/.config/opencode/skills`, `~/.claude/skills`, `~/.agents/skills`, first wins (`bin/tezgah-setup::skill_groups`) | links and the router |
162
+ | hooks | claude | user `settings.json` rows (Orca's), project rows, the plugin's `hooks/hooks.json` (tezgah's); every matching row runs, none is ordered before another | plugin rows |
163
+ | hooks | codex, cursor | `hooks.json` array order, then the repo's `.codex/` or `.cursor/hooks.json`; Codex runs a row only once its hash is trusted | own rows after Orca's |
164
+ | hooks | opencode | every `*.js`/`*.ts` in `plugins/` and `plugin/`; `tezgah.js` is linked into both, and its `globalThis.__tezgahPluginLoaded` guard makes the second load return `{}` | the plugin |
165
+ | hooks | omp | `~/.omp/agent/extensions/` first, then `config.yml` `extensions:`, one load per absolute path; per event in that order, and the first `block` ends a tool call | the bridge in `hooks/pre/` |
166
+ | mcp | each | omp: project `.omp/mcp.json`, then `~/.omp/agent/mcp.json`, first name wins; claude: `~/.claude.json`, the repo's `.mcp.json`, the plugin's `.mcp.json`; codex: `$CODEX_HOME/config.toml`; cursor: `~/.cursor/mcp.json`; opencode: `opencode.json` `mcp` | one `tezgah` and one `codegraph` row per host |
167
+
168
+ Orca is a launcher, not a host. It starts omp with its own `--extension`. It
169
+ gives each Codex account its own `CODEX_HOME` under
170
+ `~/Library/Application Support/orca/codex-accounts/<id>/home`. It adds its
171
+ status rows to Claude's `settings.json`, to Cursor's `hooks.json`, to that
172
+ Codex home's `hooks.json` and to opencode's `plugins/`. Its rows sit beside
173
+ tezgah's. The installer arms the `CODEX_HOME` it runs under
174
+ (`hooks/tezgah_paths.py::HOST_DIRS`). An install from a plain terminal and one
175
+ from an Orca pane therefore arm two different Codex homes.
176
+
177
+ `tezgah-doctor --stack [--repo PATH] [--json]` prints this stack as it stands
178
+ on the machine (`hooks/tezgah_stack.py::host_stack`). It exits 1 on a conflict
179
+ (`hooks/tezgah_stack.py::conflicts`). The kinds are `skill-differs`, `skill-dup`,
180
+ `skill-stale`, `rule-twice`, `hook-twice`, `mcp-twice`, `backup-in-scan` and
181
+ `orphan-copy`. `skill-differs` names the copy that wins. `skill-stale` is a
182
+ tezgah copy that is not this tree's bytes. `orphan-copy` is a Claude plugin
183
+ copy that `installed_plugins.json` no longer names.
184
+
185
+ A backup tezgah writes never lands in a host dir. It goes under
186
+ `~/.config/tezgah/backups/` (`bin/tezgah-setup::backup_base`), because omp
187
+ lists every file in `hooks/pre/` as a hook. `--install` moves the backups older
188
+ releases left in host dirs (`bin/tezgah-setup::sweep_backups`).
189
+
144
190
  ## The model wire
145
191
 
146
192
  The matrix above is about events. This is about the other side of a host: which
@@ -196,7 +242,7 @@ In order, each step verified by the one below it:
196
242
  (`hosts/omp/tezgah-hook.ts.in` rendered at `hooks/tezgah_attest.py::omp_bridge`).
197
243
  3. `install_<host>()` writing that host's files, registered in `INSTALLERS`
198
244
  (`bin/tezgah-setup::INSTALLERS`), and `uninstall_<host>()` removing only
199
- tezgah-managed links and blocks (`bin/tezgah-setup:3286-3418`).
245
+ tezgah-managed links and blocks (`bin/tezgah-setup:3287-3419`).
200
246
  4. `host_checks_<host>()` returning `(label, bool)` rows over what was actually
201
247
  written, registered in `HOST_CHECKS` (`bin/tezgah-setup::HOST_CHECKS`); the
202
248
  `--report` output is that list (`bin/tezgah-setup::report`). Give the row a home-qualified
@@ -398,3 +444,4 @@ In order, each step verified by the one below it:
398
444
  - `hosts/opencode/plugins/tezgah.js`, `hosts/opencode/tui/tezgah-tui.tsx`
399
445
  - `statusline.py`, `bin/tezgah-status`, `hooks/tezgah_context.py`
400
446
  - `hooks/tezgah_models.py` - the per-host model surface in `## The model wire`
447
+ - `hooks/tezgah_stack.py`, `bin/tezgah-doctor` `--stack` - the order in `## Precedence`
package/docs/index.json CHANGED
@@ -368,7 +368,8 @@
368
368
  "how do I install or upgrade a released version without a git checkout",
369
369
  "where does an installed version unpack and how do I roll back",
370
370
  "something is broken what do I check first",
371
- "how do I turn on taste signal capture and measure my corrections"
371
+ "how do I turn on taste signal capture and measure my corrections",
372
+ "how does taste learn my coding preferences and inject them"
372
373
  ],
373
374
  "answers_tr": [
374
375
  "bunu bir makineye nasıl kurar, doğrular ve yükseltirim",
@@ -376,7 +377,8 @@
376
377
  "git deposu olmadan yayımlanmış bir sürümü nasıl kurar veya yükseltirim",
377
378
  "kurulu bir sürüm nereye açılır ve nasıl geri alırım",
378
379
  "bir şey bozuk, önce neyi kontrol etmeliyim",
379
- "zevk sinyali kaydını nasıl açar ve düzeltmelerimi nasıl ölçerim"
380
+ "zevk sinyali kaydını nasıl açar ve düzeltmelerimi nasıl ölçerim",
381
+ "taste kodlama tercihlerimi nasıl öğrenir ve oturuma nasıl ekler"
380
382
  ],
381
383
  "sources": [
382
384
  "bin/tezgah-setup",
@@ -390,6 +392,7 @@
390
392
  "README.md",
391
393
  "hooks/tezgah_embed.py",
392
394
  "hooks/tezgah_taste.py",
395
+ "hooks/tezgah_taste_ledger.py",
393
396
  "bin/tezgah-taste"
394
397
  ]
395
398
  },
@@ -466,6 +469,34 @@
466
469
  "docs/testing.md",
467
470
  "AGENTS.md"
468
471
  ]
472
+ },
473
+ {
474
+ "path": "docs/orca.md",
475
+ "title": "Orca: what it owns, what tezgah owns, where they meet",
476
+ "title_tr": "Orca: neyin sahibi, tezgah neyin sahibi, nerede kesişirler",
477
+ "audience": "maintainer, agent working inside Orca",
478
+ "answers": [
479
+ "how does a session know it runs inside Orca",
480
+ "should a slice use orca worktree create or git worktree add",
481
+ "which checkouts of this repo does Orca track",
482
+ "does tezgah overwrite Orca's codex home or hooks"
483
+ ],
484
+ "answers_tr": [
485
+ "bir oturum Orca içinde çalıştığını nasıl anlar",
486
+ "bir dilim orca worktree create mi yoksa git worktree add mi kullanmalı",
487
+ "Orca bu deponun hangi çalışma kopyalarını izliyor",
488
+ "tezgah Orca'nın codex dizinini veya hook'larını ezer mi"
489
+ ],
490
+ "sources": [
491
+ "hooks/tezgah_orca.py",
492
+ "hooks/tezgah_context.py",
493
+ "hooks/tezgah_policy.py",
494
+ "hooks/tezgah_agents.py",
495
+ "bin/tezgah-status",
496
+ "bin/tezgah-setup",
497
+ "skills/tezgah-contract/SKILL.md",
498
+ "statusline.py"
499
+ ]
469
500
  }
470
501
  ]
471
- }
502
+ }