@azure-id/orc 2.0.2 → 2.1.1

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 (129) hide show
  1. package/CHANGELOG.md +455 -0
  2. package/README-id.md +52 -23
  3. package/README.md +36 -20
  4. package/bin/build-agents.js +205 -206
  5. package/bin/clear-logs.js +620 -0
  6. package/bin/cli.js +47417 -46420
  7. package/bin/fix.js +459 -0
  8. package/bin/gotcha.js +288 -8
  9. package/bin/habit.js +1538 -1453
  10. package/bin/mockrun-catalog.js +3 -0
  11. package/bin/onboarding-content.js +2 -1
  12. package/bin/pricing.json +28 -21
  13. package/bin/trace-write.js +869 -657
  14. package/bin/verify-contracts.js +5552 -5471
  15. package/bin/verify-package.js +716 -705
  16. package/bin/webui/api.js +125 -6
  17. package/bin/webui/css/panels/hookui.css +79 -0
  18. package/bin/webui/fixtures/behaviour.js +30 -0
  19. package/bin/webui/fixtures/extra.js +2036 -2036
  20. package/bin/webui/fixtures/flow.js +81 -81
  21. package/bin/webui/fixtures/hookui.js +506 -431
  22. package/bin/webui/fixtures/index.js +32 -3
  23. package/bin/webui/fixtures/knowledge.js +2 -2
  24. package/bin/webui/fixtures/maintenance.js +123 -42
  25. package/bin/webui/fixtures/rules.js +1007 -1002
  26. package/bin/webui/fixtures/settings.js +6 -7
  27. package/bin/webui/fixtures/stats.js +9 -6
  28. package/bin/webui/fixtures/wait.js +98 -97
  29. package/bin/webui/i18n/en/behaviour.json +13 -0
  30. package/bin/webui/i18n/en/hookui.json +290 -194
  31. package/bin/webui/i18n/en/maintenance.json +71 -52
  32. package/bin/webui/i18n/en/settings.json +60 -60
  33. package/bin/webui/i18n/id/behaviour.json +13 -0
  34. package/bin/webui/i18n/id/hookui.json +290 -194
  35. package/bin/webui/i18n/id/maintenance.json +71 -52
  36. package/bin/webui/i18n/id/settings.json +60 -60
  37. package/bin/webui/js/05-banners.js +181 -172
  38. package/bin/webui/js/06-edit.js +193 -183
  39. package/bin/webui/js/panels/behaviour.js +77 -12
  40. package/bin/webui/js/panels/hookui.js +2196 -1691
  41. package/bin/webui/js/panels/maintenance.js +336 -235
  42. package/bin/webui/js/panels/settings.js +726 -719
  43. package/mock-run/INDEX.md +1 -0
  44. package/mock-run/a-normal-day.md +2 -2
  45. package/mock-run/extra-slots.md +177 -177
  46. package/mock-run/habits.md +1 -1
  47. package/mock-run/orc-aftermath.md +392 -392
  48. package/mock-run/orc-budget.md +29 -33
  49. package/mock-run/orc-cli.md +200 -200
  50. package/mock-run/orc-explain.md +86 -86
  51. package/mock-run/orc-extra.md +389 -392
  52. package/mock-run/orc-fast.md +2 -2
  53. package/mock-run/orc-fix.md +87 -0
  54. package/mock-run/orc-pattern.md +112 -112
  55. package/mock-run/orc-quick.md +146 -146
  56. package/mock-run/orc.md +4 -4
  57. package/package.json +1 -1
  58. package/templates/agents/MODEL-MAPPING.md +176 -173
  59. package/templates/agents/orc-analyze-mini-sonnet-5-high.md +2 -2
  60. package/templates/agents/{orc-claude-writer-opus-5-med.md → orc-claude-writer-opus-5-low.md} +4 -4
  61. package/templates/agents/orc-executor-haiku-4-5.md +1 -1
  62. package/templates/agents/orc-executor-opus-5-low.md +1 -1
  63. package/templates/agents/orc-executor-sonnet-4-6-high.md +1 -1
  64. package/templates/agents/orc-executor-sonnet-4-6-med.md +1 -1
  65. package/templates/agents/orc-executor-sonnet-5-high.md +2 -2
  66. package/templates/agents/orc-executor-sonnet-5-low.md +155 -0
  67. package/templates/agents/orc-executor-sonnet-5-med.md +155 -0
  68. package/templates/agents/orc-pattern-codifier-sonnet-5-high.md +2 -2
  69. package/templates/agents/orc-planner-mini-sonnet-5-high.md +2 -2
  70. package/templates/agents/{orc-recon-sonnet-4-6-med.md → orc-recon-sonnet-5-med.md} +4 -4
  71. package/templates/agents/orc-retro-sonnet-5-high.md +2 -2
  72. package/templates/agents/{orc-reviewer-opus-5-med.md → orc-reviewer-opus-5-low.md} +4 -4
  73. package/templates/agents/{orc-wiki-scanner-opus-5-med.md → orc-wiki-scanner-opus-5-low.md} +3 -3
  74. package/templates/agents/orc-wiki-scanner-sonnet-5-high.md +2 -2
  75. package/templates/commands/orc-fast.md +10 -10
  76. package/templates/commands/orc-fix.md +18 -0
  77. package/templates/commands/orc-wiki.md +64 -64
  78. package/templates/hooks/README.md +75 -7
  79. package/templates/hooks/orc-session-hook.js +78 -3
  80. package/templates/hooks/orc-statusline-render.js +317 -23
  81. package/templates/hooks/orc-statusline.js +162 -49
  82. package/templates/hooks/orc-subagent-line.js +23 -5
  83. package/templates/hooks/orc-weather-fetch.js +123 -0
  84. package/templates/skills/_shared/extra-dispatch.md +1331 -1331
  85. package/templates/skills/_shared/gotchas.md +27 -7
  86. package/templates/skills/_shared/habits.md +2 -2
  87. package/templates/skills/_shared/opus5-only.md +137 -137
  88. package/templates/skills/_shared/phases/review.md +2 -2
  89. package/templates/skills/_shared/phases/trace-verbs.md +17 -3
  90. package/templates/skills/_shared/phases/trace.md +136 -136
  91. package/templates/skills/_shared/review-slice.md +28 -1
  92. package/templates/skills/_shared/wait.md +1 -0
  93. package/templates/skills/orc/README.md +2 -2
  94. package/templates/skills/orc/SKILL.md +227 -227
  95. package/templates/skills/orc/config.md +25 -27
  96. package/templates/skills/orc/examples/full-run-mock.md +74 -73
  97. package/templates/skills/orc/references/effort-and-mode.md +222 -222
  98. package/templates/skills/orc/references/preflight-report.md +3 -3
  99. package/templates/skills/orc/references/ultra-mode.md +1 -1
  100. package/templates/skills/orc/schemas/checkpoint.md +122 -122
  101. package/templates/skills/orc/subskills/orc-review-verify/SKILL.md +3 -3
  102. package/templates/skills/orc/subskills/orc-review-verify/core.md +1 -1
  103. package/templates/skills/orc-analyze/SKILL.md +4 -2
  104. package/templates/skills/orc-claude/SKILL.md +14 -14
  105. package/templates/skills/orc-claude/examples/claude-run-mock.md +65 -65
  106. package/templates/skills/orc-diy/SKILL.md +5 -5
  107. package/templates/skills/orc-fast/SKILL.md +210 -208
  108. package/templates/skills/orc-fix/SKILL.md +110 -0
  109. package/templates/skills/orc-grill/SKILL.md +232 -232
  110. package/templates/skills/orc-grill/references/grill-doc.md +1 -1
  111. package/templates/skills/orc-mini/SKILL.md +4 -4
  112. package/templates/skills/orc-mini/examples/mini-run-mock.md +4 -2
  113. package/templates/skills/orc-quick/README.md +536 -536
  114. package/templates/skills/orc-quick/SKILL.md +2 -2
  115. package/templates/skills/orc-quick/references/context-doc.md +145 -145
  116. package/templates/skills/orc-quick/references/dispatch-gate.md +220 -219
  117. package/templates/skills/orc-quick/references/gh-mode.md +4 -2
  118. package/templates/skills/orc-retro/SKILL.md +1 -1
  119. package/templates/skills/orc-retro/examples/retro-mock.md +16 -16
  120. package/templates/skills/orc-wiki/README.md +325 -325
  121. package/templates/skills/orc-wiki/SKILL.md +195 -195
  122. package/templates/skills/orc-wiki/examples/wiki-run-mock.md +37 -37
  123. package/templates/skills/orc-wiki/references/extra.md +2 -2
  124. package/templates/skills/orc-wiki/references/partial-refresh.md +276 -271
  125. package/templates/skills/orc-wiki/references/phases/phase-0.md +1 -1
  126. package/templates/skills/orc-wiki/references/phases/phase-2.md +3 -3
  127. package/templates/skills/orc-wiki/references/phases/phase-3c.md +3 -2
  128. package/templates/agents/orc-claude-writer-opus-4-8-high.md +0 -48
  129. package/templates/agents/orc-wiki-scanner-opus-4-8-high.md +0 -74
