@r1z4x/tezgah 1.11.0 → 1.14.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 (109) hide show
  1. package/CHANGELOG.md +507 -1
  2. package/MANIFEST +40 -0
  3. package/NOTICE +152 -0
  4. package/README.de.md +41 -20
  5. package/README.es.md +39 -19
  6. package/README.fr.md +41 -20
  7. package/README.md +18 -10
  8. package/README.tr.md +39 -21
  9. package/bin/codegen +9 -2
  10. package/bin/consult +6 -0
  11. package/bin/tezgah-adhd +3 -0
  12. package/bin/tezgah-agents +3 -0
  13. package/bin/tezgah-capture +3 -0
  14. package/bin/tezgah-design +329 -54
  15. package/bin/tezgah-docs +158 -42
  16. package/bin/tezgah-dsh +1 -1
  17. package/bin/tezgah-gate +7 -2
  18. package/bin/tezgah-import-ai-research +16 -4
  19. package/bin/tezgah-index +3 -0
  20. package/bin/tezgah-lessons +13 -4
  21. package/bin/tezgah-mcp +4 -0
  22. package/bin/tezgah-migrate +3 -0
  23. package/bin/tezgah-ocr-review +2 -1
  24. package/bin/tezgah-optimize +4 -1
  25. package/bin/tezgah-pony +3 -0
  26. package/bin/tezgah-research +51 -10
  27. package/bin/tezgah-rollback +3 -0
  28. package/bin/tezgah-route +1 -1
  29. package/bin/tezgah-setup +1741 -95
  30. package/bin/tezgah-status +11 -1
  31. package/bin/tezgah-task +58 -17
  32. package/bin/tezgah.js +10 -3
  33. package/commands/adhd.md +1 -1
  34. package/docs/README.md +19 -8
  35. package/docs/architecture.md +2 -2
  36. package/docs/citations-baseline.json +11 -17
  37. package/docs/clarity-baseline.json +12 -6
  38. package/docs/contract.md +83 -34
  39. package/docs/environment.md +59 -24
  40. package/docs/evidence.md +128 -25
  41. package/docs/gate.md +77 -23
  42. package/docs/glossary.md +1 -1
  43. package/docs/hosts.md +343 -48
  44. package/docs/layers.md +2 -2
  45. package/docs/models.md +6 -0
  46. package/docs/operations.md +83 -29
  47. package/docs/research.md +37 -11
  48. package/docs/skills.md +31 -8
  49. package/docs/status-line.md +39 -23
  50. package/docs/testing.md +50 -17
  51. package/docs/vision.md +6 -2
  52. package/hooks/hooks.json +1 -1
  53. package/hooks/lang/en.json +16 -2
  54. package/hooks/lang/tr.json +14 -2
  55. package/hooks/projects-posttooluse.py +11 -3
  56. package/hooks/projects-stop.py +3 -2
  57. package/hooks/tezgah_agents.py +27 -3
  58. package/hooks/tezgah_attest.py +79 -10
  59. package/hooks/tezgah_context.py +61 -31
  60. package/hooks/tezgah_gate.py +480 -126
  61. package/hooks/tezgah_integrity.py +577 -133
  62. package/hooks/tezgah_lessons.py +5 -2
  63. package/hooks/tezgah_paths.py +60 -8
  64. package/hooks/tezgah_policy.py +48 -18
  65. package/hooks/tezgah_replay.py +8 -2
  66. package/hooks/tezgah_research.py +167 -24
  67. package/hooks/tezgah_skill_pick.py +1 -1
  68. package/hooks/tezgah_snapshot.py +6 -3
  69. package/hooks/tezgah_stack.py +236 -1
  70. package/hooks/tezgah_stopspec.py +2 -1
  71. package/hooks/tezgah_task.py +2 -1
  72. package/hosts/amp/hook.py +217 -0
  73. package/hosts/amp/tezgah-plugin.ts.in +396 -0
  74. package/hosts/antigravity/hook.py +255 -0
  75. package/hosts/codex/hook.py +3 -2
  76. package/hosts/commandcode/hook.py +239 -0
  77. package/hosts/copilot/hook.py +284 -0
  78. package/hosts/cursor/hook.py +4 -2
  79. package/hosts/droid/hook.py +259 -0
  80. package/hosts/dsh/hooks.json +1 -1
  81. package/hosts/gemini/hook.py +213 -0
  82. package/hosts/goose/hook.py +145 -0
  83. package/hosts/omp/hook.py +5 -2
  84. package/hosts/omp/tezgah-hook.ts.in +157 -42
  85. package/hosts/opencode/plugins/tezgah.js +209 -63
  86. package/hosts/qwen/hook.py +206 -0
  87. package/output-styles/tezgah.md +11 -6
  88. package/package.json +6 -3
  89. package/packaging/build.sh +4 -4
  90. package/packaging/install.sh +11 -0
  91. package/packaging/upgrade.sh +3 -0
  92. package/skills/ai-research/LICENSE +21 -0
  93. package/skills/design-contract/SKILL.md +27 -6
  94. package/skills/design-library/LICENSE +21 -0
  95. package/skills/harness/SKILL.md +2 -0
  96. package/skills/i-have-adhd/SKILL.md +10 -1
  97. package/skills/plan-add/SKILL.md +5 -1
  98. package/skills/plan-add/render_table.py +7 -0
  99. package/skills/pm-frameworks/LICENSE +21 -0
  100. package/skills/research/SKILL.md +22 -9
  101. package/skills/rl-env/LICENSE +201 -0
  102. package/skills/tezgah-contract/SKILL.md +21 -8
  103. package/statusline.py +20 -9
  104. package/third_party/open-code-review/LICENSE +201 -0
  105. package/third_party/open-code-review/README.md +15 -0
  106. package/third_party/open-code-review/prompts/main_task_system.md +26 -0
  107. package/third_party/open-code-review/prompts/review_filter_task_system.md +13 -0
  108. package/workflows/graph-map.js +14 -5
  109. package/workflows/graph-review.js +14 -2
