@azure-id/orc 1.9.1 → 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 (224) hide show
  1. package/CHANGELOG.md +390 -0
  2. package/README-id.md +20 -36
  3. package/README.md +24 -34
  4. package/bin/build-agents.js +206 -109
  5. package/bin/cli.js +760 -62
  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/onboarding-content.js +4 -4
  13. package/bin/pricing.json +207 -200
  14. package/bin/run-undo.js +398 -0
  15. package/bin/trace-write.js +657 -0
  16. package/bin/verify-contracts.js +527 -81
  17. package/bin/verify-package.js +58 -11
  18. package/bin/webui/api.js +26 -0
  19. package/bin/webui/app.html +239 -232
  20. package/bin/webui/css/00-tokens.css +110 -92
  21. package/bin/webui/css/04-motion.css +249 -152
  22. package/bin/webui/css/06-responsive.css +203 -178
  23. package/bin/webui/css/panels/behaviour.css +205 -0
  24. package/bin/webui/css/panels/knowledge.css +563 -116
  25. package/bin/webui/fixtures/behaviour.js +532 -0
  26. package/bin/webui/fixtures/extra.js +2036 -2036
  27. package/bin/webui/fixtures/hookui.js +5 -5
  28. package/bin/webui/fixtures/index.js +34 -0
  29. package/bin/webui/fixtures/knowledge.js +7 -1
  30. package/bin/webui/fixtures/settings.js +305 -305
  31. package/bin/webui/fixtures/stats.js +16 -0
  32. package/bin/webui/i18n/en/behaviour.json +143 -0
  33. package/bin/webui/i18n/en/knowledge.json +117 -2
  34. package/bin/webui/i18n/en/nav.json +25 -24
  35. package/bin/webui/i18n/en/tour.json +37 -35
  36. package/bin/webui/i18n/id/behaviour.json +143 -0
  37. package/bin/webui/i18n/id/knowledge.json +117 -2
  38. package/bin/webui/i18n/id/nav.json +25 -24
  39. package/bin/webui/i18n/id/tour.json +37 -35
  40. package/bin/webui/js/01-i18n.js +155 -154
  41. package/bin/webui/js/90-tour.js +498 -494
  42. package/bin/webui/js/91-shortcuts.js +126 -126
  43. package/bin/webui/js/99-boot.js +121 -118
  44. package/bin/webui/js/panels/behaviour.js +1022 -0
  45. package/bin/webui/js/panels/knowledge.js +552 -7
  46. package/bin/webui/js/panels/overview.js +13 -4
  47. package/mock-run/INDEX.md +109 -107
  48. package/mock-run/context-combiner.md +100 -100
  49. package/mock-run/gotcha-import.md +118 -0
  50. package/mock-run/habits.md +129 -0
  51. package/mock-run/orc-budget.md +534 -534
  52. package/mock-run/orc-challenge-council.md +262 -262
  53. package/mock-run/orc-quick.md +6 -1
  54. package/mock-run/orc-ultra.md +103 -103
  55. package/package.json +1 -1
  56. package/templates/agents/MODEL-MAPPING.md +49 -49
  57. package/templates/agents/orc-advisor-opus-5-xhigh.md +50 -56
  58. package/templates/agents/orc-analyze-mini-opus-5-med.md +55 -60
  59. package/templates/agents/orc-analyze-mini-sonnet-5-high.md +55 -58
  60. package/templates/agents/orc-challenge-advisor-opus-5-med.md +75 -75
  61. package/templates/agents/orc-challenge-contrarian-opus-5-high.md +110 -110
  62. package/templates/agents/orc-challenge-executor-opus-5-med.md +114 -114
  63. package/templates/agents/orc-challenge-expansionist-opus-5-med.md +112 -112
  64. package/templates/agents/orc-challenge-judge-opus-5-high.md +132 -132
  65. package/templates/agents/orc-challenge-outsider-opus-5-low.md +109 -109
  66. package/templates/agents/orc-challenge-principles-opus-5-high.md +109 -109
  67. package/templates/agents/orc-challenge-reader-opus-5-low.md +90 -90
  68. package/templates/agents/orc-claude-writer-opus-4-8-high.md +48 -53
  69. package/templates/agents/orc-claude-writer-opus-5-med.md +48 -55
  70. package/templates/agents/orc-context-combiner-opus-5-high.md +78 -88
  71. package/templates/agents/orc-doc-checker-opus-5-low.md +108 -108
  72. package/templates/agents/orc-doc-writer-opus-5-med.md +134 -134
  73. package/templates/agents/orc-executor-haiku-4-5.md +14 -6
  74. package/templates/agents/orc-executor-opus-4-7-high.md +14 -6
  75. package/templates/agents/orc-executor-opus-4-7-med.md +14 -6
  76. package/templates/agents/orc-executor-opus-4-8-high.md +14 -6
  77. package/templates/agents/orc-executor-opus-5-high.md +15 -7
  78. package/templates/agents/orc-executor-opus-5-low.md +15 -7
  79. package/templates/agents/orc-executor-opus-5-med.md +15 -7
  80. package/templates/agents/orc-executor-sonnet-4-6-high.md +14 -6
  81. package/templates/agents/orc-executor-sonnet-4-6-med.md +14 -6
  82. package/templates/agents/orc-executor-sonnet-5-high.md +14 -6
  83. package/templates/agents/orc-graph-noter-sonnet-4-6-med.md +1 -10
  84. package/templates/agents/orc-judge-opus-5-xhigh.md +81 -85
  85. package/templates/agents/orc-learn-writer-opus-5-low.md +67 -73
  86. package/templates/agents/orc-pattern-codifier-opus-5-med.md +58 -65
  87. package/templates/agents/orc-pattern-codifier-sonnet-5-high.md +58 -63
  88. package/templates/agents/orc-planner-mini-opus-5-med.md +4 -7
  89. package/templates/agents/orc-planner-mini-sonnet-5-high.md +2 -3
  90. package/templates/agents/orc-planner-opus-5-med.md +157 -160
  91. package/templates/agents/orc-recon-opus-5-low.md +3 -10
  92. package/templates/agents/orc-recon-sonnet-4-6-med.md +1 -8
  93. package/templates/agents/orc-retro-opus-5-med.md +8 -10
  94. package/templates/agents/orc-retro-sonnet-5-high.md +6 -7
  95. package/templates/agents/orc-reviewer-opus-5-med.md +96 -60
  96. package/templates/agents/orc-scout-opus-5-low.md +35 -40
  97. package/templates/agents/orc-scout-sonnet-4-6-high.md +35 -39
  98. package/templates/agents/orc-system-analyst-opus-5-high.md +115 -120
  99. package/templates/agents/orc-test-author-opus-5-med.md +70 -71
  100. package/templates/agents/orc-test-designer-opus-5-high.md +158 -158
  101. package/templates/agents/orc-test-interpreter-opus-5-low.md +130 -130
  102. package/templates/agents/orc-trace-writer-haiku-4-5.md +3 -7
  103. package/templates/agents/orc-verifier-opus-5-med.md +77 -69
  104. package/templates/agents/orc-wiki-scanner-opus-4-8-high.md +74 -79
  105. package/templates/agents/orc-wiki-scanner-opus-5-med.md +74 -81
  106. package/templates/agents/orc-wiki-scanner-sonnet-5-high.md +97 -106
  107. package/templates/commands/orc-analyze.md +13 -21
  108. package/templates/commands/orc-fast.md +10 -15
  109. package/templates/commands/orc-poly.md +12 -21
  110. package/templates/commands/orc-pr-driver.md +11 -30
  111. package/templates/commands/orc-pr-setup.md +10 -31
  112. package/templates/commands/orc-route.md +11 -41
  113. package/templates/commands/orc-test.md +5 -60
  114. package/templates/commands/orc-ultra.md +17 -17
  115. package/templates/commands/orc-verify.md +11 -11
  116. package/templates/hooks/README.md +36 -2
  117. package/templates/hooks/orc-effort-guard.js +178 -178
  118. package/templates/hooks/orc-session-hook.js +264 -0
  119. package/templates/hooks/orc-statusline.js +9 -7
  120. package/templates/skills/_shared/README.md +9 -0
  121. package/templates/skills/_shared/code-graph.md +48 -56
  122. package/templates/skills/_shared/config-precedence.md +200 -198
  123. package/templates/skills/_shared/extra-dispatch.md +1331 -1346
  124. package/templates/skills/_shared/gotchas.md +228 -177
  125. package/templates/skills/_shared/habits.md +101 -0
  126. package/templates/skills/_shared/lane-contract.md +84 -0
  127. package/templates/skills/_shared/opus5-only.md +8 -8
  128. package/templates/skills/_shared/phases/README.md +142 -83
  129. package/templates/skills/_shared/phases/analyst-gates.md +125 -136
  130. package/templates/skills/_shared/phases/execution.md +8 -14
  131. package/templates/skills/_shared/phases/house-rules.md +27 -32
  132. package/templates/skills/_shared/phases/intake.md +127 -133
  133. package/templates/skills/_shared/phases/mock-example.md +46 -56
  134. package/templates/skills/_shared/phases/plan-handoff.md +91 -97
  135. package/templates/skills/_shared/phases/planning.md +7 -17
  136. package/templates/skills/_shared/phases/preflight.md +19 -42
  137. package/templates/skills/_shared/phases/review.md +23 -27
  138. package/templates/skills/_shared/phases/rules.md +18 -42
  139. package/templates/skills/_shared/phases/scoring.md +55 -65
  140. package/templates/skills/_shared/phases/security-checklist.md +46 -50
  141. package/templates/skills/_shared/phases/security.md +45 -55
  142. package/templates/skills/_shared/phases/ship.md +6 -15
  143. package/templates/skills/_shared/phases/stop-resume.md +2 -5
  144. package/templates/skills/_shared/phases/summary.md +73 -48
  145. package/templates/skills/_shared/phases/testgen.md +41 -51
  146. package/templates/skills/_shared/phases/trace-verbs.md +433 -0
  147. package/templates/skills/_shared/phases/trace.md +136 -367
  148. package/templates/skills/_shared/phases/verify.md +62 -70
  149. package/templates/skills/_shared/phases/wave-grouping.md +128 -133
  150. package/templates/skills/_shared/phases/wiki-consult.md +10 -6
  151. package/templates/skills/_shared/read-ladder.md +2 -55
  152. package/templates/skills/_shared/return-validation.md +17 -70
  153. package/templates/skills/_shared/review-slice.md +79 -0
  154. package/templates/skills/_shared/smoke-gate.md +46 -28
  155. package/templates/skills/context-combiner/SKILL.md +16 -45
  156. package/templates/skills/context-combiner/schemas/combined-report.md +78 -78
  157. package/templates/skills/orc/README.md +2 -2
  158. package/templates/skills/orc/SKILL.md +41 -69
  159. package/templates/skills/orc/config.md +9 -9
  160. package/templates/skills/orc/examples/full-run-mock.md +73 -73
  161. package/templates/skills/orc/references/effort-and-mode.md +222 -222
  162. package/templates/skills/orc/references/pattern-gate.md +89 -89
  163. package/templates/skills/orc/references/phases/intake.md +41 -47
  164. package/templates/skills/orc/references/phases/integration.md +13 -19
  165. package/templates/skills/orc/references/preflight-report.md +10 -11
  166. package/templates/skills/orc/references/ultra-mode.md +11 -9
  167. package/templates/skills/orc/subskills/orc-execution/SKILL.md +27 -73
  168. package/templates/skills/orc/subskills/orc-execution/core.md +12 -99
  169. package/templates/skills/orc/subskills/orc-execution/subagent.md +14 -13
  170. package/templates/skills/orc/subskills/orc-planner/SKILL.md +2 -2
  171. package/templates/skills/orc/subskills/orc-planner-mini/SKILL.md +121 -121
  172. package/templates/skills/orc/subskills/orc-review-verify/SKILL.md +35 -76
  173. package/templates/skills/orc/subskills/orc-review-verify/core.md +52 -135
  174. package/templates/skills/orc/subskills/orc-review-verify/subagent.md +7 -7
  175. package/templates/skills/orc/subskills/orc-testgen/SKILL.md +34 -45
  176. package/templates/skills/orc/subskills/orc-testgen/core.md +20 -59
  177. package/templates/skills/orc/subskills/orc-testgen/subagent.md +7 -7
  178. package/templates/skills/orc-advisor/SKILL.md +56 -60
  179. package/templates/skills/orc-analyze/SKILL.md +32 -68
  180. package/templates/skills/orc-analyze/examples/analyze-mock.md +42 -42
  181. package/templates/skills/orc-analyze/schemas/report-audit.md +84 -83
  182. package/templates/skills/orc-analyze/schemas/report-prose.md +64 -63
  183. package/templates/skills/orc-analyze/schemas/report-requirement.md +78 -78
  184. package/templates/skills/orc-analyze-mini/SKILL.md +35 -70
  185. package/templates/skills/orc-challenge/README.md +142 -142
  186. package/templates/skills/orc-challenge/examples/council-full-roster.md +273 -273
  187. package/templates/skills/orc-challenge/references/council.md +315 -315
  188. package/templates/skills/orc-challenge/references/intake.md +171 -171
  189. package/templates/skills/orc-claude/SKILL.md +14 -14
  190. package/templates/skills/orc-diy/README.md +174 -143
  191. package/templates/skills/orc-diy/SKILL.md +23 -74
  192. package/templates/skills/orc-diy/references/blocks/pattern.md +18 -18
  193. package/templates/skills/orc-diy/references/flow-schema.md +1 -1
  194. package/templates/skills/orc-doc/README.md +229 -229
  195. package/templates/skills/orc-doc/examples/orc-doc-prd-run.md +325 -325
  196. package/templates/skills/orc-doc/references/chunking.md +527 -527
  197. package/templates/skills/orc-fast/SKILL.md +45 -73
  198. package/templates/skills/orc-judge/SKILL.md +77 -82
  199. package/templates/skills/orc-learn/SKILL.md +14 -14
  200. package/templates/skills/orc-learn/examples/learn-run-mock.md +61 -61
  201. package/templates/skills/orc-mini/SKILL.md +65 -113
  202. package/templates/skills/orc-pattern/SKILL.md +27 -48
  203. package/templates/skills/orc-poly/SKILL.md +32 -61
  204. package/templates/skills/orc-pr-driver/SKILL.md +25 -51
  205. package/templates/skills/orc-pr-driver/references/green-gate.md +113 -105
  206. package/templates/skills/orc-pr-setup/SKILL.md +20 -45
  207. package/templates/skills/orc-quick/README.md +43 -2
  208. package/templates/skills/orc-quick/SKILL.md +76 -107
  209. package/templates/skills/orc-quick/references/dispatch-gate.md +16 -5
  210. package/templates/skills/orc-quick/references/gh-mode.md +48 -1
  211. package/templates/skills/orc-quick/references/look.md +3 -1
  212. package/templates/skills/orc-retro/SKILL.md +19 -18
  213. package/templates/skills/orc-retro/examples/retro-mock.md +1 -1
  214. package/templates/skills/orc-route/SKILL.md +25 -45
  215. package/templates/skills/orc-test/SKILL.md +16 -37
  216. package/templates/skills/orc-verify/SKILL.md +21 -39
  217. package/templates/skills/orc-verify/examples/verify-mock.md +33 -33
  218. package/templates/skills/orc-wait/SKILL.md +156 -163
  219. package/templates/skills/orc-wiki/references/extra.md +1 -1
  220. package/templates/skills/orc-wiki/references/phases/phase-0.md +1 -6
  221. package/templates/skills/orc-wiki/references/phases/phase-1.md +1 -6
  222. package/templates/skills/orc-wiki/references/phases/phase-2.md +1 -6
  223. package/templates/skills/orc-wiki/references/phases/phase-3.md +1 -6
  224. package/templates/skills/orc-wiki/references/phases/phase-3c.md +1 -6
