@walwal-harness/cli 6.1.5 → 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 +12 -0
- package/assets/launchd/com.walwal.harness-wake.plist.template +1 -1
- package/assets/templates/company-pipeline-manifest.json +71 -0
- package/assets/templates/config.json +4 -0
- package/bin/init.js +1 -0
- package/conventions/shared.md +18 -0
- package/package.json +1 -1
- package/scripts/conductor-tick.sh +97 -4
- package/scripts/harness-hourly-review.sh +284 -5
- package/scripts/harness-parity-audit.sh +101 -0
- package/scripts/harness-queue-manager.sh +1 -1
- package/scripts/harness-service-ops-monitor.sh +48 -3
- package/scripts/harness-wake-install.sh +1 -0
- package/scripts/harness-wake.sh +100 -8
- package/scripts/harness-worker-dispatch.sh +52 -21
- package/scripts/lib/harness-agent-resolver.sh +127 -0
package/scripts/harness-wake.sh
CHANGED
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
|
|
12
12
|
set -uo pipefail
|
|
13
13
|
|
|
14
|
+
export PATH="$HOME/.local/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:${PATH:-}"
|
|
15
|
+
|
|
14
16
|
LOG_DIR="${HOME}/.walwal-harness/logs"
|
|
15
17
|
mkdir -p "$LOG_DIR"
|
|
16
18
|
LOG="$LOG_DIR/wake.log"
|
|
@@ -57,14 +59,81 @@ find_tmux_session() {
|
|
|
57
59
|
return 1
|
|
58
60
|
}
|
|
59
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
|
+
|
|
60
128
|
start_headless_tick() {
|
|
61
129
|
local project_root="$1"
|
|
62
130
|
local idle="$2"
|
|
63
131
|
local force="$3"
|
|
64
132
|
local review_path="$4"
|
|
65
|
-
local mode stamp wake_dir ops_dir prompt_rel log_rel prompt_path log_path pid_file old_pid pid status tmux_session
|
|
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
|
|
66
134
|
|
|
67
135
|
mode="${HARNESS_WAKE_MODE:-headless}"
|
|
136
|
+
executor="${HARNESS_WAKE_EXECUTOR:-claude}"
|
|
68
137
|
stamp="$(date -u "+%Y%m%dT%H%M%SZ")"
|
|
69
138
|
wake_dir="$project_root/.harness/actions/wake"
|
|
70
139
|
ops_dir="$project_root/.harness/ops/wake"
|
|
@@ -105,7 +174,8 @@ Tasks:
|
|
|
105
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.
|
|
106
175
|
4. Check conductor state against the current GOAL and route the next department without waiting for Owner.
|
|
107
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.
|
|
108
|
-
6.
|
|
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.
|
|
109
179
|
EOF
|
|
110
180
|
|
|
111
181
|
case "$mode" in
|
|
@@ -113,16 +183,36 @@ EOF
|
|
|
113
183
|
status="recorded"
|
|
114
184
|
;;
|
|
115
185
|
headless|"")
|
|
116
|
-
if
|
|
186
|
+
if agent_bin="$(resolve_agent_bin "$executor")"; then
|
|
117
187
|
(
|
|
118
188
|
cd "$project_root" || exit 1
|
|
119
|
-
|
|
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
|
|
120
194
|
) &
|
|
121
195
|
pid=$!
|
|
122
196
|
echo "$pid" > "$pid_file"
|
|
123
|
-
status="spawned pid=$pid"
|
|
197
|
+
status="spawned executor=$executor pid=$pid"
|
|
124
198
|
else
|
|
125
|
-
status="
|
|
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"
|
|
126
216
|
fi
|
|
127
217
|
;;
|
|
128
218
|
*)
|
|
@@ -130,8 +220,10 @@ EOF
|
|
|
130
220
|
;;
|
|
131
221
|
esac
|
|
132
222
|
|
|
133
|
-
echo "$(date -u "+%Y-%m-%dT%H:%M:%SZ") | wake |
|
|
134
|
-
say "wake-
|
|
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"
|
|
135
227
|
|
|
136
228
|
if tmux_session="$(find_tmux_session "$project_root" 2>/dev/null)"; then
|
|
137
229
|
tmux display-message -t "$tmux_session" "walwal wake headless tick: $status ($log_rel)" 2>/dev/null || true
|
|
@@ -11,6 +11,7 @@ set -uo pipefail
|
|
|
11
11
|
|
|
12
12
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
13
13
|
source "$SCRIPT_DIR/lib/harness-render-progress.sh"
|
|
14
|
+
source "$SCRIPT_DIR/lib/harness-agent-resolver.sh"
|
|
14
15
|
|
|
15
16
|
PROJECT_ROOT="$(resolve_harness_root "${1:-.}")" || exit 0
|
|
16
17
|
PROGRESS="$PROJECT_ROOT/.harness/progress.json"
|
|
@@ -19,6 +20,8 @@ FEATURES="$PROJECT_ROOT/.harness/actions/feature-list.json"
|
|
|
19
20
|
QUEUE="$PROJECT_ROOT/.harness/actions/feature-queue.json"
|
|
20
21
|
WORKER_DIR="$PROJECT_ROOT/.harness/actions/workers"
|
|
21
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}"
|
|
22
25
|
|
|
23
26
|
[ -f "$PROGRESS" ] || exit 0
|
|
24
27
|
[ -f "$CONFIG" ] || exit 0
|
|
@@ -28,27 +31,41 @@ command -v jq >/dev/null 2>&1 || exit 0
|
|
|
28
31
|
|
|
29
32
|
mkdir -p "$WORKER_DIR" "$LOG_DIR"
|
|
30
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
|
+
|
|
31
64
|
spawn_mode="$(jq -r '.company_mode.worker_spawn // "claude"' "$CONFIG" 2>/dev/null || echo claude)"
|
|
32
65
|
pipeline="$(jq -r '.pipeline // "FULLSTACK"' "$PROGRESS" 2>/dev/null || echo FULLSTACK)"
|
|
33
66
|
ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
34
67
|
stamp="$(date -u +%Y%m%dT%H%M%SZ)"
|
|
35
68
|
|
|
36
|
-
agent_for_feature() {
|
|
37
|
-
local fid="$1"
|
|
38
|
-
local layer service title
|
|
39
|
-
layer="$(jq -r --arg id "$fid" '.features[]? | select(.id == $id) | (.layer // .type // "")' "$FEATURES" 2>/dev/null | head -1)"
|
|
40
|
-
service="$(jq -r --arg id "$fid" '.features[]? | select(.id == $id) | (.service // "")' "$FEATURES" 2>/dev/null | head -1)"
|
|
41
|
-
title="$(jq -r --arg id "$fid" '.features[]? | select(.id == $id) | (.title // .name // .description // "")' "$FEATURES" 2>/dev/null | head -1)"
|
|
42
|
-
case "$(printf '%s %s %s' "$layer" "$service" "$title" | tr '[:upper:]' '[:lower:]')" in
|
|
43
|
-
*frontend*|*ui*|*web*|*react*|*next*) echo "generator-frontend" ;;
|
|
44
|
-
*design*|*token*|*component-spec*) echo "generator-designer" ;;
|
|
45
|
-
*devops*|*infra*|*deploy*|*ci*) echo "generator-devops" ;;
|
|
46
|
-
*)
|
|
47
|
-
if [ "$pipeline" = "FE-ONLY" ]; then echo "generator-frontend"; else echo "generator-backend"; fi
|
|
48
|
-
;;
|
|
49
|
-
esac
|
|
50
|
-
}
|
|
51
|
-
|
|
52
69
|
build_prompt() {
|
|
53
70
|
local team="$1" fid="$2" agent="$3" prompt_path="$4" log_path="$5"
|
|
54
71
|
cat > "$prompt_path" <<EOF
|
|
@@ -103,12 +120,24 @@ i=0
|
|
|
103
120
|
while [ "$i" -lt "$count" ]; do
|
|
104
121
|
team="$(jq -r ".[$i].team" <<<"$pairs")"
|
|
105
122
|
fid="$(jq -r ".[$i].feature" <<<"$pairs")"
|
|
106
|
-
|
|
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)"
|
|
107
125
|
prompt_rel=".harness/actions/workers/T${team}-${fid}-${stamp}.prompt.md"
|
|
108
126
|
log_rel=".harness/ops/workers/T${team}-${fid}-${stamp}.log"
|
|
109
127
|
prompt_path="$PROJECT_ROOT/$prompt_rel"
|
|
110
128
|
log_path="$PROJECT_ROOT/$log_rel"
|
|
111
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
|
+
|
|
112
141
|
build_prompt "$team" "$fid" "$agent" "$prompt_path" "$log_path"
|
|
113
142
|
|
|
114
143
|
pid="null"
|
|
@@ -123,22 +152,24 @@ while [ "$i" -lt "$count" ]; do
|
|
|
123
152
|
fi
|
|
124
153
|
|
|
125
154
|
tmpq="${QUEUE}.tmp.$$.$i"
|
|
126
|
-
jq --arg tid "$team" --arg agent "$agent" --arg prompt "$prompt_rel" --arg log "$log_rel" --arg status "$status" --argjson pid "$pid" --arg ts "$ts" '
|
|
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" '
|
|
127
156
|
.teams[$tid].pid = $pid |
|
|
128
157
|
.teams[$tid].agent = $agent |
|
|
158
|
+
.teams[$tid].phase = $phase |
|
|
129
159
|
.teams[$tid].prompt = $prompt |
|
|
130
160
|
.teams[$tid].log = $log |
|
|
131
161
|
.teams[$tid].spawn_status = $status |
|
|
132
162
|
.teams[$tid].started_at = $ts |
|
|
133
163
|
.queue.in_progress[.teams[$tid].feature].agent = $agent |
|
|
164
|
+
.queue.in_progress[.teams[$tid].feature].phase = $phase |
|
|
134
165
|
.queue.in_progress[.teams[$tid].feature].prompt = $prompt |
|
|
135
166
|
.queue.in_progress[.teams[$tid].feature].log = $log |
|
|
136
167
|
.queue.in_progress[.teams[$tid].feature].pid = $pid
|
|
137
168
|
' "$QUEUE" > "$tmpq" && mv "$tmpq" "$QUEUE"
|
|
138
169
|
|
|
139
|
-
jq -n --argjson team "$team" --arg feature "$fid" --arg agent "$agent" --arg prompt "$prompt_rel" --arg log "$log_rel" --arg status "$status" --argjson pid "$pid" \
|
|
140
|
-
'{team:$team, feature:$feature, agent:$agent, prompt:$prompt, log:$log, status:$status, pid:$pid}' >> "$dispatches_file"
|
|
141
|
-
echo "$ts | conductor | worker-dispatch | $status | T${team}/${fid} | $agent pid=$pid" >> "$PROJECT_ROOT/.harness/progress.log"
|
|
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"
|
|
142
173
|
i=$((i + 1))
|
|
143
174
|
done
|
|
144
175
|
|
|
@@ -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
|