@walwal-harness/cli 4.0.0-beta.1 → 4.0.0-beta.3
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/bin/init.js +95 -11
- package/package.json +1 -1
- package/skills/team-action/SKILL.md +12 -50
package/bin/init.js
CHANGED
|
@@ -129,17 +129,56 @@ function scaffoldHarness() {
|
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
-
//
|
|
132
|
+
// config.json — ALWAYS update (harness system file, not user data)
|
|
133
|
+
// But preserve user's custom settings (pre_eval_gate.frontend_cwd, behavior, etc.)
|
|
133
134
|
const configSrc = path.join(PKG_ROOT, 'assets', 'templates', 'config.json');
|
|
134
135
|
const configDest = path.join(HARNESS_DIR, 'config.json');
|
|
135
|
-
if (fs.existsSync(configSrc)
|
|
136
|
-
|
|
136
|
+
if (fs.existsSync(configSrc)) {
|
|
137
|
+
if (fileExists(configDest) && !isForce) {
|
|
138
|
+
// Merge: keep user's customizations, update harness structure
|
|
139
|
+
try {
|
|
140
|
+
const existing = JSON.parse(fs.readFileSync(configDest, 'utf8'));
|
|
141
|
+
const template = JSON.parse(fs.readFileSync(configSrc, 'utf8'));
|
|
142
|
+
// Preserve user customizations
|
|
143
|
+
const userPreserve = {
|
|
144
|
+
behavior: existing.behavior,
|
|
145
|
+
'flow.pre_eval_gate.frontend_cwd': existing?.flow?.pre_eval_gate?.frontend_cwd,
|
|
146
|
+
'flow.pre_eval_gate.backend_cwd': existing?.flow?.pre_eval_gate?.backend_cwd,
|
|
147
|
+
'flow.pre_eval_gate.frontend_checks': existing?.flow?.pre_eval_gate?.frontend_checks,
|
|
148
|
+
'flow.pre_eval_gate.backend_checks': existing?.flow?.pre_eval_gate?.backend_checks,
|
|
149
|
+
};
|
|
150
|
+
// Write template, then re-apply user settings
|
|
151
|
+
fs.writeFileSync(configDest, JSON.stringify(template, null, 2) + '\n');
|
|
152
|
+
// Re-apply preserved user settings
|
|
153
|
+
const merged = JSON.parse(fs.readFileSync(configDest, 'utf8'));
|
|
154
|
+
if (userPreserve.behavior) merged.behavior = userPreserve.behavior;
|
|
155
|
+
if (userPreserve['flow.pre_eval_gate.frontend_cwd']) {
|
|
156
|
+
merged.flow.pre_eval_gate.frontend_cwd = userPreserve['flow.pre_eval_gate.frontend_cwd'];
|
|
157
|
+
}
|
|
158
|
+
if (userPreserve['flow.pre_eval_gate.backend_cwd']) {
|
|
159
|
+
merged.flow.pre_eval_gate.backend_cwd = userPreserve['flow.pre_eval_gate.backend_cwd'];
|
|
160
|
+
}
|
|
161
|
+
if (userPreserve['flow.pre_eval_gate.frontend_checks']) {
|
|
162
|
+
merged.flow.pre_eval_gate.frontend_checks = userPreserve['flow.pre_eval_gate.frontend_checks'];
|
|
163
|
+
}
|
|
164
|
+
if (userPreserve['flow.pre_eval_gate.backend_checks']) {
|
|
165
|
+
merged.flow.pre_eval_gate.backend_checks = userPreserve['flow.pre_eval_gate.backend_checks'];
|
|
166
|
+
}
|
|
167
|
+
fs.writeFileSync(configDest, JSON.stringify(merged, null, 2) + '\n');
|
|
168
|
+
log('config.json updated (user settings preserved)');
|
|
169
|
+
} catch (e) {
|
|
170
|
+
copyFile(configSrc, configDest);
|
|
171
|
+
log('config.json replaced (merge failed)');
|
|
172
|
+
}
|
|
173
|
+
} else {
|
|
174
|
+
copyFile(configSrc, configDest);
|
|
175
|
+
}
|
|
137
176
|
}
|
|
138
177
|
|
|
139
|
-
//
|
|
178
|
+
// HARNESS.md — ALWAYS update
|
|
140
179
|
const harnessMdSrc = path.join(PKG_ROOT, 'assets', 'templates', 'HARNESS.md');
|
|
141
180
|
const harnessMdDest = path.join(HARNESS_DIR, 'HARNESS.md');
|
|
142
|
-
if (fs.existsSync(harnessMdSrc)
|
|
181
|
+
if (fs.existsSync(harnessMdSrc)) {
|
|
143
182
|
copyFile(harnessMdSrc, harnessMdDest);
|
|
144
183
|
}
|
|
145
184
|
|
|
@@ -190,16 +229,23 @@ function installSkills() {
|
|
|
190
229
|
.filter(d => d.isDirectory())
|
|
191
230
|
.map(d => d.name);
|
|
192
231
|
|
|
232
|
+
// Remove obsolete skills (cleaned up in v4)
|
|
233
|
+
const obsoleteSkills = ['harness-generator-frontend-flutter', 'harness-evaluator-functional-flutter', 'harness-team'];
|
|
234
|
+
for (const obs of obsoleteSkills) {
|
|
235
|
+
const obsPath = path.join(CLAUDE_SKILLS_DIR, obs);
|
|
236
|
+
if (fs.existsSync(obsPath)) {
|
|
237
|
+
fs.rmSync(obsPath, { recursive: true, force: true });
|
|
238
|
+
log(` Removed obsolete: ${obs}`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
193
242
|
for (const skill of skills) {
|
|
194
243
|
const src = path.join(skillsSrc, skill);
|
|
195
244
|
const dest = path.join(CLAUDE_SKILLS_DIR, `harness-${skill}`);
|
|
196
245
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
} else {
|
|
201
|
-
log(` Skipped (exists): harness-${skill}`);
|
|
202
|
-
}
|
|
246
|
+
// Skills are ALWAYS overwritten — they are harness-managed, not user-editable
|
|
247
|
+
copyDir(src, dest);
|
|
248
|
+
log(` Installed: harness-${skill}`);
|
|
203
249
|
}
|
|
204
250
|
|
|
205
251
|
log('Skills installation complete');
|
|
@@ -214,6 +260,21 @@ function installScripts() {
|
|
|
214
260
|
const scriptsSrc = path.join(PKG_ROOT, 'scripts');
|
|
215
261
|
const scriptsDest = path.join(PROJECT_ROOT, 'scripts');
|
|
216
262
|
|
|
263
|
+
// Remove obsolete scripts from previous versions
|
|
264
|
+
const obsoleteScripts = [
|
|
265
|
+
'harness-studio-v4.sh',
|
|
266
|
+
'harness-control-v4.sh',
|
|
267
|
+
'harness-prompts-v4.sh',
|
|
268
|
+
'harness-team-worker.sh',
|
|
269
|
+
];
|
|
270
|
+
for (const obs of obsoleteScripts) {
|
|
271
|
+
const obsPath = path.join(scriptsDest, obs);
|
|
272
|
+
if (fs.existsSync(obsPath)) {
|
|
273
|
+
fs.unlinkSync(obsPath);
|
|
274
|
+
log(` Removed obsolete: ${obs}`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
217
278
|
// Core scripts are ALWAYS overwritten on update (not user-editable)
|
|
218
279
|
// These contain harness logic that must stay in sync with the CLI version
|
|
219
280
|
const coreScripts = new Set([
|
|
@@ -421,6 +482,28 @@ function installUserPromptSubmitHook() {
|
|
|
421
482
|
// ─────────────────────────────────────────
|
|
422
483
|
// 4. AGENTS.md + CLAUDE.md
|
|
423
484
|
// ─────────────────────────────────────────
|
|
485
|
+
// ─────────────────────────────────────────
|
|
486
|
+
// 3d. Agent Teams env var
|
|
487
|
+
// ─────────────────────────────────────────
|
|
488
|
+
function installAgentTeamsEnv() {
|
|
489
|
+
const settingsPath = path.join(PROJECT_ROOT, '.claude', 'settings.json');
|
|
490
|
+
let settings = {};
|
|
491
|
+
if (fileExists(settingsPath)) {
|
|
492
|
+
try { settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')); } catch (e) {}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if (!settings.env) settings.env = {};
|
|
496
|
+
|
|
497
|
+
if (settings.env['CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS'] !== '1') {
|
|
498
|
+
settings.env['CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS'] = '1';
|
|
499
|
+
ensureDir(path.dirname(settingsPath));
|
|
500
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
501
|
+
log('Agent Teams enabled (CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1)');
|
|
502
|
+
} else {
|
|
503
|
+
log('Agent Teams already enabled');
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
424
507
|
function setupAgentsMd() {
|
|
425
508
|
const agentsMd = path.join(PROJECT_ROOT, 'AGENTS.md');
|
|
426
509
|
const claudeMd = path.join(PROJECT_ROOT, 'CLAUDE.md');
|
|
@@ -703,6 +786,7 @@ function main() {
|
|
|
703
786
|
installSessionHook();
|
|
704
787
|
installStatusline();
|
|
705
788
|
installUserPromptSubmitHook();
|
|
789
|
+
installAgentTeamsEnv();
|
|
706
790
|
setupAgentsMd();
|
|
707
791
|
checkPlaywrightMcp();
|
|
708
792
|
checkRecommendedSkills();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@walwal-harness/cli",
|
|
3
|
-
"version": "4.0.0-beta.
|
|
3
|
+
"version": "4.0.0-beta.3",
|
|
4
4
|
"description": "Production harness for AI agent engineering — Planner, Generator(BE/FE), Evaluator(Func/Visual), optional Brainstormer (requirements refinement). Supports React and Flutter FE stacks.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"walwal-harness": "bin/init.js"
|
|
@@ -1,64 +1,26 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: harness-team-action
|
|
3
|
-
description: "v4 Agent Teams 가동. feature-queue를 초기화하고 3개 Teammate를 생성하여 Feature
|
|
3
|
+
description: "v4 Agent Teams 가동. feature-queue를 초기화하고 3개 Teammate를 생성하여 Feature 병렬 실행. 트리거: '/harness-team-action', 'team 시작', '팀 가동'"
|
|
4
4
|
disable-model-invocation: false
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# /harness-team-action — Agent Teams 가동
|
|
8
8
|
|
|
9
|
-
##
|
|
10
|
-
|
|
11
|
-
- `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1` 이 `.claude/settings.json`에 설정되어 있어야 함
|
|
12
|
-
- `.harness/actions/feature-list.json`이 존재해야 함 (Planner가 생성)
|
|
13
|
-
|
|
14
|
-
## 실행 절차
|
|
15
|
-
|
|
16
|
-
### Step 1: Feature Queue 초기화
|
|
17
|
-
|
|
18
|
-
먼저 feature-queue.json을 확인/생성합니다:
|
|
9
|
+
## Step 1: Queue 초기화
|
|
19
10
|
|
|
20
11
|
```bash
|
|
21
|
-
if [ ! -f .harness/actions/feature-queue.json ]; then
|
|
22
|
-
bash scripts/harness-queue-manager.sh init .
|
|
23
|
-
else
|
|
24
|
-
bash scripts/harness-queue-manager.sh recover .
|
|
25
|
-
fi
|
|
26
|
-
bash scripts/harness-queue-manager.sh status .
|
|
12
|
+
if [ ! -f .harness/actions/feature-queue.json ]; then bash scripts/harness-queue-manager.sh init .; else bash scripts/harness-queue-manager.sh recover .; fi && bash scripts/harness-queue-manager.sh status .
|
|
27
13
|
```
|
|
28
14
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
**아래 내용을 그대로 실행합니다** — 3명의 Teammate를 생성하여 Feature Queue에서 작업을 분배합니다.
|
|
32
|
-
|
|
33
|
-
`.harness/actions/feature-queue.json`의 `ready` 큐에서 최대 3개 Feature를 가져와 각 Teammate에게 할당합니다.
|
|
34
|
-
Teammate가 Feature를 완료하면 queue에서 다음 ready Feature를 가져옵니다.
|
|
35
|
-
|
|
36
|
-
**Teammate 생성 요청:**
|
|
37
|
-
|
|
38
|
-
3명의 Teammate를 생성하세요:
|
|
39
|
-
|
|
40
|
-
1. **team-1** (Generator + Evaluator):
|
|
41
|
-
- `.harness/actions/feature-queue.json`의 `ready` 배열에서 첫 번째 Feature를 가져옴
|
|
42
|
-
- 해당 Feature에 대해: 코드 생성 → `tsc --noEmit` + `eslint` 검증 → AC 기반 기능 평가
|
|
43
|
-
- PASS 시: `bash scripts/harness-queue-manager.sh pass {FEATURE_ID} .` 실행 후 다음 Feature
|
|
44
|
-
- FAIL 시: 최대 3회 재시도, 3회 실패 시 `bash scripts/harness-queue-manager.sh fail {FEATURE_ID} .`
|
|
45
|
-
- AGENTS.md의 파일 소유권 규칙 준수
|
|
46
|
-
|
|
47
|
-
2. **team-2** (동일 역할): ready 배열에서 두 번째 Feature
|
|
48
|
-
|
|
49
|
-
3. **team-3** (동일 역할): ready 배열에서 세 번째 Feature
|
|
15
|
+
## Step 2: Teammate 생성
|
|
50
16
|
|
|
51
|
-
|
|
52
|
-
- `.harness/actions/feature-list.json`에서 자신의 Feature 정보(AC, depends_on) 읽기
|
|
53
|
-
- `.harness/actions/api-contract.json`에서 관련 엔드포인트 확인
|
|
54
|
-
- `CONVENTIONS.md` 존재 시 준수
|
|
55
|
-
- Feature 완료 시 `feature-list.json`의 해당 Feature `passes`에 `["generator-frontend", "evaluator-functional"]` 추가
|
|
56
|
-
- 다른 Teammate의 Feature 코드를 수정하지 않음
|
|
57
|
-
- 완료/실패 시 Lead에게 `SendMessage`로 결과 보고
|
|
17
|
+
Queue의 `ready` 배열에서 Feature를 읽고, **3명의 Teammate를 즉시 생성**하세요.
|
|
58
18
|
|
|
59
|
-
|
|
19
|
+
각 Teammate에게 전달할 지시:
|
|
20
|
+
- `.harness/actions/feature-queue.json`의 ready에서 Feature 1개를 담당
|
|
21
|
+
- 해당 Feature의 코드를 생성하고, AC(Acceptance Criteria)를 기준으로 자체 검증
|
|
22
|
+
- PASS 시: `bash scripts/harness-queue-manager.sh pass {FEATURE_ID} .` 실행
|
|
23
|
+
- FAIL 시: 재시도 (최대 3회), 3회 실패 시 `bash scripts/harness-queue-manager.sh fail {FEATURE_ID} .`
|
|
24
|
+
- 완료 후 Lead에게 결과 보고
|
|
60
25
|
|
|
61
|
-
|
|
62
|
-
- 상태 확인: `bash scripts/harness-queue-manager.sh status .`
|
|
63
|
-
- 실패 Feature 재큐: `bash scripts/harness-queue-manager.sh requeue F-XXX .`
|
|
64
|
-
- 중지: `/harness-team-stop`
|
|
26
|
+
**Teammate 이름**: team-1, team-2, team-3
|