@jjlabsio/claude-crew 0.1.43 → 0.1.44
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/package.json +1 -1
- package/scripts/crew-agent-runner.mjs +38 -2
- package/scripts/lib/artifacts.mjs +28 -5
- package/scripts/lib/checkpoint.mjs +36 -0
- package/scripts/lib/dispatch.mjs +21 -3
- package/skills/crew-dev/SKILL.md +6 -4
- package/skills/crew-do/SKILL.md +3 -1
- package/skills/crew-interview/SKILL.md +11 -1
- package/skills/crew-plan/SKILL.md +11 -1
|
@@ -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.44",
|
|
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.44"
|
|
32
32
|
}
|
package/package.json
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
persistCrewArtifact,
|
|
14
14
|
ArtifactPersistError
|
|
15
15
|
} from "./lib/artifacts.mjs";
|
|
16
|
+
import { checkpoint, CheckpointError } from "./lib/checkpoint.mjs";
|
|
16
17
|
import {
|
|
17
18
|
dispatch,
|
|
18
19
|
DispatchError,
|
|
@@ -50,6 +51,10 @@ async function main(argv) {
|
|
|
50
51
|
return persistArtifactCommand(flags);
|
|
51
52
|
}
|
|
52
53
|
|
|
54
|
+
if (command === "checkpoint") {
|
|
55
|
+
return checkpointCommand(flags);
|
|
56
|
+
}
|
|
57
|
+
|
|
53
58
|
if (command === "render-followup") {
|
|
54
59
|
return renderFollowupCommand(flags);
|
|
55
60
|
}
|
|
@@ -332,7 +337,8 @@ async function dispatchCommand(flags) {
|
|
|
332
337
|
request,
|
|
333
338
|
resolved,
|
|
334
339
|
contract: resolved.contract,
|
|
335
|
-
resumeHandle: flags["resume-handle"]
|
|
340
|
+
resumeHandle: flags["resume-handle"],
|
|
341
|
+
noCheckpoint: flags["no-checkpoint"] === true
|
|
336
342
|
});
|
|
337
343
|
|
|
338
344
|
writeDispatchResult(agentResult, flags);
|
|
@@ -350,6 +356,33 @@ async function dispatchCommand(flags) {
|
|
|
350
356
|
}
|
|
351
357
|
}
|
|
352
358
|
|
|
359
|
+
async function checkpointCommand(flags) {
|
|
360
|
+
if (typeof flags.message !== "string" || flags.message.length === 0) {
|
|
361
|
+
console.error("Missing required --message <text>");
|
|
362
|
+
return 1;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
try {
|
|
366
|
+
const result = await checkpoint({ message: flags.message });
|
|
367
|
+
|
|
368
|
+
if (flags.json) {
|
|
369
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
370
|
+
} else if (result.committed) {
|
|
371
|
+
process.stdout.write(`${result.hash} ${result.message}\n`);
|
|
372
|
+
} else {
|
|
373
|
+
process.stdout.write("Nothing to commit.\n");
|
|
374
|
+
}
|
|
375
|
+
return 0;
|
|
376
|
+
} catch (error) {
|
|
377
|
+
if (error instanceof CheckpointError) {
|
|
378
|
+
console.error(error.message);
|
|
379
|
+
return 1;
|
|
380
|
+
}
|
|
381
|
+
console.error(error.message);
|
|
382
|
+
return 1;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
353
386
|
function renderCommand(flags) {
|
|
354
387
|
if (typeof flags.role !== "string" || flags.role.length === 0) {
|
|
355
388
|
console.error("Missing required --role <name>");
|
|
@@ -437,11 +470,14 @@ function usage() {
|
|
|
437
470
|
" crew-agent-runner render-followup --previous-result <file> --new-input <file>"
|
|
438
471
|
);
|
|
439
472
|
console.error(
|
|
440
|
-
" crew-agent-runner dispatch --role <name> --request-file <path> [--json] [--resume-handle <thread-id>]"
|
|
473
|
+
" crew-agent-runner dispatch --role <name> --request-file <path> [--json] [--resume-handle <thread-id>] [--no-checkpoint]"
|
|
441
474
|
);
|
|
442
475
|
console.error(
|
|
443
476
|
" crew-agent-runner persist-artifact --role <name> --result-file <path> --request-file <path>"
|
|
444
477
|
);
|
|
478
|
+
console.error(
|
|
479
|
+
" crew-agent-runner checkpoint --message <text> [--json]"
|
|
480
|
+
);
|
|
445
481
|
}
|
|
446
482
|
|
|
447
483
|
const exitCode = await main(process.argv.slice(2));
|
|
@@ -18,7 +18,7 @@ export async function persistCrewArtifact({ workspaceRoot, contract, request, ag
|
|
|
18
18
|
return null;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
const resolvedTarget =
|
|
21
|
+
const resolvedTarget = resolveTemplateTarget(target, request);
|
|
22
22
|
const absolutePath = validateTargetPath(workspaceRoot, resolvedTarget);
|
|
23
23
|
|
|
24
24
|
await mkdir(dirname(absolutePath), { recursive: true });
|
|
@@ -57,11 +57,34 @@ function findArtifactTarget(contract) {
|
|
|
57
57
|
return null;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
function
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
function resolveTemplateTarget(target, request) {
|
|
61
|
+
const values = {
|
|
62
|
+
"task-id": firstString(request?.taskId, request?.task_id, request?.["task-id"]),
|
|
63
|
+
"run-id": firstString(request?.runId, request?.run_id, request?.["run-id"])
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const resolved = target.replace(/\{(task-id|run-id)\}/g, (match, name) => {
|
|
67
|
+
const value = values[name];
|
|
68
|
+
return value ?? match;
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const unresolved = resolved.match(/\{[^}/\\]+\}/);
|
|
72
|
+
if (unresolved) {
|
|
73
|
+
throw new ArtifactPersistError(
|
|
74
|
+
`Unresolved template variable ${unresolved[0]} in artifact target: ${target}`
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return resolved;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function firstString(...values) {
|
|
82
|
+
for (const value of values) {
|
|
83
|
+
if (typeof value === "string" && value.length > 0) {
|
|
84
|
+
return value;
|
|
85
|
+
}
|
|
63
86
|
}
|
|
64
|
-
return
|
|
87
|
+
return null;
|
|
65
88
|
}
|
|
66
89
|
|
|
67
90
|
function validateTargetPath(workspaceRoot, target) {
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
export class CheckpointError extends Error {
|
|
4
|
+
constructor(message) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = "CheckpointError";
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function checkpoint({ message, cwd }) {
|
|
11
|
+
const workDir = cwd ?? process.cwd();
|
|
12
|
+
|
|
13
|
+
await git(["add", "-A"], workDir);
|
|
14
|
+
|
|
15
|
+
const status = await git(["status", "--porcelain"], workDir);
|
|
16
|
+
if (status.trim() === "") {
|
|
17
|
+
return { committed: false, hash: null, message: null };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
await git(["commit", "--no-verify", "-m", message], workDir);
|
|
21
|
+
|
|
22
|
+
const hash = (await git(["rev-parse", "--short", "HEAD"], workDir)).trim();
|
|
23
|
+
return { committed: true, hash, message };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function git(args, cwd) {
|
|
27
|
+
return new Promise((resolve, reject) => {
|
|
28
|
+
execFile("git", args, { cwd, encoding: "utf8" }, (error, stdout, stderr) => {
|
|
29
|
+
if (error) {
|
|
30
|
+
reject(new CheckpointError(`git ${args[0]} failed: ${stderr || error.message}`));
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
resolve(stdout);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
}
|
package/scripts/lib/dispatch.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import { basename, join } from "node:path";
|
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
|
|
7
7
|
import { persistCrewArtifact, ArtifactPersistError } from "./artifacts.mjs";
|
|
8
|
+
import { checkpoint } from "./checkpoint.mjs";
|
|
8
9
|
import { renderPrompt } from "./render.mjs";
|
|
9
10
|
|
|
10
11
|
const DEFAULT_COMPANION = fileURLToPath(
|
|
@@ -86,11 +87,20 @@ export async function dispatch(input) {
|
|
|
86
87
|
}
|
|
87
88
|
|
|
88
89
|
const artifactPath = await persistArtifactSafe(input, agentResult);
|
|
89
|
-
|
|
90
|
-
|
|
90
|
+
const result = artifactPath ? { ...agentResult, artifact_path: artifactPath } : agentResult;
|
|
91
|
+
|
|
92
|
+
if (!input.noCheckpoint && agentResult.status === "complete") {
|
|
93
|
+
const taskId = input.request?.taskId ?? input.request?.["task-id"] ?? input.request?.task_id ?? null;
|
|
94
|
+
const label = [input.role, taskId].filter(Boolean).join(" ");
|
|
95
|
+
const ckpt = await checkpointSafe(
|
|
96
|
+
`chore(crew): ${label} checkpoint`
|
|
97
|
+
);
|
|
98
|
+
if (ckpt) {
|
|
99
|
+
return { ...result, checkpoint: ckpt };
|
|
100
|
+
}
|
|
91
101
|
}
|
|
92
102
|
|
|
93
|
-
return
|
|
103
|
+
return result;
|
|
94
104
|
} finally {
|
|
95
105
|
await rm(tmpDir, { recursive: true, force: true });
|
|
96
106
|
}
|
|
@@ -259,6 +269,14 @@ async function persistArtifactSafe(input, agentResult) {
|
|
|
259
269
|
}
|
|
260
270
|
}
|
|
261
271
|
|
|
272
|
+
async function checkpointSafe(message) {
|
|
273
|
+
try {
|
|
274
|
+
return await checkpoint({ message });
|
|
275
|
+
} catch {
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
262
280
|
function isNodeScript(value) {
|
|
263
281
|
const name = basename(String(value));
|
|
264
282
|
return name.endsWith(".mjs") || name.endsWith(".js");
|
package/skills/crew-dev/SKILL.md
CHANGED
|
@@ -30,7 +30,7 @@ crew-dev의 모든 에이전트 실행은 역할이나 phase와 무관하게 아
|
|
|
30
30
|
|
|
31
31
|
1. `{ role, taskId, inputs, instruction, successGate, failureHandling }` 형태의 `request-file`을 작성한다.
|
|
32
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
|
|
33
|
+
3. `action == dispatch`이면 prepare가 반환한 command에 `--no-checkpoint`를 추가하여 실행하고 AgentResult를 처리한다. crew-dev는 US 단위 체크포인트를 직접 관리하므로 dispatch 자동 체크포인트를 사용하지 않는다.
|
|
34
34
|
4. `action == agent`이면 prepare가 반환한 `subagent_type`, `model`, `prompt`로 runner 계약의 Claude 경로를 실행하고 AgentResult로 정규화한다.
|
|
35
35
|
|
|
36
36
|
이 순서를 생략하고 직접 하위 에이전트를 호출하지 않는다.
|
|
@@ -69,6 +69,8 @@ provider 선택, 런타임 선택, AgentResult 반환 형식, 후속 입력 주
|
|
|
69
69
|
qa-report-{n}.md # US 단위 FAIL 시 아카이브
|
|
70
70
|
final-review-report.md # CodeReviewer: 최종 전체 리뷰 결과
|
|
71
71
|
final-qa-report.md # QA: 최종 전체 검증 결과
|
|
72
|
+
requests/ # Dev/CodeReviewer/QA 실행 request-file 보존
|
|
73
|
+
runs/ # Dev/CodeReviewer/QA 실행 결과/메타 보존
|
|
72
74
|
.dev_loop_count # US별 개발 루프 카운터, US PASS 시 리셋
|
|
73
75
|
.dev_crash_count # US별 구현 crash 카운터, US PASS 시 리셋
|
|
74
76
|
.dev_crash_provider # crash 발생 시 현재 사용 중인 provider 기록
|
|
@@ -150,7 +152,7 @@ role instructions:
|
|
|
150
152
|
- **Phase 2b Step 1a — crash 감지 + retry**: Dev 실행이 비정상 종료되거나 자체 검증 결과가 불명확하면 crash로 판정한다. 오케스트레이터는 부분 변경을 마지막 체크포인트로 되돌리고 crash 카운터를 갱신한다. 동일 provider 재시도, provider fallback, 사용자 escalation은 runner 정책과 phase 카운터 규칙을 함께 적용한다.
|
|
151
153
|
- **Phase 2b Step 2 — CodeReviewer + QA 병렬 검증**: CodeReviewer와 QA는 동시에 실행한다. CodeReviewer는 `.crew/` 메타 변경을 제외한 코드 변경분과 인라인 가드레일만 보고 품질을 판정한다. QA는 `plan.md` 산출물에서 현재 US의 테스트 시나리오를 확인하고 빌드, 린트, 타입 체크, 전체 테스트, 테스트 전략 준수, US 시나리오 검증을 직접 실행한다. 두 역할은 파일을 직접 작성하지 않고 결과 텍스트를 반환하며, 오케스트레이터가 각 보고서 산출물로 저장한다.
|
|
152
154
|
- **Phase 2b Step 3 — 판정**: 오케스트레이터는 CodeReviewer PASS와 QA PASS가 모두 충족될 때만 US PASS로 판정한다. 하나라도 FAIL이면 US FAIL로 판정한다.
|
|
153
|
-
- **Phase 2b Step 4 — 체크포인트 commit**: US PASS 즉시 전체 변경을 stage
|
|
155
|
+
- **Phase 2b Step 4 — 체크포인트 commit**: US PASS 즉시 US 루프 카운터와 crash 카운터 산출물이 있으면 삭제한 뒤 `git add -A`로 전체 변경을 stage한다. 특히 `.crew/plans/{task-id}/requests/`, `.crew/plans/{task-id}/runs/`, 보고서, 카운터 파일처럼 새로 생긴 untracked 파이프라인 산출물이 체크포인트에서 누락되면 안 된다. stage 후 `git status --porcelain --untracked-files=all .crew/plans/{task-id}/`를 확인하고 출력이 남아 있으면 누락 산출물 경고와 함께 커밋하지 않고 실패 처리한다. 이상이 없을 때만 `git commit --no-verify -m "feat({task-id}): US-{k} {US 제목}"` 커밋을 만든다.
|
|
154
156
|
- **Phase 2b Step 5 — FAIL 처리**: 오케스트레이터는 루프 카운터를 읽고, 상한 초과 또는 같은 기준 3회 연속 실패를 확인한다. 계속 진행 가능하면 최신 review/qa 보고서를 번호가 붙은 산출물로 아카이브하고 카운터를 증가시킨 뒤 Dev retry로 돌아간다. Dev retry에는 해당 US의 피드백만 전달한다.
|
|
155
157
|
|
|
156
158
|
success gate:
|
|
@@ -214,9 +216,9 @@ output:
|
|
|
214
216
|
|
|
215
217
|
role instructions:
|
|
216
218
|
- **4a. 최종 판정**: CodeReviewer PASS와 QA PASS가 모두 충족될 때만 완료 처리한다. 하나라도 FAIL이면 Phase 3 failure handling을 적용한다.
|
|
217
|
-
- **4b.
|
|
219
|
+
- **4b. 최종 checkpoint**: Phase 3에서 저장한 최종 보고서(`final-review-report.md`, `final-qa-report.md`)와 `contract.md` 상태를 `DONE`으로 갱신한 뒤, push 전에 `node "$CLAUDE_PLUGIN_ROOT/scripts/crew-agent-runner.mjs" checkpoint --message "chore(crew-dev): {task-id} final"` 를 실행하여 모든 산출물을 커밋한다.
|
|
220
|
+
- **4c. PR 생성**: 현재 브랜치를 push하고 PR을 생성한다.
|
|
218
221
|
- PR 본문에는 구현 요약, 완료한 US 목록, 검증 결과, 최종 보고서 위치를 포함한다.
|
|
219
|
-
- PR 생성 후 `contract.md` 상태를 `DONE`으로 갱신한다.
|
|
220
222
|
|
|
221
223
|
success gate:
|
|
222
224
|
- 원격 브랜치가 push되었다.
|
package/skills/crew-do/SKILL.md
CHANGED
|
@@ -152,6 +152,8 @@ active task와 연결된 경우:
|
|
|
152
152
|
active task가 없는 경우:
|
|
153
153
|
- `.crew/runs/{run-id}/result.md`에 결과를 저장한다.
|
|
154
154
|
|
|
155
|
+
dispatch 경로(`action == dispatch`)에서 `complete`로 완료되면 자동으로 checkpoint 커밋을 생성한다. agent 경로(`action == agent`)에서는 자동 checkpoint가 없으므로, 오케스트레이터가 결과 처리 후 `node "$CLAUDE_PLUGIN_ROOT/scripts/crew-agent-runner.mjs" checkpoint --message "chore(crew-do): {run-id} complete"` 를 직접 호출한다. 어느 경로든 오케스트레이터의 후처리(result.md 저장, task log 업데이트) 후에도 커밋되지 않은 변경이 남아 있으면 추가 checkpoint를 실행한다.
|
|
156
|
+
|
|
155
157
|
`blocked_on_user`이면 questions를 사용자에게 전달하고, 답변을 받은 뒤 runner의 followup 절차로 같은 dev 실행에 주입한다.
|
|
156
158
|
|
|
157
159
|
`needs_agent` 또는 `needs_tool`이면 중앙 runner 계약에 따라 오케스트레이터가 처리한다.
|
|
@@ -163,7 +165,7 @@ active task가 없는 경우:
|
|
|
163
165
|
- 오케스트레이터가 코드를 직접 작성하지 않는다.
|
|
164
166
|
- `dev`는 필요한 탐색, 수정, 검증을 직접 수행한다.
|
|
165
167
|
- 요청 범위를 넘는 리팩터링을 하지 않는다.
|
|
166
|
-
- 의존성 추가, 마이그레이션, 대규모 삭제, commit, push, PR
|
|
168
|
+
- `dev` 에이전트는 의존성 추가, 마이그레이션, 대규모 삭제, commit, push, PR 생성을 하지 않는다. 완료 시 checkpoint 커밋은 오케스트레이터의 워크플로우 동작이며 별도 승인이 필요하지 않다.
|
|
167
169
|
- 검증 가능한 명령을 실행하고, 실행하지 못한 검증은 이유를 보고한다.
|
|
168
170
|
- `plan.md` 또는 `contract.md`가 없다는 이유로 direct mode를 실패 처리하지 않는다.
|
|
169
171
|
- 위험하거나 되돌리기 어려운 변경은 `blocked_on_user`로 중단한다.
|
|
@@ -45,7 +45,7 @@ crew-interview의 모든 에이전트 실행은 역할이나 phase와 무관하
|
|
|
45
45
|
|
|
46
46
|
1. `{ role, taskId, inputs, instruction, successGate, failureHandling }` 형태의 `request-file`을 작성한다.
|
|
47
47
|
2. `node "$CLAUDE_PLUGIN_ROOT/scripts/crew-agent-runner.mjs" prepare --role <role> --request-file <request-file> --json`을 실행한다.
|
|
48
|
-
3. `action == dispatch`이면 prepare가 반환한 command
|
|
48
|
+
3. `action == dispatch`이면 prepare가 반환한 command에 `--no-checkpoint`를 추가하여 실행하고 AgentResult를 처리한다. crew-interview는 다단계 워크플로우이므로 dispatch 자동 체크포인트를 사용하지 않고 워크플로우 완료 시 한 번만 checkpoint한다.
|
|
49
49
|
4. `action == agent`이면 prepare가 반환한 `subagent_type`, `model`, `prompt`로 runner 계약의 Claude 경로를 실행하고 AgentResult로 정규화한다.
|
|
50
50
|
|
|
51
51
|
이 순서를 생략하고 직접 하위 에이전트를 호출하지 않는다.
|
|
@@ -448,6 +448,16 @@ spec.md를 작성했습니다. 검토해주세요.
|
|
|
448
448
|
|
|
449
449
|
---
|
|
450
450
|
|
|
451
|
+
## 워크플로우 완료 시 checkpoint
|
|
452
|
+
|
|
453
|
+
오케스트레이터가 COMPLETE, ABORTED, 또는 ESCALATE를 반환하기 직전에, 워크플로우 중 생성된 모든 산출물(brief.md, spec.md, request-file 등)을 checkpoint 커밋한다.
|
|
454
|
+
|
|
455
|
+
```
|
|
456
|
+
node "$CLAUDE_PLUGIN_ROOT/scripts/crew-agent-runner.mjs" checkpoint --message "chore(crew-interview): {task-id} complete"
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
---
|
|
460
|
+
|
|
451
461
|
## 오케스트레이터 반환 스키마
|
|
452
462
|
|
|
453
463
|
**COMPLETE**:
|
|
@@ -50,7 +50,7 @@ crew-plan의 모든 에이전트 실행은 역할이나 step과 무관하게 아
|
|
|
50
50
|
|
|
51
51
|
1. `{ role, taskId, inputs, instruction, successGate, failureHandling }` 형태의 `request-file`을 작성한다.
|
|
52
52
|
2. `node "$CLAUDE_PLUGIN_ROOT/scripts/crew-agent-runner.mjs" prepare --role <role> --request-file <request-file> --json`을 실행한다.
|
|
53
|
-
3. `action == dispatch`이면 prepare가 반환한 command
|
|
53
|
+
3. `action == dispatch`이면 prepare가 반환한 command에 `--no-checkpoint`를 추가하여 실행하고 AgentResult를 처리한다. crew-plan은 다단계 워크플로우이므로 dispatch 자동 체크포인트를 사용하지 않고 워크플로우 완료 시 한 번만 checkpoint한다.
|
|
54
54
|
4. `action == agent`이면 prepare가 반환한 `subagent_type`, `model`, `prompt`로 runner 계약의 Claude 경로를 실행하고 AgentResult로 정규화한다.
|
|
55
55
|
|
|
56
56
|
이 순서를 생략하고 직접 하위 에이전트를 호출하지 않는다.
|
|
@@ -363,6 +363,16 @@ Planner + PlanEvaluator 사이클은 최대 5회 (초기 1회 + retry 최대 4
|
|
|
363
363
|
|
|
364
364
|
---
|
|
365
365
|
|
|
366
|
+
## 워크플로우 완료 시 checkpoint
|
|
367
|
+
|
|
368
|
+
오케스트레이터가 COMPLETE 또는 ESCALATE를 반환하기 직전에, 워크플로우 중 생성된 모든 산출물(analysis.md, plan.md, review.md, contract.md, request-file 등)을 checkpoint 커밋한다.
|
|
369
|
+
|
|
370
|
+
```
|
|
371
|
+
node "$CLAUDE_PLUGIN_ROOT/scripts/crew-agent-runner.mjs" checkpoint --message "chore(crew-plan): {task-id} complete"
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
---
|
|
375
|
+
|
|
366
376
|
## 오케스트레이터 반환 스키마
|
|
367
377
|
|
|
368
378
|
**COMPLETE**:
|