@@ -11,8 +11,9 @@ One store, not two: the install record is the per-artifact sha file the contract
11
11
  artifacts already use (`contract.sha256`, `bin/tezgah-setup::record_contract`),
12
12
  one line per entry under a `hook:<host>:<entry>` key. An entry is one hook event's
13
13
  tezgah-owned groups (Claude, dsh, Codex, Cursor - so a removed PreToolUse is named
14
- while PostToolUse stays), opencode's plugin links, and nothing for omp: its bridge
15
- is compared to a fresh render, the way `rules_current` compares a rules file. The
14
+ while PostToolUse stays), opencode's plugin links, and nothing for omp and Amp:
15
+ their bridge or plugin is compared to a fresh render, the way `rules_current`
16
+ compares a rules file. The
16
17
  code an entry runs is hashed only on a packaged install (a tree with no `.git`):
17
18
  a checkout install points into a tree its owner edits on purpose. A hook file
18
19
  any user can write (0666) is drift too; group write is left alone, because a
@@ -43,10 +44,22 @@ CODE_TARGETS = {
43
44
  "cursor": (os.path.join("hosts", "cursor", "hook.py"),),
44
45
  "opencode": (os.path.join("hosts", "opencode", "plugins", "tezgah.js"),),
45
46
  "omp": (os.path.join("hosts", "omp", "hook.py"),),
47
+ "qwen": (os.path.join("hosts", "qwen", "hook.py"),),
48
+ "droid": (os.path.join("hosts", "droid", "hook.py"),),
49
+ "commandcode": (os.path.join("hosts", "commandcode", "hook.py"),),
50
+ "copilot": (os.path.join("hosts", "copilot", "hook.py"),),
51
+ "gemini": (os.path.join("hosts", "gemini", "hook.py"),),
52
+ "antigravity": (os.path.join("hosts", "antigravity", "hook.py"),),
53
+ "goose": (os.path.join("hosts", "goose", "hook.py"),),
54
+ "amp": (os.path.join("hosts", "amp", "hook.py"),
55
+ os.path.join("hosts", "amp", "tezgah-plugin.ts.in")),
46
56
  }
47
57
  OPENCODE_DIRS = ("plugins", "plugin")
48
58
  OMP_BRIDGE = os.path.join(tp.HOST_DIRS["omp"], "agent", "hooks", "pre",
49
59
  "tezgah-hook.ts")
