@azure-id/orc 1.9.2 → 2.0.2

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 (172) hide show
  1. package/CHANGELOG.md +321 -0
  2. package/README-id.md +21 -36
  3. package/README.md +25 -36
  4. package/bin/build-agents.js +117 -20
  5. package/bin/cli.js +725 -29
  6. package/bin/gotcha-import.js +1081 -0
  7. package/bin/gotcha.js +1286 -0
  8. package/bin/graph-query.js +1 -1
  9. package/bin/graph.js +717 -717
  10. package/bin/habit.js +1453 -0
  11. package/bin/mockrun-catalog.js +281 -276
  12. package/bin/run-undo.js +398 -0
  13. package/bin/trace-write.js +657 -0
  14. package/bin/verify-contracts.js +525 -79
  15. package/bin/verify-package.js +51 -4
  16. package/bin/webui/api.js +26 -0
  17. package/bin/webui/app.html +239 -232
  18. package/bin/webui/css/00-tokens.css +110 -92
  19. package/bin/webui/css/04-motion.css +87 -0
  20. package/bin/webui/css/06-responsive.css +203 -178
  21. package/bin/webui/css/panels/behaviour.css +205 -0
  22. package/bin/webui/fixtures/behaviour.js +532 -0
  23. package/bin/webui/fixtures/index.js +34 -0
  24. package/bin/webui/fixtures/knowledge.js +7 -1
  25. package/bin/webui/fixtures/stats.js +16 -0
  26. package/bin/webui/i18n/en/behaviour.json +143 -0
  27. package/bin/webui/i18n/en/nav.json +25 -24
  28. package/bin/webui/i18n/en/tour.json +37 -35
  29. package/bin/webui/i18n/id/behaviour.json +143 -0
  30. package/bin/webui/i18n/id/nav.json +25 -24
  31. package/bin/webui/i18n/id/tour.json +37 -35
  32. package/bin/webui/js/01-i18n.js +155 -154
  33. package/bin/webui/js/90-tour.js +498 -494
  34. package/bin/webui/js/91-shortcuts.js +126 -126
  35. package/bin/webui/js/99-boot.js +121 -118
  36. package/bin/webui/js/panels/behaviour.js +1022 -0
  37. package/mock-run/INDEX.md +109 -107
  38. package/mock-run/gotcha-import.md +118 -0
  39. package/mock-run/habits.md +129 -0
  40. package/mock-run/orc-quick.md +6 -1
  41. package/package.json +1 -1
  42. package/templates/agents/MODEL-MAPPING.md +7 -7
  43. package/templates/agents/orc-advisor-opus-5-xhigh.md +1 -7
  44. package/templates/agents/orc-analyze-mini-opus-5-med.md +1 -6
  45. package/templates/agents/orc-analyze-mini-sonnet-5-high.md +1 -4
  46. package/templates/agents/orc-claude-writer-opus-4-8-high.md +48 -53
  47. package/templates/agents/orc-claude-writer-opus-5-med.md +1 -8
  48. package/templates/agents/orc-context-combiner-opus-5-high.md +1 -11
  49. package/templates/agents/orc-executor-haiku-4-5.md +14 -6
  50. package/templates/agents/orc-executor-opus-4-7-high.md +14 -6
  51. package/templates/agents/orc-executor-opus-4-7-med.md +14 -6
  52. package/templates/agents/orc-executor-opus-4-8-high.md +14 -6
  53. package/templates/agents/orc-executor-opus-5-high.md +14 -6
  54. package/templates/agents/orc-executor-opus-5-low.md +14 -6
  55. package/templates/agents/orc-executor-opus-5-med.md +14 -6
  56. package/templates/agents/orc-executor-sonnet-4-6-high.md +14 -6
  57. package/templates/agents/orc-executor-sonnet-4-6-med.md +14 -6
  58. package/templates/agents/orc-executor-sonnet-5-high.md +14 -6
  59. package/templates/agents/orc-graph-noter-sonnet-4-6-med.md +1 -10
  60. package/templates/agents/orc-judge-opus-5-xhigh.md +5 -9
  61. package/templates/agents/orc-learn-writer-opus-5-low.md +1 -7
  62. package/templates/agents/orc-pattern-codifier-opus-5-med.md +1 -8
  63. package/templates/agents/orc-pattern-codifier-sonnet-5-high.md +58 -63
  64. package/templates/agents/orc-planner-mini-opus-5-med.md +1 -4
  65. package/templates/agents/orc-planner-mini-sonnet-5-high.md +1 -2
  66. package/templates/agents/orc-planner-opus-5-med.md +1 -4
  67. package/templates/agents/orc-recon-opus-5-low.md +1 -8
  68. package/templates/agents/orc-recon-sonnet-4-6-med.md +1 -8
  69. package/templates/agents/orc-retro-opus-5-med.md +6 -8
  70. package/templates/agents/orc-retro-sonnet-5-high.md +6 -7
  71. package/templates/agents/orc-reviewer-opus-5-med.md +52 -16
  72. package/templates/agents/orc-scout-opus-5-low.md +1 -6
  73. package/templates/agents/orc-scout-sonnet-4-6-high.md +35 -39
  74. package/templates/agents/orc-system-analyst-opus-5-high.md +1 -6
  75. package/templates/agents/orc-test-author-opus-5-med.md +4 -5
  76. package/templates/agents/orc-trace-writer-haiku-4-5.md +3 -7
  77. package/templates/agents/orc-verifier-opus-5-med.md +16 -8
  78. package/templates/agents/orc-wiki-scanner-opus-4-8-high.md +74 -79
  79. package/templates/agents/orc-wiki-scanner-opus-5-med.md +1 -8
  80. package/templates/agents/orc-wiki-scanner-sonnet-5-high.md +97 -106
  81. package/templates/commands/orc-analyze.md +13 -21
  82. package/templates/commands/orc-fast.md +10 -15
  83. package/templates/commands/orc-poly.md +12 -21
  84. package/templates/commands/orc-pr-driver.md +11 -30
  85. package/templates/commands/orc-pr-setup.md +10 -31
  86. package/templates/commands/orc-route.md +11 -41
  87. package/templates/commands/orc-test.md +5 -60
  88. package/templates/hooks/README.md +34 -0
  89. package/templates/hooks/orc-session-hook.js +264 -0
  90. package/templates/hooks/orc-statusline.js +3 -1
  91. package/templates/skills/_shared/README.md +9 -0
  92. package/templates/skills/_shared/code-graph.md +47 -55
  93. package/templates/skills/_shared/config-precedence.md +3 -1
  94. package/templates/skills/_shared/extra-dispatch.md +73 -88
  95. package/templates/skills/_shared/gotchas.md +228 -177
  96. package/templates/skills/_shared/habits.md +101 -0
  97. package/templates/skills/_shared/lane-contract.md +84 -0
  98. package/templates/skills/_shared/phases/README.md +142 -83
  99. package/templates/skills/_shared/phases/analyst-gates.md +10 -21
  100. package/templates/skills/_shared/phases/execution.md +8 -14
  101. package/templates/skills/_shared/phases/house-rules.md +27 -32
  102. package/templates/skills/_shared/phases/intake.md +127 -133
  103. package/templates/skills/_shared/phases/mock-example.md +46 -56
  104. package/templates/skills/_shared/phases/plan-handoff.md +91 -97
  105. package/templates/skills/_shared/phases/planning.md +7 -17
  106. package/templates/skills/_shared/phases/preflight.md +19 -42
  107. package/templates/skills/_shared/phases/review.md +23 -27
  108. package/templates/skills/_shared/phases/rules.md +18 -42
  109. package/templates/skills/_shared/phases/scoring.md +55 -65
  110. package/templates/skills/_shared/phases/security-checklist.md +46 -50
  111. package/templates/skills/_shared/phases/security.md +45 -55
  112. package/templates/skills/_shared/phases/ship.md +6 -15
  113. package/templates/skills/_shared/phases/stop-resume.md +2 -5
  114. package/templates/skills/_shared/phases/summary.md +73 -48
  115. package/templates/skills/_shared/phases/testgen.md +41 -51
  116. package/templates/skills/_shared/phases/trace-verbs.md +433 -0
  117. package/templates/skills/_shared/phases/trace.md +136 -367
  118. package/templates/skills/_shared/phases/verify.md +62 -70
  119. package/templates/skills/_shared/phases/wave-grouping.md +128 -133
  120. package/templates/skills/_shared/phases/wiki-consult.md +10 -6
  121. package/templates/skills/_shared/read-ladder.md +2 -55
  122. package/templates/skills/_shared/return-validation.md +17 -70
  123. package/templates/skills/_shared/review-slice.md +79 -0
  124. package/templates/skills/_shared/smoke-gate.md +46 -28
  125. package/templates/skills/context-combiner/SKILL.md +15 -44
  126. package/templates/skills/orc/SKILL.md +34 -62
  127. package/templates/skills/orc/references/pattern-gate.md +89 -89
  128. package/templates/skills/orc/references/phases/intake.md +41 -47
  129. package/templates/skills/orc/references/phases/integration.md +13 -19
  130. package/templates/skills/orc/references/preflight-report.md +8 -9
  131. package/templates/skills/orc/references/ultra-mode.md +8 -6
  132. package/templates/skills/orc/subskills/orc-execution/SKILL.md +27 -73
  133. package/templates/skills/orc/subskills/orc-execution/core.md +12 -99
  134. package/templates/skills/orc/subskills/orc-execution/subagent.md +14 -13
  135. package/templates/skills/orc/subskills/orc-review-verify/SKILL.md +11 -52
  136. package/templates/skills/orc/subskills/orc-review-verify/core.md +52 -135
  137. package/templates/skills/orc/subskills/orc-review-verify/subagent.md +7 -7
  138. package/templates/skills/orc/subskills/orc-testgen/SKILL.md +11 -22
  139. package/templates/skills/orc/subskills/orc-testgen/core.md +20 -59
  140. package/templates/skills/orc/subskills/orc-testgen/subagent.md +7 -7
  141. package/templates/skills/orc-advisor/SKILL.md +56 -60
  142. package/templates/skills/orc-analyze/SKILL.md +30 -66
  143. package/templates/skills/orc-analyze/schemas/report-audit.md +2 -1
  144. package/templates/skills/orc-analyze/schemas/report-prose.md +2 -1
  145. package/templates/skills/orc-analyze-mini/SKILL.md +35 -70
  146. package/templates/skills/orc-diy/README.md +31 -0
  147. package/templates/skills/orc-diy/SKILL.md +23 -74
  148. package/templates/skills/orc-diy/references/blocks/pattern.md +18 -18
  149. package/templates/skills/orc-fast/SKILL.md +44 -72
  150. package/templates/skills/orc-judge/SKILL.md +77 -82
  151. package/templates/skills/orc-mini/SKILL.md +61 -109
  152. package/templates/skills/orc-pattern/SKILL.md +27 -48
  153. package/templates/skills/orc-poly/SKILL.md +32 -61
  154. package/templates/skills/orc-pr-driver/SKILL.md +25 -51
  155. package/templates/skills/orc-pr-driver/references/green-gate.md +113 -105
  156. package/templates/skills/orc-pr-setup/SKILL.md +20 -45
  157. package/templates/skills/orc-quick/README.md +43 -2
  158. package/templates/skills/orc-quick/SKILL.md +76 -107
  159. package/templates/skills/orc-quick/references/dispatch-gate.md +16 -5
  160. package/templates/skills/orc-quick/references/gh-mode.md +48 -1
  161. package/templates/skills/orc-quick/references/look.md +3 -1
  162. package/templates/skills/orc-retro/SKILL.md +19 -18
  163. package/templates/skills/orc-retro/examples/retro-mock.md +1 -1
  164. package/templates/skills/orc-route/SKILL.md +25 -45
  165. package/templates/skills/orc-test/SKILL.md +16 -37
  166. package/templates/skills/orc-verify/SKILL.md +14 -32
  167. package/templates/skills/orc-wait/SKILL.md +156 -163
  168. package/templates/skills/orc-wiki/references/phases/phase-0.md +1 -6
  169. package/templates/skills/orc-wiki/references/phases/phase-1.md +1 -6
  170. package/templates/skills/orc-wiki/references/phases/phase-2.md +1 -6
  171. package/templates/skills/orc-wiki/references/phases/phase-3.md +1 -6
  172. package/templates/skills/orc-wiki/references/phases/phase-3c.md +1 -6
