Skip to content

Comments

feat: Phase 3a — auto-adopt untracked PRs into supervisor pipeline#1704

Merged
marcusquinn merged 1 commit intomainfrom
feature/pr-adoption
Feb 18, 2026
Merged

feat: Phase 3a — auto-adopt untracked PRs into supervisor pipeline#1704
marcusquinn merged 1 commit intomainfrom
feature/pr-adoption

Conversation

@marcusquinn
Copy link
Owner

@marcusquinn marcusquinn commented Feb 18, 2026

Summary

  • Adds Phase 3a to the supervisor pulse cycle: automatic adoption of untracked PRs
  • PRs created in interactive sessions (not via worker dispatch) are now automatically discovered and managed by the supervisor
  • Adopted PRs flow through the normal Phase 3 lifecycle: review triage → merge → verify → clean up

Problem

When a human or interactive AI session creates a PR, the supervisor has no knowledge of it. The PR must be manually merged, and the supervisor can't:

  • Run review triage (CodeRabbit, Codacy, SonarCloud thread analysis)
  • Auto-merge when CI passes
  • Run post-merge verification
  • Clean up worktrees

This is especially problematic for self-improvement PRs where the supervisor's AI reasoning identified the problem but the fix was implemented interactively.

Solution

Phase 3a (adopt_untracked_prs()) runs before Phase 3 in each pulse cycle:

  1. Lists open PRs for each tracked repo via gh pr list
  2. Extracts task ID from PR title (tNNN: description pattern)
  3. Checks if the PR is already tracked in the supervisor DB
  4. If not tracked:
    • Existing task in DB: Links the PR URL and transitions to complete
    • New task (in TODO.md only): Creates a DB entry with status=complete, model=interactive
  5. Phase 3 then processes the adopted PR through the normal lifecycle

