@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
@@ -48,11 +48,12 @@ from tezgah_guard import attest_session, import_failed, safe # noqa: E402
48
48
  try:
49
49
  from tezgah_context import ( # noqa: E402
50
50
  command_text, context_for, record, shell_kind, slug, under)
51
- from tezgah_gate import decision, explored # noqa: E402
51
+ from tezgah_gate import decision, explored, graph_advice # noqa: E402
52
52
  from tezgah_integrity import ( # noqa: E402
53
53
  SUBAGENT_CHANNEL, bind_session, note, note_tool, ran_nothing, report_bytes,
54
54
  stop_reason)
55
55
  from tezgah_paths import cache_dir, off # noqa: E402
56
+ from tezgah_taste import write_note # noqa: E402
56
57
  from tezgah_untrusted import marks # noqa: E402
57
58
  except Exception as exc:
58
59
  import_failed(exc)
@@ -318,9 +319,16 @@ def dispatch(payload):
318
319
  if (kind in ("graph", "consult") and under(cwd) and not quiet
319
320
  and first_time(session_id, "graph")):
320
321
  reinforce = REINFORCE
321
- # both are one line for the model, on the result they belong to; a turn
322
- # that earns both reads them together rather than one replacing the other
323
- text = "\n".join(t for t in (notice, reinforce) if t)
322
+ # the gate's graph line for a code-symbol search (graph_advice) rides
323
+ # the same channel; all three are one line each for the model, on the
324
+ # result they belong to, read together rather than one replacing another
325
+ advice = (graph_advice(gate_name(payload.get("tool_name", "")), inp, cwd,
326
+ "cursor") if under(cwd) and not quiet else None)
327
+ # the opt-in taste note for the first write to this file type
328
+ # (hooks/tezgah_taste.write_note)
329
+ taste = (write_note(session_id, inp, cwd, gate_name(payload.get("tool_name", "")))
330
+ if under(cwd) and not call_failed(payload) else None)
331
+ text = "\n".join(t for t in (notice, reinforce, advice, taste) if t)
324
332
  out = {"additional_context": text} if text else {}
325
333
  elif event == "postToolUseFailure":
326
334
  if kind:
package/hosts/omp/hook.py CHANGED
@@ -19,7 +19,8 @@ the same code every other host runs.
19
19
  {"event": "user_prompt", "cwd": ..., "prompt": ...}
20
20
  -> {"context": <reminder + the rules this prompt arms>}
21
21
  {"event": "pre_tool_use", "cwd": ..., "tool": ..., "input": {...}}
22
- -> {"deny": reason}
22
+ -> {"deny": reason} | {"advice": line} (the graph line the bridge hands
23
+ the model with this call's result; never a block)
23
24
  {"event": "post_tool_use", "cwd": ..., "tool": ..., "input": {...},
24
25
  "failed": bool, "idx": glyph}
25
26
  -> {"status": "pony✓ ...", "idx": glyph, "label": one line}
@@ -57,11 +58,12 @@ try:
57
58
  from tezgah_context import ( # noqa: E402
58
59
  color_default, command_text, context_for, health_segments, record,
59
60
  render_tiers, shell_kind, skill_read_kind)
60
- from tezgah_gate import decision # noqa: E402
61
+ from tezgah_gate import decision, graph_advice # noqa: E402
61
62
  from tezgah_integrity import ( # noqa: E402
62
63
  SUBAGENT_CHANNEL, bind_session, note, note_tool, stop_reason, untrusted_label,
63
64
  untrusted_source)
64
65
  from tezgah_paths import off, root_for # noqa: E402
66
+ from tezgah_taste import write_note # noqa: E402
65
67
  from tezgah_untrusted import marks # noqa: E402
66
68
  except Exception as exc:
67
69
  import_failed(exc)
@@ -142,9 +144,12 @@ def handle(payload):
142
144
  # answered at every turn boundary
143
145
  return answered(*status_line(cwd, session_id))
144
146
  if event == "pre_tool_use":
145
- reason = decision(payload.get("tool", ""), payload.get("input") or {},
146
- cwd, session_id)
147
- return {"deny": reason} if reason else {}
147
+ tool, inp = payload.get("tool", ""), payload.get("input") or {}
148
+ reason = decision(tool, inp, cwd, session_id)
149
+ if reason:
150
+ return {"deny": reason}
151
+ advice = graph_advice(tool, inp, cwd, "omp")
152
+ return {"advice": advice} if advice else {}
148
153
  if not root_for(cwd):
149
154
  return {}
150
155
  if event == "session_start":
@@ -179,7 +184,9 @@ def handle(payload):
179
184
  with_core=False)