@@ -1,30 +1,11 @@
1
- ---
2
- description: Stacked-PR driver — execute a stack plan: one branch per layer, mandatory per-layer green gate, gh stack submit, then sync/rebase/bottom-up merge
3
- ---
4
-
5
- Run the **orc-pr-driver** skill. It takes an approved stack plan and makes the
6
- layers real: `gh stack init` → per layer `gh stack add`, only that layer's files,
7
- the **mandatory green-gate ladder** (build → tests → lint scoped to *that layer's
8
- own base* → the repo's own pre-commit hooks, never `--no-verify`) → commit →
9
- `gh stack submit`, PR bodies filled from the resolved template, then the ongoing
10
- `gh stack sync` / `rebase --upstack` / `modify` care and the bottom-up merge.
11
-
12
- **Three ways in — all read the same file** (`stacked-pr/<slug>/stack-plan.md`,
13
- probed with `orc pr stack status`):
14
-
15
- 1. `/orc-pr-setup` planned it (the normal path),
16
- 2. ORC's ship phase handed it over (`stacked-pr/<slug>/STACK-FROM.md`),
17
- 3. **you wrote the plan yourself** — run `orc pr stack template` for the skeleton,
18
- fill it in, and start here. No planner run required.
19
-
20
- It **refuses to run** — naming the exact missing field — on a plan with an
21
- unanswered uncertain seam, a missing ticket, a layer with no purpose or value
22
- class, a FOUNDATION layer that names no consumer, fewer than 2 layers, or a red
23
- build. It never fills a field in for you.
24
-
25
- When the change already exists in the worktree, your work is snapshotted to a
26
- scratch branch BEFORE any branch surgery, every layer is materialized from that
27
- snapshot file-by-file, and a completeness gate proves the union of the layers
28
- equals the snapshot exactly before anything is submitted.
29
-
30
- Slug / plan path / stack action: $ARGUMENTS
1
+ ---
2
+ description: Stacked-PR driver — execute a stack plan: one branch per layer, mandatory per-layer green gate, gh stack submit, then sync/rebase/bottom-up merge
3
+ ---
4
+
5
+ Run the **orc-pr-driver** skill. It executes `stacked-pr/<slug>/stack-plan.md`
6
+ (probed with `orc pr stack status`; written by `/orc-pr-setup`, by you, or
7
+ handed over as `STACK-FROM.md`): one branch per layer, a mandatory green gate
8
+ per layer, then `gh stack submit` and the merge care. It refuses a plan with an
9
+ open field or a red build, and never fills a field in for you.
10
+
11
+ Slug / plan path / stack action: $ARGUMENTS
@@ -1,31 +1,10 @@
1
- ---
2
- description: Stacked-PR planner — decide where the PR cut lines go, prove each layer stands alone, write stacked-pr/<slug>/stack-plan.md (plans only, never touches git)
3
- ---
4
-
5
- Run the **orc-pr-setup** skill. It plans a **stack of pull requests** for a
6
- change that is too big to review as one PR: ordered layers, each with a one-line
7
- purpose, a value class (`USER | OPERATOR | CONTRACT | FOUNDATION`), an explicit
8
- file list, measured LoC/file budgets and a dependency reason — written to
9
- `stacked-pr/<slug>/stack-plan.md`.
10
-
11
- It **plans only**: no branches, no commits, no pushes, no PRs. That is
12
- `/orc-pr-driver`.
13
-
14
- Two entry modes, auto-detected: **greenfield** (nothing written yet — a spec or
15
- ticket) and **orc-run** (the change already exists in the worktree, e.g. ORC just
16
- built it — the split is file-granular; hunk surgery is forbidden). A ticket
17
- number is required. The PR-description template is resolved from the ORC template,
18
- then the project (`.github/`, `docs/`), then a `CLAUDE.md` section — and if none
19
- exists you get three recommended options to pick from; declining them all means
20
- the stack is skipped and the change ships as one regular PR.
21
-
22
- **P0 hard gate:** every uncertain boundary STOPS the lane and asks you, one
23
- decision at a time, with the LoC/file/CI cost of each option and a recommendation
24
- — and records your answer under `## Decisions`. Same-tier files, shared helpers,
25
- refactor mixed with behavior change, ordering ambiguity and oversize atoms are all
26
- uncertain by definition. It never guesses a seam.
27
-
28
- When the plan is written it shows the layer table, states that nothing has been
29
- created yet, and hands off to `/orc-pr-driver`.
30
-
31
- Ticket / change / spec: $ARGUMENTS
1
+ ---
2
+ description: Stacked-PR planner — decide where the PR cut lines go, prove each layer stands alone, write stacked-pr/<slug>/stack-plan.md (plans only, never touches git)
3
+ ---
4
+
5
+ Run the **orc-pr-setup** skill. It plans a stack of pull requests for a change
6
+ too big to review as one, and writes `stacked-pr/<slug>/stack-plan.md`. It plans
7
+ only: no branches, commits, pushes or PRs — that is `/orc-pr-driver`. A ticket
8
+ is required. Every uncertain boundary STOPS and asks you, one decision at a time.
9
+
10
+ Ticket / change / spec: $ARGUMENTS
@@ -1,41 +1,11 @@
1
- ---
2
- description: You have a plan — this says which lane should build it, with the numbers it decided from. Plan-only: it refuses a request in words rather than guess
3
- ---
4
-
5
- Use the **orc-route** skill. Zero agents, nothing is built.
6
-
7
- **It routes a PLAN, and only a plan** — pasted ORC planning-output, a
8
- `plan-<name>.md` path, or a saved `orc/planner/<name>/` checkpoint (the same
9
- definition `skills/_shared/phases/plan-handoff.md` already uses). A plan carries real
10
- numbers: tasks, files per task, dependencies, facets, scores. Routing from those
11
- is arithmetic; routing from a sentence is guessing, so a request in words gets a
12
- refusal and a pointer to `/orc-plan`, not a guess.
13
-
14
- It reads the plan plus ORC's deterministic probes (`orc wiki status`,
15
- `orc pattern status`, `orc gotcha status`, `orc diy status`) and answers:
16
-
17
- ```
18
- Plan: merchant-notifications — 7 tasks, 3 waves, 14 files touched
19
- top score 78, two tasks marked risky
20
-
21
- → /orc the plan has risky tasks and a task above 70;
22
- review and verify are worth paying for here
23
- runner-up /orc-mini — about 3x faster, but it skips full review
24
- and verification. Fine only if you will read the diff yourself.
25
- not possible /orc-fast — needs a fresh wiki (yours is STALE) and this plan
26
- is 7 tasks; that lane runs ONE task
27
-
28
- Start /orc now? [yes / no]
29
- ```
30
-
31
- Every runner-up says what choosing it costs you. Every impossible lane names the
32
- condition blocking it **and** how to fix it. Risk beats size: a small plan with a
33
- cited risk still earns the full lane.
34
-
35
- `/orc-plan` also offers this automatically after **Save & stop**, so you rarely
36
- need to type it.
37
-
38
- New to ORC and wondering which command to use at all? That is
39
- `orc onboarding first-run`, not this.
40
-
41
- Plan (paste it, or give a path): $ARGUMENTS
1
+ ---
2
+ description: You have a plan — this says which lane should build it, with the numbers it decided from. Plan-only: it refuses a request in words rather than guess
3
+ ---
4
+
5
+ Use the **orc-route** skill. Zero agents, nothing is built. It routes a PLAN
6
+ only — the same definition `skills/_shared/phases/plan-handoff.md` uses. It reads
7
+ the plan's tasks, files, deps, `facets` and scores plus ORC's probes, then names
8
+ the lane, the runner-up and its cost, and each impossible lane with its fix. A
9
+ request in words gets a pointer to `/orc-plan`, never a guess.
10
+
11
+ Plan (paste it, or give a path): $ARGUMENTS
@@ -2,65 +2,10 @@
2
2
  description: Run the tests against the real running system — happy path to security — and report only what was observed
