@azure-id/orc 2.1.1 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/CHANGELOG.md +320 -0
  2. package/README.md +48 -23
  3. package/bin/build-agents.js +207 -205
  4. package/bin/cli.js +48188 -47417
  5. package/bin/fix.js +21 -18
  6. package/bin/graph-extract.js +314 -6
  7. package/bin/graph-gain.js +83 -2
  8. package/bin/graph-map.js +23 -1
  9. package/bin/graph-notes.js +1 -1
  10. package/bin/graph-query.js +1362 -37
  11. package/bin/graph-resolve.js +1 -1
  12. package/bin/graph-shard.js +29 -6
  13. package/bin/graph-signals.js +5 -3
  14. package/bin/graph.js +7 -3
  15. package/bin/habit.js +1 -1
  16. package/bin/pricing.json +8 -1
  17. package/bin/trace-write.js +40 -11
  18. package/bin/verify-contracts.js +5661 -5552
  19. package/bin/verify-package.js +716 -716
  20. package/bin/webui/api.js +8 -0
  21. package/bin/webui/fixtures/extra.js +2036 -2036
  22. package/bin/webui/fixtures/flow.js +1 -1
  23. package/bin/webui/fixtures/index.js +2 -2
  24. package/bin/webui/fixtures/knowledge.js +8 -1
  25. package/bin/webui/fixtures/pact.js +112 -111
  26. package/bin/webui/fixtures/stats.js +2 -2
  27. package/bin/webui/i18n/en/knowledge.json +2 -0
  28. package/bin/webui/i18n/id/knowledge.json +2 -0
  29. package/bin/webui/js/panels/knowledge.js +17 -0
  30. package/guides/code-graph-benefit.md +116 -0
  31. package/guides/configuration.md +150 -0
  32. package/guides/documents.md +262 -0
  33. package/guides/extra-models.md +448 -0
  34. package/guides/habits-and-gotchas.md +152 -0
  35. package/guides/knowledge-reads.md +190 -0
  36. package/guides/model-selection.md +186 -0
  37. package/guides/rules.md +305 -0
  38. package/guides/status-line.md +344 -0
  39. package/mock-run/orc-cli.md +3 -2
  40. package/mock-run/orc-doc.md +452 -448
  41. package/mock-run/orc.md +157 -157
  42. package/package.json +2 -1
  43. package/templates/agents/MODEL-MAPPING.md +176 -176
  44. package/templates/agents/{orc-executor-haiku-4-5.md → orc-executor-haiku-5-high.md} +21 -8
  45. package/templates/agents/orc-executor-opus-4-7-high.md +17 -5
  46. package/templates/agents/orc-executor-opus-4-7-med.md +17 -5
  47. package/templates/agents/orc-executor-opus-4-8-high.md +17 -5
  48. package/templates/agents/orc-executor-opus-5-high.md +17 -5
  49. package/templates/agents/orc-executor-opus-5-low.md +17 -5
  50. package/templates/agents/orc-executor-opus-5-med.md +17 -5
  51. package/templates/agents/orc-executor-sonnet-4-6-high.md +17 -5
  52. package/templates/agents/orc-executor-sonnet-5-high.md +17 -5
  53. package/templates/agents/orc-executor-sonnet-5-low.md +17 -5
  54. package/templates/agents/orc-executor-sonnet-5-med.md +17 -5
  55. package/templates/agents/{orc-graph-noter-sonnet-4-6-med.md → orc-graph-noter-haiku-5-high.md} +4 -4
  56. package/templates/agents/orc-planner-mini-opus-5-med.md +3 -1
  57. package/templates/agents/orc-planner-mini-sonnet-5-high.md +3 -1
  58. package/templates/agents/orc-planner-opus-5-med.md +3 -1
  59. package/templates/agents/{orc-trace-writer-haiku-4-5.md → orc-trace-writer-haiku-5-high.md} +4 -3
  60. package/templates/commands/orc-doc.md +128 -128
  61. package/templates/commands/orc-export.md +51 -46
  62. package/templates/hooks/README.md +8 -3
  63. package/templates/hooks/orc-effort-guard.js +105 -27
  64. package/templates/hooks/orc-graph-hook.js +252 -4
  65. package/templates/hooks/orc-statusline.js +13 -6
  66. package/templates/hooks/orc-trace.js +1 -1
  67. package/templates/skills/_shared/README.md +2 -2
  68. package/templates/skills/_shared/code-graph.md +185 -251
  69. package/templates/skills/_shared/extra-dispatch.md +1331 -1331
  70. package/templates/skills/_shared/lane-contract.md +7 -3
  71. package/templates/skills/_shared/opus5-only.md +137 -137
  72. package/templates/skills/_shared/phases/execution.md +13 -11
  73. package/templates/skills/_shared/phases/planning.md +27 -9
  74. package/templates/skills/_shared/phases/preflight.md +3 -3
  75. package/templates/skills/_shared/phases/review.md +4 -3
  76. package/templates/skills/_shared/phases/ship.md +7 -10
  77. package/templates/skills/_shared/phases/summary.md +2 -3
  78. package/templates/skills/_shared/phases/trace-verbs.md +21 -16
  79. package/templates/skills/_shared/phases/trace.md +3 -3
  80. package/templates/skills/_shared/phases/wiki-consult.md +2 -2
  81. package/templates/skills/_shared/pr-templates.md +108 -106
  82. package/templates/skills/_shared/read-ladder.md +8 -2
  83. package/templates/skills/_shared/return-validation.md +10 -6
  84. package/templates/skills/_shared/review-slice.md +1 -1
  85. package/templates/skills/_shared/stack-plan.md +135 -135
  86. package/templates/skills/_shared/wait.md +14 -2
  87. package/templates/skills/context-combiner/SKILL.md +2 -2
  88. package/templates/skills/orc/SKILL.md +227 -227
  89. package/templates/skills/orc/config.md +2 -2
  90. package/templates/skills/orc/examples/full-run-mock.md +74 -74
  91. package/templates/skills/orc/schemas/planning-output.md +3 -0
  92. package/templates/skills/orc-aftermath/SKILL.md +15 -15
  93. package/templates/skills/orc-analyze/SKILL.md +2 -2
  94. package/templates/skills/orc-analyze-mini/SKILL.md +2 -2
  95. package/templates/skills/orc-boundary/SKILL.md +15 -15
  96. package/templates/skills/orc-boundary/references/card.md +81 -78
  97. package/templates/skills/orc-boundary/references/gate.md +113 -113
  98. package/templates/skills/orc-brainstorm/SKILL.md +15 -15
  99. package/templates/skills/orc-budget/SKILL.md +15 -15
  100. package/templates/skills/orc-challenge/README.md +1 -1
  101. package/templates/skills/orc-challenge/SKILL.md +16 -16
  102. package/templates/skills/orc-challenge/examples/council-full-roster.md +1 -1
  103. package/templates/skills/orc-claude/SKILL.md +4 -4
  104. package/templates/skills/orc-claude/examples/claude-run-mock.md +1 -1
  105. package/templates/skills/orc-diy/SKILL.md +2 -2
  106. package/templates/skills/orc-diy/references/blocks/wiki.md +26 -26
  107. package/templates/skills/orc-diy/references/flow-schema.md +2 -2
  108. package/templates/skills/orc-doc/README.md +2 -2
  109. package/templates/skills/orc-doc/SKILL.md +501 -490
  110. package/templates/skills/orc-doc/examples/orc-doc-prd-run.md +12 -1
  111. package/templates/skills/orc-doc/references/chunking.md +6 -4
  112. package/templates/skills/orc-doc/references/gates.md +311 -311
  113. package/templates/skills/orc-doc/references/resume-protocol.md +229 -228
  114. package/templates/skills/orc-explain/SKILL.md +88 -88
  115. package/templates/skills/orc-export/SKILL.md +190 -187
  116. package/templates/skills/orc-fast/SKILL.md +213 -210
  117. package/templates/skills/orc-grill/SKILL.md +245 -245
  118. package/templates/skills/orc-handoff/SKILL.md +11 -11
  119. package/templates/skills/orc-learn/SKILL.md +13 -13
  120. package/templates/skills/orc-learn/examples/learn-run-mock.md +2 -2
  121. package/templates/skills/orc-mini/SKILL.md +12 -12
  122. package/templates/skills/orc-mini/examples/mini-run-mock.md +4 -5
  123. package/templates/skills/orc-pact/SKILL.md +15 -15
  124. package/templates/skills/orc-pact/references/gate.md +70 -70
  125. package/templates/skills/orc-pattern/SKILL.md +3 -3
  126. package/templates/skills/orc-poly/SKILL.md +4 -4
  127. package/templates/skills/orc-poly/examples/poly-run-mock.md +51 -51
  128. package/templates/skills/orc-pr-driver/SKILL.md +4 -3
  129. package/templates/skills/orc-pr-driver/references/orc-run-split.md +108 -99
  130. package/templates/skills/orc-pr-setup/SKILL.md +2 -2
  131. package/templates/skills/orc-quick/README.md +535 -536
  132. package/templates/skills/orc-quick/SKILL.md +29 -32
  133. package/templates/skills/orc-quick/references/context-doc.md +145 -145
  134. package/templates/skills/orc-quick/references/dispatch-gate.md +220 -220
  135. package/templates/skills/orc-quick/references/gh-mode.md +2 -1
  136. package/templates/skills/orc-quick/references/look.md +34 -12
  137. package/templates/skills/orc-retro/SKILL.md +2 -2
  138. package/templates/skills/orc-retro/examples/retro-mock.md +2 -2
  139. package/templates/skills/orc-route/SKILL.md +2 -2
  140. package/templates/skills/orc-test/SKILL.md +6 -3
  141. package/templates/skills/orc-verify/SKILL.md +2 -2
  142. package/templates/skills/orc-wait/SKILL.md +10 -2
  143. package/templates/skills/orc-wiki/SKILL.md +4 -4
  144. package/templates/skills/orc-wiki/references/pattern-prewarm.md +19 -19
  145. package/templates/agents/orc-executor-sonnet-4-6-med.md +0 -155
