Conversation
- Move trigger_batch_release() to release.sh module - Move cmd_release() to release.sh module - Functions now properly modularized for batch release management
|
Caution Review failedThe pull request is closed. WalkthroughThe refactoring modularizes supervisor functionality by consolidating batch release logic into Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant release.sh
participant VersionMgr as version-manager.sh
participant Git
participant GitHub API
User->>release.sh: cmd_release [--type patch/minor/major]
activate release.sh
release.sh->>release.sh: Resolve target batch (from arg or latest completed)
release.sh->>release.sh: Load batch metadata & validate release type
release.sh->>Git: Ensure main branch
release.sh->>Git: Stash uncommitted changes
release.sh->>Git: Pull latest
release.sh->>VersionMgr: Invoke with --skip-preflight --force
activate VersionMgr
VersionMgr->>VersionMgr: Bump version, update manifests
VersionMgr->>Git: Commit & push version changes
VersionMgr-->>release.sh: Release output
deactivate VersionMgr
release.sh->>Git: Restore stashed changes
release.sh->>GitHub API: Post release completion notification
release.sh->>release.sh: Log release summary in memory
release.sh-->>User: Release complete
deactivate release.sh
sequenceDiagram
participant todo-sync.sh
participant Git
participant GitHub API
participant VERIFY.md
User->>todo-sync.sh: Task deployment complete
activate todo-sync.sh
todo-sync.sh->>Git: Fetch PR metadata for task
todo-sync.sh->>GitHub API: GET /repos/.../pulls/{id}
activate GitHub API
GitHub API-->>todo-sync.sh: PR files, diff, metadata
deactivate GitHub API
todo-sync.sh->>todo-sync.sh: Filter substantive changes (exclude TODO.md)
todo-sync.sh->>VERIFY.md: Generate & insert verification entry
todo-sync.sh->>Git: pull --rebase (retry loop)
todo-sync.sh->>Git: add VERIFY.md
todo-sync.sh->>Git: commit "Verify: task-{id}"
todo-sync.sh->>Git: push origin
todo-sync.sh->>GitHub API: POST comment with verification checklist
todo-sync.sh-->>User: Verification queue updated
deactivate todo-sync.sh
sequenceDiagram
participant todo-sync.sh
participant TODO.md
participant Git
participant GitHub API
User->>todo-sync.sh: update_todo_on_complete(task_id)
activate todo-sync.sh
todo-sync.sh->>todo-sync.sh: Validate deliverables in supervisor DB
todo-sync.sh->>TODO.md: Check for open `#plan` subtasks
alt Has open subtasks
todo-sync.sh-->>User: Skip (active dependencies)
else Deliverables valid
todo-sync.sh->>TODO.md: Mark task ✅, update status
todo-sync.sh->>Git: pull --rebase (concurrent handling)
todo-sync.sh->>Git: add & commit TODO.md
todo-sync.sh->>Git: push origin
todo-sync.sh->>GitHub API: POST completion comment
todo-sync.sh-->>User: TODO.md updated & synced
end
deactivate todo-sync.sh
Estimated Code Review Effort🎯 4 (Complex) | ⏱️ ~60 minutes Reasoning: Heterogeneous changes across three files with distinct logic patterns—release.sh introduces sophisticated batch workflow orchestration with version management and multi-step state handling; todo-sync.sh adds intricate verification queue population, concurrent git operations with retry logic, and complex TODO.md mutation patterns; supervisor-helper.sh shifts architectural responsibility toward GitHub-based interactions. Each file requires independent reasoning for correctness, error paths, and git concurrency semantics. Possibly Related Issues
Possibly Related PRs
Poem
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…iable with underscore
🔍 Code Quality Report�[0;35m[MONITOR]�[0m Code Review Monitoring Report �[0;34m[INFO]�[0m Latest Quality Status: �[0;34m[INFO]�[0m Recent monitoring activity: 📈 Current Quality Metrics
Generated on: Thu Feb 12 12:30:42 UTC 2026 Generated by AI DevOps Framework Code Review Monitoring |
…helper.sh - Removed trigger_batch_release() (now in release.sh module) - Removed cmd_release() (now in release.sh module) - Functions are now sourced from supervisor/release.sh
🔍 Code Quality Report�[0;35m[MONITOR]�[0m Code Review Monitoring Report �[0;34m[INFO]�[0m Latest Quality Status: �[0;34m[INFO]�[0m Recent monitoring activity: 📈 Current Quality Metrics
Generated on: Thu Feb 12 12:32:09 UTC 2026 Generated by AI DevOps Framework Code Review Monitoring |
- Move commit_and_push_todo() to todo-sync.sh module - Move populate_verify_queue() to todo-sync.sh module - Move mark_verify_entry() to todo-sync.sh module - Move process_verify_queue() to todo-sync.sh module - Move commit_verify_changes() to todo-sync.sh module - Move update_todo_on_complete() to todo-sync.sh module - Move generate_verify_entry() to todo-sync.sh module - Move update_todo_on_blocked() to todo-sync.sh module - Move cmd_update_todo() to todo-sync.sh module - Move cmd_reconcile_todo() to todo-sync.sh module - Functions now properly modularized for TODO.md synchronization
🔍 Code Quality Report�[0;35m[MONITOR]�[0m Code Review Monitoring Report �[0;34m[INFO]�[0m Latest Quality Status: �[0;34m[INFO]�[0m Recent monitoring activity: 📈 Current Quality Metrics
Generated on: Thu Feb 12 12:38:22 UTC 2026 Generated by AI DevOps Framework Code Review Monitoring |
…r-helper.sh - Removed 11 functions (869 lines) now in todo-sync.sh module - Functions are now sourced from supervisor/todo-sync.sh - Syntax check passed
🔍 Code Quality Report�[0;35m[MONITOR]�[0m Code Review Monitoring Report �[0;34m[INFO]�[0m Latest Quality Status: �[0;34m[INFO]�[0m Recent monitoring activity: 📈 Current Quality Metrics
Generated on: Thu Feb 12 12:40:06 UTC 2026 Generated by AI DevOps Framework Code Review Monitoring |
|
Summary of ChangesHello @marcusquinn, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly refactors the supervisor module by extracting release management and TODO.md synchronization functionalities into separate, specialized shell scripts. This change aims to improve the overall structure and maintainability of the supervisor system, making it easier to understand, test, and extend. The core Highlights
Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request refactors supervisor-helper.sh by extracting release and todo-sync functionalities into dedicated modules, significantly improving code organization and maintainability. However, the new todo-sync.sh module introduces critical security vulnerabilities, including command injection via awk interpolation, insecure construction of verification checks from untrusted PR filenames, and sed/grep injection issues. Additionally, a minor code cleanliness issue with an unused variable in todo-sync.sh was identified. Addressing these security concerns by using safe variable passing in awk (via -v), sanitizing PR-derived filenames, and using the -- separator for grep commands is crucial.
| sed -i.bak "s/^- \[ \] \(v[0-9]* $task_id .*\)/- [x] \1 verified:$today/" "$verify_file" | ||
| else | ||
| # Mark [!] and add failed:date reason:description | ||
| local escaped_reason | ||
| escaped_reason=$(echo "$reason" | sed 's/[&/\]/\\&/g' | head -c 200) | ||
| sed -i.bak "s/^- \[ \] \(v[0-9]* $task_id .*\)/- [!] \1 failed:$today reason:$escaped_reason/" "$verify_file" | ||
| fi | ||
| rm -f "${verify_file}.bak" | ||
|
|
||
| return 0 | ||
| } | ||
|
|
||
| # Add task to TODO.md | ||
| add_task_to_todo() { | ||
| : | ||
| ####################################### | ||
| # Process verification queue — run checks for deployed tasks (t180.3) | ||
| # Scans VERIFY.md for pending entries, runs checks, updates states | ||
| # Called from pulse Phase 6 | ||
| ####################################### | ||
| process_verify_queue() { | ||
| local batch_id="${1:-}" | ||
|
|
||
| ensure_db | ||
|
|
||
| # Find deployed tasks that need verification | ||
| local deployed_tasks | ||
| local where_clause="t.status = 'deployed'" | ||
| if [[ -n "$batch_id" ]]; then | ||
| where_clause="$where_clause AND EXISTS (SELECT 1 FROM batch_tasks bt WHERE bt.task_id = t.id AND bt.batch_id = '$(sql_escape "$batch_id")')" | ||
| fi | ||
|
|
||
| deployed_tasks=$(db -separator '|' "$SUPERVISOR_DB" " | ||
| SELECT t.id, t.repo, t.pr_url FROM tasks t | ||
| WHERE $where_clause | ||
| ORDER BY t.updated_at ASC; | ||
| ") | ||
|
|
||
| if [[ -z "$deployed_tasks" ]]; then | ||
| return 0 | ||
| fi | ||
|
|
||
| local verified_count=0 | ||
| local failed_count=0 | ||
|
|
||
| while IFS='|' read -r tid trepo _tpr; do | ||
| [[ -z "$tid" ]] && continue | ||
|
|
||
| local verify_file="$trepo/todo/VERIFY.md" | ||
| if [[ ! -f "$verify_file" ]]; then | ||
| continue | ||
| fi | ||
|
|
||
| # Check if there's a pending verify entry for this task | ||
| if ! grep -q "^- \[ \] v[0-9]* $tid " "$verify_file" 2>/dev/null; then | ||
| continue | ||
| fi | ||
|
|
||
| log_info " $tid: running verification checks" | ||
| cmd_transition "$tid" "verifying" 2>>"$SUPERVISOR_LOG" || { | ||
| log_warn " $tid: failed to transition to verifying" | ||
| continue | ||
| } | ||
|
|
||
| if run_verify_checks "$tid" "$trepo"; then | ||
| cmd_transition "$tid" "verified" 2>>"$SUPERVISOR_LOG" || true | ||
| verified_count=$((verified_count + 1)) | ||
| log_success " $tid: VERIFIED" | ||
| else | ||
| cmd_transition "$tid" "verify_failed" 2>>"$SUPERVISOR_LOG" || true | ||
| failed_count=$((failed_count + 1)) | ||
| log_warn " $tid: VERIFY FAILED" | ||
| send_task_notification "$tid" "verify_failed" "Post-merge verification failed" 2>>"$SUPERVISOR_LOG" || true | ||
| fi | ||
| done <<<"$deployed_tasks" | ||
|
|
||
| if [[ $((verified_count + failed_count)) -gt 0 ]]; then | ||
| log_info "Verification: $verified_count passed, $failed_count failed" | ||
| fi | ||
|
|
||
| return 0 | ||
| } | ||
|
|
||
| ####################################### | ||
| # Commit and push VERIFY.md changes after verification (t180.3) | ||
| ####################################### | ||
| commit_verify_changes() { | ||
| local repo="$1" | ||
| local task_id="$2" | ||
| local result="$3" | ||
|
|
||
| local verify_file="$repo/todo/VERIFY.md" | ||
| if [[ ! -f "$verify_file" ]]; then | ||
| return 0 | ||
| fi | ||
|
|
||
| # Check if there are changes to commit | ||
| if ! git -C "$repo" diff --quiet -- "todo/VERIFY.md" 2>/dev/null; then | ||
| local msg="chore: mark $task_id verification $result in VERIFY.md [skip ci]" | ||
| git -C "$repo" add "todo/VERIFY.md" 2>>"$SUPERVISOR_LOG" || return 1 | ||
| git -C "$repo" commit -m "$msg" 2>>"$SUPERVISOR_LOG" || return 1 | ||
| git -C "$repo" push origin main 2>>"$SUPERVISOR_LOG" || return 1 | ||
| log_info "Committed VERIFY.md update for $task_id ($result)" | ||
| fi | ||
|
|
||
| return 0 | ||
| } | ||
|
|
||
| # Parse TODO.md for task metadata | ||
| parse_todo_metadata() { | ||
| : | ||
| ####################################### | ||
| # Update TODO.md when a task completes | ||
| # Marks the task checkbox as [x], adds completed:YYYY-MM-DD | ||
| # Then commits and pushes the change | ||
| # Guard (t163): requires verified deliverables before marking [x] | ||
| ####################################### | ||
| update_todo_on_complete() { | ||
| local task_id="$1" | ||
|
|
||
| ensure_db | ||
|
|
||
| local escaped_id | ||
| escaped_id=$(sql_escape "$task_id") | ||
| local task_row | ||
| task_row=$(db -separator '|' "$SUPERVISOR_DB" " | ||
| SELECT repo, description, pr_url FROM tasks WHERE id = '$escaped_id'; | ||
| ") | ||
|
|
||
| if [[ -z "$task_row" ]]; then | ||
| log_error "Task not found: $task_id" | ||
| return 1 | ||
| fi | ||
|
|
||
| local trepo tdesc tpr_url | ||
| IFS='|' read -r trepo tdesc tpr_url <<<"$task_row" | ||
|
|
||
| # Verify deliverables before marking complete (t163.4) | ||
| if ! verify_task_deliverables "$task_id" "$tpr_url" "$trepo"; then | ||
| log_warn "Task $task_id failed deliverable verification - NOT marking [x] in TODO.md" | ||
| log_warn " To manually verify: add 'verified:$(date +%Y-%m-%d)' to the task line" | ||
| return 1 | ||
| fi | ||
|
|
||
| local todo_file="$trepo/TODO.md" | ||
| if [[ ! -f "$todo_file" ]]; then | ||
| log_warn "TODO.md not found at $todo_file" | ||
| return 1 | ||
| fi | ||
|
|
||
| # t278: Guard against marking #plan tasks complete when subtasks are still open. | ||
| # A #plan task is a parent that was decomposed into subtasks. It should only be | ||
| # marked [x] when ALL its subtasks are [x]. This prevents decomposition workers | ||
| # from prematurely completing the parent. | ||
| local task_line | ||
| task_line=$(grep -E "^[[:space:]]*- \[[ x-]\] ${task_id}( |$)" "$todo_file" | head -1 || true) | ||
| if [[ -n "$task_line" && "$task_line" == *"#plan"* ]]; then | ||
| # Get the indentation level of this task | ||
| local task_indent | ||
| task_indent=$(echo "$task_line" | sed -E 's/^([[:space:]]*).*/\1/' | wc -c) | ||
| task_indent=$((task_indent - 1)) # wc -c counts newline | ||
|
|
||
| # Check for open subtasks (lines indented deeper with [ ]) | ||
| local open_subtasks | ||
| open_subtasks=$(awk -v tid="$task_id" -v tindent="$task_indent" ' | ||
| BEGIN { found=0 } | ||
| /- \[[ x-]\] '"$task_id"'( |$)/ { found=1; next } | ||
| found && /^[[:space:]]*- \[/ { | ||
| # Count leading spaces | ||
| match($0, /^[[:space:]]*/); | ||
| line_indent = RLENGTH; | ||
| if (line_indent > tindent) { | ||
| if ($0 ~ /- \[ \]/) { print $0 } | ||
| } else { found=0 } | ||
| } | ||
| found && /^[[:space:]]*$/ { next } | ||
| found && !/^[[:space:]]*- / && !/^[[:space:]]*$/ { found=0 } | ||
| ' "$todo_file") | ||
|
|
||
| if [[ -n "$open_subtasks" ]]; then | ||
| local open_count | ||
| open_count=$(echo "$open_subtasks" | wc -l | tr -d ' ') | ||
| log_warn "Task $task_id is a #plan task with $open_count open subtask(s) — NOT marking [x]" | ||
| log_warn " Parent #plan tasks should only be completed when all subtasks are done" | ||
| return 1 | ||
| fi | ||
| fi | ||
|
|
||
| local today | ||
| today=$(date +%Y-%m-%d) | ||
|
|
||
| # Match the task line (open checkbox with task ID) | ||
| # Handles both top-level and indented subtasks | ||
| if ! grep -qE "^[[:space:]]*- \[ \] ${task_id}( |$)" "$todo_file"; then | ||
| log_warn "Task $task_id not found as open in $todo_file (may already be completed)" | ||
| return 0 | ||
| fi | ||
|
|
||
| # Mark as complete: [ ] -> [x], append completed:date | ||
| # Use sed to match the line and transform it | ||
| local sed_pattern="s/^([[:space:]]*- )\[ \] (${task_id} .*)$/\1[x] \2 completed:${today}/" |
There was a problem hiding this comment.
This section of code (lines 231-425) has a critical security vulnerability: the task_id variable is used directly in sed patterns without proper escaping. This can lead to command injection, file corruption, or logic bypass if task_id contains special sed delimiters or characters. Additionally, the _tpr variable at line 273 is assigned but never used, which should be removed for code clarity and maintainability.
| local open_subtasks | ||
| open_subtasks=$(awk -v tid="$task_id" -v tindent="$task_indent" ' | ||
| BEGIN { found=0 } | ||
| /- \[[ x-]\] '"$task_id"'( |$)/ { found=1; next } |
There was a problem hiding this comment.
The task_id variable is directly interpolated into an awk script regex pattern. An attacker who can control the task_id (e.g., via the update-todo command) can inject arbitrary awk code. For example, a task_id like foo/ { system("touch /tmp/pwned") } /bar would execute the injected command for every line in the file. This is a high-severity command injection vulnerability.
| checks+=$'\n'" check: shellcheck $file" | ||
| checks+=$'\n'" check: file-exists $file" | ||
| ;; | ||
| *.md) | ||
| checks+=$'\n'" check: file-exists $file" | ||
| ;; | ||
| *.toon) | ||
| checks+=$'\n'" check: file-exists $file" | ||
| ;; | ||
| *.yml | *.yaml) | ||
| checks+=$'\n'" check: file-exists $file" | ||
| ;; | ||
| *.json) | ||
| checks+=$'\n'" check: file-exists $file" | ||
| ;; | ||
| *) | ||
| checks+=$'\n'" check: file-exists $file" | ||
| ;; | ||
| esac | ||
| done <<<"$substantive_files" | ||
|
|
||
| # Also add subagent-index check if any .md files in .agents/ were changed | ||
| if echo "$substantive_files" | grep -qE '\.agents/.*\.md$'; then | ||
| local base_names | ||
| base_names=$(echo "$substantive_files" | grep -E '\.agents/.*\.md$' | xargs -I{} basename {} .md || true) | ||
| while IFS= read -r bname; do | ||
| [[ -z "$bname" ]] && continue | ||
| # Only check for subagent-index entries for tool/service/workflow files | ||
| if echo "$substantive_files" | grep -qE "\.agents/(tools|services|workflows)/.*${bname}\.md$"; then | ||
| checks+=$'\n'" check: rg \"$bname\" .agents/subagent-index.toon" |
There was a problem hiding this comment.
Filenames from a Pull Request are used to construct shell commands that are stored in VERIFY.md and later executed by the verification process. An attacker can create a PR with malicious filenames (e.g., ; touch /tmp/pwned ;.sh) to achieve remote code execution on the supervisor machine when verification checks are run. This is a critical command injection vulnerability.
| if grep -q "^- \[.\] v[0-9]* $task_id " "$verify_file" 2>/dev/null; then | ||
| log_info "Verify entry already exists for $task_id in VERIFY.md" | ||
| return 0 | ||
| fi | ||
|
|
||
| # Get changed files from PR | ||
| local changed_files | ||
| if ! changed_files=$(gh pr view "$pr_number" --repo "$repo_slug" --json files --jq '.files[].path' 2>>"$SUPERVISOR_LOG"); then | ||
| log_warn "populate_verify_queue: failed to fetch PR files for $task_id (#$pr_number)" | ||
| return 1 | ||
| fi | ||
|
|
||
| if [[ -z "$changed_files" ]]; then | ||
| log_info "No files changed in PR #$pr_number for $task_id" | ||
| return 0 | ||
| fi | ||
|
|
||
| # Filter to substantive files (skip TODO.md, planning files) | ||
| local substantive_files | ||
| substantive_files=$(echo "$changed_files" | grep -vE '^(TODO\.md$|todo/)' || true) | ||
|
|
||
| if [[ -z "$substantive_files" ]]; then | ||
| log_info "No substantive files in PR #$pr_number for $task_id — skipping verify" | ||
| return 0 | ||
| fi | ||
|
|
||
| # Get task description from DB | ||
| local task_desc | ||
| task_desc=$(db "$SUPERVISOR_DB" "SELECT description FROM tasks WHERE id = '$(sql_escape "$task_id")';" 2>/dev/null || echo "$task_id") | ||
| # Truncate long descriptions | ||
| if [[ ${#task_desc} -gt 60 ]]; then | ||
| task_desc="${task_desc:0:57}..." | ||
| fi | ||
|
|
||
| # Determine next verify ID | ||
| local last_vnum | ||
| last_vnum=$(grep -oE 'v[0-9]+' "$verify_file" | grep -oE '[0-9]+' | sort -n | tail -1 || echo "0") | ||
| last_vnum=$((10#$last_vnum)) | ||
| local next_vnum=$((last_vnum + 1)) | ||
| local verify_id | ||
| verify_id=$(printf "v%03d" "$next_vnum") | ||
|
|
||
| local today | ||
| today=$(date +%Y-%m-%d) | ||
|
|
||
| # Build the verify entry | ||
| local entry="" | ||
| entry+="- [ ] $verify_id $task_id $task_desc | PR #$pr_number | merged:$today" | ||
| entry+=$'\n' | ||
| entry+=" files: $(echo "$substantive_files" | tr '\n' ',' | sed 's/,$//' | sed 's/,/, /g')" | ||
|
|
||
| # Generate check directives based on file types | ||
| local checks="" | ||
| while IFS= read -r file; do | ||
| [[ -z "$file" ]] && continue | ||
| case "$file" in | ||
| *.sh) | ||
| checks+=$'\n'" check: shellcheck $file" | ||
| checks+=$'\n'" check: file-exists $file" | ||
| ;; | ||
| *.md) | ||
| checks+=$'\n'" check: file-exists $file" | ||
| ;; | ||
| *.toon) | ||
| checks+=$'\n'" check: file-exists $file" | ||
| ;; | ||
| *.yml | *.yaml) | ||
| checks+=$'\n'" check: file-exists $file" | ||
| ;; | ||
| *.json) | ||
| checks+=$'\n'" check: file-exists $file" | ||
| ;; | ||
| *) | ||
| checks+=$'\n'" check: file-exists $file" | ||
| ;; | ||
| esac | ||
| done <<<"$substantive_files" | ||
|
|
||
| # Also add subagent-index check if any .md files in .agents/ were changed | ||
| if echo "$substantive_files" | grep -qE '\.agents/.*\.md$'; then | ||
| local base_names | ||
| base_names=$(echo "$substantive_files" | grep -E '\.agents/.*\.md$' | xargs -I{} basename {} .md || true) | ||
| while IFS= read -r bname; do | ||
| [[ -z "$bname" ]] && continue | ||
| # Only check for subagent-index entries for tool/service/workflow files | ||
| if echo "$substantive_files" | grep -qE "\.agents/(tools|services|workflows)/.*${bname}\.md$"; then | ||
| checks+=$'\n'" check: rg \"$bname\" .agents/subagent-index.toon" | ||
| fi | ||
| done <<<"$base_names" | ||
| fi | ||
|
|
||
| entry+="$checks" | ||
|
|
||
| # Append to VERIFY.md before the end marker | ||
| if grep -q '<!-- VERIFY-QUEUE-END -->' "$verify_file"; then | ||
| # Insert before the end marker | ||
| local temp_file | ||
| temp_file=$(mktemp) | ||
| _save_cleanup_scope | ||
| trap '_run_cleanups' RETURN | ||
| push_cleanup "rm -f '${temp_file}'" | ||
| awk -v entry="$entry" ' | ||
| /<!-- VERIFY-QUEUE-END -->/ { | ||
| print entry | ||
| print "" | ||
| } | ||
| { print } | ||
| ' "$verify_file" >"$temp_file" | ||
| mv "$temp_file" "$verify_file" | ||
| else | ||
| # No end marker — append to end of file | ||
| echo "" >>"$verify_file" | ||
| echo "$entry" >>"$verify_file" | ||
| fi | ||
|
|
||
| log_success "Added verify entry $verify_id for $task_id to VERIFY.md" | ||
| return 0 | ||
| } | ||
|
|
||
| # Mark task complete in TODO.md | ||
| mark_todo_complete() { | ||
| : | ||
| ####################################### | ||
| # Mark a verify entry as passed [x] or failed [!] in VERIFY.md (t180.3) | ||
| ####################################### | ||
| mark_verify_entry() { | ||
| local verify_file="$1" | ||
| local task_id="$2" | ||
| local result="$3" | ||
| local today="${4:-$(date +%Y-%m-%d)}" | ||
| local reason="${5:-}" | ||
|
|
||
| if [[ "$result" == "pass" ]]; then | ||
| # Mark [x] and add verified:date | ||
| sed -i.bak "s/^- \[ \] \(v[0-9]* $task_id .*\)/- [x] \1 verified:$today/" "$verify_file" | ||
| else | ||
| # Mark [!] and add failed:date reason:description | ||
| local escaped_reason | ||
| escaped_reason=$(echo "$reason" | sed 's/[&/\]/\\&/g' | head -c 200) | ||
| sed -i.bak "s/^- \[ \] \(v[0-9]* $task_id .*\)/- [!] \1 failed:$today reason:$escaped_reason/" "$verify_file" | ||
| fi | ||
| rm -f "${verify_file}.bak" | ||
|
|
||
| return 0 | ||
| } | ||
|
|
||
| # Add task to TODO.md | ||
| add_task_to_todo() { | ||
| : | ||
| ####################################### | ||
| # Process verification queue — run checks for deployed tasks (t180.3) | ||
| # Scans VERIFY.md for pending entries, runs checks, updates states | ||
| # Called from pulse Phase 6 | ||
| ####################################### | ||
| process_verify_queue() { | ||
| local batch_id="${1:-}" | ||
|
|
||
| ensure_db | ||
|
|
||
| # Find deployed tasks that need verification | ||
| local deployed_tasks | ||
| local where_clause="t.status = 'deployed'" | ||
| if [[ -n "$batch_id" ]]; then | ||
| where_clause="$where_clause AND EXISTS (SELECT 1 FROM batch_tasks bt WHERE bt.task_id = t.id AND bt.batch_id = '$(sql_escape "$batch_id")')" | ||
| fi | ||
|
|
||
| deployed_tasks=$(db -separator '|' "$SUPERVISOR_DB" " | ||
| SELECT t.id, t.repo, t.pr_url FROM tasks t | ||
| WHERE $where_clause | ||
| ORDER BY t.updated_at ASC; | ||
| ") | ||
|
|
||
| if [[ -z "$deployed_tasks" ]]; then | ||
| return 0 | ||
| fi | ||
|
|
||
| local verified_count=0 | ||
| local failed_count=0 | ||
|
|
||
| while IFS='|' read -r tid trepo _tpr; do | ||
| [[ -z "$tid" ]] && continue | ||
|
|
||
| local verify_file="$trepo/todo/VERIFY.md" | ||
| if [[ ! -f "$verify_file" ]]; then | ||
| continue | ||
| fi | ||
|
|
||
| # Check if there's a pending verify entry for this task | ||
| if ! grep -q "^- \[ \] v[0-9]* $tid " "$verify_file" 2>/dev/null; then | ||
| continue | ||
| fi | ||
|
|
||
| log_info " $tid: running verification checks" | ||
| cmd_transition "$tid" "verifying" 2>>"$SUPERVISOR_LOG" || { | ||
| log_warn " $tid: failed to transition to verifying" | ||
| continue | ||
| } | ||
|
|
||
| if run_verify_checks "$tid" "$trepo"; then | ||
| cmd_transition "$tid" "verified" 2>>"$SUPERVISOR_LOG" || true | ||
| verified_count=$((verified_count + 1)) | ||
| log_success " $tid: VERIFIED" | ||
| else | ||
| cmd_transition "$tid" "verify_failed" 2>>"$SUPERVISOR_LOG" || true | ||
| failed_count=$((failed_count + 1)) | ||
| log_warn " $tid: VERIFY FAILED" | ||
| send_task_notification "$tid" "verify_failed" "Post-merge verification failed" 2>>"$SUPERVISOR_LOG" || true | ||
| fi | ||
| done <<<"$deployed_tasks" | ||
|
|
||
| if [[ $((verified_count + failed_count)) -gt 0 ]]; then | ||
| log_info "Verification: $verified_count passed, $failed_count failed" | ||
| fi | ||
|
|
||
| return 0 | ||
| } | ||
|
|
||
| ####################################### | ||
| # Commit and push VERIFY.md changes after verification (t180.3) | ||
| ####################################### | ||
| commit_verify_changes() { | ||
| local repo="$1" | ||
| local task_id="$2" | ||
| local result="$3" | ||
|
|
||
| local verify_file="$repo/todo/VERIFY.md" | ||
| if [[ ! -f "$verify_file" ]]; then | ||
| return 0 | ||
| fi | ||
|
|
||
| # Check if there are changes to commit | ||
| if ! git -C "$repo" diff --quiet -- "todo/VERIFY.md" 2>/dev/null; then | ||
| local msg="chore: mark $task_id verification $result in VERIFY.md [skip ci]" | ||
| git -C "$repo" add "todo/VERIFY.md" 2>>"$SUPERVISOR_LOG" || return 1 | ||
| git -C "$repo" commit -m "$msg" 2>>"$SUPERVISOR_LOG" || return 1 | ||
| git -C "$repo" push origin main 2>>"$SUPERVISOR_LOG" || return 1 | ||
| log_info "Committed VERIFY.md update for $task_id ($result)" | ||
| fi | ||
|
|
||
| return 0 | ||
| } | ||
|
|
||
| # Parse TODO.md for task metadata | ||
| parse_todo_metadata() { | ||
| : | ||
| ####################################### | ||
| # Update TODO.md when a task completes | ||
| # Marks the task checkbox as [x], adds completed:YYYY-MM-DD | ||
| # Then commits and pushes the change | ||
| # Guard (t163): requires verified deliverables before marking [x] | ||
| ####################################### | ||
| update_todo_on_complete() { | ||
| local task_id="$1" | ||
|
|
||
| ensure_db | ||
|
|
||
| local escaped_id | ||
| escaped_id=$(sql_escape "$task_id") | ||
| local task_row | ||
| task_row=$(db -separator '|' "$SUPERVISOR_DB" " | ||
| SELECT repo, description, pr_url FROM tasks WHERE id = '$escaped_id'; | ||
| ") | ||
|
|
||
| if [[ -z "$task_row" ]]; then | ||
| log_error "Task not found: $task_id" | ||
| return 1 | ||
| fi | ||
|
|
||
| local trepo tdesc tpr_url | ||
| IFS='|' read -r trepo tdesc tpr_url <<<"$task_row" | ||
|
|
||
| # Verify deliverables before marking complete (t163.4) | ||
| if ! verify_task_deliverables "$task_id" "$tpr_url" "$trepo"; then | ||
| log_warn "Task $task_id failed deliverable verification - NOT marking [x] in TODO.md" | ||
| log_warn " To manually verify: add 'verified:$(date +%Y-%m-%d)' to the task line" | ||
| return 1 | ||
| fi | ||
|
|
||
| local todo_file="$trepo/TODO.md" | ||
| if [[ ! -f "$todo_file" ]]; then | ||
| log_warn "TODO.md not found at $todo_file" | ||
| return 1 | ||
| fi | ||
|
|
||
| # t278: Guard against marking #plan tasks complete when subtasks are still open. | ||
| # A #plan task is a parent that was decomposed into subtasks. It should only be | ||
| # marked [x] when ALL its subtasks are [x]. This prevents decomposition workers | ||
| # from prematurely completing the parent. | ||
| local task_line | ||
| task_line=$(grep -E "^[[:space:]]*- \[[ x-]\] ${task_id}( |$)" "$todo_file" | head -1 || true) | ||
| if [[ -n "$task_line" && "$task_line" == *"#plan"* ]]; then | ||
| # Get the indentation level of this task | ||
| local task_indent | ||
| task_indent=$(echo "$task_line" | sed -E 's/^([[:space:]]*).*/\1/' | wc -c) | ||
| task_indent=$((task_indent - 1)) # wc -c counts newline | ||
|
|
||
| # Check for open subtasks (lines indented deeper with [ ]) | ||
| local open_subtasks | ||
| open_subtasks=$(awk -v tid="$task_id" -v tindent="$task_indent" ' | ||
| BEGIN { found=0 } | ||
| /- \[[ x-]\] '"$task_id"'( |$)/ { found=1; next } | ||
| found && /^[[:space:]]*- \[/ { | ||
| # Count leading spaces | ||
| match($0, /^[[:space:]]*/); | ||
| line_indent = RLENGTH; | ||
| if (line_indent > tindent) { | ||
| if ($0 ~ /- \[ \]/) { print $0 } | ||
| } else { found=0 } | ||
| } | ||
| found && /^[[:space:]]*$/ { next } | ||
| found && !/^[[:space:]]*- / && !/^[[:space:]]*$/ { found=0 } | ||
| ' "$todo_file") | ||
|
|
||
| if [[ -n "$open_subtasks" ]]; then | ||
| local open_count | ||
| open_count=$(echo "$open_subtasks" | wc -l | tr -d ' ') | ||
| log_warn "Task $task_id is a #plan task with $open_count open subtask(s) — NOT marking [x]" | ||
| log_warn " Parent #plan tasks should only be completed when all subtasks are done" | ||
| return 1 | ||
| fi | ||
| fi | ||
|
|
||
| local today | ||
| today=$(date +%Y-%m-%d) | ||
|
|
||
| # Match the task line (open checkbox with task ID) | ||
| # Handles both top-level and indented subtasks | ||
| if ! grep -qE "^[[:space:]]*- \[ \] ${task_id}( |$)" "$todo_file"; then | ||
| log_warn "Task $task_id not found as open in $todo_file (may already be completed)" | ||
| return 0 | ||
| fi | ||
|
|
||
| # Mark as complete: [ ] -> [x], append completed:date | ||
| # Use sed to match the line and transform it | ||
| local sed_pattern="s/^([[:space:]]*- )\[ \] (${task_id} .*)$/\1[x] \2 completed:${today}/" | ||
|
|
||
| sed_inplace -E "$sed_pattern" "$todo_file" | ||
|
|
||
| # Verify the change was made | ||
| if ! grep -qE "^[[:space:]]*- \[x\] ${task_id} " "$todo_file"; then | ||
| log_error "Failed to update TODO.md for $task_id" | ||
| return 1 | ||
| fi | ||
|
|
||
| log_success "Updated TODO.md: $task_id marked complete ($today)" | ||
|
|
||
| local commit_msg="chore: mark $task_id complete in TODO.md" | ||
| if [[ -n "$tpr_url" ]]; then | ||
| commit_msg="chore: mark $task_id complete in TODO.md (${tpr_url})" | ||
| fi | ||
| commit_and_push_todo "$trepo" "$commit_msg" | ||
| return $? | ||
| } | ||
|
|
||
| ####################################### | ||
| # Generate a VERIFY.md entry for a deployed task (t180.4) | ||
| # Auto-creates check directives based on PR files: | ||
| # - .sh files: shellcheck + bash -n + file-exists | ||
| # - .md files: file-exists | ||
| # - test files: bash <test> | ||
| # - other: file-exists | ||
| # Appends entry before <!-- VERIFY-QUEUE-END --> marker | ||
| # $1: task_id | ||
| ####################################### | ||
| generate_verify_entry() { | ||
| local task_id="$1" | ||
|
|
||
| ensure_db | ||
|
|
||
| local escaped_id | ||
| escaped_id=$(sql_escape "$task_id") | ||
| local task_row | ||
| task_row=$(db -separator '|' "$SUPERVISOR_DB" " | ||
| SELECT repo, description, pr_url FROM tasks WHERE id = '$escaped_id'; | ||
| ") | ||
|
|
||
| if [[ -z "$task_row" ]]; then | ||
| log_warn "generate_verify_entry: task not found: $task_id" | ||
| return 1 | ||
| fi | ||
|
|
||
| local trepo tdesc tpr_url | ||
| IFS='|' read -r trepo tdesc tpr_url <<<"$task_row" | ||
|
|
||
| local verify_file="$trepo/todo/VERIFY.md" | ||
| if [[ ! -f "$verify_file" ]]; then | ||
| log_warn "generate_verify_entry: VERIFY.md not found at $verify_file" | ||
| return 1 | ||
| fi | ||
|
|
||
| # Check if entry already exists for this task | ||
| local task_id_escaped | ||
| task_id_escaped=$(printf '%s' "$task_id" | sed 's/\./\\./g') | ||
| if grep -qE "^- \[.\] v[0-9]+ ${task_id_escaped} " "$verify_file"; then | ||
| log_info "generate_verify_entry: entry already exists for $task_id" | ||
| return 0 | ||
| fi | ||
|
|
||
| # Get next vNNN number | ||
| local last_v | ||
| last_v=$(grep -oE '^- \[.\] v([0-9]+)' "$verify_file" | grep -oE '[0-9]+' | sort -n | tail -1 || echo "0") | ||
| last_v=$((10#$last_v)) | ||
| local next_v=$((last_v + 1)) | ||
| local vid | ||
| vid=$(printf "v%03d" "$next_v") | ||
|
|
||
| # Extract PR number | ||
| local pr_number="" | ||
| if [[ "$tpr_url" =~ /pull/([0-9]+) ]]; then | ||
| pr_number="${BASH_REMATCH[1]}" | ||
| fi | ||
|
|
||
| local today | ||
| today=$(date +%Y-%m-%d) | ||
|
|
||
| # Get files changed in PR (requires gh CLI) | ||
| local files_list="" | ||
| local -a check_lines=() | ||
|
|
||
| if [[ -n "$pr_number" ]] && command -v gh &>/dev/null && check_gh_auth; then | ||
| local repo_slug="" | ||
| repo_slug=$(detect_repo_slug "$trepo" 2>/dev/null || echo "") | ||
| if [[ -n "$repo_slug" ]]; then | ||
| files_list=$(gh pr view "$pr_number" --repo "$repo_slug" --json files --jq '.files[].path' 2>/dev/null | tr '\n' ', ' | sed 's/,$//') | ||
|
|
||
| # Generate check directives based on file types | ||
| while IFS= read -r fpath; do | ||
| [[ -z "$fpath" ]] && continue | ||
| case "$fpath" in | ||
| tests/*.sh | test-*.sh) | ||
| check_lines+=(" check: bash $fpath") | ||
| ;; | ||
| *.sh) | ||
| check_lines+=(" check: file-exists $fpath") | ||
| check_lines+=(" check: shellcheck $fpath") | ||
| check_lines+=(" check: bash -n $fpath") | ||
| ;; | ||
| *.md) | ||
| check_lines+=(" check: file-exists $fpath") | ||
| ;; | ||
| *) | ||
| check_lines+=(" check: file-exists $fpath") | ||
| ;; | ||
| esac | ||
| done < <(gh pr view "$pr_number" --repo "$repo_slug" --json files --jq '.files[].path' 2>/dev/null) | ||
| fi | ||
| fi | ||
|
|
||
| # Fallback: if no checks generated, add basic file-exists for PR | ||
| if [[ ${#check_lines[@]} -eq 0 && -n "$pr_number" ]]; then | ||
| check_lines+=(" check: rg \"$task_id\" $trepo/TODO.md") | ||
| fi | ||
|
|
||
| # Build the entry | ||
| local entry_header="- [ ] $vid $task_id ${tdesc%% *} | PR #${pr_number:-unknown} | merged:$today" | ||
| local entry_body="" | ||
| if [[ -n "$files_list" ]]; then | ||
| entry_body+=" files: $files_list"$'\n' | ||
| fi | ||
| for cl in "${check_lines[@]}"; do | ||
| entry_body+="$cl"$'\n' | ||
| done | ||
|
|
||
| # Insert before <!-- VERIFY-QUEUE-END --> | ||
| local marker="<!-- VERIFY-QUEUE-END -->" | ||
| if ! grep -q "$marker" "$verify_file"; then | ||
| log_warn "generate_verify_entry: VERIFY-QUEUE-END marker not found" | ||
| return 1 | ||
| fi | ||
|
|
||
| # Build full entry text | ||
| local full_entry | ||
| full_entry=$(printf '%s\n%s\n' "$entry_header" "$entry_body") | ||
|
|
||
| # Insert before marker using temp file (portable across macOS/Linux) | ||
| local tmp_file | ||
| tmp_file=$(mktemp) | ||
| _save_cleanup_scope | ||
| trap '_run_cleanups' RETURN | ||
| push_cleanup "rm -f '${tmp_file}'" | ||
| awk -v entry="$full_entry" -v mark="$marker" '{ | ||
| if (index($0, mark) > 0) { print entry; } | ||
| print; | ||
| }' "$verify_file" >"$tmp_file" && mv "$tmp_file" "$verify_file" | ||
|
|
||
| log_success "Generated verify entry $vid for $task_id (PR #${pr_number:-unknown})" | ||
|
|
||
| # Commit and push | ||
| commit_and_push_todo "$trepo" "chore: add verify entry $vid for $task_id" 2>>"$SUPERVISOR_LOG" || true | ||
|
|
||
| return 0 | ||
| } | ||
|
|
||
| ####################################### | ||
| # Update TODO.md when a task is blocked or failed | ||
| # Adds Notes line with blocked reason | ||
| # Then commits and pushes the change | ||
| # t296: Also posts a comment to GitHub issue if ref:GH# exists | ||
| ####################################### | ||
| update_todo_on_blocked() { | ||
| local task_id="$1" | ||
| local reason="${2:-unknown}" | ||
|
|
||
| ensure_db | ||
|
|
||
| local escaped_id | ||
| escaped_id=$(sql_escape "$task_id") | ||
| local trepo | ||
| trepo=$(db "$SUPERVISOR_DB" "SELECT repo FROM tasks WHERE id = '$escaped_id';") | ||
|
|
||
| if [[ -z "$trepo" ]]; then | ||
| log_error "Task not found: $task_id" | ||
| return 1 | ||
| fi | ||
|
|
||
| local todo_file="$trepo/TODO.md" | ||
| if [[ ! -f "$todo_file" ]]; then | ||
| log_warn "TODO.md not found at $todo_file" | ||
| return 1 | ||
| fi | ||
|
|
||
| # Find the task line number | ||
| local line_num | ||
| line_num=$(grep -nE "^[[:space:]]*- \[ \] ${task_id}( |$)" "$todo_file" | head -1 | cut -d: -f1) |
There was a problem hiding this comment.
Multiple calls to grep use variables directly as patterns without the -- separator. If a variable (like task_id or tid) starts with a hyphen, it will be interpreted as a command-line option for grep, leading to potential argument injection. Always use -- before the pattern to ensure it is treated as data.
| if grep -q "^- \[.\] v[0-9]* $task_id " "$verify_file" 2>/dev/null; then | |
| log_info "Verify entry already exists for $task_id in VERIFY.md" | |
| return 0 | |
| fi | |
| # Get changed files from PR | |
| local changed_files | |
| if ! changed_files=$(gh pr view "$pr_number" --repo "$repo_slug" --json files --jq '.files[].path' 2>>"$SUPERVISOR_LOG"); then | |
| log_warn "populate_verify_queue: failed to fetch PR files for $task_id (#$pr_number)" | |
| return 1 | |
| fi | |
| if [[ -z "$changed_files" ]]; then | |
| log_info "No files changed in PR #$pr_number for $task_id" | |
| return 0 | |
| fi | |
| # Filter to substantive files (skip TODO.md, planning files) | |
| local substantive_files | |
| substantive_files=$(echo "$changed_files" | grep -vE '^(TODO\.md$|todo/)' || true) | |
| if [[ -z "$substantive_files" ]]; then | |
| log_info "No substantive files in PR #$pr_number for $task_id — skipping verify" | |
| return 0 | |
| fi | |
| # Get task description from DB | |
| local task_desc | |
| task_desc=$(db "$SUPERVISOR_DB" "SELECT description FROM tasks WHERE id = '$(sql_escape "$task_id")';" 2>/dev/null || echo "$task_id") | |
| # Truncate long descriptions | |
| if [[ ${#task_desc} -gt 60 ]]; then | |
| task_desc="${task_desc:0:57}..." | |
| fi | |
| # Determine next verify ID | |
| local last_vnum | |
| last_vnum=$(grep -oE 'v[0-9]+' "$verify_file" | grep -oE '[0-9]+' | sort -n | tail -1 || echo "0") | |
| last_vnum=$((10#$last_vnum)) | |
| local next_vnum=$((last_vnum + 1)) | |
| local verify_id | |
| verify_id=$(printf "v%03d" "$next_vnum") | |
| local today | |
| today=$(date +%Y-%m-%d) | |
| # Build the verify entry | |
| local entry="" | |
| entry+="- [ ] $verify_id $task_id $task_desc | PR #$pr_number | merged:$today" | |
| entry+=$'\n' | |
| entry+=" files: $(echo "$substantive_files" | tr '\n' ',' | sed 's/,$//' | sed 's/,/, /g')" | |
| # Generate check directives based on file types | |
| local checks="" | |
| while IFS= read -r file; do | |
| [[ -z "$file" ]] && continue | |
| case "$file" in | |
| *.sh) | |
| checks+=$'\n'" check: shellcheck $file" | |
| checks+=$'\n'" check: file-exists $file" | |
| ;; | |
| *.md) | |
| checks+=$'\n'" check: file-exists $file" | |
| ;; | |
| *.toon) | |
| checks+=$'\n'" check: file-exists $file" | |
| ;; | |
| *.yml | *.yaml) | |
| checks+=$'\n'" check: file-exists $file" | |
| ;; | |
| *.json) | |
| checks+=$'\n'" check: file-exists $file" | |
| ;; | |
| *) | |
| checks+=$'\n'" check: file-exists $file" | |
| ;; | |
| esac | |
| done <<<"$substantive_files" | |
| # Also add subagent-index check if any .md files in .agents/ were changed | |
| if echo "$substantive_files" | grep -qE '\.agents/.*\.md$'; then | |
| local base_names | |
| base_names=$(echo "$substantive_files" | grep -E '\.agents/.*\.md$' | xargs -I{} basename {} .md || true) | |
| while IFS= read -r bname; do | |
| [[ -z "$bname" ]] && continue | |
| # Only check for subagent-index entries for tool/service/workflow files | |
| if echo "$substantive_files" | grep -qE "\.agents/(tools|services|workflows)/.*${bname}\.md$"; then | |
| checks+=$'\n'" check: rg \"$bname\" .agents/subagent-index.toon" | |
| fi | |
| done <<<"$base_names" | |
| fi | |
| entry+="$checks" | |
| # Append to VERIFY.md before the end marker | |
| if grep -q '<!-- VERIFY-QUEUE-END -->' "$verify_file"; then | |
| # Insert before the end marker | |
| local temp_file | |
| temp_file=$(mktemp) | |
| _save_cleanup_scope | |
| trap '_run_cleanups' RETURN | |
| push_cleanup "rm -f '${temp_file}'" | |
| awk -v entry="$entry" ' | |
| /<!-- VERIFY-QUEUE-END -->/ { | |
| print entry | |
| print "" | |
| } | |
| { print } | |
| ' "$verify_file" >"$temp_file" | |
| mv "$temp_file" "$verify_file" | |
| else | |
| # No end marker — append to end of file | |
| echo "" >>"$verify_file" | |
| echo "$entry" >>"$verify_file" | |
| fi | |
| log_success "Added verify entry $verify_id for $task_id to VERIFY.md" | |
| return 0 | |
| } | |
| # Mark task complete in TODO.md | |
| mark_todo_complete() { | |
| : | |
| ####################################### | |
| # Mark a verify entry as passed [x] or failed [!] in VERIFY.md (t180.3) | |
| ####################################### | |
| mark_verify_entry() { | |
| local verify_file="$1" | |
| local task_id="$2" | |
| local result="$3" | |
| local today="${4:-$(date +%Y-%m-%d)}" | |
| local reason="${5:-}" | |
| if [[ "$result" == "pass" ]]; then | |
| # Mark [x] and add verified:date | |
| sed -i.bak "s/^- \[ \] \(v[0-9]* $task_id .*\)/- [x] \1 verified:$today/" "$verify_file" | |
| else | |
| # Mark [!] and add failed:date reason:description | |
| local escaped_reason | |
| escaped_reason=$(echo "$reason" | sed 's/[&/\]/\\&/g' | head -c 200) | |
| sed -i.bak "s/^- \[ \] \(v[0-9]* $task_id .*\)/- [!] \1 failed:$today reason:$escaped_reason/" "$verify_file" | |
| fi | |
| rm -f "${verify_file}.bak" | |
| return 0 | |
| } | |
| # Add task to TODO.md | |
| add_task_to_todo() { | |
| : | |
| ####################################### | |
| # Process verification queue — run checks for deployed tasks (t180.3) | |
| # Scans VERIFY.md for pending entries, runs checks, updates states | |
| # Called from pulse Phase 6 | |
| ####################################### | |
| process_verify_queue() { | |
| local batch_id="${1:-}" | |
| ensure_db | |
| # Find deployed tasks that need verification | |
| local deployed_tasks | |
| local where_clause="t.status = 'deployed'" | |
| if [[ -n "$batch_id" ]]; then | |
| where_clause="$where_clause AND EXISTS (SELECT 1 FROM batch_tasks bt WHERE bt.task_id = t.id AND bt.batch_id = '$(sql_escape "$batch_id")')" | |
| fi | |
| deployed_tasks=$(db -separator '|' "$SUPERVISOR_DB" " | |
| SELECT t.id, t.repo, t.pr_url FROM tasks t | |
| WHERE $where_clause | |
| ORDER BY t.updated_at ASC; | |
| ") | |
| if [[ -z "$deployed_tasks" ]]; then | |
| return 0 | |
| fi | |
| local verified_count=0 | |
| local failed_count=0 | |
| while IFS='|' read -r tid trepo _tpr; do | |
| [[ -z "$tid" ]] && continue | |
| local verify_file="$trepo/todo/VERIFY.md" | |
| if [[ ! -f "$verify_file" ]]; then | |
| continue | |
| fi | |
| # Check if there's a pending verify entry for this task | |
| if ! grep -q "^- \[ \] v[0-9]* $tid " "$verify_file" 2>/dev/null; then | |
| continue | |
| fi | |
| log_info " $tid: running verification checks" | |
| cmd_transition "$tid" "verifying" 2>>"$SUPERVISOR_LOG" || { | |
| log_warn " $tid: failed to transition to verifying" | |
| continue | |
| } | |
| if run_verify_checks "$tid" "$trepo"; then | |
| cmd_transition "$tid" "verified" 2>>"$SUPERVISOR_LOG" || true | |
| verified_count=$((verified_count + 1)) | |
| log_success " $tid: VERIFIED" | |
| else | |
| cmd_transition "$tid" "verify_failed" 2>>"$SUPERVISOR_LOG" || true | |
| failed_count=$((failed_count + 1)) | |
| log_warn " $tid: VERIFY FAILED" | |
| send_task_notification "$tid" "verify_failed" "Post-merge verification failed" 2>>"$SUPERVISOR_LOG" || true | |
| fi | |
| done <<<"$deployed_tasks" | |
| if [[ $((verified_count + failed_count)) -gt 0 ]]; then | |
| log_info "Verification: $verified_count passed, $failed_count failed" | |
| fi | |
| return 0 | |
| } | |
| ####################################### | |
| # Commit and push VERIFY.md changes after verification (t180.3) | |
| ####################################### | |
| commit_verify_changes() { | |
| local repo="$1" | |
| local task_id="$2" | |
| local result="$3" | |
| local verify_file="$repo/todo/VERIFY.md" | |
| if [[ ! -f "$verify_file" ]]; then | |
| return 0 | |
| fi | |
| # Check if there are changes to commit | |
| if ! git -C "$repo" diff --quiet -- "todo/VERIFY.md" 2>/dev/null; then | |
| local msg="chore: mark $task_id verification $result in VERIFY.md [skip ci]" | |
| git -C "$repo" add "todo/VERIFY.md" 2>>"$SUPERVISOR_LOG" || return 1 | |
| git -C "$repo" commit -m "$msg" 2>>"$SUPERVISOR_LOG" || return 1 | |
| git -C "$repo" push origin main 2>>"$SUPERVISOR_LOG" || return 1 | |
| log_info "Committed VERIFY.md update for $task_id ($result)" | |
| fi | |
| return 0 | |
| } | |
| # Parse TODO.md for task metadata | |
| parse_todo_metadata() { | |
| : | |
| ####################################### | |
| # Update TODO.md when a task completes | |
| # Marks the task checkbox as [x], adds completed:YYYY-MM-DD | |
| # Then commits and pushes the change | |
| # Guard (t163): requires verified deliverables before marking [x] | |
| ####################################### | |
| update_todo_on_complete() { | |
| local task_id="$1" | |
| ensure_db | |
| local escaped_id | |
| escaped_id=$(sql_escape "$task_id") | |
| local task_row | |
| task_row=$(db -separator '|' "$SUPERVISOR_DB" " | |
| SELECT repo, description, pr_url FROM tasks WHERE id = '$escaped_id'; | |
| ") | |
| if [[ -z "$task_row" ]]; then | |
| log_error "Task not found: $task_id" | |
| return 1 | |
| fi | |
| local trepo tdesc tpr_url | |
| IFS='|' read -r trepo tdesc tpr_url <<<"$task_row" | |
| # Verify deliverables before marking complete (t163.4) | |
| if ! verify_task_deliverables "$task_id" "$tpr_url" "$trepo"; then | |
| log_warn "Task $task_id failed deliverable verification - NOT marking [x] in TODO.md" | |
| log_warn " To manually verify: add 'verified:$(date +%Y-%m-%d)' to the task line" | |
| return 1 | |
| fi | |
| local todo_file="$trepo/TODO.md" | |
| if [[ ! -f "$todo_file" ]]; then | |
| log_warn "TODO.md not found at $todo_file" | |
| return 1 | |
| fi | |
| # t278: Guard against marking #plan tasks complete when subtasks are still open. | |
| # A #plan task is a parent that was decomposed into subtasks. It should only be | |
| # marked [x] when ALL its subtasks are [x]. This prevents decomposition workers | |
| # from prematurely completing the parent. | |
| local task_line | |
| task_line=$(grep -E "^[[:space:]]*- \[[ x-]\] ${task_id}( |$)" "$todo_file" | head -1 || true) | |
| if [[ -n "$task_line" && "$task_line" == *"#plan"* ]]; then | |
| # Get the indentation level of this task | |
| local task_indent | |
| task_indent=$(echo "$task_line" | sed -E 's/^([[:space:]]*).*/\1/' | wc -c) | |
| task_indent=$((task_indent - 1)) # wc -c counts newline | |
| # Check for open subtasks (lines indented deeper with [ ]) | |
| local open_subtasks | |
| open_subtasks=$(awk -v tid="$task_id" -v tindent="$task_indent" ' | |
| BEGIN { found=0 } | |
| /- \[[ x-]\] '"$task_id"'( |$)/ { found=1; next } | |
| found && /^[[:space:]]*- \[/ { | |
| # Count leading spaces | |
| match($0, /^[[:space:]]*/); | |
| line_indent = RLENGTH; | |
| if (line_indent > tindent) { | |
| if ($0 ~ /- \[ \]/) { print $0 } | |
| } else { found=0 } | |
| } | |
| found && /^[[:space:]]*$/ { next } | |
| found && !/^[[:space:]]*- / && !/^[[:space:]]*$/ { found=0 } | |
| ' "$todo_file") | |
| if [[ -n "$open_subtasks" ]]; then | |
| local open_count | |
| open_count=$(echo "$open_subtasks" | wc -l | tr -d ' ') | |
| log_warn "Task $task_id is a #plan task with $open_count open subtask(s) — NOT marking [x]" | |
| log_warn " Parent #plan tasks should only be completed when all subtasks are done" | |
| return 1 | |
| fi | |
| fi | |
| local today | |
| today=$(date +%Y-%m-%d) | |
| # Match the task line (open checkbox with task ID) | |
| # Handles both top-level and indented subtasks | |
| if ! grep -qE "^[[:space:]]*- \[ \] ${task_id}( |$)" "$todo_file"; then | |
| log_warn "Task $task_id not found as open in $todo_file (may already be completed)" | |
| return 0 | |
| fi | |
| # Mark as complete: [ ] -> [x], append completed:date | |
| # Use sed to match the line and transform it | |
| local sed_pattern="s/^([[:space:]]*- )\[ \] (${task_id} .*)$/\1[x] \2 completed:${today}/" | |
| sed_inplace -E "$sed_pattern" "$todo_file" | |
| # Verify the change was made | |
| if ! grep -qE "^[[:space:]]*- \[x\] ${task_id} " "$todo_file"; then | |
| log_error "Failed to update TODO.md for $task_id" | |
| return 1 | |
| fi | |
| log_success "Updated TODO.md: $task_id marked complete ($today)" | |
| local commit_msg="chore: mark $task_id complete in TODO.md" | |
| if [[ -n "$tpr_url" ]]; then | |
| commit_msg="chore: mark $task_id complete in TODO.md (${tpr_url})" | |
| fi | |
| commit_and_push_todo "$trepo" "$commit_msg" | |
| return $? | |
| } | |
| ####################################### | |
| # Generate a VERIFY.md entry for a deployed task (t180.4) | |
| # Auto-creates check directives based on PR files: | |
| # - .sh files: shellcheck + bash -n + file-exists | |
| # - .md files: file-exists | |
| # - test files: bash <test> | |
| # - other: file-exists | |
| # Appends entry before <!-- VERIFY-QUEUE-END --> marker | |
| # $1: task_id | |
| ####################################### | |
| generate_verify_entry() { | |
| local task_id="$1" | |
| ensure_db | |
| local escaped_id | |
| escaped_id=$(sql_escape "$task_id") | |
| local task_row | |
| task_row=$(db -separator '|' "$SUPERVISOR_DB" " | |
| SELECT repo, description, pr_url FROM tasks WHERE id = '$escaped_id'; | |
| ") | |
| if [[ -z "$task_row" ]]; then | |
| log_warn "generate_verify_entry: task not found: $task_id" | |
| return 1 | |
| fi | |
| local trepo tdesc tpr_url | |
| IFS='|' read -r trepo tdesc tpr_url <<<"$task_row" | |
| local verify_file="$trepo/todo/VERIFY.md" | |
| if [[ ! -f "$verify_file" ]]; then | |
| log_warn "generate_verify_entry: VERIFY.md not found at $verify_file" | |
| return 1 | |
| fi | |
| # Check if entry already exists for this task | |
| local task_id_escaped | |
| task_id_escaped=$(printf '%s' "$task_id" | sed 's/\./\\./g') | |
| if grep -qE "^- \[.\] v[0-9]+ ${task_id_escaped} " "$verify_file"; then | |
| log_info "generate_verify_entry: entry already exists for $task_id" | |
| return 0 | |
| fi | |
| # Get next vNNN number | |
| local last_v | |
| last_v=$(grep -oE '^- \[.\] v([0-9]+)' "$verify_file" | grep -oE '[0-9]+' | sort -n | tail -1 || echo "0") | |
| last_v=$((10#$last_v)) | |
| local next_v=$((last_v + 1)) | |
| local vid | |
| vid=$(printf "v%03d" "$next_v") | |
| # Extract PR number | |
| local pr_number="" | |
| if [[ "$tpr_url" =~ /pull/([0-9]+) ]]; then | |
| pr_number="${BASH_REMATCH[1]}" | |
| fi | |
| local today | |
| today=$(date +%Y-%m-%d) | |
| # Get files changed in PR (requires gh CLI) | |
| local files_list="" | |
| local -a check_lines=() | |
| if [[ -n "$pr_number" ]] && command -v gh &>/dev/null && check_gh_auth; then | |
| local repo_slug="" | |
| repo_slug=$(detect_repo_slug "$trepo" 2>/dev/null || echo "") | |
| if [[ -n "$repo_slug" ]]; then | |
| files_list=$(gh pr view "$pr_number" --repo "$repo_slug" --json files --jq '.files[].path' 2>/dev/null | tr '\n' ', ' | sed 's/,$//') | |
| # Generate check directives based on file types | |
| while IFS= read -r fpath; do | |
| [[ -z "$fpath" ]] && continue | |
| case "$fpath" in | |
| tests/*.sh | test-*.sh) | |
| check_lines+=(" check: bash $fpath") | |
| ;; | |
| *.sh) | |
| check_lines+=(" check: file-exists $fpath") | |
| check_lines+=(" check: shellcheck $fpath") | |
| check_lines+=(" check: bash -n $fpath") | |
| ;; | |
| *.md) | |
| check_lines+=(" check: file-exists $fpath") | |
| ;; | |
| *) | |
| check_lines+=(" check: file-exists $fpath") | |
| ;; | |
| esac | |
| done < <(gh pr view "$pr_number" --repo "$repo_slug" --json files --jq '.files[].path' 2>/dev/null) | |
| fi | |
| fi | |
| # Fallback: if no checks generated, add basic file-exists for PR | |
| if [[ ${#check_lines[@]} -eq 0 && -n "$pr_number" ]]; then | |
| check_lines+=(" check: rg \"$task_id\" $trepo/TODO.md") | |
| fi | |
| # Build the entry | |
| local entry_header="- [ ] $vid $task_id ${tdesc%% *} | PR #${pr_number:-unknown} | merged:$today" | |
| local entry_body="" | |
| if [[ -n "$files_list" ]]; then | |
| entry_body+=" files: $files_list"$'\n' | |
| fi | |
| for cl in "${check_lines[@]}"; do | |
| entry_body+="$cl"$'\n' | |
| done | |
| # Insert before <!-- VERIFY-QUEUE-END --> | |
| local marker="<!-- VERIFY-QUEUE-END -->" | |
| if ! grep -q "$marker" "$verify_file"; then | |
| log_warn "generate_verify_entry: VERIFY-QUEUE-END marker not found" | |
| return 1 | |
| fi | |
| # Build full entry text | |
| local full_entry | |
| full_entry=$(printf '%s\n%s\n' "$entry_header" "$entry_body") | |
| # Insert before marker using temp file (portable across macOS/Linux) | |
| local tmp_file | |
| tmp_file=$(mktemp) | |
| _save_cleanup_scope | |
| trap '_run_cleanups' RETURN | |
| push_cleanup "rm -f '${tmp_file}'" | |
| awk -v entry="$full_entry" -v mark="$marker" '{ | |
| if (index($0, mark) > 0) { print entry; } | |
| print; | |
| }' "$verify_file" >"$tmp_file" && mv "$tmp_file" "$verify_file" | |
| log_success "Generated verify entry $vid for $task_id (PR #${pr_number:-unknown})" | |
| # Commit and push | |
| commit_and_push_todo "$trepo" "chore: add verify entry $vid for $task_id" 2>>"$SUPERVISOR_LOG" || true | |
| return 0 | |
| } | |
| ####################################### | |
| # Update TODO.md when a task is blocked or failed | |
| # Adds Notes line with blocked reason | |
| # Then commits and pushes the change | |
| # t296: Also posts a comment to GitHub issue if ref:GH# exists | |
| ####################################### | |
| update_todo_on_blocked() { | |
| local task_id="$1" | |
| local reason="${2:-unknown}" | |
| ensure_db | |
| local escaped_id | |
| escaped_id=$(sql_escape "$task_id") | |
| local trepo | |
| trepo=$(db "$SUPERVISOR_DB" "SELECT repo FROM tasks WHERE id = '$escaped_id';") | |
| if [[ -z "$trepo" ]]; then | |
| log_error "Task not found: $task_id" | |
| return 1 | |
| fi | |
| local todo_file="$trepo/TODO.md" | |
| if [[ ! -f "$todo_file" ]]; then | |
| log_warn "TODO.md not found at $todo_file" | |
| return 1 | |
| fi | |
| # Find the task line number | |
| local line_num | |
| line_num=$(grep -nE "^[[:space:]]*- \[ \] ${task_id}( |$)" "$todo_file" | head -1 | cut -d: -f1) | |
| if grep -q -- "^- \[.\] v[0-9]* $task_id " "$verify_file" 2>/dev/null; then |



Summary
Successfully extracted supervisor module functions from the monolithic
supervisor-helper.shinto dedicated module files, improving code organization and maintainability.Modules Extracted
release.sh (289 lines)
trigger_batch_release()- Automated batch release via version-manager.shcmd_release()- Manual release trigger commandtodo-sync.sh (810 lines)
commit_and_push_todo()- Concurrent-safe TODO.md updatespopulate_verify_queue()- Post-merge verification queue (t180.2)mark_verify_entry()- Mark verification results (t180.3)process_verify_queue()- Run verification checks (t180.3)commit_verify_changes()- Commit VERIFY.md updatesupdate_todo_on_complete()- Mark tasks complete with deliverable verification (t163)generate_verify_entry()- Auto-generate verification entries (t180.4)update_todo_on_blocked()- Mark tasks blocked with GitHub issue sync (t296)cmd_update_todo()- Manual TODO.md update commandcmd_reconcile_todo()- Bulk-fix stale TODO.md entriesImpact
bash -nvalidationTesting
supervisor-helper.sh help- displays full command listsupervisor-helper.sh status- shows current supervisor stateNext Steps
This PR establishes the pattern for further modularization. Remaining candidates:
Each module can be extracted incrementally following the same pattern demonstrated here.
Summary by CodeRabbit
New Features
Refactor