Skip to content

Commit 4d9f937

Browse files
authored
Merge pull request #274 from Fr-e-d/contrib/sync-1777585299
sync: update 4 file(s) in core/
2 parents 865b57b + be28d09 commit 4d9f937

4 files changed

Lines changed: 488 additions & 2 deletions

File tree

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
#!/usr/bin/env bash
2+
# daemon-dispatch.sh — 3-phase dispatch library for delivery-daemon.sh (E134S02)
3+
#
4+
# Sourceable library. No top-level execution code.
5+
# Caller must set before sourcing or calling any function:
6+
# BACKLOG_FILE — absolute path to active.backlog.yaml
7+
# SCHEDULER — absolute path to backlog-scheduler.sh
8+
# PROJECT_DIR — repo root (for runtime-routing-logger.js)
9+
# Optional:
10+
# GAAI_STUB_DELAY_S — seconds to sleep between stubs (default: 0)
11+
# ROUTING_LOG_PATH — test-only override for --log-path (default: empty, uses logger default)
12+
13+
# ── Field extractors (AC1 — verbatim per story AC1 specification) ─────────
14+
15+
get_phase_status() {
16+
local id="$1"
17+
awk -v id="$id" '
18+
$0 == "- id: " id { found=1; next }
19+
found && /^- id:/ { exit }
20+
found && /^[[:space:]]+phase_status:/ {
21+
gsub(/^[[:space:]]+phase_status:[[:space:]]*/, "")
22+
gsub(/[[:space:]]*$/, "")
23+
gsub(/^"|"$/, "")
24+
print
25+
exit
26+
}
27+
' "$BACKLOG_FILE"
28+
}
29+
30+
get_delivery_pipeline() {
31+
local id="$1"
32+
awk -v id="$id" '
33+
$0 == "- id: " id { found=1; next }
34+
found && /^- id:/ { exit }
35+
found && /^[[:space:]]+delivery_pipeline:/ {
36+
gsub(/^[[:space:]]+delivery_pipeline:[[:space:]]*/, "")
37+
gsub(/[[:space:]]*$/, "")
38+
gsub(/^"|"$/, "")
39+
print
40+
exit
41+
}
42+
' "$BACKLOG_FILE"
43+
}
44+
45+
# Helper: read impl_model_tag from backlog (returns "absent" if unset/missing)
46+
get_impl_model_tag() {
47+
local id="$1"
48+
local val
49+
val=$(awk -v id="$id" '
50+
$0 == "- id: " id { found=1; next }
51+
found && /^- id:/ { exit }
52+
found && /^[[:space:]]+impl_model:/ {
53+
gsub(/^[[:space:]]+impl_model:[[:space:]]*/, "")
54+
gsub(/[[:space:]]*$/, "")
55+
gsub(/^"|"$/, "")
56+
print
57+
exit
58+
}
59+
' "$BACKLOG_FILE" 2>/dev/null || true)
60+
echo "${val:-absent}"
61+
}
62+
63+
# ── Routing record helper ─────────────────────────────────────────────────
64+
# Emits one JSONL record to runtime-routing.jsonl via runtime-routing-logger.js.
65+
# Arguments: story_id trace_id phase provider fallback_reason
66+
_emit_routing_record() {
67+
local story_id="$1" trace_id="$2" phase="$3" provider="$4" fallback_reason="$5"
68+
local impl_tag
69+
impl_tag=$(get_impl_model_tag "$story_id")
70+
71+
local log_path_args=()
72+
if [[ -n "${ROUTING_LOG_PATH:-}" ]]; then
73+
log_path_args=(--log-path "$ROUTING_LOG_PATH")
74+
fi
75+
76+
node "$PROJECT_DIR/.gaai/core/adapters/claude-code/runtime-routing-logger.js" \
77+
--trace-id "$trace_id" \
78+
--story-id "$story_id" \
79+
--phase "$phase" \
80+
--provider "$provider" \
81+
--model "n/a" \
82+
--duration-ms 0 \
83+
--fallback-reason "$fallback_reason" \
84+
--impl-model-tag "$impl_tag" \
85+
"${log_path_args[@]}" \
86+
2>/dev/null || true
87+
}
88+
89+
# ── Stub phase handlers (AC3 + AC4) ──────────────────────────────────────
90+
91+
handle_plan_phase() {
92+
local story_id="$1" trace_id="$2"
93+
local ts
94+
ts=$(date '+%H:%M:%S')
95+
echo "[${ts}] ${story_id} phase=plan dispatched (stub)"
96+
97+
# Emit routing record (AC4)
98+
_emit_routing_record "$story_id" "$trace_id" "plan" "stub" ""
99+
100+
# Advance phase_status: not_started → planned (AC3)
101+
if ! "$SCHEDULER" --set-phase-status "$story_id" planned "$BACKLOG_FILE" 2>/dev/null; then
102+
echo "[ERROR] ${story_id} handle_plan_phase: --set-phase-status planned failed"
103+
_emit_routing_record "$story_id" "$trace_id" "plan" "error" "set-phase-status-failed"
104+
return 1
105+
fi
106+
107+
sleep "${GAAI_STUB_DELAY_S:-0}"
108+
return 0
109+
}
110+
111+
handle_impl_phase() {
112+
local story_id="$1" trace_id="$2"
113+
local ts
114+
ts=$(date '+%H:%M:%S')
115+
echo "[${ts}] ${story_id} phase=impl dispatched (stub)"
116+
117+
# Emit routing record (AC4)
118+
_emit_routing_record "$story_id" "$trace_id" "impl" "stub" ""
119+
120+
# Advance phase_status: planned → implemented (AC3)
121+
if ! "$SCHEDULER" --set-phase-status "$story_id" implemented "$BACKLOG_FILE" 2>/dev/null; then
122+
echo "[ERROR] ${story_id} handle_impl_phase: --set-phase-status implemented failed"
123+
_emit_routing_record "$story_id" "$trace_id" "impl" "error" "set-phase-status-failed"
124+
return 1
125+
fi
126+
127+
sleep "${GAAI_STUB_DELAY_S:-0}"
128+
return 0
129+
}
130+
131+
handle_qa_phase() {
132+
local story_id="$1" trace_id="$2"
133+
local ts
134+
ts=$(date '+%H:%M:%S')
135+
echo "[${ts}] ${story_id} phase=qa dispatched (stub)"
136+
137+
# Emit routing record (AC4)
138+
_emit_routing_record "$story_id" "$trace_id" "qa" "stub" ""
139+
140+
# Advance phase_status: implemented → qa_passed (AC3)
141+
if ! "$SCHEDULER" --set-phase-status "$story_id" qa_passed "$BACKLOG_FILE" 2>/dev/null; then
142+
echo "[ERROR] ${story_id} handle_qa_phase: --set-phase-status qa_passed failed"
143+
_emit_routing_record "$story_id" "$trace_id" "qa" "error" "set-phase-status-failed"
144+
return 1
145+
fi
146+
147+
sleep "${GAAI_STUB_DELAY_S:-0}"
148+
return 0
149+
}
150+
151+
handle_commit_phase() {
152+
local story_id="$1" trace_id="$2"
153+
local ts
154+
ts=$(date '+%H:%M:%S')
155+
echo "[${ts}] ${story_id} phase=commit dispatched (stub)"
156+
157+
# Emit routing record (AC4)
158+
_emit_routing_record "$story_id" "$trace_id" "commit" "stub" ""
159+
160+
# Advance phase_status: qa_passed → done (stub — real commit/PR/merge is E134S06)
161+
if ! "$SCHEDULER" --set-phase-status "$story_id" done "$BACKLOG_FILE" 2>/dev/null; then
162+
echo "[ERROR] ${story_id} handle_commit_phase: --set-phase-status done failed"
163+
_emit_routing_record "$story_id" "$trace_id" "commit" "error" "set-phase-status-failed"
164+
return 1
165+
fi
166+
167+
sleep "${GAAI_STUB_DELAY_S:-0}"
168+
return 0
169+
}
170+
171+
# ── Main dispatcher (AC1 + AC6) ───────────────────────────────────────────
172+
#
173+
# Called by delivery-daemon.sh main loop for stories with delivery_pipeline=3phase.
174+
# Reads phase_status, routes to the appropriate handler for ONE phase, then returns.
175+
# The caller loops until phase_status is done/failed/escalated.
176+
#
177+
# Arguments: story_id [trace_id]
178+
# Returns: 0 on success, 1 on dispatch error (logs [ERROR] per AC6)
179+
dispatch_3phase_story() {
180+
local story_id="$1"
181+
local trace_id="${2:-$(python3 -c 'import uuid; print(str(uuid.uuid4()))' 2>/dev/null || echo "stub-$(date +%s)-$$")}"
182+
183+
# Read phase_status (AC1 — awk extractor)
184+
local ps
185+
ps=$(get_phase_status "$story_id")
186+
187+
if [[ -z "$ps" ]]; then
188+
# AC6(i): log ERROR
189+
echo "[ERROR] ${story_id} dispatch_3phase_story: phase_status field missing or empty"
190+
# AC6(iv): emit error routing record
191+
_emit_routing_record "$story_id" "$trace_id" "plan" "error" "phase_status_missing"
192+
# AC6(ii): return non-zero (caller loop will break)
193+
return 1
194+
fi
195+
196+
case "$ps" in
197+
not_started)
198+
handle_plan_phase "$story_id" "$trace_id" || return 1
199+
;;
200+
planned)
201+
handle_impl_phase "$story_id" "$trace_id" || return 1
202+
;;
203+
implemented)
204+
handle_qa_phase "$story_id" "$trace_id" || return 1
205+
;;
206+
qa_passed)
207+
handle_commit_phase "$story_id" "$trace_id" || return 1
208+
;;
209+
done|failed|escalated)
210+
# Terminal states — caller loop should stop. Not an error.
211+
return 0
212+
;;
213+
*)
214+
# AC6(i)(ii)(iii)(iv): invalid phase_status
215+
echo "[ERROR] ${story_id} dispatch_3phase_story: invalid phase_status='${ps}' — known values: not_started planned implemented qa_passed done failed escalated"
216+
_emit_routing_record "$story_id" "$trace_id" "plan" "error" "invalid_phase_status:${ps}"
217+
return 1
218+
;;
219+
esac
220+
221+
return 0
222+
}