Safety

  • Only adopts PRs with task IDs in the title (convention: tNNN: description)
  • Skips PRs that are already tracked
  • Skips task IDs not found in TODO.md (won't adopt random external PRs)
  • Uses INSERT (not INSERT OR REPLACE) so it can't overwrite existing tasks
  • Rate-limited to 20 PRs per repo per pulse

Testing

  • bash -n: syntax OK
  • ShellCheck: zero new violations
  • Deployed to ~/.aidevops/agents/scripts/supervisor/

Summary by CodeRabbit

  • New Features
    • Automated detection and integration of untracked pull requests into the supervisor pipeline. The system automatically scans repositories, identifies untracked pull requests with task identifiers, matches them to corresponding tracked tasks, consolidates duplicate entries, and manages pull requests through standard review, merge, and verification workflows when integration tools are available.

Add adopt_untracked_prs() function that runs before Phase 3 in each pulse
cycle. Scans open PRs for tracked repos and adopts any that:

1. Have a task ID in the title (tNNN: description pattern)
2. Are not already tracked in the supervisor DB
3. Have a matching 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 PRs created in interactive sessions (not via worker dispatch) were
invisible to the supervisor and required manual merging.

Two adoption paths:
- New task: creates a DB entry with model='interactive' and associates it
  with the active batch
- Existing task: links the PR URL and transitions to 'complete' so Phase 3
  picks it up (handles cases where a worker was dispatched but the human
  implemented the fix first)
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 18, 2026

Walkthrough

A new Phase 3a workflow is introduced in the supervisor pipeline to automatically adopt untracked open PRs from monitored repositories. The workflow extracts task IDs from PR titles, checks the task database for matches, and either links to existing tasks or creates new entries before integrating them into the standard review and merge lifecycle.

Changes

Cohort / File(s) Summary
PR Adoption Workflow
.agents/scripts/supervisor/pulse.sh
Introduces Phase 3a PR adoption logic via adopt_untracked_prs() function. Scans open PRs for task IDs, reconciles with task DB, links or creates task entries, and logs adoption events. Function is defined and invoked at multiple Phase 3a insertion points. Note: Function definition appears duplicated in multiple locations within the file.

Sequence Diagram(s)

sequenceDiagram
    participant Supervisor as Supervisor Pipeline
    participant GH as gh CLI
    participant DB as Task Database
    participant Repo as Repository (TODO.md)
    participant Batch as Active Batch

    Supervisor->>GH: Scan open PRs (Phase 3a)
    GH-->>Supervisor: PR list with titles
    
    loop For each PR
        Supervisor->>Supervisor: Extract task ID from PR title
        alt Valid task ID found
            Supervisor->>DB: Check for existing PR URL match
            alt PR URL exists in DB
                Supervisor->>DB: Link/update task, set status complete
            else PR URL not found
                Supervisor->>DB: Check if task ID exists & is linkable
                alt Task ID in DB
                    Supervisor->>DB: Update pr_url, mark complete
                else Task ID not in DB
                    Supervisor->>Repo: Verify TODO.md exists
                    Repo-->>Supervisor: Task description from TODO.md
                    Supervisor->>DB: Create new task entry
                    Supervisor->>Batch: Associate with active batch
                end
            end
            Supervisor->>Supervisor: Log adoption event, increment counter
        else No valid task ID
            Supervisor->>Supervisor: Skip PR
        end
    end
    
    Supervisor-->>Supervisor: Log total adopted PRs
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

📋 Orphaned PRs, now given a home,
Phase 3a waves its automation wand,
Task IDs extracted from titles they roam,
Database embraces what was once withdrawn,
The pipeline flows complete and strong! 🚀

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: introduction of Phase 3a to auto-adopt untracked PRs into the supervisor pipeline, which aligns with the 183-line addition to pulse.sh.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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/pr-adoption

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.

@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 addresses a critical gap where Pull Requests created manually or through interactive AI sessions were not managed by the supervisor. By implementing a new 'Phase 3a', the system can now automatically discover and integrate these untracked PRs into its existing lifecycle, ensuring they benefit from automated review, merging, verification, and cleanup processes. This enhancement significantly improves the supervisor's ability to manage all relevant development tasks, especially for self-improvement PRs.

Highlights

  • Automatic PR Adoption: Introduced 'Phase 3a' to the supervisor pulse cycle, enabling automatic adoption of untracked Pull Requests.
  • Interactive Session PR Management: Pull Requests created in interactive sessions (not via worker dispatch) are now automatically discovered and managed by the supervisor.
  • Integrated Lifecycle: Adopted PRs seamlessly flow through the normal Phase 3 lifecycle, including review triage, auto-merge, post-merge verification, and worktree cleanup.
  • Robust Adoption Criteria: PRs are adopted only if they have a task ID in the title (e.g., 'tNNN:'), are not already tracked, and have a matching open task in TODO.md, with rate-limiting applied.
Changelog
  • .agents/scripts/supervisor/pulse.sh
    • Added a new Phase 3a to the cmd_pulse function, which calls adopt_untracked_prs to automatically discover and manage untracked PRs.
    • Implemented the adopt_untracked_prs function, which iterates through tracked repositories, lists open PRs, extracts task IDs from PR titles, and either links existing tasks or creates new ones in the supervisor database for untracked PRs that match specific criteria.
Activity
  • No specific activity (comments, reviews, progress) was provided in the context for this pull request.
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.

@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, 24 code smells

�[0;34m[INFO]�[0m Recent monitoring activity:
Wed Feb 18 16:57:13 UTC 2026: Code review monitoring started
Wed Feb 18 16:57:13 UTC 2026: SonarCloud - Bugs: 0, Vulnerabilities: 0, Code Smells: 24

📈 Current Quality Metrics

  • BUGS: 0
  • CODE SMELLS: 24
  • VULNERABILITIES: 0

Generated on: Wed Feb 18 16:57:16 UTC 2026


Generated by AI DevOps Framework Code Review Monitoring

@sonarqubecloud
Copy link

@marcusquinn marcusquinn merged commit 5a6cb35 into main Feb 18, 2026
9 of 11 checks passed
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 introduces a valuable feature for automatically adopting interactively-created pull requests into the supervisor's management pipeline. The implementation is well-structured and includes good commentary. My review includes one high-severity security concern regarding SQL query construction, which violates both the repository style guide and an established rule for preventing SQL injection. Additionally, there are two medium-severity suggestions to improve maintainability and efficiency by refactoring a loop and extracting a hardcoded value into a constant.

Comment on lines +2253 to +2257
existing_pr=$(db "$SUPERVISOR_DB" "
SELECT id FROM tasks
WHERE pr_url = '$(sql_escape "$pr_url")'
LIMIT 1;
" 2>/dev/null || echo "")

Choose a reason for hiding this comment

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

high

This method of constructing SQL queries by embedding escaped variables is vulnerable to SQL injection if the sql_escape function is flawed. It also violates the repository style guide, which states: 'Use parameterized queries where possible' (line 25).

References
  1. The code constructs SQL queries using string interpolation with an escape function, which is not as secure as using parameterized queries. The style guide recommends using parameterized queries where possible to prevent SQL injection vulnerabilities. (link)
  2. To prevent SQL injection in shell scripts using sqlite3, create a helper function that uses .param set for safe parameterized bindings instead of direct string interpolation.

Comment on lines +2227 to +2228
open_prs=$(gh pr list --repo "$repo_slug" --state open --limit 20 \
--json number,title,url 2>/dev/null || echo "[]")

Choose a reason for hiding this comment

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

medium

The hardcoded limit 20 for 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_prs function:

local -r MAX_PRS_TO_ADOPT=20
Suggested change
open_prs=$(gh pr list --repo "$repo_slug" --state open --limit 20 \
--json number,title,url 2>/dev/null || echo "[]")
open_prs=$(gh pr list --repo "$repo_slug" --state open --limit "$MAX_PRS_TO_ADOPT" \
--json number,title,url 2>/dev/null || echo "[]")

Comment on lines +2234 to +2346
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 "")

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

Choose a reason for hiding this comment

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

medium

This loop is inefficient as it calls jq multiple times for every pull request. A more efficient and idiomatic approach is to use a single jq command to format all the data and pipe it to a while read loop. Using process substitution (< <(...)) also avoids running the loop in a subshell, ensuring that variables like adopted_count are correctly modified in the current shell context.

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')

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (2)
.agents/scripts/supervisor/pulse.sh (2)

2277-2283: Missing state_log entry for the existing-task link path.

Every other state transition in the pulse writes to state_log (and often proof_log) so that audit queries, batch completion checks, and Phase 14 AI self-reflection have a complete picture. The UPDATE … SET status='complete' path here is silent — if a task mysteriously appears in complete state with a new pr_url, there is no log record of when or why Phase 3a intervened.

Add a state_log INSERT after the UPDATE (and a matching one after the INSERT at line 2331).

♻️ Proposed additions
 				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
+				db "$SUPERVISOR_DB" "
+					INSERT INTO state_log (task_id, from_state, to_state, timestamp, reason)
+					VALUES ('$(sql_escape "$task_id")', '$existing_status', 'complete',
+						strftime('%Y-%m-%dT%H:%M:%SZ', 'now'),
+						'Phase 3a: linked untracked PR #$pr_number ($pr_url)');
+				" 2>/dev/null || true

Apply a similar INSERT INTO state_log after the main INSERT INTO tasks at line 2331.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/scripts/supervisor/pulse.sh around lines 2277 - 2283, The UPDATE
path that sets tasks.status='complete' and pr_url in the db("$SUPERVISOR_DB")
block is missing a corresponding audit entry; after the UPDATE (the block that
uses sql_escape "$pr_url" and sql_escape "$task_id") add an INSERT INTO
state_log recording task_id, old_state (or 'existing-task'),
new_state='complete', actor='phase_3a' (or appropriate actor), and timestamp
(strftime('%Y-%m-%dT%H:%M:%SZ','now')) so the transition is auditable; likewise,
add a matching INSERT INTO state_log immediately after the other INSERT INTO
tasks block (the main task creation path) so both the UPDATE-overwrite and the
initial INSERT produce state_log entries.

2233-2239: Triple jq subprocesses per PR; a single invocation covers all three fields.

With the 20-PR cap this is negligible, but three printf | jq fork-execs per iteration is avoidable.

♻️ Proposed refactor — single jq pass, TSV output
-		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))
+		while IFS=$'\t' read -r pr_number pr_title pr_url; do
+			[[ -z "$pr_number" ]] && continue

And replace the closing done with:

-		done
+		done < <(printf '%s' "$open_prs" | jq -r '.[] | [.number, .title, .url] | `@tsv`' 2>/dev/null || true)

Note: @tsv escapes tab/newline characters within field values, so PR titles containing tabs are handled safely.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/scripts/supervisor/pulse.sh around lines 2233 - 2239, The loop
currently runs three separate printf|jq calls per PR to populate pr_number,
pr_title and pr_url; replace those with a single jq invocation that emits the
three fields as a TSV and read them into pr_number/pr_title/pr_url in one step
(e.g. use jq -r ".[$i] | [.number, .title, .url] | `@tsv`" against $open_prs and
then read the tab-separated output into the three variables), and update the
loop’s closing/done handling accordingly so the single read feeds the variables
and preserves proper escaping of tabs/newlines in titles.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.agents/scripts/supervisor/pulse.sh:
- Around line 2271-2288: The code currently forces tasks with existing_status
matching (queued|running|evaluating|retrying|complete) to be marked complete and
linked to the PR, which can orphan live workers; update the conditional in the
pulse.sh block that checks existing_status (the regex match used around the
UPDATE for task_id/pr_url and the log_info line "Phase 3a: Linked PR") to remove
"running" and "evaluating" from the allowlist so only tasks in queued, retrying,
or complete are auto-linked; keep the rest of the update and adopted_count
increment logic unchanged and ensure the conditional uses the updated regex to
prevent hijacking in-flight work.
- Around line 2296-2300: The grep regex is treating dots in $task_id as
wildcards, so update the code that sets todo_line to escape dots in the task id
before interpolation; e.g. create an escaped variable like
escaped_task_id=${task_id//./\\.} and use grep -E "^[[:space:]]*- \\[( |x|-)\\]
$escaped_task_id " "$todo_file" to ensure literal dots are matched (refer to
todo_line, $task_id, grep -E and $todo_file in the diff).
- Around line 2309-2316: The batch lookup currently selects the most-recent
active batch across all repos; update the SQL in the db call that sets
batch_id_for_adopt to restrict batches to the current repo by adding an EXISTS
subquery that joins batch_tasks bt to tasks t and matches t.repo = '$(sql_escape
"$repo_path")' (same pattern used in cron.sh/state.sh). Keep the surrounding
variable (batch_id_for_adopt), db invocation and SUPERVISOR_DB usage unchanged;
only modify the WHERE clause to add the EXISTS(...) repo filter so an adopted
task is associated only with a batch containing tasks from the same repo.

