@jjlabsio/claude-crew 0.1.33 → 0.1.35
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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +8 -7
- package/README.md +22 -0
- package/agents/code-reviewer.md +7 -0
- package/agents/dev.md +7 -0
- package/agents/explorer.md +7 -0
- package/agents/plan-evaluator.md +7 -0
- package/agents/planner.md +7 -0
- package/agents/pm.md +7 -0
- package/agents/qa.md +8 -1
- package/agents/researcher.md +7 -0
- package/agents/techlead.md +7 -0
- package/data/agent-contracts.json +350 -0
- package/data/agent-instructions/code-reviewer.md +47 -0
- package/data/agent-instructions/dev.md +48 -0
- package/data/agent-instructions/explorer.md +14 -0
- package/data/agent-instructions/plan-evaluator.md +68 -0
- package/data/agent-instructions/planner.md +73 -0
- package/data/agent-instructions/pm.md +47 -0
- package/data/agent-instructions/qa.md +65 -0
- package/data/agent-instructions/researcher.md +15 -0
- package/data/agent-instructions/techlead.md +66 -0
- package/hooks/enforce-delegation.mjs +51 -0
- package/package.json +8 -3
- package/scripts/crew-agent-runner.mjs +382 -0
- package/scripts/lib/build.mjs +213 -0
- package/scripts/lib/cli.mjs +30 -0
- package/scripts/lib/config.mjs +33 -0
- package/scripts/lib/contracts.mjs +146 -0
- package/scripts/lib/dispatch.mjs +241 -0
- package/scripts/lib/installHooks.mjs +136 -0
- package/scripts/lib/pluginRoot.mjs +10 -0
- package/scripts/lib/prepare.mjs +37 -0
- package/scripts/lib/render.mjs +138 -0
- package/scripts/lib/renderFollowup.mjs +51 -0
- package/scripts/lib/resolve.mjs +72 -0
- package/scripts/lib/skillDispatchContract.mjs +93 -0
- package/scripts/lib/validate.mjs +104 -0
- package/skills/crew-agent-runner/SKILL.md +113 -0
- package/skills/crew-dev/SKILL.md +171 -776
- package/skills/crew-interview/SKILL.md +137 -57
- package/skills/crew-plan/SKILL.md +224 -460
- package/skills/crew-setup/SKILL.md +32 -19
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
|
|
4
|
+
import { build } from "./lib/build.mjs";
|
|
5
|
+
import { loadContracts } from "./lib/contracts.mjs";
|
|
6
|
+
import {
|
|
7
|
+
loadCatalog,
|
|
8
|
+
loadProjectConfig,
|
|
9
|
+
loadUserConfig
|
|
10
|
+
} from "./lib/config.mjs";
|
|
11
|
+
import { parseArgv } from "./lib/cli.mjs";
|
|
12
|
+
import {
|
|
13
|
+
dispatch,
|
|
14
|
+
DispatchError,
|
|
15
|
+
formatDispatchProviderGuardMessage
|
|
16
|
+
} from "./lib/dispatch.mjs";
|
|
17
|
+
import { installHooks } from "./lib/installHooks.mjs";
|
|
18
|
+
import { prepareDispatch } from "./lib/prepare.mjs";
|
|
19
|
+
import { renderFollowup } from "./lib/renderFollowup.mjs";
|
|
20
|
+
import { renderPrompt } from "./lib/render.mjs";
|
|
21
|
+
import { resolveRole } from "./lib/resolve.mjs";
|
|
22
|
+
import { validate } from "./lib/validate.mjs";
|
|
23
|
+
|
|
24
|
+
async function main(argv) {
|
|
25
|
+
const { positional, flags } = parseArgv(argv);
|
|
26
|
+
const command = positional[0];
|
|
27
|
+
|
|
28
|
+
if (positional.length !== 1) {
|
|
29
|
+
usage();
|
|
30
|
+
return 2;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (command === "render") {
|
|
34
|
+
return renderCommand(flags);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (command === "prepare") {
|
|
38
|
+
return prepareCommand(flags);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (command === "dispatch") {
|
|
42
|
+
return dispatchCommand(flags);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (command === "render-followup") {
|
|
46
|
+
return renderFollowupCommand(flags);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (command === "build") {
|
|
50
|
+
return buildCommand(flags);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (command === "validate") {
|
|
54
|
+
return validateCommand(flags);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (command === "install-hooks") {
|
|
58
|
+
return installHooksCommand(flags);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (command !== "resolve") {
|
|
62
|
+
usage();
|
|
63
|
+
return 2;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (typeof flags.role !== "string" || flags.role.length === 0) {
|
|
67
|
+
console.error("Missing required --role <name>");
|
|
68
|
+
return 1;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const value = resolveRole({
|
|
73
|
+
role: flags.role,
|
|
74
|
+
catalog: loadCatalog(),
|
|
75
|
+
userConfig: loadUserConfig(),
|
|
76
|
+
projectConfig: loadProjectConfig(),
|
|
77
|
+
contracts: loadContracts()
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
if (flags.json) {
|
|
81
|
+
process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
|
|
82
|
+
} else {
|
|
83
|
+
process.stdout.write(formatTable(value));
|
|
84
|
+
}
|
|
85
|
+
return 0;
|
|
86
|
+
} catch (error) {
|
|
87
|
+
console.error(error.message);
|
|
88
|
+
return 1;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function prepareCommand(flags) {
|
|
93
|
+
if (typeof flags.role !== "string" || flags.role.length === 0) {
|
|
94
|
+
console.error("Missing required --role <name>");
|
|
95
|
+
return 1;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (
|
|
99
|
+
typeof flags["request-file"] !== "string" ||
|
|
100
|
+
flags["request-file"].length === 0
|
|
101
|
+
) {
|
|
102
|
+
console.error("Missing required --request-file <path>");
|
|
103
|
+
return 1;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
const contracts = loadContracts();
|
|
108
|
+
const resolved = resolveRole({
|
|
109
|
+
role: flags.role,
|
|
110
|
+
catalog: loadCatalog(),
|
|
111
|
+
userConfig: loadUserConfig(),
|
|
112
|
+
projectConfig: loadProjectConfig(),
|
|
113
|
+
contracts
|
|
114
|
+
});
|
|
115
|
+
const request = JSON.parse(readFileSync(flags["request-file"], "utf8"));
|
|
116
|
+
const prepared = prepareDispatch({
|
|
117
|
+
role: flags.role,
|
|
118
|
+
requestFile: flags["request-file"],
|
|
119
|
+
request,
|
|
120
|
+
resolved
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
if (flags.json) {
|
|
124
|
+
process.stdout.write(`${JSON.stringify(prepared, null, 2)}\n`);
|
|
125
|
+
} else {
|
|
126
|
+
process.stdout.write(formatPrepared(prepared));
|
|
127
|
+
}
|
|
128
|
+
return 0;
|
|
129
|
+
} catch (error) {
|
|
130
|
+
console.error(error.message);
|
|
131
|
+
return 1;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async function installHooksCommand(flags) {
|
|
136
|
+
if (
|
|
137
|
+
flags.root !== undefined &&
|
|
138
|
+
(typeof flags.root !== "string" || flags.root.length === 0)
|
|
139
|
+
) {
|
|
140
|
+
console.error("Missing value for --root <path>");
|
|
141
|
+
return 1;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
await installHooks({ root: flags.root ?? process.cwd() });
|
|
146
|
+
return 0;
|
|
147
|
+
} catch (error) {
|
|
148
|
+
console.error(error.message);
|
|
149
|
+
return 1;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function buildCommand(flags) {
|
|
154
|
+
if (
|
|
155
|
+
flags.root !== undefined &&
|
|
156
|
+
(typeof flags.root !== "string" || flags.root.length === 0)
|
|
157
|
+
) {
|
|
158
|
+
console.error("Missing value for --root <path>");
|
|
159
|
+
return 1;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
await build({ root: flags.root ?? process.cwd() });
|
|
164
|
+
return 0;
|
|
165
|
+
} catch (error) {
|
|
166
|
+
console.error(error.message);
|
|
167
|
+
return 1;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async function validateCommand(flags) {
|
|
172
|
+
if (
|
|
173
|
+
flags.root !== undefined &&
|
|
174
|
+
(typeof flags.root !== "string" || flags.root.length === 0)
|
|
175
|
+
) {
|
|
176
|
+
console.error("Missing value for --root <path>");
|
|
177
|
+
return 1;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
try {
|
|
181
|
+
const result = await validate({ root: flags.root ?? process.cwd() });
|
|
182
|
+
if (result.ok) {
|
|
183
|
+
process.stdout.write("OK\n");
|
|
184
|
+
return 0;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
for (const error of result.errors) {
|
|
188
|
+
console.error(error);
|
|
189
|
+
}
|
|
190
|
+
return 1;
|
|
191
|
+
} catch (error) {
|
|
192
|
+
console.error(error.message);
|
|
193
|
+
return 1;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function renderFollowupCommand(flags) {
|
|
198
|
+
if (
|
|
199
|
+
typeof flags["previous-result"] !== "string" ||
|
|
200
|
+
flags["previous-result"].length === 0
|
|
201
|
+
) {
|
|
202
|
+
console.error("Missing required --previous-result <file>");
|
|
203
|
+
return 1;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (
|
|
207
|
+
typeof flags["new-input"] !== "string" ||
|
|
208
|
+
flags["new-input"].length === 0
|
|
209
|
+
) {
|
|
210
|
+
console.error("Missing required --new-input <file>");
|
|
211
|
+
return 1;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
const previousResult = JSON.parse(
|
|
216
|
+
readFileSync(flags["previous-result"], "utf8")
|
|
217
|
+
);
|
|
218
|
+
const newInput = readFileSync(flags["new-input"], "utf8");
|
|
219
|
+
process.stdout.write(renderFollowup({ previousResult, newInput }));
|
|
220
|
+
return 0;
|
|
221
|
+
} catch (error) {
|
|
222
|
+
console.error(error.message);
|
|
223
|
+
return 1;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async function dispatchCommand(flags) {
|
|
228
|
+
if (typeof flags.role !== "string" || flags.role.length === 0) {
|
|
229
|
+
console.error("Missing required --role <name>");
|
|
230
|
+
return 1;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (
|
|
234
|
+
typeof flags["request-file"] !== "string" ||
|
|
235
|
+
flags["request-file"].length === 0
|
|
236
|
+
) {
|
|
237
|
+
console.error("Missing required --request-file <path>");
|
|
238
|
+
return 1;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (
|
|
242
|
+
flags["resume-handle"] !== undefined &&
|
|
243
|
+
(typeof flags["resume-handle"] !== "string" ||
|
|
244
|
+
flags["resume-handle"].length === 0)
|
|
245
|
+
) {
|
|
246
|
+
console.error("Missing value for --resume-handle <thread-id>");
|
|
247
|
+
return 1;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
try {
|
|
251
|
+
const contracts = loadContracts();
|
|
252
|
+
const resolved = resolveRole({
|
|
253
|
+
role: flags.role,
|
|
254
|
+
catalog: loadCatalog(),
|
|
255
|
+
userConfig: loadUserConfig(),
|
|
256
|
+
projectConfig: loadProjectConfig(),
|
|
257
|
+
contracts
|
|
258
|
+
});
|
|
259
|
+
if (resolved.provider !== "codex") {
|
|
260
|
+
console.error(
|
|
261
|
+
formatDispatchProviderGuardMessage(flags.role, resolved.provider)
|
|
262
|
+
);
|
|
263
|
+
return 2;
|
|
264
|
+
}
|
|
265
|
+
const request = JSON.parse(readFileSync(flags["request-file"], "utf8"));
|
|
266
|
+
|
|
267
|
+
const agentResult = await dispatch({
|
|
268
|
+
role: flags.role,
|
|
269
|
+
request,
|
|
270
|
+
resolved,
|
|
271
|
+
contract: resolved.contract,
|
|
272
|
+
resumeHandle: flags["resume-handle"]
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
writeDispatchResult(agentResult, flags);
|
|
276
|
+
if (agentResult.status === "failed") {
|
|
277
|
+
console.error("Companion returned failed AgentResult.");
|
|
278
|
+
return 1;
|
|
279
|
+
}
|
|
280
|
+
return 0;
|
|
281
|
+
} catch (error) {
|
|
282
|
+
if (error instanceof DispatchError && error.agentResult) {
|
|
283
|
+
writeDispatchResult(error.agentResult, flags);
|
|
284
|
+
}
|
|
285
|
+
console.error(error.message);
|
|
286
|
+
return error instanceof DispatchError ? error.exitCode : 1;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function renderCommand(flags) {
|
|
291
|
+
if (typeof flags.role !== "string" || flags.role.length === 0) {
|
|
292
|
+
console.error("Missing required --role <name>");
|
|
293
|
+
return 1;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (
|
|
297
|
+
typeof flags["request-file"] !== "string" ||
|
|
298
|
+
flags["request-file"].length === 0
|
|
299
|
+
) {
|
|
300
|
+
console.error("Missing required --request-file <path>");
|
|
301
|
+
return 1;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
try {
|
|
305
|
+
const contracts = loadContracts();
|
|
306
|
+
const contract = findContract(flags.role, contracts);
|
|
307
|
+
if (!contract) {
|
|
308
|
+
throw new Error(`Unknown role: ${flags.role}`);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const request = JSON.parse(readFileSync(flags["request-file"], "utf8"));
|
|
312
|
+
process.stdout.write(
|
|
313
|
+
renderPrompt({
|
|
314
|
+
role: flags.role,
|
|
315
|
+
request,
|
|
316
|
+
contract
|
|
317
|
+
})
|
|
318
|
+
);
|
|
319
|
+
return 0;
|
|
320
|
+
} catch (error) {
|
|
321
|
+
console.error(error.message);
|
|
322
|
+
return 1;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function formatTable(value) {
|
|
327
|
+
const rows = [
|
|
328
|
+
["role", value.role],
|
|
329
|
+
["provider", value.provider],
|
|
330
|
+
["model", value.model],
|
|
331
|
+
["reasoning", value.reasoning ?? ""],
|
|
332
|
+
["codex_sandbox", value.codex_sandbox],
|
|
333
|
+
["dispatch.path", value.dispatch.path],
|
|
334
|
+
["dispatch.write", String(value.dispatch.write)]
|
|
335
|
+
];
|
|
336
|
+
|
|
337
|
+
const keyWidth = Math.max(...rows.map(([key]) => key.length));
|
|
338
|
+
return `${rows
|
|
339
|
+
.map(([key, val]) => `${key.padEnd(keyWidth)} ${val}`)
|
|
340
|
+
.join("\n")}\n`;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function formatPrepared(value) {
|
|
344
|
+
if (value.action === "dispatch") {
|
|
345
|
+
return `${value.command.join(" ")}\n`;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return value.prompt;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function findContract(role, contracts) {
|
|
352
|
+
return contracts.roles.find((contract) => contract.role === role);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function writeDispatchResult(agentResult, flags) {
|
|
356
|
+
if (flags.json) {
|
|
357
|
+
process.stdout.write(`${JSON.stringify(agentResult, null, 2)}\n`);
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
process.stdout.write(`${agentResult.summary ?? agentResult.status}\n`);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function usage() {
|
|
365
|
+
console.error("Usage: crew-agent-runner resolve --role <name> [--json]");
|
|
366
|
+
console.error(" crew-agent-runner build [--root <path>]");
|
|
367
|
+
console.error(" crew-agent-runner validate [--root <path>]");
|
|
368
|
+
console.error(" crew-agent-runner install-hooks [--root <path>]");
|
|
369
|
+
console.error(
|
|
370
|
+
" crew-agent-runner prepare --role <name> --request-file <path> [--json]"
|
|
371
|
+
);
|
|
372
|
+
console.error(" crew-agent-runner render --role <name> --request-file <path>");
|
|
373
|
+
console.error(
|
|
374
|
+
" crew-agent-runner render-followup --previous-result <file> --new-input <file>"
|
|
375
|
+
);
|
|
376
|
+
console.error(
|
|
377
|
+
" crew-agent-runner dispatch --role <name> --request-file <path> [--json] [--resume-handle <thread-id>]"
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const exitCode = await main(process.argv.slice(2));
|
|
382
|
+
process.exitCode = exitCode;
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { mkdir, readdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
|
+
|
|
5
|
+
import { loadContracts } from "./contracts.mjs";
|
|
6
|
+
|
|
7
|
+
const DEFAULT_CONTRACTS_PATH = "data/agent-contracts.json";
|
|
8
|
+
const FALLBACK_CONTRACTS_PATH = "contracts.json";
|
|
9
|
+
const DEFAULT_CATALOG_PATH = "data/provider-catalog.json";
|
|
10
|
+
const FALLBACK_CATALOG_PATH = "provider-catalog.json";
|
|
11
|
+
const DEFAULT_INSTRUCTIONS_DIR = "data/agent-instructions";
|
|
12
|
+
const FALLBACK_INSTRUCTIONS_DIR = "instructions";
|
|
13
|
+
const DEFAULT_PLUGIN_PATH = ".claude-plugin/plugin.json";
|
|
14
|
+
const FALLBACK_PLUGIN_PATH = "plugin.json";
|
|
15
|
+
|
|
16
|
+
export async function build({ root = process.cwd() } = {}) {
|
|
17
|
+
const inputs = resolveBuildInputs(resolve(root));
|
|
18
|
+
const contracts = loadContracts(inputs.contractsPath);
|
|
19
|
+
|
|
20
|
+
await warnOrphanInstructions({
|
|
21
|
+
instructionsDir: inputs.instructionsDir,
|
|
22
|
+
contracts
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const derived = await deriveBuildOutput({
|
|
26
|
+
root,
|
|
27
|
+
contracts,
|
|
28
|
+
instructionsDir: inputs.instructionsDir,
|
|
29
|
+
pluginPath: inputs.pluginPath
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const agentsDir = join(inputs.projectRoot, "agents");
|
|
33
|
+
await mkdir(agentsDir, { recursive: true });
|
|
34
|
+
|
|
35
|
+
for (const [role, content] of derived.agents.entries()) {
|
|
36
|
+
await writeFile(join(agentsDir, `${role}.md`), content, "utf8");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
await writeFile(inputs.pluginPath, derived.pluginJson, "utf8");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function deriveBuildOutput({
|
|
43
|
+
root = process.cwd(),
|
|
44
|
+
contracts,
|
|
45
|
+
instructionsDir,
|
|
46
|
+
pluginPath
|
|
47
|
+
} = {}) {
|
|
48
|
+
const inputs = resolveBuildInputs(resolve(root));
|
|
49
|
+
const resolvedContracts =
|
|
50
|
+
contracts ?? loadContracts(inputs.contractsPath);
|
|
51
|
+
const resolvedInstructionsDir = instructionsDir ?? inputs.instructionsDir;
|
|
52
|
+
const resolvedPluginPath = pluginPath ?? inputs.pluginPath;
|
|
53
|
+
|
|
54
|
+
const instructionsByRole = new Map();
|
|
55
|
+
const missingInstructions = [];
|
|
56
|
+
for (const contract of resolvedContracts.roles) {
|
|
57
|
+
const role = contract.role;
|
|
58
|
+
const instructionPath = join(resolvedInstructionsDir, `${role}.md`);
|
|
59
|
+
if (!existsSync(instructionPath)) {
|
|
60
|
+
missingInstructions.push(role);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
instructionsByRole.set(role, await readFile(instructionPath, "utf8"));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (missingInstructions.length > 0) {
|
|
68
|
+
throw new Error(
|
|
69
|
+
`Missing agent instructions: ${missingInstructions.join(", ")}`
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const agents = new Map();
|
|
74
|
+
for (const contract of resolvedContracts.roles) {
|
|
75
|
+
const role = contract.role;
|
|
76
|
+
|
|
77
|
+
const model = contract.claudeSubagent?.model;
|
|
78
|
+
if (typeof model !== "string" || model.length === 0) {
|
|
79
|
+
throw new Error(`Missing Claude subagent model for role: ${role}`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const agent = renderAgent({
|
|
83
|
+
contract,
|
|
84
|
+
model,
|
|
85
|
+
instructions: instructionsByRole.get(role)
|
|
86
|
+
});
|
|
87
|
+
agents.set(role, agent);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const plugin = JSON.parse(await readFile(resolvedPluginPath, "utf8"));
|
|
91
|
+
plugin.agents = resolvedContracts.roles.map(
|
|
92
|
+
(contract) => `./agents/${contract.role}.md`
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
agents,
|
|
97
|
+
pluginJson: `${JSON.stringify(plugin, null, 2)}\n`
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function serializeFrontmatter({ name, model, description, tools }) {
|
|
102
|
+
return [
|
|
103
|
+
"---",
|
|
104
|
+
`name: ${name}`,
|
|
105
|
+
`model: ${model}`,
|
|
106
|
+
`description: ${description}`,
|
|
107
|
+
`tools: [${tools.join(", ")}]`,
|
|
108
|
+
"---"
|
|
109
|
+
].join("\n");
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function renderAgent({ contract, model, instructions }) {
|
|
113
|
+
const frontmatter = serializeFrontmatter({
|
|
114
|
+
name: contract.role,
|
|
115
|
+
model,
|
|
116
|
+
description: contract.claudeSubagent.description,
|
|
117
|
+
tools: contract.claudeSubagent.tools
|
|
118
|
+
});
|
|
119
|
+
const body = [renderCapability(contract), normalizeBlockBody(instructions)]
|
|
120
|
+
.filter(Boolean)
|
|
121
|
+
.join("\n\n");
|
|
122
|
+
|
|
123
|
+
return `${frontmatter}\n\n${body}\n`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function renderCapability(contract) {
|
|
127
|
+
const tools = Array.isArray(contract.claudeSubagent?.tools)
|
|
128
|
+
? contract.claudeSubagent.tools
|
|
129
|
+
: [];
|
|
130
|
+
const outputs = Array.isArray(contract.outputs) ? contract.outputs : [];
|
|
131
|
+
|
|
132
|
+
return [
|
|
133
|
+
"## Capability",
|
|
134
|
+
`workspaceAccess: ${contract.capabilities?.workspaceAccess ?? "unknown"}`,
|
|
135
|
+
`canAskUser: ${String(tools.includes("AskUserQuestion"))}`,
|
|
136
|
+
`canRequestAgent: ${String(tools.includes("Agent"))}`,
|
|
137
|
+
`canUseShell: ${String(tools.includes("Bash"))}`,
|
|
138
|
+
`canWriteCrewFiles: ${String(canWriteCrewFiles(outputs))}`
|
|
139
|
+
].join("\n");
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function warnOrphanInstructions({ instructionsDir, contracts }) {
|
|
143
|
+
const roles = new Set(contracts.roles.map((contract) => contract.role));
|
|
144
|
+
const entries = await readdir(instructionsDir, { withFileTypes: true });
|
|
145
|
+
for (const entry of entries) {
|
|
146
|
+
if (!entry.isFile() || !entry.name.endsWith(".md")) {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const role = entry.name.slice(0, -".md".length);
|
|
151
|
+
if (!roles.has(role)) {
|
|
152
|
+
console.error(`Warning: instruction file has no contract role: ${role}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function resolveInput(root, primary, fallback) {
|
|
158
|
+
const primaryPath = join(root, primary);
|
|
159
|
+
if (existsSync(primaryPath)) {
|
|
160
|
+
return primaryPath;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const fallbackPath = join(root, fallback);
|
|
164
|
+
if (existsSync(fallbackPath)) {
|
|
165
|
+
return fallbackPath;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return primaryPath;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function resolveBuildInputs(root = process.cwd()) {
|
|
172
|
+
const projectRoot = resolve(root);
|
|
173
|
+
return {
|
|
174
|
+
projectRoot,
|
|
175
|
+
contractsPath: resolveInput(
|
|
176
|
+
projectRoot,
|
|
177
|
+
DEFAULT_CONTRACTS_PATH,
|
|
178
|
+
FALLBACK_CONTRACTS_PATH
|
|
179
|
+
),
|
|
180
|
+
catalogPath: resolveInput(
|
|
181
|
+
projectRoot,
|
|
182
|
+
DEFAULT_CATALOG_PATH,
|
|
183
|
+
FALLBACK_CATALOG_PATH
|
|
184
|
+
),
|
|
185
|
+
instructionsDir: resolveInput(
|
|
186
|
+
projectRoot,
|
|
187
|
+
DEFAULT_INSTRUCTIONS_DIR,
|
|
188
|
+
FALLBACK_INSTRUCTIONS_DIR
|
|
189
|
+
),
|
|
190
|
+
pluginPath: resolveInput(
|
|
191
|
+
projectRoot,
|
|
192
|
+
DEFAULT_PLUGIN_PATH,
|
|
193
|
+
FALLBACK_PLUGIN_PATH
|
|
194
|
+
)
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function normalizeBlockBody(body) {
|
|
199
|
+
return String(body)
|
|
200
|
+
.replace(/^\uFEFF/, "")
|
|
201
|
+
.replace(/\r\n?/g, "\n")
|
|
202
|
+
.replace(/\n+$/g, "");
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function canWriteCrewFiles(outputs) {
|
|
206
|
+
return outputs.some((output) => {
|
|
207
|
+
return (
|
|
208
|
+
output?.type === "artifact" &&
|
|
209
|
+
typeof output.target === "string" &&
|
|
210
|
+
output.target.startsWith(".crew/")
|
|
211
|
+
);
|
|
212
|
+
});
|
|
213
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export function parseArgv(argv) {
|
|
2
|
+
const positional = [];
|
|
3
|
+
const flags = {};
|
|
4
|
+
|
|
5
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
6
|
+
const arg = argv[index];
|
|
7
|
+
|
|
8
|
+
if (!arg.startsWith("--") || arg === "--") {
|
|
9
|
+
positional.push(arg);
|
|
10
|
+
continue;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const key = arg.slice(2);
|
|
14
|
+
if (key.length === 0) {
|
|
15
|
+
positional.push(arg);
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const next = argv[index + 1];
|
|
20
|
+
if (next === undefined || next.startsWith("--")) {
|
|
21
|
+
flags[key] = true;
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
flags[key] = next;
|
|
26
|
+
index += 1;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return { positional, flags };
|
|
30
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
|
+
|
|
5
|
+
import { pluginPath } from "./pluginRoot.mjs";
|
|
6
|
+
|
|
7
|
+
export function loadCatalog(filePath) {
|
|
8
|
+
return readJson(
|
|
9
|
+
filePath === undefined
|
|
10
|
+
? pluginPath("data", "provider-catalog.json")
|
|
11
|
+
: resolve(process.cwd(), filePath),
|
|
12
|
+
true
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function loadUserConfig(filePath = join(homedir(), ".claude", "crew", "config.json")) {
|
|
17
|
+
return readJson(filePath, true);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function loadProjectConfig(projectRoot = process.cwd()) {
|
|
21
|
+
return readJson(join(projectRoot, ".crew", "config.json"), true);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function readJson(filePath, allowMissing) {
|
|
25
|
+
if (!existsSync(filePath)) {
|
|
26
|
+
if (allowMissing) {
|
|
27
|
+
return {};
|
|
28
|
+
}
|
|
29
|
+
throw new Error(`File not found: ${filePath}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return JSON.parse(readFileSync(filePath, "utf8"));
|
|
33
|
+
}
|