package/README.md CHANGED
@@ -7,14 +7,14 @@
7
7
  *Intake → analyze → plan → score → parallel subagents → review → verify → ship.*
8
8
 
9
9
  ![npm](https://img.shields.io/npm/v/%40azure-id%2Forc?style=for-the-badge&color=cb3837&logo=npm)
10
- ![Version](https://img.shields.io/badge/version-2.0.2-blue.svg?style=for-the-badge)
10
+ ![Version](https://img.shields.io/badge/version-2.1.1-blue.svg?style=for-the-badge)
11
11
  ![License](https://img.shields.io/badge/license-MIT-green.svg?style=for-the-badge)
12
12
  ![Node](https://img.shields.io/badge/node-%3E%3D18-brightgreen.svg?style=for-the-badge)
13
13
  ![Claude Code](https://img.shields.io/badge/Claude_Code-Skills-purple.svg?style=for-the-badge)
14
14
  ![Dependencies](https://img.shields.io/badge/dependencies-zero-lightgrey.svg?style=for-the-badge)
15
15
  ![GitHub stars](https://img.shields.io/github/stars/azure-id/orc?style=for-the-badge&color=yellow)
16
16
 
17
- **Latest: v2.0.2** · updated 28-09-2026 · [full changelog](CHANGELOG.md)
17
+ **Latest: v2.1.1** · updated 02-10-2026 · [full changelog](CHANGELOG.md)
18
18
 
19
19
  **On npm: [`@azure-id/orc`](https://www.npmjs.com/package/@azure-id/orc)** — `npm i -g @azure-id/orc`
20
20
 
@@ -255,6 +255,7 @@ ORC have terminal hook to see: Context Window %, 5 Hour usage %, Weekly usage %
255
255
  | **`/orc-fast`** | The fastest lane. Needs a fresh wiki **and** a cached code pattern; then it skips the analyst and planner entirely. A missing prerequisite falls back to `/orc-mini` — the chat never stops. | [see it](mock-run/orc-fast.md) |
256
256
  | **`/orc-quick`** | Ask for anything: a fix, a question, a defect hunt, a dependency bump, PR comments. Look → ask once → do. **It always asks which agent to dispatch**, and no setting can change that. A defect is **reproduced red before it is fixed**. | [see it](mock-run/orc-quick.md) |
257
257
  | **`/orc-wait`** | Wall-clock pause without losing the run. You see the window is nearly full, type `/orc-wait 30`, and ORC hands the run back to disk, waits in detached hops that **cost zero tokens**, and picks up where it stopped. Three modes decide how much finishes first: `safe` · `soft` (forces the checkpoint) · `hard` (fastest, can lose an in-flight return). `/orc-wait block <reason>` tells it not to stop you at all. | — |
258
+ | **`/orc-fix`** | Fix one thing after a review: a Sonar issue, a red CI, a bug someone found. Inside a paused run it only **records** the fix (one observation and one `FIX` line in that run's trace), so the review that missed it is counted. With no run open, it fixes like `/orc-quick` and asks which agent to use. | [see it](mock-run/orc-fix.md) |
258
259
  | **`/orc-diy`** | Your own lane, composed in the terminal with `orc diy` and compiled. Unconfigured or stale → it refuses and offers plain `/orc`. | [see it](mock-run/orc-diy.md) |
259
260
 
260
261
  ### Work out what to build
@@ -617,8 +618,18 @@ fixed published formula turns them into a number. A cited risk forces a floor of
617
618
  70. The score maps through a published table to a **named, model-pinned agent**,
618
619
  so what ran is inspectable rather than requested in prose.
619
620
 
621
+ The default table (since v2.1.0):
622
+
623
+ | Score | Agent | Model · effort |
624
+ |---|---|---|
625
+ | `[0,21)` | `orc-executor-sonnet-5-low` | Sonnet 5 · low |
626
+ | `[21,31)` | `orc-executor-sonnet-5-med` | Sonnet 5 · medium |
627
+ | `[31,41)` | `orc-executor-sonnet-5-high` | Sonnet 5 · high |
628
+ | `[41,90)` | `orc-executor-opus-5-low` | Opus 5.5 · low |
629
+ | `[90,100]` | `orc-executor-opus-5-med` | Opus 5.5 · medium |
630
+
620
631
  > **The rule that catches everyone:** a subagent's model can never be higher than
621
- > your main session's. Run your session on Opus 5.
632
+ > your main session's. Every band from score 41 needs an Opus 5.5 main session.
622
633
 
623
634
  **Full detail — the bands, `opus5_only`, and the tier guard `orc init` installs:
624
635
  [guides/model-selection.md](guides/model-selection.md).**
@@ -649,13 +660,13 @@ clobbers. `orc ui` ▸ Settings edits the same keys through the same validators.
649
660
 
650
661
  ```
651
662
  templates/
652
- ├── skills/ 31 skill folders, 38 SKILL.md files (a lane may ship subskills) —
663
+ ├── skills/ 34 skill folders, 41 SKILL.md files (a lane may ship subskills) —
653
664
  │ the lanes above, plus the ones with no command of their own:
654
665
  │ context-combiner, orc-advisor, orc-judge, orc-analyze-mini,
655
666
  │ and _shared/ (cross-lane contract prose)
656
- ├── commands/ 29 slash commands
667
+ ├── commands/ 33 slash commands
657
668
  ├── hooks/ effort guard (PreToolUse) · statusline warning · behavior trace
658
- └── agents/ 48 model-pinned subagents + MODEL-MAPPING.md
669
+ └── agents/ 50 model-pinned subagents + MODEL-MAPPING.md
659
670
  bin/cli.js installer, config editor, flow composer, run-state reader, and
660
671
  the deterministic half of every lane. Every read speaks --json
661
672
  bin/graph*.js the code graph: store, extraction, resolution + its cache and
@@ -730,25 +741,30 @@ a current audit: [EVAL-REPORT.md](EVAL-REPORT.md).
730
741
  **Full history: [CHANGELOG.md](CHANGELOG.md)** — or `orc changelog`, which prints
731
742
  only what is newer than the version you have.
732
743
 
733
- ### v2.0.2 — the lanes record what you answered, and the reviewer always gets the card _(2026-09-28)_
734
-
735
- **A patch from the live evals.** Habits now really work in a live run, and the reviewer
736
- always gets its card:
744
+ ### v2.1.1 — the quick and fast lanes move to Sonnet 5 _(2026-10-02)_
737
745
 
738
- - **Your answers are recorded correctly.** `orc trace write` checks every answer as it
739
- arrives and hands a wrong one back with the right ids. At the end of a run it names
740
- any question nobody recorded.
741
- - **The reviewer always gets the gotcha card.** A hook hands it to every reviewer,
742
- verifier and judge, so a lane that forgets it cannot lose it.
743
- - **A run that writes no trace is stopped once** and told how to fix it.
744
- - **`/orc-quick` always takes the undo snapshot.**
745
- - **The compact rules card is on by default for `/orc-mini` and `/orc-fast`** (eval E4 passed).
746
+ - **`/orc-quick` offers Sonnet 5 medium and Opus 5.5 low.** For code:
747
+ `orc-executor-sonnet-5-med` or `orc-executor-opus-5-low`. For a question:
748
+ `orc-recon-sonnet-5-med` or `orc-recon-opus-5-low`.
749
+ - **`/orc-fast` uses `orc-executor-sonnet-5-med`** (it used Sonnet 4.6 high). Run it
750
+ from a Sonnet 5 or Opus main session.
751
+ - **`/orc` examples and the "cheap subagent" rule name Sonnet 5.** Sonnet 4.6 stays
752
+ only for the graph noter, the scout, `orc diy` choices and opt-in names.
753
+ - **The recon agent has a new name:** `orc-recon-sonnet-4-6-med` →
754
+ `orc-recon-sonnet-5-med`.
755
+ - **`orc extra`:** the `quick-executor` slot uses `orc-executor-sonnet-5-med`. The
756
+ `extra_fallback_agent` options show `orc-executor-sonnet-5-high` (not Sonnet 4.6).
746
757
 
747
- **What you have to do:** `orc update`.
758
+ **What you have to do:** `orc update --prune`. If a script names
759
+ `orc-recon-sonnet-4-6-med`, change it to `orc-recon-sonnet-5-med`.
748
760
 
749
761
  <details>
750
- <summary><strong>Earlier releases</strong> — 124 of them, titles only. Full text in <a href="CHANGELOG.md">CHANGELOG.md</a>.</summary>
762
+ <summary><strong>Earlier releases</strong> — 128 of them, titles only. Full text in <a href="CHANGELOG.md">CHANGELOG.md</a>.</summary>
751
763
 
764
+ - **v2.1.0** — the review counts, the score table moves up, and old logs can go · _2026-10-02_
765
+ - **v2.0.4** — design the status line to the last cell · _2026-09-30_
766
+ - **v2.0.3** — the status line you designed is the status line you see · _2026-09-30_
767
+ - **v2.0.2** — the lanes record what you answered, and the reviewer always gets the card · _2026-09-28_
752
768
  - **v2.0.1** — the trace keeps the gate name, and the lanes find the habits rule · _2026-09-27_
753
769
  - **v2.0.0** — the coding lanes remember what you fixed and how you work · _2026-09-27_
754
770
  - **v1.9.2** — the code graph gets its own tab, and Opus 5 becomes Opus 5.5 · _2026-09-23_
@@ -1,206 +1,205 @@
1
- #!/usr/bin/env node
2
- "use strict";
3
- /**
4
- * Agent generator. Two sets, one rule: a file that differs from its siblings
5
- * ONLY in frontmatter and the "You are … (<model>, <effort>)" line is GENERATED.
6
- *
7
- * 1. The 10 executors — agents-src/executor.template.md × VARIANTS
8
- * (name/model/effort + score band).
9
- * 2. The 8 model-twin pairs (v2.0.0 T15) — agents-src/twins/<family>.template.md
10
- * × TWINS (name/model/effort/description + the `who` line). One body per
11
- * family: before T15 each pair was two hand-kept copies that nothing
12
- * checked against each other.
13
- *
14
- * Edit the template (or the table), run `npm run build:agents`, and every copy
15
- * is stamped out identically. A model change is still a RENAME: a new row, not
16
- * an edited one.
17
- *
18
- * Modes:
19
- * node bin/build-agents.js write both sets into templates/agents/
20
- * node bin/build-agents.js --check fail if any generated file drifted from
21
- * its template (runs in `npm run verify`
22
- * and prepack — a hand-edit to a generated
23
- * agent file fails the build)
24
- */
25
- const fs = require("fs");
26
- const path = require("path");
27
-
28
- const ROOT = path.join(__dirname, "..");
29
- const TEMPLATE = path.join(ROOT, "agents-src", "executor.template.md");
30
- const OUT_DIR = path.join(ROOT, "templates", "agents");
31
-
32
- // Score→model bands mirror templates/agents/MODEL-MAPPING.md and the single
33
- // score→model table in skills/orc/config.md (documented drift — change
34
- // together). `effort: null` = a model with NO effort ladder (haiku): the
35
- // generator omits the `effort:` frontmatter line entirely.
36
- //
37
- // v1.0.0 W4 — the table is SIX bands, not eight, and the `opus5_only` ladder is
38
- // TWO, not three. Four of these agents are named by NO default row:
39
- // names them, and they ship anyway (D14). They stay reachable through
40
- // `rubric_bands_override`, `orc diy` `fixed_executor` and `extra_fallback_agent`,
41
- // and deleting a generated file that no row happens to name today would be a
42
- // rename-shaped migration for what is only a table change. Their `band` string
43
- // says so rather than naming a range that no longer exists — a band label that
44
- // lies is worse than one that admits it is unreachable by default.
45
- const VARIANTS = [
46
- { name: "orc-executor-opus-5-med", model: "claude-opus-5-5", effort: "medium", band: "highest-complexity [90,100]" },
47
- { name: "orc-executor-opus-5-low", model: "claude-opus-5-5", effort: "low", band: "upper-complexity [65,90)" },
48
- { name: "orc-executor-sonnet-5-high", model: "claude-sonnet-5", effort: "high", band: "mid-complexity [55,65)" },
49
- { name: "orc-executor-sonnet-4-6-high", model: "claude-sonnet-4-6", effort: "high", band: "low-mid-complexity [40,55)" },
50
- { name: "orc-executor-sonnet-4-6-med", model: "claude-sonnet-4-6", effort: "medium", band: "low-complexity [30,40)" },
51
- { name: "orc-executor-haiku-4-5", model: "claude-haiku-4-5", effort: null, band: "lowest-complexity [0,30)" },
52
- // NAMED BY NO BAND since v1.0.0 W4 (D14) — kept on disk, dispatched only when a
53
- // user names one explicitly.
54
- { name: "orc-executor-opus-5-high", model: "claude-opus-5-5", effort: "high", band: "none (opt-in only, see MODEL-MAPPING.md)" },
55
- { name: "orc-executor-opus-4-8-high", model: "claude-opus-4-8", effort: "high", band: "none (opt-in only, see MODEL-MAPPING.md)" },
56
- { name: "orc-executor-opus-4-7-high", model: "claude-opus-4-7", effort: "high", band: "none (opt-in only, see MODEL-MAPPING.md)" },
57
- { name: "orc-executor-opus-4-7-med", model: "claude-opus-4-7", effort: "medium", band: "none (opt-in only, see MODEL-MAPPING.md)" },
58
- ];
59
-
60
- // v2.0.0 T15 — the model twins. Each family is one template under
61
- // agents-src/twins/; a row fills five placeholders: {{NAME}}, {{MODEL}},
62
- // {{EFFORT}}, {{DESC}} (the one-line frontmatter description) and {{WHO}} (the
63
- // "(Sonnet 5, high)" part of the "You are …" line — absent for a family whose
64
- // body never names its own model). The pair is a runtime choice (`opus5_only`,
65
- // the recon gate, the wiki tier ladder), so both halves always ship.
66
- const TWIN_DIR = path.join(ROOT, "agents-src", "twins");
67
- const TWINS = [
68
- { family: "analyze-mini", variants: [
69
- { name: "orc-analyze-mini-sonnet-5-high", model: "claude-sonnet-5", effort: "high", who: "Sonnet 5, high",
70
- desc: "ORC mini System Analyst — claude-sonnet-5, high effort. Dispatched by /orc-mini and /orc-analyze-mini at analysis: single pass, no deep mode, no scouts." },
71
- { name: "orc-analyze-mini-opus-5-med", model: "claude-opus-5-5", effort: "medium", who: "Opus 5.5, medium",
72
- desc: "ORC mini System Analyst — claude-opus-5-5, medium effort. Dispatched by /orc-mini and /orc-analyze-mini, instead of orc-analyze-mini-sonnet-5-high when `opus5_only: true`." },
73
- ] },
74
- { family: "planner-mini", variants: [
75
- { name: "orc-planner-mini-sonnet-5-high", model: "claude-sonnet-5", effort: "high", who: "Sonnet 5, high",
76
- desc: "ORC mini Requirement Planner — claude-sonnet-5, high effort. Dispatched by /orc-mini at planning." },
77
- { name: "orc-planner-mini-opus-5-med", model: "claude-opus-5-5", effort: "medium", who: "Opus 5.5, medium",
78
- desc: "ORC mini Requirement Planner — claude-opus-5-5, medium effort. Dispatched by /orc-mini at planning, instead of orc-planner-mini-sonnet-5-high when `opus5_only: true`." },
79
- ] },
80
- { family: "pattern-codifier", variants: [
81
- { name: "orc-pattern-codifier-sonnet-5-high", model: "claude-sonnet-5", effort: "high",
82
- desc: "ORC Pattern Codifier — claude-sonnet-5, high effort. Dispatched by orc-pattern (lazy /orc miss, eager orc-wiki, or manual /orc-pattern) for ONE language." },
83
- { name: "orc-pattern-codifier-opus-5-med", model: "claude-opus-5-5", effort: "medium",
84
- desc: "ORC Pattern Codifier — claude-opus-5-5, medium effort. Dispatched by orc-pattern, instead of orc-pattern-codifier-sonnet-5-high when `opus5_only: true`." },
85
- ] },
86
- { family: "recon", variants: [
87
- { name: "orc-recon-sonnet-4-6-med", model: "claude-sonnet-4-6", effort: "medium", who: "Sonnet 4.6, medium",
88
- desc: "ORC Recon — claude-sonnet-4-6, medium effort. Dispatched by /orc-quick at the dispatch gate, for a read-only question. It never edits." },
89
- { name: "orc-recon-opus-5-low", model: "claude-opus-5-5", effort: "low", who: "Opus 5.5, low",
90
- desc: "ORC Recon — claude-opus-5-5, low effort. Dispatched by /orc-quick at the dispatch gate, for a WIDE or SUBTLE read-only question. It never edits." },
91
- ] },
92
- { family: "retro", variants: [
93
- { name: "orc-retro-sonnet-5-high", model: "claude-sonnet-5", effort: "high", who: "Sonnet 5, high",
94
- desc: "ORC Retro miner — claude-sonnet-5, high effort. Dispatched by /orc-retro to mine the behavior traces. Read-only, report-only." },
95
- { name: "orc-retro-opus-5-med", model: "claude-opus-5-5", effort: "medium", who: "Opus 5.5, medium",
96
- desc: "ORC Retro miner — claude-opus-5-5, medium effort. Dispatched by /orc-retro, instead of orc-retro-sonnet-5-high when `opus5_only: true`. Read-only." },
97
- ] },
98
- { family: "scout", variants: [
99
- { name: "orc-scout-sonnet-4-6-high", model: "claude-sonnet-4-6", effort: "high", who: "Sonnet 4.6, high",
100
- desc: "ORC Code Scout — claude-sonnet-4-6, high effort. Dispatched by orc in the analyst's DEEP mode (≤max_scouts in parallel), ONE coverage area each." },
101
- { name: "orc-scout-opus-5-low", model: "claude-opus-5-5", effort: "low", who: "Opus 5.5, low",
102
- desc: "ORC Code Scout — claude-opus-5-5, low effort. Dispatched by orc in the analyst's DEEP mode, instead of orc-scout-sonnet-4-6-high when `opus5_only: true`." },
103
- ] },
104
- { family: "claude-writer", variants: [
105
- { name: "orc-claude-writer-opus-4-8-high", model: "claude-opus-4-8", effort: "high", who: "Opus 4.8, high",
106
- desc: "ORC CLAUDE.md Writer — claude-opus-4-8, high effort. Dispatched by /orc-claude to create, update or refresh the repo-root CLAUDE.md." },
107
- { name: "orc-claude-writer-opus-5-med", model: "claude-opus-5-5", effort: "medium", who: "Opus 5.5, medium",
108
- desc: "ORC CLAUDE.md Writer — claude-opus-5-5, medium effort. Dispatched by /orc-claude, instead of orc-claude-writer-opus-4-8-high when `opus5_only: true`." },
109
- ] },
110
- { family: "wiki-scanner", variants: [
111
- { name: "orc-wiki-scanner-opus-4-8-high", model: "claude-opus-4-8", effort: "high",
112
- desc: "ORC Wiki Scanner — claude-opus-4-8, high effort. Dispatched by orc-wiki per scan-task (DEEP tier), ONE coverage area each." },
113
- { name: "orc-wiki-scanner-opus-5-med", model: "claude-opus-5-5", effort: "medium",
114
- desc: "ORC Wiki Scanner — claude-opus-5-5, medium effort. Dispatched by orc-wiki per scan-task, for BOTH tiers when `opus5_only: true`." },
115
- ] },
116
- ];
117
-
118
-
119
- // Line endings are NOT content here: .gitattributes stores these files with LF
120
- // and checks them out native, so a Windows worktree is CRLF and a Linux one LF.
121
- // The injected frontmatter line therefore borrows the TEMPLATE's own ending, and
122
- // --check compares LF-normalized text — otherwise the guard fails on Windows for
123
- // a difference git itself does not record.
124
- const eolOf = (s) => (/\r\n/.test(s) ? "\r\n" : "\n");
125
- const lf = (s) => s.replace(/\r\n/g, "\n");
126
-
127
- function render(template, v) {
128
- const effortDesc = v.effort ? `, ${v.effort} effort` : " (no effort ladder)";
129
- const effortFm = v.effort ? `effort: ${v.effort}${eolOf(template)}` : "";
130
- return template
131
- .replace(/\{\{NAME\}\}/g, v.name)
132
- .replace(/\{\{MODEL\}\}/g, v.model)
133
- .replace(/\{\{EFFORT_DESC\}\}/g, effortDesc)
134
- .replace(/\{\{EFFORT_FM\}\}/g, effortFm)
135
- .replace(/\{\{BAND\}\}/g, v.band);
136
- }
137
-
138
- function renderTwin(template, v) {
139
- return template
140
- .replace(/\{\{NAME\}\}/g, v.name)
141
- .replace(/\{\{MODEL\}\}/g, v.model)
142
- .replace(/\{\{EFFORT\}\}/g, v.effort)
143
- .replace(/\{\{DESC\}\}/g, v.desc)
144
- .replace(/\{\{WHO\}\}/g, v.who || "");
145
- }
146
-
147
- // Every [template, row, renderer] the generator owns, executors first.
148
- function jobs() {
149
- const template = fs.readFileSync(TEMPLATE, "utf8");
150
- if (/\{\{(?!NAME|MODEL|EFFORT_DESC|EFFORT_FM|BAND)\w/.test(template)) {
151
- console.error("❌ build-agents: unknown {{placeholder}} in executor.template.md");
152
- process.exit(1);
153
- }
154
- const out = VARIANTS.map((v) => ({ v, out: render(template, v), src: "agents-src/executor.template.md" }));
155
- for (const fam of TWINS) {
156
- const src = "agents-src/twins/" + fam.family + ".template.md";
157
- const t = fs.readFileSync(path.join(TWIN_DIR, fam.family + ".template.md"), "utf8");
158
- if (/\{\{(?!(?:NAME|MODEL|EFFORT|DESC|WHO)\}\})\w/.test(t)) {
159
- console.error("❌ build-agents: unknown {{placeholder}} in " + src);
160
- process.exit(1);
161
- }
162
- if (/\{\{WHO\}\}/.test(t) !==fam.variants.every((v) => v.who)) {
163
- console.error("❌ build-agents: " + src + " and its TWINS rows disagree about {{WHO}}");
164
- process.exit(1);
165
- }
166
- for (const v of fam.variants) out.push({ v, out: renderTwin(t, v), src });
167
- }
168
- return out;
169
- }
170
-
171
- function main() {
172
- const checkMode = process.argv.includes("--check");
173
- const all = jobs();
174
- let drifted = 0;
175
- for (const { v, out, src } of all) {
176
- const dest = path.join(OUT_DIR, v.name + ".md");
177
- if (checkMode) {
178
- const current = fs.existsSync(dest) ? fs.readFileSync(dest, "utf8") : null;
179
- if (current === null || lf(current) !== lf(out)) {
180
- drifted++;
181
- console.error(
182
- `❌ generated agent drifted: templates/agents/${v.name}.md\n` +
183
- ` This agent is GENERATED — edit ${src}\n` +
184
- ` (or the VARIANTS / TWINS table in bin/build-agents.js) and run: npm run build:agents`
185
- );
186
- }
187
- } else {
188
- fs.writeFileSync(dest, out);
189
- console.log(` gen templates/agents/${v.name}.md`);
190
- }
191
- }
192
-
193
- if (checkMode) {
194
- if (drifted) process.exit(1);
195
- const twins = all.length - VARIANTS.length;
196
- console.log(
197
- `✅ ORC executor agents OK — ${VARIANTS.length} files match the template; ${twins} model twins match agents-src/twins/.`
198
- );
199
- }
200
- }
201
-
202
- // Required by test/payload.test.js, which checks the line-ending rule above
203
- // without writing to templates/agents/.
204
- module.exports = { VARIANTS, TWINS, render, renderTwin, lf, eolOf };
205
-
206
- if (require.main === module) main();
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ /**
4
+ * Agent generator. Two sets, one rule: a file that differs from its siblings
5
+ * ONLY in frontmatter and the "You are … (<model>, <effort>)" line is GENERATED.
6
+ *
7
+ * 1. The 10 executors — agents-src/executor.template.md × VARIANTS
8
+ * (name/model/effort + score band).
9
+ * 2. The 8 model-twin pairs (v2.0.0 T15) — agents-src/twins/<family>.template.md
10
+ * × TWINS (name/model/effort/description + the `who` line). One body per
11
+ * family: before T15 each pair was two hand-kept copies that nothing
12
+ * checked against each other.
13
+ *
14
+ * Edit the template (or the table), run `npm run build:agents`, and every copy
15
+ * is stamped out identically. A model change is still a RENAME: a new row, not
16
+ * an edited one.
17
+ *
18
+ * Modes:
19
+ * node bin/build-agents.js write both sets into templates/agents/
20
+ * node bin/build-agents.js --check fail if any generated file drifted from
21
+ * its template (runs in `npm run verify`
22
+ * and prepack — a hand-edit to a generated
23
+ * agent file fails the build)
24
+ */
25
+ const fs = require("fs");
26
+ const path = require("path");
27
+
28
+ const ROOT = path.join(__dirname, "..");
29
+ const TEMPLATE = path.join(ROOT, "agents-src", "executor.template.md");
30
+ const OUT_DIR = path.join(ROOT, "templates", "agents");
31
+
32
+ // Score→model bands mirror templates/agents/MODEL-MAPPING.md and the single
33
+ // score→model table in skills/orc/config.md (documented drift — change
34
+ // together). `effort: null` = a model with NO effort ladder (haiku): the
35
+ // generator omits the `effort:` frontmatter line entirely.
36
+ //
37
+ // v1.0.0 W4 — the table was SIX bands, not eight, and the `opus5_only` ladder is
38
+ // TWO, not three. v2.1.0 made the table FIVE bands (Sonnet 5 low/med/high, then
39
+ // Opus 5.5 low/med). Seven of these agents are named by NO default row:
40
+ // names them, and they ship anyway (D14). They stay reachable through
41
+ // `rubric_bands_override`, `orc diy` `fixed_executor` and `extra_fallback_agent`,
42
+ // and deleting a generated file that no row happens to name today would be a
43
+ // rename-shaped migration for what is only a table change. Their `band` string
44
+ // says so rather than naming a range that no longer exists — a band label that
45
+ // lies is worse than one that admits it is unreachable by default.
46
+ const VARIANTS = [
47
+ { name: "orc-executor-opus-5-med", model: "claude-opus-5-5", effort: "medium", band: "highest-complexity [90,100]" },
48
+ { name: "orc-executor-opus-5-low", model: "claude-opus-5-5", effort: "low", band: "upper-complexity [41,90)" },
49
+ { name: "orc-executor-sonnet-5-high", model: "claude-sonnet-5-5", effort: "high", band: "mid-complexity [31,41)" },
50
+ { name: "orc-executor-sonnet-5-med", model: "claude-sonnet-5-5", effort: "medium", band: "low-complexity [21,31)" },
51
+ { name: "orc-executor-sonnet-5-low", model: "claude-sonnet-5-5", effort: "low", band: "lowest-complexity [0,21)" },
52
+ // NAMED BY NO BAND since v1.0.0 W4 / v2.1.0 (D14) — kept on disk, dispatched
53
+ // only when a user names one explicitly.
54
+ { name: "orc-executor-sonnet-4-6-high", model: "claude-sonnet-4-6", effort: "high", band: "none (opt-in only, see MODEL-MAPPING.md)" },
55
+ { name: "orc-executor-sonnet-4-6-med", model: "claude-sonnet-4-6", effort: "medium", band: "none (opt-in only, see MODEL-MAPPING.md)" },
56
+ { name: "orc-executor-haiku-4-5", model: "claude-haiku-4-5", effort: null, band: "none (opt-in only, see MODEL-MAPPING.md)" },
57
+ { name: "orc-executor-opus-5-high", model: "claude-opus-5-5", effort: "high", band: "none (opt-in only, see MODEL-MAPPING.md)" },
58
+ { name: "orc-executor-opus-4-8-high", model: "claude-opus-4-8", effort: "high", band: "none (opt-in only, see MODEL-MAPPING.md)" },
59
+ { name: "orc-executor-opus-4-7-high", model: "claude-opus-4-7", effort: "high", band: "none (opt-in only, see MODEL-MAPPING.md)" },
60
+ { name: "orc-executor-opus-4-7-med", model: "claude-opus-4-7", effort: "medium", band: "none (opt-in only, see MODEL-MAPPING.md)" },
61
+ ];
62
+
63
+ // v2.0.0 T15 — the model twins. Each family is one template under
64
+ // agents-src/twins/; a row fills five placeholders: {{NAME}}, {{MODEL}},
65
+ // {{EFFORT}}, {{DESC}} (the one-line frontmatter description) and {{WHO}} (the
66
+ // "(Sonnet 5, high)" part of the "You are …" line — absent for a family whose
67
+ // body never names its own model). The pair is a runtime choice (`opus5_only`,
68
+ // the recon gate, the wiki tier ladder), so both halves always ship.
69
+ const TWIN_DIR = path.join(ROOT, "agents-src", "twins");
70
+ const TWINS = [
71
+ { family: "analyze-mini", variants: [
72
+ { name: "orc-analyze-mini-sonnet-5-high", model: "claude-sonnet-5-5", effort: "high", who: "Sonnet 5, high",
73
+ desc: "ORC mini System Analyst — claude-sonnet-5-5, high effort. Dispatched by /orc-mini and /orc-analyze-mini at analysis: single pass, no deep mode, no scouts." },
74
+ { name: "orc-analyze-mini-opus-5-med", model: "claude-opus-5-5", effort: "medium", who: "Opus 5.5, medium",
75
+ desc: "ORC mini System Analyst — claude-opus-5-5, medium effort. Dispatched by /orc-mini and /orc-analyze-mini, instead of orc-analyze-mini-sonnet-5-high when `opus5_only: true`." },
76
+ ] },
77
+ { family: "planner-mini", variants: [
78
+ { name: "orc-planner-mini-sonnet-5-high", model: "claude-sonnet-5-5", effort: "high", who: "Sonnet 5, high",
79
+ desc: "ORC mini Requirement Planner — claude-sonnet-5-5, high effort. Dispatched by /orc-mini at planning." },
80
+ { name: "orc-planner-mini-opus-5-med", model: "claude-opus-5-5", effort: "medium", who: "Opus 5.5, medium",
81
+ desc: "ORC mini Requirement Planner — claude-opus-5-5, medium effort. Dispatched by /orc-mini at planning, instead of orc-planner-mini-sonnet-5-high when `opus5_only: true`." },
82
+ ] },
83
+ { family: "pattern-codifier", variants: [
84
+ { name: "orc-pattern-codifier-sonnet-5-high", model: "claude-sonnet-5-5", effort: "high",
85
+ desc: "ORC Pattern Codifier — claude-sonnet-5-5, high effort. Dispatched by orc-pattern (lazy /orc miss, eager orc-wiki, or manual /orc-pattern) for ONE language." },
86
+ { name: "orc-pattern-codifier-opus-5-med", model: "claude-opus-5-5", effort: "medium",
87
+ desc: "ORC Pattern Codifier — claude-opus-5-5, medium effort. Dispatched by orc-pattern, instead of orc-pattern-codifier-sonnet-5-high when `opus5_only: true`." },
88
+ ] },
89
+ { family: "recon", variants: [
90
+ { name: "orc-recon-sonnet-5-med", model: "claude-sonnet-5-5", effort: "medium", who: "Sonnet 5, medium",
91
+ desc: "ORC Recon — claude-sonnet-5-5, medium effort. Dispatched by /orc-quick at the dispatch gate, for a read-only question. It never edits." },
92
+ { name: "orc-recon-opus-5-low", model: "claude-opus-5-5", effort: "low", who: "Opus 5.5, low",
93
+ desc: "ORC Recon — claude-opus-5-5, low effort. Dispatched by /orc-quick at the dispatch gate, for a WIDE or SUBTLE read-only question. It never edits." },
94
+ ] },
95
+ { family: "retro", variants: [
96
+ { name: "orc-retro-sonnet-5-high", model: "claude-sonnet-5-5", effort: "high", who: "Sonnet 5, high",
97
+ desc: "ORC Retro miner — claude-sonnet-5-5, high effort. Dispatched by /orc-retro to mine the behavior traces. Read-only, report-only." },
98
+ { name: "orc-retro-opus-5-med", model: "claude-opus-5-5", effort: "medium", who: "Opus 5.5, medium",
99
+ desc: "ORC Retro miner — claude-opus-5-5, medium effort. Dispatched by /orc-retro, instead of orc-retro-sonnet-5-high when `opus5_only: true`. Read-only." },
100
+ ] },
101
+ { family: "scout", variants: [
102
+ { name: "orc-scout-sonnet-4-6-high", model: "claude-sonnet-4-6", effort: "high", who: "Sonnet 4.6, high",
103
+ desc: "ORC Code Scout — claude-sonnet-4-6, high effort. Dispatched by orc in the analyst's DEEP mode (≤max_scouts in parallel), ONE coverage area each." },
104
+ { name: "orc-scout-opus-5-low", model: "claude-opus-5-5", effort: "low", who: "Opus 5.5, low",
105
+ desc: "ORC Code Scout — claude-opus-5-5, low effort. Dispatched by orc in the analyst's DEEP mode, instead of orc-scout-sonnet-4-6-high when `opus5_only: true`." },
106
+ ] },
107
+ { family: "claude-writer", variants: [
108
+ { name: "orc-claude-writer-opus-5-low", model: "claude-opus-5-5", effort: "low", who: "Opus 5.5, low",
109
+ desc: "ORC CLAUDE.md Writer — claude-opus-5-5, low effort. Dispatched by /orc-claude to create, update or refresh the repo-root CLAUDE.md." },
110
+ ] },
111
+ { family: "wiki-scanner", variants: [
112
+ { name: "orc-wiki-scanner-opus-5-low", model: "claude-opus-5-5", effort: "low",
113
+ desc: "ORC Wiki Scanner — claude-opus-5-5, low effort. Dispatched by orc-wiki per scan-task (DEEP tier), ONE coverage area each." },
114
+ ] },
115
+ ];
116
+
117
+
118
+ // Line endings are NOT content here: .gitattributes stores these files with LF
119
+ // and checks them out native, so a Windows worktree is CRLF and a Linux one LF.
120
+ // The injected frontmatter line therefore borrows the TEMPLATE's own ending, and
121
+ // --check compares LF-normalized text — otherwise the guard fails on Windows for
122
+ // a difference git itself does not record.
123
+ const eolOf = (s) => (/\r\n/.test(s) ? "\r\n" : "\n");
124
+ const lf = (s) => s.replace(/\r\n/g, "\n");
125
+
126
+ function render(template, v) {
127
+ const effortDesc = v.effort ? `, ${v.effort} effort` : " (no effort ladder)";
128
+ const effortFm = v.effort ? `effort: ${v.effort}${eolOf(template)}` : "";
129
+ return template
130
+ .replace(/\{\{NAME\}\}/g, v.name)
131
+ .replace(/\{\{MODEL\}\}/g, v.model)
132
+ .replace(/\{\{EFFORT_DESC\}\}/g, effortDesc)
133
+ .replace(/\{\{EFFORT_FM\}\}/g, effortFm)
134
+ .replace(/\{\{BAND\}\}/g, v.band);
135
+ }
136
+
137
+ function renderTwin(template, v) {
138
+ return template
139
+ .replace(/\{\{NAME\}\}/g, v.name)
140
+ .replace(/\{\{MODEL\}\}/g, v.model)
141
+ .replace(/\{\{EFFORT\}\}/g, v.effort)
142
+ .replace(/\{\{DESC\}\}/g, v.desc)
143
+ .replace(/\{\{WHO\}\}/g, v.who || "");
144
+ }
145
+
146
+ // Every [template, row, renderer] the generator owns, executors first.
147
+ function jobs() {
148
+ const template = fs.readFileSync(TEMPLATE, "utf8");
149
+ if (/\{\{(?!NAME|MODEL|EFFORT_DESC|EFFORT_FM|BAND)\w/.test(template)) {
150
+ console.error("❌ build-agents: unknown {{placeholder}} in executor.template.md");
151
+ process.exit(1);
152
+ }
153
+ const out = VARIANTS.map((v) => ({ v, out: render(template, v), src: "agents-src/executor.template.md" }));
154
+ for (const fam of TWINS) {
155
+ const src = "agents-src/twins/" + fam.family + ".template.md";
156
+ const t = fs.readFileSync(path.join(TWIN_DIR, fam.family + ".template.md"), "utf8");
157
+ if (/\{\{(?!(?:NAME|MODEL|EFFORT|DESC|WHO)\}\})\w/.test(t)) {
158
+ console.error("❌ build-agents: unknown {{placeholder}} in " + src);
159
+ process.exit(1);
160
+ }
161
+ if (/\{\{WHO\}\}/.test(t) !==fam.variants.every((v) => v.who)) {
162
+ console.error("❌ build-agents: " + src + " and its TWINS rows disagree about {{WHO}}");
163
+ process.exit(1);
164
+ }
165
+ for (const v of fam.variants) out.push({ v, out: renderTwin(t, v), src });
166
+ }
167
+ return out;
168
+ }
169
+
170
+ function main() {
171
+ const checkMode = process.argv.includes("--check");
172
+ const all = jobs();
173
+ let drifted = 0;
174
+ for (const { v, out, src } of all) {
175
+ const dest = path.join(OUT_DIR, v.name + ".md");
176
+ if (checkMode) {
177
+ const current = fs.existsSync(dest) ? fs.readFileSync(dest, "utf8") : null;
178
+ if (current === null || lf(current) !== lf(out)) {
179
+ drifted++;
180
+ console.error(
181
+ `❌ generated agent drifted: templates/agents/${v.name}.md\n` +
182
+ ` This agent is GENERATED — edit ${src}\n` +
183
+ ` (or the VARIANTS / TWINS table in bin/build-agents.js) and run: npm run build:agents`
184
+ );
185
+ }
186
+ } else {
187
+ fs.writeFileSync(dest, out);
188
+ console.log(` gen templates/agents/${v.name}.md`);
189
+ }
190
+ }
191
+
192
+ if (checkMode) {
193
+ if (drifted) process.exit(1);
194
+ const twins = all.length - VARIANTS.length;
195
+ console.log(
196
+ `✅ ORC executor agents OK — ${VARIANTS.length} files match the template; ${twins} model twins match agents-src/twins/.`
197
+ );
198
+ }
199
+ }
200
+
201
+ // Required by test/payload.test.js, which checks the line-ending rule above
202
+ // without writing to templates/agents/.
203
+ module.exports = { VARIANTS, TWINS, render, renderTwin, lf, eolOf };
204
+
205
+ if (require.main === module) main();