@walwal-harness/cli 5.0.5 → 5.0.7

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.
@@ -242,16 +242,31 @@ logev fail "{FEATURE_ID} FAIL #{ATTEMPT} — {사유요약}"
242
242
  NEW_FEATURE=$(bash "$HARNESS_ROOT/scripts/harness-queue-manager.sh" dequeue {N} "$HARNESS_ROOT")
243
243
  logev gen-start "$NEW_FEATURE auto-acquired"
244
244
  ```
245
- 3. `ready=0`이면 → "Team-{N} 대기 중 — 모든 할당 피처 완료" 반환
245
+ 3. `ready=0`이면 → next-sprint 시도:
246
+ ```bash
247
+ NEXT=$(bash "$HARNESS_ROOT/scripts/harness-queue-manager.sh" next-sprint "$HARNESS_ROOT" 2>&1)
248
+ if echo "$NEXT" | grep -q "Advancing"; then
249
+ NEW_FEATURE=$(bash "$HARNESS_ROOT/scripts/harness-queue-manager.sh" dequeue {N} "$HARNESS_ROOT")
250
+ logev gen-start "$NEW_FEATURE auto-acquired (next sprint)"
251
+ else
252
+ logev result "Team-{N} 완료 — 모든 Sprint 처리됨"
253
+ fi
254
+ ```
246
255
  ```
247
256
 
248
- ### Step 5: 결과 수집 및 다음 라운드
257
+ ### Step 5: 결과 수집 및 다음 라운드 (자동 Sprint 전환 포함)
249
258
 
250
259
  모든 Team Agent가 완료되면:
251
260
  1. 각 Agent 반환 메시지에서 PASS/FAIL 확인
252
261
  2. Queue 상태 재확인: `bash scripts/harness-queue-manager.sh status .`
253
262
  3. ready 큐에 새로 unblock된 feature가 있으면 → **Step 3으로 돌아가서** 추가 팀 생성
254
- 4. ready=0, in_progress=0이면 → 최종 결과 보고
263
+ 4. ready=0, in_progress=0이면 → **자동 Sprint 전환 시도**:
264
+ ```bash
265
+ bash scripts/harness-queue-manager.sh next-sprint .
266
+ ```
267
+ - `"Advancing to Sprint N"` → 새 Sprint 피처가 queue에 로드됨 → **Step 3으로 돌아가서** 팀 생성
268
+ - `"ALL SPRINTS COMPLETE"` → 전체 프로젝트 완료 보고
269
+ - `"Cannot advance: N failed"` → 실패 피처 해결 필요, 사용자 개입 요청
255
270
 
256
271
  ## 핵심 원칙
