@jjlabsio/claude-crew 0.1.15 → 0.1.17
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/hud/index.mjs +197 -42
- package/package.json +1 -1
- package/scripts/setup-hud.mjs +0 -12
- package/skills/crew-dev/SKILL.md +9 -2
- package/skills/crew-plan/SKILL.md +14 -4
|
@@ -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.17",
|
|
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.17"
|
|
32
32
|
}
|
package/hud/index.mjs
CHANGED
|
@@ -198,10 +198,32 @@ function getRateLimits(stdin) {
|
|
|
198
198
|
const n = typeof v === 'number' ? v : parseFloat(v);
|
|
199
199
|
return isNaN(n) ? null : Math.round(Math.min(Math.max(n, 0), 100));
|
|
200
200
|
};
|
|
201
|
+
const parseResetAt = (v) => {
|
|
202
|
+
if (typeof v !== 'number' || !Number.isFinite(v) || v <= 0) return null;
|
|
203
|
+
return new Date(v * 1000);
|
|
204
|
+
};
|
|
201
205
|
const fiveHour = parse(rl.five_hour?.used_percentage);
|
|
202
206
|
const sevenDay = parse(rl.seven_day?.used_percentage);
|
|
207
|
+
const fiveHourResetAt = parseResetAt(rl.five_hour?.resets_at);
|
|
208
|
+
const sevenDayResetAt = parseResetAt(rl.seven_day?.resets_at);
|
|
203
209
|
if (fiveHour == null && sevenDay == null) return null;
|
|
204
|
-
return { fiveHour, sevenDay };
|
|
210
|
+
return { fiveHour, sevenDay, fiveHourResetAt, sevenDayResetAt };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function formatResetTime(resetAt) {
|
|
214
|
+
if (!resetAt) return '';
|
|
215
|
+
const diffMs = resetAt.getTime() - Date.now();
|
|
216
|
+
if (diffMs <= 0) return '';
|
|
217
|
+
const diffMins = Math.ceil(diffMs / 60000);
|
|
218
|
+
if (diffMins < 60) return `${diffMins}m`;
|
|
219
|
+
const hours = Math.floor(diffMins / 60);
|
|
220
|
+
const mins = diffMins % 60;
|
|
221
|
+
if (hours >= 24) {
|
|
222
|
+
const days = Math.floor(hours / 24);
|
|
223
|
+
const remHours = hours % 24;
|
|
224
|
+
return remHours > 0 ? `${days}d ${remHours}h` : `${days}d`;
|
|
225
|
+
}
|
|
226
|
+
return mins > 0 ? `${hours}h ${mins}m` : `${hours}h`;
|
|
205
227
|
}
|
|
206
228
|
|
|
207
229
|
function colorizeRateLimits(limits) {
|
|
@@ -210,9 +232,14 @@ function colorizeRateLimits(limits) {
|
|
|
210
232
|
const color = pct >= 85 ? red : pct >= 70 ? yellow : green;
|
|
211
233
|
return color(`${pct}%`);
|
|
212
234
|
};
|
|
235
|
+
const formatWindow = (label, pct, resetAt) => {
|
|
236
|
+
const reset = formatResetTime(resetAt);
|
|
237
|
+
const resetStr = reset ? ` ${dim(`(${reset})`)}` : '';
|
|
238
|
+
return `${label}:${colorize(pct)}${resetStr}`;
|
|
239
|
+
};
|
|
213
240
|
const parts = [];
|
|
214
|
-
if (limits.fiveHour != null) parts.push(
|
|
215
|
-
if (limits.sevenDay != null) parts.push(
|
|
241
|
+
if (limits.fiveHour != null) parts.push(formatWindow('5h', limits.fiveHour, limits.fiveHourResetAt));
|
|
242
|
+
if (limits.sevenDay != null) parts.push(formatWindow('weekly', limits.sevenDay, limits.sevenDayResetAt));
|
|
216
243
|
return parts.join(' ');
|
|
217
244
|
}
|
|
218
245
|
|
|
@@ -220,7 +247,7 @@ function colorizeRateLimits(limits) {
|
|
|
220
247
|
// Transcript parsing (agents + skills)
|
|
221
248
|
// ---------------------------------------------------------------------------
|
|
222
249
|
function parseTranscript(transcriptPath) {
|
|
223
|
-
const result = { agents: [], lastSkill: null, sessionStart: null };
|
|
250
|
+
const result = { agents: [], todos: [], lastSkill: null, sessionStart: null };
|
|
224
251
|
if (!transcriptPath || !existsSync(transcriptPath)) return result;
|
|
225
252
|
|
|
226
253
|
const agentModels = loadAgentModels();
|
|
@@ -229,55 +256,120 @@ function parseTranscript(transcriptPath) {
|
|
|
229
256
|
const content = readFileSync(transcriptPath, 'utf-8');
|
|
230
257
|
const lines = content.split('\n').filter(Boolean);
|
|
231
258
|
|
|
232
|
-
// Map of tool_use_id -> agent info
|
|
233
259
|
const agentMap = new Map();
|
|
260
|
+
const latestTodos = [];
|
|
261
|
+
const taskIdToIndex = new Map();
|
|
234
262
|
let lastTimestamp = null;
|
|
235
263
|
|
|
236
264
|
for (const line of lines) {
|
|
237
265
|
let entry;
|
|
238
266
|
try { entry = JSON.parse(line); } catch { continue; }
|
|
239
267
|
|
|
240
|
-
// Track last known timestamp
|
|
241
268
|
if (entry.timestamp) {
|
|
242
269
|
lastTimestamp = entry.timestamp;
|
|
243
270
|
}
|
|
244
271
|
|
|
245
|
-
// Session start time
|
|
246
272
|
if (!result.sessionStart && entry.timestamp) {
|
|
247
273
|
result.sessionStart = new Date(entry.timestamp);
|
|
248
274
|
}
|
|
249
275
|
|
|
250
|
-
//
|
|
276
|
+
// Process tool_use blocks from assistant messages
|
|
251
277
|
if (entry.type === 'tool_use' || entry.type === 'assistant') {
|
|
252
|
-
const
|
|
253
|
-
if (Array.isArray(
|
|
254
|
-
for (const block of
|
|
255
|
-
if (block.type
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
278
|
+
const blocks = entry.message?.content;
|
|
279
|
+
if (Array.isArray(blocks)) {
|
|
280
|
+
for (const block of blocks) {
|
|
281
|
+
if (block.type !== 'tool_use') continue;
|
|
282
|
+
|
|
283
|
+
// Agent start
|
|
284
|
+
if (block.name === 'Agent' || block.name === 'proxy_Agent') {
|
|
285
|
+
const id = block.id;
|
|
286
|
+
if (id) {
|
|
287
|
+
const input = block.input || {};
|
|
288
|
+
const agentType = input.subagent_type || input.type || 'general';
|
|
289
|
+
const rawType = agentType.replace(/^claude-crew:/, '');
|
|
290
|
+
const model = input.model || agentModels[rawType] || null;
|
|
291
|
+
const description = input.description || input.prompt?.slice(0, 50) || '';
|
|
292
|
+
const ts = entry.timestamp || lastTimestamp;
|
|
293
|
+
agentMap.set(id, {
|
|
294
|
+
id,
|
|
295
|
+
type: agentType,
|
|
296
|
+
model,
|
|
297
|
+
description,
|
|
298
|
+
startTime: ts ? new Date(ts) : null,
|
|
299
|
+
status: 'running',
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Skill invocation
|
|
305
|
+
if (block.name === 'Skill' || block.name === 'proxy_Skill') {
|
|
306
|
+
const skillName = block.input?.skill || block.input?.name;
|
|
307
|
+
if (skillName) {
|
|
308
|
+
result.lastSkill = skillName;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// TodoWrite — full replacement of todo list
|
|
313
|
+
if (block.name === 'TodoWrite') {
|
|
314
|
+
const input = block.input || {};
|
|
315
|
+
if (input.todos && Array.isArray(input.todos)) {
|
|
316
|
+
const contentToTaskIds = new Map();
|
|
317
|
+
for (const [taskId, idx] of taskIdToIndex) {
|
|
318
|
+
if (idx < latestTodos.length) {
|
|
319
|
+
const c = latestTodos[idx].content;
|
|
320
|
+
const ids = contentToTaskIds.get(c) ?? [];
|
|
321
|
+
ids.push(taskId);
|
|
322
|
+
contentToTaskIds.set(c, ids);
|
|
323
|
+
}
|
|
274
324
|
}
|
|
325
|
+
|
|
326
|
+
latestTodos.length = 0;
|
|
327
|
+
taskIdToIndex.clear();
|
|
328
|
+
latestTodos.push(...input.todos);
|
|
329
|
+
|
|
330
|
+
for (let i = 0; i < latestTodos.length; i++) {
|
|
331
|
+
const ids = contentToTaskIds.get(latestTodos[i].content);
|
|
332
|
+
if (ids) {
|
|
333
|
+
for (const taskId of ids) {
|
|
334
|
+
taskIdToIndex.set(taskId, i);
|
|
335
|
+
}
|
|
336
|
+
contentToTaskIds.delete(latestTodos[i].content);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// TaskCreate — append a single task
|
|
343
|
+
if (block.name === 'TaskCreate') {
|
|
344
|
+
const input = block.input || {};
|
|
345
|
+
const subject = typeof input.subject === 'string' ? input.subject : '';
|
|
346
|
+
const description = typeof input.description === 'string' ? input.description : '';
|
|
347
|
+
const todoContent = subject || description || 'Untitled task';
|
|
348
|
+
const status = normalizeTaskStatus(input.status) ?? 'pending';
|
|
349
|
+
latestTodos.push({ content: todoContent, status });
|
|
350
|
+
|
|
351
|
+
const taskId = typeof input.taskId === 'string' || typeof input.taskId === 'number'
|
|
352
|
+
? String(input.taskId)
|
|
353
|
+
: block.id;
|
|
354
|
+
if (taskId) {
|
|
355
|
+
taskIdToIndex.set(taskId, latestTodos.length - 1);
|
|
275
356
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// TaskUpdate — update status/content of existing task
|
|
360
|
+
if (block.name === 'TaskUpdate') {
|
|
361
|
+
const input = block.input || {};
|
|
362
|
+
const index = resolveTaskIndex(input.taskId, taskIdToIndex, latestTodos);
|
|
363
|
+
if (index !== null) {
|
|
364
|
+
const status = normalizeTaskStatus(input.status);
|
|
365
|
+
if (status) {
|
|
366
|
+
latestTodos[index] = { ...latestTodos[index], status };
|
|
367
|
+
}
|
|
368
|
+
const subject = typeof input.subject === 'string' ? input.subject : '';
|
|
369
|
+
const description = typeof input.description === 'string' ? input.description : '';
|
|
370
|
+
const newContent = subject || description;
|
|
371
|
+
if (newContent) {
|
|
372
|
+
latestTodos[index] = { ...latestTodos[index], content: newContent };
|
|
281
373
|
}
|
|
282
374
|
}
|
|
283
375
|
}
|
|
@@ -297,9 +389,9 @@ function parseTranscript(transcriptPath) {
|
|
|
297
389
|
}
|
|
298
390
|
}
|
|
299
391
|
if (entry.type === 'user') {
|
|
300
|
-
const
|
|
301
|
-
if (Array.isArray(
|
|
302
|
-
for (const block of
|
|
392
|
+
const blocks = entry.message?.content;
|
|
393
|
+
if (Array.isArray(blocks)) {
|
|
394
|
+
for (const block of blocks) {
|
|
303
395
|
if (block.type === 'tool_result') {
|
|
304
396
|
const toolUseId = block.tool_use_id;
|
|
305
397
|
if (toolUseId && agentMap.has(toolUseId)) {
|
|
@@ -307,6 +399,7 @@ function parseTranscript(transcriptPath) {
|
|
|
307
399
|
agent.status = 'completed';
|
|
308
400
|
const ts = entry.timestamp || lastTimestamp;
|
|
309
401
|
if (ts) agent.endTime = new Date(ts);
|
|
402
|
+
}
|
|
310
403
|
}
|
|
311
404
|
}
|
|
312
405
|
}
|
|
@@ -321,11 +414,46 @@ function parseTranscript(transcriptPath) {
|
|
|
321
414
|
if (a.startTime && (now - a.startTime.getTime()) > STALE_THRESHOLD_MS) return false;
|
|
322
415
|
return true;
|
|
323
416
|
});
|
|
417
|
+
result.todos = [...latestTodos];
|
|
324
418
|
} catch { /* ignore parse errors */ }
|
|
325
419
|
|
|
326
420
|
return result;
|
|
327
421
|
}
|
|
328
422
|
|
|
423
|
+
// ---------------------------------------------------------------------------
|
|
424
|
+
// Todo helpers
|
|
425
|
+
// ---------------------------------------------------------------------------
|
|
426
|
+
function normalizeTaskStatus(status) {
|
|
427
|
+
if (typeof status !== 'string') return null;
|
|
428
|
+
switch (status) {
|
|
429
|
+
case 'pending':
|
|
430
|
+
case 'not_started':
|
|
431
|
+
return 'pending';
|
|
432
|
+
case 'in_progress':
|
|
433
|
+
case 'running':
|
|
434
|
+
return 'in_progress';
|
|
435
|
+
case 'completed':
|
|
436
|
+
case 'complete':
|
|
437
|
+
case 'done':
|
|
438
|
+
return 'completed';
|
|
439
|
+
default:
|
|
440
|
+
return null;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function resolveTaskIndex(taskId, taskIdToIndex, latestTodos) {
|
|
445
|
+
if (typeof taskId === 'string' || typeof taskId === 'number') {
|
|
446
|
+
const key = String(taskId);
|
|
447
|
+
const mapped = taskIdToIndex.get(key);
|
|
448
|
+
if (typeof mapped === 'number') return mapped;
|
|
449
|
+
if (/^\d+$/.test(key)) {
|
|
450
|
+
const numericIndex = parseInt(key, 10) - 1;
|
|
451
|
+
if (numericIndex >= 0 && numericIndex < latestTodos.length) return numericIndex;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
return null;
|
|
455
|
+
}
|
|
456
|
+
|
|
329
457
|
// ---------------------------------------------------------------------------
|
|
330
458
|
// Agent model name (short)
|
|
331
459
|
// ---------------------------------------------------------------------------
|
|
@@ -373,13 +501,13 @@ function renderAgentsMultiLine(agents, maxLines = 5) {
|
|
|
373
501
|
const prefix = isLast ? '\u2514\u2500' : '\u251c\u2500';
|
|
374
502
|
|
|
375
503
|
const rawType = a.type.includes(':') ? a.type.split(':').pop() : a.type;
|
|
376
|
-
const name = rawType
|
|
377
|
-
const model = shortModelName(a.model)
|
|
378
|
-
const duration = formatAgentDuration(a.startTime, a.endTime)
|
|
504
|
+
const name = rawType;
|
|
505
|
+
const model = `[${shortModelName(a.model)}]`;
|
|
506
|
+
const duration = formatAgentDuration(a.startTime, a.endTime);
|
|
379
507
|
const desc = a.description.length > 40 ? a.description.slice(0, 37) + '...' : a.description;
|
|
380
508
|
|
|
381
509
|
detailLines.push(
|
|
382
|
-
`${dim(prefix)} ${cyan(name)} ${model}${dim(
|
|
510
|
+
`${dim(prefix)} ${cyan(name)} ${dim(model)} : ${desc} ${dim(`(${duration})`)}`
|
|
383
511
|
);
|
|
384
512
|
});
|
|
385
513
|
|
|
@@ -391,6 +519,29 @@ function renderAgentsMultiLine(agents, maxLines = 5) {
|
|
|
391
519
|
return { headerPart, detailLines };
|
|
392
520
|
}
|
|
393
521
|
|
|
522
|
+
// ---------------------------------------------------------------------------
|
|
523
|
+
// Todo progress rendering
|
|
524
|
+
// ---------------------------------------------------------------------------
|
|
525
|
+
function renderTodosLine(todos) {
|
|
526
|
+
if (!todos || todos.length === 0) return null;
|
|
527
|
+
|
|
528
|
+
const inProgress = todos.find(t => t.status === 'in_progress');
|
|
529
|
+
const completed = todos.filter(t => t.status === 'completed').length;
|
|
530
|
+
const total = todos.length;
|
|
531
|
+
|
|
532
|
+
if (!inProgress) {
|
|
533
|
+
if (completed === total && total > 0) {
|
|
534
|
+
return `${green('\u2713')} all done ${dim(`(${completed}/${total})`)}`;
|
|
535
|
+
}
|
|
536
|
+
return null;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
const content = inProgress.content.length > 50
|
|
540
|
+
? inProgress.content.slice(0, 47) + '...'
|
|
541
|
+
: inProgress.content;
|
|
542
|
+
return `${yellow('\u25b8')} ${content} ${dim(`(${completed}/${total})`)}`;
|
|
543
|
+
}
|
|
544
|
+
|
|
394
545
|
// ---------------------------------------------------------------------------
|
|
395
546
|
// Session duration
|
|
396
547
|
// ---------------------------------------------------------------------------
|
|
@@ -483,11 +634,15 @@ async function main() {
|
|
|
483
634
|
const rateLimitsStr = colorizeRateLimits(rateLimits);
|
|
484
635
|
if (rateLimitsStr) midElements.push(rateLimitsStr);
|
|
485
636
|
|
|
637
|
+
// --- Todos line ---
|
|
638
|
+
const todosLine = renderTodosLine(transcript.todos);
|
|
639
|
+
|
|
486
640
|
// --- Output ---
|
|
487
641
|
const outputLines = [];
|
|
488
642
|
outputLines.push(topElements.join(SEPARATOR));
|
|
489
643
|
outputLines.push(midElements.join(SEPARATOR));
|
|
490
644
|
outputLines.push(...detailLines);
|
|
645
|
+
if (todosLine) outputLines.push(todosLine);
|
|
491
646
|
|
|
492
647
|
console.log(outputLines.filter(Boolean).join('\n'));
|
|
493
648
|
}
|
package/package.json
CHANGED
package/scripts/setup-hud.mjs
CHANGED
|
@@ -69,18 +69,6 @@ async function main() {
|
|
|
69
69
|
writeFileSync(localSettingsPath, JSON.stringify(localSettings, null, 2));
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
// --- Remove legacy global statusLine from ~/.claude/settings.json ---
|
|
73
|
-
const globalSettingsPath = join(process.env.CLAUDE_CONFIG_DIR || join(homedir(), '.claude'), 'settings.json');
|
|
74
|
-
if (existsSync(globalSettingsPath)) {
|
|
75
|
-
try {
|
|
76
|
-
const globalSettings = JSON.parse(readFileSync(globalSettingsPath, 'utf-8'));
|
|
77
|
-
if (globalSettings.statusLine) {
|
|
78
|
-
delete globalSettings.statusLine;
|
|
79
|
-
writeFileSync(globalSettingsPath, JSON.stringify(globalSettings, null, 2));
|
|
80
|
-
}
|
|
81
|
-
} catch { /* ignore */ }
|
|
82
|
-
}
|
|
83
|
-
|
|
84
72
|
console.log(JSON.stringify({ continue: true }));
|
|
85
73
|
} catch (e) {
|
|
86
74
|
console.log(JSON.stringify({
|
package/skills/crew-dev/SKILL.md
CHANGED
|
@@ -229,7 +229,7 @@ contract.md, plan.md, brief.md, spec.md, dev-log.md는 읽지 않는다.
|
|
|
229
229
|
6. 에러 처리 적절성
|
|
230
230
|
|
|
231
231
|
## 출력
|
|
232
|
-
|
|
232
|
+
아래 형식으로 리뷰 결과를 텍스트로 반환하라. 파일을 직접 작성하지 않는다.
|
|
233
233
|
|
|
234
234
|
## 판정 규칙
|
|
235
235
|
- 가드레일 위반 → critical
|
|
@@ -269,7 +269,7 @@ contract.md, brief.md, spec.md는 읽지 않는다.
|
|
|
269
269
|
6. E2E / 통합 검증 — plan.md의 테스트 시나리오 기반
|
|
270
270
|
|
|
271
271
|
## 출력
|
|
272
|
-
|
|
272
|
+
아래 형식으로 검증 결과를 텍스트로 반환하라. 파일을 직접 작성하지 않는다.
|
|
273
273
|
|
|
274
274
|
## 판정 규칙
|
|
275
275
|
- 항목 1-6 중 하나라도 FAIL → 전체 FAIL
|
|
@@ -281,6 +281,13 @@ contract.md, brief.md, spec.md는 읽지 않는다.
|
|
|
281
281
|
- 코드를 수정하지 않는다. 검증만 한다.
|
|
282
282
|
```
|
|
283
283
|
|
|
284
|
+
**Phase 3 결과 저장 (오케스트레이터 직접)**:
|
|
285
|
+
|
|
286
|
+
CodeReviewer와 QA 에이전트는 read-only이므로 파일을 직접 작성하지 않는다.
|
|
287
|
+
오케스트레이터가 각 에이전트의 반환 텍스트를 파일로 저장한다:
|
|
288
|
+
- CodeReviewer 결과 → `.crew/plans/{task-id}/review-report.md`
|
|
289
|
+
- QA 결과 → `.crew/plans/{task-id}/qa-report.md`
|
|
290
|
+
|
|
284
291
|
**Phase 3 병렬 실행 방법**:
|
|
285
292
|
|
|
286
293
|
오케스트레이터는 한 번의 메시지에서 두 개의 Agent tool 호출을 동시에 수행한다:
|
|
@@ -132,15 +132,20 @@ Agent(subagent_type="techlead", description="TechLead: {task-id} 사전 분석",
|
|
|
132
132
|
Agent(subagent_type="researcher", description="외부 리서치: {리서치 대상}", prompt="...")
|
|
133
133
|
|
|
134
134
|
## 출력
|
|
135
|
-
|
|
135
|
+
아래 필수 섹션을 포함한 분석 결과를 텍스트로 반환하라. 파일을 직접 작성하지 않는다.
|
|
136
136
|
|
|
137
|
-
|
|
137
|
+
필수 섹션: 요구사항 보완, 코드베이스 맥락(관련 파일/기존 패턴/테스트 구조), 아키텍처 방향(권장+대안), 엣지 케이스/리스크, 가드레일(Must/Must NOT), 테스트 인프라(프레임워크/패턴/유무), 외부 리서치(해당 시).
|
|
138
138
|
|
|
139
139
|
## 규칙
|
|
140
140
|
- 요구사항에 빈틈이 있으면 AskUserQuestion으로 유저에게 직접 질문하라.
|
|
141
141
|
- 탐색(양)은 서브에이전트에게, 판단(질)은 직접.
|
|
142
142
|
```
|
|
143
143
|
|
|
144
|
+
**Step 3 결과 저장 (오케스트레이터 직접)**:
|
|
145
|
+
|
|
146
|
+
TechLead 에이전트는 read-only이므로 파일을 직접 작성하지 않는다.
|
|
147
|
+
오케스트레이터가 TechLead의 반환 텍스트를 `.crew/plans/{task-id}/analysis.md`로 저장한다.
|
|
148
|
+
|
|
144
149
|
**실패 조건**: analysis.md가 없거나 가드레일 섹션이 비어 있으면 즉시 에스컬레이션.
|
|
145
150
|
|
|
146
151
|
---
|
|
@@ -314,10 +319,15 @@ Explorer 서브에이전트를 호출하여 plan.md에서 참조하는 파일/
|
|
|
314
319
|
Agent(subagent_type="explorer", description="코드 참조 확인: {파일 목록 요약}", prompt="plan.md에서 참조하는 다음 파일/모듈이 존재하는지 확인하라: {파일 목록}")
|
|
315
320
|
|
|
316
321
|
## 출력
|
|
317
|
-
|
|
318
|
-
|
|
322
|
+
아래 형식으로 검증 결과를 텍스트로 반환하라. 파일을 직접 작성하지 않는다.
|
|
323
|
+
형식: 판정(PASS/FAIL), 항목별 결과(E1-E5 YES/NO + 근거), FAIL 상세(NO 항목의 문제+수정 방향), 근본 원인 분류(FAIL 시).
|
|
319
324
|
```
|
|
320
325
|
|
|
326
|
+
**Step 5 결과 저장 (오케스트레이터 직접)**:
|
|
327
|
+
|
|
328
|
+
PlanEvaluator 에이전트는 read-only이므로 파일을 직접 작성하지 않는다.
|
|
329
|
+
오케스트레이터가 PlanEvaluator의 반환 텍스트를 `.crew/plans/{task-id}/review.md`로 저장한다.
|
|
330
|
+
|
|
321
331
|
---
|
|
322
332
|
|
|
323
333
|
### Step 6 — PASS 처리
|