Files
gh-tstomtimes-orchestra/hooks/progress-tracker-update.sh
2025-11-30 09:03:11 +08:00

219 lines
6.8 KiB
Bash
Executable File

#!/bin/bash
# Orchestra Progress Tracker - Update Script
# Version: 2.0.0
# Handles atomic updates to progress.json with file locking
# Called from post_code_write.sh after TodoWrite tool execution
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="${PROJECT_ROOT:-$(cd "$SCRIPT_DIR/.." && pwd)}"
PROGRESS_FILE="$PROJECT_ROOT/.orchestra/cache/progress.json"
LOCK_FILE="/tmp/orchestra-progress-lock-${USER}"
# Source utility library
if [ -f "$SCRIPT_DIR/lib/progress-utils.sh" ]; then
source "$SCRIPT_DIR/lib/progress-utils.sh"
else
echo "ERROR: progress-utils.sh not found" >&2
exit 1
fi
# Parse TodoWrite parameters from stdin or argument
parse_tool_params() {
local params="$1"
# If params is empty, try to read from stdin
if [ -z "$params" ]; then
if [ ! -t 0 ]; then
params=$(cat)
fi
fi
echo "$params"
}
# Process a single todo item
process_todo() {
local todo_json="$1"
local timestamp="$2"
# Extract fields from todo
local task_id=$(echo "$todo_json" | jq -r '.id // empty')
local content=$(echo "$todo_json" | jq -r '.content // empty')
local active_form=$(echo "$todo_json" | jq -r '.activeForm // empty')
local status=$(echo "$todo_json" | jq -r '.status // "pending"')
local parent_id=$(echo "$todo_json" | jq -r '.parentId // null')
# Validate required fields
if [ -z "$task_id" ] || [ -z "$content" ]; then
log_event "WARN" "Skipping todo with missing required fields"
return 0
fi
# Detect agent
local agent=$(detect_agent_from_todo "$active_form" "$content" "$PROGRESS_FILE")
log_event "DEBUG" "Processing task $task_id: agent=$agent, status=$status"
# Check if task already exists
local existing=$(jq --arg id "$task_id" '.todos[] | select(.id == $id)' "$PROGRESS_FILE" 2>/dev/null || echo "")
local temp_file="${PROGRESS_FILE}.task.tmp"
if [ -z "$existing" ]; then
# New task: add with full metadata
log_event "INFO" "Creating new task: $task_id (Agent: $agent)"
jq --arg id "$task_id" \
--arg content "$content" \
--arg activeForm "$active_form" \
--arg status "$status" \
--arg agent "$agent" \
--argjson startTime "$timestamp" \
--argjson lastUpdateTime "$timestamp" \
--arg parentId "$parent_id" \
'.todos += [{
id: $id,
content: $content,
activeForm: $activeForm,
status: $status,
parentId: (if $parentId == "null" then null else $parentId end),
agent: $agent,
startTime: $startTime,
lastUpdateTime: $lastUpdateTime,
estimatedDuration: null,
currentStep: null,
totalSteps: null,
tags: []
}]' "$PROGRESS_FILE" > "$temp_file"
mv "$temp_file" "$PROGRESS_FILE"
# Add history entry for new task
add_history_entry "$PROGRESS_FILE" "$timestamp" "task_started" "$task_id" "$agent" "Task created"
else
# Existing task: update status and lastUpdateTime
local old_status=$(echo "$existing" | jq -r '.status')
log_event "DEBUG" "Updating task $task_id: $old_status -> $status"
jq --arg id "$task_id" \
--arg status "$status" \
--arg agent "$agent" \
--arg activeForm "$active_form" \
--argjson lastUpdateTime "$timestamp" \
'(.todos[] | select(.id == $id)) |= (. + {
status: $status,
agent: $agent,
activeForm: $activeForm,
lastUpdateTime: $lastUpdateTime
})' "$PROGRESS_FILE" > "$temp_file"
mv "$temp_file" "$PROGRESS_FILE"
# Log status change in history
if [ "$old_status" != "$status" ]; then
local event_type="task_updated"
if [ "$status" = "completed" ]; then
event_type="task_completed"
fi
log_event "INFO" "Task $task_id: $old_status$status (Agent: $agent)"
add_history_entry "$PROGRESS_FILE" "$timestamp" "$event_type" "$task_id" "$agent" "$old_status -> $status"
fi
fi
# Update currentAgent in metadata
jq --arg agent "$agent" \
'.metadata.currentAgent = $agent' "$PROGRESS_FILE" > "$temp_file"
mv "$temp_file" "$PROGRESS_FILE"
}
# Main update logic with file locking
main() {
local tool_params=$(parse_tool_params "$1")
# If no params provided, exit silently
if [ -z "$tool_params" ] || [ "$tool_params" = "{}" ] || [ "$tool_params" = "null" ]; then
log_event "DEBUG" "No TodoWrite parameters provided, skipping update"
exit 0
fi
log_event "DEBUG" "Starting progress update with params: ${tool_params:0:100}..."
# Ensure progress file exists
if [ ! -f "$PROGRESS_FILE" ]; then
log_event "INFO" "Initializing progress.json"
init_progress_file_if_missing "$PROGRESS_FILE"
fi
# Acquire exclusive lock with timeout (cross-platform approach)
# Try to create lock file, wait if it exists
local lock_attempts=0
local max_attempts=50 # 5 seconds (50 * 0.1s)
while [ $lock_attempts -lt $max_attempts ]; do
if mkdir "$LOCK_FILE" 2>/dev/null; then
# Lock acquired
break
fi
# Lock exists, wait a bit
sleep 0.1
lock_attempts=$((lock_attempts + 1))
done
if [ $lock_attempts -ge $max_attempts ]; then
log_event "ERROR" "Failed to acquire lock for progress update (timeout)"
exit 1
fi
# Ensure lock is released on exit
trap "rmdir '$LOCK_FILE' 2>/dev/null || true" EXIT
log_event "DEBUG" "Lock acquired successfully"
# Get current timestamp
local timestamp=$(get_timestamp_ms)
# Parse todos array from parameters
local todos=$(echo "$tool_params" | jq -c '.todos // []' 2>/dev/null || echo "[]")
if [ "$todos" = "[]" ] || [ -z "$todos" ]; then
log_event "DEBUG" "No todos in parameters"
rmdir "$LOCK_FILE" 2>/dev/null || true
exit 0
fi
# Process each todo
echo "$todos" | jq -c '.[]' 2>/dev/null | while IFS= read -r todo; do
if [ -n "$todo" ]; then
process_todo "$todo" "$timestamp"
fi
done
# Update metadata (task counts, completion rate, active agents)
log_event "DEBUG" "Updating metadata"
update_metadata "$PROGRESS_FILE" "$timestamp"
log_event "INFO" "Progress update completed successfully"
# Release lock
rmdir "$LOCK_FILE" 2>/dev/null || true
local exit_code=$?
if [ $exit_code -ne 0 ]; then
log_event "ERROR" "Progress update failed with exit code $exit_code"
fi
exit $exit_code
}
# Error handling
trap 'log_event "ERROR" "Update script terminated unexpectedly: $?"' ERR
# Run main function
main "$@"