Skip to content

Commit dd597f2

Browse files
authored
Merge pull request #289 from Fr-e-d/contrib/sync-1777597913
sync: update 7 file(s) in core/
2 parents b33b3fd + 10e84d2 commit dd597f2

7 files changed

Lines changed: 893 additions & 631 deletions

File tree

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

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,28 @@
1010
# GAAI_STUB_DELAY_S — seconds to sleep between stubs (default: 0)
1111
# ROUTING_LOG_PATH — test-only override for --log-path (default: empty, uses logger default)
1212

13+
# ── Active-spawn marker directory (AC1) ──────────────────────────────────
14+
# LOCK_DIR is set by delivery-daemon.sh before sourcing this library.
15+
# Provide a fallback so this library is usable in tests without the full daemon env.
16+
_marker_dir() {
17+
echo "${LOCK_DIR:-${PROJECT_DIR}/.gaai/project/contexts/backlog/.delivery-locks}"
18+
}
19+
20+
_write_active_marker() {
21+
local story_id="$1" phase="$2"
22+
local mdir
23+
mdir=$(_marker_dir)
24+
mkdir -p "$mdir" 2>/dev/null || true
25+
touch "${mdir}/${story_id}.${phase}.active" 2>/dev/null || true
26+
}
27+
28+
_remove_active_marker() {
29+
local story_id="$1" phase="$2"
30+
local mdir
31+
mdir=$(_marker_dir)
32+
rm -f "${mdir}/${story_id}.${phase}.active" 2>/dev/null || true
33+
}
34+
1335
# ── Field extractors (AC1 — verbatim per story AC1 specification) ─────────
1436

1537
get_phase_status() {
@@ -982,23 +1004,37 @@ dispatch_3phase_story() {
9821004

9831005
case "$ps" in
9841006
not_started)
985-
handle_plan_phase "$story_id" "$trace_id" || return 1
1007+
_write_active_marker "$story_id" "plan"
1008+
handle_plan_phase "$story_id" "$trace_id"
1009+
local _plan_rc=$?
1010+
_remove_active_marker "$story_id" "plan"
1011+
[[ $_plan_rc -ne 0 ]] && return 1
9861012
;;
9871013
planned)
988-
handle_impl_phase "$story_id" "$trace_id" || return 1
1014+
_write_active_marker "$story_id" "impl"
1015+
handle_impl_phase "$story_id" "$trace_id"
1016+
local _impl_rc=$?
1017+
_remove_active_marker "$story_id" "impl"
1018+
[[ $_impl_rc -ne 0 ]] && return 1
9891019
;;
9901020
implemented)
991-
handle_qa_phase "$story_id" "$trace_id" || return 1
1021+
_write_active_marker "$story_id" "qa"
1022+
handle_qa_phase "$story_id" "$trace_id"
1023+
local _qa_rc=$?
1024+
_remove_active_marker "$story_id" "qa"
1025+
[[ $_qa_rc -ne 0 ]] && return 1
9921026
;;
9931027
qa_passed)
994-
handle_commit_phase "$story_id" "$trace_id" || return 1
1028+
_write_active_marker "$story_id" "commit"
1029+
handle_commit_phase "$story_id" "$trace_id"
1030+
local _commit_rc=$?
1031+
_remove_active_marker "$story_id" "commit"
1032+
[[ $_commit_rc -ne 0 ]] && return 1
9951033
;;
9961034
done|failed|escalated|qa_failed|qa_escalated)
997-
# Terminal states — caller loop should stop. Not an error.
9981035
return 0
9991036
;;
10001037
*)
1001-
# AC6(i)(ii)(iii)(iv): invalid phase_status
10021038
echo "[ERROR] ${story_id} dispatch_3phase_story: invalid phase_status='${ps}' — known values: not_started planned implemented qa_passed qa_failed qa_escalated done failed escalated"
10031039
_emit_routing_record "$story_id" "$trace_id" "plan" "error" "invalid_phase_status:${ps}"
10041040
return 1

.gaai/core/scripts/daemon-monitor-tail.sh

