@jjlabsio/claude-crew 0.1.11 → 0.1.13
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 +111 -0
- package/hooks/hooks.json +12 -0
- package/hud/index.mjs +27 -2
- package/package.json +1 -1
- package/skills/crew-dev/SKILL.md +18 -6
- package/skills/crew-plan/SKILL.md +24 -8
|
@@ -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.13",
|
|
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.13"
|
|
32
32
|
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Delegation Enforcer — PreToolUse hook
|
|
4
|
+
*
|
|
5
|
+
* When an Agent/Task call includes subagent_type, auto-injects the model
|
|
6
|
+
* from the matching agent definition (agents/*.md frontmatter).
|
|
7
|
+
* If subagent_type is missing on an Agent/Task call, blocks with a warning.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { readFileSync, readdirSync } from 'node:fs';
|
|
11
|
+
import { join, dirname } from 'node:path';
|
|
12
|
+
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Read stdin
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
async function readStdin(timeoutMs = 3000) {
|
|
17
|
+
if (process.stdin.isTTY) return null;
|
|
18
|
+
return new Promise((resolve) => {
|
|
19
|
+
let data = '';
|
|
20
|
+
const timer = setTimeout(() => resolve(data || null), timeoutMs);
|
|
21
|
+
process.stdin.setEncoding('utf-8');
|
|
22
|
+
process.stdin.on('data', (chunk) => { data += chunk; });
|
|
23
|
+
process.stdin.on('end', () => { clearTimeout(timer); resolve(data || null); });
|
|
24
|
+
process.stdin.on('error', () => { clearTimeout(timer); resolve(null); });
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// Load agent definitions from agents/*.md frontmatter
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
function loadAgentDefinitions(pluginRoot) {
|
|
32
|
+
const agentsDir = join(pluginRoot, 'agents');
|
|
33
|
+
const defs = {};
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
const files = readdirSync(agentsDir).filter(f => f.endsWith('.md'));
|
|
37
|
+
for (const file of files) {
|
|
38
|
+
const content = readFileSync(join(agentsDir, file), 'utf-8');
|
|
39
|
+
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
40
|
+
if (!fmMatch) continue;
|
|
41
|
+
|
|
42
|
+
const fm = fmMatch[1];
|
|
43
|
+
const name = fm.match(/^name:\s*(.+)$/m)?.[1]?.trim();
|
|
44
|
+
const model = fm.match(/^model:\s*(.+)$/m)?.[1]?.trim();
|
|
45
|
+
|
|
46
|
+
if (name && model) {
|
|
47
|
+
defs[name] = model;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
} catch { /* ignore */ }
|
|
51
|
+
|
|
52
|
+
return defs;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
// Main
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
async function main() {
|
|
59
|
+
const raw = await readStdin();
|
|
60
|
+
if (!raw) {
|
|
61
|
+
console.log(JSON.stringify({ continue: true }));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let event;
|
|
66
|
+
try { event = JSON.parse(raw); } catch {
|
|
67
|
+
console.log(JSON.stringify({ continue: true }));
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const toolName = (event.tool_name || '').toLowerCase();
|
|
72
|
+
|
|
73
|
+
// Only intercept Agent/Task calls
|
|
74
|
+
if (toolName !== 'agent' && toolName !== 'task') {
|
|
75
|
+
console.log(JSON.stringify({ continue: true }));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const input = event.tool_input || {};
|
|
80
|
+
|
|
81
|
+
// If subagent_type is missing, pass through without modification
|
|
82
|
+
if (!input.subagent_type) {
|
|
83
|
+
console.log(JSON.stringify({ continue: true }));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Canonicalize subagent_type (strip plugin prefix if present)
|
|
88
|
+
const rawType = input.subagent_type.replace(/^claude-crew:/, '');
|
|
89
|
+
|
|
90
|
+
// Load agent definitions and auto-inject model if missing
|
|
91
|
+
const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT || dirname(import.meta.url.replace('file://', '')).replace('/hooks', '');
|
|
92
|
+
const agentDefs = loadAgentDefinitions(pluginRoot);
|
|
93
|
+
|
|
94
|
+
if (!input.model && agentDefs[rawType]) {
|
|
95
|
+
// Auto-inject model from agent definition
|
|
96
|
+
const injectedModel = agentDefs[rawType];
|
|
97
|
+
console.log(JSON.stringify({
|
|
98
|
+
continue: true,
|
|
99
|
+
modifiedInput: { ...input, model: injectedModel },
|
|
100
|
+
hookSpecificOutput: {
|
|
101
|
+
hookEventName: 'PreToolUse',
|
|
102
|
+
additionalContext: `model 자동 주입: ${rawType} → ${injectedModel}`,
|
|
103
|
+
},
|
|
104
|
+
}));
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
console.log(JSON.stringify({ continue: true }));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
main();
|
package/hooks/hooks.json
CHANGED
package/hud/index.mjs
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { execSync } from 'node:child_process';
|
|
13
|
-
import { readFileSync, existsSync } from 'node:fs';
|
|
13
|
+
import { readFileSync, existsSync, readdirSync } from 'node:fs';
|
|
14
14
|
import { join, dirname, basename } from 'node:path';
|
|
15
15
|
import { fileURLToPath } from 'node:url';
|
|
16
16
|
|
|
@@ -57,6 +57,28 @@ function getVersion() {
|
|
|
57
57
|
return '0.0.0';
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
// Agent definitions (subagent_type → model)
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
function loadAgentModels() {
|
|
64
|
+
try {
|
|
65
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
66
|
+
const agentsDir = join(__dirname, '..', 'agents');
|
|
67
|
+
const models = {};
|
|
68
|
+
const files = readdirSync(agentsDir).filter(f => f.endsWith('.md'));
|
|
69
|
+
for (const file of files) {
|
|
70
|
+
const content = readFileSync(join(agentsDir, file), 'utf-8');
|
|
71
|
+
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
72
|
+
if (!fmMatch) continue;
|
|
73
|
+
const fm = fmMatch[1];
|
|
74
|
+
const name = fm.match(/^name:\s*(.+)$/m)?.[1]?.trim();
|
|
75
|
+
const model = fm.match(/^model:\s*(.+)$/m)?.[1]?.trim();
|
|
76
|
+
if (name && model) models[name] = model;
|
|
77
|
+
}
|
|
78
|
+
return models;
|
|
79
|
+
} catch { return {}; }
|
|
80
|
+
}
|
|
81
|
+
|
|
60
82
|
// ---------------------------------------------------------------------------
|
|
61
83
|
// Git helpers
|
|
62
84
|
// ---------------------------------------------------------------------------
|
|
@@ -144,6 +166,8 @@ function parseTranscript(transcriptPath) {
|
|
|
144
166
|
const result = { agents: [], lastSkill: null, sessionStart: null };
|
|
145
167
|
if (!transcriptPath || !existsSync(transcriptPath)) return result;
|
|
146
168
|
|
|
169
|
+
const agentModels = loadAgentModels();
|
|
170
|
+
|
|
147
171
|
try {
|
|
148
172
|
const content = readFileSync(transcriptPath, 'utf-8');
|
|
149
173
|
const lines = content.split('\n').filter(Boolean);
|
|
@@ -178,7 +202,8 @@ function parseTranscript(transcriptPath) {
|
|
|
178
202
|
if (id) {
|
|
179
203
|
const input = block.input || {};
|
|
180
204
|
const agentType = input.subagent_type || input.type || 'general';
|
|
181
|
-
const
|
|
205
|
+
const rawType = agentType.replace(/^claude-crew:/, '');
|
|
206
|
+
const model = input.model || agentModels[rawType] || null;
|
|
182
207
|
const description = input.description || input.prompt?.slice(0, 50) || '';
|
|
183
208
|
const ts = entry.timestamp || lastTimestamp;
|
|
184
209
|
agentMap.set(id, {
|
package/package.json
CHANGED
package/skills/crew-dev/SKILL.md
CHANGED
|
@@ -56,7 +56,7 @@ description: contract.md를 입력으로 받아 Dev + CodeReviewer + QA 파이
|
|
|
56
56
|
| **CodeReviewer** | code-reviewer | git diff(직접 실행), 가드레일(인라인) | contract.md, plan.md, brief.md, spec.md, dev-log.md | 수용 기준 체리피킹 방지 (.crew/는 .gitignore 대상이므로 diff에 노출되지 않음) |
|
|
57
57
|
| **QA** | qa | plan.md | contract.md, brief.md, spec.md | 검증 편향 방지 |
|
|
58
58
|
|
|
59
|
-
**중요**: 모든 에이전트 호출 시 반드시 `subagent_type` 파라미터를 지정해야 한다.
|
|
59
|
+
**중요**: 모든 에이전트 호출 시 반드시 `subagent_type` 파라미터를 지정해야 한다. `subagent_type`이 없으면 PreToolUse hook이 호출을 차단한다. `model` 파라미터는 생략 가능 — hook이 에이전트 정의에서 자동 주입한다.
|
|
60
60
|
|
|
61
61
|
---
|
|
62
62
|
|
|
@@ -107,7 +107,11 @@ IN_PROGRESS — Dev 에이전트가 구현 중이다.
|
|
|
107
107
|
|
|
108
108
|
**모델**: opus
|
|
109
109
|
|
|
110
|
-
|
|
110
|
+
호출:
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
Agent(subagent_type="dev", description="Dev: {task-id} 구현", prompt="...")
|
|
114
|
+
```
|
|
111
115
|
|
|
112
116
|
**첫 번째 실행 시 에이전트 프롬프트**:
|
|
113
117
|
|
|
@@ -188,7 +192,11 @@ CodeReviewer와 QA를 **동시에** Agent tool 2개로 호출한다.
|
|
|
188
192
|
|
|
189
193
|
**모델**: opus
|
|
190
194
|
|
|
191
|
-
|
|
195
|
+
호출:
|
|
196
|
+
|
|
197
|
+
```
|
|
198
|
+
Agent(subagent_type="code-reviewer", description="CodeReviewer: {task-id} 코드 리뷰", prompt="...")
|
|
199
|
+
```
|
|
192
200
|
|
|
193
201
|
오케스트레이터가 해야 할 사전 작업:
|
|
194
202
|
1. contract.md에서 가드레일 섹션(Must/Must NOT)만 추출한다.
|
|
@@ -233,7 +241,11 @@ contract.md, plan.md, brief.md, spec.md, dev-log.md는 읽지 않는다.
|
|
|
233
241
|
|
|
234
242
|
**모델**: sonnet
|
|
235
243
|
|
|
236
|
-
|
|
244
|
+
호출:
|
|
245
|
+
|
|
246
|
+
```
|
|
247
|
+
Agent(subagent_type="qa", description="QA: {task-id} 검증", prompt="...")
|
|
248
|
+
```
|
|
237
249
|
|
|
238
250
|
에이전트 프롬프트:
|
|
239
251
|
|
|
@@ -274,8 +286,8 @@ contract.md, brief.md, spec.md는 읽지 않는다.
|
|
|
274
286
|
오케스트레이터는 한 번의 메시지에서 두 개의 Agent tool 호출을 동시에 수행한다:
|
|
275
287
|
|
|
276
288
|
```
|
|
277
|
-
Agent(
|
|
278
|
-
Agent(
|
|
289
|
+
Agent(subagent_type="code-reviewer", description="CodeReviewer: {task-id} 코드 리뷰", prompt="...")
|
|
290
|
+
Agent(subagent_type="qa", description="QA: {task-id} 검증", prompt="...")
|
|
279
291
|
```
|
|
280
292
|
|
|
281
293
|
---
|
|
@@ -63,7 +63,11 @@ brief.md가 없거나 비어 있습니다. 계획 파이프라인을 시작할
|
|
|
63
63
|
**모델**: opus
|
|
64
64
|
**건너뛰기 조건**: 엔지니어링 유형이면 이 단계를 건너뛴다.
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
호출:
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
Agent(subagent_type="pm", description="PM: {task-id} 요구사항 정의", prompt="...")
|
|
70
|
+
```
|
|
67
71
|
|
|
68
72
|
에이전트 프롬프트:
|
|
69
73
|
|
|
@@ -101,7 +105,11 @@ PM 에이전트가 요구사항 정의에 실패했습니다.
|
|
|
101
105
|
**모델**: opus
|
|
102
106
|
**실행 조건**: 항상 (양쪽 유형 모두)
|
|
103
107
|
|
|
104
|
-
|
|
108
|
+
호출:
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
Agent(subagent_type="techlead", description="TechLead: {task-id} 사전 분석", prompt="...")
|
|
112
|
+
```
|
|
105
113
|
|
|
106
114
|
에이전트 프롬프트:
|
|
107
115
|
|
|
@@ -114,14 +122,14 @@ PM 에이전트가 요구사항 정의에 실패했습니다.
|
|
|
114
122
|
|
|
115
123
|
## 서브에이전트 호출
|
|
116
124
|
- Explorer (Haiku): 코드베이스 탐색. 항상 호출. 병렬 2-3개로 호출하라.
|
|
117
|
-
Agent(description="코드베이스 탐색
|
|
125
|
+
Agent(subagent_type="explorer", description="코드베이스 탐색: {탐색 대상}", prompt="...")
|
|
118
126
|
**필수 탐색 항목**: 테스트 인프라도 반드시 탐색한다. Explorer 중 1개는 다음을 확인:
|
|
119
127
|
- 테스트 프레임워크 설정 파일 (jest.config.*, vitest.config.*, pytest.ini 등)
|
|
120
128
|
- 대표적인 테스트 파일 2-3개의 패턴
|
|
121
129
|
- 커버리지 설정 여부
|
|
122
130
|
- 테스트 실행 스크립트 (package.json scripts 등)
|
|
123
131
|
- Researcher (Sonnet): 외부 리서치. 필요시만 호출.
|
|
124
|
-
Agent(description="외부 리서치
|
|
132
|
+
Agent(subagent_type="researcher", description="외부 리서치: {리서치 대상}", prompt="...")
|
|
125
133
|
|
|
126
134
|
## 출력
|
|
127
135
|
.crew/plans/{task-id}/analysis.md 를 작성하라.
|
|
@@ -183,7 +191,11 @@ TechLead의 analysis.md에서 테스트 인프라 섹션을 확인한 후, 오
|
|
|
183
191
|
|
|
184
192
|
**모델**: opus
|
|
185
193
|
|
|
186
|
-
|
|
194
|
+
호출:
|
|
195
|
+
|
|
196
|
+
```
|
|
197
|
+
Agent(subagent_type="planner", description="Planner: {task-id} 구현 계획", prompt="...")
|
|
198
|
+
```
|
|
187
199
|
|
|
188
200
|
**첫 번째 실행 시 에이전트 프롬프트**:
|
|
189
201
|
|
|
@@ -260,7 +272,11 @@ plan.md 최상단에 "이전 피드백 반영" 섹션을 추가한다.
|
|
|
260
272
|
|
|
261
273
|
**모델**: sonnet (하드 임계값 판정에서 Opus 합리화 방지)
|
|
262
274
|
|
|
263
|
-
|
|
275
|
+
호출:
|
|
276
|
+
|
|
277
|
+
```
|
|
278
|
+
Agent(subagent_type="plan-evaluator", description="PlanEvaluator: {task-id} 계획 검증", prompt="...")
|
|
279
|
+
```
|
|
264
280
|
|
|
265
281
|
에이전트 프롬프트:
|
|
266
282
|
|
|
@@ -295,7 +311,7 @@ plan.md 최상단에 "이전 피드백 반영" 섹션을 추가한다.
|
|
|
295
311
|
|
|
296
312
|
## E3 코드 참조 확인
|
|
297
313
|
Explorer 서브에이전트를 호출하여 plan.md에서 참조하는 파일/모듈이 존재하는지 확인하라.
|
|
298
|
-
Agent(description="코드 참조
|
|
314
|
+
Agent(subagent_type="explorer", description="코드 참조 확인: {파일 목록 요약}", prompt="plan.md에서 참조하는 다음 파일/모듈이 존재하는지 확인하라: {파일 목록}")
|
|
299
315
|
|
|
300
316
|
## 출력
|
|
301
317
|
.crew/plans/{task-id}/review.md 를 작성하라.
|
|
@@ -458,7 +474,7 @@ Planner + PlanEvaluator 사이클은 최대 5회 (초기 1회 + retry 최대 4
|
|
|
458
474
|
| Planner (retry) | planner | opus | spec.md (유저 가치 시) + analysis.md + review-{n}.md | brief.md |
|
|
459
475
|
| PlanEvaluator | plan-evaluator | sonnet | spec.md/brief.md + analysis.md + plan.md | brief.md (유저 가치 시) |
|
|
460
476
|
|
|
461
|
-
**중요**: 모든 에이전트 호출 시 반드시 `subagent_type` 파라미터를 지정해야 한다.
|
|
477
|
+
**중요**: 모든 에이전트 호출 시 반드시 `subagent_type` 파라미터를 지정해야 한다. `subagent_type`이 없으면 PreToolUse hook이 호출을 차단한다. `model` 파라미터는 생략 가능 — hook이 에이전트 정의에서 자동 주입한다.
|
|
462
478
|
|
|
463
479
|
---
|
|
464
480
|
|