180
185
  return {"context": context} if context else {}
181
186
  if event == "user_prompt":
182
- context = context_for("user_prompt", cwd, payload)
187
+ # host="omp": the skill hint drops the absolute path, since omp's
188
+ # read tool resolves the skill:// address on its own
189
+ context = context_for("user_prompt", cwd, dict(payload, host="omp"))
183
190
  return {"context": context} if context else {}
184
191
  if event == "post_tool_use":
185
192
  tool = payload.get("tool", "")
@@ -240,6 +247,11 @@ def handle(payload):
240
247
  # deny: the bridge puts it in front of the content itself, so the model
241
248
  # reads where the text came from - or, on an effect after such a read,
242
249
  # the taint notice - while it reads the result.
250
+ # the opt-in taste note for the first write to this file type
251
+ # (hooks/tezgah_taste.write_note) rides the same label
252
+ taste = (safe(session_id, write_note, session_id, inp, cwd, tool)
253
+ if failed is not True else None)
254
+ label = "\n".join(t for t in (label, taste) if t)
243
255
  if label:
244
256
  out["label"] = label
245
257
  return out
@@ -263,11 +263,17 @@ function skillRead(tool: string, args: any): boolean {
263
263
 
264
264
  // The gate is only consulted for the tools the shared rule knows: the refusal
265
265
  // of the grep-only explorer (task/agent), the anti-shortcut and attribution
266
- // checks (bash), and the test-skip check (the edit/write names). An MCP tool is
266
+ // checks (bash), the test-skip check (the edit/write names), and the code-graph
267
+ // nudge on omp's search tools (grep, find, search, ast_grep). An MCP tool is
267
268
  // consulted when its name carries an effect verb (MCP_EFFECT, see `tool_call`).
268
- const GATED = ["grep", "bash", "shell", "command", "task", "agent", "edit",
269
- "write", "multiedit", "notebookedit", "apply_patch", "edit_file", "write_file",
270
- "search_replace", "powershell", "pwsh"];
269
+ const GATED = ["grep", "find", "search", "ast_grep", "bash", "shell", "command",
270
+ "task", "agent", "edit", "write", "multiedit", "notebookedit", "apply_patch",
271
+ "edit_file", "write_file", "search_replace", "powershell", "pwsh"];
272
+
273
+ // The gate's non-blocking graph line (tezgah_gate.graph_advice), held from the
274
+ // call to its result: `tool_call` can only block, `tool_result` can hand the
275
+ // model `additionalContext`, and carrying it across costs no second spawn.
276
+ const ADVICE = new Map<string, string>();
271
277
 
272
278
  // The effect verbs of tezgah_integrity.MCP_VERBS, as one pattern: a read-only
273
279
  // MCP call (a code-graph query) reaches no content rule, so it pays no spawn.
@@ -647,15 +653,22 @@ export default function tezgahOmp(pi: any): void {
647
653
  return undefined; // a hook that cannot answer cannot refuse either
648
654
  }
649
655
  if (res.deny) return { block: true, reason: res.deny };
656
+ if (res.advice && event?.toolCallId != null) {
657
+ ADVICE.set(String(event.toolCallId), String(res.advice));
658
+ }
650
659
  return undefined;
651
660
  });
652
661
 