257
272
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@walwal-harness/cli",
3
- "version": "5.0.5",
3
+ "version": "5.0.7",
4
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"
@@ -65,14 +65,18 @@ release_queue_lock() {
65
65
  # ── Concurrency from config ──
66
66
  CONCURRENCY=3
67
67
  if [ -f "$CONFIG" ]; then
68
- _c=$(jq -r '.flow.parallel.concurrency // 3' "$CONFIG" 2>/dev/null)
68
+ _c=$(jq -r '.flow.team.concurrency // .flow.parallel.concurrency // 3' "$CONFIG" 2>/dev/null)
69
69
  if [ "$_c" -gt 0 ] 2>/dev/null; then CONCURRENCY=$_c; fi
70
70
  fi
71
71
 
72
72
  # ══════════════════════════════════════════
73
73
  # init — Build queue from feature-list.json
74
+ # Usage: init [sprint_number]
75
+ # sprint_number: optional, filter features by sprint (default: all)
74
76
  # ══════════════════════════════════════════
75
77
  cmd_init() {
78
+ local sprint_filter="${1:-all}"
79
+
76
80
  if [ ! -f "$FEATURES" ]; then
77
81
  echo "[queue] feature-list.json not found."
78
82
  exit 1
@@ -80,7 +84,7 @@ cmd_init() {
80
84
 
81
85
  # Build dependency graph and topological sort
82
86
  # Output: feature-queue.json with ready (no deps) and blocked (has deps)
83
- jq --argjson concurrency "$CONCURRENCY" '
87
+ jq --argjson concurrency "$CONCURRENCY" --arg sprint "$sprint_filter" '
84
88
  # Build passed set (features already passed by evaluator)
85
89
  def passed_set:
86
90
  [.features[] | select(
@@ -88,9 +92,15 @@ cmd_init() {
88
92
  ((.passes // []) | any(. == "evaluator-functional"))
89
93
  ) | .id] ;
90
94
 
95
+ # Filter features by sprint if specified
96
+ def target_features:
97
+ if $sprint == "all" then .features
98
+ else [.features[] | select((.sprint // 1) == ($sprint | tonumber))]
99
+ end ;
100
+
91
101
  # Separate ready vs blocked
92
- def classify(passed):
93
- reduce .features[] as $f (
102
+ def classify(features; passed):
103
+ reduce features[] as $f (
94
104
  { ready: [], blocked: {} };
95
105
  ($f.depends_on // []) as $deps |
96
106
  if ($f.id | IN(passed[])) then . # already passed, skip
@@ -104,10 +114,12 @@ cmd_init() {
104
114
  ) ;
105
115
 
106
116
  passed_set as $passed |
107
- classify($passed) as $classified |
117
+ target_features as $targets |
118
+ classify($targets; $passed) as $classified |
108
119
  {
109
- version: "4.0",
120
+ version: "5.0",
110
121
  concurrency: $concurrency,
122
+ current_sprint: (if $sprint == "all" then null else ($sprint | tonumber) end),
111
123
  queue: {
112
124
  ready: $classified.ready,
113
125
  blocked: $classified.blocked,
@@ -129,10 +141,69 @@ cmd_init() {
129
141
  blocked_count=$(jq '.queue.blocked | length' "$QUEUE")
130
142
  passed_count=$(jq '.queue.passed | length' "$QUEUE")
131
143
 
132
- echo "[queue] Initialized: $ready_count ready, $blocked_count blocked, $passed_count already passed"
144
+ echo "[queue] Initialized (sprint=$sprint_filter): $ready_count ready, $blocked_count blocked, $passed_count already passed"
133
145
  echo "[queue] Concurrency: $CONCURRENCY teams"
134
146
  }
135
147
 
148
+ # ══════════════════════════════════════════
149
+ # next-sprint — Auto-advance to next sprint
150
+ # Checks if current sprint is complete, loads next sprint features
151
+ # ══════════════════════════════════════════
152
+ cmd_next_sprint() {
153
+ if [ ! -f "$QUEUE" ]; then echo "[queue] Run 'init' first."; exit 1; fi
154
+ if [ ! -f "$FEATURES" ]; then echo "[queue] feature-list.json not found."; exit 1; fi
155
+
156
+ acquire_queue_lock
157
+
158
+ local ready in_prog failed
159
+ ready=$(jq '.queue.ready | length' "$QUEUE" 2>/dev/null || echo 0)
160
+ in_prog=$(jq '.queue.in_progress | length' "$QUEUE" 2>/dev/null || echo 0)
161
+ failed=$(jq '.queue.failed | length' "$QUEUE" 2>/dev/null || echo 0)
162
+
163
+ # Current sprint still has work
164
+ if [ "$ready" -gt 0 ] || [ "$in_prog" -gt 0 ]; then
165
+ release_queue_lock
166
+ echo "[queue] Current sprint still active: $ready ready, $in_prog in progress"
167
+ return 1
168
+ fi
169
+
170
+ # Failed features block advancement
171
+ if [ "$failed" -gt 0 ]; then
172
+ release_queue_lock
173
+ echo "[queue] Cannot advance: $failed failed features. Requeue or fix them first."
174
+ return 1
175
+ fi
176
+
177
+ # Find current sprint number
178
+ local current_sprint
179
+ current_sprint=$(jq -r '.current_sprint // 0' "$QUEUE" 2>/dev/null)
180
+ if [ "$current_sprint" = "null" ] || [ "$current_sprint" = "0" ]; then
181
+ # Detect from passed features
182
+ current_sprint=$(jq -r --slurpfile q "$QUEUE" '
183
+ [.features[] | select(.id as $fid | $q[0].queue.passed | index($fid)) | .sprint // 1] | max // 1
184
+ ' "$FEATURES" 2>/dev/null)
185
+ fi
186
+
187
+ local next_sprint=$((current_sprint + 1))
188
+
189
+ # Check if next sprint features exist
190
+ local next_count
191
+ next_count=$(jq --arg s "$next_sprint" '[.features[] | select((.sprint // 1) == ($s | tonumber))] | length' "$FEATURES" 2>/dev/null)
192
+
193
+ if [ "$next_count" -eq 0 ]; then
194
+ release_queue_lock
195
+ echo "[queue] ALL SPRINTS COMPLETE. No Sprint $next_sprint features found."
196
+ echo "[queue] Total passed: $(jq '.queue.passed | length' "$QUEUE")"
197
+ return 0
198
+ fi
199
+
200
+ release_queue_lock
201
+
202
+ # Re-init with next sprint (preserves passed from previous sprints)
203
+ echo "[queue] Sprint $current_sprint complete! Advancing to Sprint $next_sprint ($next_count features)"
204
+ cmd_init "$next_sprint"
205
+ }
206
+
136
207
  # ══════════════════════════════════════════
137
208
  # dequeue — Assign next ready feature to team
138
209
  # ══════════════════════════════════════════
@@ -355,16 +426,17 @@ cmd_recover() {
355
426
 
356
427
  # ── Dispatch ──
357
428
  case "$CMD" in
358
- init) cmd_init ;;
359
- dequeue) cmd_dequeue "$@" ;;
360
- pass) cmd_pass "$@" ;;
361
- fail) cmd_fail "$@" ;;
362
- requeue) cmd_requeue "$@" ;;
363
- update_phase) cmd_update_phase "$@" ;;
364
- recover) cmd_recover ;;
365
- status) cmd_status ;;
429
+ init) cmd_init "$@" ;;
430
+ dequeue) cmd_dequeue "$@" ;;
431
+ pass) cmd_pass "$@" ;;
432
+ fail) cmd_fail "$@" ;;
433
+ requeue) cmd_requeue "$@" ;;
434
+ update_phase) cmd_update_phase "$@" ;;
435
+ recover) cmd_recover ;;
436
+ next-sprint) cmd_next_sprint ;;
437
+ status) cmd_status ;;
366
438
  *)
