Skip to content

t311.3: Extract supervisor modules#1220

Merged
marcusquinn merged 5 commits intomainfrom
feature/t311.3
Feb 12, 2026
Merged

t311.3: Extract supervisor modules#1220
marcusquinn merged 5 commits intomainfrom
feature/t311.3

Conversation

@marcusquinn
Copy link
Owner

@marcusquinn marcusquinn commented Feb 12, 2026

Summary

Successfully extracted supervisor module functions from the monolithic supervisor-helper.sh into dedicated module files, improving code organization and maintainability.

Modules Extracted

  1. release.sh (289 lines)

    • trigger_batch_release() - Automated batch release via version-manager.sh
    • cmd_release() - Manual release trigger command
    • Handles version bumping, changelog generation, and GitHub releases
  2. todo-sync.sh (810 lines)

    • commit_and_push_todo() - Concurrent-safe TODO.md updates
    • populate_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 updates
    • update_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 command
    • cmd_reconcile_todo() - Bulk-fix stale TODO.md entries

Impact

  • Main file reduced: 15,281 → 14,412 lines (869 lines removed)
  • Total extracted: 1,099 lines into 2 modules
  • ShellCheck: Zero violations in both modules
  • Syntax: All files pass bash -n validation
  • Functionality: Supervisor commands tested and working

Testing

  • supervisor-helper.sh help - displays full command list
  • supervisor-helper.sh status - shows current supervisor state
  • ✅ ShellCheck passes on all modified files
  • ✅ Syntax validation passes
  • ✅ Functions properly sourced from modules

Next Steps

This PR establishes the pattern for further modularization. Remaining candidates:

  • batch.sh (batch management functions)
  • dispatch.sh (worker dispatch and claiming)
  • lifecycle.sh (task state transitions)
  • pulse.sh (pulse cycle orchestration)
  • recovery.sh (evaluation and error recovery)

Each module can be extracted incrementally following the same pattern demonstrated here.

Summary by CodeRabbit

  • New Features

    • Enhanced task blocking notifications posted to GitHub with contextual details
    • Improved batch release workflow with validation, main-branch enforcement, and post-release notifications
    • Expanded verification queue management with status tracking and verification entry generation
  • Refactor

    • Streamlined task state management to prioritize GitHub-based visibility and notifications over local file modifications

- Move trigger_batch_release() to release.sh module
- Move cmd_release() to release.sh module
- Functions now properly modularized for batch release management
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 12, 2026

Caution

Review failed

The pull request is closed.

Walkthrough

The refactoring modularizes supervisor functionality by consolidating batch release logic into release.sh, expanding todo-sync.sh with comprehensive TODO/verification workflows, and removing legacy release/verification/commit functions from supervisor-helper.sh while introducing GitHub-based blocking notifications.

Changes

Cohort / File(s) Summary
Supervisor Core Cleanup
.agents/scripts/supervisor-helper.sh
Removed 1,193 lines of legacy batch release, verification queue, and TODO.md commit logic (trigger_batch_release, commit_and_push_todo, populate_verify_queue, generate_verify_entry, process_verify_queue, update_todo_on_complete, commit_verify_changes). Added post_blocked_comment_to_github() to shift blocking signals from local TODO.md updates to GitHub issue comments with label management.
Release Management
.agents/scripts/supervisor/release.sh
Added trigger_batch_release() implementing full batch workflow: version validation, repo/type checks, main branch enforcement, stash/restore handling, version-manager.sh invocation with flags, and post-release notifications. Added cmd_release() command handler with --type, --enable, --disable, --dry-run options and batch resolution logic. Replaced placeholder functions with concrete implementations.
TODO Synchronization & Verification
.agents/scripts/supervisor/todo-sync.sh
Expanded from 15 to 812 lines with 10 new public functions: commit_and_push_todo() (concurrent pull-rebase with retry), populate_verify_queue() (PR→VERIFY.md entry generation), mark_verify_entry() (timestamped result tracking), process_verify_queue() (batch verification orchestration), commit_verify_changes() (VERIFY.md push), update_todo_on_complete() (deliverable validation + TODO.md update), generate_verify_entry() (auto-entry creation), update_todo_on_blocked() (TODO.md blocking + GitHub notifications), cmd_update_todo() (manual trigger), cmd_reconcile_todo() (bulk completion reconciliation with dry-run support).

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
Loading
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
Loading
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
Loading

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

🔧 Functions scattered, now they find their home,
Release and TODO each get their own zone,
GitHub notifications bloom where TODO once stood,
Concurrent git dances—queued, verified, good! ✨
From monolith chaos to modular grace—DevOps refactored, in its right place.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/t311.3

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link

🔍 Code Quality Report

