@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
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env python3
2
+ """Orca: is this session inside it, which CLI drives it, and what it tracks.
3
+
4
+ Orca (an IDE for agent sessions) owns worktrees, terminals, its embedded browser
5
+ and artifacts, and it exports its own markers into every terminal it starts.
6
+ tezgah reads those markers - never writes them - so a session in Orca is told to
7
+ create a checkout with `orca worktree create` rather than a raw `git worktree
8
+ add` Orca does not list, and `tezgah-status --orca` shows which of a repo's
9
+ checkouts Orca tracks. Stdlib only; detection forks nothing, so the session-start
10
+ line costs no process. docs/orca.md is the page.
11
+ """
12
+ import json
13
+ import os
14
+ import shlex
15
+ import shutil
16
+ import subprocess
17
+ import sys
18
+
19
+ # The markers an Orca terminal exports (observed in Orca 1.4.221's env). Any one
20
+ # is enough; TERM_PROGRAM=Orca covers a shell that dropped the ORCA_* set.
21
+ MARKERS = ("ORCA_WORKTREE_ID", "ORCA_TERMINAL_HANDLE", "ORCA_PANE_KEY")
22
+ MAC_APP_CLI = "/Applications/Orca.app/Contents/Resources/bin/orca"
23
+ # The file Orca leaves in each per-account codex home it hands out as CODEX_HOME.
24
+ MANAGED_HOME = ".orca-managed-home"
25
+
26
+
27
+ def session(env=None):
28
+ """{worktree, terminal, version} when this process runs in an Orca terminal,
29
+ else None. `worktree` is the path half of ORCA_WORKTREE_ID
30
+ (`<repoId>::<path>`)."""
31
+ env = os.environ if env is None else env
32
+ if not any(env.get(k) for k in MARKERS) and env.get("TERM_PROGRAM") != "Orca":
33
+ return None
34
+ return {"worktree": env.get("ORCA_WORKTREE_ID", "").partition("::")[2],
35
+ "terminal": env.get("ORCA_TERMINAL_HANDLE", ""),
36
+ "version": env.get("ORCA_APP_VERSION", "")}
37
+
38
+
39
+ def cli(env=None, platform=None):
40
+ """The argv prefix that runs Orca's CLI, or None. The order is the orca-cli
41
+ skill's: Orca's own ORCA_CLI_COMMAND (WSL), then its exported bin dir, then
42
+ PATH. On Linux outside an Orca terminal a bare `orca` is the GNOME screen
43
+ reader, so only `orca-ide` counts there. TEZGAH_ORCA_BIN pins one binary
44
+ (tests): a missing one is None, never a fallback to the real app."""
45
+ env = os.environ if env is None else env
46
+ platform = platform or sys.platform
47
+ pinned = env.get("TEZGAH_ORCA_BIN")
48
+ if pinned:
49
+ return [pinned] if os.access(pinned, os.X_OK) else None
50
+ if env.get("ORCA_CLI_COMMAND"):
51
+ return shlex.split(env["ORCA_CLI_COMMAND"])
52
+ bindir = env.get("ORCA_CLI_BIN_DIR")
53
+ if bindir and os.access(os.path.join(bindir, "orca"), os.X_OK):
54
+ return [os.path.join(bindir, "orca")]
55
+ path = env.get("PATH", os.defpath)
56
+ name = ("orca-ide" if platform.startswith("linux") and not session(env)
57
+ else "orca")
58
+ found = shutil.which(name, path=path)
59
+ if found:
60
+ return [found]
61
+ if platform == "darwin" and os.access(MAC_APP_CLI, os.X_OK):
62
+ return [MAC_APP_CLI]
63
+ return None
64
+
65
+
66
+ def run(args, timeout=10):
67
+ """`orca <args> --json`'s `result`, or None when there is no CLI, the
68
+ runtime is down or the answer is not `ok`."""
69
+ argv = cli()
70
+ if not argv:
71
+ return None
72
+ try:
73
+ proc = subprocess.run(argv + list(args) + ["--json"], capture_output=True,
74
+ text=True, timeout=timeout)
75
+ data = json.loads(proc.stdout)
76
+ except (OSError, ValueError, subprocess.SubprocessError):
77
+ return None
78
+ return data.get("result") if isinstance(data, dict) and data.get("ok") else None
79
+
80
+
81
+ def checkouts(paths):
82
+ """Orca's view of a repository's git checkouts (`paths`, real paths):
83
+ {"tracked": [row per checkout Orca lists], "untracked": [path Orca does not
84
+ list]}, or None when the runtime cannot answer. Read from `orca worktree ps`,
85
+ the one call that carries terminals and agent states together."""
86
+ ps = run(["worktree", "ps", "--limit", "1000"])
87
+ if ps is None:
88
+ return None
89
+ wanted = set(paths)
90
+ tracked = {}
91
+ for row in ps.get("worktrees") or []:
92
+ path = os.path.realpath(row.get("path") or "")
93
+ if path in wanted:
94
+ tracked[path] = {
95
+ "path": path, "name": row.get("displayName") or "",
96
+ "card": row.get("workspaceStatus") or "",
97
+ "status": row.get("status") or "",
98
+ "terminals": row.get("liveTerminalCount") or 0,
99
+ "agents": ["%s:%s" % (a.get("agentType"), a.get("state"))
100
+ for a in row.get("agents") or []]}
101
+ return {"tracked": [tracked[p] for p in paths if p in tracked],
102
+ "untracked": [p for p in paths if p not in tracked]}
103
+
104
+
105
+ def context_line(env=None):
106
+ """The session-start line for a session inside Orca, or "" outside it."""
107
+ here = session(env)
108
+ if not here:
109
+ return ""
110
+ where = here["worktree"] or "unknown"
111
+ return ("Orca: this session runs in an Orca terminal (worktree `%s`); Orca "
112
+ "owns worktrees, terminals, its embedded browser and artifacts here. "
113
+ "A parallel slice that needs its own checkout gets one from `orca "
114
+ "worktree create --name <slug> --parent-worktree active --json` (add "
115
+ "`--agent <cli> --prompt <brief>` to start its worker there), not "
116
+ "`git worktree add`, which Orca does not list. A long job runs in "
117
+ "`orca terminal create --worktree <selector> --command <cmd> --json`, "
118
+ "not a background shell. Track with `orca worktree ps --json` and "
119
+ "`tezgah-status --orca`; mark checkpoints with `orca worktree set "
120
+ "--worktree active --comment <text>`. Full guide: `orca skills get "
121
+ "orca-cli`." % where)
122
+
123
+
124
+ def summary(codex_home):
125
+ """One `orca:` line for `tezgah-setup --status`: app/runtime, whether this
126
+ shell is in an Orca terminal, and whose codex home CODEX_HOME names; ""
127
+ when Orca is neither installed nor around this shell."""
128
+ argv, here = cli(), session()
129
+ if not argv and not here:
130
+ return ""
131
+ runtime = ((run(["status"]) or {}).get("runtime") or {}) if argv else {}
132
+ parts = ["orca: %s" % (
133
+ "runtime %s reachable" % runtime.get("appVersion", "")
134
+ if runtime.get("reachable") else
135
+ "CLI %s, runtime not reachable (`orca open`)" % argv[-1] if argv else
136
+ "no orca CLI found")]
137
+ parts.append("this shell runs in an Orca terminal (worktree %s)"
138
+ % (here["worktree"] or "unknown") if here
139
+ else "this shell is not an Orca terminal")
140
+ if os.path.isfile(os.path.join(codex_home, MANAGED_HOME)):
141
+ parts.append("CODEX_HOME %s is an Orca-managed codex home: tezgah adds "
142
+ "its hook groups beside Orca's and leaves Orca's entries "
143
+ "and env alone" % codex_home)
144
+ return "; ".join(parts)
@@ -497,18 +497,23 @@ def have_typesafe_key():
497
497
 