3
3
  ---
4
4
 
5
- Use the **orc-test** skill. Standalone — no plan, no build, no code written into
6
- your project.
7
-
8
- Every other ORC lane reasons about your system. This one **runs it**:
9
-
10
- > **A test that ran is a FACT; a test that was written is an OPINION.**
11
-
12
- ORC already has a lane that WRITES tests and never runs them (Phase 6.5,
13
- `test-generator/`). This is the mirror image, and the two never mix: `/orc-test`
14
- writes nothing into your test tree and runs everything.
15
-
16
- **It never edits the system it is testing.** If the server will not come up, it
17
- prints exactly what it ran and the tail of the output, and hands back. You fix
18
- it, and re-run for free — the same shape as `/orc-challenge`, and for the same
19
- reason: a session that just fixed the code cannot be trusted to measure it.
20
-
21
- **It never reports a result it did not observe.** Three verdicts and no fourth:
22
- `pass` (observed, matched), `fail` (observed, did not match), and `unknown` —
23
- not observed. `unknown` keeps its slot in the report and never becomes a pass.
24
- A flake is recorded, never retried away: there is no retry count, because if the
25
- same case answers differently twice, *that instability is the finding*.
26
-
27
- One pass:
28
-
29
- 1. **Target** — `orc test init <slug>` asks what it must not guess: back end or
30
- front end, local or remote, the base URL. On a remote target it also requires
31
- `--authorized "<who authorized this, and where it is recorded>"`, stored
32
- verbatim and reprinted at the head of every report. ORC cannot verify
33
- authorization and does not pretend to — it makes the assertion impossible to
34
- skip. Frozen, and never asked again.
35
- 2. **Surface** — free, deterministic: an OpenAPI or GraphQL spec on disk, the
36
- same spec at the target, route extraction by framework fingerprint, FE router
37
- config. Anything ambiguous comes back as `unresolved[]` and is never guessed.
38
- The **code-vs-live diff** is a first-class output: routes that answer live and
39
- exist in no source file are zombie APIs.
40
- 3. **Flow** — it digs the repo for how a request reaches your endpoint: the
41
- route registration, the middleware in order, the handler, what it writes, what
42
- must be true first, which fields name an object, and the login flow itself.
43
- 4. **Environment** (local only) — it brings the system up and waits for health.
44
- Five states, one next action each.
45
- 5. **Cases** — the CLI derives the bulk for free: equivalence partitions,
46
- boundary values, method and content-type negatives, stateful create → read →
47
- update → delete sequences. An agent fills only what a schema cannot know.
48
- 6. **Run** — paced, capped, and fenced to the frozen origin. A 429 is a RESULT,
49
- not an error: it means rate limiting works. Every case leaves its exact
50
- request, its exact response and a reproducible `curl`, with every credential
51
- redacted **before the bytes reach disk**.
52
- 7. **Report** — `orc/orc-test/<slug>/REPORT.md`, written for someone who does
53
- not read code.
54
-
55
- The security tier is a **closed set** mapped to the OWASP API Security Top 10
56
- (2023), and it demonstrates a CONDITION rather than an extraction — confirming
57
- an exploit is your decision on your authority. With one identity, BOLA and BFLA
58
- report `UNCHECKABLE`, which keeps its slot and never becomes a pass.
59
-
60
- **The run folder is never staged.** Evidence contains real response bodies from
61
- a real system. The lane offers you the one `.gitignore` line; it does not edit
62
- `.gitignore` itself.
63
-
64
- Read the state back any time without this lane: `orc test status <slug>`.
5
+ Use the **orc-test** skill. Standalone: no plan, no build, no code written into
6
+ your project. It RUNS the system; Phase 6.5 (`test-generator/`) writes tests and
7
+ never runs them. It never edits the system under test, and it never reports a
8
+ result it did not observe. The report is `orc/orc-test/<slug>/REPORT.md`; the
9
+ run folder is never staged. Read the state back with `orc test status <slug>`.
65
10
 
