@moreih29/nexus-core 0.12.0 → 0.14.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 (245) hide show
  1. package/README.md +76 -57
  2. package/assets/agents/architect/body.ko.md +177 -0
  3. package/{agents → assets/agents}/architect/body.md +16 -0
  4. package/assets/agents/designer/body.ko.md +125 -0
  5. package/{agents → assets/agents}/designer/body.md +16 -0
  6. package/assets/agents/engineer/body.ko.md +106 -0
  7. package/{agents → assets/agents}/engineer/body.md +14 -0
  8. package/assets/agents/lead/body.ko.md +70 -0
  9. package/assets/agents/lead/body.md +70 -0
  10. package/assets/agents/postdoc/body.ko.md +122 -0
  11. package/{agents → assets/agents}/postdoc/body.md +16 -0
  12. package/assets/agents/researcher/body.ko.md +137 -0
  13. package/{agents → assets/agents}/researcher/body.md +15 -0
  14. package/assets/agents/reviewer/body.ko.md +138 -0
  15. package/{agents → assets/agents}/reviewer/body.md +15 -0
  16. package/assets/agents/strategist/body.ko.md +116 -0
  17. package/{agents → assets/agents}/strategist/body.md +16 -0
  18. package/assets/agents/tester/body.ko.md +195 -0
  19. package/{agents → assets/agents}/tester/body.md +15 -0
  20. package/assets/agents/writer/body.ko.md +122 -0
  21. package/{agents → assets/agents}/writer/body.md +14 -0
  22. package/assets/capability-matrix.yml +198 -0
  23. package/assets/hooks/agent-bootstrap/handler.test.ts +368 -0
  24. package/assets/hooks/agent-bootstrap/handler.ts +119 -0
  25. package/assets/hooks/agent-bootstrap/meta.yml +10 -0
  26. package/assets/hooks/agent-finalize/handler.test.ts +368 -0
  27. package/assets/hooks/agent-finalize/handler.ts +76 -0
  28. package/assets/hooks/agent-finalize/meta.yml +10 -0
  29. package/assets/hooks/capability-matrix.yml +313 -0
  30. package/assets/hooks/post-tool-telemetry/handler.test.ts +302 -0
  31. package/assets/hooks/post-tool-telemetry/handler.ts +49 -0
  32. package/assets/hooks/post-tool-telemetry/meta.yml +11 -0
  33. package/assets/hooks/prompt-router/handler.test.ts +801 -0
  34. package/assets/hooks/prompt-router/handler.ts +261 -0
  35. package/assets/hooks/prompt-router/meta.yml +11 -0
  36. package/assets/hooks/session-init/handler.test.ts +274 -0
  37. package/assets/hooks/session-init/handler.ts +30 -0
  38. package/assets/hooks/session-init/meta.yml +9 -0
  39. package/assets/lsp-servers.json +55 -0
  40. package/assets/schema/lsp-servers.schema.json +67 -0
  41. package/assets/skills/nx-init/body.ko.md +197 -0
  42. package/{skills → assets/skills}/nx-init/body.md +11 -0
  43. package/assets/skills/nx-plan/body.ko.md +361 -0
  44. package/{skills → assets/skills}/nx-plan/body.md +13 -0
  45. package/assets/skills/nx-run/body.ko.md +161 -0
  46. package/{skills → assets/skills}/nx-run/body.md +11 -0
  47. package/assets/skills/nx-sync/body.ko.md +92 -0
  48. package/{skills → assets/skills}/nx-sync/body.md +10 -0
  49. package/assets/tools/tool-name-map.yml +353 -0
  50. package/dist/assets/hooks/agent-bootstrap/handler.d.ts +4 -0
  51. package/dist/assets/hooks/agent-bootstrap/handler.d.ts.map +1 -0
  52. package/dist/assets/hooks/agent-bootstrap/handler.js +100 -0
  53. package/dist/assets/hooks/agent-bootstrap/handler.js.map +1 -0
  54. package/dist/assets/hooks/agent-finalize/handler.d.ts +4 -0
  55. package/dist/assets/hooks/agent-finalize/handler.d.ts.map +1 -0
  56. package/dist/assets/hooks/agent-finalize/handler.js +63 -0
  57. package/dist/assets/hooks/agent-finalize/handler.js.map +1 -0
  58. package/dist/assets/hooks/post-tool-telemetry/handler.d.ts +4 -0
  59. package/dist/assets/hooks/post-tool-telemetry/handler.d.ts.map +1 -0
  60. package/dist/assets/hooks/post-tool-telemetry/handler.js +40 -0
  61. package/dist/assets/hooks/post-tool-telemetry/handler.js.map +1 -0
  62. package/dist/assets/hooks/prompt-router/handler.d.ts +4 -0
  63. package/dist/assets/hooks/prompt-router/handler.d.ts.map +1 -0
  64. package/dist/assets/hooks/prompt-router/handler.js +204 -0
  65. package/dist/assets/hooks/prompt-router/handler.js.map +1 -0
  66. package/dist/assets/hooks/session-init/handler.d.ts +4 -0
  67. package/dist/assets/hooks/session-init/handler.d.ts.map +1 -0
  68. package/dist/assets/hooks/session-init/handler.js +23 -0
  69. package/dist/assets/hooks/session-init/handler.js.map +1 -0
  70. package/dist/hooks/agent-bootstrap.js +105 -0
  71. package/dist/hooks/agent-finalize.js +164 -0
  72. package/dist/hooks/post-tool-telemetry.js +55 -0
  73. package/dist/hooks/prompt-router.js +7300 -0
  74. package/dist/hooks/session-init.js +21 -0
  75. package/dist/manifests/claude-hooks.json +52 -0
  76. package/dist/manifests/codex-hooks.json +28 -0
  77. package/dist/manifests/opencode-manifest.json +44 -0
  78. package/dist/manifests/portability-report.json +87 -0
  79. package/dist/scripts/build-agents.d.ts +157 -0
  80. package/dist/scripts/build-agents.d.ts.map +1 -0
  81. package/dist/scripts/build-agents.js +737 -0
  82. package/dist/scripts/build-agents.js.map +1 -0
  83. package/dist/scripts/build-hooks.d.ts +16 -0
  84. package/dist/scripts/build-hooks.d.ts.map +1 -0
  85. package/dist/scripts/build-hooks.js +388 -0
  86. package/dist/scripts/build-hooks.js.map +1 -0
  87. package/dist/scripts/cli.d.ts +54 -0
  88. package/dist/scripts/cli.d.ts.map +1 -0
  89. package/dist/scripts/cli.js +467 -0
  90. package/dist/scripts/cli.js.map +1 -0
  91. package/dist/src/hooks/opencode-mount.d.ts +35 -0
  92. package/dist/src/hooks/opencode-mount.d.ts.map +1 -0
  93. package/dist/src/hooks/opencode-mount.js +352 -0
  94. package/dist/src/hooks/opencode-mount.js.map +1 -0
  95. package/dist/src/hooks/runtime.d.ts +37 -0
  96. package/dist/src/hooks/runtime.d.ts.map +1 -0
  97. package/dist/src/hooks/runtime.js +274 -0
  98. package/dist/src/hooks/runtime.js.map +1 -0
  99. package/dist/src/hooks/types.d.ts +196 -0
  100. package/dist/src/hooks/types.d.ts.map +1 -0
  101. package/dist/src/hooks/types.js +85 -0
  102. package/dist/src/hooks/types.js.map +1 -0
  103. package/dist/src/lsp/cache.d.ts +9 -0
  104. package/dist/src/lsp/cache.d.ts.map +1 -0
  105. package/dist/src/lsp/cache.js +216 -0
  106. package/dist/src/lsp/cache.js.map +1 -0
  107. package/dist/src/lsp/client.d.ts +24 -0
  108. package/dist/src/lsp/client.d.ts.map +1 -0
  109. package/dist/src/lsp/client.js +166 -0
  110. package/dist/src/lsp/client.js.map +1 -0
  111. package/dist/src/lsp/detect.d.ts +77 -0
  112. package/dist/src/lsp/detect.d.ts.map +1 -0
  113. package/dist/src/lsp/detect.js +116 -0
  114. package/dist/src/lsp/detect.js.map +1 -0
  115. package/dist/src/mcp/server.d.ts +5 -0
  116. package/dist/src/mcp/server.d.ts.map +1 -0
  117. package/dist/src/mcp/server.js +34 -0
  118. package/dist/src/mcp/server.js.map +1 -0
  119. package/dist/src/mcp/tools/artifact.d.ts +4 -0
  120. package/dist/src/mcp/tools/artifact.d.ts.map +1 -0
  121. package/dist/src/mcp/tools/artifact.js +36 -0
  122. package/dist/src/mcp/tools/artifact.js.map +1 -0
  123. package/dist/src/mcp/tools/history.d.ts +3 -0
  124. package/dist/src/mcp/tools/history.d.ts.map +1 -0
  125. package/dist/src/mcp/tools/history.js +29 -0
  126. package/dist/src/mcp/tools/history.js.map +1 -0
  127. package/dist/src/mcp/tools/lsp.d.ts +13 -0
  128. package/dist/src/mcp/tools/lsp.d.ts.map +1 -0
  129. package/dist/src/mcp/tools/lsp.js +225 -0
  130. package/dist/src/mcp/tools/lsp.js.map +1 -0
  131. package/dist/src/mcp/tools/plan.d.ts +3 -0
  132. package/dist/src/mcp/tools/plan.d.ts.map +1 -0
  133. package/dist/src/mcp/tools/plan.js +317 -0
  134. package/dist/src/mcp/tools/plan.js.map +1 -0
  135. package/dist/src/mcp/tools/task.d.ts +3 -0
  136. package/dist/src/mcp/tools/task.d.ts.map +1 -0
  137. package/dist/src/mcp/tools/task.js +252 -0
  138. package/dist/src/mcp/tools/task.js.map +1 -0
  139. package/dist/src/shared/invocations.d.ts +74 -0
  140. package/dist/src/shared/invocations.d.ts.map +1 -0
  141. package/dist/src/shared/invocations.js +247 -0
  142. package/dist/src/shared/invocations.js.map +1 -0
  143. package/dist/src/shared/json-store.d.ts +37 -0
  144. package/dist/src/shared/json-store.d.ts.map +1 -0
  145. package/dist/src/shared/json-store.js +163 -0
  146. package/dist/src/shared/json-store.js.map +1 -0
  147. package/dist/src/shared/mcp-utils.d.ts +3 -0
  148. package/dist/src/shared/mcp-utils.d.ts.map +1 -0
  149. package/dist/src/shared/mcp-utils.js +6 -0
  150. package/dist/src/shared/mcp-utils.js.map +1 -0
  151. package/dist/src/shared/paths.d.ts +21 -0
  152. package/dist/src/shared/paths.d.ts.map +1 -0
  153. package/dist/src/shared/paths.js +81 -0
  154. package/dist/src/shared/paths.js.map +1 -0
  155. package/dist/src/shared/tool-log.d.ts +8 -0
  156. package/dist/src/shared/tool-log.d.ts.map +1 -0
  157. package/dist/src/shared/tool-log.js +22 -0
  158. package/dist/src/shared/tool-log.js.map +1 -0
  159. package/dist/src/types/state.d.ts +862 -0
  160. package/dist/src/types/state.d.ts.map +1 -0
  161. package/dist/src/types/state.js +66 -0
  162. package/dist/src/types/state.js.map +1 -0
  163. package/docs/consuming/codex-lead-merge.md +106 -0
  164. package/docs/plugin-guide.md +396 -0
  165. package/docs/plugin-template/claude/.github/workflows/build.yml +60 -0
  166. package/docs/plugin-template/claude/README.md +110 -0
  167. package/docs/plugin-template/claude/package.json +16 -0
  168. package/docs/plugin-template/codex/.github/workflows/build.yml +51 -0
  169. package/docs/plugin-template/codex/README.md +147 -0
  170. package/docs/plugin-template/codex/package.json +17 -0
  171. package/docs/plugin-template/opencode/.github/workflows/build.yml +61 -0
  172. package/docs/plugin-template/opencode/README.md +121 -0
  173. package/docs/plugin-template/opencode/package.json +25 -0
  174. package/package.json +36 -28
  175. package/agents/architect/meta.yml +0 -13
  176. package/agents/designer/meta.yml +0 -13
  177. package/agents/engineer/meta.yml +0 -11
  178. package/agents/postdoc/meta.yml +0 -13
  179. package/agents/researcher/meta.yml +0 -12
  180. package/agents/reviewer/meta.yml +0 -12
  181. package/agents/strategist/meta.yml +0 -13
  182. package/agents/tester/meta.yml +0 -12
  183. package/agents/writer/meta.yml +0 -11
  184. package/conformance/README.md +0 -311
  185. package/conformance/examples/plan.extension.schema.example.json +0 -25
  186. package/conformance/lifecycle/README.md +0 -48
  187. package/conformance/lifecycle/agent-complete.json +0 -44
  188. package/conformance/lifecycle/agent-resume.json +0 -43
  189. package/conformance/lifecycle/agent-spawn.json +0 -36
  190. package/conformance/lifecycle/memory-access-record.json +0 -27
  191. package/conformance/lifecycle/session-end.json +0 -48
  192. package/conformance/scenarios/full-plan-cycle.json +0 -147
  193. package/conformance/scenarios/task-deps-ordering.json +0 -95
  194. package/conformance/schema/fixture.schema.json +0 -354
  195. package/conformance/state-schemas/agent-tracker.schema.json +0 -63
  196. package/conformance/state-schemas/history.schema.json +0 -134
  197. package/conformance/state-schemas/memory-access.schema.json +0 -36
  198. package/conformance/state-schemas/plan.schema.json +0 -77
  199. package/conformance/state-schemas/tasks.schema.json +0 -98
  200. package/conformance/tools/artifact-write.json +0 -97
  201. package/conformance/tools/context.json +0 -172
  202. package/conformance/tools/history-search.json +0 -219
  203. package/conformance/tools/plan-decide.json +0 -139
  204. package/conformance/tools/plan-start.json +0 -81
  205. package/conformance/tools/plan-status.json +0 -127
  206. package/conformance/tools/plan-update.json +0 -341
  207. package/conformance/tools/task-add.json +0 -156
  208. package/conformance/tools/task-close.json +0 -161
  209. package/conformance/tools/task-list.json +0 -177
  210. package/conformance/tools/task-update.json +0 -167
  211. package/docs/behavioral-contracts.md +0 -145
  212. package/docs/consumer-implementation-guide.md +0 -840
  213. package/docs/memory-lifecycle-contract.md +0 -119
  214. package/docs/nexus-layout.md +0 -224
  215. package/docs/nexus-outputs-contract.md +0 -344
  216. package/docs/nexus-state-overview.md +0 -170
  217. package/docs/nexus-tools-contract.md +0 -438
  218. package/manifest.json +0 -448
  219. package/schema/README.md +0 -69
  220. package/schema/agent.schema.json +0 -23
  221. package/schema/common.schema.json +0 -17
  222. package/schema/manifest.schema.json +0 -78
  223. package/schema/memory-policy.schema.json +0 -98
  224. package/schema/skill.schema.json +0 -54
  225. package/schema/task-exceptions.schema.json +0 -40
  226. package/schema/vocabulary.schema.json +0 -167
  227. package/scripts/.gitkeep +0 -0
  228. package/scripts/conformance-coverage.ts +0 -466
  229. package/scripts/import-from-claude-nexus.ts +0 -403
  230. package/scripts/lib/frontmatter.ts +0 -71
  231. package/scripts/lib/lint.ts +0 -348
  232. package/scripts/lib/structure.ts +0 -159
  233. package/scripts/lib/validate.ts +0 -796
  234. package/scripts/validate.ts +0 -90
  235. package/skills/nx-init/meta.yml +0 -8
  236. package/skills/nx-plan/meta.yml +0 -10
  237. package/skills/nx-run/meta.yml +0 -8
  238. package/skills/nx-sync/meta.yml +0 -7
  239. package/vocabulary/capabilities.yml +0 -65
  240. package/vocabulary/categories.yml +0 -11
  241. package/vocabulary/invocations.yml +0 -147
  242. package/vocabulary/memory_policy.yml +0 -88
  243. package/vocabulary/resume-tiers.yml +0 -11
  244. package/vocabulary/tags.yml +0 -60
  245. package/vocabulary/task-exceptions.yml +0 -29