60
+ # The first line of every Amp plugin tezgah renders: an uninstall removes a
61
+ # file at AMP_PLUGIN only when it equals a fresh render or starts with this.
62
+ AMP_HEADER = "// tezgah: Amp plugin rendered by tezgah-setup"
50
63
 
51
64
 
52
65
  def sha(text):
@@ -82,10 +95,15 @@ def own_groups(data, own=False):
82
95
  return out
83
96
 
84
97
 
85
- def _manifest(path, own):
98
+ def _manifest(path, own, bare=False, key=None):
99
+ """`bare`: the file is keyed by event at the top, as Droid's hooks.json is;
100
+ `key`: the events sit under that top-level name, as Antigravity's do."""
86
101
  try:
87
102
  with open(path, encoding="utf-8") as fh:
88
- return own_groups(json.load(fh), own)
103
+ data = json.load(fh)
104
+ if key:
105
+ data, bare = (data.get(key) if isinstance(data, dict) else None) or {}, True
106
+ return own_groups({"hooks": data} if bare else data, own)
89
107
  except (OSError, ValueError):
90
108
  return None
91
109
 
@@ -97,11 +115,31 @@ def omp_bridge(root=ROOT):
97
115
  with open(os.path.join(root, "hosts", "omp", "tezgah-hook.ts.in"),
98
116
  encoding="utf-8") as fh:
99
117
  return fh.read().replace(
100
- "@HOOK@", os.path.join(root, "hosts", "omp", "hook.py"))
118
+ "@HOOK@", js_string(os.path.join(root, "hosts", "omp", "hook.py")))
101
119
  except OSError:
102
120
  return ""
103
121
 
104
122
 
123
+ def amp_plugin(root=ROOT):
124
+ """The Amp plugin as an install writes it: the template with this tree's
125
+ hook path, or "" when the template is unreadable. The install, the report
126
+ row, the live probe and the attestation all compare against this."""
127
+ try:
128
+ with open(os.path.join(root, "hosts", "amp", "tezgah-plugin.ts.in"),
129
+ encoding="utf-8") as fh:
130
+ return fh.read().replace(
131
+ "@HOOK@", js_string(os.path.join(root, "hosts", "amp", "hook.py")))
132
+ except OSError:
133
+ return ""
134
+
135
+
136
+ def js_string(path):
137
+ """`path` as the inside of a JS double-quoted string: a Windows checkout's
138
+ `\\t`, `\\u` or `\\x` would otherwise become a control character or a
139
+ SyntaxError, and the bridge would not load (review of plan 140)."""
140
+ return json.dumps(path)[1:-1]
141
+
142
+
105
143
  def registration(host, root=ROOT):
106
144
  """({entry: text}, [files]) for the tezgah-owned hook entries this host has
107
145
  on disk; the dict is None when the host's file cannot be read."""
@@ -109,9 +147,30 @@ def registration(host, root=ROOT):
109
147
  path = os.path.join(root, *(("hooks",) if host == "claude"
110
148
  else ("hosts", "dsh")), "hooks.json")
111
149
  return _manifest(path, own=True), [path]
112
- if host in ("codex", "cursor"):
113
- path = os.path.join(tp.HOST_DIRS[host], "hooks.json")
114
- return _manifest(path, own=False), [path]
150
+ if host in ("codex", "cursor", "qwen", "droid", "commandcode"):
151
+ # Qwen's and Command Code's hooks live in a shared settings.json
152
+ path = os.path.join(tp.HOST_DIRS[host], "settings.json"
153
+ if host in ("qwen", "commandcode") else "hooks.json")
154
+ return _manifest(path, own=False, bare=host == "droid"), [path]
155
+ if host == "copilot":
156
+ # tezgah's own file in Copilot's user hooks dir: every entry is ours
157
+ path = os.path.join(tp.HOST_DIRS["copilot"], "hooks", "tezgah.json")
158
+ return _manifest(path, own=True), [path]
159
+ if host == "gemini":
160
+ # tezgah's own extension: every entry in its hooks.json is ours
161
+ path = os.path.join(tp.HOST_DIRS["gemini"], "extensions", "tezgah", "hooks",
162
+ "hooks.json")
163
+ return _manifest(path, own=True), [path]
164
+ if host == "antigravity":
165
+ # the shared ~/.gemini/config/hooks.json: every entry under its
166
+ # top-level `tezgah` name is ours
167
+ path = os.path.join(os.path.dirname(tp.HOST_DIRS["antigravity"]), "config",
168
+ "hooks.json")
169
+ return _manifest(path, own=True, key="tezgah"), [path]
170
+ if host == "goose":
171
+ # tezgah's own plugin: every entry in its hooks.json is ours
172
+ path = os.path.join(tp.GOOSE_PLUGIN, "hooks", "hooks.json")
173
+ return _manifest(path, own=True), [path]
115
174
  if host == "opencode":