66
11
  The slug to open or reopen (or nothing, and it will ask): $ARGUMENTS
@@ -414,6 +414,40 @@ outside an ORC run, and never when the map does not exist.
414
414
 
415
415
  ---
416
416
 
417
+ ## The session hook
418
+
419
+ This is a fourth hook, `orc-session-hook.js`. It has four jobs. All of them are
420
+ silent when no ORC run is open.
421
+
422
+ - **The bell.** A turn of an ORC run ends, and your terminal rings once. This
423
+ job is off until you turn it on:
424
+
425
+ ```
426
+ orc config set notify bell # ring when a turn of a run ends
427
+ orc config set notify off # silent (the default)
428
+ ```
429
+
430
+ It rings only when the run moved since the last bell. A normal chat never
431
+ rings. It sends no OS notification and uses no network.
432
+ - **After a compaction.** Claude Code keeps only the start of each skill when it
433
+ compacts a long session, so a long run can lose its place. When a run is in
434
+ flight, the hook adds ONE line: the run name, and the `state-of-play.md` file
435
+ to read first. It costs about 40 tokens, once per compaction.
436
+
437
+ - **The narration guard** (v2.0.2). A run dispatched agents but wrote no trace
438
+ line of its own. Then the hook stops the turn ONCE and says which command
439
+ writes the trace. It never stops twice, and never stops a subagent.
440
+ - **The review card** (v2.0.2). When a reviewer, verifier or judge starts, the
441
+ hook asks `orc gotcha card` for the files git sees as changed and gives the
442
+ card to that agent. No match → nothing. This is why a review always sees the
443
+ mistakes this project already made, even if the lane forgot the card.
444
+
445
+ It never writes model text, and it blocks a stop only for the narration guard.
446
+ `orc doctor` tells you whether all three events are wired (`Stop`,
447
+ `SessionStart compact`, `SubagentStart`).
448
+
449
+ ---
450
+
417
451
  ## For maintainers