.gaai/core/scripts/delivery-daemon.sh

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ STATUS_MODE=false
130130

131131
BACKLOG_REL=".gaai/project/contexts/backlog/active.backlog.yaml"
132132
BACKLOG="$PROJECT_DIR/$BACKLOG_REL"
133+
BACKLOG_FILE="$BACKLOG" # alias for daemon-dispatch.sh library (E134S02)
133134
SCHEDULER="$SCRIPT_DIR/backlog-scheduler.sh"
134135
LOCK_DIR="$GAAI_PROJECT_DIR/contexts/backlog/.delivery-locks"
135136
LOG_DIR="$GAAI_PROJECT_DIR/contexts/backlog/.delivery-logs"
@@ -2276,6 +2277,10 @@ if (( EXIT_WHEN_IDLE_THRESHOLD > 0 )); then
22762277
log "${BLUE}Auto-stop enabled — daemon will exit after $EXIT_WHEN_IDLE_THRESHOLD consecutive idle polls (no ready stories + zero in-flight)${NC}"
22772278
fi
22782279

2280+
# ── Load 3-phase dispatch library (E134S02) ──────────────────────────────
2281+
# shellcheck disable=SC1090
2282+
source "$(dirname "$0")/daemon-dispatch.sh"
2283+
22792284
# ── Main loop ─────────────────────────────────────────────────────────────
22802285
# Counter for consecutive polls where active=0 AND no ready stories. Resets to
22812286
# 0 whenever a delivery launches OR an in-flight delivery is observed. When it
@@ -2377,7 +2382,25 @@ while true; do
23772382
fi
23782383