---

Nitpick comments:
In @.agents/scripts/supervisor/pulse.sh:
- Around line 2277-2283: The UPDATE path that sets tasks.status='complete' and
pr_url in the db("$SUPERVISOR_DB") block is missing a corresponding audit entry;
after the UPDATE (the block that uses sql_escape "$pr_url" and sql_escape
"$task_id") add an INSERT INTO state_log recording task_id, old_state (or
'existing-task'), new_state='complete', actor='phase_3a' (or appropriate actor),
and timestamp (strftime('%Y-%m-%dT%H:%M:%SZ','now')) so the transition is
auditable; likewise, add a matching INSERT INTO state_log immediately after the
other INSERT INTO tasks block (the main task creation path) so both the
UPDATE-overwrite and the initial INSERT produce state_log entries.
- Around line 2233-2239: The loop currently runs three separate printf|jq calls
per PR to populate pr_number, pr_title and pr_url; replace those with a single
jq invocation that emits the three fields as a TSV and read them into
pr_number/pr_title/pr_url in one step (e.g. use jq -r ".[$i] | [.number, .title,
.url] | `@tsv`" against $open_prs and then read the tab-separated output into the
three variables), and update the loop’s closing/done handling accordingly so the
single read feeds the variables and preserves proper escaping of tabs/newlines
in titles.