116
175
  links = (os.path.join(tp.HOST_DIRS["opencode"], d, "tezgah.js")
117
176
  for d in OPENCODE_DIRS)
@@ -124,6 +183,12 @@ def registration(host, root=ROOT):
124
183
  return {"bridge": fh.read()}, [OMP_BRIDGE]
125
184
  except OSError:
126
185
  return None, [OMP_BRIDGE]
186
+ if host == "amp":
187
+ try:
188
+ with open(tp.AMP_PLUGIN, encoding="utf-8") as fh:
189
+ return {"plugin": fh.read()}, [tp.AMP_PLUGIN]
190
+ except OSError:
191
+ return None, [tp.AMP_PLUGIN]
127
192
  return None, []
128
193
 
129
194
 
@@ -148,7 +213,7 @@ def record_texts(host, root=ROOT):
148
213
  """{key: text} an install records for one host (`bin/tezgah-setup::record_hooks`)."""
149
214
  entries, _files = registration(host, root)
150
215
  out = {}
151
- if host != "omp":
216
+ if host not in ("omp", "amp"):
152
217
  out.update((KEY % (host, name), text)
153
218
  for name, text in (entries or {}).items())
154
219
  code = code_text(host, root) if packaged(root) else None
@@ -173,10 +238,14 @@ def drift(host, root=ROOT, shas=None):
173
238
  if mode & 0o002:
174
239
  found.append("%s mode %04o" % (path, mode))
175
240
  if entries is None:
176
- found.append("%s unreadable or missing" % files[0])
241
+ # a host `registration` does not know names no file: say so, never IndexError
242
+ found.append("%s unreadable or missing" % (files[0] if files else host))
177
243
  elif host == "omp":
178
244
  if entries["bridge"] != omp_bridge(root):
179
245
  found.append("omp bridge %s differs from a fresh render" % OMP_BRIDGE)
246
+ elif host == "amp":
247
+ if entries["plugin"] != amp_plugin(root):
248
+ found.append("amp plugin %s differs from a fresh render" % tp.AMP_PLUGIN)
180
249
  else:
181
250
  names = set(recorded) - {"code"}
182
251
  if not names:
@@ -335,9 +335,10 @@ def skill_read_kind(tool, inp):
335
335
 
336
336
  Only the hosts that can see a read without paying a process per read call
337
337
  this (Claude parses its transcript, opencode classifies in-process, omp's
338
- embedded runner filters before it asks python). On codex, cursor and dsh a
339
- read is not observable at that price, so those marks stay at their armed
340
- state and the legend says so.
338
+ embedded runner filters before it asks python). On codex, commandcode,
339
+ gemini, cursor, qwen, droid, copilot, antigravity and dsh a read is not
340
+ observable at that price, so those marks stay at their armed state and the
341
+ legend says so.
341
342
 
342
343
  A read of one of the two marked skills earns its mark (`SKILL_MARKS`); a read
343
344
  of any OTHER shipped skill earns `skill:<name>`. That second kind lights
@@ -1354,11 +1355,17 @@ def session_of(payload):
1354
1355
  # again on 2026-10-22 (plan 126, second pass): a research ask arms the Research
1355
1356
  # and Product paragraphs (6239 B on Claude, S4Remeasure p08) and the skill hint
1356
1357
  # that names the research skill (about 330 B with its path) was the block given
