@walwal-harness/cli 4.0.0-beta.4 → 4.0.0-beta.6

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 CHANGED
@@ -225,25 +225,24 @@ function installSkills() {
225
225
  return;
226
226
  }
227
227
 
228
+ // harness- 프리픽스 스킬 전체 삭제 후 재복사 — 잔류 방지
229
+ if (fs.existsSync(CLAUDE_SKILLS_DIR)) {
230
+ const existing = fs.readdirSync(CLAUDE_SKILLS_DIR, { withFileTypes: true });
231
+ for (const entry of existing) {
232
+ if (entry.isDirectory() && entry.name.startsWith('harness-')) {
233
+ fs.rmSync(path.join(CLAUDE_SKILLS_DIR, entry.name), { recursive: true, force: true });
234
+ }
235
+ }
236
+ log(' Cleared existing harness-* skills');
237
+ }
238
+
228
239
  const skills = fs.readdirSync(skillsSrc, { withFileTypes: true })
229
240
  .filter(d => d.isDirectory())
230
241
  .map(d => d.name);
231
242
 
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
-
242
243
  for (const skill of skills) {
243
244
  const src = path.join(skillsSrc, skill);
244
245
  const dest = path.join(CLAUDE_SKILLS_DIR, `harness-${skill}`);
245
-
246
- // Skills are ALWAYS overwritten — they are harness-managed, not user-editable
247
246
  copyDir(src, dest);
248
247
  log(` Installed: harness-${skill}`);
249
248
  }
@@ -260,63 +259,28 @@ function installScripts() {
260
259
  const scriptsSrc = path.join(PKG_ROOT, 'scripts');
261
260
  const scriptsDest = path.join(PROJECT_ROOT, 'scripts');
262
261
 
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
- }
262
+ // 전체 삭제 재복사 버전 간 잔류 파일 방지
263
+ if (fs.existsSync(scriptsDest)) {
264
+ fs.rmSync(scriptsDest, { recursive: true, force: true });
265
+ log(' Cleared existing scripts/');
276
266
  }
277
267
 
