-
Notifications
You must be signed in to change notification settings - Fork 42
feat: Phase 3a — auto-adopt untracked PRs into supervisor pipeline #1704
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -687,6 +687,19 @@ cmd_pulse() { | |||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| # Phase 3a: Adopt untracked PRs into the supervisor pipeline | ||||||||||||||||||||||||||||||||||||||||||||||
| # Scans open PRs for each tracked repo and adopts any that: | ||||||||||||||||||||||||||||||||||||||||||||||
| # 1. Have a task ID in the title (tNNN: description) | ||||||||||||||||||||||||||||||||||||||||||||||
| # 2. Are not already tracked in the supervisor DB | ||||||||||||||||||||||||||||||||||||||||||||||
| # 3. Have a matching open task in TODO.md | ||||||||||||||||||||||||||||||||||||||||||||||
| # Adopted PRs get a DB entry with status=complete so Phase 3 processes them | ||||||||||||||||||||||||||||||||||||||||||||||
| # through the normal review → merge → verify lifecycle. | ||||||||||||||||||||||||||||||||||||||||||||||
| # This closes the gap where interactive sessions create PRs that the | ||||||||||||||||||||||||||||||||||||||||||||||
| # supervisor can't manage (review, merge, verify, clean up). | ||||||||||||||||||||||||||||||||||||||||||||||
| if command -v gh &>/dev/null; then | ||||||||||||||||||||||||||||||||||||||||||||||
| adopt_untracked_prs 2>>"$SUPERVISOR_LOG" || true | ||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| # Phase 3: Post-PR lifecycle (t128.8) | ||||||||||||||||||||||||||||||||||||||||||||||
| # Process tasks that workers completed (PR created) but still need merge/deploy | ||||||||||||||||||||||||||||||||||||||||||||||
| # t265: Redirect stderr to log and capture errors before || true suppresses them | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -2170,6 +2183,176 @@ RULES: | |||||||||||||||||||||||||||||||||||||||||||||
| return 0 | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| ####################################### | ||||||||||||||||||||||||||||||||||||||||||||||
| # Phase 3a: Adopt untracked PRs into the supervisor pipeline | ||||||||||||||||||||||||||||||||||||||||||||||
| # Scans open PRs for each tracked repo and creates DB entries for any | ||||||||||||||||||||||||||||||||||||||||||||||
| # that have a task ID in the title but aren't tracked in the supervisor DB. | ||||||||||||||||||||||||||||||||||||||||||||||
| # This allows PRs created in interactive sessions to be managed by the | ||||||||||||||||||||||||||||||||||||||||||||||
| # supervisor (review, merge, verify, clean up) without manual registration. | ||||||||||||||||||||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||||||||||||||||||||
| # Adoption criteria: | ||||||||||||||||||||||||||||||||||||||||||||||
| # 1. PR title matches pattern: tNNN: description (or tNNN.N: description) | ||||||||||||||||||||||||||||||||||||||||||||||
| # 2. No task in the DB already has this PR URL | ||||||||||||||||||||||||||||||||||||||||||||||
| # 3. The task ID exists as an open task in TODO.md | ||||||||||||||||||||||||||||||||||||||||||||||
| # 4. The task is not already in the DB (avoids duplicating worker tasks) | ||||||||||||||||||||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||||||||||||||||||||
| # Adopted tasks enter the DB with status=complete and the PR URL, so | ||||||||||||||||||||||||||||||||||||||||||||||
| # Phase 3 picks them up through the normal lifecycle. | ||||||||||||||||||||||||||||||||||||||||||||||
| ####################################### | ||||||||||||||||||||||||||||||||||||||||||||||
| adopt_untracked_prs() { | ||||||||||||||||||||||||||||||||||||||||||||||
| ensure_db | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| # Collect all unique repos from the DB | ||||||||||||||||||||||||||||||||||||||||||||||
| local repos | ||||||||||||||||||||||||||||||||||||||||||||||
| repos=$(db "$SUPERVISOR_DB" "SELECT DISTINCT repo FROM tasks WHERE repo IS NOT NULL AND repo != '';" 2>/dev/null || echo "") | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| if [[ -z "$repos" ]]; then | ||||||||||||||||||||||||||||||||||||||||||||||
| return 0 | ||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| local adopted_count=0 | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| while IFS= read -r repo_path; do | ||||||||||||||||||||||||||||||||||||||||||||||
| [[ -z "$repo_path" || ! -d "$repo_path" ]] && continue | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| # Get repo slug for gh CLI | ||||||||||||||||||||||||||||||||||||||||||||||
| local repo_slug | ||||||||||||||||||||||||||||||||||||||||||||||
| repo_slug=$(detect_repo_slug "$repo_path" 2>/dev/null || echo "") | ||||||||||||||||||||||||||||||||||||||||||||||
| if [[ -z "$repo_slug" ]]; then | ||||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| # List open PRs (limit to 20 to avoid API rate limits) | ||||||||||||||||||||||||||||||||||||||||||||||
| local open_prs | ||||||||||||||||||||||||||||||||||||||||||||||
| open_prs=$(gh pr list --repo "$repo_slug" --state open --limit 20 \ | ||||||||||||||||||||||||||||||||||||||||||||||
| --json number,title,url 2>/dev/null || echo "[]") | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| local pr_count | ||||||||||||||||||||||||||||||||||||||||||||||
| pr_count=$(printf '%s' "$open_prs" | jq 'length' 2>/dev/null || echo 0) | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| local i=0 | ||||||||||||||||||||||||||||||||||||||||||||||
| while [[ "$i" -lt "$pr_count" ]]; do | ||||||||||||||||||||||||||||||||||||||||||||||
| local pr_number pr_title pr_url | ||||||||||||||||||||||||||||||||||||||||||||||
| pr_number=$(printf '%s' "$open_prs" | jq -r ".[$i].number" 2>/dev/null || echo "") | ||||||||||||||||||||||||||||||||||||||||||||||
| pr_title=$(printf '%s' "$open_prs" | jq -r ".[$i].title" 2>/dev/null || echo "") | ||||||||||||||||||||||||||||||||||||||||||||||
| pr_url=$(printf '%s' "$open_prs" | jq -r ".[$i].url" 2>/dev/null || echo "") | ||||||||||||||||||||||||||||||||||||||||||||||
| i=$((i + 1)) | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| # Extract task ID from PR title (pattern: tNNN: or tNNN.N:) | ||||||||||||||||||||||||||||||||||||||||||||||
| local task_id="" | ||||||||||||||||||||||||||||||||||||||||||||||
| if [[ "$pr_title" =~ ^(t[0-9]+(\.[0-9]+)?):\ .* ]]; then | ||||||||||||||||||||||||||||||||||||||||||||||
| task_id="${BASH_REMATCH[1]}" | ||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| if [[ -z "$task_id" ]]; then | ||||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| # Check if this PR is already tracked in the DB | ||||||||||||||||||||||||||||||||||||||||||||||
| local existing_pr | ||||||||||||||||||||||||||||||||||||||||||||||
| existing_pr=$(db "$SUPERVISOR_DB" " | ||||||||||||||||||||||||||||||||||||||||||||||
| SELECT id FROM tasks | ||||||||||||||||||||||||||||||||||||||||||||||
| WHERE pr_url = '$(sql_escape "$pr_url")' | ||||||||||||||||||||||||||||||||||||||||||||||
| LIMIT 1; | ||||||||||||||||||||||||||||||||||||||||||||||
| " 2>/dev/null || echo "") | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+2253
to
+2257
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This method of constructing SQL queries by embedding escaped variables is vulnerable to SQL injection if the References
|
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| if [[ -n "$existing_pr" ]]; then | ||||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| # Check if this task ID is already in the DB (worker-dispatched) | ||||||||||||||||||||||||||||||||||||||||||||||
| local existing_task | ||||||||||||||||||||||||||||||||||||||||||||||
| existing_task=$(db "$SUPERVISOR_DB" " | ||||||||||||||||||||||||||||||||||||||||||||||
| SELECT id, status FROM tasks | ||||||||||||||||||||||||||||||||||||||||||||||
| WHERE id = '$(sql_escape "$task_id")' | ||||||||||||||||||||||||||||||||||||||||||||||
| LIMIT 1; | ||||||||||||||||||||||||||||||||||||||||||||||
| " 2>/dev/null || echo "") | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| if [[ -n "$existing_task" ]]; then | ||||||||||||||||||||||||||||||||||||||||||||||
| # Task exists but doesn't have this PR URL — link it | ||||||||||||||||||||||||||||||||||||||||||||||
| local existing_status | ||||||||||||||||||||||||||||||||||||||||||||||
| existing_status=$(echo "$existing_task" | cut -d'|' -f2) | ||||||||||||||||||||||||||||||||||||||||||||||
| # Only link if the task is in a state where a PR makes sense | ||||||||||||||||||||||||||||||||||||||||||||||
| if [[ "$existing_status" =~ ^(queued|running|evaluating|retrying|complete)$ ]]; then | ||||||||||||||||||||||||||||||||||||||||||||||
| db "$SUPERVISOR_DB" " | ||||||||||||||||||||||||||||||||||||||||||||||
| UPDATE tasks | ||||||||||||||||||||||||||||||||||||||||||||||
| SET pr_url = '$(sql_escape "$pr_url")', | ||||||||||||||||||||||||||||||||||||||||||||||
| status = 'complete', | ||||||||||||||||||||||||||||||||||||||||||||||
| updated_at = strftime('%Y-%m-%dT%H:%M:%SZ', 'now') | ||||||||||||||||||||||||||||||||||||||||||||||
| WHERE id = '$(sql_escape "$task_id")'; | ||||||||||||||||||||||||||||||||||||||||||||||
| " 2>/dev/null || true | ||||||||||||||||||||||||||||||||||||||||||||||
| log_info "Phase 3a: Linked PR #$pr_number to existing task $task_id (was: $existing_status)" | ||||||||||||||||||||||||||||||||||||||||||||||
| adopted_count=$((adopted_count + 1)) | ||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+2271
to
+2288
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Force-completing When
Remove 🐛 Proposed fix- if [[ "$existing_status" =~ ^(queued|running|evaluating|retrying|complete)$ ]]; then
+ if [[ "$existing_status" =~ ^(queued|retrying|complete)$ ]]; then🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| # Task not in DB — check if it exists in TODO.md | ||||||||||||||||||||||||||||||||||||||||||||||
| local todo_file="$repo_path/TODO.md" | ||||||||||||||||||||||||||||||||||||||||||||||
| if [[ ! -f "$todo_file" ]]; then | ||||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| local todo_line | ||||||||||||||||||||||||||||||||||||||||||||||
| todo_line=$(grep -E "^[[:space:]]*- \[( |x|-)\] $task_id " "$todo_file" 2>/dev/null | head -1 || true) | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| if [[ -z "$todo_line" ]]; then | ||||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+2296
to
+2300
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unescaped
🛡️ Proposed fix+ local escaped_task_id
+ escaped_task_id=$(printf '%s' "$task_id" | sed 's/\./\\./g')
todo_line=$(grep -E "^[[:space:]]*- \[( |x|-)\] $task_id " "$todo_file" 2>/dev/null | head -1 || true)
+ todo_line=$(grep -E "^[[:space:]]*- \[( |x|-)\] $escaped_task_id " "$todo_file" 2>/dev/null | head -1 || true)🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| # Extract description from TODO.md | ||||||||||||||||||||||||||||||||||||||||||||||
| local description | ||||||||||||||||||||||||||||||||||||||||||||||
| description=$(echo "$todo_line" | sed -E 's/^[[:space:]]*- \[( |x|-)\] [^ ]* //' || true) | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| # Adopt: create a DB entry with status=complete and the PR URL | ||||||||||||||||||||||||||||||||||||||||||||||
| # Phase 3 will then process it through review → merge → verify | ||||||||||||||||||||||||||||||||||||||||||||||
| local batch_id_for_adopt="" | ||||||||||||||||||||||||||||||||||||||||||||||
| # Find the active batch for this repo to associate the task | ||||||||||||||||||||||||||||||||||||||||||||||
| batch_id_for_adopt=$(db "$SUPERVISOR_DB" " | ||||||||||||||||||||||||||||||||||||||||||||||
| SELECT b.id FROM batches b | ||||||||||||||||||||||||||||||||||||||||||||||
| WHERE b.status IN ('active', 'running') | ||||||||||||||||||||||||||||||||||||||||||||||
| ORDER BY b.created_at DESC | ||||||||||||||||||||||||||||||||||||||||||||||
| LIMIT 1; | ||||||||||||||||||||||||||||||||||||||||||||||
| " 2>/dev/null || echo "") | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+2309
to
+2316
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # Step 1: Locate and read the pulse.sh file around lines 2309-2316
wc -l .agents/scripts/supervisor/pulse.shRepository: marcusquinn/aidevops Length of output: 104 🏁 Script executed: # Step 2: Read the specific section in question
sed -n '2300,2325p' .agents/scripts/supervisor/pulse.shRepository: marcusquinn/aidevops Length of output: 955 🏁 Script executed: # Step 3: Search for CREATE TABLE statements to understand schema
rg "CREATE TABLE" --type sh -A 10Repository: marcusquinn/aidevops Length of output: 50376 🏁 Script executed: # Step 4: Search for how other batch queries are structured in the codebase
rg "SELECT.*FROM.*batches" --type sh -A 3 -B 2Repository: marcusquinn/aidevops Length of output: 17112 🏁 Script executed: # Step 5: Look for how repo filtering is typically done
rg "batch_tasks" --type sh -A 3Repository: marcusquinn/aidevops Length of output: 15281 🏁 Script executed: # Check if there are other places in pulse.sh where batch_id_for_adopt is used
grep -n "batch_id_for_adopt" .agents/scripts/supervisor/pulse.sh | head -20Repository: marcusquinn/aidevops Length of output: 284 🏁 Script executed: # Verify the batches table definition in the actual database schema
grep -A 15 "CREATE TABLE IF NOT EXISTS batches" .agents/scripts/supervisor/database.shRepository: marcusquinn/aidevops Length of output: 838 🏁 Script executed: # Check the context around lines 2309-2316 to see what repo_path is
sed -n '2295,2345p' .agents/scripts/supervisor/pulse.shRepository: marcusquinn/aidevops Length of output: 1761 Batch lookup is not scoped to the current repo — cross-repo contamination in multi-repo setups. The query at lines 2311–2318 picks the most-recently-created active batch across ALL repos. In a multi-repo deployment, a task adopted from repo A will be inserted into repo B's active batch, skewing batch progress counters and potentially triggering that batch's completion actions (retrospective, distillation, auto-release) prematurely. Filter by repo by joining through ♻️ Proposed fix- batch_id_for_adopt=$(db "$SUPERVISOR_DB" "
- SELECT b.id FROM batches b
- WHERE b.status IN ('active', 'running')
- ORDER BY b.created_at DESC
- LIMIT 1;
- " 2>/dev/null || echo "")
+ batch_id_for_adopt=$(db "$SUPERVISOR_DB" "
+ SELECT b.id FROM batches b
+ WHERE b.status IN ('active', 'running')
+ AND EXISTS (
+ SELECT 1 FROM batch_tasks bt
+ JOIN tasks t ON bt.task_id = t.id
+ WHERE bt.batch_id = b.id
+ AND t.repo = '$(sql_escape "$repo_path")'
+ )
+ ORDER BY b.created_at DESC
+ LIMIT 1;
+ " 2>/dev/null || echo "")📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| db "$SUPERVISOR_DB" " | ||||||||||||||||||||||||||||||||||||||||||||||
| INSERT INTO tasks (id, status, description, repo, pr_url, model, max_retries, created_at, updated_at) | ||||||||||||||||||||||||||||||||||||||||||||||
| VALUES ( | ||||||||||||||||||||||||||||||||||||||||||||||
| '$(sql_escape "$task_id")', | ||||||||||||||||||||||||||||||||||||||||||||||
| 'complete', | ||||||||||||||||||||||||||||||||||||||||||||||
| '$(sql_escape "$description")', | ||||||||||||||||||||||||||||||||||||||||||||||
| '$(sql_escape "$repo_path")', | ||||||||||||||||||||||||||||||||||||||||||||||
| '$(sql_escape "$pr_url")', | ||||||||||||||||||||||||||||||||||||||||||||||
| 'interactive', | ||||||||||||||||||||||||||||||||||||||||||||||
| 0, | ||||||||||||||||||||||||||||||||||||||||||||||
| strftime('%Y-%m-%dT%H:%M:%SZ', 'now'), | ||||||||||||||||||||||||||||||||||||||||||||||
| strftime('%Y-%m-%dT%H:%M:%SZ', 'now') | ||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||
| " 2>/dev/null || { | ||||||||||||||||||||||||||||||||||||||||||||||
| log_warn "Phase 3a: Failed to insert task $task_id (may already exist)" | ||||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| # Associate with active batch if one exists | ||||||||||||||||||||||||||||||||||||||||||||||
| if [[ -n "$batch_id_for_adopt" ]]; then | ||||||||||||||||||||||||||||||||||||||||||||||
| db "$SUPERVISOR_DB" " | ||||||||||||||||||||||||||||||||||||||||||||||
| INSERT OR IGNORE INTO batch_tasks (batch_id, task_id) | ||||||||||||||||||||||||||||||||||||||||||||||
| VALUES ('$(sql_escape "$batch_id_for_adopt")', '$(sql_escape "$task_id")'); | ||||||||||||||||||||||||||||||||||||||||||||||
| " 2>/dev/null || true | ||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| log_success "Phase 3a: Adopted PR #$pr_number ($pr_url) as task $task_id" | ||||||||||||||||||||||||||||||||||||||||||||||
| adopted_count=$((adopted_count + 1)) | ||||||||||||||||||||||||||||||||||||||||||||||
| done | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+2234
to
+2346
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This loop is inefficient as it calls while IFS=$'\t' read -r pr_number pr_title pr_url; do
# Extract task ID from PR title (pattern: tNNN: or tNNN.N:)
local task_id=""
if [[ "$pr_title" =~ ^(t[0-9]+(\.[0-9]+)?):\ .* ]]; then
task_id="${BASH_REMATCH[1]}"
fi
if [[ -z "$task_id" ]]; then
continue
fi
# Check if this PR is already tracked in the DB
local existing_pr
existing_pr=$(db "$SUPERVISOR_DB" "
SELECT id FROM tasks
WHERE pr_url = '$(sql_escape "$pr_url")'
LIMIT 1;
" 2>/dev/null || echo "")
if [[ -n "$existing_pr" ]]; then
continue
fi
# Check if this task ID is already in the DB (worker-dispatched)
local existing_task
existing_task=$(db "$SUPERVISOR_DB" "
SELECT id, status FROM tasks
WHERE id = '$(sql_escape "$task_id")'
LIMIT 1;
" 2>/dev/null || echo "")
if [[ -n "$existing_task" ]]; then
# Task exists but doesn't have this PR URL — link it
local existing_status
existing_status=$(echo "$existing_task" | cut -d'|' -f2)
# Only link if the task is in a state where a PR makes sense
if [[ "$existing_status" =~ ^(queued|running|evaluating|retrying|complete)$ ]]; then
db "$SUPERVISOR_DB" "
UPDATE tasks
SET pr_url = '$(sql_escape "$pr_url")',
status = 'complete',
updated_at = strftime('%Y-%m-%dT%H:%M:%SZ', 'now')
WHERE id = '$(sql_escape "$task_id")';
" 2>/dev/null || true
log_info "Phase 3a: Linked PR #$pr_number to existing task $task_id (was: $existing_status)"
adopted_count=$((adopted_count + 1))
fi
continue
fi
# Task not in DB — check if it exists in TODO.md
local todo_file="$repo_path/TODO.md"
if [[ ! -f "$todo_file" ]]; then
continue
fi
local todo_line
todo_line=$(grep -E "^[[:space:]]*- \[ |x|-\] $task_id " "$todo_file" 2>/dev/null | head -1 || true)
if [[ -z "$todo_line" ]]; then
continue
fi
# Extract description from TODO.md
local description
description=$(echo "$todo_line" | sed -E 's/^[[:space:]]*- \[ |x|-\] [^ ]* //' || true)
# Adopt: create a DB entry with status=complete and the PR URL
# Phase 3 will then process it through review → merge → verify
local batch_id_for_adopt=""
# Find the active batch for this repo to associate the task
batch_id_for_adopt=$(db "$SUPERVISOR_DB" "
SELECT b.id FROM batches b
WHERE b.status IN ('active', 'running')
ORDER BY b.created_at DESC
LIMIT 1;
" 2>/dev/null || echo "")
db "$SUPERVISOR_DB" "
INSERT INTO tasks (id, status, description, repo, pr_url, model, max_retries, created_at, updated_at)
VALUES (
'$(sql_escape "$task_id")',
'complete',
'$(sql_escape "$description")',
'$(sql_escape "$repo_path")',
'$(sql_escape "$pr_url")',
'interactive',
0,
strftime('%Y-%m-%dT%H:%M:%SZ', 'now'),
strftime('%Y-%m-%dT%H:%M:%SZ', 'now')
);
" 2>/dev/null || {
log_warn "Phase 3a: Failed to insert task $task_id (may already exist)"
continue
}
# Associate with active batch if one exists
if [[ -n "$batch_id_for_adopt" ]]; then
db "$SUPERVISOR_DB" "
INSERT OR IGNORE INTO batch_tasks (batch_id, task_id)
VALUES ('$(sql_escape "$batch_id_for_adopt")', '$(sql_escape "$task_id")');
" 2>/dev/null || true
fi
log_success "Phase 3a: Adopted PR #$pr_number ($pr_url) as task $task_id"
adopted_count=$((adopted_count + 1))
done < <(printf '%s' "$open_prs" | jq -r '.[] | [.number, .title, .url] | @tsv') |
||||||||||||||||||||||||||||||||||||||||||||||
| done <<<"$repos" | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| if [[ "$adopted_count" -gt 0 ]]; then | ||||||||||||||||||||||||||||||||||||||||||||||
| log_info "Phase 3a: Adopted $adopted_count untracked PR(s)" | ||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| return 0 | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| ####################################### | ||||||||||||||||||||||||||||||||||||||||||||||
| # Process post-PR lifecycle for all eligible tasks | ||||||||||||||||||||||||||||||||||||||||||||||
| # Called as Phase 3 of the pulse cycle | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The hardcoded limit
20for the number of pull requests to fetch should be extracted into a constant for better readability and maintainability. This makes it easier to find and change in the future.You can define it at the top of the
adopt_untracked_prsfunction:local -r MAX_PRS_TO_ADOPT=20