Comment on lines +2271 to +2288
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
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Force-completing running/evaluating tasks can orphan active worker processes.

When existing_status is running, the UPDATE at line 2277 sets status='complete' while the worker process is still alive. Phase 4's health check skips tasks whose DB status is not running/dispatched, so the worker is no longer supervised. It continues executing — making commits, API calls — until it either exits naturally or Phase 4e's PPID=1 sweep eventually catches it.

evaluating is less severe (no live worker) but still bypasses the normal evaluation/outcome flow.

Remove running and evaluating from the allowlist; these states indicate in-flight work that shouldn't be hijacked.

🐛 Proposed fix
-			if [[ "$existing_status" =~ ^(queued|running|evaluating|retrying|complete)$ ]]; then
+			if [[ "$existing_status" =~ ^(queued|retrying|complete)$ ]]; then
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/scripts/supervisor/pulse.sh around lines 2271 - 2288, The code
currently forces tasks with existing_status matching
(queued|running|evaluating|retrying|complete) to be marked complete and linked
to the PR, which can orphan live workers; update the conditional in the pulse.sh
block that checks existing_status (the regex match used around the UPDATE for
task_id/pr_url and the log_info line "Phase 3a: Linked PR") to remove "running"
and "evaluating" from the allowlist so only tasks in queued, retrying, or
complete are auto-linked; keep the rest of the update and adopted_count
increment logic unchanged and ensure the conditional uses the updated regex to
prevent hijacking in-flight work.