1357
- # up, so the prompt budget moves to 7000 B. A
1358
+ # up, so the prompt budget moves to 7000 B. Moved again on 2026-10-28 (plan
1359
+ # 136): the subagent pointer says an owner-only question goes back as
1360
+ # `ESCALATE: owner` (+330 B), the fixture's subagent_start reached 5958 B and
1361
+ # gave up `consult` (205 B), so subagent_start moves to 6100 B. Moved again on
1362
+ # 2026-10-29 (plan 142): the output-shape paragraph says what the host's plan
1363
+ # mode changes (+168 B), and the worktree fixture's session_start gave up its
1364
+ # worktrees line again, so the session budgets move to 16200 B. A
1358
1365
  # budget in bytes, not tokens: this file has no tokenizer and a wrong estimate
1359
1366
  # would be worse than a bound.
1360
- CONTEXT_BUDGET = {"session_start": 16000, "post_compact": 16000,
1361
- "subagent_start": 5800, "user_prompt": 7000}
1367
+ CONTEXT_BUDGET = {"session_start": 16200, "post_compact": 16200,
1368
+ "subagent_start": 6100, "user_prompt": 7000}
1362
1369
  DEFAULT_BUDGET = 13000
1363
1370
  # The blocks in the order they are given up when the budget is exceeded, lowest
1364
1371
  # value first: text another surface already carries (the plan table lives in the
@@ -1520,12 +1527,13 @@ GATE_MIN_CALLS = 3
1520
1527
  # The tool names whose every call leaves a PreToolUse row (`began`,
1521
1528
  # tezgah_gate.decision's write and shell branch) and every refused one a `deny`:
1522
1529
  # Claude's write and shell tools, omp's `bash`/`edit`/`write` and Codex's
1523
- # `exec_command`/`apply_patch`. A
1530
+ # `exec_command`/`apply_patch`, Droid's `Execute`/`Create`/`ApplyPatch`. A
1524
1531
  # tool the gate may pass without a row (Read, Grep, Task, WebFetch, an MCP
1525
1532
  # call) is not counted, so its absence from the ledger is never read as a
1526
1533
  # disarmed gate.
1527
1534
  GATED_TOOLS = re.compile(r"(?i)^(?:bash|powershell|pwsh|edit|write|multiedit|"
1528
- r"notebookedit|exec_command|shell|apply_patch)$")
1535
+ r"notebookedit|exec_command|shell|apply_patch|"
1536
+ r"execute|create|applypatch)$")
1529
1537
  GATE_INACTIVE = (
1530
1538
  "tezgah gate inactive on this host: %d gated tool call(s) since this "
1531
1539
  "session's first turn and not one row from the gate, so the shortcut "
@@ -1651,7 +1659,8 @@ def context_nudge(payload, session_id):
1651
1659
  # The ledger kinds a hook other than the tool hooks writes: the prompt hook's
1652
1660
  # turn marker, judge row, lesson rows and disarm row, the Stop hook's claim,
1653
1661
  # refusal, after_block and shape rows and the stop_spec rows of plan 063's shadow
1654
- # (old ledgers only), the subagent-end hook's subagent_end row,
1662
+ # (old ledgers only), the subagent-end hook's subagent_end row, Droid's Stop
1663
+ # `reply_unread` row (a turn end whose reply the transcript did not yield),
1655
1664
  # compaction, the subagent mark and the `subagent_start` lineage row (both from
1656
1665
  # SubagentStart), the omp fork's `forked` row, the guard's crash
1657
1666
  # row (any hook), the damage row any reader writes and the session start's
@@ -1664,7 +1673,7 @@ NOT_TOOL_HOOK = frozenset((b"turn", b"judge", b"claim", b"refusal",
1664
1673
  b"after_block", b"subagent_end", b"stop_spec",
1665
1674
  b"shape", b"compact", b"orch", b"crash", b"route",
1666
1675
  b"spawned", b"forked", b"lesson", b"disarm", b"ledger_damage",
1667
- b"attest", b"subagent_start"))
1676
+ b"attest", b"subagent_start", b"reply_unread"))
1668
1677
  # The rows PostToolUse writes (`note_tool`): they prove the host ran the call,
1669
1678
  # not that the gate saw it - a PostToolUse hook keeps writing them while a
1670
1679
  # broken PreToolUse hook lets every call through - so they are no proof either.