498
498
 
499
499
  def have_judge_key():
500
- """True when tezgah's own judgement seam resolves a credential: the env var
501
- it reads, else its key file - and, when neither resolves, the OpenRouter
502
- fallback's own two channels (`OPENROUTER_API_KEY`, `~/.config/openrouter/key`),
503
- which are the channels `hooks/tezgah_judge.py`'s `credential()` picks
504
- between. This is what `bin/tezgah-triage`, `bin/tezgah-docs` and the skill
505
- picker spend. Never omp's login store: the seam does not open it.
500
+ """True when tezgah's own judgement seam can ask anyone: the session's own
501
+ CLI (`session_cli()`), else - when the `fallback` setting lets a third party
502
+ answer - the TypeSafe env var or key file, then the OpenRouter fallback's own
503
+ two channels (`OPENROUTER_API_KEY`, `~/.config/openrouter/key`), which are
504
+ the channels `hooks/tezgah_judge.py`'s `providers()` picks between. This is
505
+ what `bin/tezgah-triage`, `bin/tezgah-docs` and the skill picker spend. Never
506
+ omp's login store: the seam does not open it.
506
507
 
507
508
  Kept next to `have_typesafe_key()` because the two are asked together and a
508
509
  report that showed one answer under both questions was wrong in both
509
510
  directions - a key file alone (seam works, omp does not) and a login-store