23792384
increment_retry "$story_id"
2380-
launch_delivery "$story_id"
2385+
2386+
# ── Route: 3phase dispatch OR legacy wrapper (E134S02) ────────────────
2387+
_dp=$(get_delivery_pipeline "$story_id")
2388+
if [[ "$_dp" == "3phase" ]]; then
2389+
_trace_id=$(node -e "import('node:crypto').then(m=>process.stdout.write(m.randomUUID()))" 2>/dev/null \
2390+
|| python3 -c "import uuid; print(str(uuid.uuid4()),end='')" 2>/dev/null \
2391+
|| echo "$(date +%s)-$$-$RANDOM")
2392+
while true; do
2393+
if ! dispatch_3phase_story "$story_id" "$_trace_id"; then
2394+
_ps=$(get_phase_status "$story_id")
2395+
log "${RED}$story_id — 3phase dispatch error at phase_status='${_ps}' — story left in place for retry${NC}"
2396+
break
2397+
fi
2398+
_ps=$(get_phase_status "$story_id")
2399+
[[ "$_ps" == "done" || "$_ps" == "failed" || "$_ps" == "escalated" ]] && break
2400+
done
2401+
else
2402+
launch_delivery "$story_id"
2403+
fi
23812404
((launched++))
23822405

23832406
done <<< "$ready_stories"

0 commit comments

Comments
 (0)