278
- // Core scripts are ALWAYS overwritten on update (not user-editable)
279
- // These contain harness logic that must stay in sync with the CLI version
280
- const coreScripts = new Set([
281
- 'harness-next.sh',
282
- 'harness-session-start.sh',
283
- 'harness-statusline.sh',
284
- 'harness-user-prompt-submit.sh',
285
- 'harness-dashboard.sh',
286
- 'harness-monitor.sh',
287
- 'harness-eval-watcher.sh',
288
- 'harness-tmux.sh',
289
- 'harness-control.sh',
290
- 'harness-dashboard-v4.sh',
291
- 'harness-queue-manager.sh',
292
- ]);
293
-
294
268
  if (fs.existsSync(scriptsSrc)) {
295
- ensureDir(scriptsDest);
296
- const entries = fs.readdirSync(scriptsSrc, { withFileTypes: true });
297
- for (const entry of entries) {
298
- const srcPath = path.join(scriptsSrc, entry.name);
299
- const destPath = path.join(scriptsDest, entry.name);
300
- if (entry.isDirectory()) {
301
- // lib/ and other subdirectories — always overwrite
302
- copyDir(srcPath, destPath);
303
- try {
304
- const subFiles = fs.readdirSync(destPath);
305
- for (const f of subFiles) {
306
- if (f.endsWith('.sh')) {
307
- fs.chmodSync(path.join(destPath, f), '755');
308
- }
309
- }
310
- } catch (e) {}
311
- } else {
312
- // Core scripts: always overwrite. Others: skip if exists (unless --force)
313
- const isCore = coreScripts.has(entry.name);
314
- if (isCore || !fileExists(destPath) || isForce) {
315
- copyFile(srcPath, destPath);
316
- try { fs.chmodSync(destPath, '755'); } catch (e) {}
269
+ copyDir(scriptsSrc, scriptsDest);
270
+
271
+ // chmod +x for all .sh files (recursive)
272
+ function chmodRecursive(dir) {
273
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
274
+ for (const entry of entries) {
275
+ const full = path.join(dir, entry.name);
276
+ if (entry.isDirectory()) {
277
+ chmodRecursive(full);
278
+ } else if (entry.name.endsWith('.sh')) {
279
+ try { fs.chmodSync(full, '755'); } catch (e) {}
317
280
  }
318
281
  }
319
282
  }
283
+ chmodRecursive(scriptsDest);
320
284
  }
321
285
 
322
286
  log('Scripts installation complete');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@walwal-harness/cli",
3
- "version": "4.0.0-beta.4",
3
+ "version": "4.0.0-beta.6",
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,19 +1,19 @@
1
1
  #!/bin/bash
2
- # harness-studio-setup.sh — Claude 세션 내부에서 실행하여 3-column 레이아웃 자동 구축
3
- #
4
- # Claude가 이미 실행 중인 tmux pane에서 호출됨.
5
- # 현재 pane(Claude가 있는 곳)을 Left로 유지하고,
6
- # Center(Dashboard + History)와 Right(Monitor)를 split으로 생성.
2
+ # harness-studio-setup.sh — Claude 세션에서 3-column 레이아웃 자동 구축
7
3
  #
8
4
  # ┌──────────────┬──────────────┬──────────────┐
9
5
  # │ │ Dashboard │ │
10
- # │ 여기서 │ (v4 queue) │ Team Monitor│
11
- # │ Claude 실행중├──────────────┤ (lifecycle) │
6
+ # │ Claude │ (v4 queue) │ Team Monitor│
7
+ # │ (Lead) ├──────────────┤ (lifecycle) │
12
8
  # │ │ Command │ │
13
9
  # │ │ History │ │
14
10
  # └──────────────┴──────────────┴──────────────┘
15
11
  #
16
- # Usage (Claude가 bash 도구로 호출):
12
+ # 가지 상황을 모두 처리:
13
+ # A) tmux 안에서 실행 → 현재 pane을 split하여 레이아웃 구축
14
+ # B) tmux 밖에서 실행 → 새 tmux 세션 생성, Claude를 좌측 pane에서 재실행
15
+ #
16
+ # Usage:
17
17
  # bash scripts/harness-studio-setup.sh [project-root]
18
18
  #
19
19
  # 이미 구축됐으면 skip (멱등성 보장)
@@ -21,6 +21,7 @@
21
21
  set -euo pipefail
22
22
 
23
23
  SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
24
+ SESSION_NAME="harness-studio"
24
25
 
25
26
  PROJECT_ROOT="${1:-}"
26
27
  if [ -z "$PROJECT_ROOT" ]; then
@@ -36,53 +37,107 @@ if [ -z "$PROJECT_ROOT" ] || [ ! -d "$PROJECT_ROOT/.harness" ]; then
36
37
  exit 1
37
38
  fi
38
39
 
39
- # ── tmux 내부인지 확인 ──
40
- if [ -z "${TMUX:-}" ]; then
41
- echo "[studio] Not inside tmux. Layout requires tmux."
42
- echo "[studio] Start with: tmux new-session -s harness"
43
- exit 1
40
+ # ── Resolve Claude command ──
41
+ CLAUDE_CMD="claude --dangerously-skip-permissions"
42
+ if [ -f "$PROJECT_ROOT/.harness/handoff.json" ]; then
43
+ _model=$(jq -r '.model // empty' "$PROJECT_ROOT/.harness/handoff.json" 2>/dev/null)
44
+ if [ -n "$_model" ] && [ "$_model" != "null" ]; then
45
+ CLAUDE_CMD="$CLAUDE_CMD --model $_model"
46
+ fi
47
+ fi
48
+
49
+ # ══════════════════════════════════════════
50
+ # Case A: 이미 tmux 안에 있음 → pane split
51
+ # ══════════════════════════════════════════
52
+ if [ -n "${TMUX:-}" ]; then
53
+ # 멱등성: 이미 pane이 3개 이상이면 skip
54
+ PANE_COUNT=$(tmux list-panes | wc -l | tr -d ' ')
55
+ if [ "$PANE_COUNT" -ge 3 ]; then
56
+ echo "[studio] Layout already set up ($PANE_COUNT panes). Skipping."
57
+ exit 0
58
+ fi
59
+
60
+ PANE_CLAUDE=$(tmux display-message -p '#{pane_id}')
61
+ echo "[studio] Setting up 3-column layout (in-place split)..."
62
+
63
+ # Left 35% | Right 65%
64
+ PANE_MID=$(tmux split-window -h -p 65 -t "$PANE_CLAUDE" -c "$PROJECT_ROOT" \
65
+ -P -F '#{pane_id}' \
66
+ "bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-dashboard-v4.sh\" \"${PROJECT_ROOT}\"'")
67
+
68
+ # Middle 45% | Right 55%
69
+ PANE_RIGHT=$(tmux split-window -h -p 55 -t "$PANE_MID" -c "$PROJECT_ROOT" \
70
+ -P -F '#{pane_id}' \
71
+ "bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-monitor.sh\" \"${PROJECT_ROOT}\"'")
72
+
73
+ # Dashboard top 45% | History bottom 55%
74
+ PANE_HISTORY=$(tmux split-window -v -p 55 -t "$PANE_MID" -c "$PROJECT_ROOT" \
75
+ -P -F '#{pane_id}' \
76
+ "bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-prompt-history.sh\" \"${PROJECT_ROOT}\"'")
77
+
78
+ # Pane titles
79
+ tmux select-pane -t "$PANE_CLAUDE" -T "Lead (Claude)"
80
+ tmux select-pane -t "$PANE_MID" -T "Dashboard"
81
+ tmux select-pane -t "$PANE_HISTORY" -T "Command History"
82
+ tmux select-pane -t "$PANE_RIGHT" -T "Team Monitor"
83
+
84
+ tmux set-option pane-border-status top 2>/dev/null || true
85
+ tmux set-option pane-border-format " #{pane_title} " 2>/dev/null || true
86
+
87
+ # 포커스를 Claude pane으로 복귀
88
+ tmux select-pane -t "$PANE_CLAUDE"
89
+
90
+ echo "[studio] Layout ready (in-place)."
91
+ exit 0
44
92
  fi
45
93
 
46
- # ── 멱등성: 이미 pane이 3개 이상이면 skip ──
47
- PANE_COUNT=$(tmux list-panes | wc -l | tr -d ' ')
48
- if [ "$PANE_COUNT" -ge 3 ]; then
49
- echo "[studio] Layout already set up ($PANE_COUNT panes). Skipping."
94
+ # ══════════════════════════════════════════
95
+ # Case B: tmux 밖에 있음 세션 생성
96
+ # ══════════════════════════════════════════
97
+
98
+ # 이미 세션이 있으면 attach만
99
+ if tmux has-session -t "$SESSION_NAME" 2>/dev/null; then
100
+ echo "[studio] Session '$SESSION_NAME' already exists. Attaching..."
101
+ echo "[studio] ATTACH_TMUX=$SESSION_NAME"
50
102
  exit 0
51
103
  fi
52
104
 
53
- # ── 현재 pane = Claude (Left) ──
54
- PANE_CLAUDE=$(tmux display-message -p '#{pane_id}')
105
+ echo "[studio] Creating new tmux session with 3-column layout..."
55
106
 
56
- echo "[studio] Setting up 3-column layout..."
107
+ # 1. 세션 Left pane (Claude 실행)
108
+ PANE_MAIN=$(tmux new-session -d -s "$SESSION_NAME" -c "$PROJECT_ROOT" -x 220 -y 55 \
109
+ -P -F '#{pane_id}')
57
110
 
58
- # ── Split: Left 35% | Right 65% ──
59
- PANE_MID=$(tmux split-window -h -p 65 -t "$PANE_CLAUDE" -c "$PROJECT_ROOT" \
111
+ # 2. Left 35% | Right 65%
112
+ PANE_MID=$(tmux split-window -h -p 65 -t "$PANE_MAIN" -c "$PROJECT_ROOT" \
60
113
  -P -F '#{pane_id}' \
61
114
  "bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-dashboard-v4.sh\" \"${PROJECT_ROOT}\"'")
62
115
 
63
- # ── Split right: Middle 45% | Right 55% ──
116
+ # 3. Middle 45% | Right 55%
64
117
  PANE_RIGHT=$(tmux split-window -h -p 55 -t "$PANE_MID" -c "$PROJECT_ROOT" \
65
118
  -P -F '#{pane_id}' \
66
119
  "bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-monitor.sh\" \"${PROJECT_ROOT}\"'")
67
120
 
68
- # ── Split middle vertically: Dashboard (top 45%) | History (bottom 55%) ──
121
+ # 4. Dashboard top 45% | History bottom 55%
69
122
  PANE_HISTORY=$(tmux split-window -v -p 55 -t "$PANE_MID" -c "$PROJECT_ROOT" \
70
123
  -P -F '#{pane_id}' \
71
124
  "bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-prompt-history.sh\" \"${PROJECT_ROOT}\"'")
72
125
 
73
- # ── Pane titles ──
74
- tmux select-pane -t "$PANE_CLAUDE" -T "Lead (Claude)"
126
+ # 5. Left pane에서 Claude 자동 실행
127
+ tmux send-keys -t "$PANE_MAIN" "unset npm_config_prefix 2>/dev/null" Enter
128
+ tmux send-keys -t "$PANE_MAIN" "clear && $CLAUDE_CMD" Enter
129
+
130
+ # Pane titles
131
+ tmux select-pane -t "$PANE_MAIN" -T "Lead (Claude)"
75
132
  tmux select-pane -t "$PANE_MID" -T "Dashboard"
76
133
  tmux select-pane -t "$PANE_HISTORY" -T "Command History"
77
134
  tmux select-pane -t "$PANE_RIGHT" -T "Team Monitor"
78
135
 
79
- tmux set-option pane-border-status top 2>/dev/null || true
80
- tmux set-option pane-border-format " #{pane_title} " 2>/dev/null || true
136
+ tmux set-option -t "$SESSION_NAME" pane-border-status top 2>/dev/null || true
137
+ tmux set-option -t "$SESSION_NAME" pane-border-format " #{pane_title} " 2>/dev/null || true
81
138
 
82
- # ── 포커스를 Claude pane으로 돌림 ──
83
- tmux select-pane -t "$PANE_CLAUDE"
139
+ # Focus on Claude pane
140
+ tmux select-pane -t "$PANE_MAIN"
84
141
 
85
- echo "[studio] Layout ready."
86
- echo "[studio] Left : Lead (Claude) — you are here"
87
- echo "[studio] Center : Dashboard + Command History"
88
- echo "[studio] Right : Team Monitor"
142
+ echo "[studio] Session '$SESSION_NAME' created."
143
+ echo "[studio] ATTACH_TMUX=$SESSION_NAME"
@@ -8,14 +8,28 @@ disable-model-invocation: false
8
8
 
9
9
  ## Step 0: Studio 레이아웃 자동 구축
10
10
 
11
- tmux 내부에서 실행 중이면, 3-column 레이아웃을 자동 구축합니다:
11
+ 3-column 대시보드 레이아웃을 자동 구축합니다:
12
12
 
13
13
  ```bash
14
14
  bash scripts/harness-studio-setup.sh .
15
15
  ```
16
16
 
17
- > 이미 구축됐으면 자동 skip (멱등).
18
- > tmux 밖이면 경고만 출력하고 계속 진행 (필수는 아님).
17
+ 스크립트 출력을 확인합니다:
18
+
19
+ - **`Layout ready`** → 현재 터미널에 split 완료. 바로 Step 1로.
20
+ - **`ATTACH_TMUX=harness-studio`** → 새 tmux 세션이 생성됨 (tmux 밖에서 실행한 경우).
21
+ 사용자에게 아래 안내를 출력하고 **STOP**합니다:
22
+
23
+ ```
24
+ Studio 레이아웃이 준비되었습니다!
25
+ 다른 터미널에서 아래 명령을 실행하세요:
26
+
27
+ tmux attach -t harness-studio
28
+
29
+ 새 창에서 Claude가 자동 실행됩니다. 거기서 "팀 가동"을 입력하면 Teams가 시작됩니다.
30
+ ```
31
+
32
+ - **`already set up`** → 이미 구축됨. 바로 Step 1로.
19
33
 
20
34
  ## Step 1: Queue 초기화
21
35