510
511
  record alone (omp works, seam does not). A blank file is not a credential
511
512
  here either, so this stays in step with the seam's own `strip() or None`."""
513
+ if session_cli():
514
+ return True
515
+ if fallback_policy() == "none":
516
+ return False
512
517
  for env, home in (("TYPESAFE_API_KEY", ".config/typesafe/key"),
513
518
  ("OPENROUTER_API_KEY", ".config/openrouter/key")):
514
519
  if os.environ.get(env, "").strip():
@@ -522,6 +527,35 @@ def have_judge_key():
522
527
  return False
523
528
 
524
529
 
530
+ # How far a model call may leave the session's own vendor (config.json
531
+ # `fallback`). `vendor`, the default: the session's own model first, and a
532
+ # fallback only to the same vendor's model (a subagent chain may reach Opus
533
+ # through OpenRouter; the judge has no such route, so it falls back to nobody
534
+ # while the session CLI exists, and to a third party only when there is no
535
+ # session CLI at all). `any`: a cross-vendor fallback is allowed after the
536
+ # session's own model fails. `none`: no provider but the session's, ever.
537
+ FALLBACKS = ("vendor", "any", "none")
538
+
539
+
540
+ def fallback_policy():
541
+ """The configured `fallback` value, `vendor` when unset or unknown."""
542
+ cfg = config()
543
+ value = cfg.get("fallback") if isinstance(cfg, dict) else None
544
+ return value if value in FALLBACKS else "vendor"
545
+
546
+
547
+ def session_cli():
548
+ """`omp` or `claude` - the CLI of the host this process runs under, when it
549
+ is installed - else None. omp sets CLAUDECODE too, so OMPCODE is read first,
550
+ and an omp session whose binary is missing is None rather than Claude: the
551
+ session's credential is omp's, not whatever `claude` is logged in as."""
552
+ if os.environ.get("OMPCODE"):
553
+ return "omp" if omp_bin() else None
554
+ if os.environ.get("CLAUDECODE"):
555
+ return "claude" if claude_bin() else None
556
+ return None
557
+
558
+
525
559
  def orx_bin():
