@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.
- package/CHANGELOG.md +42 -0
- package/commands/harness-solo.md +20 -20
- package/commands/harness-team.md +1 -1
- package/package.json +1 -1
- package/scripts/conductor-tick.sh +382 -0
- package/scripts/harness-archive.sh +123 -0
- package/scripts/harness-dashboard-up.sh +72 -0
- package/scripts/harness-dashboard.sh +509 -0
- package/scripts/harness-goal-init.sh +72 -0
- package/scripts/harness-goal-show.sh +37 -0
- package/scripts/harness-gotcha-memory.sh +348 -0
- package/scripts/harness-gotcha-register.sh +393 -0
- package/scripts/harness-meeting-doc.sh +153 -0
- package/scripts/harness-monitor.sh +398 -0
- package/scripts/harness-next.sh +577 -0
- package/scripts/harness-progress-set.sh +41 -0
- package/scripts/harness-prompt-history.sh +164 -0
- package/scripts/harness-queue-manager.sh +694 -0
- package/scripts/harness-session-start.sh +140 -0
- package/scripts/harness-statusline.sh +139 -0
- package/scripts/harness-task-session.sh +48 -0
- package/scripts/harness-tmux.sh +372 -0
- package/scripts/harness-user-prompt-submit.sh +123 -0
- package/scripts/init-agents-md.sh +337 -0
- package/scripts/init-ref-docs.sh +262 -0
- package/scripts/lib/harness-audit.sh +113 -0
- package/scripts/lib/harness-guardrail.sh +148 -0
- package/scripts/lib/harness-keywait.sh +57 -0
- package/scripts/lib/harness-render-progress.sh +346 -0
- package/scripts/scan-project.sh +367 -0
- package/skills/brainstorming/SKILL.md +4 -4
- package/skills/conductor/SKILL.md +12 -16
- package/skills/cqo/SKILL.md +7 -0
- package/skills/cto/SKILL.md +7 -0
- package/skills/dispatcher/SKILL.md +17 -28
- package/skills/evaluator-code-quality/SKILL.md +1 -1
- package/skills/evaluator-functional/SKILL.md +1 -1
- package/skills/evaluator-visual/SKILL.md +1 -1
- package/skills/generator-backend/SKILL.md +1 -1
- package/skills/generator-frontend/SKILL.md +1 -1
- package/skills/meeting-manager/SKILL.md +33 -1
- package/skills/planner/SKILL.md +23 -9
- 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
|