@@ -13,7 +13,7 @@ Every value comes from a file on your disk or from data Claude Code gives the
13
13
  hook. No value costs model tokens. Nothing here starts a run.
14
14
 
15
15
  ```
16
- 🚀 ORC v1.2.1 - Opus 5/high · context (34%) · 5h 41% (2h13m) ↔ wk 12% · ucs 6% · wiki: fresh
16
+ 🚀 ORC v1.2.1 - Opus 5.5/high · context (34%) · 5h 41% (2h13m) ↔ wk 12% · ucs 6% · wiki: fresh
17
17
  ▰ status: quick · Q3 DO · agents 7 (2 running) · orc-extra: on · Dur 48m · MTok 412K · main
18
18
  ```
19
19
 
@@ -24,7 +24,7 @@ Line 2 answers: **what is this session doing?**
24
24
 
25
25
  ## Line 1
26
26
 
27
- ### 1. The icon and `ORC v1.2.1 - Opus 5/high`
27
+ ### 1. The icon and `ORC v1.2.1 - Opus 5.5/high`
28
28
 
29
29
  The icon is the verdict. The words are the ORC version you have installed and
30
30
  the model and effort you are running now.
@@ -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
@@ -1,178 +1,178 @@
1
- #!/usr/bin/env node
2
- "use strict";
3
-
4
- /**
5
- * ORC effort guard — a Claude Code PreToolUse hook.
6
- *
7
- * Blocks invocation of the full `orc` orchestrator skill unless the MAIN
8
- * session is running at HIGH reasoning effort (Opus 5 / Fable 5 clear at
9
- * medium+ — see the session-model bridge below). This is the one half of the
10
- * "run ORC on Opus 4.8 high or better" rule hooks can enforce deterministically:
11
- * Claude Code exposes `effort.level` (and $CLAUDE_EFFORT) to PreToolUse, but
12
- * NOT the model id (only SessionStart may see the model, and cannot block).
13
- * The model tier is surfaced separately by the statusline warning + the
14
- * self-check inside skills/orc/SKILL.md.
15
- *
16
- * Wiring (installed by `orc init` into .claude/settings.json):
17
- * hooks.PreToolUse[] { matcher: "Skill", hooks:[{ type:"command",
18
- * command: 'node "<.claude>/hooks/orc-effort-guard.js"' }] }
19
- *
20
- * Contract: read PreToolUse JSON from stdin. Exit 0 = allow. Exit 2 = block
21
- * the tool call and show stderr to Claude, which relays it to the user.
22
- *
23
- * Also surfaces a "newer orc version available" nudge here — this hook fires
24
- * exactly when /orc is invoked, so it's the natural place to tell the user
25
- * without them running `orc version`. On allow it emits a `systemMessage`
26
- * (shown to the user, NOT added to model context → zero tokens); on block it
27
- * appends the nudge to the block reason. Version check is cached 24h and
28
- * fail-silent. (systemMessage handling may vary by Claude Code version — verify
29
- * with /doctor if you don't see it; the statusline shows it regardless.)
30
- */
31
-
32
- // Shared update-check helper (sibling file). Degrade gracefully if absent.
33
- let updater = null;
34
- try {
35
- updater = require("./orc-update-lib.js");
36
- } catch (_) {
37
- updater = null;
38
- }
39
-
40
- // Effort ladder: low < medium < high < xhigh < max. A tier that meets the bar
41
- // meets it at that rung OR anything stronger — so "requires high" allows xhigh
42
- // and max too (the bug this fixes: an xhigh/max session was hard-blocked).
43
- const EFFORT_LADDER = ["low", "medium", "high", "xhigh", "max"];
44
- const effortsAtOrAbove = (e) => {
45
- const i = EFFORT_LADDER.indexOf(String(e).toLowerCase());
46
- return i === -1 ? ["high", "xhigh", "max"] : EFFORT_LADDER.slice(i);
47
- };
48
-
49
- // Session-model bridge (written by orc-statusline.js). The guard can't see the
50
- // model id itself, so it reads it here to grant the medium-effort allowance of
51
- // the models that are strictly stronger than the Opus 4.8 baseline (Opus 5 and
52
- // Fable 5). Fail-OPEN: missing / unreadable / stale → null, and the guard
53
- // behaves exactly as it would without a bridge (never blocks on our own error).
54
- // Stale = older than this window; a live session re-renders the statusline far
55
- // more often, so a fresh file always exists during active use.
56
- const BRIDGE_MAX_AGE_MS = 30 * 60 * 1000;
57
- function readSessionModel(projectDir) {
58
- try {
59
- const fs = require("fs");
60
- const path = require("path");
61
- const p = path.join(projectDir, ".claude", "orc", "session-model.json");
62
- const j = JSON.parse(fs.readFileSync(p, "utf8").replace(/^/, ""));
63
- if (!j || typeof j.written_at !== "number") return null;
64
- if (Date.now() - j.written_at > BRIDGE_MAX_AGE_MS) return null;
65
- return String(j.model_id || "");
66
- } catch (_) {
67
- return null;
68
- }
69
- }
70
- // Models that clear /orc at medium effort — strictly stronger than the Opus 4.8
71
- // baseline, so medium on them beats high on it. Opus 5 (v0.34.0) joins Fable 5.
72
- // The `\b` after the 5 keeps 4.8 / 4.7 / 5-something-else correctly gated.
73
- const isMediumOkModel = (id) =>
74
- /(opus|fable)[\s._-]?5\b/.test(String(id || "").toLowerCase());
75
-
76
- let raw = "";
77
- process.stdin.on("data", (c) => (raw += c));
78
- process.stdin.on("end", () => {
79
- let data = {};
80
- try {
81
- data = JSON.parse(raw || "{}");
82
- } catch (_) {
83
- // Unparseable payload — never block on our own error.
84
- process.exit(0);
85
- }
86
-
87
- const tool = data.tool_name || "";
88
- const input = data.tool_input || {};
89
- const skill = String(input.skill || input.name || "");
90
-
91
- // Only gate the full orchestrator and the compiled DIY lane. orc-mini /
92
- // orc-fast / orc-analyze / subskills pass — orc-fast is DESIGNED to run at
93
- // Sonnet medium; never widen this match to include it.
94
- const isOrc = tool === "Skill" && /^orc$/i.test(skill);
95
- const isDiy = tool === "Skill" && /^orc-diy$/i.test(skill);
96
- if (!isOrc && !isDiy) process.exit(0);
97
-
98
- const effort = String(
99
- (data.effort && data.effort.level) || process.env.CLAUDE_EFFORT || ""
100
- ).toLowerCase();
101
- const installed = updater ? updater.installedVersion(__dirname) : null;
102
- const projectDir = data.cwd || process.cwd();
103
-
104
- // Baseline /orc: the session must be at high effort OR stronger (xhigh/max).
105
- // Opus 5 and Fable 5 additionally clear at medium — strictly-capable models —
106
- // detected through the session-model bridge (the guard can't see the model id
107
- // itself; fail-open when the bridge is missing/stale, i.e. medium stays blocked).
108
- let requiredEfforts = effortsAtOrAbove("high");
109
- let requiredLabel =
110
- "Opus 4.8 at high effort (Opus 5 / Fable 5 also clear at medium+)";
111
- if (!isDiy && isMediumOkModel(readSessionModel(projectDir))) {
112
- requiredEfforts = ["medium", ...requiredEfforts];
113
- }
114
-
115
- // /orc-diy: the required tier is whatever the flow was COMPILED for — read
116
- // it from flow.lock.json (written only by the `orc diy` CLI). Fail closed:
117
- // no lock = no compiled flow = deterministic onboarding block. `compile` /
118
- // `status` invocations pass at any effort (they just shell out to the CLI).
119
- if (isDiy) {
120
- if (/^\s*(compile|status)\b/i.test(String(input.args || ""))) process.exit(0);
121
- const fs = require("fs");
122
- const path = require("path");
123
- let lock = null;
124
- try {
125
- lock = JSON.parse(
126
- fs
127
- .readFileSync(
128
- path.join(projectDir, ".claude", "orc", "diy", "flow.lock.json"),
129
- "utf8"
130
- )
131
- .replace(/^\uFEFF/, "")
132
- );
133
- } catch (_) {}
134
- if (!lock || !lock.session_tier) {
135
- process.stderr.write(
136
- "\n⛔ /orc-diy blocked — no compiled flow in this project (fail-closed gate)." +
137
- "\n Compose and build your flow in the terminal first:" +
138
- "\n orc diy init (then shape it: orc diy set <key> <value>)" +
139
- "\n orc diy compile" +
140
- "\n Guide: .claude/skills/orc-diy/README.md — or use plain /orc for this request.\n"
141
- );
142
- process.exit(2);
143
- }
144
- // Effort half is DETERMINISTIC from the compiled slug's suffix (allow that
145
- // effort or anything stronger); the model half is warn-only on the
146
- // statusline (hooks can't block on model). Covers the full tier grid —
147
- // sonnet-4-6 / opus-4-7 / opus-4-8 / fable-5 at med|high|xhigh|max.
148
- const m = String(lock.session_tier).match(/-(med|high|xhigh|max)$/);
149
- const slugEffort = m ? (m[1] === "med" ? "medium" : m[1]) : "high";
150
- requiredEfforts = effortsAtOrAbove(slugEffort);
151
- requiredLabel = `${lock.session_tier} (compiled session_tier — needs effort ${slugEffort}+)`;
152
- }
153
-
154
- const decide = (nudge) => {
155
- if (requiredEfforts.includes(effort)) {
156
- // Requirement met — allow. Surface a version nudge if one exists.
157
- if (nudge) {
158
- try {
159
- process.stdout.write(JSON.stringify({ systemMessage: nudge }));
160
- } catch (_) {}
161
- }
162
- process.exit(0);
163
- }
164
-
165
- process.stderr.write(
166
- `\n⛔ ${isDiy ? "/orc-diy" : "ORC"} blocked — required effort not met` +
167
- (effort ? ` (current effort: ${effort}).` : " (effort not detected).") +
168
- `\n Switch this session to ${requiredLabel}, then re-run.` +
169
- "\n Run the MAIN session at (or above) the required tier — subagents cannot" +
170
- "\n exceed the main tier, so pinned agents silently downgrade below it.\n" +
171
- (nudge ? "\n" + nudge + "\n" : "")
172
- );
173
- process.exit(2);
174
- };
175
-
176
- if (updater) updater.refreshAndNudge(installed, decide);
177
- else decide(null);
178
- });
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ /**
5
+ * ORC effort guard — a Claude Code PreToolUse hook.
6
+ *
7
+ * Blocks invocation of the full `orc` orchestrator skill unless the MAIN
8
+ * session is running at HIGH reasoning effort (Opus 5.5 / Fable 5 clear at
9
+ * medium+ — see the session-model bridge below). This is the one half of the
10
+ * "run ORC on Opus 4.8 high or better" rule hooks can enforce deterministically:
11
+ * Claude Code exposes `effort.level` (and $CLAUDE_EFFORT) to PreToolUse, but
12
+ * NOT the model id (only SessionStart may see the model, and cannot block).
13
+ * The model tier is surfaced separately by the statusline warning + the
14
+ * self-check inside skills/orc/SKILL.md.
15
+ *
16
+ * Wiring (installed by `orc init` into .claude/settings.json):
17
+ * hooks.PreToolUse[] { matcher: "Skill", hooks:[{ type:"command",
18
+ * command: 'node "<.claude>/hooks/orc-effort-guard.js"' }] }
19
+ *
20
+ * Contract: read PreToolUse JSON from stdin. Exit 0 = allow. Exit 2 = block
21
+ * the tool call and show stderr to Claude, which relays it to the user.
22
+ *
23
+ * Also surfaces a "newer orc version available" nudge here — this hook fires
24
+ * exactly when /orc is invoked, so it's the natural place to tell the user
25
+ * without them running `orc version`. On allow it emits a `systemMessage`
26
+ * (shown to the user, NOT added to model context → zero tokens); on block it
27
+ * appends the nudge to the block reason. Version check is cached 24h and
28
+ * fail-silent. (systemMessage handling may vary by Claude Code version — verify
29
+ * with /doctor if you don't see it; the statusline shows it regardless.)
30
+ */
31
+
32
+ // Shared update-check helper (sibling file). Degrade gracefully if absent.
33
+ let updater = null;
34
+ try {
35
+ updater = require("./orc-update-lib.js");
36
+ } catch (_) {
37
+ updater = null;
38
+ }
39
+
40
+ // Effort ladder: low < medium < high < xhigh < max. A tier that meets the bar
41
+ // meets it at that rung OR anything stronger — so "requires high" allows xhigh
42
+ // and max too (the bug this fixes: an xhigh/max session was hard-blocked).
43
+ const EFFORT_LADDER = ["low", "medium", "high", "xhigh", "max"];
44
+ const effortsAtOrAbove = (e) => {
45
+ const i = EFFORT_LADDER.indexOf(String(e).toLowerCase());
46
+ return i === -1 ? ["high", "xhigh", "max"] : EFFORT_LADDER.slice(i);
47
+ };
48
+
49
+ // Session-model bridge (written by orc-statusline.js). The guard can't see the
50
+ // model id itself, so it reads it here to grant the medium-effort allowance of
51
+ // the models that are strictly stronger than the Opus 4.8 baseline (Opus 5.5 and
52
+ // Fable 5). Fail-OPEN: missing / unreadable / stale → null, and the guard
53
+ // behaves exactly as it would without a bridge (never blocks on our own error).
54
+ // Stale = older than this window; a live session re-renders the statusline far
55
+ // more often, so a fresh file always exists during active use.
56
+ const BRIDGE_MAX_AGE_MS = 30 * 60 * 1000;
57
+ function readSessionModel(projectDir) {
58
+ try {
59
+ const fs = require("fs");
60
+ const path = require("path");
61
+ const p = path.join(projectDir, ".claude", "orc", "session-model.json");
62
+ const j = JSON.parse(fs.readFileSync(p, "utf8").replace(/^/, ""));
63
+ if (!j || typeof j.written_at !== "number") return null;
64
+ if (Date.now() - j.written_at > BRIDGE_MAX_AGE_MS) return null;
65
+ return String(j.model_id || "");
66
+ } catch (_) {
67
+ return null;
68
+ }
69
+ }
70
+ // Models that clear /orc at medium effort — strictly stronger than the Opus 4.8
71
+ // baseline, so medium on them beats high on it. Opus 5.5 (v0.34.0) joins Fable 5.
72
+ // The `\b` after the 5 keeps 4.8 / 4.7 / 5-something-else correctly gated.
73
+ const isMediumOkModel = (id) =>
74
+ /(opus|fable)[\s._-]?5\b/.test(String(id || "").toLowerCase());
75
+
76
+ let raw = "";
77
+ process.stdin.on("data", (c) => (raw += c));
78
+ process.stdin.on("end", () => {
79
+ let data = {};
80
+ try {
81
+ data = JSON.parse(raw || "{}");
82
+ } catch (_) {
83
+ // Unparseable payload — never block on our own error.
84
+ process.exit(0);
85
+ }
86
+
87
+ const tool = data.tool_name || "";
88
+ const input = data.tool_input || {};
89
+ const skill = String(input.skill || input.name || "");
90
+
91
+ // Only gate the full orchestrator and the compiled DIY lane. orc-mini /
92
+ // orc-fast / orc-analyze / subskills pass — orc-fast is DESIGNED to run at
93
+ // Sonnet medium; never widen this match to include it.
94
+ const isOrc = tool === "Skill" && /^orc$/i.test(skill);
95
+ const isDiy = tool === "Skill" && /^orc-diy$/i.test(skill);
96
+ if (!isOrc && !isDiy) process.exit(0);
97
+
98
+ const effort = String(
99
+ (data.effort && data.effort.level) || process.env.CLAUDE_EFFORT || ""
100
+ ).toLowerCase();
101
+ const installed = updater ? updater.installedVersion(__dirname) : null;
102
+ const projectDir = data.cwd || process.cwd();
103
+
104
+ // Baseline /orc: the session must be at high effort OR stronger (xhigh/max).
105
+ // Opus 5.5 and Fable 5 additionally clear at medium — strictly-capable models —
106
+ // detected through the session-model bridge (the guard can't see the model id
107
+ // itself; fail-open when the bridge is missing/stale, i.e. medium stays blocked).
108
+ let requiredEfforts = effortsAtOrAbove("high");
109
+ let requiredLabel =
110
+ "Opus 4.8 at high effort (Opus 5.5 / Fable 5 also clear at medium+)";
111
+ if (!isDiy && isMediumOkModel(readSessionModel(projectDir))) {
112
+ requiredEfforts = ["medium", ...requiredEfforts];
113
+ }
114
+
115
+ // /orc-diy: the required tier is whatever the flow was COMPILED for — read
116
+ // it from flow.lock.json (written only by the `orc diy` CLI). Fail closed:
117
+ // no lock = no compiled flow = deterministic onboarding block. `compile` /
118
+ // `status` invocations pass at any effort (they just shell out to the CLI).
119
+ if (isDiy) {
120
+ if (/^\s*(compile|status)\b/i.test(String(input.args || ""))) process.exit(0);
121
+ const fs = require("fs");
122
+ const path = require("path");
123
+ let lock = null;
124
+ try {
125
+ lock = JSON.parse(
126
+ fs
127
+ .readFileSync(
128
+ path.join(projectDir, ".claude", "orc", "diy", "flow.lock.json"),
129
+ "utf8"
130
+ )
131
+ .replace(/^\uFEFF/, "")
132
+ );
133
+ } catch (_) {}
134
+ if (!lock || !lock.session_tier) {
135
+ process.stderr.write(
136
+ "\n⛔ /orc-diy blocked — no compiled flow in this project (fail-closed gate)." +
137
+ "\n Compose and build your flow in the terminal first:" +
138
+ "\n orc diy init (then shape it: orc diy set <key> <value>)" +
139
+ "\n orc diy compile" +
140
+ "\n Guide: .claude/skills/orc-diy/README.md — or use plain /orc for this request.\n"
141
+ );
142
+ process.exit(2);
143
+ }
144
+ // Effort half is DETERMINISTIC from the compiled slug's suffix (allow that
145
+ // effort or anything stronger); the model half is warn-only on the
146
+ // statusline (hooks can't block on model). Covers the full tier grid —
147
+ // sonnet-4-6 / opus-4-7 / opus-4-8 / fable-5 at med|high|xhigh|max.
148
+ const m = String(lock.session_tier).match(/-(med|high|xhigh|max)$/);
149
+ const slugEffort = m ? (m[1] === "med" ? "medium" : m[1]) : "high";
150
+ requiredEfforts = effortsAtOrAbove(slugEffort);
151
+ requiredLabel = `${lock.session_tier} (compiled session_tier — needs effort ${slugEffort}+)`;
152
+ }
153
+
154
+ const decide = (nudge) => {
155
+ if (requiredEfforts.includes(effort)) {
156
+ // Requirement met — allow. Surface a version nudge if one exists.
157
+ if (nudge) {
158
+ try {
159
+ process.stdout.write(JSON.stringify({ systemMessage: nudge }));
160
+ } catch (_) {}
161
+ }
162
+ process.exit(0);
163
+ }
164
+
165
+ process.stderr.write(
166
+ `\n⛔ ${isDiy ? "/orc-diy" : "ORC"} blocked — required effort not met` +
167
+ (effort ? ` (current effort: ${effort}).` : " (effort not detected).") +
168
+ `\n Switch this session to ${requiredLabel}, then re-run.` +
169
+ "\n Run the MAIN session at (or above) the required tier — subagents cannot" +
170
+ "\n exceed the main tier, so pinned agents silently downgrade below it.\n" +
171
+ (nudge ? "\n" + nudge + "\n" : "")
172
+ );
173
+ process.exit(2);
174
+ };
175
+
176
+ if (updater) updater.refreshAndNudge(installed, decide);
177
+ else decide(null);
178
+ });