@walwal-harness/cli 4.0.0 → 5.0.1
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/README.md +118 -294
- package/assets/templates/config.json +10 -9
- package/assets/templates/progress.json.template +7 -1
- package/bin/init.js +115 -84
- package/commands/harness-solo.md +68 -0
- package/commands/harness-stop.md +53 -0
- package/{skills/team-action/SKILL.md → commands/harness-team.md} +59 -29
- package/package.json +9 -10
- package/scripts/harness-dashboard.sh +219 -154
- package/scripts/harness-monitor.sh +17 -12
- package/scripts/harness-prompt-history.sh +1 -1
- package/scripts/harness-queue-manager.sh +1 -1
- package/scripts/harness-session-start.sh +24 -13
- package/scripts/harness-statusline.sh +31 -8
- package/scripts/harness-tmux.sh +135 -72
- package/scripts/harness-user-prompt-submit.sh +22 -15
- package/skills/dispatcher/SKILL.md +6 -6
- package/skills/evaluator-functional/SKILL.md +2 -2
- package/skills/generator-frontend/SKILL.md +2 -2
- package/scripts/harness-control.sh +0 -183
- package/scripts/harness-dashboard-v4.sh +0 -200
- package/scripts/harness-eval-watcher.sh +0 -228
- package/scripts/harness-studio-setup.sh +0 -139
- package/scripts/harness-studio.sh +0 -66
- package/scripts/harness-tmux-v4.sh +0 -148
- package/skills/team-stop/SKILL.md +0 -30
package/bin/init.js
CHANGED
|
@@ -16,6 +16,7 @@ const { execSync } = require('child_process');
|
|
|
16
16
|
const PKG_ROOT = path.resolve(__dirname, '..');
|
|
17
17
|
const args = process.argv.slice(2);
|
|
18
18
|
const subcommand = args.find(a => !a.startsWith('-')) || null;
|
|
19
|
+
const subcommandArgs = args.filter(a => !a.startsWith('-') && a !== subcommand);
|
|
19
20
|
const isAuto = args.includes('--auto');
|
|
20
21
|
const isForce = args.includes('--force');
|
|
21
22
|
const isHelp = args.includes('--help') || args.includes('-h');
|
|
@@ -100,14 +101,26 @@ function scaffoldHarness() {
|
|
|
100
101
|
ensureDir(path.join(HARNESS_DIR, 'archive'));
|
|
101
102
|
ensureDir(path.join(HARNESS_DIR, 'gotchas'));
|
|
102
103
|
|
|
103
|
-
// Copy gotchas
|
|
104
|
+
// Copy gotchas — ALWAYS overwrite system templates, preserve user custom entries
|
|
104
105
|
const gotchasSrc = path.join(PKG_ROOT, 'gotchas');
|
|
105
106
|
if (fs.existsSync(gotchasSrc)) {
|
|
107
|
+
const CUSTOM_MARKER = '## Custom Gotchas';
|
|
106
108
|
const files = fs.readdirSync(gotchasSrc);
|
|
107
109
|
for (const file of files) {
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
110
|
+
const destPath = path.join(HARNESS_DIR, 'gotchas', file);
|
|
111
|
+
const srcPath = path.join(gotchasSrc, file);
|
|
112
|
+
if (fileExists(destPath) && file.endsWith('.md')) {
|
|
113
|
+
// Preserve user-added custom section
|
|
114
|
+
const existing = fs.readFileSync(destPath, 'utf8');
|
|
115
|
+
const customIdx = existing.indexOf(CUSTOM_MARKER);
|
|
116
|
+
const userCustom = customIdx !== -1 ? existing.substring(customIdx) : '';
|
|
117
|
+
let newContent = fs.readFileSync(srcPath, 'utf8');
|
|
118
|
+
if (userCustom) {
|
|
119
|
+
newContent = newContent.trimEnd() + '\n\n' + userCustom;
|
|
120
|
+
}
|
|
121
|
+
fs.writeFileSync(destPath, newContent);
|
|
122
|
+
} else {
|
|
123
|
+
copyFile(srcPath, destPath);
|
|
111
124
|
}
|
|
112
125
|
}
|
|
113
126
|
}
|
|
@@ -129,6 +142,25 @@ function scaffoldHarness() {
|
|
|
129
142
|
}
|
|
130
143
|
}
|
|
131
144
|
|
|
145
|
+
// Migrate progress.json v1 → v2 (add mode + team_state fields)
|
|
146
|
+
const progressPath = path.join(HARNESS_DIR, 'progress.json');
|
|
147
|
+
if (fileExists(progressPath)) {
|
|
148
|
+
try {
|
|
149
|
+
const progress = JSON.parse(fs.readFileSync(progressPath, 'utf8'));
|
|
150
|
+
if (!progress.version || progress.version < 2) {
|
|
151
|
+
progress.version = 2;
|
|
152
|
+
progress.mode = progress.mode || 'solo';
|
|
153
|
+
if (!progress.team_state) {
|
|
154
|
+
progress.team_state = { active_teams: 0, paused_at: null, resume_from: null };
|
|
155
|
+
}
|
|
156
|
+
fs.writeFileSync(progressPath, JSON.stringify(progress, null, 2) + '\n');
|
|
157
|
+
log('progress.json migrated to v2 (mode + team_state added)');
|
|
158
|
+
}
|
|
159
|
+
} catch (e) {
|
|
160
|
+
log('WARNING: Could not migrate progress.json');
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
132
164
|
// config.json — ALWAYS update (harness system file, not user data)
|
|
133
165
|
// But preserve user's custom settings (pre_eval_gate.frontend_cwd, behavior, etc.)
|
|
134
166
|
const configSrc = path.join(PKG_ROOT, 'assets', 'templates', 'config.json');
|
|
@@ -286,6 +318,42 @@ function installScripts() {
|
|
|
286
318
|
log('Scripts installation complete');
|
|
287
319
|
}
|
|
288
320
|
|
|
321
|
+
// ─────────────────────────────────────────
|
|
322
|
+
// 3a. Commands → .claude/commands/
|
|
323
|
+
// ─────────────────────────────────────────
|
|
324
|
+
function installCommands() {
|
|
325
|
+
log('Installing commands to .claude/commands/...');
|
|
326
|
+
|
|
327
|
+
const commandsSrc = path.join(PKG_ROOT, 'commands');
|
|
328
|
+
if (!fs.existsSync(commandsSrc)) {
|
|
329
|
+
log('WARNING: commands/ directory not found in package');
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const commandsDest = path.join(PROJECT_ROOT, '.claude', 'commands');
|
|
334
|
+
ensureDir(commandsDest);
|
|
335
|
+
|
|
336
|
+
// Remove existing harness-* commands to prevent stale files
|
|
337
|
+
if (fs.existsSync(commandsDest)) {
|
|
338
|
+
const existing = fs.readdirSync(commandsDest);
|
|
339
|
+
for (const f of existing) {
|
|
340
|
+
if (f.startsWith('harness-')) {
|
|
341
|
+
fs.unlinkSync(path.join(commandsDest, f));
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
log(' Cleared existing harness-* commands');
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Copy all command files
|
|
348
|
+
const files = fs.readdirSync(commandsSrc).filter(f => f.endsWith('.md'));
|
|
349
|
+
for (const file of files) {
|
|
350
|
+
copyFile(path.join(commandsSrc, file), path.join(commandsDest, file));
|
|
351
|
+
log(` Installed: /${file.replace('.md', '')}`);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
log('Commands installation complete');
|
|
355
|
+
}
|
|
356
|
+
|
|
289
357
|
// ─────────────────────────────────────────
|
|
290
358
|
// 3b. SessionStart hook
|
|
291
359
|
// ─────────────────────────────────────────
|
|
@@ -615,45 +683,43 @@ function showHelp() {
|
|
|
615
683
|
╚══════════════════════════════════════╝
|
|
616
684
|
|
|
617
685
|
Usage:
|
|
618
|
-
npx walwal-harness
|
|
619
|
-
npx walwal-harness --force
|
|
620
|
-
npx walwal-harness
|
|
621
|
-
npx walwal-harness
|
|
622
|
-
npx walwal-harness
|
|
623
|
-
|
|
686
|
+
npx walwal-harness Initialize project for harness engineering
|
|
687
|
+
npx walwal-harness --force Re-initialize (overwrites existing files)
|
|
688
|
+
npx walwal-harness team Launch Team Mode (tmux studio + auto teams)
|
|
689
|
+
npx walwal-harness team --kill Kill Team Mode tmux session
|
|
690
|
+
npx walwal-harness --help Show this help
|
|
691
|
+
|
|
692
|
+
Modes (use inside claude/codex session):
|
|
693
|
+
/harness-solo Solo mode — prompting-based sequential pipeline
|
|
694
|
+
/harness-team Team mode — auto parallel Gen-Eval loop (3 teams)
|
|
695
|
+
/harness-stop Stop Team mode (preserves queue state)
|
|
624
696
|
|
|
625
697
|
What it does:
|
|
626
698
|
1. Scaffolds .harness/ directory (actions, archive, gotchas, config)
|
|
627
699
|
2. Installs skills to .claude/skills/ (dispatcher, planner, generators, evaluators)
|
|
628
|
-
3.
|
|
629
|
-
4.
|
|
630
|
-
5.
|
|
631
|
-
6.
|
|
700
|
+
3. Installs commands to .claude/commands/ (solo, team, stop)
|
|
701
|
+
4. Copies helper scripts to scripts/
|
|
702
|
+
5. Registers SessionStart + UserPromptSubmit hooks
|
|
703
|
+
6. Installs statusline (persistent 1-line status bar)
|
|
632
704
|
7. Creates AGENTS.md + CLAUDE.md symlink
|
|
633
|
-
8. Checks Playwright MCP configuration
|
|
634
|
-
9. Checks recommended external skills (Vercel, design skills)
|
|
635
|
-
|
|
636
|
-
Auto routing:
|
|
637
|
-
Every user prompt is routed through harness-dispatcher.
|
|
638
|
-
Per-message opt-out: say "harness skip" or "harness 없이".
|
|
639
|
-
Global disable: edit .harness/config.json → behavior.auto_route_dispatcher = false
|
|
640
705
|
|
|
641
706
|
After init:
|
|
642
|
-
1. Restart Claude Code session (exit
|
|
643
|
-
2. Say "하네스 엔지니어링 시작" or
|
|
707
|
+
1. Restart Claude Code session (/exit → re-enter directory)
|
|
708
|
+
2. Say "하네스 엔지니어링 시작" or /harness-dispatcher
|
|
709
|
+
3. After Planner completes: /harness-team (team) or continue prompting (solo)
|
|
644
710
|
`);
|
|
645
711
|
}
|
|
646
712
|
|
|
647
713
|
// ─────────────────────────────────────────
|
|
648
714
|
// Main
|
|
649
715
|
// ─────────────────────────────────────────
|
|
650
|
-
function
|
|
651
|
-
const
|
|
716
|
+
function runTeamStudio() {
|
|
717
|
+
const killMode = args.includes('--kill');
|
|
652
718
|
const scriptsDir = path.join(PKG_ROOT, 'scripts');
|
|
653
719
|
const tmuxScript = path.join(scriptsDir, 'harness-tmux.sh');
|
|
654
720
|
|
|
655
721
|
if (!fs.existsSync(tmuxScript)) {
|
|
656
|
-
log('ERROR: harness-tmux.sh not found.
|
|
722
|
+
log('ERROR: harness-tmux.sh not found.');
|
|
657
723
|
process.exit(1);
|
|
658
724
|
}
|
|
659
725
|
|
|
@@ -664,57 +730,18 @@ function runStudio() {
|
|
|
664
730
|
process.exit(1);
|
|
665
731
|
}
|
|
666
732
|
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
function runStudioV4() {
|
|
673
|
-
// Enable Agent Teams in project settings
|
|
674
|
-
const settingsPath = path.join(PROJECT_ROOT, '.claude', 'settings.json');
|
|
675
|
-
let settings = {};
|
|
676
|
-
if (fileExists(settingsPath)) {
|
|
677
|
-
try { settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')); } catch (e) {}
|
|
733
|
+
if (killMode) {
|
|
734
|
+
const cmd = `bash "${tmuxScript}" --kill`;
|
|
735
|
+
execSync(cmd, { stdio: 'inherit' });
|
|
736
|
+
return;
|
|
678
737
|
}
|
|
679
738
|
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
ensureDir(path.dirname(settingsPath));
|
|
683
|
-
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
684
|
-
|
|
685
|
-
// Initialize feature queue if feature-list.json exists
|
|
686
|
-
const featureList = path.join(PROJECT_ROOT, '.harness', 'actions', 'feature-list.json');
|
|
687
|
-
const featureQueue = path.join(PROJECT_ROOT, '.harness', 'actions', 'feature-queue.json');
|
|
688
|
-
const queueMgr = path.join(PKG_ROOT, 'scripts', 'harness-queue-manager.sh');
|
|
689
|
-
|
|
690
|
-
if (fs.existsSync(featureList) && fs.existsSync(queueMgr)) {
|
|
691
|
-
if (!fs.existsSync(featureQueue)) {
|
|
692
|
-
log('Initializing feature queue...');
|
|
693
|
-
try { execSync(`bash "${queueMgr}" init "${PROJECT_ROOT}"`, { stdio: 'inherit' }); } catch (e) {}
|
|
694
|
-
} else {
|
|
695
|
-
log('Recovering feature queue...');
|
|
696
|
-
try { execSync(`bash "${queueMgr}" recover "${PROJECT_ROOT}"`, { stdio: 'inherit' }); } catch (e) {}
|
|
697
|
-
}
|
|
698
|
-
}
|
|
739
|
+
// Enable Agent Teams in project settings
|
|
740
|
+
installAgentTeamsEnv();
|
|
699
741
|
|
|
700
|
-
|
|
701
|
-
log('
|
|
702
|
-
|
|
703
|
-
log('╠═══════════════════════════════════════════════════════════╣');
|
|
704
|
-
log('║ ║');
|
|
705
|
-
log('║ CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 set in ║');
|
|
706
|
-
log('║ .claude/settings.json ║');
|
|
707
|
-
log('║ ║');
|
|
708
|
-
log('║ Next steps: ║');
|
|
709
|
-
log('║ 1. Restart Claude Code (/exit → re-enter) ║');
|
|
710
|
-
log('║ 2. Run Planner: "하네스 엔지니어링 시작" ║');
|
|
711
|
-
log('║ 3. Start Teams: /harness-team-action ║');
|
|
712
|
-
log('║ ║');
|
|
713
|
-
log('║ Or use --teammate-mode tmux for split panes: ║');
|
|
714
|
-
log('║ $ claude --teammate-mode tmux ║');
|
|
715
|
-
log('║ ║');
|
|
716
|
-
log('╚═══════════════════════════════════════════════════════════╝');
|
|
717
|
-
console.log('');
|
|
742
|
+
const cmd = `bash "${tmuxScript}" "${PROJECT_ROOT}" --team`;
|
|
743
|
+
log('Launching Team Mode Studio...');
|
|
744
|
+
execSync(cmd, { stdio: 'inherit' });
|
|
718
745
|
}
|
|
719
746
|
|
|
720
747
|
function main() {
|
|
@@ -723,13 +750,15 @@ function main() {
|
|
|
723
750
|
return;
|
|
724
751
|
}
|
|
725
752
|
|
|
726
|
-
if (subcommand === '
|
|
727
|
-
|
|
753
|
+
if (subcommand === 'team') {
|
|
754
|
+
runTeamStudio();
|
|
728
755
|
return;
|
|
729
756
|
}
|
|
730
757
|
|
|
731
|
-
|
|
732
|
-
|
|
758
|
+
// Legacy subcommands — redirect to new equivalents
|
|
759
|
+
if (subcommand === 'studio' || subcommand === 'studio-v4' || subcommand === 'v4') {
|
|
760
|
+
log('NOTE: "studio" and "v4" subcommands are replaced by "team".');
|
|
761
|
+
runTeamStudio();
|
|
733
762
|
return;
|
|
734
763
|
}
|
|
735
764
|
|
|
@@ -747,6 +776,7 @@ function main() {
|
|
|
747
776
|
scaffoldHarness();
|
|
748
777
|
installSkills();
|
|
749
778
|
installScripts();
|
|
779
|
+
installCommands();
|
|
750
780
|
installSessionHook();
|
|
751
781
|
installStatusline();
|
|
752
782
|
installUserPromptSubmitHook();
|
|
@@ -760,20 +790,21 @@ function main() {
|
|
|
760
790
|
log('');
|
|
761
791
|
|
|
762
792
|
if (isAuto) {
|
|
763
|
-
// postinstall context — Claude Code is likely already running
|
|
764
793
|
log('╔═══════════════════════════════════════════════════════════╗');
|
|
765
|
-
log('║
|
|
766
|
-
log('║ ║');
|
|
767
|
-
log('║ Claude Code discovers skills at session startup. ║');
|
|
768
|
-
log('║ Type /exit, then re-enter this directory to begin. ║');
|
|
794
|
+
log('║ Restart Claude Code for skills & commands to activate! ║');
|
|
769
795
|
log('║ ║');
|
|
770
796
|
log('║ Then say: "하네스 엔지니어링 시작" ║');
|
|
771
797
|
log('║ Or invoke: /harness-dispatcher ║');
|
|
798
|
+
log('║ ║');
|
|
799
|
+
log('║ After Planner completes: ║');
|
|
800
|
+
log('║ /harness-team → Team mode (auto parallel) ║');
|
|
801
|
+
log('║ /harness-solo → Solo mode (prompting sequential) ║');
|
|
772
802
|
log('╚═══════════════════════════════════════════════════════════╝');
|
|
773
803
|
} else {
|
|
774
804
|
log('Next steps:');
|
|
775
805
|
log(' 1. Restart Claude Code session (/exit → re-enter directory)');
|
|
776
|
-
log(' 2. Say "하네스 엔지니어링 시작" or
|
|
806
|
+
log(' 2. Say "하네스 엔지니어링 시작" or /harness-dispatcher');
|
|
807
|
+
log(' 3. After Planner: /harness-team (team) or keep prompting (solo)');
|
|
777
808
|
}
|
|
778
809
|
console.log('');
|
|
779
810
|
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# /harness-solo — Solo Mode 시작/전환
|
|
2
|
+
|
|
3
|
+
프롬프트 기반으로 하네스 파이프라인을 순차 진행합니다.
|
|
4
|
+
Team 모드에서 전환 시, 진행 중이던 피처의 상태를 보존하고 Solo로 이어갑니다.
|
|
5
|
+
|
|
6
|
+
## 실행 절차
|
|
7
|
+
|
|
8
|
+
### Step 1: 현재 모드 확인 + 전환
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
# 현재 mode 읽기
|
|
12
|
+
MODE=$(jq -r '.mode // "solo"' .harness/progress.json 2>/dev/null)
|
|
13
|
+
echo "현재 모드: $MODE"
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
**team → solo 전환 시:**
|
|
17
|
+
```bash
|
|
18
|
+
# in_progress features를 ready로 복구
|
|
19
|
+
bash scripts/harness-queue-manager.sh recover .
|
|
20
|
+
|
|
21
|
+
# mode를 solo로 설정
|
|
22
|
+
jq '.mode = "solo" | .team_state.active_teams = 0 | .team_state.paused_at = (now | todate)' .harness/progress.json > /tmp/progress_tmp.json && mv /tmp/progress_tmp.json .harness/progress.json
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**paused → solo 전환 시:**
|
|
26
|
+
```bash
|
|
27
|
+
jq '.mode = "solo"' .harness/progress.json > /tmp/progress_tmp.json && mv /tmp/progress_tmp.json .harness/progress.json
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Step 2: 현재 진행 상태 표시
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# progress.json 상태 확인
|
|
34
|
+
cat .harness/progress.json | jq '{mode, sprint, current_agent, agent_status, next_agent}'
|
|
35
|
+
|
|
36
|
+
# feature-queue.json이 있으면 남은 피처 표시
|
|
37
|
+
if [ -f .harness/actions/feature-queue.json ]; then
|
|
38
|
+
echo "=== Feature Queue ==="
|
|
39
|
+
bash scripts/harness-queue-manager.sh status .
|
|
40
|
+
fi
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Step 3: 다음 에이전트 안내
|
|
44
|
+
|
|
45
|
+
progress.json의 `next_agent` 필드를 확인하여 다음 단계를 안내합니다:
|
|
46
|
+
|
|
47
|
+
- **next_agent = "dispatcher"** → "프롬프트로 요구사항을 입력하면 자동으로 dispatcher가 분석합니다."
|
|
48
|
+
- **next_agent = "planner"** → "/harness-planner 를 호출하거나, 계획을 요청하세요."
|
|
49
|
+
- **next_agent = "generator-backend"** → "/harness-generator-backend 를 호출하세요."
|
|
50
|
+
- **next_agent = "generator-frontend"** → "/harness-generator-frontend 를 호출하세요."
|
|
51
|
+
- **next_agent = "evaluator-functional"** → "/harness-evaluator-functional 를 호출하세요."
|
|
52
|
+
- **next_agent = "evaluator-visual"** → "/harness-evaluator-visual 를 호출하세요."
|
|
53
|
+
|
|
54
|
+
### Step 4: Solo 모드 진행
|
|
55
|
+
|
|
56
|
+
사용자가 프롬프트로 각 에이전트 스킬을 호출하여 순차 진행합니다.
|
|
57
|
+
각 에이전트 완료 시 harness-next.sh가 자동으로 다음 에이전트를 결정합니다.
|
|
58
|
+
|
|
59
|
+
feature-queue.json이 존재하는 경우:
|
|
60
|
+
- 피처 완료(evaluator PASS) 시 `bash scripts/harness-queue-manager.sh pass {FEATURE_ID} .`를 호출하여 공유 상태 업데이트
|
|
61
|
+
- Team 모드로 전환해도 이미 완료된 피처는 skip됨
|
|
62
|
+
|
|
63
|
+
## Solo 모드에서 가능한 작업
|
|
64
|
+
|
|
65
|
+
- 프롬프트로 각 에이전트 순차 실행
|
|
66
|
+
- 코드 직접 수정 및 디버깅
|
|
67
|
+
- 피처 단위 작업 선택
|
|
68
|
+
- Team 모드로 언제든 전환: `/harness-team`
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# /harness-stop — Team Mode 중단
|
|
2
|
+
|
|
3
|
+
실행 중인 Team 모드를 안전하게 중단합니다.
|
|
4
|
+
진행 중인 피처는 ready 상태로 복구되어, 이후 Solo 또는 Team 모드에서 이어갈 수 있습니다.
|
|
5
|
+
|
|
6
|
+
## 실행 절차
|
|
7
|
+
|
|
8
|
+
### Step 1: 현재 모드 확인
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
MODE=$(jq -r '.mode // "solo"' .harness/progress.json 2>/dev/null)
|
|
12
|
+
echo "현재 모드: $MODE"
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
- **solo** → "이미 Solo 모드입니다. Team을 시작하려면 /harness-team 을 사용하세요." 안내 후 중단.
|
|
16
|
+
- **team** → Step 2로 진행.
|
|
17
|
+
|
|
18
|
+
### Step 2: Queue 상태 보존
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# in_progress features를 ready로 복구
|
|
22
|
+
bash scripts/harness-queue-manager.sh recover .
|
|
23
|
+
|
|
24
|
+
# 현재 큐 상태 출력
|
|
25
|
+
bash scripts/harness-queue-manager.sh status .
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Step 3: 모드 전환
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# mode를 paused로 설정 (team에서 명시적으로 중단)
|
|
32
|
+
jq '.mode = "paused" | .team_state.active_teams = 0 | .team_state.paused_at = (now | todate)' .harness/progress.json > /tmp/progress_tmp.json && mv /tmp/progress_tmp.json .harness/progress.json
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Step 4: tmux 세션 정리 (선택)
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# harness-studio tmux 세션이 있으면 정리
|
|
39
|
+
tmux kill-session -t harness-studio 2>/dev/null && echo "tmux 세션 정리 완료" || echo "활성 tmux 세션 없음"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Step 5: 재개 안내
|
|
43
|
+
|
|
44
|
+
다음 안내를 사용자에게 전달합니다:
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
Team 모드가 안전하게 중단되었습니다.
|
|
48
|
+
진행 중이던 피처는 ready 상태로 복구되었습니다.
|
|
49
|
+
|
|
50
|
+
재개 방법:
|
|
51
|
+
/harness-team → Team 모드 재개 (남은 피처 자동 할당)
|
|
52
|
+
/harness-solo → Solo 모드로 전환 (프롬프트 기반 순차 진행)
|
|
53
|
+
```
|
|
@@ -1,35 +1,51 @@
|
|
|
1
|
-
|
|
2
|
-
name: harness-team-action
|
|
3
|
-
description: "v4 Agent Teams 가동. Studio 레이아웃 구축 → Queue 초기화 → Gen↔Eval 분리 사이클 팀 병렬 실행. 트리거: '/harness-team-action', 'team 시작', '팀 가동'"
|
|
4
|
-
disable-model-invocation: false
|
|
5
|
-
---
|
|
1
|
+
# /harness-team — Team Mode 시작/재개
|
|
6
2
|
|
|
7
|
-
|
|
3
|
+
Planner가 완료한 feature-list.json의 피처들을 3개 팀이 병렬로 Gen→Eval 사이클을 수행합니다.
|
|
8
4
|
|
|
9
|
-
##
|
|
5
|
+
## 실행 절차
|
|
10
6
|
|
|
11
|
-
|
|
7
|
+
### Step 0: 선행 조건 확인
|
|
12
8
|
|
|
13
9
|
```bash
|
|
14
|
-
|
|
10
|
+
# Planner 아티팩트 확인
|
|
11
|
+
[ -f .harness/actions/feature-list.json ] && [ -f .harness/actions/api-contract.json ] && echo "READY" || echo "NOT_READY"
|
|
15
12
|
```
|
|
16
13
|
|
|
17
|
-
|
|
14
|
+
- **NOT_READY** → "Planner가 먼저 완료되어야 합니다. /harness-dispatcher 또는 프롬프트로 진행하세요." 안내 후 중단.
|
|
15
|
+
- **READY** → Step 1로 진행.
|
|
16
|
+
|
|
17
|
+
### Step 1: 모드 전환 + Queue 초기화
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
```bash
|
|
20
|
+
# progress.json에 mode=team 설정
|
|
21
|
+
jq '.mode = "team" | .team_state.active_teams = 3 | .team_state.paused_at = null' .harness/progress.json > /tmp/progress_tmp.json && mv /tmp/progress_tmp.json .harness/progress.json
|
|
22
|
+
|
|
23
|
+
# Queue 초기화 또는 복구
|
|
24
|
+
if [ ! -f .harness/actions/feature-queue.json ]; then
|
|
25
|
+
bash scripts/harness-queue-manager.sh init .
|
|
26
|
+
else
|
|
27
|
+
bash scripts/harness-queue-manager.sh recover .
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
# Queue 상태 확인
|
|
31
|
+
bash scripts/harness-queue-manager.sh status .
|
|
32
|
+
```
|
|
22
33
|
|
|
23
|
-
|
|
34
|
+
### Step 2: tmux Studio 레이아웃 구축
|
|
24
35
|
|
|
25
36
|
```bash
|
|
26
|
-
|
|
37
|
+
bash scripts/harness-tmux.sh --team
|
|
27
38
|
```
|
|
28
39
|
|
|
29
|
-
|
|
40
|
+
스크립트 출력을 확인합니다:
|
|
41
|
+
- **`Layout ready`** → tmux 레이아웃 구축 완료. Step 3로.
|
|
42
|
+
- **`OPENED_TERMINAL=true`** → 새 Terminal.app 창에 Studio 레이아웃이 자동 구축됨. Step 3로.
|
|
43
|
+
- **`already set up`** → 이미 구축됨. Step 3로.
|
|
44
|
+
|
|
45
|
+
### Step 3: Feature 할당 (dequeue)
|
|
30
46
|
|
|
31
47
|
Queue status 결과를 확인하여 `ready` 큐에 feature가 있는지 확인합니다.
|
|
32
|
-
ready feature 수와 설정된 concurrency 중 작은 값만큼 팀을
|
|
48
|
+
ready feature 수와 설정된 concurrency(기본 3) 중 작은 값만큼 팀을 생성합니다.
|
|
33
49
|
|
|
34
50
|
**각 팀마다** dequeue 명령으로 feature를 원자적으로 할당합니다:
|
|
35
51
|
|
|
@@ -39,7 +55,7 @@ bash scripts/harness-queue-manager.sh dequeue {TEAM_NUMBER} .
|
|
|
39
55
|
|
|
40
56
|
dequeue 결과로 feature ID가 반환됩니다. 빈 결과면 해당 팀은 생성하지 않습니다.
|
|
41
57
|
|
|
42
|
-
|
|
58
|
+
### Step 4: Agent 도구로 팀 생성 (Gen↔Eval 분리 사이클)
|
|
43
59
|
|
|
44
60
|
dequeue로 할당받은 feature마다 **Agent 도구**를 호출합니다.
|
|
45
61
|
**반드시 `isolation: "worktree"`를 사용**하여 각 팀이 독립된 코드 복사본에서 작업합니다.
|
|
@@ -58,6 +74,7 @@ Agent({
|
|
|
58
74
|
|
|
59
75
|
```
|
|
60
76
|
당신은 Harness Team-{N} 워커입니다. 하나의 Feature에 대해 Gen→Eval 사이클을 수행합니다.
|
|
77
|
+
완료 후 자동으로 다음 Feature를 dequeue하여 연속 작업합니다.
|
|
61
78
|
|
|
62
79
|
## 할당된 Feature
|
|
63
80
|
- Feature ID: {FEATURE_ID}
|
|
@@ -87,6 +104,7 @@ progress.log에 한 줄씩 남기세요. 대시보드는 3초마다 tail합니
|
|
|
87
104
|
| `eval-done` | Eval 결과 수신 | `verdict=PASS score=2.95` |
|
|
88
105
|
| `result` / `pass` | PASS 확정 | `F-001 PASS — queue.pass` |
|
|
89
106
|
| `fail` | FAIL 확정(재시도/최종) | `FAIL #1 — AC-2 missing` |
|
|
107
|
+
| `escalate` | 5회 초과 실패 | `F-001 ESCALATED — user intervention required` |
|
|
90
108
|
|
|
91
109
|
**progress.log 기록** (하네스 루트의 progress.log에 append):
|
|
92
110
|
```bash
|
|
@@ -145,14 +163,10 @@ logev gen-done "{FEATURE_ID} done — {변경파일수} files, {LOC} LOC"
|
|
|
145
163
|
|
|
146
164
|
## Phase 2: Evaluator (독립 평가 — Agent 도구 사용)
|
|
147
165
|
|
|
148
|
-
**Eval 시작
|
|
166
|
+
**Eval 시작 로깅:**
|
|
149
167
|
```bash
|
|
150
168
|
logev eval-start "{FEATURE_ID} spawning evaluator"
|
|
151
169
|
bash "$HARNESS_ROOT/scripts/harness-queue-manager.sh" update_phase {FEATURE_ID} eval "$HARNESS_ROOT"
|
|
152
|
-
# Evaluator가 AC를 하나씩 검증할 때마다:
|
|
153
|
-
# logev eval-check "AC-3 — POST /users returns 201"
|
|
154
|
-
# 최종:
|
|
155
|
-
# logev eval-done "verdict=PASS score=2.95"
|
|
156
170
|
```
|
|
157
171
|
|
|
158
172
|
코드 생성이 완료되면 **별도 Agent를 생성하여 평가**합니다.
|
|
@@ -199,7 +213,7 @@ FEEDBACK: (FAIL인 경우만) 구체적 수정 지시
|
|
|
199
213
|
|
|
200
214
|
Evaluator Agent 결과를 확인합니다:
|
|
201
215
|
|
|
202
|
-
### PASS인 경우 (VERDICT: PASS, SCORE
|
|
216
|
+
### PASS인 경우 (VERDICT: PASS, SCORE >= 2.80):
|
|
203
217
|
```bash
|
|
204
218
|
logev result "{FEATURE_ID} PASS score={SCORE} — merging"
|
|
205
219
|
bash "$HARNESS_ROOT/scripts/harness-queue-manager.sh" pass {FEATURE_ID} "$HARNESS_ROOT"
|
|
@@ -212,20 +226,31 @@ logev fail "{FEATURE_ID} FAIL #{ATTEMPT} — {사유요약}"
|
|
|
212
226
|
```
|
|
213
227
|
1. Evaluator의 FEEDBACK을 읽고 코드를 수정 (Phase 1로 돌아감)
|
|
214
228
|
2. 수정 후 다시 Phase 2 (새 Evaluator Agent 생성 — 이전 Eval 컨텍스트 없음)
|
|
215
|
-
3. 최대
|
|
229
|
+
3. 최대 **5회** 시도. 5회 모두 FAIL이면:
|
|
216
230
|
```bash
|
|
217
|
-
logev
|
|
231
|
+
logev escalate "{FEATURE_ID} ESCALATED after 5 attempts — user intervention required"
|
|
218
232
|
bash "$HARNESS_ROOT/scripts/harness-queue-manager.sh" fail {FEATURE_ID} "$HARNESS_ROOT"
|
|
219
233
|
```
|
|
220
|
-
실패 사유와 마지막 Eval 결과를 Lead에게 반환.
|
|
234
|
+
실패 사유와 마지막 Eval 결과를 Lead에게 반환. **사용자 개입을 요청**합니다.
|
|
235
|
+
|
|
236
|
+
## Phase 4: 자동 업무 획득
|
|
237
|
+
|
|
238
|
+
피처 PASS 또는 최종 FAIL 처리 후:
|
|
239
|
+
1. Queue 상태 확인: `bash "$HARNESS_ROOT/scripts/harness-queue-manager.sh" status "$HARNESS_ROOT"`
|
|
240
|
+
2. `ready` 큐에 피처가 있으면 → dequeue → **새 Gen-Eval 루프 시작** (Phase 1부터 반복)
|
|
241
|
+
```bash
|
|
242
|
+
NEW_FEATURE=$(bash "$HARNESS_ROOT/scripts/harness-queue-manager.sh" dequeue {N} "$HARNESS_ROOT")
|
|
243
|
+
logev gen-start "$NEW_FEATURE auto-acquired"
|
|
244
|
+
```
|
|
245
|
+
3. `ready=0`이면 → "Team-{N} 대기 중 — 모든 할당 피처 완료" 반환
|
|
221
246
|
```
|
|
222
247
|
|
|
223
|
-
|
|
248
|
+
### Step 5: 결과 수집 및 다음 라운드
|
|
224
249
|
|
|
225
250
|
모든 Team Agent가 완료되면:
|
|
226
251
|
1. 각 Agent 반환 메시지에서 PASS/FAIL 확인
|
|
227
252
|
2. Queue 상태 재확인: `bash scripts/harness-queue-manager.sh status .`
|
|
228
|
-
3. ready 큐에 새로 unblock된 feature가 있으면 → **Step
|
|
253
|
+
3. ready 큐에 새로 unblock된 feature가 있으면 → **Step 3으로 돌아가서** 추가 팀 생성
|
|
229
254
|
4. ready=0, in_progress=0이면 → 최종 결과 보고
|
|
230
255
|
|
|
231
256
|
## 핵심 원칙
|
|
@@ -242,3 +267,8 @@ logev fail "{FEATURE_ID} FAIL #{ATTEMPT} — {사유요약}"
|
|
|
242
267
|
### 격리
|
|
243
268
|
- 각 팀은 `isolation: "worktree"`로 독립 코드 복사본에서 작업
|
|
244
269
|
- 팀 간 코드 충돌 없음
|
|
270
|
+
|
|
271
|
+
### 에스컬레이션 (5회 초과)
|
|
272
|
+
- 5회 연속 FAIL 시 사용자 개입 요청
|
|
273
|
+
- escalate 로그 기록 → Dashboard에 즉시 표시
|
|
274
|
+
- 해당 팀은 다음 피처로 이동하지 않고 대기
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@walwal-harness/cli",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Production harness for AI agent engineering — Planner, Generator(BE/FE), Evaluator(Func/Visual), optional Brainstormer
|
|
3
|
+
"version": "5.0.1",
|
|
4
|
+
"description": "Production harness for AI agent engineering — Solo/Team mode, Planner, Generator(BE/FE), Evaluator(Func/Visual), optional Brainstormer. Supports React, Next.js, and Flutter FE stacks.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"walwal-harness": "bin/init.js"
|
|
7
7
|
},
|
|
@@ -9,15 +9,17 @@
|
|
|
9
9
|
"postinstall": "node bin/init.js --auto",
|
|
10
10
|
"init": "node bin/init.js",
|
|
11
11
|
"scan": "bash scripts/scan-project.sh .",
|
|
12
|
-
"reset": "node bin/init.js --force"
|
|
13
|
-
"studio": "bash scripts/harness-studio.sh",
|
|
14
|
-
"studio:kill": "bash scripts/harness-studio.sh --kill"
|
|
12
|
+
"reset": "node bin/init.js --force"
|
|
15
13
|
},
|
|
16
14
|
"keywords": [
|
|
17
15
|
"harness-engineering",
|
|
18
16
|
"ai-agent",
|
|
19
17
|
"claude-code",
|
|
18
|
+
"codex",
|
|
20
19
|
"skill",
|
|
20
|
+
"command",
|
|
21
|
+
"solo-mode",
|
|
22
|
+
"team-mode",
|
|
21
23
|
"brainstorming",
|
|
22
24
|
"planner",
|
|
23
25
|
"generator",
|
|
@@ -25,7 +27,6 @@
|
|
|
25
27
|
"nestjs",
|
|
26
28
|
"react",
|
|
27
29
|
"flutter",
|
|
28
|
-
"riverpod",
|
|
29
30
|
"playwright"
|
|
30
31
|
],
|
|
31
32
|
"author": "",
|
|
@@ -34,10 +35,8 @@
|
|
|
34
35
|
"bin/",
|
|
35
36
|
"skills/",
|
|
36
37
|
"scripts/",
|
|
38
|
+
"commands/",
|
|
37
39
|
"assets/",
|
|
38
40
|
"gotchas/"
|
|
39
|
-
]
|
|
40
|
-
"dependencies": {
|
|
41
|
-
"@walwal-harness/cli": "^3.3.0"
|
|
42
|
-
}
|
|
41
|
+
]
|
|
43
42
|
}
|