@@ -1,205 +1,207 @@
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();
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). Six 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
+ // v2.2.0 — `haiku-4-5` and `sonnet-4-6-med` were RENAMED into this one row
56
+ // (an identical twin would have been the result). cli.js LEGACY_AGENTS maps
57
+ // the old names for configs that still write them.
58
+ { name: "orc-executor-haiku-5-high", model: "claude-haiku-5-5", effort: "high", band: "none (opt-in only, see MODEL-MAPPING.md)" },
59
+ { name: "orc-executor-opus-5-high", model: "claude-opus-5-5", effort: "high", band: "none (opt-in only, see MODEL-MAPPING.md)" },
60
+ { name: "orc-executor-opus-4-8-high", model: "claude-opus-4-8", effort: "high", band: "none (opt-in only, see MODEL-MAPPING.md)" },
61
+ { name: "orc-executor-opus-4-7-high", model: "claude-opus-4-7", effort: "high", band: "none (opt-in only, see MODEL-MAPPING.md)" },
62
+ { name: "orc-executor-opus-4-7-med", model: "claude-opus-4-7", effort: "medium", band: "none (opt-in only, see MODEL-MAPPING.md)" },
63
+ ];
64
+
65
+ // v2.0.0 T15 — the model twins. Each family is one template under
66
+ // agents-src/twins/; a row fills five placeholders: {{NAME}}, {{MODEL}},
67
+ // {{EFFORT}}, {{DESC}} (the one-line frontmatter description) and {{WHO}} (the
68
+ // "(Sonnet 5, high)" part of the "You are …" line — absent for a family whose
69
+ // body never names its own model). The pair is a runtime choice (`opus5_only`,
70
+ // the recon gate, the wiki tier ladder), so both halves always ship.
71
+ const TWIN_DIR = path.join(ROOT, "agents-src", "twins");
72
+ const TWINS = [
73
+ { family: "analyze-mini", variants: [
74
+ { name: "orc-analyze-mini-sonnet-5-high", model: "claude-sonnet-5-5", effort: "high", who: "Sonnet 5, high",
75
+ 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." },
76
+ { name: "orc-analyze-mini-opus-5-med", model: "claude-opus-5-5", effort: "medium", who: "Opus 5.5, medium",
77
+ 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`." },
78
+ ] },
79
+ { family: "planner-mini", variants: [
80
+ { name: "orc-planner-mini-sonnet-5-high", model: "claude-sonnet-5-5", effort: "high", who: "Sonnet 5, high",
81
+ desc: "ORC mini Requirement Planner — claude-sonnet-5-5, high effort. Dispatched by /orc-mini at planning." },
82
+ { name: "orc-planner-mini-opus-5-med", model: "claude-opus-5-5", effort: "medium", who: "Opus 5.5, medium",
83
+ 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`." },
84
+ ] },
85
+ { family: "pattern-codifier", variants: [
86
+ { name: "orc-pattern-codifier-sonnet-5-high", model: "claude-sonnet-5-5", effort: "high",
87
+ 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." },
88
+ { name: "orc-pattern-codifier-opus-5-med", model: "claude-opus-5-5", effort: "medium",
89
+ 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`." },
90
+ ] },
91
+ { family: "recon", variants: [
92
+ { name: "orc-recon-sonnet-5-med", model: "claude-sonnet-5-5", effort: "medium", who: "Sonnet 5, medium",
93
+ 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." },
94
+ { name: "orc-recon-opus-5-low", model: "claude-opus-5-5", effort: "low", who: "Opus 5.5, low",
95
+ 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." },
96
+ ] },
97
+ { family: "retro", variants: [
98
+ { name: "orc-retro-sonnet-5-high", model: "claude-sonnet-5-5", effort: "high", who: "Sonnet 5, high",
99
+ desc: "ORC Retro miner — claude-sonnet-5-5, high effort. Dispatched by /orc-retro to mine the behavior traces. Read-only, report-only." },
100
+ { name: "orc-retro-opus-5-med", model: "claude-opus-5-5", effort: "medium", who: "Opus 5.5, medium",
101
+ 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." },
102
+ ] },
103
+ { family: "scout", variants: [
104
+ { name: "orc-scout-sonnet-4-6-high", model: "claude-sonnet-4-6", effort: "high", who: "Sonnet 4.6, high",
105
+ 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." },
106
+ { name: "orc-scout-opus-5-low", model: "claude-opus-5-5", effort: "low", who: "Opus 5.5, low",
107
+ 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`." },
108
+ ] },
109
+ { family: "claude-writer", variants: [
110
+ { name: "orc-claude-writer-opus-5-low", model: "claude-opus-5-5", effort: "low", who: "Opus 5.5, low",
111
+ 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." },
112
+ ] },
113
+ { family: "wiki-scanner", variants: [
114
+ { name: "orc-wiki-scanner-opus-5-low", model: "claude-opus-5-5", effort: "low",
115
+ desc: "ORC Wiki Scanner — claude-opus-5-5, low effort. Dispatched by orc-wiki per scan-task (DEEP tier), ONE coverage area each." },
116
+ ] },
117
+ ];
118
+
119
+
120
+ // Line endings are NOT content here: .gitattributes stores these files with LF
121
+ // and checks them out native, so a Windows worktree is CRLF and a Linux one LF.
122
+ // The injected frontmatter line therefore borrows the TEMPLATE's own ending, and
123
+ // --check compares LF-normalized text — otherwise the guard fails on Windows for
124
+ // a difference git itself does not record.
125
+ const eolOf = (s) => (/\r\n/.test(s) ? "\r\n" : "\n");
126
+ const lf = (s) => s.replace(/\r\n/g, "\n");
127
+
128
+ function render(template, v) {
129
+ const effortDesc = v.effort ? `, ${v.effort} effort` : " (no effort ladder)";
130
+ const effortFm = v.effort ? `effort: ${v.effort}${eolOf(template)}` : "";
131
+ return template
132
+ .replace(/\{\{NAME\}\}/g, v.name)
133
+ .replace(/\{\{MODEL\}\}/g, v.model)
134
+ .replace(/\{\{EFFORT_DESC\}\}/g, effortDesc)
135
+ .replace(/\{\{EFFORT_FM\}\}/g, effortFm)
136
+ .replace(/\{\{BAND\}\}/g, v.band);
137
+ }
138
+
139
+ function renderTwin(template, v) {
140
+ return template
141
+ .replace(/\{\{NAME\}\}/g, v.name)
142
+ .replace(/\{\{MODEL\}\}/g, v.model)
143
+ .replace(/\{\{EFFORT\}\}/g, v.effort)
144
+ .replace(/\{\{DESC\}\}/g, v.desc)
145
+ .replace(/\{\{WHO\}\}/g, v.who || "");
146
+ }
147
+
148
+ // Every [template, row, renderer] the generator owns, executors first.
149
+ function jobs() {
150
+ const template = fs.readFileSync(TEMPLATE, "utf8");
151
+ if (/\{\{(?!NAME|MODEL|EFFORT_DESC|EFFORT_FM|BAND)\w/.test(template)) {
152
+ console.error("❌ build-agents: unknown {{placeholder}} in executor.template.md");
153
+ process.exit(1);
154
+ }
155
+ const out = VARIANTS.map((v) => ({ v, out: render(template, v), src: "agents-src/executor.template.md" }));
156
+ for (const fam of TWINS) {
157
+ const src = "agents-src/twins/" + fam.family + ".template.md";
158
+ const t = fs.readFileSync(path.join(TWIN_DIR, fam.family + ".template.md"), "utf8");
159
+ if (/\{\{(?!(?:NAME|MODEL|EFFORT|DESC|WHO)\}\})\w/.test(t)) {
160
+ console.error("❌ build-agents: unknown {{placeholder}} in " + src);
161
+ process.exit(1);
162
+ }
163
+ if (/\{\{WHO\}\}/.test(t) !==fam.variants.every((v) => v.who)) {
164
+ console.error("❌ build-agents: " + src + " and its TWINS rows disagree about {{WHO}}");
165
+ process.exit(1);
166
+ }
167
+ for (const v of fam.variants) out.push({ v, out: renderTwin(t, v), src });
168
+ }
169
+ return out;
170
+ }
171
+
172
+ function main() {
173
+ const checkMode = process.argv.includes("--check");
174
+ const all = jobs();
175
+ let drifted = 0;
176
+ for (const { v, out, src } of all) {
177
+ const dest = path.join(OUT_DIR, v.name + ".md");
178
+ if (checkMode) {
179
+ const current = fs.existsSync(dest) ? fs.readFileSync(dest, "utf8") : null;
180
+ if (current === null || lf(current) !== lf(out)) {
181
+ drifted++;
182
+ console.error(
183
+ `❌ generated agent drifted: templates/agents/${v.name}.md\n` +
184
+ ` This agent is GENERATED — edit ${src}\n` +
185
+ ` (or the VARIANTS / TWINS table in bin/build-agents.js) and run: npm run build:agents`
186
+ );
187
+ }
188
+ } else {
189
+ fs.writeFileSync(dest, out);
190
+ console.log(` gen templates/agents/${v.name}.md`);
191
+ }
192
+ }
193
+
194
+ if (checkMode) {
195
+ if (drifted) process.exit(1);
196
+ const twins = all.length - VARIANTS.length;
197
+ console.log(
198
+ `✅ ORC executor agents OK — ${VARIANTS.length} files match the template; ${twins} model twins match agents-src/twins/.`
199
+ );
200
+ }
201
+ }
202
+
203
+ // Required by test/payload.test.js, which checks the line-ending rule above
204
+ // without writing to templates/agents/.
205
+ module.exports = { VARIANTS, TWINS, render, renderTwin, lf, eolOf };
206
+
207
+ if (require.main === module) main();