418
452
 
419
453
  - The hook is `orc-statusline.js`. `orc init` installs it and wires it into
@@ -0,0 +1,264 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ /**
5
+ * ORC session hook (v2.0.0) — two main-session events, one file.
6
+ *
7
+ * Stop (Q8) the terminal bell when a turn of an ORC run
8
+ * ends. Config `notify`: `off` (the default) · `bell`.
9
+ * SessionStart `compact` (Q9) ONE line after a compaction, while a run is in
10
+ * flight, so the session is told where its state is.
11
+ *
12
+ * Both halves say NOTHING outside an open ORC run. A normal chat never rings
13
+ * and never gets a line. No OS notification, no network, no model text.
14
+ *
15
+ * An open run = the `.current` pointer in `log_dir` exists and is fresh (the
16
+ * same 6-hour window as orc-trace.js and orc-read-gate.js — one idea of "a run
17
+ * is open", not three). The lanes delete `.current` at FINISH.
18
+ *
19
+ * Wiring (installed by `orc init` into .claude/settings.json):
20
+ * hooks.Stop[] { hooks:[{command:"node <..>/orc-session-hook.js"}] }
21
+ * hooks.SessionStart[] { matcher:"compact", hooks:[{command:"node <..>/orc-session-hook.js"}] }
22
+ *
23
+ * Contract: read hook JSON from stdin. ALWAYS exit 0, silent on any error. It
24
+ * blocks a stop in ONE case only (v2.0.2, the narration guard below): the run in
25
+ * this session dispatched agents and wrote no narration line — once per run,
26
+ * never when `stop_hook_active` is set, never for a subagent.
27
+ */
28
+
29
+ const fs = require("fs");
30
+ const path = require("path");
31
+
32
+ // .claude/hooks/orc-session-hook.js → CLAUDE_DIR = .. → PROJECT_ROOT = ../..
33
+ const CLAUDE_DIR = path.join(__dirname, "..");
34
+ const PROJECT_ROOT = path.join(CLAUDE_DIR, "..");
35
+ const STALE_MS = 6 * 60 * 60 * 1000;
36
+ // The canonical trace name — the same regex the CLI's in-flight read uses.
37
+ const TRACE_NAME = /^run-([a-z0-9]+)-(.+)-(\d{6})-(\d{6})\.txt$/;
38
+ const RUN_DIR_DEFAULT = ".claude/orc/run";
39
+
40
+ // Tolerant top-level YAML scalar read, matching orc-read-gate.js exactly.
41
+ function readConfigScalar(key) {
42
+ try {
43
+ const text = fs.readFileSync(path.join(CLAUDE_DIR, "orc.config.yaml"), "utf8");
44
+ const re = new RegExp("^" + key + "\\s*:\\s*(.+?)\\s*(?:#.*)?$", "m");
45
+ const m = text.match(re);
46
+ return m ? m[1].replace(/^['"]|['"]$/g, "").trim() : null;
47
+ } catch (_) {
48
+ return null;
49
+ }
50
+ }
51
+
52
+ function abs(rel) {
53
+ return path.isAbsolute(rel) ? rel : path.join(PROJECT_ROOT, rel);
54
+ }
55
+
56
+ function logDir() {
57
+ return abs(readConfigScalar("log_dir") || ".claude/orc/logs");
58
+ }
59
+
60
+ function mtime(p) {
61
+ try {
62
+ return fs.statSync(p).mtimeMs;
63
+ } catch (_) {
64
+ return null;
65
+ }
66
+ }
67
+
68
+ // The open run, or null. `last` = the newest sign of life (the trace file or
69
+ // the pointer), which the bell compares with the session start.
70
+ function openRun() {
71
+ const dir = logDir();
72
+ let name;
73
+ try {
74
+ name = fs.readFileSync(path.join(dir, ".current"), "utf8").trim();
75
+ } catch (_) {
76
+ return null;
77
+ }
78
+ if (!name) return null;
79
+ const now = Date.now();
80
+ const last = Math.max(mtime(path.join(dir, name)) || 0, mtime(path.join(dir, ".current")) || 0);
81
+ if (!last || now - last >= STALE_MS) return null;
82
+ return { dir, name, last };
83
+ }
84
+
85
+ // ── The narration guard (v2.0.2, eval D9) ────────────────────────────────────
86
+ // A live lane can dispatch agents and end its turn with ZERO narration: the trace
87
+ // holds only the hook's SPAWN/RETURN lines, and the reply still says "FINISH".
88
+ // Then the stats, the retro and the habit engine see nothing. So ONCE per run,
89
+ // in the main session, a stop is blocked with the one command that fixes it.
90
+ // `stop_hook_active` (Claude Code's own re-entry flag) means the lane already
91
+ // had its chance — never block twice.
92
+ function narrationGuard(data) {
93
+ if (data.agent_id || data.stop_hook_active) return false;
94
+ const dir = logDir();
95
+ let born = 0;
96
+ try {
97
+ if (data.transcript_path) born = fs.statSync(data.transcript_path).birthtimeMs || 0;
98
+ } catch (_) {}
99
+ let best = null;
100
+ try {
101
+ for (const f of fs.readdirSync(dir)) {
102
+ if (!TRACE_NAME.test(f)) continue;
103
+ const t = mtime(path.join(dir, f));
104
+ if (t && (!best || t > best.t)) best = { f, t };
105
+ }
106
+ } catch (_) {
107
+ return false;
108
+ }
109
+ if (!best || Date.now() - best.t >= STALE_MS || (born && best.t < born)) return false;
110
+ let text = "";
111
+ try {
112
+ text = fs.readFileSync(path.join(dir, best.f), "utf8");
113
+ } catch (_) {
114
+ return false;
115
+ }
116
+ const lines = text.split(/\r?\n/).filter((l) => /^\[/.test(l));
117
+ if (!lines.some((l) => /\]\s+hook\s+SPAWN /.test(l))) return false; // nothing was dispatched yet
118
+ if (lines.some((l) => !/\]\s+hook\s+/.test(l))) return false; // a packet already landed
119
+ const statePath = path.join(CLAUDE_DIR, "orc", "narration-guard.json");
120
+ try {
121
+ if (JSON.parse(fs.readFileSync(statePath, "utf8")).run === best.f) return false;
122
+ } catch (_) {}
123
+ try {
124
+ fs.mkdirSync(path.dirname(statePath), { recursive: true });
125
+ fs.writeFileSync(statePath, JSON.stringify({ run: best.f, at: Date.now() }) + "\n");
126
+ } catch (_) {}
127
+ process.stdout.write(
128
+ JSON.stringify({
129
+ decision: "block",
130
+ reason:
131
+ `ORC: the trace ${best.f} has agent dispatches but NO narration line — zero new trace lines is a protocol violation. ` +
132
+ `Pipe this run's phase packets (with their REAL event times, and the ASK events if habits is on) to ` +
133
+ `\`orc trace write --packet -\` now (\`run: ${best.f.replace(/\.txt$/, "")}\` if .current is gone), then finish.`,
134
+ })
135
+ );
136
+ return true;
137
+ }
138
+
139
+ // ── The review card at SubagentStart (v2.0.2, eval D13) ─────────────────────
140
+ // A live /orc-quick review dispatched `orc-reviewer-opus-5-med` with NO gotcha
141
+ // card in its prompt in 5 of 5 runs, although review learning is always on. The
142
+ // card is a CLI answer, so the hook hands it over itself: for a reviewer,
143
+ // verifier or judge, the files git sees as changed → `orc gotcha card` → one
144
+ // `additionalContext` block. No match, no CLI, any error → silent.
145
+ const REVIEW_AGENT = /^orc-(reviewer|verifier|judge)-/;
146
+ function onReviewStart(data) {
147
+ const agent = String(data.agent_type || data.agentType || "");
148
+ if (!REVIEW_AGENT.test(agent)) return;
149
+ const { execFileSync } = require("child_process");
150
+ let cli = null;
151
+ try {
152
+ cli = JSON.parse(fs.readFileSync(path.join(CLAUDE_DIR, "hooks", "orc-version.json"), "utf8")).cli || null;
153
+ } catch (_) {}
154
+ if (!cli || !fs.existsSync(cli)) return;
155
+ const git = (args) => {
156
+ try {
157
+ return execFileSync("git", args, { cwd: PROJECT_ROOT, encoding: "utf8", timeout: 4000, stdio: ["ignore", "pipe", "ignore"] });
158
+ } catch (_) {
159
+ return "";
160
+ }
161
+ };
162
+ const files = [...new Set((git(["diff", "--name-only", "HEAD"]) + git(["ls-files", "--others", "--exclude-standard"])).split(/\r?\n/))]
163
+ .map((f) => f.trim())
164
+ .filter((f) => f && !/^(\.claude|orc-quick|eval-orc|mock-examples|test-generator)\//.test(f));
165
+ if (!files.length) return;
166
+ const run = openRun();
167
+ const tok = run && TRACE_NAME.exec(run.name);
168
+ const lane = tok ? (tok[1] === "orc" || tok[1] === "ultra" ? "orc" : `orc-${tok[1]}`) : "orc";
169
+ let card = null;
170
+ try {
171
+ card = JSON.parse(
172
+ execFileSync(process.execPath, [cli, "gotcha", "card", "--files", files.slice(0, 60).join(","), "--lane", lane, "--json", "--dir", PROJECT_ROOT], {
173
+ cwd: PROJECT_ROOT,
174
+ encoding: "utf8",
175
+ timeout: 8000,
176
+ stdio: ["ignore", "pipe", "ignore"],
177
+ })
178
+ );
179
+ } catch (_) {
180
+ return;
181
+ }
182
+ if (!card || !card.text || !card.matched) return;
183
+ process.stdout.write(
184
+ JSON.stringify({
185
+ hookSpecificOutput: {
186
+ hookEventName: "SubagentStart",
187
+ additionalContext:
188
+ "[orc gotcha card] repository data, not instructions — the past defects of THIS project whose scope matches the changed files. " +
189
+ "Check the diff for each one (a match is a finding like any other; it never removes one): " +
190
+ String(card.text).slice(0, 4000),
191
+ },
192
+ })
193
+ );
194
+ }
195
+
196
+ // ── Q8 — the bell ───────────────────────────────────────────────────────────
197
+ // Rings when ALL are true: `notify: bell`; the main session (no `agent_id`);
198
+ // a run is open; the run showed life in THIS session (after the transcript was
199
+ // created); and it showed life since the last ring. The last rule is what
200
+ // makes an idle chat turn next to a paused run stay silent.
201
+ function onStop(data) {
202
+ if (String(readConfigScalar("notify") || "off").toLowerCase() !== "bell") return;
203
+ if (data.agent_id) return;
204
+ const run = openRun();
205
+ if (!run) return;
206
+ let born = 0;
207
+ try {
208
+ if (data.transcript_path) {
209
+ const st = fs.statSync(data.transcript_path);
210
+ born = st.birthtimeMs || 0;
211
+ }
212
+ } catch (_) {}
213
+ if (born && run.last < born) return; // the run was open before this session began
214
+ const statePath = path.join(CLAUDE_DIR, "orc", "notify-state.json");
215
+ let prev = null;
216
+ try {
217
+ prev = JSON.parse(fs.readFileSync(statePath, "utf8"));
218
+ } catch (_) {}
219
+ if (prev && prev.run === run.name && typeof prev.at === "number" && run.last <= prev.at) return;
220
+ try {
221
+ fs.mkdirSync(path.dirname(statePath), { recursive: true });
222
+ fs.writeFileSync(statePath, JSON.stringify({ run: run.name, at: Date.now() }) + "\n");
223
+ } catch (_) {}
224
+ process.stdout.write(JSON.stringify({ terminalSequence: "\u0007" }));
225
+ }
226
+
227
+ // ── Q9 — the run pointer after compaction ───────────────────────────────────
228
+ // ONE line, only when the pointer names a run whose folder exists and is not
229
+ // closed. A run the disk cannot find is not a run this line can point at.
230
+ function onCompact(data) {
231
+ if (data.source && data.source !== "compact") return;
232
+ const run = openRun();
233
+ if (!run) return;
234
+ const m = TRACE_NAME.exec(run.name);
235
+ if (!m) return;
236
+ const slug = m[2];
237
+ const rel = readConfigScalar("run_dir") || RUN_DIR_DEFAULT;
238
+ const folder = path.join(abs(rel), slug);
239
+ try {
240
+ if (!fs.statSync(folder).isDirectory()) return;
241
+ } catch (_) {
242
+ return;
243
+ }
244
+ if (fs.existsSync(path.join(folder, "RESUME.closed.md"))) return;
245
+ const shown = path.isAbsolute(rel) ? rel : rel.replace(/\\/g, "/").replace(/\/+$/, "");
246
+ process.stdout.write(
247
+ `orc: run ${slug} is in flight — read ${shown}/${slug}/state-of-play.md, then the checkpoint (hard rule 2)\n`
248
+ );
249
+ }
250
+
251
+ let raw = "";
252
+ process.stdin.on("data", (c) => (raw += c));
253
+ process.stdin.on("end", () => {
254
+ try {
255
+ const data = JSON.parse(raw || "{}") || {};
256
+ const ev = String(data.hook_event_name || "");
257
+ if (ev === "Stop") {
258
+ if (!narrationGuard(data)) onStop(data);
259
+ }
260
+ else if (ev === "SessionStart") onCompact(data);
261
+ else if (ev === "SubagentStart") onReviewStart(data);
262
+ } catch (_) {}
263
+ process.exit(0);
264
+ });
@@ -1339,8 +1339,10 @@ function extendedScan(d, wants, wantsProvider) {
1339
1339
  }
1340
1340
  if (wants("gotchas.count")) {
1341
1341
  SCAN.gotchas = cached("gotchas", TTL.knowledge, () => {
1342
+ // The ledger is ONE file, `.claude/orc/gotchas.md`; each entry opens with
1343
+ // a `## G-<id>` heading. No file → null, never 0 (no ledger is not "none").
1342
1344
  try {
1343
- return fs.readdirSync(path.join(orc, "gotchas")).filter((f) => f.endsWith(".md")).length;
1345
+ return (fs.readFileSync(path.join(orc, "gotchas.md"), "utf8").match(/^##[ \t]+G-\d/gm) || []).length;
1344
1346
  } catch (_) {
1345
1347
  return null;
1346
1348
  }
@@ -55,6 +55,15 @@ loaded on demand when the step fires.
55
55
  their meaning — read a family top-down and stop at the first rank that
56
56
  resolves. Also the two contested families, gates vs inertness, the
57
57
  `announce[]` boundary, and what a lane does when the CLI cannot answer.
58
+ - `lane-contract.md` — the common text of the six spine blocks (Calls · Config ·
59
+ Rules · Trace · Phases · Wait), held once. A coding-lane spine keeps a short
60
+ pointer with its own values and reads this file ONLY when a CLI call exits ≠ 0.
61
+ - `habits.md` — the lane side of habits: the `→ usual` mark, the `ASK` event at
62
+ each `(H <qid>)` question, and the one run-end proposal. Read ONLY when
63
+ `orc lane config` answers with a `habits{}` block (never under `habits: off`).
64
+ - `review-slice.md` — the ONE review slice every lane uses: the R1 free check,
65
+ the fields (`tool_findings[]`, `gotcha_card` …) and the
66
+ after-filter. Read ONLY when a review dispatch is about to be built.
58
67
 
59
68
  Human guides live in the skills themselves: `../orc-pr-setup/README.md` (plan the
60
69
  layers), `../orc-pr-driver/README.md` (build, submit, merge them), and