�[0;35m[MONITOR]�[0m Code Review Monitoring Report

�[0;34m[INFO]�[0m Latest Quality Status:
SonarCloud: 0 bugs, 0 vulnerabilities, 15 code smells

�[0;34m[INFO]�[0m Recent monitoring activity:
Thu Feb 12 12:30:39 UTC 2026: Code review monitoring started
Thu Feb 12 12:30:40 UTC 2026: SonarCloud - Bugs: 0, Vulnerabilities: 0, Code Smells: 15

📈 Current Quality Metrics

  • BUGS: 0
  • CODE SMELLS: 15
  • VULNERABILITIES: 0

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
@github-actions
Copy link

🔍 Code Quality Report

�[0;35m[MONITOR]�[0m Code Review Monitoring Report

�[0;34m[INFO]�[0m Latest Quality Status:
SonarCloud: 0 bugs, 0 vulnerabilities, 15 code smells

�[0;34m[INFO]�[0m Recent monitoring activity:
Thu Feb 12 12:32:06 UTC 2026: Code review monitoring started
Thu Feb 12 12:32:06 UTC 2026: SonarCloud - Bugs: 0, Vulnerabilities: 0, Code Smells: 15

📈 Current Quality Metrics

  • BUGS: 0
  • CODE SMELLS: 15
  • VULNERABILITIES: 0

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
@github-actions
Copy link

🔍 Code Quality Report

�[0;35m[MONITOR]�[0m Code Review Monitoring Report

�[0;34m[INFO]�[0m Latest Quality Status:
SonarCloud: 0 bugs, 0 vulnerabilities, 15 code smells

�[0;34m[INFO]�[0m Recent monitoring activity:
Thu Feb 12 12:38:19 UTC 2026: Code review monitoring started
Thu Feb 12 12:38:20 UTC 2026: SonarCloud - Bugs: 0, Vulnerabilities: 0, Code Smells: 15

📈 Current Quality Metrics

  • BUGS: 0
  • CODE SMELLS: 15
  • VULNERABILITIES: 0

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
@github-actions
Copy link

🔍 Code Quality Report

�[0;35m[MONITOR]�[0m Code Review Monitoring Report

�[0;34m[INFO]�[0m Latest Quality Status:
SonarCloud: 0 bugs, 0 vulnerabilities, 15 code smells

�[0;34m[INFO]�[0m Recent monitoring activity:
Thu Feb 12 12:40:03 UTC 2026: Code review monitoring started
Thu Feb 12 12:40:04 UTC 2026: SonarCloud - Bugs: 0, Vulnerabilities: 0, Code Smells: 15

📈 Current Quality Metrics

  • BUGS: 0
  • CODE SMELLS: 15
  • VULNERABILITIES: 0

Generated on: Thu Feb 12 12:40:06 UTC 2026


Generated by AI DevOps Framework Code Review Monitoring

@marcusquinn marcusquinn marked this pull request as ready for review February 12, 2026 12:40
@sonarqubecloud
Copy link

@gemini-code-assist
Copy link

Summary of Changes

Hello @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 supervisor-helper.sh script is now leaner, focusing on its primary orchestration responsibilities, while related concerns are encapsulated in their respective modules.

Highlights

  • Modularization of Supervisor Script: Extracted 1,099 lines of code from the monolithic supervisor-helper.sh into two new, dedicated module files: release.sh and todo-sync.sh.
  • Release Management Module: Moved trigger_batch_release and cmd_release functions into release.sh, centralizing automated and manual release processes.
  • TODO.md and Verification Synchronization Module: Consolidated all TODO.md and VERIFY.md related functions, including commit_and_push_todo, populate_verify_queue, mark_verify_entry, process_verify_queue, commit_verify_changes, update_todo_on_complete, generate_verify_entry, update_todo_on_blocked, cmd_update_todo, and cmd_reconcile_todo into todo-sync.sh.
  • Improved Maintainability: Reduced the main supervisor-helper.sh file by 869 lines, enhancing code organization and making future development and debugging more straightforward.
Changelog
  • .agents/scripts/supervisor-helper.sh
    • Removed trigger_batch_release function.
    • Removed cmd_release function.
    • Removed commit_and_push_todo function.
    • Removed populate_verify_queue function.
    • Removed mark_verify_entry function.
    • Removed process_verify_queue function.
    • Removed commit_verify_changes function.
    • Removed update_todo_on_complete function.
    • Removed generate_verify_entry function.
    • Removed update_todo_on_blocked function.
    • Removed cmd_update_todo function.
    • Removed cmd_reconcile_todo function.
  • .agents/scripts/supervisor/release.sh
    • Added trigger_batch_release function for automated batch releases.
    • Added cmd_release function for manual release triggers.
    • Removed placeholder functions for release management.
  • .agents/scripts/supervisor/todo-sync.sh
    • Added commit_and_push_todo function for concurrent-safe TODO.md updates.
    • Added populate_verify_queue function for post-merge verification queue.
    • Added mark_verify_entry function to mark verification results.
    • Added process_verify_queue function to run verification checks.
    • Added commit_verify_changes function to commit VERIFY.md updates.
    • Added update_todo_on_complete function to mark tasks complete with deliverable verification.
    • Added generate_verify_entry function to auto-generate verification entries.
    • Added update_todo_on_blocked function to mark tasks blocked with GitHub issue sync.
    • Added cmd_update_todo function for manual TODO.md updates.
    • Added cmd_reconcile_todo function for bulk-fixing stale TODO.md entries.
    • Removed placeholder functions for TODO.md synchronization.
Activity
  • No human activity (comments, reviews) has been recorded on this pull request yet.
Using Gemini Code Assist

The 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 /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

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 .gemini/ folder in the base of the repository. Detailed instructions can be found here.

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

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@marcusquinn marcusquinn merged commit b5bd154 into main Feb 12, 2026
9 of 11 checks passed
@marcusquinn marcusquinn deleted the feature/t311.3 branch February 12, 2026 12:42
Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +231 to +425
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}/"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-critical critical

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 }

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-critical critical

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.

Comment on lines +157 to +186
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"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-critical critical

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.

Comment on lines +100 to +614
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)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-medium medium

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.

Suggested change
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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

Comments