@walwal-harness/cli 6.0.5 → 6.1.0

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.
Files changed (43) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/commands/harness-solo.md +20 -20
  3. package/commands/harness-team.md +1 -1
  4. package/package.json +1 -1
  5. package/scripts/conductor-tick.sh +382 -0
  6. package/scripts/harness-archive.sh +123 -0
  7. package/scripts/harness-dashboard-up.sh +72 -0
  8. package/scripts/harness-dashboard.sh +509 -0
  9. package/scripts/harness-goal-init.sh +72 -0
  10. package/scripts/harness-goal-show.sh +37 -0
  11. package/scripts/harness-gotcha-memory.sh +348 -0
  12. package/scripts/harness-gotcha-register.sh +393 -0
  13. package/scripts/harness-meeting-doc.sh +153 -0
  14. package/scripts/harness-monitor.sh +398 -0
  15. package/scripts/harness-next.sh +577 -0
  16. package/scripts/harness-progress-set.sh +41 -0
  17. package/scripts/harness-prompt-history.sh +164 -0
  18. package/scripts/harness-queue-manager.sh +694 -0
  19. package/scripts/harness-session-start.sh +140 -0
  20. package/scripts/harness-statusline.sh +139 -0
  21. package/scripts/harness-task-session.sh +48 -0
  22. package/scripts/harness-tmux.sh +372 -0
  23. package/scripts/harness-user-prompt-submit.sh +123 -0
  24. package/scripts/init-agents-md.sh +337 -0
  25. package/scripts/init-ref-docs.sh +262 -0
  26. package/scripts/lib/harness-audit.sh +113 -0
  27. package/scripts/lib/harness-guardrail.sh +148 -0
  28. package/scripts/lib/harness-keywait.sh +57 -0
  29. package/scripts/lib/harness-render-progress.sh +346 -0
  30. package/scripts/scan-project.sh +367 -0
  31. package/skills/brainstorming/SKILL.md +4 -4
  32. package/skills/conductor/SKILL.md +12 -16
  33. package/skills/cqo/SKILL.md +7 -0
  34. package/skills/cto/SKILL.md +7 -0
  35. package/skills/dispatcher/SKILL.md +17 -28
  36. package/skills/evaluator-code-quality/SKILL.md +1 -1
  37. package/skills/evaluator-functional/SKILL.md +1 -1
  38. package/skills/evaluator-visual/SKILL.md +1 -1
  39. package/skills/generator-backend/SKILL.md +1 -1
  40. package/skills/generator-frontend/SKILL.md +1 -1
  41. package/skills/meeting-manager/SKILL.md +33 -1
  42. package/skills/planner/SKILL.md +23 -9
  43. package/skills/service-ops/SKILL.md +19 -4
