@walwal-harness/cli 6.1.4 → 6.1.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/CHANGELOG.md +40 -0
- package/README.md +43 -74
- package/assets/launchd/com.walwal.harness-wake.plist.template +32 -0
- package/assets/templates/CONVENTIONS.md +12 -0
- package/assets/templates/HARNESS.md +8 -8
- package/assets/templates/company-pipeline-manifest.json +71 -0
- package/assets/templates/config.json +29 -27
- package/assets/templates/memory.md +16 -2
- package/assets/templates/progress.json.template +6 -5
- package/bin/init.js +180 -90
- package/conventions/shared.md +34 -0
- package/gotchas/conductor.md +16 -0
- package/gotchas/dispatcher.md +16 -0
- package/gotchas/meeting-manager.md +8 -0
- package/gotchas/service-ops.md +8 -0
- package/package.json +4 -4
- package/scripts/conductor-tick.sh +157 -56
- package/scripts/harness-archive.sh +7 -5
- package/scripts/harness-dashboard-up.sh +11 -3
- package/scripts/harness-hourly-review.sh +582 -0
- package/scripts/harness-meeting-doc.sh +55 -16
- package/scripts/harness-next.sh +1 -1
- package/scripts/harness-parity-audit.sh +101 -0
- package/scripts/harness-queue-manager.sh +13 -6
- package/scripts/harness-service-ops-monitor.sh +273 -0
- package/scripts/harness-session-start.sh +60 -31
- package/scripts/harness-statusline.sh +8 -12
- package/scripts/harness-stop.sh +85 -0
- package/scripts/harness-user-prompt-submit.sh +11 -29
- package/scripts/harness-wake-install.sh +180 -0
- package/scripts/harness-wake.sh +350 -0
- package/scripts/harness-worker-dispatch.sh +188 -0
- package/scripts/lib/harness-agent-resolver.sh +127 -0
- package/scripts/lib/harness-progress-migrate.sh +6 -1
- package/scripts/lib/harness-render-progress.sh +3 -3
- package/skills/brainstorming/SKILL.md +2 -2
- package/skills/conductor/SKILL.md +67 -55
- package/skills/cqo/SKILL.md +1 -0
- package/skills/cto/SKILL.md +1 -0
- package/skills/dispatcher/SKILL.md +33 -14
- package/skills/evaluator-code-quality/SKILL.md +4 -4
- package/skills/evaluator-functional/SKILL.md +6 -6
- package/skills/evaluator-visual/SKILL.md +4 -4
- package/skills/generator-backend/SKILL.md +4 -4
- package/skills/generator-frontend/SKILL.md +5 -5
- package/skills/meeting-manager/SKILL.md +82 -12
- package/skills/planner/SKILL.md +3 -3
- package/skills/service-ops/SKILL.md +25 -0
- package/commands/harness-solo.md +0 -103
- package/commands/harness-stop.md +0 -53
- package/commands/harness-team.md +0 -530
- 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-monitor.sh +0 -398
- package/scripts/harness-prompt-history.sh +0 -164
- package/scripts/harness-tmux.sh +0 -372
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# harness-wake.sh — hourly safety wake (v6.2)
|
|
3
|
+
#
|
|
4
|
+
# launchd 가 1시간 주기로 호출. 등록된 모든 walwal-harness 프로젝트에 대해:
|
|
5
|
+
# 1) idle 한 시간이 임계치 이상이면 (.harness/progress.json mtime 기준, review 실행 전 캡처)
|
|
6
|
+
# 2) deterministic hourly review 를 디스크에 남김
|
|
7
|
+
# 3) 기본값으로 기존 tmux 세션에 긴 prompt 를 주입하지 않고 headless 1-tick Claude 를 실행
|
|
8
|
+
#
|
|
9
|
+
# 등록 방식: ~/.walwal-harness/projects.list 에 한 줄에 하나씩 절대경로 기재.
|
|
10
|
+
# 또는 환경변수 WALWAL_HARNESS_PROJECTS 에 콜론(:)으로 구분된 절대경로 목록.
|
|
11
|
+
|
|
12
|
+
set -uo pipefail
|
|
13
|
+
|
|
14
|
+
export PATH="$HOME/.local/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:${PATH:-}"
|
|
15
|
+
|
|
16
|
+
LOG_DIR="${HOME}/.walwal-harness/logs"
|
|
17
|
+
mkdir -p "$LOG_DIR"
|
|
18
|
+
LOG="$LOG_DIR/wake.log"
|
|
19
|
+
|
|
20
|
+
ts() { date "+%Y-%m-%d %H:%M:%S"; }
|
|
21
|
+
say() { echo "[$(ts)] $*" >> "$LOG"; }
|
|
22
|
+
|
|
23
|
+
stat_mtime() {
|
|
24
|
+
local path="$1"
|
|
25
|
+
stat -f "%m" "$path" 2>/dev/null || stat -c "%Y" "$path" 2>/dev/null
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
in_force_wake_window() {
|
|
29
|
+
local hhmm start end
|
|
30
|
+
hhmm="$(date "+%H%M")"
|
|
31
|
+
start="${HARNESS_WAKE_FORCE_START_HHMM:-0555}"
|
|
32
|
+
end="${HARNESS_WAKE_FORCE_END_HHMM:-0615}"
|
|
33
|
+
|
|
34
|
+
# 기본값은 Anthropic 5-hour reset 이 자주 걸리는 06:00 KST 주변.
|
|
35
|
+
# StartInterval=3600 이 몇 분 밀려 실행돼도 복구되도록 20분 창을 둔다.
|
|
36
|
+
[ "$((10#$hhmm))" -ge "$((10#$start))" ] && [ "$((10#$hhmm))" -le "$((10#$end))" ]
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
find_tmux_session() {
|
|
40
|
+
local project_root="$1"
|
|
41
|
+
local base sanitized upper var cand
|
|
42
|
+
|
|
43
|
+
base=$(basename "$project_root")
|
|
44
|
+
sanitized=$(echo "$base" | tr -- '-. ' '___')
|
|
45
|
+
for cand in "claude_${sanitized}_" "claude_${base}_"; do
|
|
46
|
+
if tmux has-session -t "$cand" 2>/dev/null; then
|
|
47
|
+
echo "$cand"
|
|
48
|
+
return 0
|
|
49
|
+
fi
|
|
50
|
+
done
|
|
51
|
+
|
|
52
|
+
upper=$(echo "$sanitized" | tr 'a-z' 'A-Z')
|
|
53
|
+
var="WALWAL_HARNESS_TMUX_${upper}"
|
|
54
|
+
if [ -n "${!var:-}" ] && tmux has-session -t "${!var}" 2>/dev/null; then
|
|
55
|
+
echo "${!var}"
|
|
56
|
+
return 0
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
return 1
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
resolve_agent_bin() {
|
|
63
|
+
local executor="$1"
|
|
64
|
+
local env_name cand
|
|
65
|
+
|
|
66
|
+
case "$executor" in
|
|
67
|
+
codex)
|
|
68
|
+
env_name="${HARNESS_CODEX_BIN:-${CODEX_BIN:-}}"
|
|
69
|
+
;;
|
|
70
|
+
claude|*)
|
|
71
|
+
env_name="${HARNESS_CLAUDE_BIN:-${CLAUDE_BIN:-}}"
|
|
72
|
+
executor="claude"
|
|
73
|
+
;;
|
|
74
|
+
esac
|
|
75
|
+
|
|
76
|
+
for cand in "$env_name" "$HOME/.local/bin/$executor" "/opt/homebrew/bin/$executor" "/usr/local/bin/$executor"; do
|
|
77
|
+
[ -n "$cand" ] && [ -x "$cand" ] && { echo "$cand"; return 0; }
|
|
78
|
+
done
|
|
79
|
+
|
|
80
|
+
command -v "$executor" 2>/dev/null && return 0
|
|
81
|
+
return 1
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
run_conductor_fallback() {
|
|
85
|
+
local project_root="$1"
|
|
86
|
+
local reason="$2"
|
|
87
|
+
local tick="$project_root/scripts/conductor-tick.sh"
|
|
88
|
+
local progress="$project_root/.harness/progress.json"
|
|
89
|
+
local progress_set="$project_root/scripts/harness-progress-set.sh"
|
|
90
|
+
local before after status
|
|
91
|
+
|
|
92
|
+
[ "${HARNESS_WAKE_CONDUCTOR_FALLBACK:-1}" = "1" ] || return 0
|
|
93
|
+
[ -x "$tick" ] || return 0
|
|
94
|
+
[ -f "$progress" ] || return 0
|
|
95
|
+
command -v jq >/dev/null 2>&1 || return 0
|
|
96
|
+
|
|
97
|
+
before="$(jq -c '{current_agent,next_agent,agent_status,conductor:(.conductor.state // null),decision:(.meetings.decision // {})}' "$progress" 2>/dev/null || echo '{}')"
|
|
98
|
+
|
|
99
|
+
if [ -x "$progress_set" ] &&
|
|
100
|
+
jq -e '
|
|
101
|
+
(.meetings.decision.owner // "") != "" and
|
|
102
|
+
((((.meetings.active // []) | length) > 0) or ((.meetings.decision.required_execution // null) != null))
|
|
103
|
+
' "$progress" >/dev/null 2>&1 && {
|
|
104
|
+
[ "$(jq -r '.current_agent // ""' "$progress")" != "meeting-manager" ] ||
|
|
105
|
+
[ "$(jq -r '.agent_status // ""' "$progress")" != "completed" ];
|
|
106
|
+
}; then
|
|
107
|
+
bash "$progress_set" "$project_root" '
|
|
108
|
+
.current_agent = "meeting-manager" |
|
|
109
|
+
.agent_status = "completed" |
|
|
110
|
+
.conductor.state = "running" |
|
|
111
|
+
.conductor.current_action = "wake-meeting-decision-ready" |
|
|
112
|
+
.workflow.last_transition = (now | todate) |
|
|
113
|
+
.workflow.last_reason = "wake-meeting-decision-ready"
|
|
114
|
+
' >/dev/null 2>&1 || true
|
|
115
|
+
fi
|
|
116
|
+
|
|
117
|
+
if bash "$tick" "$project_root" >/dev/null 2>&1; then
|
|
118
|
+
status="ok"
|
|
119
|
+
else
|
|
120
|
+
status="failed"
|
|
121
|
+
fi
|
|
122
|
+
|
|
123
|
+
after="$(jq -c '{current_agent,next_agent,agent_status,conductor:(.conductor.state // null),last_action:(.conductor.current_action // null)}' "$progress" 2>/dev/null || echo '{}')"
|
|
124
|
+
echo "$(date -u "+%Y-%m-%dT%H:%M:%SZ") | wake | conductor-fallback | $status | reason=$reason | before=$before | after=$after" >> "$project_root/.harness/progress.log"
|
|
125
|
+
say "conductor-fallback: $project_root ($status, reason=$reason)"
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
start_headless_tick() {
|
|
129
|
+
local project_root="$1"
|
|
130
|
+
local idle="$2"
|
|
131
|
+
local force="$3"
|
|
132
|
+
local review_path="$4"
|
|
133
|
+
local mode executor agent_bin stamp wake_dir ops_dir prompt_rel log_rel prompt_path log_path pid_file old_pid pid status tmux_session session_name session_safe
|
|
134
|
+
|
|
135
|
+
mode="${HARNESS_WAKE_MODE:-headless}"
|
|
136
|
+
executor="${HARNESS_WAKE_EXECUTOR:-claude}"
|
|
137
|
+
stamp="$(date -u "+%Y%m%dT%H%M%SZ")"
|
|
138
|
+
wake_dir="$project_root/.harness/actions/wake"
|
|
139
|
+
ops_dir="$project_root/.harness/ops/wake"
|
|
140
|
+
mkdir -p "$wake_dir" "$ops_dir"
|
|
141
|
+
|
|
142
|
+
prompt_rel=".harness/actions/wake/wake-${stamp}.prompt.md"
|
|
143
|
+
log_rel=".harness/ops/wake/wake-${stamp}.log"
|
|
144
|
+
prompt_path="$project_root/$prompt_rel"
|
|
145
|
+
log_path="$project_root/$log_rel"
|
|
146
|
+
pid_file="$ops_dir/wake.pid"
|
|
147
|
+
|
|
148
|
+
if [ -f "$pid_file" ]; then
|
|
149
|
+
old_pid="$(cat "$pid_file" 2>/dev/null || true)"
|
|
150
|
+
if [ -n "$old_pid" ] && kill -0 "$old_pid" 2>/dev/null; then
|
|
151
|
+
say "skip-headless: $project_root (previous wake pid=$old_pid still running)"
|
|
152
|
+
return 0
|
|
153
|
+
fi
|
|
154
|
+
fi
|
|
155
|
+
|
|
156
|
+
cat > "$prompt_path" <<EOF
|
|
157
|
+
You are the walwal-harness autonomous hourly wake tick.
|
|
158
|
+
|
|
159
|
+
Project root: $project_root
|
|
160
|
+
Wake reason: idle=${idle}s force=${force}
|
|
161
|
+
Hourly review: ${review_path:-"(not available)"}
|
|
162
|
+
|
|
163
|
+
Policy:
|
|
164
|
+
- Owner input after the initial GOAL is an interrupt or additional request, never a reason to wait.
|
|
165
|
+
- Never set conductor.state to waiting_owner. Normalize any waiting_owner state to waiting_meeting and continue.
|
|
166
|
+
- meeting-manager is the default synchronization loop. Incidents, service warnings, goal drift, and escalations go to meeting-manager first.
|
|
167
|
+
- Keep this to one bounded autonomous tick. Do not run an endless loop.
|
|
168
|
+
- Do not write future timestamps to .harness/progress.log or artifacts.
|
|
169
|
+
- Keep terminal output concise; write evidence to project files.
|
|
170
|
+
|
|
171
|
+
Tasks:
|
|
172
|
+
1. Read CONVENTIONS.md if present, .harness/conventions/shared.md, relevant .harness/conventions/<role>.md, .harness/gotchas/<role>.md, .harness/memory.md, .harness/progress.json, and the hourly review above.
|
|
173
|
+
2. If service_ops.requested_mode is monitor, run or trigger one service-ops monitor pass and record last_check. stream_active may be true only during the pass and must be false when finished.
|
|
174
|
+
3. Treat the hourly review as Executive Meeting Minutes. Verify it contains CEO/COO/CTO/CQO/Service-Ops role positions, discussion, decision JSON, and action items.
|
|
175
|
+
4. Check conductor state against the current GOAL and route the next department without waiting for Owner.
|
|
176
|
+
5. If there is an active incident or operational warning, make sure meeting-manager has shared context and an action decision that names CTO/CQO/Service-Ops responsibilities.
|
|
177
|
+
6. Split meaningful progress from paperwork-only artifacts in your summary.
|
|
178
|
+
7. Append a short factual summary to .harness/progress.log and any normal harness artifacts used by this project.
|
|
179
|
+
EOF
|
|
180
|
+
|
|
181
|
+
case "$mode" in
|
|
182
|
+
record)
|
|
183
|
+
status="recorded"
|
|
184
|
+
;;
|
|
185
|
+
headless|"")
|
|
186
|
+
if agent_bin="$(resolve_agent_bin "$executor")"; then
|
|
187
|
+
(
|
|
188
|
+
cd "$project_root" || exit 1
|
|
189
|
+
if [ "$executor" = "codex" ]; then
|
|
190
|
+
"$agent_bin" exec -C "$project_root" "$(cat "$prompt_path")" > "$log_path" 2>&1
|
|
191
|
+
else
|
|
192
|
+
"$agent_bin" -p "$(cat "$prompt_path")" > "$log_path" 2>&1
|
|
193
|
+
fi
|
|
194
|
+
) &
|
|
195
|
+
pid=$!
|
|
196
|
+
echo "$pid" > "$pid_file"
|
|
197
|
+
status="spawned executor=$executor pid=$pid"
|
|
198
|
+
else
|
|
199
|
+
status="BLOCKED_RUNTIME ${executor}-not-found"
|
|
200
|
+
fi
|
|
201
|
+
;;
|
|
202
|
+
tmux)
|
|
203
|
+
if ! command -v tmux >/dev/null 2>&1; then
|
|
204
|
+
status="BLOCKED_RUNTIME tmux-not-found"
|
|
205
|
+
elif agent_bin="$(resolve_agent_bin "$executor")"; then
|
|
206
|
+
session_safe="$(basename "$project_root" | tr -c '[:alnum:]_' '_')"
|
|
207
|
+
session_name="walwal_${session_safe}_${stamp}"
|
|
208
|
+
if [ "$executor" = "codex" ]; then
|
|
209
|
+
tmux new-session -d -s "$session_name" "cd '$project_root' && '$agent_bin' exec -C '$project_root' - < '$prompt_path' 2>&1 | tee '$log_path'"
|
|
210
|
+
else
|
|
211
|
+
tmux new-session -d -s "$session_name" "cd '$project_root' && '$agent_bin' -p \"\$(cat '$prompt_path')\" 2>&1 | tee '$log_path'"
|
|
212
|
+
fi
|
|
213
|
+
status="spawned-tmux executor=$executor session=$session_name"
|
|
214
|
+
else
|
|
215
|
+
status="BLOCKED_RUNTIME ${executor}-not-found"
|
|
216
|
+
fi
|
|
217
|
+
;;
|
|
218
|
+
*)
|
|
219
|
+
status="recorded unsupported-mode=$mode"
|
|
220
|
+
;;
|
|
221
|
+
esac
|
|
222
|
+
|
|
223
|
+
echo "$(date -u "+%Y-%m-%dT%H:%M:%SZ") | wake | agent-tick | $status | idle=${idle}s | force=${force} | mode=${mode} | prompt=$prompt_rel | log=$log_rel" >> "$project_root/.harness/progress.log"
|
|
224
|
+
say "wake-agent: $project_root (idle ${idle}s, force=${force}, mode=${mode}, executor=${executor}) → $status $log_rel"
|
|
225
|
+
|
|
226
|
+
run_conductor_fallback "$project_root" "$status"
|
|
227
|
+
|
|
228
|
+
if tmux_session="$(find_tmux_session "$project_root" 2>/dev/null)"; then
|
|
229
|
+
tmux display-message -t "$tmux_session" "walwal wake headless tick: $status ($log_rel)" 2>/dev/null || true
|
|
230
|
+
fi
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
PROJECTS_LIST="${HOME}/.walwal-harness/projects.list"
|
|
234
|
+
PROJECTS=()
|
|
235
|
+
|
|
236
|
+
if [ -n "${WALWAL_HARNESS_PROJECTS:-}" ]; then
|
|
237
|
+
IFS=':' read -r -a PROJECTS <<< "$WALWAL_HARNESS_PROJECTS"
|
|
238
|
+
elif [ -f "$PROJECTS_LIST" ]; then
|
|
239
|
+
while IFS= read -r line; do
|
|
240
|
+
line="${line%%#*}"
|
|
241
|
+
line="$(echo "$line" | xargs)"
|
|
242
|
+
[ -z "$line" ] && continue
|
|
243
|
+
PROJECTS+=("$line")
|
|
244
|
+
done < "$PROJECTS_LIST"
|
|
245
|
+
fi
|
|
246
|
+
|
|
247
|
+
if [ "${#PROJECTS[@]}" -eq 0 ]; then
|
|
248
|
+
say "no registered projects (set ~/.walwal-harness/projects.list or WALWAL_HARNESS_PROJECTS)"
|
|
249
|
+
exit 0
|
|
250
|
+
fi
|
|
251
|
+
|
|
252
|
+
# 임계치: idle 60분 이상이면 깨운다 (Stop 훅이 정상 동작하면 거의 연속 활동이므로
|
|
253
|
+
# 1시간 이상 정지 = 어디선가 멈춘 상태로 판단)
|
|
254
|
+
IDLE_THRESHOLD_SECONDS="${HARNESS_WAKE_IDLE_SECONDS:-3300}" # 55분 (안전 마진)
|
|
255
|
+
NOW_EPOCH=$(date "+%s")
|
|
256
|
+
FORCE_WAKE=0
|
|
257
|
+
if in_force_wake_window; then
|
|
258
|
+
FORCE_WAKE=1
|
|
259
|
+
fi
|
|
260
|
+
|
|
261
|
+
for PROJECT_ROOT in "${PROJECTS[@]}"; do
|
|
262
|
+
if [ ! -d "$PROJECT_ROOT" ]; then
|
|
263
|
+
say "skip: $PROJECT_ROOT (not a directory)"
|
|
264
|
+
continue
|
|
265
|
+
fi
|
|
266
|
+
PROGRESS="$PROJECT_ROOT/.harness/progress.json"
|
|
267
|
+
if [ ! -f "$PROGRESS" ]; then
|
|
268
|
+
say "skip: $PROJECT_ROOT (no .harness/progress.json)"
|
|
269
|
+
continue
|
|
270
|
+
fi
|
|
271
|
+
PROJECT_FORCE_WAKE="$FORCE_WAKE"
|
|
272
|
+
|
|
273
|
+
if command -v jq >/dev/null 2>&1 && [ "$(jq -r '.conductor.state // "idle"' "$PROGRESS" 2>/dev/null || echo idle)" = "waiting_owner" ]; then
|
|
274
|
+
if [ -x "$PROJECT_ROOT/scripts/harness-progress-set.sh" ]; then
|
|
275
|
+
bash "$PROJECT_ROOT/scripts/harness-progress-set.sh" "$PROJECT_ROOT" \
|
|
276
|
+
'.conductor.state = "waiting_meeting" |
|
|
277
|
+
.conductor.current_action = "autonomous-normalize-waiting-owner" |
|
|
278
|
+
.next_agent = "meeting-manager" |
|
|
279
|
+
.agent_status = "pending" |
|
|
280
|
+
.meetings.active = ((.meetings.active // []) + ["meeting-manager"] | unique) |
|
|
281
|
+
.meetings.requested_type = (.meetings.requested_type // "followup-review") |
|
|
282
|
+
.meetings.requested_reason = (.meetings.requested_reason // "autonomous-normalize-waiting-owner") |
|
|
283
|
+
.workflow.stage = "ops-monitoring" |
|
|
284
|
+
.workflow.last_transition = (now | todate) |
|
|
285
|
+
.workflow.last_reason = "autonomous-normalize-waiting-owner"' \
|
|
286
|
+
>/dev/null 2>&1 || true
|
|
287
|
+
say "normalize: $PROJECT_ROOT (conductor=waiting_owner → waiting_meeting)"
|
|
288
|
+
PROJECT_FORCE_WAKE=1
|
|
289
|
+
fi
|
|
290
|
+
fi
|
|
291
|
+
|
|
292
|
+
# idle 시간은 hourly-review 실행 전에 캡처한다. review 는 progress.json 과
|
|
293
|
+
# progress.log 를 갱신하므로, 이후 mtime 을 보면 wake 가 자기 활동을
|
|
294
|
+
# "프로젝트 활동 중"으로 오판한다.
|
|
295
|
+
if MTIME=$(stat_mtime "$PROGRESS"); then :;
|
|
296
|
+
else
|
|
297
|
+
say "skip: $PROJECT_ROOT (cannot stat progress.json)"
|
|
298
|
+
continue
|
|
299
|
+
fi
|
|
300
|
+
IDLE=$((NOW_EPOCH - MTIME))
|
|
301
|
+
|
|
302
|
+
# Every hourly batch must leave disk-backed evidence, even if Claude/tmux is
|
|
303
|
+
# stopped or busy. This deterministic review runs after the idle snapshot.
|
|
304
|
+
if [ -x "$PROJECT_ROOT/scripts/harness-hourly-review.sh" ]; then
|
|
305
|
+
review_output=$(bash "$PROJECT_ROOT/scripts/harness-hourly-review.sh" "$PROJECT_ROOT" 2>/dev/null || true)
|
|
306
|
+
review_path=$(printf "%s\n" "$review_output" | grep -Eo '(/[^[:space:]]+|[.][^[:space:]]+)\.md' | tail -1)
|
|
307
|
+
if [ -z "$review_path" ]; then
|
|
308
|
+
review_path=$(printf "%s\n" "$review_output" | tail -1)
|
|
309
|
+
fi
|
|
310
|
+
if [ -n "$review_path" ]; then
|
|
311
|
+
say "review: $PROJECT_ROOT → $review_path"
|
|
312
|
+
else
|
|
313
|
+
say "review-fail: $PROJECT_ROOT"
|
|
314
|
+
fi
|
|
315
|
+
fi
|
|
316
|
+
|
|
317
|
+
if [ "$IDLE" -lt "$IDLE_THRESHOLD_SECONDS" ] && [ "$PROJECT_FORCE_WAKE" -ne 1 ]; then
|
|
318
|
+
say "skip: $PROJECT_ROOT (idle ${IDLE}s < threshold ${IDLE_THRESHOLD_SECONDS}s — 활동 중)"
|
|
319
|
+
continue
|
|
320
|
+
fi
|
|
321
|
+
|
|
322
|
+
# 회사 루프가 멈춘 상태인지 확인
|
|
323
|
+
if command -v jq >/dev/null 2>&1; then
|
|
324
|
+
CSTATE=$(jq -r '.conductor.state // "idle"' "$PROGRESS" 2>/dev/null || echo "idle")
|
|
325
|
+
SSTATUS=$(jq -r '.sprint.status // "init"' "$PROGRESS" 2>/dev/null || echo "init")
|
|
326
|
+
if [ "$SSTATUS" = "completed" ] || [ "$SSTATUS" = "aborted" ]; then
|
|
327
|
+
say "continue: $PROJECT_ROOT (sprint=$SSTATUS — autonomous review still required)"
|
|
328
|
+
fi
|
|
329
|
+
if [ "$CSTATE" = "paused" ] || [ "$CSTATE" = "completed" ] || [ "$CSTATE" = "escalated" ]; then
|
|
330
|
+
say "continue: $PROJECT_ROOT (conductor=$CSTATE — route through meeting-manager/service-ops)"
|
|
331
|
+
fi
|
|
332
|
+
fi
|
|
333
|
+
|
|
334
|
+
# 발화 직전 service-ops monitor 트리거 partial update.
|
|
335
|
+
# requested_mode 만 set — conductor-tick 이 이걸 보고 service-ops 를 spawn 한다.
|
|
336
|
+
# stream_active 는 service-ops 본인이 spawn 시 §3.1.1 에 따라 직접 set (정직성 룰).
|
|
337
|
+
# auto-retro / incident / null / monitor 가 아닌 모드가 진행 중이면 덮어쓰지 않음.
|
|
338
|
+
if [ -x "$PROJECT_ROOT/scripts/harness-progress-set.sh" ]; then
|
|
339
|
+
bash "$PROJECT_ROOT/scripts/harness-progress-set.sh" "$PROJECT_ROOT" \
|
|
340
|
+
'if (.service_ops.requested_mode // "") == "" or (.service_ops.requested_mode // "") == "monitor" or (.service_ops.requested_mode // "") == "auto-retro"
|
|
341
|
+
then .service_ops.requested_mode = "monitor" |
|
|
342
|
+
.service_ops.monitor.wake_trigger_at = (now | todate)
|
|
343
|
+
else . end' \
|
|
344
|
+
>/dev/null 2>&1 || true
|
|
345
|
+
fi
|
|
346
|
+
|
|
347
|
+
start_headless_tick "$PROJECT_ROOT" "$IDLE" "$PROJECT_FORCE_WAKE" "$review_path"
|
|
348
|
+
done
|
|
349
|
+
|
|
350
|
+
exit 0
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# harness-worker-dispatch.sh — connect feature queue to actual worker launches
|
|
3
|
+
#
|
|
4
|
+
# Deterministic responsibilities:
|
|
5
|
+
# 1. auto-dispatch ready features to idle worker slots
|
|
6
|
+
# 2. create per-worker prompt + log paths
|
|
7
|
+
# 3. optionally launch `claude -p` workers in background
|
|
8
|
+
# 4. write progress.json.company_state so dashboards/CLI show real activity
|
|
9
|
+
|
|
10
|
+
set -uo pipefail
|
|
11
|
+
|
|
12
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
13
|
+
source "$SCRIPT_DIR/lib/harness-render-progress.sh"
|
|
14
|
+
source "$SCRIPT_DIR/lib/harness-agent-resolver.sh"
|
|
15
|
+
|
|
16
|
+
PROJECT_ROOT="$(resolve_harness_root "${1:-.}")" || exit 0
|
|
17
|
+
PROGRESS="$PROJECT_ROOT/.harness/progress.json"
|
|
18
|
+
CONFIG="$PROJECT_ROOT/.harness/config.json"
|
|
19
|
+
FEATURES="$PROJECT_ROOT/.harness/actions/feature-list.json"
|
|
20
|
+
QUEUE="$PROJECT_ROOT/.harness/actions/feature-queue.json"
|
|
21
|
+
WORKER_DIR="$PROJECT_ROOT/.harness/actions/workers"
|
|
22
|
+
LOG_DIR="$PROJECT_ROOT/.harness/ops/workers"
|
|
23
|
+
DISPATCH_LOCK="$PROJECT_ROOT/.harness/.worker-dispatch-lock"
|
|
24
|
+
DISPATCH_LOCK_STALE_SEC="${DISPATCH_LOCK_STALE_SEC:-900}"
|
|
25
|
+
|
|
26
|
+
[ -f "$PROGRESS" ] || exit 0
|
|
27
|
+
[ -f "$CONFIG" ] || exit 0
|
|
28
|
+
[ -f "$FEATURES" ] || exit 0
|
|
29
|
+
[ -x "$SCRIPT_DIR/harness-queue-manager.sh" ] || exit 0
|
|
30
|
+
command -v jq >/dev/null 2>&1 || exit 0
|
|
31
|
+
|
|
32
|
+
mkdir -p "$WORKER_DIR" "$LOG_DIR"
|
|
33
|
+
|
|
34
|
+
acquire_dispatch_lock() {
|
|
35
|
+
if mkdir "$DISPATCH_LOCK" 2>/dev/null; then
|
|
36
|
+
printf '%s\n' "$$" > "$DISPATCH_LOCK/pid"
|
|
37
|
+
printf '%s\n' "$(date +%s)" > "$DISPATCH_LOCK/created_at"
|
|
38
|
+
trap 'rm -rf "$DISPATCH_LOCK" 2>/dev/null || true' EXIT INT TERM
|
|
39
|
+
return 0
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
local now created age holder
|
|
43
|
+
now="$(date +%s)"
|
|
44
|
+
created="$(cat "$DISPATCH_LOCK/created_at" 2>/dev/null || echo "$now")"
|
|
45
|
+
holder="$(cat "$DISPATCH_LOCK/pid" 2>/dev/null || echo unknown)"
|
|
46
|
+
age=$((now - created))
|
|
47
|
+
if [ "$age" -ge "$DISPATCH_LOCK_STALE_SEC" ]; then
|
|
48
|
+
rm -rf "$DISPATCH_LOCK" 2>/dev/null || true
|
|
49
|
+
if mkdir "$DISPATCH_LOCK" 2>/dev/null; then
|
|
50
|
+
printf '%s\n' "$$" > "$DISPATCH_LOCK/pid"
|
|
51
|
+
printf '%s\n' "$now" > "$DISPATCH_LOCK/created_at"
|
|
52
|
+
trap 'rm -rf "$DISPATCH_LOCK" 2>/dev/null || true' EXIT INT TERM
|
|
53
|
+
return 0
|
|
54
|
+
fi
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) | conductor | worker-dispatch | skipped-locked | holder=${holder} age=${age}s" >> "$PROJECT_ROOT/.harness/progress.log"
|
|
58
|
+
echo "[]"
|
|
59
|
+
exit 0
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
acquire_dispatch_lock
|
|
63
|
+
|
|
64
|
+
spawn_mode="$(jq -r '.company_mode.worker_spawn // "claude"' "$CONFIG" 2>/dev/null || echo claude)"
|
|
65
|
+
pipeline="$(jq -r '.pipeline // "FULLSTACK"' "$PROGRESS" 2>/dev/null || echo FULLSTACK)"
|
|
66
|
+
ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
67
|
+
stamp="$(date -u +%Y%m%dT%H%M%SZ)"
|
|
68
|
+
|
|
69
|
+
build_prompt() {
|
|
70
|
+
local team="$1" fid="$2" agent="$3" prompt_path="$4" log_path="$5"
|
|
71
|
+
cat > "$prompt_path" <<EOF
|
|
72
|
+
You are running as walwal-harness company worker.
|
|
73
|
+
|
|
74
|
+
Agent: harness-${agent}
|
|
75
|
+
Worker slot: ${team}
|
|
76
|
+
Feature ID: ${fid}
|
|
77
|
+
Project root: ${PROJECT_ROOT}
|
|
78
|
+
|
|
79
|
+
Follow these rules exactly:
|
|
80
|
+
1. Read AGENTS.md, CONVENTIONS.md if present, .harness/conventions/shared.md, .harness/conventions/${agent}.md if present, .harness/gotchas/${agent}.md if present, .harness/memory.md, .harness/progress.json, .harness/actions/feature-list.json, .harness/actions/api-contract.json, and the relevant harness skill for ${agent}.
|
|
81
|
+
2. Work only on FEATURE_ID=${fid}. Do not broaden scope.
|
|
82
|
+
3. Respect IA ownership and do not edit files outside your department.
|
|
83
|
+
4. Write a report to .harness/actions/gen-report-${fid}.md if you are a generator, or .harness/actions/evaluation-${fid}.md if you are an evaluator.
|
|
84
|
+
5. On success, update feature-list passes and run: bash scripts/harness-queue-manager.sh pass ${fid}
|
|
85
|
+
6. On failure, write the reason and run: bash scripts/harness-queue-manager.sh fail ${fid} "<short reason>"
|
|
86
|
+
7. Use partial progress updates only via scripts/harness-progress-set.sh.
|
|
87
|
+
8. Append real timestamped progress to .harness/progress.log. Never write future timestamps.
|
|
88
|
+
|
|
89
|
+
This worker log is: ${log_path#$PROJECT_ROOT/}
|
|
90
|
+
EOF
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if [ ! -f "$QUEUE" ]; then
|
|
94
|
+
bash "$SCRIPT_DIR/harness-queue-manager.sh" init all "$PROJECT_ROOT" >/dev/null 2>&1 || exit 0
|
|
95
|
+
else
|
|
96
|
+
bash "$SCRIPT_DIR/harness-queue-manager.sh" recover "$PROJECT_ROOT" >/dev/null 2>&1 || true
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
pairs="$(bash "$SCRIPT_DIR/harness-queue-manager.sh" auto-dispatch "$PROJECT_ROOT" 2>/dev/null || echo "[]")"
|
|
100
|
+
if ! jq -e 'type == "array"' >/dev/null 2>&1 <<<"$pairs"; then
|
|
101
|
+
pairs="[]"
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
count="$(jq 'length' <<<"$pairs")"
|
|
105
|
+
if [ "$count" -eq 0 ]; then
|
|
106
|
+
active_workers="$(jq '[.teams | to_entries[] | select(.value.status == "busy") | {team:(.key|tonumber), feature:.value.feature, pid:.value.pid, phase:(.value.phase // "gen")}]' "$QUEUE" 2>/dev/null || echo "[]")"
|
|
107
|
+
jq --arg ts "$ts" --argjson workers "$active_workers" '
|
|
108
|
+
.company_state.active_workers = ($workers | length) |
|
|
109
|
+
.company_state.workers = $workers |
|
|
110
|
+
.company_state.last_dispatch_at = (.company_state.last_dispatch_at // null)
|
|
111
|
+
' "$PROGRESS" > "${PROGRESS}.tmp" && mv "${PROGRESS}.tmp" "$PROGRESS"
|
|
112
|
+
echo "[]"
|
|
113
|
+
exit 0
|
|
114
|
+
fi
|
|
115
|
+
|
|
116
|
+
dispatches_file="$(mktemp)"
|
|
117
|
+
: > "$dispatches_file"
|
|
118
|
+
|
|
119
|
+
i=0
|
|
120
|
+
while [ "$i" -lt "$count" ]; do
|
|
121
|
+
team="$(jq -r ".[$i].team" <<<"$pairs")"
|
|
122
|
+
fid="$(jq -r ".[$i].feature" <<<"$pairs")"
|
|
123
|
+
phase="$(jq -r --arg fid "$fid" '.queue.in_progress[$fid].phase // "gen"' "$QUEUE" 2>/dev/null || echo gen)"
|
|
124
|
+
agent="$(resolve_feature_agent "$PROJECT_ROOT" "$fid" "$phase" "$pipeline" 2>/dev/null || true)"
|
|
125
|
+
prompt_rel=".harness/actions/workers/T${team}-${fid}-${stamp}.prompt.md"
|
|
126
|
+
log_rel=".harness/ops/workers/T${team}-${fid}-${stamp}.log"
|
|
127
|
+
prompt_path="$PROJECT_ROOT/$prompt_rel"
|
|
128
|
+
log_path="$PROJECT_ROOT/$log_rel"
|
|
129
|
+
|
|
130
|
+
if [ -z "$agent" ]; then
|
|
131
|
+
reason="agent_resolution_failed phase=${phase}"
|
|
132
|
+
bash "$SCRIPT_DIR/harness-queue-manager.sh" fail "$fid" "$PROJECT_ROOT" >/dev/null 2>&1 || true
|
|
133
|
+
printf '%s\n' "$reason" > "$log_path"
|
|
134
|
+
jq -n --argjson team "$team" --arg feature "$fid" --arg phase "$phase" --arg status "blocked" --arg reason "$reason" \
|
|
135
|
+
'{team:$team, feature:$feature, phase:$phase, agent:null, prompt:null, log:null, status:$status, reason:$reason, pid:null}' >> "$dispatches_file"
|
|
136
|
+
echo "$ts | conductor | worker-dispatch | blocked | T${team}/${fid} | ${reason}" >> "$PROJECT_ROOT/.harness/progress.log"
|
|
137
|
+
i=$((i + 1))
|
|
138
|
+
continue
|
|
139
|
+
fi
|
|
140
|
+
|
|
141
|
+
build_prompt "$team" "$fid" "$agent" "$prompt_path" "$log_path"
|
|
142
|
+
|
|
143
|
+
pid="null"
|
|
144
|
+
status="recorded"
|
|
145
|
+
if [ "$spawn_mode" = "claude" ] && command -v claude >/dev/null 2>&1; then
|
|
146
|
+
(
|
|
147
|
+
cd "$PROJECT_ROOT" || exit 1
|
|
148
|
+
claude -p "$(cat "$prompt_path")" > "$log_path" 2>&1
|
|
149
|
+
) &
|
|
150
|
+
pid="$!"
|
|
151
|
+
status="spawned"
|
|
152
|
+
fi
|
|
153
|
+
|
|
154
|
+
tmpq="${QUEUE}.tmp.$$.$i"
|
|
155
|
+
jq --arg tid "$team" --arg agent "$agent" --arg phase "$phase" --arg prompt "$prompt_rel" --arg log "$log_rel" --arg status "$status" --argjson pid "$pid" --arg ts "$ts" '
|
|
156
|
+
.teams[$tid].pid = $pid |
|
|
157
|
+
.teams[$tid].agent = $agent |
|
|
158
|
+
.teams[$tid].phase = $phase |
|
|
159
|
+
.teams[$tid].prompt = $prompt |
|
|
160
|
+
.teams[$tid].log = $log |
|
|
161
|
+
.teams[$tid].spawn_status = $status |
|
|
162
|
+
.teams[$tid].started_at = $ts |
|
|
163
|
+
.queue.in_progress[.teams[$tid].feature].agent = $agent |
|
|
164
|
+
.queue.in_progress[.teams[$tid].feature].phase = $phase |
|
|
165
|
+
.queue.in_progress[.teams[$tid].feature].prompt = $prompt |
|
|
166
|
+
.queue.in_progress[.teams[$tid].feature].log = $log |
|
|
167
|
+
.queue.in_progress[.teams[$tid].feature].pid = $pid
|
|
168
|
+
' "$QUEUE" > "$tmpq" && mv "$tmpq" "$QUEUE"
|
|
169
|
+
|
|
170
|
+
jq -n --argjson team "$team" --arg feature "$fid" --arg agent "$agent" --arg phase "$phase" --arg prompt "$prompt_rel" --arg log "$log_rel" --arg status "$status" --argjson pid "$pid" \
|
|
171
|
+
'{team:$team, feature:$feature, agent:$agent, phase:$phase, prompt:$prompt, log:$log, status:$status, pid:$pid}' >> "$dispatches_file"
|
|
172
|
+
echo "$ts | conductor | worker-dispatch | $status | T${team}/${fid} | $agent phase=$phase pid=$pid" >> "$PROJECT_ROOT/.harness/progress.log"
|
|
173
|
+
i=$((i + 1))
|
|
174
|
+
done
|
|
175
|
+
|
|
176
|
+
dispatches="$(jq -s '.' "$dispatches_file")"
|
|
177
|
+
rm -f "$dispatches_file"
|
|
178
|
+
active_workers="$(jq '[.teams | to_entries[] | select(.value.status == "busy") | {team:(.key|tonumber), feature:.value.feature, agent:.value.agent, pid:.value.pid, phase:(.value.phase // "gen"), prompt:.value.prompt, log:.value.log, spawn_status:.value.spawn_status}]' "$QUEUE")"
|
|
179
|
+
|
|
180
|
+
jq --arg ts "$ts" --argjson dispatches "$dispatches" --argjson workers "$active_workers" '
|
|
181
|
+
.company_state.active_workers = ($workers | length) |
|
|
182
|
+
.company_state.workers = $workers |
|
|
183
|
+
.company_state.last_dispatch_at = $ts |
|
|
184
|
+
.company_state.last_dispatch = $dispatches |
|
|
185
|
+
.conductor.worker_dispatches = ((.conductor.worker_dispatches // []) + $dispatches)
|
|
186
|
+
' "$PROGRESS" > "${PROGRESS}.tmp" && mv "${PROGRESS}.tmp" "$PROGRESS"
|
|
187
|
+
|
|
188
|
+
echo "$dispatches"
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# harness-agent-resolver.sh — canonical feature/phase → harness agent resolver
|
|
3
|
+
#
|
|
4
|
+
# Worker dispatch must not guess the department when Planner already wrote
|
|
5
|
+
# owner_agents/applicable_eval_axes. This resolver is intentionally strict:
|
|
6
|
+
# declared metadata wins; keyword fallback is only allowed when metadata is
|
|
7
|
+
# absent.
|
|
8
|
+
|
|
9
|
+
set -uo pipefail
|
|
10
|
+
|
|
11
|
+
resolve_feature_agent() {
|
|
12
|
+
local project_root="$1" fid="$2" phase="${3:-gen}" pipeline="${4:-FULLSTACK}"
|
|
13
|
+
local features="$project_root/.harness/actions/feature-list.json"
|
|
14
|
+
|
|
15
|
+
[ -f "$features" ] || return 2
|
|
16
|
+
command -v jq >/dev/null 2>&1 || return 2
|
|
17
|
+
|
|
18
|
+
local feature_json
|
|
19
|
+
feature_json="$(jq -c --arg id "$fid" '.features[]? | select(.id == $id)' "$features" 2>/dev/null | head -1)"
|
|
20
|
+
[ -n "$feature_json" ] && [ "$feature_json" != "null" ] || return 3
|
|
21
|
+
|
|
22
|
+
case "$phase" in
|
|
23
|
+
eval|eval:*|evaluation|evaluator-*)
|
|
24
|
+
_resolve_eval_agent "$feature_json" "$phase"
|
|
25
|
+
return $?
|
|
26
|
+
;;
|
|
27
|
+
*)
|
|
28
|
+
_resolve_generation_agent "$feature_json" "$pipeline"
|
|
29
|
+
return $?
|
|
30
|
+
;;
|
|
31
|
+
esac
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
_resolve_generation_agent() {
|
|
35
|
+
local feature_json="$1" pipeline="$2"
|
|
36
|
+
local owner_count agent
|
|
37
|
+
|
|
38
|
+
owner_count="$(jq '(.owner_agents // []) | length' <<<"$feature_json")"
|
|
39
|
+
if [ "${owner_count:-0}" -gt 0 ]; then
|
|
40
|
+
agent="$(jq -r '
|
|
41
|
+
(.owner_agents // [])
|
|
42
|
+
| map(select(
|
|
43
|
+
. == "generator-backend" or
|
|
44
|
+
. == "generator-frontend" or
|
|
45
|
+
. == "generator-designer" or
|
|
46
|
+
. == "generator-devops"
|
|
47
|
+
))
|
|
48
|
+
| .[0] // empty
|
|
49
|
+
' <<<"$feature_json")"
|
|
50
|
+
[ -n "$agent" ] || return 4
|
|
51
|
+
echo "$agent"
|
|
52
|
+
return 0
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
local layer service title text
|
|
56
|
+
layer="$(jq -r '(.layer // .type // "")' <<<"$feature_json")"
|
|
57
|
+
service="$(jq -r '(.service // "")' <<<"$feature_json")"
|
|
58
|
+
title="$(jq -r '(.title // .name // .description // "")' <<<"$feature_json")"
|
|
59
|
+
text="$(printf '%s %s %s' "$layer" "$service" "$title" | tr '[:upper:]' '[:lower:]')"
|
|
60
|
+
|
|
61
|
+
case "$text" in
|
|
62
|
+
*frontend*|*ui*|*web*|*react*|*next*) echo "generator-frontend" ;;
|
|
63
|
+
*design*|*token*|*component-spec*) echo "generator-designer" ;;
|
|
64
|
+
*devops*|*infra*|*deploy*|*ci*) echo "generator-devops" ;;
|
|
65
|
+
*)
|
|
66
|
+
if [ "$pipeline" = "FE-ONLY" ]; then echo "generator-frontend"; else echo "generator-backend"; fi
|
|
67
|
+
;;
|
|
68
|
+
esac
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
_resolve_eval_agent() {
|
|
72
|
+
local feature_json="$1" phase="$2"
|
|
73
|
+
local requested_axis axes_count agent
|
|
74
|
+
|
|
75
|
+
requested_axis="${phase#eval:}"
|
|
76
|
+
requested_axis="${requested_axis#evaluator-}"
|
|
77
|
+
[ "$requested_axis" = "$phase" ] && requested_axis=""
|
|
78
|
+
[ "$requested_axis" = "eval" ] && requested_axis=""
|
|
79
|
+
[ "$requested_axis" = "evaluation" ] && requested_axis=""
|
|
80
|
+
|
|
81
|
+
axes_count="$(jq '(.applicable_eval_axes // []) | length' <<<"$feature_json")"
|
|
82
|
+
[ "${axes_count:-0}" -gt 0 ] || return 5
|
|
83
|
+
|
|
84
|
+
if [ -n "$requested_axis" ]; then
|
|
85
|
+
agent="$(_axis_to_evaluator "$requested_axis")" || return 6
|
|
86
|
+
jq -e --arg agent "$agent" '
|
|
87
|
+
def map_axis:
|
|
88
|
+
if . == "code-quality" or . == "code_quality" or . == "evaluator-code-quality" then "evaluator-code-quality"
|
|
89
|
+
elif . == "functional" or . == "evaluator-functional" then "evaluator-functional"
|
|
90
|
+
elif . == "visual" or . == "evaluator-visual" then "evaluator-visual"
|
|
91
|
+
elif . == "architecture" or . == "evaluator-architecture" then "evaluator-architecture"
|
|
92
|
+
elif . == "security" or . == "evaluator-security" then "evaluator-security"
|
|
93
|
+
else empty end;
|
|
94
|
+
(.applicable_eval_axes // []) | map(map_axis) | index($agent)
|
|
95
|
+
' <<<"$feature_json" >/dev/null || return 7
|
|
96
|
+
echo "$agent"
|
|
97
|
+
return 0
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
agent="$(jq -r '
|
|
101
|
+
def map_axis:
|
|
102
|
+
if . == "code-quality" or . == "code_quality" or . == "evaluator-code-quality" then "evaluator-code-quality"
|
|
103
|
+
elif . == "functional" or . == "evaluator-functional" then "evaluator-functional"
|
|
104
|
+
elif . == "visual" or . == "evaluator-visual" then "evaluator-visual"
|
|
105
|
+
elif . == "architecture" or . == "evaluator-architecture" then "evaluator-architecture"
|
|
106
|
+
elif . == "security" or . == "evaluator-security" then "evaluator-security"
|
|
107
|
+
else empty end;
|
|
108
|
+
(.applicable_eval_axes // []) | map(map_axis) | .[0] // empty
|
|
109
|
+
' <<<"$feature_json")"
|
|
110
|
+
[ -n "$agent" ] || return 6
|
|
111
|
+
echo "$agent"
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
_axis_to_evaluator() {
|
|
115
|
+
case "$1" in
|
|
116
|
+
code-quality|code_quality|evaluator-code-quality) echo "evaluator-code-quality" ;;
|
|
117
|
+
functional|evaluator-functional) echo "evaluator-functional" ;;
|
|
118
|
+
visual|evaluator-visual) echo "evaluator-visual" ;;
|
|
119
|
+
architecture|evaluator-architecture) echo "evaluator-architecture" ;;
|
|
120
|
+
security|evaluator-security) echo "evaluator-security" ;;
|
|
121
|
+
*) return 1 ;;
|
|
122
|
+
esac
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if [ "${BASH_SOURCE[0]}" = "$0" ]; then
|
|
126
|
+
resolve_feature_agent "${1:-.}" "${2:-}" "${3:-gen}" "${4:-FULLSTACK}"
|
|
127
|
+
fi
|