@@ -0,0 +1,737 @@
1
+ /**
2
+ * scripts/build-agents.ts
3
+ *
4
+ * Build pipeline for nexus agents and skills.
5
+ *
6
+ * Inputs:
7
+ * assets/agents/<n>/body.md × 9 agents
8
+ * assets/skills/<n>/body.md × 4 skills
9
+ * assets/capability-matrix.yml
10
+ * assets/tools/tool-name-map.yml (invocations section)
11
+ *
12
+ * Outputs per harness:
13
+ * dist/claude/
14
+ * .claude-plugin/plugin.json (Template — skip if exists, --force to overwrite)
15
+ * .claude-plugin/marketplace.json (Template — skip if exists, --force to overwrite)
16
+ * agents/<n>.md × N
17
+ * skills/<n>/SKILL.md × 4
18
+ * settings.json (Managed — primary agent injection, omitted if no primary)
19
+ *
20
+ * dist/opencode/
21
+ * package.json (Template — skip if exists)
22
+ * opencode.json.fragment (Managed — always overwrite)
23
+ * src/index.ts (Managed — always overwrite)
24
+ * src/agents/<n>.ts × N (Managed — always overwrite, mode:primary gets mode field)
25
+ * .opencode/skills/<n>/SKILL.md × 4 (Managed)
26
+ *
27
+ * dist/codex/
28
+ * plugin/.codex-plugin/plugin.json (Managed)
29
+ * plugin/skills/<n>/SKILL.md × 4 (Managed)
30
+ * agents/<n>.toml × N (Managed)
31
+ * prompts/<n>.md × N (Managed)
32
+ * install/config.fragment.toml (Managed)
33
+ * install/AGENTS.fragment.md (Managed — primary agents only, omitted if none)
34
+ *
35
+ * Overwrite policy:
36
+ * Managed paths — always overwrite (unless --dry-run)
37
+ * Template paths — skip if file exists (overwrite only with --force)
38
+ *
39
+ * CLI flags:
40
+ * --harness=claude|opencode|codex (default: all)
41
+ * --target=<dir> (default: dist/)
42
+ * --dry-run print affected files, no writes
43
+ * --force force Template file overwrite
44
+ * --strict error if Managed output has untracked modifications
45
+ * --only=<agent|skill name> restrict to a single asset
46
+ */
47
+ import { readFileSync, readdirSync, existsSync, mkdirSync, writeFileSync, } from "node:fs";
48
+ import { join, resolve, dirname } from "node:path";
49
+ import { fileURLToPath } from "node:url";
50
+ import { execSync } from "node:child_process";
51
+ import { parse as parseYaml } from "yaml";
52
+ import { expandInvocations } from "../src/shared/invocations.js";
53
+ // ---------------------------------------------------------------------------
54
+ // Constants
55
+ // ---------------------------------------------------------------------------
56
+ const __dirname = dirname(fileURLToPath(import.meta.url));
57
+ export const ROOT = resolve(__dirname, "..");
58
+ export const AGENTS_DIR = join(ROOT, "assets/agents");
59
+ export const SKILLS_DIR = join(ROOT, "assets/skills");
60
+ export const CAPABILITY_MATRIX_PATH = join(ROOT, "assets/capability-matrix.yml");
61
+ export const TOOL_NAME_MAP_PATH = join(ROOT, "assets/tools/tool-name-map.yml");
62
+ const HARNESSES = ["claude", "opencode", "codex"];
63
+ // Track dry-run affected files
64
+ const dryRunFiles = [];
65
+ // ---------------------------------------------------------------------------
66
+ // Frontmatter parsing
67
+ // ---------------------------------------------------------------------------
68
+ const FRONTMATTER_RE = /^---\n([\s\S]*?)\n---\n?([\s\S]*)$/;
69
+ /**
70
+ * Split a body.md file into frontmatter object and body text.
71
+ * Throws on YAML parse failure.
72
+ */
73
+ export function parseFrontmatter(raw, filePath) {
74
+ const match = FRONTMATTER_RE.exec(raw);
75
+ if (!match) {
76
+ throw new Error(`[build-agents] Missing or malformed frontmatter in: ${filePath}`);
77
+ }
78
+ let fm;
79
+ try {
80
+ fm = parseYaml(match[1]);
81
+ }
82
+ catch (err) {
83
+ throw new Error(`[build-agents] YAML parse failure in frontmatter of: ${filePath}\n ${String(err)}`);
84
+ }
85
+ if (!fm.id || !fm.name) {
86
+ throw new Error(`[build-agents] Missing required frontmatter fields (id, name) in: ${filePath}`);
87
+ }
88
+ const VALID_MODES = ["primary", "subagent", "all"];
89
+ if (fm.mode !== undefined && !VALID_MODES.includes(fm.mode)) {
90
+ throw new Error(`[build-agents] Invalid mode "${fm.mode}" in: ${filePath}. Valid values: ${VALID_MODES.join(", ")}`);
91
+ }
92
+ return { fm, body: (match[2] ?? "").trimStart() };
93
+ }
94
+ // ---------------------------------------------------------------------------
95
+ // Stage 1: Load assets
96
+ // ---------------------------------------------------------------------------
97
+ /**
98
+ * Load all agents and skills from assets/, whitelisting only body.md.
99
+ */
100
+ export function loadAssets(opts) {
101
+ const entries = [];
102
+ // Load agents
103
+ if (existsSync(AGENTS_DIR)) {
104
+ for (const entry of readdirSync(AGENTS_DIR, { withFileTypes: true })) {
105
+ if (!entry.isDirectory())
106
+ continue;
107
+ if (opts?.only && entry.name !== opts.only)
108
+ continue;
109
+ const bodyPath = join(AGENTS_DIR, entry.name, "body.md");
110
+ if (!existsSync(bodyPath)) {
111
+ throw new Error(`[build-agents] Missing body.md for agent: ${entry.name}`);
112
+ }
113
+ const raw = readFileSync(bodyPath, "utf-8");
114
+ const { fm, body } = parseFrontmatter(raw, bodyPath);
115
+ entries.push({ type: "agent", name: entry.name, frontmatter: fm, body, bodyPath });
116
+ }
117
+ }
118
+ // Load skills
119
+ if (existsSync(SKILLS_DIR)) {
120
+ for (const entry of readdirSync(SKILLS_DIR, { withFileTypes: true })) {
121
+ if (!entry.isDirectory())
122
+ continue;
123
+ if (opts?.only && entry.name !== opts.only)
124
+ continue;
125
+ const bodyPath = join(SKILLS_DIR, entry.name, "body.md");
126
+ if (!existsSync(bodyPath)) {
127
+ throw new Error(`[build-agents] Missing body.md for skill: ${entry.name}`);
128
+ }
129
+ const raw = readFileSync(bodyPath, "utf-8");
130
+ const { fm, body } = parseFrontmatter(raw, bodyPath);
131
+ entries.push({ type: "skill", name: entry.name, frontmatter: fm, body, bodyPath });
132
+ }
133
+ }
134
+ return entries;
135
+ }
136
+ // ---------------------------------------------------------------------------
137
+ // Stage 2: Load capability matrix
138
+ // ---------------------------------------------------------------------------
139
+ export function loadCapabilityMatrix() {
140
+ if (!existsSync(CAPABILITY_MATRIX_PATH)) {
141
+ throw new Error(`[build-agents] capability-matrix.yml not found at: ${CAPABILITY_MATRIX_PATH}`);
142
+ }
143
+ const raw = readFileSync(CAPABILITY_MATRIX_PATH, "utf-8");
144
+ return parseYaml(raw);
145
+ }
146
+ // ---------------------------------------------------------------------------
147
+ // Stage 3: Load invocations
148
+ // ---------------------------------------------------------------------------
149
+ export function loadInvocations() {
150
+ if (!existsSync(TOOL_NAME_MAP_PATH)) {
151
+ throw new Error(`[build-agents] tool-name-map.yml not found at: ${TOOL_NAME_MAP_PATH}`);
152
+ }
153
+ const raw = readFileSync(TOOL_NAME_MAP_PATH, "utf-8");
154
+ const parsed = parseYaml(raw);
155
+ if (!parsed.invocations) {
156
+ throw new Error(`[build-agents] tool-name-map.yml missing 'invocations' section`);
157
+ }
158
+ return parsed.invocations;
159
+ }
160
+ // ---------------------------------------------------------------------------
161
+ // Capability resolution helpers
162
+ // ---------------------------------------------------------------------------
163
+ /**
164
+ * Collect all Claude disallowedTools for an agent based on its capabilities[].
165
+ */
166
+ export function resolveClaudeDisallowedTools(capabilities, capMatrix) {
167
+ const tools = [];
168
+ for (const cap of capabilities) {
169
+ const entry = capMatrix.capabilities[cap];
170
+ if (!entry) {
171
+ throw new Error(`[build-agents] Unknown capability: ${cap}`);
172
+ }
173
+ if (entry.claude?.disallowedTools) {
174
+ for (const t of entry.claude.disallowedTools) {
175
+ if (!tools.includes(t))
176
+ tools.push(t);
177
+ }
178
+ }
179
+ }
180
+ return tools;
181
+ }
182
+ /**
183
+ * Collect merged OpenCode permission block for an agent.
184
+ */
185
+ export function resolveOpencodePermissions(capabilities, capMatrix) {
186
+ const perms = {};
187
+ for (const cap of capabilities) {
188
+ const entry = capMatrix.capabilities[cap];
189
+ if (!entry) {
190
+ throw new Error(`[build-agents] Unknown capability: ${cap}`);
191
+ }
192
+ if (entry.opencode?.permission) {
193
+ Object.assign(perms, entry.opencode.permission);
194
+ }
195
+ }
196
+ return perms;
197
+ }
198
+ /**
199
+ * Resolve codex sandbox_mode and disabled_tools for an agent.
200
+ * sandbox_mode: take the most restrictive non-null value ("read-only" wins).
201
+ */
202
+ export function resolveCodexConfig(capabilities, capMatrix) {
203
+ let sandboxMode = null;
204
+ const disabledTools = [];
205
+ for (const cap of capabilities) {
206
+ const entry = capMatrix.capabilities[cap];
207
+ if (!entry) {
208
+ throw new Error(`[build-agents] Unknown capability: ${cap}`);
209
+ }
210
+ if (entry.codex?.sandbox_mode) {
211
+ // "read-only" is the most restrictive
212
+ sandboxMode = entry.codex.sandbox_mode;
213
+ }
214
+ if (entry.codex?.disabled_tools) {
215
+ for (const t of entry.codex.disabled_tools) {
216
+ if (t && !disabledTools.includes(t))
217
+ disabledTools.push(t);
218
+ }
219
+ }
220
+ }
221
+ return { sandbox_mode: sandboxMode, disabled_tools: disabledTools };
222
+ }
223
+ /**
224
+ * Resolve model slug for a given model_tier and harness.
225
+ */
226
+ export function resolveModel(modelTier, harness, capMatrix) {
227
+ const tierEntry = capMatrix.model_tier[modelTier];
228
+ if (!tierEntry)
229
+ return null;
230
+ const val = tierEntry[harness];
231
+ return val === null || val === undefined ? null : val;
232
+ }
233
+ // ---------------------------------------------------------------------------
234
+ // Overwrite policy
235
+ // ---------------------------------------------------------------------------
236
+ /**
237
+ * Write a file according to the managed/template overwrite policy.
238
+ *
239
+ * Managed: always overwrite (unless --dry-run)
240
+ * Template: skip if exists (overwrite only with --force)
241
+ * --dry-run: record path, no write
242
+ * --strict: error if Managed path has untracked git modifications
243
+ */
244
+ export function applyOverwritePolicy(filePath, content, isManaged, opts) {
245
+ if (opts.dryRun) {
246
+ dryRunFiles.push(filePath);
247
+ return;
248
+ }
249
+ if (isManaged) {
250
+ if (opts.strict) {
251
+ // Check if the file is tracked by git and has local modifications
252
+ if (existsSync(filePath)) {
253
+ try {
254
+ const rel = filePath.startsWith(ROOT)
255
+ ? filePath.slice(ROOT.length + 1)
256
+ : filePath;
257
+ const result = execSync(`git status --short -- ${JSON.stringify(rel)}`, {
258
+ cwd: ROOT,
259
+ encoding: "utf-8",
260
+ stdio: ["pipe", "pipe", "pipe"],
261
+ }).trim();
262
+ if (result && !result.startsWith("?")) {
263
+ throw new Error(`[build-agents] --strict: managed file has untracked modifications: ${filePath}`);
264
+ }
265
+ }
266
+ catch (err) {
267
+ if (String(err).includes("--strict:"))
268
+ throw err;
269
+ // git not available or file not tracked — allow
270
+ }
271
+ }
272
+ }
273
+ mkdirSync(dirname(filePath), { recursive: true });
274
+ writeFileSync(filePath, content, "utf-8");
275
+ }
276
+ else {
277
+ // Template: skip if exists unless --force
278
+ if (existsSync(filePath) && !opts.force) {
279
+ return;
280
+ }
281
+ mkdirSync(dirname(filePath), { recursive: true });
282
+ writeFileSync(filePath, content, "utf-8");
283
+ }
284
+ }
285
+ // ---------------------------------------------------------------------------
286
+ // Harness: Claude
287
+ // ---------------------------------------------------------------------------
288
+ function claudeAgentMarkdown(asset, capMatrix, invocations) {
289
+ const fm = asset.frontmatter;
290
+ const disallowed = resolveClaudeDisallowedTools(fm.capabilities ?? [], capMatrix);
291
+ const model = resolveModel(fm.model_tier, "claude", capMatrix);
292
+ const fmLines = ["---"];
293
+ if (fm.description)
294
+ fmLines.push(`description: ${JSON.stringify(fm.description)}`);
295
+ if (model)
296
+ fmLines.push(`model: ${model}`);
297
+ if (disallowed.length > 0) {
298
+ fmLines.push(`disallowedTools:`);
299
+ for (const t of disallowed) {
300
+ fmLines.push(` - ${t}`);
301
+ }
302
+ }
303
+ fmLines.push("---");
304
+ fmLines.push("");
305
+ const expandedBody = expandInvocations(asset.body, "claude", invocations);
306
+ return fmLines.join("\n") + expandedBody;
307
+ }
308
+ function claudeSkillMarkdown(asset, invocations) {
309
+ const fm = asset.frontmatter;
310
+ const fmLines = ["---"];
311
+ if (fm.description)
312
+ fmLines.push(`description: ${JSON.stringify(fm.description)}`);
313
+ if (fm.triggers && fm.triggers.length > 0) {
314
+ fmLines.push(`triggers:`);
315
+ for (const t of fm.triggers) {
316
+ fmLines.push(` - ${t}`);
317
+ }
318
+ }
319
+ fmLines.push("---");
320
+ fmLines.push("");
321
+ const expandedBody = expandInvocations(asset.body, "claude", invocations);
322
+ return fmLines.join("\n") + expandedBody;
323
+ }
324
+ function buildPluginJson(agents) {
325
+ return JSON.stringify({
326
+ name: "claude-nexus",
327
+ version: "0.13.0",
328
+ description: "Nexus agent suite for Claude Code",
329
+ agents: agents.map((a) => ({
330
+ id: a.frontmatter.id,
331
+ name: a.frontmatter.name,
332
+ description: a.frontmatter.description,
333
+ file: `agents/${a.name}.md`,
334
+ })),
335
+ }, null, 2) + "\n";
336
+ }
337
+ function buildMarketplaceJson(agents) {
338
+ return JSON.stringify({
339
+ schema_version: "1.0",
340
+ agents: agents.map((a) => ({
341
+ id: a.frontmatter.id,
342
+ name: a.frontmatter.name,
343
+ description: a.frontmatter.description,
344
+ category: a.frontmatter.category,
345
+ model_tier: a.frontmatter.model_tier,
346
+ })),
347
+ }, null, 2) + "\n";
348
+ }
349
+ export function buildForClaude(assets, capMatrix, invocations, opts) {
350
+ const baseDir = join(opts.targetDir, "claude");
351
+ const agentAssets = assets.filter((a) => a.type === "agent");
352
+ const skillAssets = assets.filter((a) => a.type === "skill");
353
+ // Template files: .claude-plugin/plugin.json and marketplace.json
354
+ const pluginJsonPath = join(baseDir, ".claude-plugin", "plugin.json");
355
+ const marketplacePath = join(baseDir, ".claude-plugin", "marketplace.json");
356
+ applyOverwritePolicy(pluginJsonPath, buildPluginJson(agentAssets), false, opts);
357
+ applyOverwritePolicy(marketplacePath, buildMarketplaceJson(agentAssets), false, opts);
358
+ // Managed: agents/<n>.md
359
+ for (const agent of agentAssets) {
360
+ const outPath = join(baseDir, "agents", `${agent.name}.md`);
361
+ const content = claudeAgentMarkdown(agent, capMatrix, invocations);
362
+ applyOverwritePolicy(outPath, content, true, opts);
363
+ }
364
+ // Managed: skills/<n>/SKILL.md
365
+ for (const skill of skillAssets) {
366
+ const outPath = join(baseDir, "skills", skill.name, "SKILL.md");
367
+ const content = claudeSkillMarkdown(skill, invocations);
368
+ applyOverwritePolicy(outPath, content, true, opts);
369
+ }
370
+ // Managed: settings.json (primary agent injection)
371
+ const primaryAgents = agentAssets.filter((a) => (a.frontmatter.mode ?? "subagent") === "primary");
372
+ if (primaryAgents.length > 0) {
373
+ if (primaryAgents.length > 1) {
374
+ console.warn(`[build-agents] Warning: multiple primary agents found (${primaryAgents.map((a) => a.name).join(", ")}). Using first: ${primaryAgents[0].name}`);
375
+ }
376
+ const primaryAgent = primaryAgents[0];
377
+ const settingsPath = join(baseDir, "settings.json");
378
+ const settingsContent = JSON.stringify({ agent: primaryAgent.frontmatter.id }, null, 2) + "\n";
379
+ applyOverwritePolicy(settingsPath, settingsContent, true, opts);
380
+ }
381
+ }
382
+ // ---------------------------------------------------------------------------
383
+ // Harness: OpenCode
384
+ // ---------------------------------------------------------------------------
385
+ /**
386
+ * Generate OpenCode src/agents/<n>.ts content.
387
+ * Uses template literal inline. Backtick and ${ are escaped via string concatenation.
388
+ */
389
+ function opencodeAgentTs(asset, capMatrix, invocations) {
390
+ const fm = asset.frontmatter;
391
+ const perms = resolveOpencodePermissions(fm.capabilities ?? [], capMatrix);
392
+ const expandedBody = expandInvocations(asset.body, "opencode", invocations);
393
+ // Build permission block
394
+ const permEntries = Object.entries(perms);
395
+ const permBlock = permEntries.length > 0
396
+ ? ` permission: {\n${permEntries.map(([k, v]) => ` ${k}: "${v}",`).join("\n")}\n },`
397
+ : "";
398
+ // Escape content for embedding in a template literal
399
+ // We use string concatenation to avoid issues with backtick and ${ in the template
400
+ const escapedBody = expandedBody.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
401
+ const escapedDesc = fm.description.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
402
+ const lines = [
403
+ `// Auto-generated by build-agents.ts — do not edit`,
404
+ `// Source: assets/agents/${asset.name}/body.md`,
405
+ `import type { AgentConfig } from "opencode";`,
406
+ ``,
407
+ `export const ${camelCase(asset.name)}: AgentConfig = {`,
408
+ ` id: ${JSON.stringify(fm.id)},`,
409
+ ` name: ${JSON.stringify(fm.name)},`,
410
+ ` description: \`${escapedDesc}\`,`,
411
+ ];
412
+ if (permBlock)
413
+ lines.push(permBlock);
414
+ // Emit mode field only for primary agents (subagent is the OpenCode default)
415
+ if (fm.mode === "primary") {
416
+ lines.push(` mode: "primary",`);
417
+ }
418
+ lines.push(` system: \`${escapedBody}\`,`, `};`, ``);
419
+ return lines.join("\n");
420
+ }
421
+ function opencodeIndexTs(agents) {
422
+ const imports = agents
423
+ .map((a) => `import { ${camelCase(a.name)} } from "./agents/${a.name}.js";`)
424
+ .join("\n");
425
+ const exports = `export const agents = [\n${agents.map((a) => ` ${camelCase(a.name)},`).join("\n")}\n];`;
426
+ return [
427
+ `// Auto-generated by build-agents.ts — do not edit`,
428
+ ``,
429
+ imports,
430
+ ``,
431
+ exports,
432
+ ``,
433
+ ].join("\n");
434
+ }
435
+ function opencodePackageJson(agents) {
436
+ return (JSON.stringify({
437
+ name: "opencode-nexus",
438
+ version: "0.13.0",
439
+ description: "Nexus agent suite for OpenCode",
440
+ type: "module",
441
+ main: "./src/index.ts",
442
+ exports: {
443
+ ".": "./src/index.ts",
444
+ },
445
+ peerDependencies: {
446
+ opencode: "*",
447
+ },
448
+ }, null, 2) + "\n");
449
+ }
450
+ function opencodeJsonFragment(agents) {
451
+ // Fragment to be merged into opencode.json
452
+ return (JSON.stringify({
453
+ agents: agents.map((a) => ({
454
+ id: a.frontmatter.id,
455
+ module: `./src/agents/${a.name}.js`,
456
+ })),
457
+ }, null, 2) + "\n");
458
+ }
459
+ function opencodeSkillMarkdown(asset, invocations) {
460
+ const fm = asset.frontmatter;
461
+ const fmLines = ["---"];
462
+ if (fm.description)
463
+ fmLines.push(`description: ${JSON.stringify(fm.description)}`);
464
+ if (fm.triggers && fm.triggers.length > 0) {
465
+ fmLines.push(`triggers:`);
466
+ for (const t of fm.triggers) {
467
+ fmLines.push(` - ${t}`);
468
+ }
469
+ }
470
+ fmLines.push("---");
471
+ fmLines.push("");
472
+ const expandedBody = expandInvocations(asset.body, "opencode", invocations);
473
+ return fmLines.join("\n") + expandedBody;
474
+ }
475
+ export function buildForOpencode(assets, capMatrix, invocations, opts) {
476
+ const baseDir = join(opts.targetDir, "opencode");
477
+ const agentAssets = assets.filter((a) => a.type === "agent");
478
+ const skillAssets = assets.filter((a) => a.type === "skill");
479
+ // Template: package.json
480
+ const pkgPath = join(baseDir, "package.json");
481
+ applyOverwritePolicy(pkgPath, opencodePackageJson(agentAssets), false, opts);
482
+ // Managed: opencode.json.fragment
483
+ const fragmentPath = join(baseDir, "opencode.json.fragment");
484
+ applyOverwritePolicy(fragmentPath, opencodeJsonFragment(agentAssets), true, opts);
485
+ // Managed: src/index.ts
486
+ const indexPath = join(baseDir, "src", "index.ts");
487
+ applyOverwritePolicy(indexPath, opencodeIndexTs(agentAssets), true, opts);
488
+ // Managed: src/agents/<n>.ts
489
+ for (const agent of agentAssets) {
490
+ const outPath = join(baseDir, "src", "agents", `${agent.name}.ts`);
491
+ const content = opencodeAgentTs(agent, capMatrix, invocations);
492
+ applyOverwritePolicy(outPath, content, true, opts);
493
+ }
494
+ // Managed: .opencode/skills/<n>/SKILL.md
495
+ for (const skill of skillAssets) {
496
+ const outPath = join(baseDir, ".opencode", "skills", skill.name, "SKILL.md");
497
+ const content = opencodeSkillMarkdown(skill, invocations);
498
+ applyOverwritePolicy(outPath, content, true, opts);
499
+ }
500
+ }
501
+ // ---------------------------------------------------------------------------
502
+ // Harness: Codex
503
+ // ---------------------------------------------------------------------------
504
+ /**
505
+ * Escape a string for TOML multi-line literal string (''' ''').
506
+ * Literal strings do not allow escapes, so we must not include '''.
507
+ * Fallback: use basic multi-line string with minimal escaping.
508
+ */
509
+ function tomlMultilineString(value) {
510
+ // Use basic multi-line string: """ ... """
511
+ // Escape backslash and double-quote sequences
512
+ const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
513
+ return `"""\n${escaped}\n"""`;
514
+ }
515
+ function codexAgentToml(asset, capMatrix, invocations) {
516
+ const fm = asset.frontmatter;
517
+ const { sandbox_mode, disabled_tools } = resolveCodexConfig(fm.capabilities ?? [], capMatrix);
518
+ const model = resolveModel(fm.model_tier, "codex", capMatrix);
519
+ const expandedBody = expandInvocations(asset.body, "codex", invocations);
520
+ const lines = [
521
+ `# Auto-generated by build-agents.ts — do not edit`,
522
+ `# Source: assets/agents/${asset.name}/body.md`,
523
+ ``,
524
+ `[agents.${fm.id}]`,
525
+ `description = ${JSON.stringify(fm.description)}`,
526
+ ];
527
+ if (model)
528
+ lines.push(`model = ${JSON.stringify(model)}`);
529
+ if (sandbox_mode)
530
+ lines.push(`sandbox_mode = ${JSON.stringify(sandbox_mode)}`);
531
+ if (disabled_tools.length > 0) {
532
+ lines.push(`disabled_tools = [${disabled_tools.map((t) => JSON.stringify(t)).join(", ")}]`);
533
+ }
534
+ lines.push(``, `[agents.${fm.id}.system]`, `content = ${tomlMultilineString(expandedBody)}`, ``);
535
+ return lines.join("\n");
536
+ }
537
+ function codexPromptMarkdown(asset, invocations) {
538
+ const expandedBody = expandInvocations(asset.body, "codex", invocations);
539
+ const fm = asset.frontmatter;
540
+ return [
541
+ `---`,
542
+ `name: ${JSON.stringify(fm.name)}`,
543
+ `description: ${JSON.stringify(fm.description)}`,
544
+ `---`,
545
+ ``,
546
+ expandedBody,
547
+ ].join("\n");
548
+ }
549
+ function codexSkillMarkdown(asset, invocations) {
550
+ const fm = asset.frontmatter;
551
+ const fmLines = ["---"];
552
+ if (fm.description)
553
+ fmLines.push(`description: ${JSON.stringify(fm.description)}`);
554
+ if (fm.triggers && fm.triggers.length > 0) {
555
+ fmLines.push(`triggers:`);
556
+ for (const t of fm.triggers) {
557
+ fmLines.push(` - ${t}`);
558
+ }
559
+ }
560
+ fmLines.push("---");
561
+ fmLines.push("");
562
+ const expandedBody = expandInvocations(asset.body, "codex", invocations);
563
+ return fmLines.join("\n") + expandedBody;
564
+ }
565
+ function codexPluginJson(agents) {
566
+ return (JSON.stringify({
567
+ name: "codex-nexus",
568
+ version: "0.13.0",
569
+ description: "Nexus agent suite for Codex",
570
+ agents: agents.map((a) => ({
571
+ id: a.frontmatter.id,
572
+ config: `agents/${a.name}.toml`,
573
+ prompt: `prompts/${a.name}.md`,
574
+ })),
575
+ }, null, 2) + "\n");
576
+ }
577
+ function codexConfigFragment(agents) {
578
+ const lines = [
579
+ `# Auto-generated by build-agents.ts — do not edit`,
580
+ `# Merge this fragment into your codex config.toml`,
581
+ ``,
582
+ `[mcp_servers.nx]`,
583
+ `command = "nexus-mcp"`,
584
+ ``,
585
+ ];
586
+ return lines.join("\n");
587
+ }
588
+ export function buildForCodex(assets, capMatrix, invocations, opts) {
589
+ const baseDir = join(opts.targetDir, "codex");
590
+ const agentAssets = assets.filter((a) => a.type === "agent");
591
+ const skillAssets = assets.filter((a) => a.type === "skill");
592
+ // Managed: plugin/.codex-plugin/plugin.json
593
+ const pluginJsonPath = join(baseDir, "plugin", ".codex-plugin", "plugin.json");
594
+ applyOverwritePolicy(pluginJsonPath, codexPluginJson(agentAssets), true, opts);
595
+ // Managed: plugin/skills/<n>/SKILL.md
596
+ for (const skill of skillAssets) {
597
+ const outPath = join(baseDir, "plugin", "skills", skill.name, "SKILL.md");
598
+ const content = codexSkillMarkdown(skill, invocations);
599
+ applyOverwritePolicy(outPath, content, true, opts);
600
+ }
601
+ // Managed: agents/<n>.toml
602
+ for (const agent of agentAssets) {
603
+ const outPath = join(baseDir, "agents", `${agent.name}.toml`);
604
+ const content = codexAgentToml(agent, capMatrix, invocations);
605
+ applyOverwritePolicy(outPath, content, true, opts);
606
+ }
607
+ // Managed: prompts/<n>.md
608
+ for (const agent of agentAssets) {
609
+ const outPath = join(baseDir, "prompts", `${agent.name}.md`);
610
+ const content = codexPromptMarkdown(agent, invocations);
611
+ applyOverwritePolicy(outPath, content, true, opts);
612
+ }
613
+ // Managed: install/config.fragment.toml
614
+ const fragmentPath = join(baseDir, "install", "config.fragment.toml");
615
+ applyOverwritePolicy(fragmentPath, codexConfigFragment(agentAssets), true, opts);
616
+ // Managed: install/AGENTS.fragment.md (primary agents only)
617
+ const primaryAgents = agentAssets.filter((a) => (a.frontmatter.mode ?? "subagent") === "primary");
618
+ if (primaryAgents.length > 0) {
619
+ const agentsFragmentPath = join(baseDir, "install", "AGENTS.fragment.md");
620
+ const blocks = primaryAgents.map((agent) => {
621
+ const expandedBody = expandInvocations(agent.body, "codex", invocations);
622
+ return [
623
+ `<!-- nexus-core:${agent.frontmatter.id}:start -->`,
624
+ `# ${agent.frontmatter.name}`,
625
+ ``,
626
+ expandedBody,
627
+ `<!-- nexus-core:${agent.frontmatter.id}:end -->`,
628
+ ].join("\n");
629
+ });
630
+ const agentsFragmentContent = blocks.join("\n\n") + "\n";
631
+ applyOverwritePolicy(agentsFragmentPath, agentsFragmentContent, true, opts);
632
+ }
633
+ }
634
+ // ---------------------------------------------------------------------------
635
+ // Utilities
636
+ // ---------------------------------------------------------------------------
637
+ function camelCase(str) {
638
+ return str.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
639
+ }
640
+ // ---------------------------------------------------------------------------
641
+ // CLI arg parsing
642
+ // ---------------------------------------------------------------------------
643
+ export function parseArgs(argv) {
644
+ const args = argv.slice(2); // remove node and script path
645
+ let harnesses = [...HARNESSES];
646
+ let targetDir = join(ROOT, "dist");
647
+ let dryRun = false;
648
+ let force = false;
649
+ let strict = false;
650
+ let only;
651
+ for (const arg of args) {
652
+ if (arg.startsWith("--harness=")) {
653
+ const val = arg.slice("--harness=".length);
654
+ if (!HARNESSES.includes(val)) {
655
+ throw new Error(`[build-agents] Unknown harness: ${val}. Valid: ${HARNESSES.join(", ")}`);
656
+ }
657
+ harnesses = [val];
658
+ }
659
+ else if (arg.startsWith("--target=")) {
660
+ targetDir = resolve(arg.slice("--target=".length));
661
+ }
662
+ else if (arg === "--dry-run") {
663
+ dryRun = true;
664
+ }
665
+ else if (arg === "--force") {
666
+ force = true;
667
+ }
668
+ else if (arg === "--strict") {
669
+ strict = true;
670
+ }
671
+ else if (arg.startsWith("--only=")) {
672
+ only = arg.slice("--only=".length);
673
+ }
674
+ }
675
+ return { harnesses, targetDir, dryRun, force, strict, only };
676
+ }
677
+ // ---------------------------------------------------------------------------
678
+ // Main
679
+ // ---------------------------------------------------------------------------
680
+ export async function buildAgents(opts) {
681
+ // Stage 1: Load assets
682
+ const assets = loadAssets({ only: opts.only });
683
+ const agentCount = assets.filter((a) => a.type === "agent").length;
684
+ const skillCount = assets.filter((a) => a.type === "skill").length;
685
+ console.log(`[build-agents] Loaded ${agentCount} agents, ${skillCount} skills`);
686
+ // Stage 2: Load capability matrix
687
+ const capMatrix = loadCapabilityMatrix();
688
+ // Validate all capability IDs referenced by assets
689
+ const knownCapIds = new Set(Object.keys(capMatrix.capabilities));
690
+ for (const asset of assets) {
691
+ for (const cap of asset.frontmatter.capabilities ?? []) {
692
+ if (!knownCapIds.has(cap)) {
693
+ throw new Error(`[build-agents] "${asset.name}" references unknown capability: "${cap}". ` +
694
+ `Known: ${[...knownCapIds].join(", ")}`);
695
+ }
696
+ }
697
+ }
698
+ // Stage 3: Load invocations
699
+ const invocations = loadInvocations();
700
+ // Stage 4: Build per harness
701
+ if (opts.dryRun) {
702
+ console.log(`[build-agents] --dry-run mode: listing affected files only`);
703
+ }
704
+ for (const harness of opts.harnesses) {
705
+ console.log(`[build-agents] Building for harness: ${harness}`);
706
+ if (harness === "claude") {
707
+ buildForClaude(assets, capMatrix, invocations, opts);
708
+ }
709
+ else if (harness === "opencode") {
710
+ buildForOpencode(assets, capMatrix, invocations, opts);
711
+ }
712
+ else if (harness === "codex") {
713
+ buildForCodex(assets, capMatrix, invocations, opts);
714
+ }
715
+ }
716
+ if (opts.dryRun) {
717
+ console.log(`[build-agents] Affected files (${dryRunFiles.length}):`);
718
+ for (const f of dryRunFiles) {
719
+ console.log(` ${f}`);
720
+ }
721
+ // Clear for next run
722
+ dryRunFiles.length = 0;
723
+ return;
724
+ }
725
+ console.log(`[build-agents] Done`);
726
+ }
727
+ // Run when executed directly
728
+ if (import.meta.url === `file://${process.argv[1]}` ||
729
+ process.argv[1]?.endsWith("build-agents.ts") ||
730
+ process.argv[1]?.endsWith("build-agents.js")) {
731
+ const opts = parseArgs(process.argv);
732
+ buildAgents(opts).catch((err) => {
733
+ process.stderr.write(`[build-agents] FATAL: ${String(err)}\n`);
734
+ process.exit(1);
735
+ });
736
+ }
737
+ //# sourceMappingURL=build-agents.js.map