Comment on lines +2296 to +2300
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
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Unescaped . in extended regex: subtask IDs like t123.4 match any character in that position.

grep -E treats . in $task_id as a wildcard, so t123.4 would also match a line containing t123X4. Escape the dot before interpolation.

🛡️ 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
Verify each finding against the current code and only fix it if needed.

In @.agents/scripts/supervisor/pulse.sh around lines 2296 - 2300, The grep regex
is treating dots in $task_id as wildcards, so update the code that sets
todo_line to escape dots in the task id before interpolation; e.g. create an
escaped variable like escaped_task_id=${task_id//./\\.} and use grep -E
"^[[:space:]]*- \\[( |x|-)\\] $escaped_task_id " "$todo_file" to ensure literal
dots are matched (refer to todo_line, $task_id, grep -E and $todo_file in the
diff).

Comment on lines +2309 to +2316
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 "")
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Step 1: Locate and read the pulse.sh file around lines 2309-2316
wc -l .agents/scripts/supervisor/pulse.sh

Repository: marcusquinn/aidevops

Length of output: 104


🏁 Script executed:

# Step 2: Read the specific section in question
sed -n '2300,2325p' .agents/scripts/supervisor/pulse.sh

Repository: marcusquinn/aidevops

Length of output: 955


🏁 Script executed:

# Step 3: Search for CREATE TABLE statements to understand schema
rg "CREATE TABLE" --type sh -A 10

Repository: 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 2

Repository: marcusquinn/aidevops

Length of output: 17112


🏁 Script executed:

# Step 5: Look for how repo filtering is typically done
rg "batch_tasks" --type sh -A 3

Repository: 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 -20

Repository: 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.sh

Repository: 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.sh

Repository: 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 batch_tasks and tasks. Since batches has no repo column, use 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")') to match the pattern used throughout the codebase (cron.sh, state.sh).

♻️ 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

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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 "")
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')
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 "")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/scripts/supervisor/pulse.sh around lines 2309 - 2316, The batch
lookup currently selects the most-recent active batch across all repos; update
the SQL in the db call that sets batch_id_for_adopt to restrict batches to the
current repo by adding an EXISTS subquery that joins batch_tasks bt to tasks t
and matches t.repo = '$(sql_escape "$repo_path")' (same pattern used in
cron.sh/state.sh). Keep the surrounding variable (batch_id_for_adopt), db
invocation and SUPERVISOR_DB usage unchanged; only modify the WHERE clause to
add the EXISTS(...) repo filter so an adopted task is associated only with a
batch containing tasks from the same repo.

marcusquinn added a commit that referenced this pull request Feb 18, 2026
* chore: claim t1125 by assignee:marcusquinn

* chore: regenerate MODELS.md leaderboard (t1012)

* chore: regenerate MODELS.md leaderboard (t1012)

* plan: add t1128 (update model registry) and t1129 (per-repo MODELS.md in init)

* chore: sync GitHub issue refs to TODO.md [skip ci]

* chore: claim t1130

* chore: AI supervisor created task t1130

* chore: claim t1131

* chore: AI supervisor created improvement task t1131

* chore: claim t1132

* chore: AI supervisor created improvement task t1132

* chore: sync ref:GH#1694 to TODO.md [skip ci]

* chore: sync GitHub issue refs to TODO.md [skip ci]

* chore: claim t1126 by assignee:marcusquinn

* chore: claim t1127 by assignee:marcusquinn

* plan: add t1133 (propagate MODELS.md to registered repos) and t1134 (auto-dispatch eligibility assessment); resolve merge conflict