Lines changed: 195 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
LOG_DIR="${1:-.gaai/project/contexts/backlog/.delivery-logs}"
66
PROJECT_DIR="$(cd "$(dirname "$0")/../../.." && pwd)"
77
BACKLOG="$PROJECT_DIR/.gaai/project/contexts/backlog/active.backlog.yaml"
8+
LOCK_DIR="${PROJECT_DIR}/.gaai/project/contexts/backlog/.delivery-locks"
9+
WORKTREE_BASE="${PROJECT_DIR}/../.gaai-worktrees/$(basename "$PROJECT_DIR")"
810

911
HAS_JQ=false
1012
command -v jq &>/dev/null && HAS_JQ=true
@@ -50,9 +52,157 @@ format_model() {
5052

5153
PHASE_CACHE_DIR="${PROJECT_DIR}/.gaai/project/contexts/backlog/.delivery-locks/.phase-cache"
5254

55+
# Returns story IDs — one per line — for all currently active deliveries.
56+
# For legacy pipeline: active tmux sessions named gaai-deliver-{id}.
57+
# For 3phase pipeline: .lock files in LOCK_DIR where backlog status=in_progress.
58+
detect_active_stories() {
59+
local seen=()
60+
61+
# Legacy: tmux sessions
62+
local tmux_ids
63+
tmux_ids=$(tmux list-sessions -F '#{session_name}' 2>/dev/null \
64+
| grep '^gaai-deliver-' \
65+
| sed 's/gaai-deliver-//' || true)
66+
for _id in $tmux_ids; do
67+
local _dp
68+
_dp=$(awk -v id="$_id" '
69+
$0 == "- id: " id { found=1; next }
70+
found && /^- id:/ { exit }
71+
found && /^[[:space:]]+delivery_pipeline:/ {
72+
gsub(/^[[:space:]]+delivery_pipeline:[[:space:]]*/, "")
73+
gsub(/[[:space:]]*/, ""); print; exit
74+
}
75+
' "$BACKLOG" 2>/dev/null || true)
76+
[[ "$_dp" != "3phase" ]] && echo "$_id"
77+
seen+=("$_id")
78+
done
79+
80+
# 3phase: .lock files with in_progress status
81+
if [[ -d "$LOCK_DIR" ]]; then
82+
for _lf in "$LOCK_DIR"/*.lock; do
83+
[[ -f "$_lf" ]] || continue
84+
local _sid
85+
_sid=$(basename "$_lf" .lock)
86+
# Skip if already emitted via tmux path
87+
local _dup=0
88+
for _s in "${seen[@]:-}"; do [[ "$_s" == "$_sid" ]] && _dup=1 && break; done
89+
[[ $_dup -eq 1 ]] && continue
90+
local _status _dp2
91+
_status=$(awk -v id="$_sid" '
92+
$0 == "- id: " id { found=1; next }
93+
found && /^- id:/ { exit }
94+
found && /^[[:space:]]+status:/ {
95+
gsub(/^[[:space:]]+status:[[:space:]]*/, "")
96+
gsub(/[[:space:]]*/, ""); print; exit
97+
}
98+
' "$BACKLOG" 2>/dev/null || true)
99+
_dp2=$(awk -v id="$_sid" '
100+
$0 == "- id: " id { found=1; next }
101+
found && /^- id:/ { exit }
102+
found && /^[[:space:]]+delivery_pipeline:/ {
103+
gsub(/^[[:space:]]+delivery_pipeline:[[:space:]]*/, "")
104+
gsub(/[[:space:]]*/, ""); print; exit
105+
}
106+
' "$BACKLOG" 2>/dev/null || true)
107+
[[ "$_status" == "in_progress" && "$_dp2" == "3phase" ]] && echo "$_sid"
108+
done
109+
fi
110+
}
111+
112+
# Returns the canonical log path for the current active phase of a 3phase story.
113+
# AC2: per-phase log at {worktree}/.delivery-logs/{id}.{phase}.log
114+
# Falls back to [no log yet] sentinel string when log does not exist.
115+
resolve_3phase_log() {
116+
local story_id="$1"
117+
local worktree="${WORKTREE_BASE}/${story_id}-workspace"
118+
119+
# Determine active phase from markers (AC1 priority order)
120+
local active_phase=""
121+
for _ph in plan impl qa commit; do
122+
if [[ -f "${LOCK_DIR}/${story_id}.${_ph}.active" ]]; then
123+
active_phase="$_ph"
124+
break
125+
fi
126+
done
127+
128+
if [[ -z "$active_phase" ]]; then
129+
# No active marker: derive last relevant phase from phase_status
130+
local ps
131+
ps=$(awk -v id="$story_id" '
132+
$0 == "- id: " id { found=1; next }
133+
found && /^- id:/ { exit }
134+
found && /^[[:space:]]+phase_status:/ {
135+
gsub(/^[[:space:]]+phase_status:[[:space:]]*/, "")
136+
gsub(/[[:space:]]*/, ""); print; exit
137+
}
138+
' "$BACKLOG" 2>/dev/null || true)
139+
case "$ps" in
140+
not_started) active_phase="plan" ;;
141+
planned) active_phase="plan" ;;
142+
implemented) active_phase="impl" ;;
143+
qa_passed) active_phase="qa" ;;
144+
done) active_phase="commit" ;;
145+
qa_failed|qa_escalated) active_phase="qa" ;;
146+
failed|escalated) active_phase="impl" ;;
147+
"") echo "[?]"; return ;;
148+
*) echo "[?]"; return ;;
149+
esac
150+
fi
151+
152+
local log_path="${worktree}/.delivery-logs/${story_id}.${active_phase}.log"
153+
if [[ -f "$log_path" ]]; then
154+
echo "$log_path"
155+
else
156+
echo "[no log yet]"
157+
fi
158+
}
159+
160+
# Returns the display phase label for a 3phase story using authoritative markers.
161+
# AC1: markers take priority over phase_status for in-progress display.
162+
detect_phase_3phase() {
163+
local story_id="$1"
164+
165+
# Active marker check (highest priority)
166+
for _ph in plan impl qa commit; do
167+
if [[ -f "${LOCK_DIR}/${story_id}.${_ph}.active" ]]; then
168+
case "$_ph" in
169+
plan) echo "PLAN" ;;
170+
impl) echo "IMPL" ;;
171+
qa) echo "QA" ;;
172+
commit) echo "COMMIT" ;;
173+
esac
174+
return
175+
fi
176+
done
177+
178+
# No active marker: read phase_status for terminal / idle display
179+
local ps
180+
ps=$(awk -v id="$story_id" '
181+
$0 == "- id: " id { found=1; next }
182+
found && /^- id:/ { exit }
183+
found && /^[[:space:]]+phase_status:/ {
184+
gsub(/^[[:space:]]+phase_status:[[:space:]]*/, "")
185+
gsub(/[[:space:]]*/, ""); print; exit
186+
}
187+
' "$BACKLOG" 2>/dev/null || true)
188+
189+
case "$ps" in
190+
done) echo "DONE" ;;
191+
failed|escalated) echo "FAILED" ;;
192+
qa_failed) echo "QA_FAILED" ;;
193+
qa_escalated) echo "QA_ESCALATED" ;;
194+
not_started|planned|implemented|qa_passed)
195+
echo "IDLE @ ${ps}" ;;
196+
"") echo "[?]" ;;
197+
*) echo "[?]" ;;
198+
esac
199+
}
200+
53201
parse_log() {
54202
local log_file="$1"
55203
local story_id="$2"
204+
local pipeline="${3:-legacy}" # "3phase" or "legacy"
205+
local phase_override="${4:-}" # pre-computed phase label (3phase only)
56206

57207
if [[ ! -f "$log_file" ]]; then
58208
echo -e " ${DIM}(log not yet created)${NC}"
@@ -185,6 +335,12 @@ parse_log() {
185335
fi
186336

187337
# ── Phase detection ──
338+
if [[ "$pipeline" == "3phase" && -n "$phase_override" ]]; then
339+
# 3phase: use authoritative marker-derived label (AC1)
340+
phase_label="$phase_override"
341+
phase_origin=""
342+
else
343+
# Legacy pipeline: existing log-content heuristic (unchanged)
188344
# Walk recent events, classify each Bash command / Write target into a phase tag,
189345
# keep the LAST non-empty classification — that's the current phase.
190346
# Origin is captured from the same event so we can show e.g. "IMPL (sub)" when
@@ -300,6 +456,7 @@ parse_log() {
300456
# Current window advanced: write updated winner back to cache
301457
printf '%s\t%s\t%s\n' "$cur_rank" "$phase_label" "$phase_origin" > "$cache_file" 2>/dev/null || true
302458
fi
459+
fi # close: if [[ 3phase ]] ... else (legacy heuristic) ... fi
303460
else
304461
last_text=$(tail -200 "$log_file" 2>/dev/null \
305462
| grep -o '"type":"tool_use"[^}]*"name":"[^"]*"' \
@@ -382,30 +539,59 @@ parse_log() {
382539
fi
383540
}
384541

542+
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
385543
while true; do
386544
clear
387545
# In tmux: clear scrollback left by `clear` so prior refresh doesn't ghost below
388546
[[ -n "${TMUX:-}" ]] && tmux clear-history 2>/dev/null || true
389547
echo "═══ Active Deliveries (refreshes every 5s) ═══"
390548
echo ""
391549

392-
# Find active tmux delivery sessions
393-
active_sessions=$(tmux list-sessions -F '#{session_name}' 2>/dev/null \
394-
| grep '^gaai-deliver-' \
395-
| sed 's/gaai-deliver-//' || true)
550+
# Read active stories (3phase from locks + legacy from tmux)
551+
active_ids=()
552+
while IFS= read -r _id; do
553+
[[ -n "$_id" ]] && active_ids+=("$_id")
554+
done < <(detect_active_stories)
396555

397-
if [[ -z "$active_sessions" ]]; then
556+
if [[ ${#active_ids[@]} -eq 0 ]]; then
398557
echo -e " ${DIM}No active deliveries. Use /gaai-discover to create stories for the backlog.${NC}"
399558
sleep 5
400559
continue
401560
fi
402561

403-
for story_id in $active_sessions; do
404-
log_file="$LOG_DIR/${story_id}.log"
405-
echo "── $story_id ──"
406-
parse_log "$log_file" "$story_id"
562+
for story_id in "${active_ids[@]}"; do
563+
# Determine pipeline
564+
pipeline=$(awk -v id="$story_id" '
565+
$0 == "- id: " id { found=1; next }
566+
found && /^- id:/ { exit }
567+
found && /^[[:space:]]+delivery_pipeline:/ {
568+
gsub(/^[[:space:]]+delivery_pipeline:[[:space:]]*/, "")
569+
gsub(/[[:space:]]*/, ""); print; exit
570+
}
571+
' "$BACKLOG" 2>/dev/null || true)
572+
573+
if [[ "$pipeline" == "3phase" ]]; then
574+
# AC1: marker-based phase detection
575+
phase_label=$(detect_phase_3phase "$story_id")
576+
# AC2: per-phase log path resolution
577+
log_path=$(resolve_3phase_log "$story_id")
578+
if [[ "$log_path" == "[no log yet]" || "$log_path" == "[?]" ]]; then
579+
echo "── $story_id ── [${phase_label}]"
580+
echo -e " ${DIM}${log_path}${NC}"
581+
echo ""
582+
continue
583+
fi
584+
echo "── $story_id ── [${phase_label}]"
585+
parse_log "$log_path" "$story_id" "3phase" "$phase_label"
586+
else
587+
# Legacy: unchanged path
588+
log_path="$LOG_DIR/${story_id}.log"
589+
echo "── $story_id ──"
590+
parse_log "$log_path" "$story_id" "legacy" ""
591+
fi
407592
echo ""
408593
done
409594

410595
sleep 5
411596
done
597+
fi

0 commit comments

Comments
 (0)