@jjlabsio/claude-crew 0.1.34 → 0.1.36
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 +1 -1
- package/hooks/enforce-delegation.mjs +51 -0
- package/package.json +2 -1
- package/scripts/crew-agent-runner.mjs +59 -0
- package/scripts/lib/prepare.mjs +37 -0
- package/scripts/lib/render.mjs +29 -1
- package/scripts/lib/skillDispatchContract.mjs +93 -0
- package/scripts/lib/validate.mjs +4 -0
- package/skills/crew-agent-runner/SKILL.md +11 -13
- package/skills/crew-dev/SKILL.md +13 -0
- package/skills/crew-interview/SKILL.md +13 -0
- package/skills/crew-plan/SKILL.md +13 -0
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"name": "claude-crew",
|
|
12
12
|
"source": "./",
|
|
13
13
|
"description": "오케스트레이터 + PM, 플래너, 개발, QA, 마케팅 에이전트 팀으로 단일 제품의 개발과 마케팅을 통합 관리",
|
|
14
|
-
"version": "0.1.
|
|
14
|
+
"version": "0.1.36",
|
|
15
15
|
"author": {
|
|
16
16
|
"name": "Jaejin Song",
|
|
17
17
|
"email": "wowlxx28@gmail.com"
|
|
@@ -28,5 +28,5 @@
|
|
|
28
28
|
"category": "workflow"
|
|
29
29
|
}
|
|
30
30
|
],
|
|
31
|
-
"version": "0.1.
|
|
31
|
+
"version": "0.1.36"
|
|
32
32
|
}
|
|
@@ -10,6 +10,10 @@
|
|
|
10
10
|
import { readFileSync, readdirSync } from 'node:fs';
|
|
11
11
|
import { join, dirname } from 'node:path';
|
|
12
12
|
|
|
13
|
+
import { loadCatalog, loadProjectConfig, loadUserConfig } from '../scripts/lib/config.mjs';
|
|
14
|
+
import { loadContracts } from '../scripts/lib/contracts.mjs';
|
|
15
|
+
import { resolveRole } from '../scripts/lib/resolve.mjs';
|
|
16
|
+
|
|
13
17
|
// ---------------------------------------------------------------------------
|
|
14
18
|
// Read stdin
|
|
15
19
|
// ---------------------------------------------------------------------------
|
|
@@ -52,6 +56,30 @@ function loadAgentDefinitions(pluginRoot) {
|
|
|
52
56
|
return defs;
|
|
53
57
|
}
|
|
54
58
|
|
|
59
|
+
function resolveCrewRoleProvider(role) {
|
|
60
|
+
return resolveRole({
|
|
61
|
+
role,
|
|
62
|
+
catalog: loadCatalog(),
|
|
63
|
+
userConfig: loadUserConfig(),
|
|
64
|
+
projectConfig: loadProjectConfig(),
|
|
65
|
+
contracts: loadContracts()
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function blockCodexAgentCall(role, resolved) {
|
|
70
|
+
return {
|
|
71
|
+
continue: true,
|
|
72
|
+
decision: 'block',
|
|
73
|
+
reason: [
|
|
74
|
+
`Role '${role}' is configured for Codex provider and cannot be called with the Agent tool.`,
|
|
75
|
+
'Use the central runner prepare/dispatch path instead:',
|
|
76
|
+
`node "$CLAUDE_PLUGIN_ROOT/scripts/crew-agent-runner.mjs" prepare --role ${role} --request-file <request-file> --json`,
|
|
77
|
+
`node "$CLAUDE_PLUGIN_ROOT/scripts/crew-agent-runner.mjs" dispatch --role ${role} --request-file <request-file> --json`,
|
|
78
|
+
`Resolved model: ${resolved.model}${resolved.reasoning ? ` / ${resolved.reasoning}` : ''}`
|
|
79
|
+
].join('\n')
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
55
83
|
// ---------------------------------------------------------------------------
|
|
56
84
|
// Main
|
|
57
85
|
// ---------------------------------------------------------------------------
|
|
@@ -86,6 +114,29 @@ async function main() {
|
|
|
86
114
|
|
|
87
115
|
// Canonicalize subagent_type (strip plugin prefix if present)
|
|
88
116
|
const rawType = input.subagent_type.replace(/^claude-crew:/, '');
|
|
117
|
+
let resolved = null;
|
|
118
|
+
try {
|
|
119
|
+
resolved = resolveCrewRoleProvider(rawType);
|
|
120
|
+
} catch {
|
|
121
|
+
resolved = null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (resolved?.provider === 'codex') {
|
|
125
|
+
console.log(JSON.stringify(blockCodexAgentCall(rawType, resolved)));
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (resolved?.provider === 'claude' && !input.model && resolved.model) {
|
|
130
|
+
console.log(JSON.stringify({
|
|
131
|
+
continue: true,
|
|
132
|
+
modifiedInput: { ...input, model: resolved.model },
|
|
133
|
+
hookSpecificOutput: {
|
|
134
|
+
hookEventName: 'PreToolUse',
|
|
135
|
+
additionalContext: `model 자동 주입: ${rawType} → ${resolved.model}`,
|
|
136
|
+
},
|
|
137
|
+
}));
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
89
140
|
|
|
90
141
|
// Load agent definitions and auto-inject model if missing
|
|
91
142
|
const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT || dirname(import.meta.url.replace('file://', '')).replace('/hooks', '');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jjlabsio/claude-crew",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.36",
|
|
4
4
|
"description": "1인 SaaS 개발자를 위한 멀티 에이전트 오케스트레이션 — 개발, 마케팅, 일정을 한 대화에서 통합 관리",
|
|
5
5
|
"author": "Jaejin Song <wowlxx28@gmail.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"LICENSE"
|
|
29
29
|
],
|
|
30
30
|
"scripts": {
|
|
31
|
+
"smoke": "node tests/smoke/run.mjs",
|
|
31
32
|
"test": "vitest",
|
|
32
33
|
"test:run": "vitest run",
|
|
33
34
|
"test:watch": "vitest"
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
formatDispatchProviderGuardMessage
|
|
16
16
|
} from "./lib/dispatch.mjs";
|
|
17
17
|
import { installHooks } from "./lib/installHooks.mjs";
|
|
18
|
+
import { prepareDispatch } from "./lib/prepare.mjs";
|
|
18
19
|
import { renderFollowup } from "./lib/renderFollowup.mjs";
|
|
19
20
|
import { renderPrompt } from "./lib/render.mjs";
|
|
20
21
|
import { resolveRole } from "./lib/resolve.mjs";
|
|
@@ -33,6 +34,10 @@ async function main(argv) {
|
|
|
33
34
|
return renderCommand(flags);
|
|
34
35
|
}
|
|
35
36
|
|
|
37
|
+
if (command === "prepare") {
|
|
38
|
+
return prepareCommand(flags);
|
|
39
|
+
}
|
|
40
|
+
|
|
36
41
|
if (command === "dispatch") {
|
|
37
42
|
return dispatchCommand(flags);
|
|
38
43
|
}
|
|
@@ -84,6 +89,49 @@ async function main(argv) {
|
|
|
84
89
|
}
|
|
85
90
|
}
|
|
86
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
|
+
|
|
87
135
|
async function installHooksCommand(flags) {
|
|
88
136
|
if (
|
|
89
137
|
flags.root !== undefined &&
|
|
@@ -292,6 +340,14 @@ function formatTable(value) {
|
|
|
292
340
|
.join("\n")}\n`;
|
|
293
341
|
}
|
|
294
342
|
|
|
343
|
+
function formatPrepared(value) {
|
|
344
|
+
if (value.action === "dispatch") {
|
|
345
|
+
return `${value.command.join(" ")}\n`;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return value.prompt;
|
|
349
|
+
}
|
|
350
|
+
|
|
295
351
|
function findContract(role, contracts) {
|
|
296
352
|
return contracts.roles.find((contract) => contract.role === role);
|
|
297
353
|
}
|
|
@@ -310,6 +366,9 @@ function usage() {
|
|
|
310
366
|
console.error(" crew-agent-runner build [--root <path>]");
|
|
311
367
|
console.error(" crew-agent-runner validate [--root <path>]");
|
|
312
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
|
+
);
|
|
313
372
|
console.error(" crew-agent-runner render --role <name> --request-file <path>");
|
|
314
373
|
console.error(
|
|
315
374
|
" crew-agent-runner render-followup --previous-result <file> --new-input <file>"
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { renderPrompt } from "./render.mjs";
|
|
2
|
+
import { pluginPath } from "./pluginRoot.mjs";
|
|
3
|
+
|
|
4
|
+
export function prepareDispatch({ role, requestFile, request, resolved }) {
|
|
5
|
+
if (resolved.provider === "codex") {
|
|
6
|
+
return {
|
|
7
|
+
role,
|
|
8
|
+
provider: "codex",
|
|
9
|
+
action: "dispatch",
|
|
10
|
+
command: [
|
|
11
|
+
"node",
|
|
12
|
+
pluginPath("scripts", "crew-agent-runner.mjs"),
|
|
13
|
+
"dispatch",
|
|
14
|
+
"--role",
|
|
15
|
+
role,
|
|
16
|
+
"--request-file",
|
|
17
|
+
requestFile,
|
|
18
|
+
"--json"
|
|
19
|
+
],
|
|
20
|
+
resolved
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
role,
|
|
26
|
+
provider: "claude",
|
|
27
|
+
action: "agent",
|
|
28
|
+
subagent_type: role,
|
|
29
|
+
model: resolved.model,
|
|
30
|
+
prompt: renderPrompt({
|
|
31
|
+
role,
|
|
32
|
+
request,
|
|
33
|
+
contract: resolved.contract
|
|
34
|
+
}),
|
|
35
|
+
resolved
|
|
36
|
+
};
|
|
37
|
+
}
|
package/scripts/lib/render.mjs
CHANGED
|
@@ -7,7 +7,8 @@ export function renderPrompt(input) {
|
|
|
7
7
|
section("Outputs", renderJson(contract.outputs)),
|
|
8
8
|
section("Instructions", request.instruction, { required: true }),
|
|
9
9
|
section("Success Gate", request.successGate),
|
|
10
|
-
section("Failure Handling", request.failureHandling)
|
|
10
|
+
section("Failure Handling", request.failureHandling),
|
|
11
|
+
section("AgentResult Contract", renderAgentResultContract())
|
|
11
12
|
].filter(Boolean);
|
|
12
13
|
|
|
13
14
|
return `${parts.join("\n\n")}\n`;
|
|
@@ -72,6 +73,33 @@ function renderJson(value) {
|
|
|
72
73
|
return JSON.stringify(value, null, 2);
|
|
73
74
|
}
|
|
74
75
|
|
|
76
|
+
function renderAgentResultContract() {
|
|
77
|
+
return [
|
|
78
|
+
"Return exactly one final AgentResult JSON object wrapped in these tags:",
|
|
79
|
+
"",
|
|
80
|
+
"```text",
|
|
81
|
+
"<crew-agent-result>",
|
|
82
|
+
"{",
|
|
83
|
+
' "status": "complete | blocked_on_user | needs_agent | needs_tool | failed",',
|
|
84
|
+
' "artifact": null,',
|
|
85
|
+
' "questions": [],',
|
|
86
|
+
' "requests": [],',
|
|
87
|
+
' "summary": "short summary",',
|
|
88
|
+
' "error": null',
|
|
89
|
+
"}",
|
|
90
|
+
"</crew-agent-result>",
|
|
91
|
+
"```",
|
|
92
|
+
"",
|
|
93
|
+
"Rules:",
|
|
94
|
+
"- The wrapper tags are mandatory.",
|
|
95
|
+
"- The JSON inside the tags must be valid JSON.",
|
|
96
|
+
"- Use complete when the requested artifact is ready.",
|
|
97
|
+
"- Use blocked_on_user only with a non-empty questions array.",
|
|
98
|
+
"- Use needs_agent or needs_tool only with a non-empty requests array.",
|
|
99
|
+
"- Use failed with an error string when the task cannot continue."
|
|
100
|
+
].join("\n");
|
|
101
|
+
}
|
|
102
|
+
|
|
75
103
|
function normalizeBody(body) {
|
|
76
104
|
if (body === undefined || body === null) {
|
|
77
105
|
return "";
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
export const RUNNER_DISPATCH_MARKER =
|
|
5
|
+
"중앙 `crew-agent-runner` 스킬의 dispatch 절차로 실행한다.";
|
|
6
|
+
|
|
7
|
+
export const COMMON_AGENT_INTERFACE_HEADING =
|
|
8
|
+
"## 공통 에이전트 실행 인터페이스";
|
|
9
|
+
|
|
10
|
+
export const REQUIRED_INTERFACE_MARKERS = [
|
|
11
|
+
COMMON_AGENT_INTERFACE_HEADING,
|
|
12
|
+
'node "$CLAUDE_PLUGIN_ROOT/scripts/crew-agent-runner.mjs" prepare --role <role> --request-file <request-file> --json',
|
|
13
|
+
"request-file",
|
|
14
|
+
"action == dispatch",
|
|
15
|
+
"action == agent",
|
|
16
|
+
"직접 하위 에이전트를 호출하지 않는다",
|
|
17
|
+
"AgentResult"
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
const FORBIDDEN_DIRECT_DISPATCH_PATTERNS = [
|
|
21
|
+
/Agent\(/,
|
|
22
|
+
/Bash\(/,
|
|
23
|
+
new RegExp(["crew", "codex", "companion"].join("-")),
|
|
24
|
+
/runAgent\(/,
|
|
25
|
+
new RegExp(["AskUser", "Question"].join("")),
|
|
26
|
+
new RegExp(`<${["crew", "agent", "result"].join("-")}>`)
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
export async function validateWorkflowSkillDispatchContracts({
|
|
30
|
+
root = process.cwd()
|
|
31
|
+
} = {}) {
|
|
32
|
+
const skillsDir = join(root, "skills");
|
|
33
|
+
const errors = [];
|
|
34
|
+
let entries = [];
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
entries = await readdir(skillsDir, { withFileTypes: true });
|
|
38
|
+
} catch (error) {
|
|
39
|
+
if (error?.code === "ENOENT") {
|
|
40
|
+
return errors;
|
|
41
|
+
}
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
for (const entry of entries) {
|
|
46
|
+
if (!entry.isDirectory()) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const relPath = `skills/${entry.name}/SKILL.md`;
|
|
51
|
+
const text = await readUtf8OrNull(join(root, relPath));
|
|
52
|
+
if (!text || entry.name === "crew-agent-runner") {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (!text.includes(RUNNER_DISPATCH_MARKER)) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
errors.push(...validateWorkflowSkillDispatchContract(text, relPath));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return errors;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function validateWorkflowSkillDispatchContract(text, relPath = "SKILL.md") {
|
|
67
|
+
const errors = [];
|
|
68
|
+
|
|
69
|
+
for (const pattern of FORBIDDEN_DIRECT_DISPATCH_PATTERNS) {
|
|
70
|
+
if (pattern.test(text)) {
|
|
71
|
+
errors.push(`${relPath}: direct agent dispatch is forbidden: ${pattern}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
for (const marker of REQUIRED_INTERFACE_MARKERS) {
|
|
76
|
+
if (!text.includes(marker)) {
|
|
77
|
+
errors.push(`${relPath}: missing runner dispatch interface marker: ${marker}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return errors;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function readUtf8OrNull(path) {
|
|
85
|
+
try {
|
|
86
|
+
return await readFile(path, "utf8");
|
|
87
|
+
} catch (error) {
|
|
88
|
+
if (error?.code === "ENOENT") {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
}
|
package/scripts/lib/validate.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import { join, relative, resolve } from "node:path";
|
|
|
3
3
|
|
|
4
4
|
import { deriveBuildOutput, resolveBuildInputs } from "./build.mjs";
|
|
5
5
|
import { loadContracts } from "./contracts.mjs";
|
|
6
|
+
import { validateWorkflowSkillDispatchContracts } from "./skillDispatchContract.mjs";
|
|
6
7
|
|
|
7
8
|
export async function validate({ root = process.cwd() } = {}) {
|
|
8
9
|
const projectRoot = resolve(root);
|
|
@@ -26,6 +27,9 @@ export async function validate({ root = process.cwd() } = {}) {
|
|
|
26
27
|
}))
|
|
27
28
|
);
|
|
28
29
|
errors.push(...compareSandboxConsistency({ contracts, catalog }));
|
|
30
|
+
errors.push(
|
|
31
|
+
...(await validateWorkflowSkillDispatchContracts({ root: projectRoot }))
|
|
32
|
+
);
|
|
29
33
|
|
|
30
34
|
return { ok: errors.length === 0, errors };
|
|
31
35
|
}
|
|
@@ -5,7 +5,7 @@ description: 모든 crew 에이전트 dispatch의 중앙 규약 — provider별
|
|
|
5
5
|
|
|
6
6
|
# crew-agent-runner
|
|
7
7
|
|
|
8
|
-
crew 업무 스킬은 에이전트 provider별 호출 세부사항을 직접 구현하지 않고 이 중앙 규약을 따른다. 본 스킬은 resolve, dispatch, resume, followup 주입, retry/fallback/escalate 판단의 공통 표면을 정의한다.
|
|
8
|
+
crew 업무 스킬은 에이전트 provider별 호출 세부사항을 직접 구현하지 않고 이 중앙 규약을 따른다. 본 스킬은 prepare, resolve, dispatch, resume, followup 주입, retry/fallback/escalate 판단의 공통 표면을 정의한다.
|
|
9
9
|
|
|
10
10
|
설치 후 drift 차단용 pre-commit hook은 `node scripts/crew-agent-runner.mjs install-hooks`로 설치한다.
|
|
11
11
|
(plugin 개발자 전용 — 사용자는 호출하지 않습니다. build/validate는 plugin source repo의 drift 차단 도구입니다.)
|
|
@@ -14,26 +14,24 @@ crew 업무 스킬은 에이전트 provider별 호출 세부사항을 직접 구
|
|
|
14
14
|
|
|
15
15
|
업무 스킬(crew-plan/crew-interview/crew-dev)이 role을 실행해야 할 때 본 절차를 따른다.
|
|
16
16
|
|
|
17
|
-
### 1.
|
|
17
|
+
### 1. request 객체 작성
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
`{ role, inputs (path+content), instruction, successGate, failureHandling, taskId }` 형태의 임시 JSON 파일을 작성한다.
|
|
20
20
|
|
|
21
|
-
### 2.
|
|
21
|
+
### 2. prepare
|
|
22
22
|
|
|
23
|
-
`
|
|
23
|
+
오케스트레이터는 `node "$CLAUDE_PLUGIN_ROOT/scripts/crew-agent-runner.mjs" prepare --role <role> --request-file <request-file> --json`을 실행한다.
|
|
24
|
+
prepare는 provider/model/contract를 해석하고 다음 action 중 하나를 반환한다.
|
|
24
25
|
|
|
25
|
-
### 3a.
|
|
26
|
+
### 3a. dispatch action
|
|
26
27
|
|
|
27
|
-
`
|
|
28
|
-
dispatch CLI는 codex provider role에만 사용. claude provider role은 render + Agent tool 경로를 사용.
|
|
28
|
+
`action == dispatch`이면 prepare가 반환한 command를 실행한다. 이 경로는 Codex provider role에만 사용하며 AgentResult JSON을 즉시 반환한다.
|
|
29
29
|
|
|
30
|
-
### 3b.
|
|
30
|
+
### 3b. agent action
|
|
31
31
|
|
|
32
|
-
`
|
|
32
|
+
`action == agent`이면 prepare가 반환한 `subagent_type`, `model`, `prompt`로 메인 오케스트레이터가 Claude provider 역할을 실행하고, sub-agent 결과를 AgentResult JSON 형식으로 정규화한다.
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
2. 메인 오케스트레이터(Claude conversation)가 `Agent(subagent_type=<role>, model=<model>, prompt=<rendered>)`를 호출한다.
|
|
36
|
-
3. sub-agent 결과를 AgentResult JSON 형식으로 정규화한다.
|
|
34
|
+
주의: 업무 스킬은 직접 provider를 분기하거나 직접 하위 에이전트를 호출하지 않는다. 항상 prepare 결과의 action만 수행한다.
|
|
37
35
|
|
|
38
36
|
## AgentResult 상태 처리
|
|
39
37
|
|
package/skills/crew-dev/SKILL.md
CHANGED
|
@@ -23,6 +23,19 @@ description: contract.md를 입력으로 받아 Dev + CodeReviewer + QA 파이
|
|
|
23
23
|
에이전트가 사용자 입력이 필요하다고 반환하면 오케스트레이터가 사용자에게 질문한다.
|
|
24
24
|
에이전트가 추가 역할 실행이나 허용 도구 실행을 요청하면 오케스트레이터가 runner 정책에 따라 처리하고 같은 역할 실행에 후속 입력으로 주입한다.
|
|
25
25
|
|
|
26
|
+
## 공통 에이전트 실행 인터페이스
|
|
27
|
+
|
|
28
|
+
crew-dev의 모든 에이전트 실행은 역할이나 phase와 무관하게 아래 인터페이스만 사용한다.
|
|
29
|
+
오케스트레이터는 `Dev`, `CodeReviewer`, `QA`, 후속 요청 role을 실행할 때마다 이 순서를 반복한다.
|
|
30
|
+
|
|
31
|
+
1. `{ role, taskId, inputs, instruction, successGate, failureHandling }` 형태의 `request-file`을 작성한다.
|
|
32
|
+
2. `node "$CLAUDE_PLUGIN_ROOT/scripts/crew-agent-runner.mjs" prepare --role <role> --request-file <request-file> --json`을 실행한다.
|
|
33
|
+
3. `action == dispatch`이면 prepare가 반환한 command를 실행하고 AgentResult를 처리한다.
|
|
34
|
+
4. `action == agent`이면 prepare가 반환한 `subagent_type`, `model`, `prompt`로 runner 계약의 Claude 경로를 실행하고 AgentResult로 정규화한다.
|
|
35
|
+
|
|
36
|
+
이 순서를 생략하고 직접 하위 에이전트를 호출하지 않는다.
|
|
37
|
+
provider 선택, 런타임 선택, AgentResult 반환 형식, 후속 입력 주입, retry/fallback/escalate 판단은 모두 중앙 runner 계약을 따른다.
|
|
38
|
+
|
|
26
39
|
---
|
|
27
40
|
|
|
28
41
|
## 절대 금지
|
|
@@ -36,6 +36,19 @@ description: 유저 요구사항을 인터뷰하여 개발 가능한 수준의 s
|
|
|
36
36
|
|
|
37
37
|
에이전트 실행은 모두 중앙 `crew-agent-runner` 스킬의 dispatch 절차로 실행한다. 이 문서는 어떤 런타임을 어떻게 호출하는지가 아니라, 각 Phase가 어떤 역할에게 어떤 입력을 주고 어떤 결과를 받아야 하는지만 정의한다.
|
|
38
38
|
|
|
39
|
+
## 공통 에이전트 실행 인터페이스
|
|
40
|
+
|
|
41
|
+
crew-interview의 모든 에이전트 실행은 역할이나 phase와 무관하게 아래 인터페이스만 사용한다.
|
|
42
|
+
오케스트레이터는 `pm`, `explorer`, `researcher`, 후속 요청 role을 실행할 때마다 이 순서를 반복한다.
|
|
43
|
+
|
|
44
|
+
1. `{ role, taskId, inputs, instruction, successGate, failureHandling }` 형태의 `request-file`을 작성한다.
|
|
45
|
+
2. `node "$CLAUDE_PLUGIN_ROOT/scripts/crew-agent-runner.mjs" prepare --role <role> --request-file <request-file> --json`을 실행한다.
|
|
46
|
+
3. `action == dispatch`이면 prepare가 반환한 command를 실행하고 AgentResult를 처리한다.
|
|
47
|
+
4. `action == agent`이면 prepare가 반환한 `subagent_type`, `model`, `prompt`로 runner 계약의 Claude 경로를 실행하고 AgentResult로 정규화한다.
|
|
48
|
+
|
|
49
|
+
이 순서를 생략하고 직접 하위 에이전트를 호출하지 않는다.
|
|
50
|
+
provider 선택, 런타임 선택, AgentResult 반환 형식, 후속 입력 주입, retry/fallback/escalate 판단은 모두 중앙 runner 계약을 따른다.
|
|
51
|
+
|
|
39
52
|
### Phase 1 — 초기화
|
|
40
53
|
|
|
41
54
|
중앙 `crew-agent-runner` 스킬의 dispatch 절차로 실행한다.
|
|
@@ -42,6 +42,19 @@ crew-interview가 생성한 spec.md를 입력으로 받아 **HOW(어떻게 만
|
|
|
42
42
|
|
|
43
43
|
각 에이전트 단계는 중앙 `crew-agent-runner` 스킬의 dispatch 절차로 실행한다. 이 문서는 역할, 입력, 기대 산출물, 검증 기준만 정의하며 실행 방식은 runner 계약을 따른다.
|
|
44
44
|
|
|
45
|
+
## 공통 에이전트 실행 인터페이스
|
|
46
|
+
|
|
47
|
+
crew-plan의 모든 에이전트 실행은 역할이나 step과 무관하게 아래 인터페이스만 사용한다.
|
|
48
|
+
오케스트레이터는 `techlead`, `planner`, `plan-evaluator`, 후속 요청 role을 실행할 때마다 이 순서를 반복한다.
|
|
49
|
+
|
|
50
|
+
1. `{ role, taskId, inputs, instruction, successGate, failureHandling }` 형태의 `request-file`을 작성한다.
|
|
51
|
+
2. `node "$CLAUDE_PLUGIN_ROOT/scripts/crew-agent-runner.mjs" prepare --role <role> --request-file <request-file> --json`을 실행한다.
|
|
52
|
+
3. `action == dispatch`이면 prepare가 반환한 command를 실행하고 AgentResult를 처리한다.
|
|
53
|
+
4. `action == agent`이면 prepare가 반환한 `subagent_type`, `model`, `prompt`로 runner 계약의 Claude 경로를 실행하고 AgentResult로 정규화한다.
|
|
54
|
+
|
|
55
|
+
이 순서를 생략하고 직접 하위 에이전트를 호출하지 않는다.
|
|
56
|
+
provider 선택, 런타임 선택, AgentResult 반환 형식, 후속 입력 주입, retry/fallback/escalate 판단은 모두 중앙 runner 계약을 따른다.
|
|
57
|
+
|
|
45
58
|
### Step 1 — spec.md 검증
|
|
46
59
|
|
|
47
60
|
role: orchestrator
|