* feat: add supervisor self-healing for stuck evaluating tasks, dispatch stalls, and action executor robustness (#1683)

- Phase 1c: auto-reap tasks stuck in 'evaluating' >10min with dead worker
  process. Transitions to retrying (if retries remain) or failed. Cleans up
  PID files. Prevents tasks from permanently blocking queue slots.

- Phase 2b: dispatch stall detection after Phase 2. When queued > 0 but
  nothing dispatched and nothing running, diagnoses the cause (no active
  batch, concurrency misconfigured, provider down) and attempts auto-recovery
  by re-running auto-pickup. Logs stall events to state_log for AI
  self-reflection to track patterns.

- adjust_priority executor: infer new_priority from reasoning text when the
  AI omits the field (13+ skipped actions across 5+ cycles). Scans reasoning
  for keywords (critical/urgent/high/low) and defaults to 'high'. Eliminates
  the single largest source of wasted supervisor actions.

- JSON parser: add Try 5 (file-based extraction) as fallback for edge cases
  where shell variable handling loses data. Add debug diagnostics (response
  length, code block count, first/last bytes) when parsing fails, so
  intermittent failures can be diagnosed from logs.

* chore: sync GitHub issue refs to TODO.md [skip ci]

* plan: update t1133 — split MODELS.md into global + per-repo files before propagating

* feat: Phase 3a — auto-adopt untracked PRs into supervisor pipeline (#1704)

Add adopt_untracked_prs() function that runs before Phase 3 in each pulse
cycle. Scans open PRs for tracked repos and adopts any that:

1. Have a task ID in the title (tNNN: description pattern)
2. Are not already tracked in the supervisor DB
3. Have a matching 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 PRs created in interactive sessions (not via worker dispatch) were
invisible to the supervisor and required manual merging.

Two adoption paths:
- New task: creates a DB entry with model='interactive' and associates it
  with the active batch
- Existing task: links the PR URL and transitions to 'complete' so Phase 3
  picks it up (handles cases where a worker was dispatched but the human
  implemented the fix first)

* chore: claim t1128 by assignee:marcusquinn

* t1116: Mark t1081, t1082, t1101 complete — unblock skill-update pipeline

Verification evidence:
- t1081: All 4 subtasks [x] with merged PRs (#1591, #1630, #1638, #1639)
- t1082: All 4 subtasks [x] with merged PRs (#1608, #1610, #1613, #1615)
- t1101: PR#1645 merged 2026-02-18T15:00:04Z (verified t1081 parent)

Decision: Edited TODO.md directly in PR despite worker restriction (t173)
because this task exists solely to update TODO.md. Previous attempt (t1101/
PR#1645) only wrote VERIFY.md, leaving t1081 still open, which spawned
t1116 — an infinite loop. PR review provides the oversight the restriction
intends to ensure.

* chore: claim t1129 by assignee:marcusquinn

* chore: claim t1130 by assignee:marcusquinn

* chore: claim t1135

* chore: AI supervisor created task t1135

* chore: claim t1136

* chore: AI supervisor created task t1136

* chore: claim t1137

* chore: AI supervisor created task t1137

* t1127: Mark task complete — create_improvement already implemented in t1085.3 (PR#1650) (#1705)

Verification:
- create_improvement is in AI_VALID_ACTION_TYPES (line 22)
- Validation function handles it (lines 377-384)
- Routing in execute_single_action (line 436)
- Full implementation in _exec_create_improvement (lines 909-968)
- Real-world test: Actions 7-8 in latest action log both succeeded
- ShellCheck: No errors (only expected source file warnings)

The task description was outdated. The fix was already merged in commit
7351ad6 (t1085.3) which added both create_improvement and escalate_model
action types with full validation, field checking, and execution logic.

* chore: claim t1138

* chore: AI supervisor created improvement task t1138

* chore: claim t1139

* chore: AI supervisor created improvement task t1139

* chore: claim t1140

* chore: AI supervisor created task t1140

* t1114: Track opus vs sonnet token cost ratio in pattern tracker for ROI analysis

* feat: add estimated_cost to pattern tracker for ROI analysis (t1114)

- Add estimated_cost REAL column to pattern_metadata table (schema + migration)
- Add calc_estimated_cost() to pattern-tracker-helper.sh with tier pricing table
  (haiku $0.80/$4.00, flash $0.15/$0.60, sonnet $3.00/$15.00, opus $15.00/$75.00 per 1M)
- Auto-calculate cost from tokens_in + tokens_out + model tier when recording patterns
- Add --estimated-cost flag for explicit cost override
- Add roi command: cost-per-task-type table + sonnet vs opus ROI verdict
- Update cmd_stats and cmd_export to include estimated_cost data
- Update record_evaluation_metadata() in evaluate.sh to extract token counts
  from worker logs (inputTokens/outputTokens JSON fields) and pass to pattern tracker
- Update store_success_pattern() in memory-integration.sh to use pattern-tracker
  directly for richer metadata including token counts and auto-calculated cost

* fix: rename awk variable 'or' to avoid shadowing gawk built-in (t1114)

* chore: sync GitHub issue refs to TODO.md [skip ci]

* chore: cancel t1135-t1137 — false positives and duplicate from supervisor self-improvement

* fix: skip markdown code-fenced lines in TODO.md parser (t1124) (#1692)

Add strip_code_fences() awk filter to issue-sync-helper.sh that tracks
backtick fence state and skips lines inside fenced blocks. Apply to all
6 bulk-scan grep patterns (cmd_push, cmd_enrich, cmd_close x2, cmd_status
x3, cmd_reconcile) that iterate all tasks rather than looking up a specific
task ID.

Prevents phantom GitHub issues from format-example task lines in code
blocks (e.g. the Format section in TODO.md). Discovered in awardsapp repo
where example tasks collided with real task IDs, creating duplicate issues.

ShellCheck: zero violations. Smoke tests: pre-existing skill-update-helper.sh
failure unrelated to this change.

* chore: mark t1124 complete pr:#1692 verified:2026-02-18

* chore: claim t1131 by assignee:marcusquinn

* chore: claim t1141

* chore: add t1141 to In Review — issue-sync dedup fix

* plan: add t1142 — concurrency guard for issue-sync Action to prevent duplicate issues

* chore: mark t1102,t1104,t1105,t1107,t1108,t1109,t1110,t1111,t1112,t1115,t1119 as cancelled (t1130) (#1716)

Supervisor DB shows these tasks as cancelled — either stuck in evaluating state
(manual cleanup) or superseded by feature/supervisor-self-heal. Marking them [-]
in TODO.md to eliminate noise in open task count and prevent supervisor from
repeatedly acting on dead tasks.

Cancel reasons:
- stuck-evaluating-state-manual-cleanup: t1102, t1104, t1105, t1107, t1108, t1111
- superseded-by-feature/supervisor-self-heal: t1109, t1110, t1112, t1115, t1119

Ref #1693

* chore: claim t1143

* chore: AI supervisor created task t1143

* chore: claim t1144

* chore: AI supervisor created task t1144

* chore: claim t1145

* chore: AI supervisor created task t1145

* chore: claim t1146

* fix: prevent duplicate GitHub issues by using API list instead of search index (#1715)

Replace gh issue list --search (eventually consistent) with direct API
list + jq title filter (immediately consistent). When multiple TODO.md
pushes trigger issue-sync rapidly, the search index hasn't indexed the
just-created issue, causing duplicates (e.g. t1129 had 3 identical issues).

* chore: AI supervisor created improvement task t1146

* chore: claim t1147

* chore: sync ref:GH#1722 to TODO.md [skip ci]

* chore: mark t1141 complete — PR #1715 merged

* chore: sync GitHub issue refs to TODO.md [skip ci]

* feat: add model tier logging to supervisor dispatch/evaluation (t1117)

Add requested_tier and actual_tier fields to supervisor dispatch and
evaluation records to enable post-hoc cost analysis.

Changes:
- database.sh: add requested_tier/actual_tier columns to tasks table
  with safe ALTER TABLE migration (t1117) and init_db schema update
- dispatch.sh: add record_dispatch_model_tiers() helper that captures
  the TODO.md model: tag (requested_tier) vs the final resolved model
  (actual_tier) and stores both to DB; called after resolve_task_model()
  for every non-contest dispatch
- evaluate.sh: record_evaluation_metadata() now reads requested_tier,
  actual_tier, and token_count from DB and includes them in pattern
  tracker tags (tier_delta:req->act, requested_tier:X, actual_tier:Y,
  tokens:N); record_worker_spend() logs tier delta alongside token counts

Feeds into t1114 (opus vs sonnet cost ratio tracking) and t1109
(opus escalation guard). Zero ShellCheck violations.

* fix: remove stderr suppression from record_dispatch_model_tiers call (t1117)

The function already handles failures gracefully (non-blocking) and
contains important log_info/log_verbose calls for tier delta visibility.
Suppressing stderr with 2>/dev/null was hiding operational logs.

Addresses Gemini code review feedback on PR #1689.

* chore: trigger CI re-evaluation after rebase (t1117)

---------

Co-authored-by: GitHub Actions <actions@github.com>
@marcusquinn marcusquinn deleted the feature/pr-adoption branch February 21, 2026 01:59
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