@@ -0,0 +1,372 @@
1
+ #!/bin/bash
2
+ # harness-tmux.sh — Unified Harness Studio layout
3
+ #
4
+ # Team Mode (--team):
5
+ # ┌──────────────┬──────────────┬──────────────┐
6
+ # │ │ │ TEAM 1 │
7
+ # │ Dashboard │ │ │
8
+ # │ (queue + │ Gotcha & ├──────────────┤
9
+ # │ status + │ Memory │ TEAM 2 │
10
+ # │ features) │ │ │
11
+ # ├──────────────┤ ├──────────────┤
12
+ # │ Archive │ │ TEAM 3 │
13
+ # │ Prompt │ │ │
14
+ # └──────────────┴──────────────┴──────────────┘
15
+ # (Archive Prompt는 별도 패널 — Dashboard 모니터링 영역 보호)
16
+ #
17
+ # Rendering strategy:
18
+ # 1. iTerm2 detected → native split panes (no tmux needed)
19
+ # 2. tmux available → tmux session
20
+ # 3. Fallback → open new terminal windows
21
+ #
22
+ # Usage:
23
+ # bash scripts/harness-tmux.sh [project-root] --team
24
+ # bash scripts/harness-tmux.sh [project-root] --solo
25
+ # bash scripts/harness-tmux.sh [project-root] --team --force-tmux # skip iTerm2, use tmux
26
+ # bash scripts/harness-tmux.sh --kill
27
+
28
+ set -euo pipefail
29
+
30
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
31
+ SESSION_NAME="harness-studio"
32
+
33
+ PROJECT_ROOT=""
34
+ MODE=""
35
+ DETACH=false
36
+ FORCE_TMUX=false
37
+
38
+ for arg in "$@"; do
39
+ case "$arg" in
40
+ --team) MODE="team" ;;
41
+ --solo) MODE="solo" ;;
42
+ --detach) DETACH=true ;;
43
+ --force-tmux) FORCE_TMUX=true ;;
44
+ --kill)
45
+ tmux kill-session -t "$SESSION_NAME" 2>/dev/null && echo "tmux session killed." || true
46
+ # Also try to close iTerm2 studio tab if exists
47
+ if [ "${TERM_PROGRAM:-}" = "iTerm.app" ]; then
48
+ osascript -e '
49
+ tell application "iTerm2"
50
+ repeat with w in windows
51
+ repeat with t in tabs of w
52
+ if name of current session of t contains "harness-studio" then
53
+ close t
54
+ end if
55
+ end repeat
56
+ end repeat
57
+ end tell
58
+ ' 2>/dev/null || true
59
+ fi
60
+ echo "Killed."
61
+ exit 0
62
+ ;;
63
+ *)
64
+ if [ -d "$arg" ]; then PROJECT_ROOT="$arg"; fi
65
+ ;;
66
+ esac
67
+ done
68
+
69
+ if [ -z "$PROJECT_ROOT" ]; then
70
+ dir="$(pwd)"
71
+ while [ "$dir" != "/" ]; do
72
+ if [ -d "$dir/.harness" ]; then PROJECT_ROOT="$dir"; break; fi
73
+ dir="$(dirname "$dir")"
74
+ done
75
+ fi
76
+
77
+ if [ -z "$PROJECT_ROOT" ] || [ ! -d "$PROJECT_ROOT/.harness" ]; then
78
+ echo "Error: .harness/ not found."
79
+ exit 1
80
+ fi
81
+
82
+ # Auto-detect mode from progress.json if not specified.
83
+ # Self-heal: feature-queue.json 에 in_progress 가 있으면 mode 와 무관하게 team 으로 간주.
84
+ # (SessionStart 훅 외 경로에서 dashboard/tmux 가 stale solo 를 표시하던 버그)
85
+ if [ -z "$MODE" ]; then
86
+ if command -v jq &>/dev/null && [ -f "$PROJECT_ROOT/.harness/progress.json" ]; then
87
+ MODE=$(jq -r '.mode // "solo"' "$PROJECT_ROOT/.harness/progress.json" 2>/dev/null)
88
+ QUEUE_FILE="$PROJECT_ROOT/.harness/actions/feature-queue.json"
89
+ if [ "$MODE" != "team" ] && [ -f "$QUEUE_FILE" ]; then
90
+ ACTIVE_COUNT=$(jq -r '(.queue.in_progress | length) // 0' "$QUEUE_FILE" 2>/dev/null || echo 0)
91
+ if [ "${ACTIVE_COUNT:-0}" -gt 0 ]; then
92
+ HEAL_TEAMS=$ACTIVE_COUNT
93
+ [ "$HEAL_TEAMS" -gt 3 ] && HEAL_TEAMS=3
94
+ bash "$SCRIPT_DIR/harness-progress-set.sh" "$PROJECT_ROOT" \
95
+ ".mode = \"team\" | .team_state.active_teams = ${HEAL_TEAMS} | .team_state.paused_at = null" \
96
+ 2>/dev/null
97
+ MODE="team"
98
+ echo "# tmux: mode auto-healed to 'team' (queue had ${ACTIVE_COUNT} in_progress)"
99
+ if [ -f "$PROJECT_ROOT/.harness/progress.log" ]; then
100
+ echo "$(date +'%Y-%m-%d %H:%M') | system | heal | mode | tmux reset solo→team (queue had ${ACTIVE_COUNT} in_progress)" >> "$PROJECT_ROOT/.harness/progress.log"
101
+ fi
102
+ fi
103
+ fi
104
+ fi
105
+ MODE="${MODE:-solo}"
106
+ fi
107
+
108
+ echo "Project: $PROJECT_ROOT"
109
+ echo "Mode: $MODE"
110
+
111
+ # ══════════════════════════════════════════
112
+ # Strategy 1: iTerm2 native split panes
113
+ # ══════════════════════════════════════════
114
+ launch_iterm2_team() {
115
+ osascript <<APPLESCRIPT
116
+ tell application "iTerm2"
117
+ activate
118
+
119
+ -- Create new window with first session (Prompt History)
120
+ set studioWindow to (create window with default profile)
121
+
122
+ tell studioWindow
123
+ tell current session of current tab
124
+ set name to "harness-studio"
125
+ write text "cd '${PROJECT_ROOT}' && bash '${SCRIPT_DIR}/harness-dashboard.sh' '${PROJECT_ROOT}'"
126
+
127
+ -- Split down (bottom of Dashboard column) → Archive Prompt
128
+ set archivePane to (split horizontally with default profile)
129
+ tell archivePane
130
+ write text "cd '${PROJECT_ROOT}' && bash '${SCRIPT_DIR}/harness-prompt-history.sh' '${PROJECT_ROOT}'"
131
+ end tell
132
+
133
+ -- Split right → Gotchas (top of Rules column, full-height initially)
134
+ set gotchaPane to (split vertically with default profile)
135
+ tell gotchaPane
136
+ write text "cd '${PROJECT_ROOT}' && bash '${SCRIPT_DIR}/harness-gotcha-memory.sh' '${PROJECT_ROOT}' --mode gotcha"
137
+
138
+ -- IMPORTANT: Team 컬럼을 먼저 분리 (전체 높이 유지). 그 후에 gotchaPane 을 horizontal 분할.
139
+ set t1Pane to (split vertically with default profile)
140
+ tell t1Pane
141
+ write text "cd '${PROJECT_ROOT}' && bash '${SCRIPT_DIR}/harness-monitor.sh' '${PROJECT_ROOT}' --team 1"
142
+
143
+ set t2Pane to (split horizontally with default profile)
144
+ tell t2Pane
145
+ write text "cd '${PROJECT_ROOT}' && bash '${SCRIPT_DIR}/harness-monitor.sh' '${PROJECT_ROOT}' --team 2"
146
+
147
+ set t3Pane to (split horizontally with default profile)
148
+ tell t3Pane
149
+ write text "cd '${PROJECT_ROOT}' && bash '${SCRIPT_DIR}/harness-monitor.sh' '${PROJECT_ROOT}' --team 3"
150
+ end tell
151
+ end tell
152
+ end tell
153
+
154
+ -- Team 컬럼 분리 후, gotchaPane (Rules 컬럼) 을 horizontal 두 번 split → Conventions / Memory
155
+ set convPane to (split horizontally with default profile)
156
+ tell convPane
157
+ write text "cd '${PROJECT_ROOT}' && bash '${SCRIPT_DIR}/harness-gotcha-memory.sh' '${PROJECT_ROOT}' --mode conventions"
158
+
159
+ set memPane to (split horizontally with default profile)
160
+ tell memPane
161
+ write text "cd '${PROJECT_ROOT}' && bash '${SCRIPT_DIR}/harness-gotcha-memory.sh' '${PROJECT_ROOT}' --mode memory"
162
+ end tell
163
+ end tell
164
+ end tell
165
+ end tell
166
+ end tell
167
+ end tell
168
+ APPLESCRIPT
169
+ }
170
+
171
+ launch_iterm2_solo() {
172
+ # Solo 레이아웃 (Team 모드와 동일 구조, 우측만 단일 Monitor 패널):
173
+ # Left col : Dashboard (top) + Prompt History (bottom)
174
+ # Middle : Gotcha · Memory · Conventions
175
+ # Right : Monitor (lifecycle stream, 단일 에이전트)
176
+ osascript <<APPLESCRIPT
177
+ tell application "iTerm2"
178
+ activate
179
+
180
+ set studioWindow to (create window with default profile)
181
+
182
+ tell studioWindow
183
+ tell current session of current tab
184
+ set name to "harness-studio"
185
+ write text "cd '${PROJECT_ROOT}' && bash '${SCRIPT_DIR}/harness-dashboard.sh' '${PROJECT_ROOT}'"
186
+
187
+ -- Split down (bottom of Dashboard column) → Prompt History
188
+ set archivePane to (split horizontally with default profile)
189
+ tell archivePane
190
+ write text "cd '${PROJECT_ROOT}' && bash '${SCRIPT_DIR}/harness-prompt-history.sh' '${PROJECT_ROOT}'"
191
+ end tell
192
+
193
+ -- Split right → Gotchas (top of Rules column, full-height initially)
194
+ set gotchaPane to (split vertically with default profile)
195
+ tell gotchaPane
196
+ write text "cd '${PROJECT_ROOT}' && bash '${SCRIPT_DIR}/harness-gotcha-memory.sh' '${PROJECT_ROOT}' --mode gotcha"
197
+
198
+ -- Monitor 컬럼을 먼저 분리 (전체 높이 유지)
199
+ set monPane to (split vertically with default profile)
200
+ tell monPane
201
+ write text "cd '${PROJECT_ROOT}' && bash '${SCRIPT_DIR}/harness-monitor.sh' '${PROJECT_ROOT}'"
202
+ end tell
203
+
204
+ -- Monitor 분리 후, gotchaPane (Rules 컬럼) 을 horizontal 두 번 split → Conventions / Memory
205
+ set convPane to (split horizontally with default profile)
206
+ tell convPane
207
+ write text "cd '${PROJECT_ROOT}' && bash '${SCRIPT_DIR}/harness-gotcha-memory.sh' '${PROJECT_ROOT}' --mode conventions"
208
+
209
+ set memPane to (split horizontally with default profile)
210
+ tell memPane
211
+ write text "cd '${PROJECT_ROOT}' && bash '${SCRIPT_DIR}/harness-gotcha-memory.sh' '${PROJECT_ROOT}' --mode memory"
212
+ end tell
213
+ end tell
214
+ end tell
215
+ end tell
216
+ end tell
217
+ end tell
218
+ APPLESCRIPT
219
+ }
220
+
221
+ # ══════════════════════════════════════════
222
+ # Strategy 2: tmux
223
+ # ══════════════════════════════════════════
224
+ launch_tmux_team() {
225
+ tmux kill-session -t "$SESSION_NAME" 2>/dev/null || true
226
+
227
+ PANE_DASH=$(tmux new-session -d -s "$SESSION_NAME" -c "$PROJECT_ROOT" -x 220 -y 55 -P -F '#{pane_id}')
228
+ PANE_GOTCHA=$(tmux split-window -h -p 70 -t "$PANE_DASH" -c "$PROJECT_ROOT" -P -F '#{pane_id}')
229
+ PANE_T1=$(tmux split-window -h -p 60 -t "$PANE_GOTCHA" -c "$PROJECT_ROOT" -P -F '#{pane_id}' \
230
+ "bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-monitor.sh\" \"${PROJECT_ROOT}\" --team 1'")
231
+ PANE_T2=$(tmux split-window -v -p 66 -t "$PANE_T1" -c "$PROJECT_ROOT" -P -F '#{pane_id}' \
232
+ "bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-monitor.sh\" \"${PROJECT_ROOT}\" --team 2'")
233
+ PANE_T3=$(tmux split-window -v -p 50 -t "$PANE_T2" -c "$PROJECT_ROOT" -P -F '#{pane_id}' \
234
+ "bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-monitor.sh\" \"${PROJECT_ROOT}\" --team 3'")
235
+ # Dashboard 열을 상(Dashboard)/하(Archive Prompt)로 분할 — archive가 길어져도 모니터링 유지
236
+ PANE_ARCHIVE=$(tmux split-window -v -p 35 -t "$PANE_DASH" -c "$PROJECT_ROOT" -P -F '#{pane_id}' \
237
+ "bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-prompt-history.sh\" \"${PROJECT_ROOT}\"'")
238
+
239
+ # Gotcha 패널을 3 개 sub-pane 으로 수직 분할: Gotchas / Conventions / Memory
240
+ # 각 pane 은 tmux copy-mode (prefix + [) 로 독립 스크롤 가능
241
+ PANE_CONV=$(tmux split-window -v -p 60 -t "$PANE_GOTCHA" -c "$PROJECT_ROOT" -P -F '#{pane_id}' \
242
+ "bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-gotcha-memory.sh\" \"${PROJECT_ROOT}\" --mode conventions'")
243
+ PANE_MEM=$(tmux split-window -v -p 50 -t "$PANE_CONV" -c "$PROJECT_ROOT" -P -F '#{pane_id}' \
244
+ "bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-gotcha-memory.sh\" \"${PROJECT_ROOT}\" --mode memory'")
245
+
246
+ tmux send-keys -t "$PANE_DASH" "bash \"${SCRIPT_DIR}/harness-dashboard.sh\" \"${PROJECT_ROOT}\"" Enter
247
+ tmux send-keys -t "$PANE_GOTCHA" "bash \"${SCRIPT_DIR}/harness-gotcha-memory.sh\" \"${PROJECT_ROOT}\" --mode gotcha" Enter
248
+
249
+ tmux select-pane -t "$PANE_DASH" -T "Dashboard"
250
+ tmux select-pane -t "$PANE_ARCHIVE" -T "Archive Prompt"
251
+ tmux select-pane -t "$PANE_GOTCHA" -T "Gotchas (scrollable: prefix + [ )"
252
+ tmux select-pane -t "$PANE_CONV" -T "Conventions (scrollable: prefix + [ )"
253
+ tmux select-pane -t "$PANE_MEM" -T "Shared Memory (scrollable: prefix + [ )"
254
+ tmux select-pane -t "$PANE_T1" -T "TEAM 1"
255
+ tmux select-pane -t "$PANE_T2" -T "TEAM 2"
256
+ tmux select-pane -t "$PANE_T3" -T "TEAM 3"
257
+ tmux select-pane -t "$PANE_DASH"
258
+
259
+ tmux set-option -t "$SESSION_NAME" pane-border-status top 2>/dev/null || true
260
+ tmux set-option -t "$SESSION_NAME" pane-border-format " #{pane_title} " 2>/dev/null || true
261
+ }
262
+
263
+ launch_tmux_solo() {
264
+ # Solo 레이아웃 (Team 모드와 동일 구조, 우측만 단일 Monitor 패널).
265
+ tmux kill-session -t "$SESSION_NAME" 2>/dev/null || true
266
+
267
+ PANE_DASH=$(tmux new-session -d -s "$SESSION_NAME" -c "$PROJECT_ROOT" -x 200 -y 50 -P -F '#{pane_id}')
268
+ PANE_GOTCHA=$(tmux split-window -h -p 70 -t "$PANE_DASH" -c "$PROJECT_ROOT" -P -F '#{pane_id}')
269
+ PANE_MONITOR=$(tmux split-window -h -p 50 -t "$PANE_GOTCHA" -c "$PROJECT_ROOT" -P -F '#{pane_id}' \
270
+ "bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-monitor.sh\" \"${PROJECT_ROOT}\"'")
271
+ PANE_HISTORY=$(tmux split-window -v -p 35 -t "$PANE_DASH" -c "$PROJECT_ROOT" -P -F '#{pane_id}' \
272
+ "bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-prompt-history.sh\" \"${PROJECT_ROOT}\"'")
273
+
274
+ # Gotcha 패널을 3 개 sub-pane 으로 수직 분할: Gotchas / Conventions / Memory
275
+ PANE_CONV=$(tmux split-window -v -p 60 -t "$PANE_GOTCHA" -c "$PROJECT_ROOT" -P -F '#{pane_id}' \
276
+ "bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-gotcha-memory.sh\" \"${PROJECT_ROOT}\" --mode conventions'")
277
+ PANE_MEM=$(tmux split-window -v -p 50 -t "$PANE_CONV" -c "$PROJECT_ROOT" -P -F '#{pane_id}' \
278
+ "bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-gotcha-memory.sh\" \"${PROJECT_ROOT}\" --mode memory'")
279
+
280
+ tmux send-keys -t "$PANE_DASH" "bash \"${SCRIPT_DIR}/harness-dashboard.sh\" \"${PROJECT_ROOT}\"" Enter
281
+ tmux send-keys -t "$PANE_GOTCHA" "bash \"${SCRIPT_DIR}/harness-gotcha-memory.sh\" \"${PROJECT_ROOT}\" --mode gotcha" Enter
282
+
283
+ tmux select-pane -t "$PANE_DASH" -T "Dashboard"
284
+ tmux select-pane -t "$PANE_HISTORY" -T "Prompt History"
285
+ tmux select-pane -t "$PANE_GOTCHA" -T "Gotchas (scrollable: prefix + [ )"
286
+ tmux select-pane -t "$PANE_CONV" -T "Conventions (scrollable: prefix + [ )"
287
+ tmux select-pane -t "$PANE_MEM" -T "Shared Memory (scrollable: prefix + [ )"
288
+ tmux select-pane -t "$PANE_MONITOR" -T "Monitor"
289
+ tmux select-pane -t "$PANE_DASH"
290
+
291
+ tmux set-option -t "$SESSION_NAME" pane-border-status top 2>/dev/null || true
292
+ tmux set-option -t "$SESSION_NAME" pane-border-format " #{pane_title} " 2>/dev/null || true
293
+ }
294
+
295
+ # ══════════════════════════════════════════
296
+ # Dispatch: pick best strategy
297
+ # ══════════════════════════════════════════
298
+
299
+ # Detect iTerm2 — works even from Claude Code's non-TTY Bash
300
+ IS_ITERM=false
301
+ if [ "${TERM_PROGRAM:-}" = "iTerm.app" ]; then
302
+ IS_ITERM=true
303
+ elif [ "$(uname)" = "Darwin" ]; then
304
+ # Check if iTerm2 is running (for non-TTY environments like Claude Code)
305
+ if pgrep -q "iTerm2" 2>/dev/null; then
306
+ IS_ITERM=true
307
+ fi
308
+ fi
309
+
310
+ HAS_TMUX=false
311
+ if command -v tmux &>/dev/null; then
312
+ HAS_TMUX=true
313
+ fi
314
+
315
+ # --force-tmux overrides iTerm2 detection
316
+ if [ "$FORCE_TMUX" = true ]; then
317
+ if [ "$HAS_TMUX" != true ]; then
318
+ echo "ERROR: --force-tmux specified but tmux not installed. Run: brew install tmux"
319
+ exit 1
320
+ fi
321
+ IS_ITERM=false
322
+ fi
323
+
324
+ if [ "$IS_ITERM" = true ]; then
325
+ echo "Strategy: iTerm2 native split panes"
326
+ if [ "$MODE" = "team" ]; then
327
+ launch_iterm2_team
328
+ else
329
+ launch_iterm2_solo
330
+ fi
331
+ echo "Layout ready"
332
+
333
+ elif [ "$HAS_TMUX" = true ]; then
334
+ echo "Strategy: tmux"
335
+ if [ "$MODE" = "team" ]; then
336
+ launch_tmux_team
337
+ else
338
+ launch_tmux_solo
339
+ fi
340
+
341
+ # Attach to tmux
342
+ if [ "$DETACH" = true ]; then
343
+ echo "Session created. Attach: tmux attach -t $SESSION_NAME"
344
+ echo "Layout ready"
345
+ elif ! tty -s 2>/dev/null; then
346
+ # Non-TTY (e.g., Claude Code Bash tool) — open in new terminal
347
+ if [ "$(uname)" = "Darwin" ]; then
348
+ osascript -e "
349
+ tell application \"Terminal\"
350
+ do script \"tmux attach -t $SESSION_NAME\"
351
+ activate
352
+ end tell
353
+ " 2>/dev/null && echo "OPENED_TERMINAL=true" || echo "Layout ready"
354
+ else
355
+ echo "Session created. Attach: tmux attach -t $SESSION_NAME"
356
+ echo "Layout ready"
357
+ fi
358
+ else
359
+ if [ -n "${TMUX:-}" ]; then
360
+ tmux switch-client -t "$SESSION_NAME"
361
+ else
362
+ echo "Launching Harness Studio ($MODE mode)..."
363
+ tmux attach -t "$SESSION_NAME"
364
+ fi
365
+ fi
366
+
367
+ else
368
+ echo "ERROR: Neither iTerm2 nor tmux found."
369
+ echo "Install tmux: brew install tmux"
370
+ echo "Or use iTerm2: https://iterm2.com"
371
+ exit 1
372
+ fi
@@ -0,0 +1,123 @@
1
+ #!/bin/bash
2
+ # harness-user-prompt-submit.sh — UserPromptSubmit hook (v5 unified)
3
+ # 핵심 상태 + 라우팅 지시만 주입. 회사 루프 기본, solo는 비상용 override.
4
+ set -e
5
+
6
+ INPUT=$(cat)
7
+ CWD=$(echo "$INPUT" | jq -r '.cwd // empty' 2>/dev/null || true)
8
+ if [ -z "$CWD" ]; then CWD="$PWD"; fi
9
+
10
+ # 조건 1: 하네스 초기화 확인
11
+ if [ ! -f "$CWD/.harness/config.json" ]; then exit 0; fi
12
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
13
+
14
+ # 조건 2: opt-out 플래그 확인
15
+ AUTO_ROUTE="true"
16
+ if command -v jq >/dev/null 2>&1; then
17
+ AUTO_ROUTE=$(jq -r '.behavior.auto_route_dispatcher // true' "$CWD/.harness/config.json" 2>/dev/null || echo "true")
18
+ fi
19
+ if [ "$AUTO_ROUTE" != "true" ]; then exit 0; fi
20
+
21
+ # 사용자 skip 감지
22
+ PROMPT=$(echo "$INPUT" | jq -r '.prompt // empty' 2>/dev/null || true)
23
+ if echo "$PROMPT" | grep -qiE "harness\s*(skip|off|bypass|없이)|without\s*harness|just\s*(answer|chat|reply)"; then
24
+ exit 0
25
+ fi
26
+
27
+ # 현재 세션 상태 읽기
28
+ PIPELINE="none"; CURRENT_AGENT="none"; NEXT_AGENT="none"
29
+ SPRINT_NUM="0"; SPRINT_STATUS="init"; AGENT_STATUS="pending"
30
+
31
+ if [ -f "$CWD/.harness/progress.json" ] && command -v jq >/dev/null 2>&1; then
32
+ conductor_state=$(jq -r '.conductor.state // "idle"' "$CWD/.harness/progress.json" 2>/dev/null || echo "idle")
33
+ pending_next=$(jq -r '.next_agent // "none"' "$CWD/.harness/progress.json" 2>/dev/null || echo "none")
34
+ if [ -x "$SCRIPT_DIR/conductor-tick.sh" ] && { [ "$conductor_state" = "running" ] || [ "$pending_next" = "conductor" ]; }; then
35
+ bash "$SCRIPT_DIR/conductor-tick.sh" "$CWD" >/dev/null 2>&1 || true
36
+ fi
37
+ PIPELINE=$(jq -r '.pipeline // "none"' "$CWD/.harness/progress.json" 2>/dev/null || echo "none")
38
+ CURRENT_AGENT=$(jq -r '.current_agent // "none"' "$CWD/.harness/progress.json" 2>/dev/null || echo "none")
39
+ NEXT_AGENT=$(jq -r '.next_agent // "none"' "$CWD/.harness/progress.json" 2>/dev/null || echo "none")
40
+ SPRINT_NUM=$(jq -r '.sprint.number // 0' "$CWD/.harness/progress.json" 2>/dev/null || echo "0")
41
+ SPRINT_STATUS=$(jq -r '.sprint.status // "init"' "$CWD/.harness/progress.json" 2>/dev/null || echo "init")
42
+ AGENT_STATUS=$(jq -r '.agent_status // "pending"' "$CWD/.harness/progress.json" 2>/dev/null || echo "pending")
43
+ fi
44
+
45
+ # ── 명령 히스토리 기록 (모든 모드 공통) ──
46
+ PROGRESS_LOG="$CWD/.harness/progress.log"
47
+ if [ -n "$PROMPT" ] && [ -d "$CWD/.harness" ]; then
48
+ # progress.log가 없으면 생성
49
+ if [ ! -f "$PROGRESS_LOG" ]; then
50
+ echo "# Harness Command History — $(date +%Y-%m-%d)" > "$PROGRESS_LOG"
51
+ fi
52
+ PROMPT_SHORT=$(echo "$PROMPT" | tr '\n' ' ' | sed 's/ */ /g' | cut -c1-80)
53
+ if [ ${#PROMPT_SHORT} -gt 2 ]; then
54
+ echo "$(date +"%Y-%m-%d %H:%M") | user-prompt | input | ${PROMPT_SHORT}" >> "$PROGRESS_LOG"
55
+ fi
56
+ fi
57
+
58
+ # ── 컨텍스트 분리 가드레일 ──
59
+ # 현재 에이전트가 활성인데 다른 에이전트 스킬을 호출하려는 경우 경고
60
+ CONTEXT_WARNING=""
61
+ CONTEXT_BLOCK=""
62
+ if [ "$CURRENT_AGENT" != "none" ] && [ "$CURRENT_AGENT" != "null" ] && [ "$AGENT_STATUS" = "running" ]; then
63
+ # 프롬프트에서 /harness-* 패턴 추출
64
+ REQUESTED_SKILL=$(echo "$PROMPT" | grep -oE '/harness-[a-z-]+' | head -1 | sed 's|/harness-||')
65
+ if [ -n "$REQUESTED_SKILL" ] && [ "$REQUESTED_SKILL" != "$CURRENT_AGENT" ]; then
66
+ CONTEXT_BLOCK="
67
+ ## Context Isolation Block
68
+ current_agent=${CURRENT_AGENT} (running) 인데 /harness-${REQUESTED_SKILL} 호출 감지.
69
+ 문서 기반 task session 분리를 강제하기 위해 이 호출은 차단됩니다.
70
+ 현재 에이전트를 먼저 완료(completed)하거나, 새 세션을 시작하세요."
71
+ fi
72
+ fi
73
+
74
+ if [ -n "$CONTEXT_BLOCK" ]; then
75
+ cat <<EOF
76
+ [harness] blocked | current=${CURRENT_AGENT} | requested=${REQUESTED_SKILL}
77
+ ${CONTEXT_BLOCK}
78
+ EOF
79
+ exit 0
80
+ fi
81
+
82
+ # ── Mode 기반 분기 ──
83
+ MODE="team"
84
+ if [ -f "$CWD/.harness/progress.json" ] && command -v jq >/dev/null 2>&1; then
85
+ MODE=$(jq -r '.mode // "team"' "$CWD/.harness/progress.json" 2>/dev/null || echo "team")
86
+ fi
87
+
88
+ if [ "$MODE" = "team" ]; then
89
+ FEATURE_QUEUE="$CWD/.harness/actions/feature-queue.json"
90
+ T_PASSED=0; T_TOTAL=0; T_FAILED=0
91
+ if [ -f "$FEATURE_QUEUE" ]; then
92
+ T_PASSED=$(jq '.queue.passed | length' "$FEATURE_QUEUE" 2>/dev/null || echo 0)
93
+ T_TOTAL=$(jq '[.queue.ready, (.queue.blocked | keys), (.queue.in_progress | keys), .queue.passed, .queue.failed] | flatten | length' "$FEATURE_QUEUE" 2>/dev/null || echo 0)
94
+ T_FAILED=$(jq '.queue.failed | length' "$FEATURE_QUEUE" 2>/dev/null || echo 0)
95
+ fi
96
+
97
+ cat <<EOF
98
+ [harness] team | ${T_PASSED}/${T_TOTAL} passed | ${T_FAILED} failed
99
+
100
+ ## Team Mode Active
101
+ - worker pool(기본 3)이 자율적으로 Gen→Eval 루프 실행 중 (max 5 retries)
102
+ - CEO/Meeting/CTO/CQO/Service-Ops control-plane은 이 상한에 포함되지 않음
103
+ - 역할: 모니터링, 실패 대응, Goal 점검
104
+ - /harness-stop → 중단 | /harness-solo → 비상용 단일 에이전트 fallback
105
+ - skip: "harness skip" 시 일반 대화
106
+ EOF
107
+ exit 0
108
+ fi
109
+
110
+ # ── Compact context 주입 (solo/paused mode) ──
111
+ cat <<EOF
112
+ [harness] S${SPRINT_NUM} | ${PIPELINE} | agent=${CURRENT_AGENT} (${AGENT_STATUS}) | next=${NEXT_AGENT}
113
+ ${CONTEXT_WARNING}
114
+ ## Route
115
+ - pipeline=none/dispatcher 미실행 → harness-dispatcher 스킬 호출
116
+ - 기본 경로는 conductor 중심 회사 루프다: dispatch -> CEO meeting -> planner(COO) -> cto -> gen/eval -> cqo -> service-ops -> batch meeting
117
+ - conductor 는 Planner 직행 대신 meeting-manager / cto / cqo / service-ops 로 next_agent를 재작성할 수 있다
118
+ - 기능 요청 → pipeline flow | 실수 지적 → gotcha flow | 메타 질문 → 짧게 응답 (skip)
119
+ - 활성 pipeline → next_agent/current_agent 컨텍스트로 계속
120
+ - skip: "harness skip", "just answer" 등 명시 시 단일 메시지 건너뜀
121
+ EOF
122
+
123
+ exit 0