@walwal-harness/cli 6.0.4 → 6.0.5
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 +21 -0
- package/bin/init.js +121 -4
- package/package.json +1 -1
- package/scripts/harness-archive.sh +0 -109
- package/scripts/harness-dashboard-up.sh +0 -72
- package/scripts/harness-dashboard.sh +0 -509
- package/scripts/harness-goal-init.sh +0 -72
- package/scripts/harness-goal-show.sh +0 -37
- package/scripts/harness-gotcha-memory.sh +0 -348
- package/scripts/harness-gotcha-register.sh +0 -393
- package/scripts/harness-monitor.sh +0 -398
- package/scripts/harness-next.sh +0 -546
- package/scripts/harness-progress-set.sh +0 -41
- package/scripts/harness-prompt-history.sh +0 -164
- package/scripts/harness-queue-manager.sh +0 -694
- package/scripts/harness-session-start.sh +0 -129
- package/scripts/harness-statusline.sh +0 -139
- package/scripts/harness-tmux.sh +0 -372
- package/scripts/harness-user-prompt-submit.sh +0 -105
- package/scripts/init-agents-md.sh +0 -337
- package/scripts/init-ref-docs.sh +0 -262
- package/scripts/lib/harness-audit.sh +0 -113
- package/scripts/lib/harness-guardrail.sh +0 -148
- package/scripts/lib/harness-keywait.sh +0 -57
- package/scripts/lib/harness-render-progress.sh +0 -346
- package/scripts/scan-project.sh +0 -367
package/scripts/harness-next.sh
DELETED
|
@@ -1,546 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# harness-next.sh — 세션 오케스트레이터
|
|
3
|
-
# 현재 progress.json 상태를 읽고 다음 에이전트를 결정한다.
|
|
4
|
-
# Feature-level 프로그래스를 출력하고 handoff.json을 생성한다.
|
|
5
|
-
set -e
|
|
6
|
-
|
|
7
|
-
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
8
|
-
source "$SCRIPT_DIR/lib/harness-render-progress.sh"
|
|
9
|
-
source "$SCRIPT_DIR/lib/harness-guardrail.sh"
|
|
10
|
-
source "$SCRIPT_DIR/lib/harness-audit.sh"
|
|
11
|
-
|
|
12
|
-
# ─────────────────────────────────────────
|
|
13
|
-
# Resolve project root
|
|
14
|
-
# ─────────────────────────────────────────
|
|
15
|
-
PROJECT_ROOT="$(resolve_harness_root "${1:-.}")" || {
|
|
16
|
-
echo "[harness] ERROR: .harness/ directory not found" >&2
|
|
17
|
-
echo " Run 'npx walwal-harness' to initialize." >&2
|
|
18
|
-
exit 1
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
PROGRESS="$PROJECT_ROOT/.harness/progress.json"
|
|
22
|
-
CONFIG="$PROJECT_ROOT/.harness/config.json"
|
|
23
|
-
PIPELINE_JSON="$PROJECT_ROOT/.harness/actions/pipeline.json"
|
|
24
|
-
HANDOFF="$PROJECT_ROOT/.harness/handoff.json"
|
|
25
|
-
|
|
26
|
-
check_jq || exit 1
|
|
27
|
-
|
|
28
|
-
# Initialize audit log
|
|
29
|
-
init_audit "$PROJECT_ROOT"
|
|
30
|
-
|
|
31
|
-
if [ ! -f "$PROGRESS" ]; then
|
|
32
|
-
echo "[harness] ERROR: progress.json not found" >&2
|
|
33
|
-
echo " Run 'npx walwal-harness --force' to reinitialize." >&2
|
|
34
|
-
exit 1
|
|
35
|
-
fi
|
|
36
|
-
|
|
37
|
-
# ─────────────────────────────────────────
|
|
38
|
-
# Read current state
|
|
39
|
-
# ─────────────────────────────────────────
|
|
40
|
-
pipeline=$(jq -r '.pipeline // "null"' "$PROGRESS")
|
|
41
|
-
sprint_num=$(jq -r '.sprint.number // 0' "$PROGRESS")
|
|
42
|
-
sprint_status=$(jq -r '.sprint.status // "init"' "$PROGRESS")
|
|
43
|
-
current_agent=$(jq -r '.current_agent // "null"' "$PROGRESS")
|
|
44
|
-
agent_status=$(jq -r '.agent_status // "pending"' "$PROGRESS")
|
|
45
|
-
next_agent=$(jq -r '.next_agent // "null"' "$PROGRESS")
|
|
46
|
-
retry_count=$(jq -r '.sprint.retry_count // 0' "$PROGRESS")
|
|
47
|
-
max_retries=$(jq -r '.flow.max_retries_per_sprint // 10' "$CONFIG" 2>/dev/null || echo 10)
|
|
48
|
-
|
|
49
|
-
# fe_stack + fe_target 치환 (Flutter Web/Mobile/Desktop 지원) — pipeline.json 에서 읽음
|
|
50
|
-
fe_stack="react"
|
|
51
|
-
fe_target="web"
|
|
52
|
-
if [ -f "$PIPELINE_JSON" ]; then
|
|
53
|
-
fe_stack=$(jq -r '.fe_stack // "react"' "$PIPELINE_JSON" 2>/dev/null || echo "react")
|
|
54
|
-
fe_target=$(jq -r '.fe_target // empty' "$PIPELINE_JSON" 2>/dev/null || true)
|
|
55
|
-
if [ -z "$fe_target" ]; then
|
|
56
|
-
# pipeline.json 에 fe_target 미지정 시 config.json 의 _default_target 사용
|
|
57
|
-
fe_target="web" # v5.6.5+: 치환 로직 제거. fe_target 은 pipeline.json 에서 명시하거나 web 기본.
|
|
58
|
-
fi
|
|
59
|
-
fi
|
|
60
|
-
|
|
61
|
-
# ─────────────────────────────────────────
|
|
62
|
-
# fe_stack 치환 (no-op — Flutter 지원 제거됨, 하위 호환용 stub)
|
|
63
|
-
# ─────────────────────────────────────────
|
|
64
|
-
substitute_fe_stack() {
|
|
65
|
-
echo "$1"
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
# ─────────────────────────────────────────
|
|
69
|
-
# Pre-Eval Gate — deterministic checks before Evaluator
|
|
70
|
-
# Generator가 완료되고 다음이 Evaluator일 때, lint/type/test를 먼저 실행.
|
|
71
|
-
# 실패 시 Evaluator를 건너뛰고 Generator로 리라우팅.
|
|
72
|
-
# ─────────────────────────────────────────
|
|
73
|
-
run_pre_eval_gate() {
|
|
74
|
-
local next="$1"
|
|
75
|
-
local gate_enabled
|
|
76
|
-
gate_enabled=$(jq -r '.flow.pre_eval_gate.enabled // false' "$CONFIG" 2>/dev/null)
|
|
77
|
-
if [ "$gate_enabled" != "true" ]; then return 0; fi
|
|
78
|
-
|
|
79
|
-
# Evaluator 에이전트인지 확인
|
|
80
|
-
case "$next" in
|
|
81
|
-
evaluator-*) ;;
|
|
82
|
-
*) return 0 ;;
|
|
83
|
-
esac
|
|
84
|
-
|
|
85
|
-
local timeout
|
|
86
|
-
timeout=$(jq -r '.flow.pre_eval_gate.timeout_seconds // 120' "$CONFIG" 2>/dev/null)
|
|
87
|
-
|
|
88
|
-
# 실패 위치 결정 (backend or frontend)
|
|
89
|
-
# current_agent가 실패로 변경된 경우, completed_agents에서 직전 Generator를 찾는다
|
|
90
|
-
local location="backend"
|
|
91
|
-
local checks_key="backend_checks"
|
|
92
|
-
local source_agent="$current_agent"
|
|
93
|
-
|
|
94
|
-
# current_agent가 generator가 아닌 경우 (예: dispatcher로 리라우팅된 상태),
|
|
95
|
-
# completed_agents에서 마지막 generator를 찾는다
|
|
96
|
-
case "$source_agent" in
|
|
97
|
-
generator-frontend)
|
|
98
|
-
location="frontend"
|
|
99
|
-
checks_key="frontend_checks"
|
|
100
|
-
;;
|
|
101
|
-
generator-backend)
|
|
102
|
-
location="backend"
|
|
103
|
-
checks_key="backend_checks"
|
|
104
|
-
;;
|
|
105
|
-
*)
|
|
106
|
-
# completed_agents에서 마지막 generator를 역순으로 찾기
|
|
107
|
-
local last_gen
|
|
108
|
-
last_gen=$(jq -r '.completed_agents // [] | map(select(startswith("generator-"))) | last // empty' "$PROGRESS" 2>/dev/null)
|
|
109
|
-
case "$last_gen" in
|
|
110
|
-
generator-frontend)
|
|
111
|
-
location="frontend"
|
|
112
|
-
checks_key="frontend_checks"
|
|
113
|
-
;;
|
|
114
|
-
generator-backend)
|
|
115
|
-
location="backend"
|
|
116
|
-
checks_key="backend_checks"
|
|
117
|
-
;;
|
|
118
|
-
esac
|
|
119
|
-
;;
|
|
120
|
-
esac
|
|
121
|
-
|
|
122
|
-
local -a checks
|
|
123
|
-
mapfile -t checks < <(jq -r ".flow.pre_eval_gate.${checks_key}[]" "$CONFIG" 2>/dev/null)
|
|
124
|
-
|
|
125
|
-
if [ ${#checks[@]} -eq 0 ]; then return 0; fi
|
|
126
|
-
|
|
127
|
-
# Resolve cwd for checks: config.pre_eval_gate.frontend_cwd / backend_cwd
|
|
128
|
-
local cwd_key="${location}_cwd"
|
|
129
|
-
local check_cwd
|
|
130
|
-
check_cwd=$(jq -r ".flow.pre_eval_gate.${cwd_key} // empty" "$CONFIG" 2>/dev/null)
|
|
131
|
-
if [ -n "$check_cwd" ] && [ "$check_cwd" != "null" ]; then
|
|
132
|
-
# Relative path from PROJECT_ROOT
|
|
133
|
-
check_cwd="$PROJECT_ROOT/$check_cwd"
|
|
134
|
-
else
|
|
135
|
-
check_cwd="$PROJECT_ROOT"
|
|
136
|
-
fi
|
|
137
|
-
|
|
138
|
-
echo ""
|
|
139
|
-
echo " ── Pre-Eval Gate ──────────────────────"
|
|
140
|
-
if [ "$check_cwd" != "$PROJECT_ROOT" ]; then
|
|
141
|
-
echo " cwd: ${check_cwd#$PROJECT_ROOT/}"
|
|
142
|
-
fi
|
|
143
|
-
local all_pass=true
|
|
144
|
-
local fail_log=""
|
|
145
|
-
|
|
146
|
-
for cmd in "${checks[@]}"; do
|
|
147
|
-
printf " %-40s " "$cmd"
|
|
148
|
-
local output
|
|
149
|
-
if output=$(cd "$check_cwd" && timeout "${timeout}s" bash -c "$cmd" 2>&1); then
|
|
150
|
-
echo "✓"
|
|
151
|
-
else
|
|
152
|
-
echo "✗"
|
|
153
|
-
all_pass=false
|
|
154
|
-
fail_log+="[FAIL] $cmd"$'\n'"$output"$'\n\n'
|
|
155
|
-
fi
|
|
156
|
-
done
|
|
157
|
-
|
|
158
|
-
if [ "$all_pass" = true ]; then
|
|
159
|
-
echo " Gate: PASS — proceeding to $next"
|
|
160
|
-
echo ""
|
|
161
|
-
return 0
|
|
162
|
-
else
|
|
163
|
-
echo ""
|
|
164
|
-
echo " Gate: FAIL — rerouting to $current_agent"
|
|
165
|
-
echo ""
|
|
166
|
-
|
|
167
|
-
# progress.json 업데이트: 실패 기록 + Generator로 리라우팅
|
|
168
|
-
local new_retry=$((retry_count + 1))
|
|
169
|
-
local fail_summary
|
|
170
|
-
fail_summary=$(echo "$fail_log" | head -20)
|
|
171
|
-
|
|
172
|
-
jq --arg agent "$current_agent" \
|
|
173
|
-
--arg loc "$location" \
|
|
174
|
-
--arg msg "Pre-eval gate failed: $fail_summary" \
|
|
175
|
-
--arg target "$current_agent" \
|
|
176
|
-
--argjson retry "$new_retry" \
|
|
177
|
-
'.sprint.status = "failed" |
|
|
178
|
-
.sprint.retry_count = $retry |
|
|
179
|
-
.agent_status = "failed" |
|
|
180
|
-
.next_agent = $target |
|
|
181
|
-
.failure.agent = $agent |
|
|
182
|
-
.failure.location = $loc |
|
|
183
|
-
.failure.message = $msg |
|
|
184
|
-
.failure.retry_target = $target' "$PROGRESS" > "${PROGRESS}.tmp" && mv "${PROGRESS}.tmp" "$PROGRESS"
|
|
185
|
-
|
|
186
|
-
# next_agent를 Generator로 덮어쓰기
|
|
187
|
-
next_agent="$current_agent"
|
|
188
|
-
return 1
|
|
189
|
-
fi
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
# ─────────────────────────────────────────
|
|
193
|
-
# Determine next agent
|
|
194
|
-
# ─────────────────────────────────────────
|
|
195
|
-
compute_next_agent() {
|
|
196
|
-
local current="$1"
|
|
197
|
-
local status="$2"
|
|
198
|
-
|
|
199
|
-
# If blocked, no next
|
|
200
|
-
if [ "$status" = "blocked" ]; then
|
|
201
|
-
echo "null"
|
|
202
|
-
return
|
|
203
|
-
fi
|
|
204
|
-
|
|
205
|
-
# If failure with retry target, use that
|
|
206
|
-
local retry_target
|
|
207
|
-
retry_target=$(jq -r '.failure.retry_target // "null"' "$PROGRESS")
|
|
208
|
-
if [ "$retry_target" != "null" ] && [ "$status" = "failed" ]; then
|
|
209
|
-
echo "$retry_target"
|
|
210
|
-
return
|
|
211
|
-
fi
|
|
212
|
-
|
|
213
|
-
# If next_agent is already set in progress.json, use it
|
|
214
|
-
if [ "$next_agent" != "null" ]; then
|
|
215
|
-
echo "$next_agent"
|
|
216
|
-
return
|
|
217
|
-
fi
|
|
218
|
-
|
|
219
|
-
# Compute from pipeline sequence
|
|
220
|
-
if [ "$pipeline" = "null" ] || [ ! -f "$CONFIG" ]; then
|
|
221
|
-
echo "dispatcher"
|
|
222
|
-
return
|
|
223
|
-
fi
|
|
224
|
-
|
|
225
|
-
local -a raw_agents
|
|
226
|
-
mapfile -t raw_agents < <(jq -r ".flow.pipeline_selection.pipelines[\"${pipeline}\"][]" "$CONFIG" 2>/dev/null | sed 's/:.*//')
|
|
227
|
-
|
|
228
|
-
# fe_stack 치환 + __skip__ 필터링
|
|
229
|
-
local -a agents
|
|
230
|
-
for a in "${raw_agents[@]}"; do
|
|
231
|
-
local sub
|
|
232
|
-
sub=$(substitute_fe_stack "$a")
|
|
233
|
-
if [ "$sub" != "__skip__" ]; then
|
|
234
|
-
agents+=("$sub")
|
|
235
|
-
fi
|
|
236
|
-
done
|
|
237
|
-
|
|
238
|
-
local found=false
|
|
239
|
-
for agent in "${agents[@]}"; do
|
|
240
|
-
if [ "$found" = true ]; then
|
|
241
|
-
echo "$agent"
|
|
242
|
-
return
|
|
243
|
-
fi
|
|
244
|
-
# current 비교 시에도 치환된 이름으로 (Flutter 변형 에이전트가 실행되는 경우)
|
|
245
|
-
if [ "$agent" = "$current" ]; then
|
|
246
|
-
found=true
|
|
247
|
-
fi
|
|
248
|
-
done
|
|
249
|
-
|
|
250
|
-
# Current is last agent → sprint complete
|
|
251
|
-
echo "archive"
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
# If current agent completed, compute next
|
|
255
|
-
if [ "$agent_status" = "completed" ] && [ "$next_agent" = "null" ]; then
|
|
256
|
-
next_agent=$(compute_next_agent "$current_agent" "$agent_status")
|
|
257
|
-
fi
|
|
258
|
-
|
|
259
|
-
# ─────────────────────────────────────────
|
|
260
|
-
# Artifact Prerequisites — 선행 아티팩트 상태 검증
|
|
261
|
-
# ─────────────────────────────────────────
|
|
262
|
-
verify_artifact_prerequisites() {
|
|
263
|
-
local target_agent="$1"
|
|
264
|
-
local states_order='["pending","draft","reviewed","approved"]'
|
|
265
|
-
|
|
266
|
-
# 에이전트의 prerequisites 가져오기
|
|
267
|
-
local prereqs
|
|
268
|
-
prereqs=$(jq -r ".artifacts.prerequisites[\"${target_agent}\"] // empty" "$CONFIG" 2>/dev/null)
|
|
269
|
-
if [ -z "$prereqs" ] || [ "$prereqs" = "null" ]; then return 0; fi
|
|
270
|
-
|
|
271
|
-
local all_met=true
|
|
272
|
-
echo ""
|
|
273
|
-
echo " ── Artifact Prerequisites ─────────────"
|
|
274
|
-
|
|
275
|
-
# prereqs의 각 키(artifact명)를 순회
|
|
276
|
-
while IFS='=' read -r artifact required_status; do
|
|
277
|
-
artifact=$(echo "$artifact" | tr -d '"' | tr -d ' ')
|
|
278
|
-
required_status=$(echo "$required_status" | tr -d '"' | tr -d ' ')
|
|
279
|
-
if [ -z "$artifact" ]; then continue; fi
|
|
280
|
-
|
|
281
|
-
local current_status
|
|
282
|
-
current_status=$(jq -r ".artifacts[\"${artifact}\"].status // \"pending\"" "$PROGRESS" 2>/dev/null)
|
|
283
|
-
|
|
284
|
-
# 상태 순서 비교
|
|
285
|
-
local required_idx current_idx
|
|
286
|
-
required_idx=$(echo "$states_order" | jq "index(\"$required_status\") // 0")
|
|
287
|
-
current_idx=$(echo "$states_order" | jq "index(\"$current_status\") // 0")
|
|
288
|
-
|
|
289
|
-
if [ "$current_idx" -ge "$required_idx" ]; then
|
|
290
|
-
printf " ✓ %-25s %s (required: %s)\n" "$artifact" "$current_status" "$required_status"
|
|
291
|
-
else
|
|
292
|
-
printf " ✗ %-25s %s (required: %s)\n" "$artifact" "$current_status" "$required_status"
|
|
293
|
-
all_met=false
|
|
294
|
-
fi
|
|
295
|
-
done < <(jq -r ".artifacts.prerequisites[\"${target_agent}\"] | to_entries[] | \"\(.key)=\(.value)\"" "$CONFIG" 2>/dev/null)
|
|
296
|
-
|
|
297
|
-
echo ""
|
|
298
|
-
|
|
299
|
-
if [ "$all_met" = true ]; then
|
|
300
|
-
echo " Prerequisites: PASS"
|
|
301
|
-
else
|
|
302
|
-
echo " Prerequisites: FAIL — preceding agent must complete artifacts first"
|
|
303
|
-
fi
|
|
304
|
-
echo ""
|
|
305
|
-
|
|
306
|
-
[ "$all_met" = true ] && return 0 || return 1
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
# ─────────────────────────────────────────
|
|
310
|
-
# Runtime Guardrail — 파일 소유권 검증
|
|
311
|
-
# ─────────────────────────────────────────
|
|
312
|
-
if [ "$agent_status" = "completed" ]; then
|
|
313
|
-
audit_log "$current_agent" "develop" "complete" "" "agent completed"
|
|
314
|
-
verify_file_ownership "$PROJECT_ROOT" || audit_gate "file-ownership" "warn" "boundary violation detected"
|
|
315
|
-
fi
|
|
316
|
-
|
|
317
|
-
# ─────────────────────────────────────────
|
|
318
|
-
# Auto Gotcha Registration — Evaluator 완료 직후 evaluation-*.md 스캔
|
|
319
|
-
# evaluation-*.md 내부의 ```gotcha_candidates``` JSON 블록을 읽어
|
|
320
|
-
# .harness/gotchas/<target>.md 에 unverified 상태로 등록/dedup.
|
|
321
|
-
# ─────────────────────────────────────────
|
|
322
|
-
case "$current_agent" in
|
|
323
|
-
evaluator-*|generator-*)
|
|
324
|
-
if [ "$agent_status" = "completed" ] || [ "$agent_status" = "failed" ]; then
|
|
325
|
-
if [ -x "$SCRIPT_DIR/harness-gotcha-register.sh" ]; then
|
|
326
|
-
# --scan-all 로 evaluation-*.md + gen-report-*.md + lead-report-*.md 의
|
|
327
|
-
# gotcha_candidates / convention_candidates 블록 모두 동적 등록
|
|
328
|
-
bash "$SCRIPT_DIR/harness-gotcha-register.sh" "$PROJECT_ROOT" --scan-all 2>&1 \
|
|
329
|
-
| grep -E '^\[gotcha-register\]' || true
|
|
330
|
-
audit_gate "gotcha-register" "scan-all" "$current_agent"
|
|
331
|
-
fi
|
|
332
|
-
fi
|
|
333
|
-
;;
|
|
334
|
-
esac
|
|
335
|
-
|
|
336
|
-
# ─────────────────────────────────────────
|
|
337
|
-
# Run Pre-Eval Gate (if applicable)
|
|
338
|
-
# ─────────────────────────────────────────
|
|
339
|
-
if [ "$agent_status" = "completed" ]; then
|
|
340
|
-
audit_gate "pre-eval" "start" "$next_agent"
|
|
341
|
-
if run_pre_eval_gate "$next_agent"; then
|
|
342
|
-
audit_gate "pre-eval" "pass" "$next_agent"
|
|
343
|
-
else
|
|
344
|
-
audit_gate "pre-eval" "fail" "rerouted to $current_agent"
|
|
345
|
-
fi
|
|
346
|
-
if verify_artifact_prerequisites "$next_agent"; then
|
|
347
|
-
audit_gate "artifact-prereq" "pass" "$next_agent"
|
|
348
|
-
else
|
|
349
|
-
audit_gate "artifact-prereq" "fail" "$next_agent"
|
|
350
|
-
fi
|
|
351
|
-
fi
|
|
352
|
-
|
|
353
|
-
# ─────────────────────────────────────────
|
|
354
|
-
# Render progress
|
|
355
|
-
# ─────────────────────────────────────────
|
|
356
|
-
render_progress "$PROJECT_ROOT"
|
|
357
|
-
render_agent_bar "$PROJECT_ROOT"
|
|
358
|
-
echo ""
|
|
359
|
-
|
|
360
|
-
# ─────────────────────────────────────────
|
|
361
|
-
# Generate handoff.json (unified session transition document)
|
|
362
|
-
# ─────────────────────────────────────────
|
|
363
|
-
if [ "$next_agent" != "null" ] && [ "$next_agent" != "archive" ] && [ "$agent_status" != "blocked" ]; then
|
|
364
|
-
# ── Escalation check: 3회 실패 시 Planner에게 scope 축소 요청 ──
|
|
365
|
-
escalate_after=$(jq -r '.flow.escalate_to_planner_after // 3' "$CONFIG" 2>/dev/null || echo 3)
|
|
366
|
-
if [ "$retry_count" -ge "$escalate_after" ] && [ "$sprint_status" = "failed" ] && [ "$next_agent" != "planner" ]; then
|
|
367
|
-
echo " ⚠ Escalation: ${retry_count}회 실패 — Planner에게 scope 축소/접근 변경 요청"
|
|
368
|
-
audit_log "system" "escalate" "start" "→planner" "retry ${retry_count}회 초과"
|
|
369
|
-
next_agent="planner"
|
|
370
|
-
jq --arg msg "Escalated after ${retry_count} failures. Planner must review scope or approach." \
|
|
371
|
-
'.next_agent = "planner" |
|
|
372
|
-
.failure.message = $msg |
|
|
373
|
-
.failure.retry_target = "planner"' "$PROGRESS" > "${PROGRESS}.tmp" && mv "${PROGRESS}.tmp" "$PROGRESS"
|
|
374
|
-
fi
|
|
375
|
-
|
|
376
|
-
# ── Read model & thinking mode for next agent ──
|
|
377
|
-
agent_model=$(jq -r ".agents[\"${next_agent}\"].model // \"opus\"" "$CONFIG" 2>/dev/null || echo "opus")
|
|
378
|
-
agent_thinking=$(jq -r ".agents[\"${next_agent}\"].thinking_mode // \"null\"" "$CONFIG" 2>/dev/null || echo "null")
|
|
379
|
-
|
|
380
|
-
# ── Build prompt text (embedded in handoff.json) ──
|
|
381
|
-
prompt="/harness-${next_agent} 를 실행하세요."
|
|
382
|
-
|
|
383
|
-
if [ "$sprint_num" -gt 0 ]; then
|
|
384
|
-
prompt+=$'\n'"Sprint ${sprint_num}"
|
|
385
|
-
if [ "$sprint_status" = "failed" ]; then
|
|
386
|
-
prompt+=" (retry ${retry_count}/${max_retries})"
|
|
387
|
-
fi
|
|
388
|
-
prompt+="을 진행합니다."
|
|
389
|
-
fi
|
|
390
|
-
|
|
391
|
-
prompt+=$'\n'".harness/handoff.json을 읽고 컨텍스트를 확인하세요."
|
|
392
|
-
|
|
393
|
-
# Inject thinking mode instruction
|
|
394
|
-
if [ "$agent_thinking" != "null" ]; then
|
|
395
|
-
case "$agent_thinking" in
|
|
396
|
-
ultraplan)
|
|
397
|
-
prompt+=$'\n\n'"[Thinking Mode: ultraplan] /${agent_thinking} 모드를 사용하세요. 깊은 사고로 아키텍처와 설계를 수행합니다."
|
|
398
|
-
;;
|
|
399
|
-
ultrathink)
|
|
400
|
-
prompt+=$'\n\n'"[Thinking Mode: ultrathink] /${agent_thinking} 모드를 사용하세요. 최대 추론 깊이로 비판적 검증을 수행합니다."
|
|
401
|
-
;;
|
|
402
|
-
plan)
|
|
403
|
-
prompt+=$'\n\n'"[Thinking Mode: plan] /${agent_thinking} 모드를 사용하세요. 구조화된 계획을 수립한 후 실행합니다."
|
|
404
|
-
;;
|
|
405
|
-
esac
|
|
406
|
-
fi
|
|
407
|
-
|
|
408
|
-
# Add failure context if retrying
|
|
409
|
-
failure_msg=$(jq -r '.failure.message // empty' "$PROGRESS")
|
|
410
|
-
if [ -n "$failure_msg" ] && [ "$failure_msg" != "null" ]; then
|
|
411
|
-
prompt+=$'\n\n'"이전 실패 사유: ${failure_msg}"
|
|
412
|
-
prompt+=$'\n'"같은 접근을 반복하지 말고, 실패 원인을 분석한 후 다른 전략으로 시도하세요."
|
|
413
|
-
fi
|
|
414
|
-
|
|
415
|
-
# ── Collect artifacts ──
|
|
416
|
-
FEATURE_LIST="$PROJECT_ROOT/.harness/actions/feature-list.json"
|
|
417
|
-
|
|
418
|
-
artifacts_ready=()
|
|
419
|
-
for f in plan.md feature-list.json api-contract.json sprint-contract.md evaluation-functional.md evaluation-visual.md; do
|
|
420
|
-
if [ -f "$PROJECT_ROOT/.harness/actions/$f" ]; then
|
|
421
|
-
artifacts_ready+=("$f")
|
|
422
|
-
fi
|
|
423
|
-
done
|
|
424
|
-
artifacts_json=$(printf '%s\n' "${artifacts_ready[@]}" | jq -R . | jq -s .)
|
|
425
|
-
|
|
426
|
-
# Collect focus features (incomplete ones)
|
|
427
|
-
focus_features="[]"
|
|
428
|
-
if [ -f "$FEATURE_LIST" ]; then
|
|
429
|
-
focus_features=$(jq '[.features[]? | select(.passes == null or (.passes | length) == 0 or ((.passes // []) | map(select(. == "evaluator-functional")) | length == 0)) | .id] | .[0:5]' "$FEATURE_LIST" 2>/dev/null || echo "[]")
|
|
430
|
-
fi
|
|
431
|
-
|
|
432
|
-
# ── Regression data ──
|
|
433
|
-
regression_source="null"
|
|
434
|
-
prev_sprint=$((sprint_num - 1))
|
|
435
|
-
prev_archive="$PROJECT_ROOT/.harness/archive/sprint-$(printf '%03d' $prev_sprint)"
|
|
436
|
-
if [ "$prev_sprint" -ge 1 ] && [ -d "$prev_archive" ]; then
|
|
437
|
-
if [ -f "$prev_archive/feature-list.json" ]; then
|
|
438
|
-
regression_source=$(jq '{
|
|
439
|
-
sprint: '"$prev_sprint"',
|
|
440
|
-
passed_features: [.features[]? | select((.passes // []) | map(select(. == "evaluator-functional")) | length > 0) | {id, name, acceptance_criteria}],
|
|
441
|
-
archive_path: "'"$prev_archive"'"
|
|
442
|
-
}' "$prev_archive/feature-list.json" 2>/dev/null || echo "null")
|
|
443
|
-
fi
|
|
444
|
-
fi
|
|
445
|
-
|
|
446
|
-
# ── Eval-specific config ──
|
|
447
|
-
eval_config="null"
|
|
448
|
-
cross_validation_data="null"
|
|
449
|
-
case "$next_agent" in
|
|
450
|
-
evaluator-*)
|
|
451
|
-
eval_config=$(jq '{
|
|
452
|
-
pass_threshold: .evaluation.scoring.pass_threshold,
|
|
453
|
-
scale: .evaluation.scoring.scale,
|
|
454
|
-
verdict_rules: .evaluation.scoring.verdict_rules,
|
|
455
|
-
regression_enabled: .evaluation.regression.enabled,
|
|
456
|
-
cross_validation_enabled: .evaluation.cross_validation.enabled,
|
|
457
|
-
adversarial_rules: .agents["'"$next_agent"'"].adversarial_rules.rules,
|
|
458
|
-
forbidden: .agents["'"$next_agent"'"].adversarial_rules.forbidden
|
|
459
|
-
}' "$CONFIG" 2>/dev/null || echo "null")
|
|
460
|
-
;;
|
|
461
|
-
esac
|
|
462
|
-
|
|
463
|
-
# ── Cross-Validation (chain: code-quality → functional → visual) ──
|
|
464
|
-
cross_validation_from_code_quality="null"
|
|
465
|
-
if [ "$next_agent" = "evaluator-functional" ] || [ "$next_agent" = "evaluator-visual" ]; then
|
|
466
|
-
cq_eval="$PROJECT_ROOT/.harness/actions/evaluation-code-quality.md"
|
|
467
|
-
if [ -f "$cq_eval" ]; then
|
|
468
|
-
cross_validation_from_code_quality=$(sed -n '/```json/,/```/p' "$cq_eval" | tail -n +2 | head -n -1 | jq 'select(.evaluator == "code-quality" or .cross_validation_from_code_quality)' 2>/dev/null || echo "null")
|
|
469
|
-
fi
|
|
470
|
-
fi
|
|
471
|
-
if [ "$next_agent" = "evaluator-visual" ]; then
|
|
472
|
-
func_eval="$PROJECT_ROOT/.harness/actions/evaluation-functional.md"
|
|
473
|
-
if [ -f "$func_eval" ]; then
|
|
474
|
-
cross_validation_data=$(sed -n '/```json/,/```/p' "$func_eval" | tail -n +2 | head -n -1 | jq 'select(.evaluator == "functional")' 2>/dev/null || echo "null")
|
|
475
|
-
fi
|
|
476
|
-
fi
|
|
477
|
-
|
|
478
|
-
# ── Build handoff.json (single source of truth for session transition) ──
|
|
479
|
-
jq -n \
|
|
480
|
-
--arg from "${current_agent:-dispatcher}" \
|
|
481
|
-
--arg to "$next_agent" \
|
|
482
|
-
--arg prompt "$prompt" \
|
|
483
|
-
--argjson sprint "$sprint_num" \
|
|
484
|
-
--argjson retry "$retry_count" \
|
|
485
|
-
--arg status "$sprint_status" \
|
|
486
|
-
--arg agent_model "$agent_model" \
|
|
487
|
-
--arg agent_thinking "$agent_thinking" \
|
|
488
|
-
--arg failure_msg "${failure_msg:-}" \
|
|
489
|
-
--argjson artifacts "$artifacts_json" \
|
|
490
|
-
--argjson focus "$focus_features" \
|
|
491
|
-
--argjson regression "$regression_source" \
|
|
492
|
-
--argjson eval_config "$eval_config" \
|
|
493
|
-
--argjson cross_val "$cross_validation_data" \
|
|
494
|
-
--argjson cross_val_cq "$cross_validation_from_code_quality" \
|
|
495
|
-
--arg timestamp "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
|
496
|
-
'{
|
|
497
|
-
from: $from,
|
|
498
|
-
to: $to,
|
|
499
|
-
prompt: $prompt,
|
|
500
|
-
sprint: $sprint,
|
|
501
|
-
retry_count: $retry,
|
|
502
|
-
sprint_status: $status,
|
|
503
|
-
model: $agent_model,
|
|
504
|
-
thinking_mode: (if $agent_thinking == "null" then null else $agent_thinking end),
|
|
505
|
-
failure_context: (if $failure_msg != "" then $failure_msg else null end),
|
|
506
|
-
artifacts_ready: $artifacts,
|
|
507
|
-
focus_features: $focus,
|
|
508
|
-
regression: $regression,
|
|
509
|
-
eval_config: $eval_config,
|
|
510
|
-
cross_validation_from_functional: $cross_val,
|
|
511
|
-
cross_validation_from_code_quality: $cross_val_cq,
|
|
512
|
-
warnings: [],
|
|
513
|
-
timestamp: $timestamp
|
|
514
|
-
}' > "$HANDOFF"
|
|
515
|
-
|
|
516
|
-
audit_handoff "${current_agent:-dispatcher}" "$next_agent" "complete"
|
|
517
|
-
|
|
518
|
-
elif [ "$next_agent" = "archive" ]; then
|
|
519
|
-
audit_log "system" "archive" "start" "sprint-${sprint_num}" "sprint cycle complete"
|
|
520
|
-
|
|
521
|
-
# ── Auto-archive: run archive script synchronously ──
|
|
522
|
-
# harness-archive.sh moves .harness/actions/* to .harness/archive/D-NNN/S-NNN/
|
|
523
|
-
# and resets progress.json. On next user prompt the flow starts fresh as a new dispatch.
|
|
524
|
-
if bash "$SCRIPT_DIR/harness-archive.sh" "$PROJECT_ROOT"; then
|
|
525
|
-
audit_log "system" "archive" "complete" "sprint-${sprint_num}" "auto-archived"
|
|
526
|
-
else
|
|
527
|
-
echo " ⚠ archive script failed — falling back to manual handoff" >&2
|
|
528
|
-
audit_log "system" "archive" "fail" "sprint-${sprint_num}" "script failed"
|
|
529
|
-
jq -n \
|
|
530
|
-
--arg from "${current_agent:-evaluator}" \
|
|
531
|
-
--argjson sprint "$sprint_num" \
|
|
532
|
-
--arg timestamp "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
|
533
|
-
'{
|
|
534
|
-
from: $from,
|
|
535
|
-
to: "archive",
|
|
536
|
-
prompt: "Sprint 문서를 아카이브하세요. harness-archive.sh 가 실패했습니다 — 수동으로 .harness/actions/* 를 .harness/archive/D-NNN/S-NNN/ 로 이동하세요.",
|
|
537
|
-
sprint: $sprint,
|
|
538
|
-
model: "opus",
|
|
539
|
-
thinking_mode: null,
|
|
540
|
-
timestamp: $timestamp
|
|
541
|
-
}' > "$HANDOFF"
|
|
542
|
-
fi
|
|
543
|
-
|
|
544
|
-
else
|
|
545
|
-
echo '{}' > "$HANDOFF"
|
|
546
|
-
fi
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# harness-progress-set.sh — Safe partial update to .harness/progress.json
|
|
3
|
-
#
|
|
4
|
-
# Usage:
|
|
5
|
-
# bash scripts/harness-progress-set.sh <project-root> <jq_filter>
|
|
6
|
-
#
|
|
7
|
-
# The jq filter is a partial assignment expression. All other fields are
|
|
8
|
-
# preserved. NEVER rewrite progress.json as a full JSON blob — mode,
|
|
9
|
-
# team_state, and similar top-level fields will be lost.
|
|
10
|
-
#
|
|
11
|
-
# Example:
|
|
12
|
-
# bash scripts/harness-progress-set.sh . '.current_agent = "planner" | .agent_status = "running"'
|
|
13
|
-
|
|
14
|
-
set -uo pipefail
|
|
15
|
-
|
|
16
|
-
PROJECT_ROOT="${1:-.}"
|
|
17
|
-
FILTER="${2:-}"
|
|
18
|
-
|
|
19
|
-
if [ -z "$FILTER" ]; then
|
|
20
|
-
echo "[progress-set] usage: $0 <project-root> <jq_filter>" >&2
|
|
21
|
-
exit 2
|
|
22
|
-
fi
|
|
23
|
-
|
|
24
|
-
PROGRESS="$PROJECT_ROOT/.harness/progress.json"
|
|
25
|
-
|
|
26
|
-
if [ ! -f "$PROGRESS" ]; then
|
|
27
|
-
echo "[progress-set] not found: $PROGRESS" >&2
|
|
28
|
-
exit 1
|
|
29
|
-
fi
|
|
30
|
-
|
|
31
|
-
# Always add .updated_at = now so callers don't forget
|
|
32
|
-
FINAL_FILTER="$FILTER | .updated_at = (now | todate)"
|
|
33
|
-
|
|
34
|
-
TMP="${PROGRESS}.tmp.$$"
|
|
35
|
-
if jq "$FINAL_FILTER" "$PROGRESS" > "$TMP" 2>/dev/null; then
|
|
36
|
-
mv "$TMP" "$PROGRESS"
|
|
37
|
-
else
|
|
38
|
-
rm -f "$TMP"
|
|
39
|
-
echo "[progress-set] jq filter failed: $FILTER" >&2
|
|
40
|
-
exit 1
|
|
41
|
-
fi
|