@@ -2087,7 +2096,8 @@ def context_for(event, cwd, payload=None, with_core=True):
2087
2096
  # The switch latch: every off() below answers for this session, and a
2088
2097
  # prompt's turn marker (with its `authorized` row) is written first, so a
2089
2098
  # switch the user's prompt names is honored from this very turn.
2090
- bind_session(session_of(payload))
2099
+ bind_session(session_of(payload),
2100
+ payload.get("parent") if isinstance(payload, dict) else None)
2091
2101
  if event == "user_prompt":
2092
2102
  # One marker per user turn, written before the reminder check: the loop
2093
2103
  # guard counts an identical call's failures in the current turn only, so
@@ -2095,13 +2105,16 @@ def context_for(event, cwd, payload=None, with_core=True):
2095
2105
  # guard state rather than part of the reminder. Only a hash of the prompt
2096
2106
  # is stored - note_turn keys the row on it so one submission cannot write
2097
2107
  # two markers and hide the failures the guard had just counted.
2098
- note_turn(session_of(payload), prompt_text(payload), workspace=root_for(cwd))
2108
+ note_turn(session_of(payload), prompt_text(payload), workspace=root_for(cwd),
2109
+ subagent=isinstance(payload, dict) and bool(payload.get("subagent")))
2099
2110
  core, disabled = core_for(cwd)
2100
2111
  # A disabled rule is also removed from the on-demand skill's reach, because
2101
2112
  # the skill is loaded separately and would otherwise re-enable it.
2113
+ # Claude's `tezgah` output style is a static copy of the core that no switch
2114
+ # re-renders, so the note names it beside the skill (OQ-12).
2102
2115
  off_note = ("Kill switches active this session: %s. Those rules are OFF; "
2103
- "ignore the matching section in the `tezgah-contract` skill."
2104
- % ", ".join(disabled)) if disabled else ""
2116
+ "ignore the matching section in the `tezgah-contract` skill and "
2117
+ "the `tezgah` output style." % ", ".join(disabled)) if disabled else ""
2105
2118
  if event == "user_prompt":
2106
2119
  prompt = prompt_text(payload)
2107
2120
  session_id = session_of(payload)
@@ -2110,24 +2123,24 @@ def context_for(event, cwd, payload=None, with_core=True):
2110
2123
  host = (payload or {}).get("host") if isinstance(payload, dict) else None
2111
2124
  tezgah_taste.note_prompt(session_id, prompt, cwd,
2112
2125
  host=host if isinstance(host, str) else None)
2113
- # Before the reminder switch: the status mark follows the gate's state
2114
- # even when the per-turn text is off. Never in DROP_ORDER, so no budget
2115
- # gives it up - it says the rules below are not being enforced.
2126
+ # The gate notice: never in DROP_ORDER, so no budget gives it up, and
2127
+ # `reminder-off` keeps it - it says the rules below are not being
2128
+ # enforced. Reading it also writes the status mark.
2116
2129
  gate = gate_inactive(session_id, payload)
2117
- # The user's grants and lifts (`user_grants`), before the reminder
2118
- # switch too: the Stop rule reads them whether or not this turn's text
2119
- # is shown. A subagent's prompt is its parent's brief, never the
2120
- # user's grant (omp's bridge sends `subagent`).
2130
+ # The user's grants and lifts (`user_grants`): the Stop rule reads them
2131
+ # whatever this turn's text shows. A subagent's prompt is its parent's
2132
+ # brief, never the user's grant (omp's bridge sends `subagent`).
2121
2133
  for clause, target in ([] if isinstance(payload, dict) and payload.get("subagent")
2122
2134
  else user_grants(prompt)):
2123
2135
  note(session_id, GRANT_KIND, clause, target=target)
2124
2136
  # per-turn nudge: openers decay over long sessions. Kept short because
2125
2137
  # it is paid every turn, and on Claude the output style already carries
2126
2138
  # the same rules on every response. The conditional rules ride along
