@walwal-harness/cli 4.0.0-alpha.6 → 4.0.0-alpha.8
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@walwal-harness/cli",
|
|
3
|
-
"version": "4.0.0-alpha.
|
|
3
|
+
"version": "4.0.0-alpha.8",
|
|
4
4
|
"description": "Production harness for AI agent engineering — Planner, Generator(BE/FE), Evaluator(Func/Visual), optional Brainstormer (requirements refinement). Supports React and Flutter FE stacks.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"walwal-harness": "bin/init.js"
|
|
@@ -149,10 +149,54 @@ render_feature_list() {
|
|
|
149
149
|
echo ""
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
+
render_prompt_history() {
|
|
153
|
+
local log_file="$PROJECT_ROOT/.harness/progress.log"
|
|
154
|
+
if [ ! -f "$log_file" ]; then return; fi
|
|
155
|
+
|
|
156
|
+
# Get terminal height to limit display
|
|
157
|
+
local term_h
|
|
158
|
+
term_h=$(tput lines 2>/dev/null || echo 50)
|
|
159
|
+
local max_lines=10 # show latest 10 entries
|
|
160
|
+
|
|
161
|
+
echo -e " ${BOLD}Prompt History${RESET} ${DIM}(newest first)${RESET}"
|
|
162
|
+
|
|
163
|
+
# Read non-comment lines, reverse (newest first), take max_lines
|
|
164
|
+
grep -v '^#' "$log_file" 2>/dev/null | grep -v '^$' | tail -r 2>/dev/null | head -"$max_lines" | \
|
|
165
|
+
while IFS= read -r line; do
|
|
166
|
+
# Parse: date | agent | action | detail
|
|
167
|
+
local ts agent action detail
|
|
168
|
+
ts=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$1); print $1}')
|
|
169
|
+
agent=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$2); print $2}')
|
|
170
|
+
action=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$3); print $3}')
|
|
171
|
+
detail=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$4); print $4}')
|
|
172
|
+
|
|
173
|
+
local short_ts icon color
|
|
174
|
+
short_ts=$(echo "$ts" | sed 's/^[0-9]*-//')
|
|
175
|
+
|
|
176
|
+
case "$agent" in
|
|
177
|
+
dispatcher*|dispatch) icon="▸"; color="$MAGENTA" ;;
|
|
178
|
+
team-*) icon="⚡"; color="$CYAN" ;;
|
|
179
|
+
manual|user) icon="★"; color="$BOLD" ;;
|
|
180
|
+
planner*) icon="□"; color="$YELLOW" ;;
|
|
181
|
+
generator*|gen*) icon="▶"; color="$GREEN" ;;
|
|
182
|
+
eval*) icon="✦"; color="$RED" ;;
|
|
183
|
+
system) icon="⚙"; color="$DIM" ;;
|
|
184
|
+
*) icon="·"; color="$DIM" ;;
|
|
185
|
+
esac
|
|
186
|
+
|
|
187
|
+
if [ ${#detail} -gt 45 ]; then detail="${detail:0:43}.."; fi
|
|
188
|
+
|
|
189
|
+
echo -e " ${color}${icon}${RESET} ${DIM}${short_ts}${RESET} ${agent} ${DIM}${action}${RESET} ${detail}"
|
|
190
|
+
done
|
|
191
|
+
|
|
192
|
+
echo ""
|
|
193
|
+
}
|
|
194
|
+
|
|
152
195
|
render_all() {
|
|
153
196
|
render_header
|
|
154
197
|
render_queue_summary
|
|
155
198
|
render_teams
|
|
199
|
+
render_prompt_history
|
|
156
200
|
render_feature_list
|
|
157
201
|
echo -e " ${DIM}Refreshing every 3s${RESET}"
|
|
158
202
|
}
|
|
@@ -42,6 +42,25 @@ fi
|
|
|
42
42
|
FEATURES="$PROJECT_ROOT/.harness/actions/feature-list.json"
|
|
43
43
|
QUEUE="$PROJECT_ROOT/.harness/actions/feature-queue.json"
|
|
44
44
|
CONFIG="$PROJECT_ROOT/.harness/config.json"
|
|
45
|
+
QUEUE_LOCK="$PROJECT_ROOT/.harness/.queue-lock"
|
|
46
|
+
|
|
47
|
+
# ── Atomic queue lock — prevent race conditions between teams ──
|
|
48
|
+
acquire_queue_lock() {
|
|
49
|
+
local max_wait=30 waited=0
|
|
50
|
+
while ! mkdir "$QUEUE_LOCK" 2>/dev/null; do
|
|
51
|
+
sleep 0.1
|
|
52
|
+
waited=$((waited + 1))
|
|
53
|
+
if [ "$waited" -ge $((max_wait * 10)) ]; then
|
|
54
|
+
rm -rf "$QUEUE_LOCK"
|
|
55
|
+
mkdir "$QUEUE_LOCK" 2>/dev/null || true
|
|
56
|
+
break
|
|
57
|
+
fi
|
|
58
|
+
done
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
release_queue_lock() {
|
|
62
|
+
rm -rf "$QUEUE_LOCK" 2>/dev/null || true
|
|
63
|
+
}
|
|
45
64
|
|
|
46
65
|
# ── Concurrency from config ──
|
|
47
66
|
CONCURRENCY=3
|
|
@@ -122,12 +141,14 @@ cmd_dequeue() {
|
|
|
122
141
|
if [ -z "$team_id" ]; then echo "[queue] Usage: dequeue <team_id>"; exit 1; fi
|
|
123
142
|
if [ ! -f "$QUEUE" ]; then echo "[queue] Run 'init' first."; exit 1; fi
|
|
124
143
|
|
|
144
|
+
acquire_queue_lock
|
|
145
|
+
|
|
125
146
|
local feature
|
|
126
147
|
feature=$(jq -r '.queue.ready[0] // empty' "$QUEUE")
|
|
127
148
|
|
|
128
149
|
if [ -z "$feature" ]; then
|
|
150
|
+
release_queue_lock
|
|
129
151
|
echo "[queue] No features in ready queue."
|
|
130
|
-
# Check if all done
|
|
131
152
|
local in_prog blocked
|
|
132
153
|
in_prog=$(jq '.queue.in_progress | length' "$QUEUE")
|
|
133
154
|
blocked=$(jq '.queue.blocked | length' "$QUEUE")
|
|
@@ -137,13 +158,13 @@ cmd_dequeue() {
|
|
|
137
158
|
return 1
|
|
138
159
|
fi
|
|
139
160
|
|
|
140
|
-
# Move feature from ready → in_progress, assign to team
|
|
141
161
|
jq --arg fid "$feature" --arg tid "$team_id" '
|
|
142
162
|
.queue.ready -= [$fid] |
|
|
143
163
|
.queue.in_progress[$fid] = { team: ($tid | tonumber), phase: "gen", attempt: 1 } |
|
|
144
164
|
.teams[$tid] = { status: "busy", feature: $fid, branch: ("feature/" + $fid), pid: null }
|
|
145
165
|
' "$QUEUE" > "${QUEUE}.tmp" && mv "${QUEUE}.tmp" "$QUEUE"
|
|
146
166
|
|
|
167
|
+
release_queue_lock
|
|
147
168
|
echo "$feature"
|
|
148
169
|
}
|
|
149
170
|
|
|
@@ -155,11 +176,11 @@ cmd_pass() {
|
|
|
155
176
|
if [ -z "$fid" ]; then echo "[queue] Usage: pass <feature_id>"; exit 1; fi
|
|
156
177
|
if [ ! -f "$QUEUE" ]; then echo "[queue] Run 'init' first."; exit 1; fi
|
|
157
178
|
|
|
158
|
-
|
|
179
|
+
acquire_queue_lock
|
|
180
|
+
|
|
159
181
|
local team_id
|
|
160
182
|
team_id=$(jq -r --arg fid "$fid" '.queue.in_progress[$fid].team // empty' "$QUEUE")
|
|
161
183
|
|
|
162
|
-
# Move from in_progress → passed, free team, unblock dependents
|
|
163
184
|
jq --arg fid "$fid" --arg tid "${team_id:-0}" '
|
|
164
185
|
# Remove from in_progress
|
|
165
186
|
del(.queue.in_progress[$fid]) |
|
|
@@ -188,6 +209,7 @@ cmd_pass() {
|
|
|
188
209
|
|
|
189
210
|
local newly_ready
|
|
190
211
|
newly_ready=$(jq -r '.queue.ready | join(", ")' "$QUEUE")
|
|
212
|
+
release_queue_lock
|
|
191
213
|
echo "[queue] $fid PASSED. Ready: [$newly_ready]"
|
|
192
214
|
}
|
|
193
215
|
|
|
@@ -199,6 +221,8 @@ cmd_fail() {
|
|
|
199
221
|
if [ -z "$fid" ]; then echo "[queue] Usage: fail <feature_id>"; exit 1; fi
|
|
200
222
|
if [ ! -f "$QUEUE" ]; then exit 1; fi
|
|
201
223
|
|
|
224
|
+
acquire_queue_lock
|
|
225
|
+
|
|
202
226
|
local team_id
|
|
203
227
|
team_id=$(jq -r --arg fid "$fid" '.queue.in_progress[$fid].team // empty' "$QUEUE")
|
|
204
228
|
|
|
@@ -211,6 +235,7 @@ cmd_fail() {
|
|
|
211
235
|
else . end)
|
|
212
236
|
' "$QUEUE" > "${QUEUE}.tmp" && mv "${QUEUE}.tmp" "$QUEUE"
|
|
213
237
|
|
|
238
|
+
release_queue_lock
|
|
214
239
|
echo "[queue] $fid FAILED."
|
|
215
240
|
}
|
|
216
241
|
|
|
@@ -239,6 +264,8 @@ cmd_update_phase() {
|
|
|
239
264
|
exit 1
|
|
240
265
|
fi
|
|
241
266
|
|
|
267
|
+
acquire_queue_lock
|
|
268
|
+
|
|
242
269
|
local jq_expr
|
|
243
270
|
jq_expr=".queue.in_progress[\"$fid\"].phase = \"$phase\""
|
|
244
271
|
if [ -n "$attempt" ]; then
|
|
@@ -246,6 +273,7 @@ cmd_update_phase() {
|
|
|
246
273
|
fi
|
|
247
274
|
|
|
248
275
|
jq "$jq_expr" "$QUEUE" > "${QUEUE}.tmp" && mv "${QUEUE}.tmp" "$QUEUE"
|
|
276
|
+
release_queue_lock
|
|
249
277
|
}
|
|
250
278
|
|
|
251
279
|
# ══════════════════════════════════════════
|
|
@@ -297,6 +325,31 @@ cmd_status() {
|
|
|
297
325
|
fi
|
|
298
326
|
}
|
|
299
327
|
|
|
328
|
+
# ══════════════════════════════════════════
|
|
329
|
+
# recover — Move stale in_progress back to ready (after studio restart)
|
|
330
|
+
# ══════════════════════════════════════════
|
|
331
|
+
cmd_recover() {
|
|
332
|
+
if [ ! -f "$QUEUE" ]; then echo "[queue] Not initialized."; return; fi
|
|
333
|
+
|
|
334
|
+
local stale_count
|
|
335
|
+
stale_count=$(jq '.queue.in_progress | length' "$QUEUE" 2>/dev/null)
|
|
336
|
+
|
|
337
|
+
if [ "${stale_count:-0}" -eq 0 ]; then
|
|
338
|
+
echo "[queue] No stale in_progress entries."
|
|
339
|
+
return
|
|
340
|
+
fi
|
|
341
|
+
|
|
342
|
+
# Move all in_progress → ready, reset all teams to idle
|
|
343
|
+
jq '
|
|
344
|
+
.queue.ready += [.queue.in_progress | keys[]] |
|
|
345
|
+
.queue.ready |= unique |
|
|
346
|
+
.queue.in_progress = {} |
|
|
347
|
+
.teams |= with_entries(.value = { status: "idle", feature: null, branch: null, pid: null })
|
|
348
|
+
' "$QUEUE" > "${QUEUE}.tmp" && mv "${QUEUE}.tmp" "$QUEUE"
|
|
349
|
+
|
|
350
|
+
echo "[queue] Recovered ${stale_count} stale features back to ready queue."
|
|
351
|
+
}
|
|
352
|
+
|
|
300
353
|
# ── Dispatch ──
|
|
301
354
|
case "$CMD" in
|
|
302
355
|
init) cmd_init ;;
|
|
@@ -305,9 +358,10 @@ case "$CMD" in
|
|
|
305
358
|
fail) cmd_fail "$@" ;;
|
|
306
359
|
requeue) cmd_requeue "$@" ;;
|
|
307
360
|
update_phase) cmd_update_phase "$@" ;;
|
|
361
|
+
recover) cmd_recover ;;
|
|
308
362
|
status) cmd_status ;;
|
|
309
363
|
*)
|
|
310
|
-
echo "Usage: harness-queue-manager.sh <init|dequeue|pass|fail|requeue|update_phase|status> [args]"
|
|
364
|
+
echo "Usage: harness-queue-manager.sh <init|dequeue|pass|fail|requeue|recover|update_phase|status> [args]"
|
|
311
365
|
exit 1
|
|
312
366
|
;;
|
|
313
367
|
esac
|
|
@@ -54,11 +54,14 @@ echo "Session: $SESSION_NAME"
|
|
|
54
54
|
|
|
55
55
|
tmux kill-session -t "$SESSION_NAME" 2>/dev/null || true
|
|
56
56
|
|
|
57
|
-
# ── Initialize
|
|
57
|
+
# ── Initialize or recover queue ──
|
|
58
58
|
QUEUE="$PROJECT_ROOT/.harness/actions/feature-queue.json"
|
|
59
59
|
if [ ! -f "$QUEUE" ]; then
|
|
60
60
|
echo "Initializing feature queue..."
|
|
61
61
|
bash "$SCRIPT_DIR/harness-queue-manager.sh" init "$PROJECT_ROOT"
|
|
62
|
+
else
|
|
63
|
+
echo "Recovering stale queue state..."
|
|
64
|
+
bash "$SCRIPT_DIR/harness-queue-manager.sh" recover "$PROJECT_ROOT"
|
|
62
65
|
fi
|
|
63
66
|
|
|
64
67
|
# ══════════════════════════════════════════
|