526
560
  """The OpenResearch `orx` executable, or None when it is not installed.
527
561
 
@@ -273,13 +273,25 @@ discovery, review, impact analysis (research goes to OpenResearch - see the
273
273
  Research section - with a host subagent only as the fallback when `orx` is
274
274
  absent). Subtasks with no data dependency
275
275
  between them MUST be spawned in ONE message so they run in parallel; dependent
276
- ones run sequentially, each briefed with the previous result. Fan out only when
276
+ ones run sequentially, each briefed with the previous result. Fanning out is the
277
+ default, not a favour the user has to ask for: two or more independent items -
278
+ a list, several files or areas, todo entries that share nothing - go to one
279
+ subagent each, and every writing slice gets its own branch and checkout (see
280
+ below), never the main checkout. The
281
+ orchestrator is the main thread itself: it does not wait idle on a slice while
282
+ independent work remains. Fan out only when
277
283
  the subtasks share no mutable file and no interface: if two of them would edit
278
284
  the same file, or one's answer decides the other's, keep them in one context or
279
285
  sequence them. The shared artifact is the coordination channel, not chatter -
280
286
  naming a lead coordinates nothing by itself. If the work
281
287
  cannot be split - one file, one bounded change, a strictly serial chain - do it
282
288
  directly. Never spawn a subagent whose briefing is bigger than the work.
289
+ A slice that needs its own checkout: inside Orca (the session-start `Orca:`
290
+ line) create it with `orca worktree create --name <slug> --parent-worktree
291
+ active --json` and track it with `orca worktree ps --json` or `tezgah-status
292
+ --orca` - a raw `git worktree add` gets no card, terminal tracking or agent
293
+ status in Orca; outside Orca, `git worktree add`. Either way the brief names
294
+ absolute paths under that checkout.
283
295
  Routing: `{ROUTE_BIN} "<brief>"` names the worker - tezgah-cheap, -standard or
284
296
  -frontier (overrides for stored data, credentials, security first; then a Jev
285
297
  judgement; the static phase table with no key). A worker that answers
@@ -400,12 +412,30 @@ experiments/variants, or producing a research artifact (report, figure, dataset)
400
412
  It is NOT "where is X defined" or "who calls Y" - that is code discovery and
401
413
  stays on the codegraph index.
402
414
 
415
+ **Lock the line to the question asked.** Restate the user's question verbatim as
416
+ the line's scope (`--ask`) and name what is in it and what is out (`--in`,
417
+ `--out`: state.json `scope`). Every finding answers an in-scope item - a claim's
418
+ `trace` names its `S<n>`, and `{RESEARCH_BIN} check` refuses one that names none -
419
+ and a finding that answers nothing asked is dropped, not reported as a bonus. Do
420
+ not widen the question, add sub-questions or chase an adjacent topic; a tangent
421
+ worth having is one line under Open questions for the user to pick. The question
422
+ picks the method, not the tooling: a qualitative, conceptual or reference question
423
+ is a source review (`--method qualitative`: no metric, no experiment), and a
424
+ metric, a locked evaluation and the experiment tree belong only to a question
425
+ whose answer is a measurement (`--method measured`). A plain factual or reference
426
+ question gets no line at all: answer it in the reply from primary sources. Depth
427
+ is the primary source read, not more breadth - the spec, the paper, the code, the
428
+ vendor's own documentation - each finding citing the source it rests on and
429
+ stating its confidence (high, medium or low, and why); a summary of summaries is
430
+ not an answer.
431
+
403
432
  When the task is research and the OpenResearch CLI (`{ORX_BIN}`) is installed,
404
433
  drive it through that CLI instead of ad-hoc local scripting. Load the operating
405
434
  manual first: the `orx` skill if the host has it, otherwise run
406
- `{ORX_BIN} skill` from the shell; then
407
- the named modules (`{ORX_BIN} skill experiment-tree`, `{ORX_BIN} skill lit-review`,
408
- `{ORX_BIN} skill evidence`, ...). Its cardinal rules are not style preferences - they
435
+ `{ORX_BIN} skill` from the shell; then the module the method needs -
436
+ `{ORX_BIN} skill lit-review` for sources, `{ORX_BIN} skill experiment-tree` and
437
+ `{ORX_BIN} skill evidence` for a measured line. On a measured line its cardinal
438
+ rules are not style preferences - they
409
439
  are what keeps results comparable, and breaking one silently invalidates the run:
410
440
  never edit a node once a run has answered it (branch a child instead); the run
411
441
  command and environment are a fixed contract identical on every node; vary the
@@ -420,7 +450,8 @@ afterwards - and `{RESEARCH_BIN} check` refuses a claim that claims a wider scop
420
450
  than the rows it rests on.
421
451
 
422
452
  If the CLI is not installed, say the research tooling is unavailable and do not
423
- improvise its protocol; fall back to a host subagent and say so. Kill switch:
453
+ improvise its protocol; fall back to a host subagent and say so, briefed with the
454
+ question verbatim, its in- and out-of-scope items and its method. Kill switch:
424
455
  `research-off`.
425
456
 
426
457
  Keep the line auditable in the repository, not in your head:
@@ -437,11 +468,13 @@ into a refusal. A report owes an independent review (`reviewer` is not the
437
468
  construct and conclusion validity threats, whatever `phase` says. `state.json`
438
469
  `deliverable` names what the line delivers and lists the ask's items, each
439
470
  answered in the report, a decision or a claim `trace`, or written `A<n> not
440
- delivered: <reason>`. A design, plan or analysis is produced as >= 3 variants in
471
+ delivered: <reason>`. A design or plan the user will choose between is produced
472
+ as >= 3 variants in
441
473
  `decisions/<id>/` - `criteria.json` committed before any cell, `variants.jsonl`
442
474
  (V0 the status quo), cells appended by `{RESEARCH_BIN} compare`, `decision.md`
443
475
  naming the chosen id, each rejected id with the criterion that ruled it out, and a
444
- `flip:` condition; a new version of a line is opened `init --supersedes <old>` and
476
+ `flip:` condition (an analysis answers its question once); a new version of a
477
+ line is opened `init --supersedes <old>` and
445
478
  carries the old one as a variant. Code or config variants are sibling orx nodes
446
479
  under one head node. Literature is found with `{ORX_BIN} discover` and read with
447
480
  `{ORX_BIN} paper`; an agent's summary is indexed as `agent-report` and never alone
@@ -602,7 +635,7 @@ prefers shell tools; that preference does NOT cover definitions, callers, or
602
635
  blast radius - those go to the graph. The graph answers from a parsed call
603
636
  graph, so it beats text search on renames, dynamic dispatch and cross-file
604
637
  callers. On Claude a PreToolUse hook denies the Explore subagent here and nudges
605
- the first identifier-shaped Grep per session toward the graph. The MCP tool's
638
+ the first code-symbol search per session toward the graph. The MCP tool's
606
639
  schema is deferred: on the FIRST code-discovery step of the session load it -
607
640
  ToolSearch("select:mcp__codegraph__codegraph_explore,mcp__plugin_tezgah_codegraph__codegraph_explore")
608
641
  (the plain name on a checkout's own server, the `plugin_tezgah_` one on a plugin
@@ -645,27 +678,40 @@ it - no apology theater, no self-justifying phrasing.
645
678
  **Ponytail (minimal code).** Laziest solution that works: YAGNI -> reuse an
646
679
  existing helper -> stdlib -> native platform feature -> installed dependency ->
647
680
  one line -> minimum code. No unrequested abstractions, no scaffolding "for
648
- later", shortest working diff. Trace the problem fully before climbing; never
649
- simplify away validation, error handling, security or anything requested. Bug
650
- fix = root cause where all callers route through. A deliberate corner cut gets
651
- a `ponytail:` comment naming the ceiling. On the first non-trivial coding task,
652
- read the full `ponytail` skill from the router (levels lite|full|ultra, set with
653
- `tezgah-pony`; the level rides this reminder when it is not `full`) - this
654
- paragraph is not the whole contract. Off: "stop ponytail".
681
+ later", deletion over addition, fewest files, shortest working diff. Trace the
682
+ problem fully before climbing; never simplify away validation, error handling,
683
+ security, accessibility or anything requested, and build the full version
684
+ without re-arguing when the user insists. Bug fix = root cause where all
685
+ callers route through. A deliberate corner cut gets a `ponytail:` comment
686
+ naming the ceiling. Non-trivial logic leaves ONE runnable check (an assert
687
+ self-check or one small test); a one-liner needs none. A complex ask ships the
688
+ lazy version and names the fuller one in the same reply instead of stalling.
689
+ Reply: code first, then at most three lines - what was skipped, when to add it.
690
+ In force from the first turn; levels lite|full|ultra are set with `tezgah-pony`
691
+ and ride the per-turn reminder when not `full`; read the full `ponytail` skill
692
+ from the router for the level table and examples. Off: "stop ponytail".
655
693
 
656
694
  **Output shape: ADHD-friendly.** The answer or the next action is on the first
657
- line, prose after it. Multi-step work is a numbered list, one bounded action per
658
- step, and while it is in flight its position is restated in one line - the todo
659
- list is that source, never re-narrate the plan. End with one concrete next step.
660
- Finish the issue in hand before raising a second one; an error states location,
661
- cause and fix with no drama; after a change say what now works. A list shows at
662
- most five items, ranked, the rest kept in reserve rather than dropped. An
695
+ line - a command, path, snippet or the decision itself, never context or the
696
+ question restated - prose after it. Multi-step work is a numbered list, one
697
+ bounded action per step, and while it is in flight its position is restated in
698
+ one line - the todo list is that source, never re-narrate the plan. End with one
699
+ concrete next step. Finish the issue in hand before raising a second one; an
700
+ error states location, cause and fix with no drama, naming a cause only when the
701
+ evidence identifies it (else what is known and what would confirm it); after a
702
+ change say what now works. A list shows at most five items, ranked, the rest
703
+ kept in reserve rather than dropped - presentation only, never the analysis. An
663
704
  estimate is in concrete units and marked as an estimate, never presented as a
664
705
  measurement. No preamble, no recap, no closer, and a question the reader raises
665
- mid-work is answered rather than deferred as the second issue. On the FIRST
666
- non-trivial answer of the session, read the full `i-have-adhd` skill from the
667
- router - this paragraph is not the whole contract. Off: `tezgah-adhd off`, or
668
- the repo's `.no-adhd`.
706
+ mid-work is answered rather than deferred as the second issue. Break the shape
707
+ only where it would delete the answer: an explanation runs as long as it needs,
708
+ with headers; an options question gets two to four ranked options,
709
+ recommendation first; after three "still broken" turns, name the suspect
710
+ assumption and ask one diagnostic question. Before sending, delete an opening
711
+ sentence that announces, a closing one that recaps or asks "anything else?", any
712
+ by-the-way sidebar and any hedge that carries no real uncertainty. In force from
713
+ the first answer; read the full `i-have-adhd` skill from the router for the
714
+ examples. Off: `tezgah-adhd off`, or the repo's `.no-adhd`.
669
715
 
670
716
  **Deliver the whole ask; never the shortcut.** The request defines the
671
717
  deliverable: every named item is in scope until the user says otherwise, and the
@@ -756,19 +802,30 @@ Off: `consult-off`.
756
802
 
757
803
  **Research: route it to OpenResearch.** When a task is research - a literature
758
804
  or reference review, forming and testing hypotheses, running or comparing
759
- experiments, producing a research artifact - drive it through the OpenResearch
760
- CLI (`{ORX_BIN}`) and load its manual first (`{ORX_BIN} skill`), following its
761
- experiment-tree rules instead of improvising the protocol. Plain code discovery
762
- stays on the code graph, not OpenResearch. At most one line is open at a time:
805
+ experiments, producing a research artifact - lock it to the question asked:
806
+ restate the user's question verbatim as the scope, name what is in and out of it
807
+ (`init --ask --in --out`), and drop every finding that does not answer an
808
+ in-scope item; never widen it or chase an adjacent topic. The question picks the
809
+ method: a qualitative or reference question is a source review (`--method
810
+ qualitative`, no metric, no experiment); metrics and the experiment tree only
811
+ when the answer is a measurement (`--method measured`). A plain factual or
812
+ reference question gets no line and no orx experiment: answer it in the reply.
813
+ Depth means primary sources read and cited, each finding with its confidence,
814
+ not a thin summary. Drive the work through the OpenResearch CLI (`{ORX_BIN}`)
815
+ and load its manual first (`{ORX_BIN} skill`; `lit-review` for sources,
816
+ `experiment-tree` for a measured line) instead of improvising the protocol.
817
+ Plain code discovery stays on the code graph, not OpenResearch.
818
+ At most one line is open at a time:
763
819
  while one is under way - not `concluded`, or carrying a live proposal, an unrun
764
820
  experiment, a missing report or review, an unlabelled review finding - no new line
765
821
  opens until it is closed or `{RESEARCH_BIN} close <slug> --limit "<reason>"` records
766
822
  what is left. `{RESEARCH_BIN} init` refuses on that rule and names each open line;
767
823
  `--allow-open "<reason>"` passes it (never beside a line `check` fails) and logs why.
768
- A plan, design or analysis is >= 3 variants compared under criteria committed
769
- first (`decisions/`); one version is not a finished line.{OPEN_LINES}
824
+ A design or plan the user will choose between is >= 3 variants compared under
825
+ criteria committed first (`decisions/`); an analysis answers its question once.{OPEN_LINES}
770
826
  If the CLI is not installed, say the research tooling is unavailable and fall
771
- back to a host subagent. Off: `research-off`.
827
+ back to a host subagent briefed with the question verbatim, its in/out scope and
828
+ its method. Off: `research-off`.
772
829
 
773
830
  **Product analysis: five axes, one evidence class per finding.** A product
774
831
  question - "analyse this product", "why is retention falling", "which feature
@@ -826,7 +883,9 @@ Never install, upgrade, restart or kill anything for tezgah, and never open an
826
883
  issue for one of its tools mid-session. Name a missing capability in one line,
827
884
  use the documented fallback (grep/find, or the second opinion skipped), and carry
828
885
  on with the task in hand. Tezgah maintenance is in scope when the user asks for
829
- it, or when the repo IS the tezgah checkout.
886
+ it, or when the repo IS the tezgah checkout. Before working a topic an
887
+ installed skill covers, run `tezgah-skill <words>` and read only the range it
888
+ returns.
830
889
 
831
890
  **Workspace: `.tezgah/` only.** Every per-project tezgah artifact - plans,
832
891
  research lines, analysis, lessons - lives under `<repo>/.tezgah/`, which the
@@ -835,6 +894,21 @@ project's `.gitignore` excludes and which keeps its own private git repository
835
894
  project root and never `git add -f` anything under `.tezgah/`. Off:
836
895
  `workspace-off`.
837
896
 
897
+ **Parallel by default: fan out independent items.** A request with two or
898
+ more independent items - a numbered or bulleted list, several files or areas, a
899
+ todo list whose entries share no file and no interface - is delegated without
900
+ being asked: one subagent per item, all spawned in ONE message, while the main
901
+ thread decomposes, integrates, verifies and reports. Every writing slice gets
902
+ its own branch and checkout - inside Orca `orca worktree create --name <slug>
903
+ --parent-worktree active --json`, else `git worktree add -b <branch> <absolute
904
+ path>`; its brief names every read, edit and shell `cwd` as an absolute path
905
+ under that checkout, never the main checkout, whose `git status --short` must
906
+ not grow. Read-only slices need no worktree. Pick each agent by task class from
907
+ the specialists line; `{ROUTE_BIN}` names the tier for code work. Serialize
908
+ only a real dependency - one slice's output is another's input, or two slices
909
+ edit one file - and say which. Never wait idle on a spawned slice while
910
+ independent work remains. Off: `orchestrate-off`.
911
+
838
912
  **Kill switches:** each one removes its own rule from this text, not just the
839
913
  status mark. `~/.config/tezgah/`: `exec-mode.off`, `orchestrate-off`,
840
914
  `consult-off`, `research-off`, `ponytail-auto.off`, `adhd-off`, `spec-off`,
@@ -852,13 +926,13 @@ The ponytail intensity level is not a switch: `tezgah-pony lite|full|ultra`.
852
926
  # time (hooks/tezgah_context.classify_prompt), because a session that never asks
853
927
  # a structural or research question should not carry their text. Keys match the
854
928
  # CORE_RULES labels in hooks/tezgah_context.py.
855
- CONDITIONAL_KEYS = ("spec", "consult", "research", "product", "graph")
929
+ CONDITIONAL_KEYS = ("spec", "consult", "research", "product", "graph", "fanout")
856
930
 
857
931
  # The always-on replacement for the conditional paragraphs: one line each so a
858
932
  # host without a per-turn hook still knows the rule exists and where the full
859
933
  # text lives.
860
934
  POINTERS = """
861
- **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.
935
+ **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.
862
936
  """
863
937
 
864
938
  # The compact per-turn form, inside the <harness-reminder> envelope the hosts