653
662
  pi.on("tool_result", async (event: any, ctx: any) => {
654
663
  const tool = String(event?.toolName ?? "");
655
664
  const args = event?.input ?? {};
665
+ const id = event?.toolCallId != null ? String(event.toolCallId) : "";
666
+ const advice = ADVICE.get(id);
667
+ ADVICE.delete(id);
668
+ const graph = advice ? { additionalContext: advice } : undefined;
656
669
  if (!WATCHED.includes(tool.toLowerCase()) && !tool.startsWith("mcp__")
657
670
  && !skillRead(tool, args)) {
658
- return;
671
+ return graph;
659
672
  }
660
673
  // Tri-state: an unread outcome must never be written as a passed check,
661
674
  // because the Stop rule below reads this ledger as evidence.
@@ -680,9 +693,9 @@ export default function tezgahOmp(pi: any): void {
680
693
  // place; every other result is left exactly as the host produced it.
681
694
  if (res.label) {
682
695
  const content = labelled(event?.content, res.label);
683
- if (content) return { content };
696
+ if (content) return { content, ...graph };
684
697
  }
685
- return undefined;
698
+ return graph;
686
699
  });
687
700
 
688
701
  pi.on("session_stop", async (event: any, ctx: any) => {
@@ -105,7 +105,8 @@ const SETUP_BIN = join(CONFIG, "bin", "tezgah-setup")
105
105
  const CONTEXT_BIN = join(CONFIG, "bin", "tezgah-context")
106
106
  const CAPTURE_BIN = join(CONFIG, "bin", "tezgah-capture")
107
107
  const GATE_BIN = join(CONFIG, "bin", "tezgah-gate")
108
- const IDENT = /^[A-Za-z_][A-Za-z0-9_]{2,}$/
108
+ const SEARCH_SYMBOL =
109
+ /^(?:\\b)?(?:(def|class|function|func|fn|interface|struct|type)\s+)?([A-Za-z_][A-Za-z0-9_]{2,})(\\?\()?(?:\\b)?$/
109
110
  // A hung core must not hold a tool call open. Every awaited core CLI runs
110
111
  // through `collect`, which gives it a deadline and answers a timeout with the
111
112
  // same fail-open value a missing binary or a non-zero exit gets: a core that
@@ -1265,9 +1266,10 @@ async function recordEvidence(sessionID, tool, args, result, workspace, cwd,
1265
1266
  // hooks/tezgah_taste.note_write in process, and this host writes its edit row
1266
1267
  // here, so the same call goes to that module through a process. The marker
1267
1268
  // lives where the kill switches do (tezgah_paths.armed), so off it costs two
1268
- // stats and spawns nothing.
1269
+ // stats and spawns nothing. The process prints the taste note for the first
1270
+ // write to this file type (tezgah_taste.write_note), returned to the caller.
1269
1271
  if (kind === "edit" && row.exit !== 1 && off("taste-on")) {
1270
- await noteTaste(sessionID, row.id, args, cwd)
1272
+ return await noteTaste(sessionID, row.id, args, cwd)
1271
1273
  }
1272
1274
  }
1273
1275
 
@@ -2111,10 +2113,12 @@ function ledgerStem(sessionID) {
2111
2113
  }
2112
2114
 
2113
2115
  // The nearest enclosing project that holds a codegraph index. The answer is the
2114
- // indexed directory itself, which is what the nudge names.
2116
+ // indexed directory itself, which is what the nudge names; a `.no-graph` met
2117
+ // first is the repo's own switch (hooks/tezgah_gate.index_slug), so no index.
2115
2118
  function indexSlug(dir) {
2116
2119
  let d = dir
2117
2120
  for (;;) {
2121
+ if (existsSync(join(d, ".no-graph"))) return null
2118
2122
  if (existsSync(join(d, GRAPH_DB))) return d
2119
2123
  const parent = d.replace(/\/[^/]+\/?$/, "")
2120
2124
  if (!parent || parent === d) return null
@@ -2122,17 +2126,23 @@ function indexSlug(dir) {
2122
2126
  }
2123
2127
  }
2124
2128
 
2129
+ // hooks/tezgah_gate.symbol_in: a code symbol (inner underscore, a lower-to-upper
2130
+ // step, or any identifier in a `def x` / `x(` pattern); a plain word such as
2131
+ // `TODO` is literal text and is never nudged.
2132
+ function symbolIn(text) {
2133
+ const m = String(text || "").trim().replace(/^['"]+|['"]+$/g, "").match(SEARCH_SYMBOL)
2134
+ if (!m) return null
2135
+ const tok = m[2]
2136
+ const shaped = tok.replace(/^_+|_+$/g, "").includes("_") || /[a-z][A-Z]/.test(tok)
2137
+ return m[1] || m[3] || shaped ? tok : null
2138
+ }
2139
+
2125
2140
  function identifierFrom(tool, args) {
2126
- if (tool === "grep") {
2127
- const p = String(args.pattern || "")
2128
- return IDENT.test(p) ? p : null
2129
- }
2141
+ if (tool === "grep") return symbolIn(args.pattern)
2130
2142
  if (tool === "bash" || tool === "shell") {
2131
2143
  const cmd = String(args.command || "")
2132
2144
  const m = cmd.match(/(?:^|[|;&(]\s*|\s)(?:grep|rg)\s+((?:-\S+\s+)*)(\S+)/)
2133
- if (!m) return null
2134
- const tok = m[2].replace(/^['"]|['"]$/g, "")
2135
- return IDENT.test(tok) ? tok : null
2145
+ return m ? symbolIn(m[2]) : null
2136
2146
  }
2137
2147
  return null
2138
2148
  }
@@ -2365,7 +2375,8 @@ function captureSnapshot(tool, args, dir, sessionID) {
2365
2375
 
2366
2376
  // hooks/tezgah_taste.py's own entry point, found beside the bin the snapshot
2367
2377
  // capture runs (the installed bins are links into the checkout, which holds no
2368
- // copy of hooks/ under CONFIG). Best effort and silent, like captureSnapshot.
2378
+ // copy of hooks/ under CONFIG). Best effort, like captureSnapshot: a failure is
2379
+ // ignored. It resolves to the taste note the process printed, else undefined.
2369
2380
  function noteTaste(sessionID, id, args, cwd) {
2370
2381
  let script
2371
2382
  try {
@@ -2374,7 +2385,8 @@ function noteTaste(sessionID, id, args, cwd) {
2374
2385
  } catch { return undefined }
2375
2386
  return collect([script, JSON.stringify({
2376
2387
  session_id: sessionID, id, input: args, cwd, host: "opencode" })],
2377
- { stdio: "ignore" }, null, () => undefined)
2388
+ { stdio: ["ignore", "pipe", "ignore"] }, null,
2389
+ (code, out) => (code === 0 && out.trim() ? out.trim() : undefined))
2378
2390
  }
2379
2391
 
2380
2392
  export const Tezgah = async ({ directory }) => {
@@ -2727,8 +2739,9 @@ export const Tezgah = async ({ directory }) => {
2727
2739
  if (notice) labelResult(output, notice)
2728
2740
  const kind = await classify(tool, args)
2729
2741
  if (kind) await record(sessionID, kind)
2730
- await recordEvidence(sessionID, tool, args, output, workspace, dir,
2731
- own || inherited)
2742
+ const taste = await recordEvidence(sessionID, tool, args, output, workspace,
2743
+ dir, own || inherited)
2744
+ if (taste) labelResult(output, taste)
2732
2745
  } catch {}
2733
2746
  },
2734
2747
 
@@ -25,27 +25,40 @@ it - no apology theater, no self-justifying phrasing.
25
25
  **Ponytail (minimal code).** Laziest solution that works: YAGNI -> reuse an
26
26
  existing helper -> stdlib -> native platform feature -> installed dependency ->
27
27
  one line -> minimum code. No unrequested abstractions, no scaffolding "for
28
- later", shortest working diff. Trace the problem fully before climbing; never
29
- simplify away validation, error handling, security or anything requested. Bug
30
- fix = root cause where all callers route through. A deliberate corner cut gets
31
- a `ponytail:` comment naming the ceiling. On the first non-trivial coding task,
32
- read the full `ponytail` skill from the router (levels lite|full|ultra, set with
33
- `tezgah-pony`; the level rides this reminder when it is not `full`) - this
34
- paragraph is not the whole contract. Off: "stop ponytail".
28
+ later", deletion over addition, fewest files, shortest working diff. Trace the
29
+ problem fully before climbing; never simplify away validation, error handling,
30
+ security, accessibility or anything requested, and build the full version
31
+ without re-arguing when the user insists. Bug fix = root cause where all
32
+ callers route through. A deliberate corner cut gets a `ponytail:` comment
33
+ naming the ceiling. Non-trivial logic leaves ONE runnable check (an assert
34
+ self-check or one small test); a one-liner needs none. A complex ask ships the
35
+ lazy version and names the fuller one in the same reply instead of stalling.
36
+ Reply: code first, then at most three lines - what was skipped, when to add it.
37
+ In force from the first turn; levels lite|full|ultra are set with `tezgah-pony`
38
+ and ride the per-turn reminder when not `full`; read the full `ponytail` skill
39
+ from the router for the level table and examples. Off: "stop ponytail".
35
40
 
36
41
  **Output shape: ADHD-friendly.** The answer or the next action is on the first
37
- line, prose after it. Multi-step work is a numbered list, one bounded action per
38
- step, and while it is in flight its position is restated in one line - the todo
39
- list is that source, never re-narrate the plan. End with one concrete next step.
40
- Finish the issue in hand before raising a second one; an error states location,
41
- cause and fix with no drama; after a change say what now works. A list shows at
42
- most five items, ranked, the rest kept in reserve rather than dropped. An
42
+ line - a command, path, snippet or the decision itself, never context or the
43
+ question restated - prose after it. Multi-step work is a numbered list, one
44
+ bounded action per step, and while it is in flight its position is restated in
45
+ one line - the todo list is that source, never re-narrate the plan. End with one
46
+ concrete next step. Finish the issue in hand before raising a second one; an
47
+ error states location, cause and fix with no drama, naming a cause only when the
48
+ evidence identifies it (else what is known and what would confirm it); after a
49
+ change say what now works. A list shows at most five items, ranked, the rest
50
+ kept in reserve rather than dropped - presentation only, never the analysis. An
43
51
  estimate is in concrete units and marked as an estimate, never presented as a
44
52
  measurement. No preamble, no recap, no closer, and a question the reader raises
45
- mid-work is answered rather than deferred as the second issue. On the FIRST
46
- non-trivial answer of the session, read the full `i-have-adhd` skill from the
47
- router - this paragraph is not the whole contract. Off: `tezgah-adhd off`, or
48
- the repo's `.no-adhd`.
53
+ mid-work is answered rather than deferred as the second issue. Break the shape
54
+ only where it would delete the answer: an explanation runs as long as it needs,
55
+ with headers; an options question gets two to four ranked options,
56
+ recommendation first; after three "still broken" turns, name the suspect
57
+ assumption and ask one diagnostic question. Before sending, delete an opening
58
+ sentence that announces, a closing one that recaps or asks "anything else?", any
59
+ by-the-way sidebar and any hedge that carries no real uncertainty. In force from
60
+ the first answer; read the full `i-have-adhd` skill from the router for the
61
+ examples. Off: `tezgah-adhd off`, or the repo's `.no-adhd`.
49
62
 
50
63
  **Deliver the whole ask; never the shortcut.** The request defines the
51
64
  deliverable: every named item is in scope until the user says otherwise, and the
@@ -118,7 +131,9 @@ Never install, upgrade, restart or kill anything for tezgah, and never open an
118
131
  issue for one of its tools mid-session. Name a missing capability in one line,
119
132
  use the documented fallback (grep/find, or the second opinion skipped), and carry
120
133
  on with the task in hand. Tezgah maintenance is in scope when the user asks for
121
- it, or when the repo IS the tezgah checkout.
134
+ it, or when the repo IS the tezgah checkout. Before working a topic an
135
+ installed skill covers, run `tezgah-skill <words>` and read only the range it
136
+ returns.
122
137
 
123
138
  **Workspace: `.tezgah/` only.** Every per-project tezgah artifact - plans,
124
139
  research lines, analysis, lessons - lives under `<repo>/.tezgah/`, which the
@@ -139,4 +154,4 @@ rule), `workspace-off` (the root plans/research/analysis refusal),
139
154
  per-repo `.no-ponytail`, `.no-adhd`, `.no-graph`, `.no-lessons`.
140
155
  The ponytail intensity level is not a switch: `tezgah-pony lite|full|ultra`.
141
156
 
142
- **On-demand rules (armed when the task class matches; full text in the `tezgah-contract` skill).** Spec-first for an underspecified or quality-only ask. A second opinion before a call that is hard to reverse or that one model would answer with unearned confidence. OpenResearch routing for research. Product analysis is a five-axis evidence task - value, usability (the running app, not the source), feasibility (a cited `path:line`), competition, and keep/fix/cut/bet triage - with one named evidence class per finding. The code graph for "who calls X" and "what breaks if Z changes". The design floor a UI turn is judged against is `.tezgah/design-contract.md`: `tezgah-design derive` writes it from the repository's own tokens, `tezgah-design check` measures a change against it, and the `design-contract` skill owns its shape.
157
+ **On-demand rules (armed when the task class matches; full text in the `tezgah-contract` skill).** Spec-first for an underspecified or quality-only ask. A second opinion before a call that is hard to reverse or that one model would answer with unearned confidence. OpenResearch routing for research. Product analysis is a five-axis evidence task - value, usability (the running app, not the source), feasibility (a cited `path:line`), competition, and keep/fix/cut/bet triage - with one named evidence class per finding. The code graph for "who calls X" and "what breaks if Z changes". Two or more independent items fan out to parallel subagents in one message, one git worktree per writing slice. The design floor a UI turn is judged against is `.tezgah/design-contract.md`: `tezgah-design derive` writes it from the repository's own tokens, `tezgah-design check` measures a change against it, and the `design-contract` skill owns its shape.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@r1z4x/tezgah",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "One shared working contract for every AI coding assistant you run: Claude Code, Codex, Cursor, opencode, dsh and oh-my-pi (omp).",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -34,8 +34,8 @@ whether they still apply, they do.
34
34
 
35
35
  Off: the `adhd-off` kill switch (`~/.config/tezgah/adhd-off`), a repo's
36
36
  `.no-adhd` mark, or the user saying "stop adhd mode" / "normal mode". The
37
- always-on text carries the compressed form of these rules; this file is the full
38
- contract, so read it rather than working from the summary.
37
+ always-on text carries every rule below in compressed form from the first turn
38
+ of a session; this file adds the reasoning and the worked examples.
39
39
 
40
40
  ## What changes about reading
41
41
 
@@ -22,11 +22,44 @@ evidence survives the session that produced it: the state on disk, the protocol
22
22
  committed before the run, the claims with their falsification criteria, and the
23
23
  review that decided what the evidence actually supports.
24
24
 
25
+ ## Scope and method: answer the question asked
26
+
27
+ Three ways a line goes wrong before any rule below can catch it: it turns a
28
+ question into metrics, it grows past what was asked, and it wanders into a topic
29
+ nobody asked about. So before anything else:
30
+
31
+ 1. **Restate the question verbatim** (`--ask "<the user's words>"`) and write
32
+ what is in scope and what is out (`--in "<covered>"` per item, `--out "<left
33
+ alone>"` per item; state.json `scope`). The in-scope items are `S1`, `S2`, ...
34
+ in order. Nothing is added to the list the user did not ask for.
35
+ 2. **Pick the method from the question.** A qualitative, conceptual or reference
36
+ question - "what does X say", "how does Y work", "which sources support Z" - is
37
+ `--method qualitative`: a source review, no metric, no experiment, no orx
38
+ experiment tree. A question whose answer is a number or a comparison of
39
+ numbers is `--method measured`, and only then does the evaluation lock, the
40
+ protocol and the experiment tree below apply. `check` refuses a qualitative
41
+ line that records results, and a rules-5 line past `bootstrap` with no scope or
42
+ method.
43
+ 3. **A plain factual or reference question gets no line** (`--tier quick` is
44
+ refused): answer it in the reply from the primary source, cited, with its
45
+ confidence.
46
+ 4. **Every finding traces to an in-scope item.** A claim's `trace` names its
47
+ `S<n>` (or the item's text); `claim` and `check` refuse one that names none. A
48
+ finding that answers nothing asked is dropped; a tangent worth having is one
49
+ line under `## Open questions` for the user to pick, never a section of the
50
+ report.
51
+ 5. **Depth is the primary source, not breadth.** Read the spec, the paper, the
52
+ code or the vendor's own documentation itself, cite it in the finding, and
53
+ state a confidence (high, medium or low, and the reason). A summary of other
54
+ summaries, or a finding with no source, is not an answer.
55
+
25
56
  ## The engine: OpenResearch
26
57
 
27
58
  Execution goes through the `orx` CLI when it is installed, never ad-hoc
28
- scripting, and its cardinal rules are not style preferences - breaking one
29
- silently invalidates the run:
59
+ scripting. A qualitative line uses its literature commands (`orx discover`,
60
+ `orx paper`, the `orx skill lit-review` module). A measured line also uses its
61
+ experiment tree, and there its cardinal rules are not style preferences -
62
+ breaking one silently invalidates the run:
30
63
 
31
64
  - never edit a node once a run has answered it; branch a child instead,
32
65
  - the run command and environment are a fixed contract identical on every node,
@@ -34,7 +67,8 @@ silently invalidates the run:
34
67
  - grow the experiment tree downward, not sideways.
35
68
 
36
69
  Load its manual before driving it: `orx skill`, then the module for the step
37
- (`orx skill experiment-tree`, `orx skill lit-review`, `orx skill evidence`).
70
+ (`orx skill lit-review` for sources; `orx skill experiment-tree` and
71
+ `orx skill evidence` for a measured line).
38
72
  Local runs need no login; managed compute does - ask the user to run `orx login`.
39
73
  If `orx` is absent, say the research tooling is unavailable and do not improvise
40
74
  its protocol; the workspace below still applies to whatever you run instead.
@@ -48,10 +82,10 @@ One directory per research line, `<repo>/.tezgah/research/<slug>/`:
48
82
 
49
83
  | Path | What it holds |
50
84
  |---|---|
51
- | `state.json` | the question, phase, direction, the locked evaluation (`metric`, `baseline`, `locked_at`, optional `environment`, and the optional two-gate pair `capability_tolerance` + `counter_metric`), the hypothesis list, `rules` (the rule set the line was opened under), `deliverable` (`kind`: design, plan, analysis, code or finding; `path`; `ask`, the ask's items one per entry; `min_variants`), and `supersedes` when it is a new version of an older line |
85
+ | `state.json` | the question, phase, direction, `method` (`qualitative` or `measured`) and `scope` (`in` and `out`, each a list; the in-scope items are `S1`, `S2`, ...), the locked evaluation of a measured line (`metric`, `baseline`, `locked_at`, optional `environment`, and the optional two-gate pair `capability_tolerance` + `counter_metric`), the hypothesis list, `rules` (the rule set the line was opened under), `deliverable` (`kind`: design, plan, analysis, code or finding; `path`; `ask`, the ask's items one per entry; `min_variants`), and `supersedes` when it is a new version of an older line |
52
86
  | `log.md` | the decision timeline: one line per decision, experiment, dead end or pivot, with the evidence that drove it |
53
87
  | `findings.md` | `## What we know`, `## Patterns`, `## Lessons`, `## Open questions` |
54
- | `claims.jsonl` | one claim per line: `statement`, `status`, `provenance`, `kind`, `falsification`, `proof`, `dependencies`, `scope` (what the claim's numbers were measured on, and it may not be wider than the rows it rests on), and `supersedes` when the claim replaces an earlier one - recorded with `tezgah-research claim <slug>`, never by editing the file |
88
+ | `claims.jsonl` | one claim per line: `statement`, `status`, `provenance`, `kind`, `falsification`, `proof`, `dependencies`, `trace` (the in-scope item it answers, `S<n>`, required from rules 5), `scope` (what the claim's numbers were measured on, and it may not be wider than the rows it rests on), and `supersedes` when the claim replaces an earlier one - recorded with `tezgah-research claim <slug>`, never by editing the file |
55
89
  | `experiments/<hypothesis>/` | `protocol.md`, `results.jsonl` (one JSON object per row, each carrying its `source`, its `scope` - what the numbers were measured on - and, when that scope is `fixture`, the `fixture` description of the input that was generated), `analysis.md`, and `raw/<runId>.log` for a run read back from the engine |
56
90
  | `literature/` | one file per source, saved when you read it, not later, and each named by its row in `literature/INDEX.jsonl` - every file under it, at any depth and of any extension, is a source the index names |
57
91
  | `decisions/<id>/` | one comparison of the deliverable's variants: `criteria.json`, `variants.jsonl`, `comparison.jsonl` and `decision.md` - see "Variants" below |
@@ -79,7 +113,8 @@ Scaffold and check it with the CLI (`~/.config/tezgah/bin/tezgah-research`, or
79
113
  `bin/tezgah-research` in the checkout):
80
114
 
81
115
  ```sh
82
- ~/.config/tezgah/bin/tezgah-research init my-line --ask "<the user's words>" --question "does X hold under Y?"
116
+ ~/.config/tezgah/bin/tezgah-research init my-line --ask "<the user's words>" --question "does X hold under Y?" \
117
+ --method measured --in "X under Y" --out "X under Z"
83
118
  ~/.config/tezgah/bin/tezgah-research commit my-line "<message>" # commit in .tezgah's repo
84
119
  ~/.config/tezgah/bin/tezgah-research check # open lines + done/ seals; 0 clean, 1 broken rule, 2 misuse
85
120
  ~/.config/tezgah/bin/tezgah-research check --all-lines # every line in full, done/ included
@@ -220,9 +255,11 @@ rules the domain library enforces on a compiled artifact apply here:
220
255
 
221
256
  **Bootstrap.** Search the literature with more than one source - `orx discover
222
257
  keyword|embedding|openalex "<query>"` first, `orx paper <id>` to read one (the
223
- `orx skill lit-review` module is the procedure) - save every source
224
- to `literature/` as you go, identify the gap, form testable hypotheses (see
225
- Ideation below), and lock the evaluation before running anything: the metric, the
258
+ `orx skill lit-review` module is the procedure) - for the in-scope items only,
259
+ and save every source to `literature/` as you go. A qualitative line stops here
260
+ and answers from those sources. A measured line identifies the gap, forms
261
+ testable hypotheses about the in-scope items (see Ideation below), and locks the
262
+ evaluation before running anything: the metric, the
226
263
  baseline, the threshold. Write it into `state.json` and commit it before the first
227
264
  results row - a criterion chosen after seeing results is not a criterion, and
228
265
  `check` refuses a line whose first commit holding a metric and a baseline does not
@@ -377,7 +414,9 @@ Pick the move that matches the state you are in:
377
414
  | "the run is stuck" | name the hidden constraint and drop it (see below) |
378
415
  | "the results are surprising" | re-derive the assumption they break, then go back to the literature |
379
416
 
380
- - **Diverge first**: list candidate mechanisms and framings without judging them.
417
+ - **Diverge first**: list candidate mechanisms and framings without judging them,
418
+ every one of them about an in-scope item - a framing of a different question is
419
+ an Open question for the user, not a candidate.
381
420
  - **Expose a hidden constraint**: list the constraints of the current approach and
382
421
  classify them hard (physically necessary), soft (convention) or hidden (never
383
422
  stated). The most productive move in the library's whole ideation set is
@@ -474,12 +513,15 @@ and `--strict` refuses it; two raters at least, and state their agreement.
474
513
 
475
514
  ## Variants
476
515
 
477
- A deliverable a reader acts on - a design, a plan, an analysis, code - is not one
478
- version redone until it looks right. Serial versions fixate on the first option
479
- and a design shown alone gets inflated ratings (Dow et al. 2010, Tohidi et al.
480
- 2006). Produce at least three variants (two for `code`; `min_variants` in
516
+ A deliverable a reader acts on and chooses between - a design, a plan, code - is
517
+ not one version redone until it looks right. Serial versions fixate on the first
518
+ option and a design shown alone gets inflated ratings (Dow et al. 2010, Tohidi et
519
+ al. 2006). Produce at least three variants (two for `code`; `min_variants` in
481
520
  `deliverable`, and below the default only with a `single_variant_reason`) and
482
- compare them under criteria fixed first, in `decisions/<id>/`:
521
+ compare them under criteria fixed first, in `decisions/<id>/`. An `analysis`
522
+ answers its question once on a rules-5 line: variants are a choice the user
523
+ makes, not a way to triple the answer (an older line keeps the three it was
524
+ opened under):
483
525
 
484
526
  | File | What it holds |
485
527
  |---|---|
@@ -495,8 +537,9 @@ after it was committed; a missing cell once `decision.md` exists or the line is
495
537
  delivering; a `chosen` that is not a produced variant, a rejected variant with no
496
538
  criterion, no `flip:`; and a chosen variant another one beats or ties on every
497
539
  numeric criterion. A judged cell with fewer than two raters warns (`--strict`
498
- refuses). A design, plan or analysis with no `decisions/` warns while the line
499
- runs, is refused once it delivers, and keeps the line open.
540
+ refuses). A design or plan (and an analysis on a line before rules 5) with no
541
+ `decisions/` warns while the line runs, is refused once it delivers, and keeps
542
+ the line open.
500
543
 
501
544
  When the variants are code or config, they are sibling orx nodes under one head
502
545
  node (orx cardinal rule 4: fan within a round, then descend onto the winner), and