367
- echo "Usage: harness-queue-manager.sh <init|dequeue|pass|fail|requeue|recover|update_phase|status> [args]"
439
+ echo "Usage: harness-queue-manager.sh <init|dequeue|pass|fail|requeue|recover|next-sprint|update_phase|status> [args]"
368
440
  exit 1
369
441
  ;;
370
442
  esac
@@ -3,13 +3,13 @@
3
3
  #
4
4
  # Team Mode (--team):
5
5
  # ┌──────────────┬──────────────┬──────────────┐
6
- # │ Prompt │ Dashboard │ TEAM 1 │
7
- # │ History │ (queue + │ Gen | Eval │
8
- # │ │ status) ├──────────────┤
9
- # ├──────────────┤ │ TEAM 2 │
10
- # │ Controller │ │ Gen | Eval │
11
- # │ (Claude / │ ├──────────────┤
12
- # │ Codex) │ │ TEAM 3 │
6
+ # │ │ │ TEAM 1 │
7
+ # │ Prompt │ Dashboard │ Gen | Eval │
8
+ # │ History │ (queue + ├──────────────┤
9
+ # │ │ status + │ TEAM 2 │
10
+ # │ │ archive) │ Gen | Eval │
11
+ # │ │ ├──────────────┤
12
+ # │ │ │ TEAM 3 │
13
13
  # └──────────────┴──────────────┴──────────────┘
14
14
  #
15
15
  # Rendering strategy:
@@ -124,12 +124,6 @@ launch_iterm2_team() {
124
124
  end tell
125
125
  end tell
126
126
  end tell
127
-
128
- -- Split down → Controller (bottom-left)
129
- set ctrlPane to (split horizontally with default profile)
130
- tell ctrlPane
131
- write text "cd '${PROJECT_ROOT}' && clear"
132
- end tell
133
127
  end tell
134
128
  end tell
135
129
  end tell
@@ -185,19 +179,16 @@ launch_tmux_team() {
185
179
  "bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-monitor.sh\" \"${PROJECT_ROOT}\" --team 2'")
186
180
  PANE_T3=$(tmux split-window -v -p 50 -t "$PANE_T2" -c "$PROJECT_ROOT" -P -F '#{pane_id}' \
187
181
  "bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-monitor.sh\" \"${PROJECT_ROOT}\" --team 3'")
188
- PANE_CTRL=$(tmux split-window -v -p 55 -t "$PANE_LEFT" -c "$PROJECT_ROOT" -P -F '#{pane_id}')
189
182
 
190
183
  tmux send-keys -t "$PANE_LEFT" "bash \"${SCRIPT_DIR}/harness-prompt-history.sh\" \"${PROJECT_ROOT}\"" Enter
191
184
  tmux send-keys -t "$PANE_MID" "bash \"${SCRIPT_DIR}/harness-dashboard.sh\" \"${PROJECT_ROOT}\"" Enter
192
- tmux send-keys -t "$PANE_CTRL" "clear" Enter
193
185
 
194
186
  tmux select-pane -t "$PANE_LEFT" -T "Prompt History"
195
- tmux select-pane -t "$PANE_CTRL" -T "Controller"
196
187
  tmux select-pane -t "$PANE_MID" -T "Dashboard"
197
188
  tmux select-pane -t "$PANE_T1" -T "TEAM 1"
198
189
  tmux select-pane -t "$PANE_T2" -T "TEAM 2"
199
190
  tmux select-pane -t "$PANE_T3" -T "TEAM 3"
200
- tmux select-pane -t "$PANE_CTRL"
191
+ tmux select-pane -t "$PANE_LEFT"
201
192
 
202
193
  tmux set-option -t "$SESSION_NAME" pane-border-status top 2>/dev/null || true
203
194
  tmux set-option -t "$SESSION_NAME" pane-border-format " #{pane_title} " 2>/dev/null || true