2127
- # only on the turn whose prompt matches their task class.
2128
- if off("reminder-off"):
2129
- return None
2130
- parts = [("reminder", render(prompt_reminder(dropped_switches(cwd))))]
2139
+ # only on the turn whose prompt matches their task class. `reminder-off`
2140
+ # mutes this one line: every other part has its own switch or none
2141
+ # (OQ-10), and the gate notice must never be given up.
2142
+ parts = ([] if off("reminder-off") else
2143
+ [("reminder", render(prompt_reminder(dropped_switches(cwd))))])
2131
2144
  if gate:
2132
2145
  parts.append(("gate", gate))
2133
2146
  # The previous turn's stamp, read before the armed paragraphs: they are
@@ -2250,8 +2263,8 @@ def context_for(event, cwd, payload=None, with_core=True):
2250
2263
  if stale:
2251
2264
  parts.append(("index", stale))
2252
2265
  if disabled:
2253
- parts.append(("offnote", "(off this session: %s)"
2254
- % ", ".join(disabled)))
2266
+ parts.append(("offnote", "(off this session: %s; ignore their sections "
2267
+ "in the `tezgah` output style)" % ", ".join(disabled)))
2255
2268
  text = budgeted(event, parts)
2256
2269
  # latched only when the budget kept it, so a dropped line comes back
2257
2270
  if heavy and heavy in text:
@@ -2450,7 +2463,12 @@ def context_for(event, cwd, payload=None, with_core=True):
2450
2463
  "(each tagged DERIVED, you concluded it, or RETRIEVED, "
2451
2464
  "you read it), Evidence (the path:line or command behind "
2452
2465
  "each), Confidence, Unresolved, Disconfirming, Conflicts. "
2453
- "Full rules: the `tezgah-contract` skill."))
2466
+ "A question only the owner can answer - choosing a variant "
2467
+ "or option a research, analysis or review recommends; a way "
2468
+ "around a tezgah refusal; deciding about another session's "
2469
+ "work (its line, plan or branch) - is never yours: return "
2470
+ "`ESCALATE: owner` with the options and a recommended "
2471
+ "default. Full rules: the `tezgah-contract` skill."))
2454
2472
  else:
2455
2473
  parts.append(("pointer", POINTER_LINE))
2456
2474
  text = budgeted(event, [(key, render(text.strip())) for key, text in parts])
@@ -2612,7 +2630,18 @@ def shell_kind(command):
2612
2630
  could light from `orx` alone and the layer's own commands stayed invisible to
2613
2631
  it. The absolute spelling needs no case of its own - this reader keeps
2614
2632
  basenames, so `<bin>/tezgah-research` and the bare name arrive as the same
2615
- word."""
2633
+ word.
2634
+
2635
+ A python or node interpreter given a script file (`python3 bin/tezgah-docs
2636
+ q`) runs that script, so the interpreter word is dropped before the line is
2637
+ read; `-c`, `-m` and `-` (stdin) keep it, as their next word is code or a
2638
+ module, not a file. The match stays on one line (spaces and tabs only), and a
2639
+ shell interpreter needs none of this: the wrapper reader already looks past
2640
+ `bash`/`sh`. `shell_programs` itself stays as it is: the gate reads it and
2641
+ must keep the interpreter."""
2642
+ command = re.sub(r"(?<![\w/.$=-])(?:[\w./-]*/)?(?:python[\d.]*|node)"
2643
+ r"(?:[ \t]+-(?![cm]\b)\w[\w-]*)*[ \t]+(?=[^\s-])", "",
2644
+ str(command or ""))
2616
2645
  ran = shell_programs(command)
2617
2646
  if "consult" in ran:
2618
2647
  return "consult"
@@ -2917,8 +2946,9 @@ colored, and the glyph carries the state on its own where color does not):
2917
2946
  name\u25cb yellow armed, on demand - not used yet this session
2918
2947
  name\u2717 red turned off by a kill switch or a per-repo .no-* mark
2919
2948
  pony, adhd, dim this surface cannot report that measure (a skill read
2920
- needs a process per read to observe on codex, cursor and
2921
- dsh, so those two marks state nothing there instead of
2949
+ needs a process per read to observe on codex, commandcode,
2950
+ gemini, cursor, qwen, droid, copilot, antigravity and dsh,
2951
+ so those two marks state nothing there instead of
2922
2952
  claiming the skill was never opened)
2923
2953
  dim no state to report: idx n/a or uncomparable, or no blocked
2924
2954
  plan