Skip to content

refactor: AI-driven task lifecycle engine replacing hardcoded bash heuristics#2107

Merged
marcusquinn merged 1 commit intomainfrom
refactor/ai-driven-pr-lifecycle
Feb 21, 2026
Merged

refactor: AI-driven task lifecycle engine replacing hardcoded bash heuristics#2107
marcusquinn merged 1 commit intomainfrom
refactor/ai-driven-pr-lifecycle

Conversation

@marcusquinn
Copy link
Owner

@marcusquinn marcusquinn commented Feb 21, 2026

Summary

  • New: .agents/scripts/supervisor/ai-lifecycle.sh — AI-driven lifecycle engine with fast-path deterministic decisions (CLEAN→merge, BEHIND→update_branch, MERGED→deploy) and sonnet AI fallback for ambiguous cases
  • Modified: pulse.sh Phase 3 now calls process_ai_lifecycle() by default; legacy path via SUPERVISOR_AI_LIFECYCLE=false
  • Modified: supervisor-helper.sh sources the new module
  • Modified: planning-detail.md adds status tag vocabulary reference

Problem

6 awards app PRs stuck in blocked state because the supervisor's rebase_sibling_pr() (local git rebase) fails on BEHIND branches that have NO actual conflicts. This blocks 8+ downstream tasks from dispatching.

Solution

Replace hardcoded bash case statements with an AI-decides/bash-executes architecture:

  1. Fast path (90%+ of cases, zero AI cost): deterministic mapping of GitHub merge state to action
  2. AI path (ambiguous cases): focused sonnet call with full state context, ~$0.002/decision
  3. Status tags on TODO.md task lines communicate exact state at every decision point
  4. Uses gh pr update-branch (GitHub API) instead of local git rebase for BEHIND branches

Feature Flag

SUPERVISOR_AI_LIFECYCLE=true (default). Set false to instantly revert to legacy behavior.

Testing

  • ShellCheck: zero violations on all 3 shell files
  • Designed for immediate live test against 6 stuck awards app PRs

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced AI-driven task lifecycle management with real-time status tracking via structured status tags (dispatched, running, evaluating, pr-open, ci-running, merged, deploying, deployed, verified, and more)
    • Added configurable lifecycle engine enabling intelligent task progression instead of hardcoded heuristics
  • Documentation

    • Added documentation describing all lifecycle, action, and problem states for enhanced task observability

…tics

Replace the 800-line cmd_pr_lifecycle case-statement approach with an
intelligence-first design: gather real-world state, decide next action,
execute, update TODO status tags.

Key changes:
- New ai-lifecycle.sh: fast-path deterministic decisions (CLEAN->merge,
  BEHIND->update_branch, MERGED->deploy) handle 90%+ of cases with zero
  AI cost. Ambiguous cases (conflicts, CI failures) get a focused sonnet
  call that picks the obvious next step.
- status: tags on TODO.md lines communicate exact state at every decision
  point (status:ci-running, status:merging, status:behind-main, etc.)
- Phase 3 in pulse.sh now calls process_ai_lifecycle() by default.
  SUPERVISOR_AI_LIFECYCLE=false falls back to legacy behavior.
- Phase 3.5/3.6 (rebase retry, opus escalation) skipped when AI lifecycle
  active — those scenarios are handled by the AI engine directly.

Fixes the awards app stuck-PR problem: 6 PRs with mergeStateStatus=BEHIND
were trapped in rebase-fail->block->reset loops. The fast path now calls
gh pr update-branch (GitHub API, no local git) instead of attempting
complex local rebases.
@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 upgrades the supervisor's ability to manage the lifecycle of pull requests by replacing rigid, hardcoded bash logic with a flexible, AI-driven decision-making system. The primary goal is to resolve common PR blocking issues, such as those caused by failed local git rebases on non-conflicting 'BEHIND' branches, and to provide clearer, real-time status updates for tasks. By leveraging AI for complex scenarios and GitHub API for simpler ones, the system aims to streamline the PR merge and deployment process, reducing manual intervention and improving overall efficiency.

Highlights

  • AI-driven Task Lifecycle Engine: Introduced a new AI-driven lifecycle engine (ai-lifecycle.sh) that replaces hardcoded bash heuristics for managing task PRs. This engine uses fast-path deterministic decisions for common states (e.g., CLEAN -> merge, BEHIND -> update_branch, MERGED -> deploy) and falls back to an AI model (Sonnet) for ambiguous cases.
  • Enhanced PR Branch Management: Implemented gh pr update-branch via the GitHub API for updating PR branches when they are behind main and have no conflicts, providing a more robust alternative to local git rebase which previously caused issues.
  • New Status Tag Vocabulary: Added a comprehensive set of status: tags to planning-detail.md that provide real-time visibility into the supervisor's actions and the current state of each task, updated by the AI lifecycle engine.
  • Integration into Supervisor Pulse: Modified pulse.sh to call the new process_ai_lifecycle() function by default in Phase 3, with a feature flag (SUPERVISOR_AI_LIFECYCLE=false) to revert to the legacy process_post_pr_lifecycle behavior.
  • Legacy Logic Guarded: Wrapped the previous Phase 3.5 (auto-retry blocked merge-conflict tasks) and Phase 3.6 (escalate rebase-blocked PRs to Opus worker) logic in pulse.sh with a conditional check, ensuring they are only active when the AI lifecycle is disabled.
Changelog
  • .agents/reference/planning-detail.md
    • Added a new 'Lifecycle Status Tags' section.
    • Introduced tables detailing 'Lifecycle states', 'Action states', and 'Problem states' with their meanings.
    • Included a description of how the AI lifecycle engine updates these status tags.
  • .agents/scripts/supervisor-helper.sh
    • Added a source command to include the new ai-lifecycle.sh module.
  • .agents/scripts/supervisor/ai-lifecycle.sh
    • Created a new bash script implementing the AI-driven task lifecycle engine.
    • Defined functions for updating and committing task status tags (update_task_status_tag, commit_status_tag_updates).
    • Implemented gather_task_state to collect comprehensive real-world state for a task.
    • Developed decide_next_action to query an AI model (Sonnet) for the next appropriate action.
    • Included execute_lifecycle_action to perform actions such as merge_pr, update_branch, rebase_branch, promote_draft, close_pr, retry_ci, deploy, mark_deployed, dismiss_reviews, and escalate.
    • Introduced fast_path_decision for deterministic, non-AI-driven actions based on clear PR states.
    • Implemented process_task_lifecycle to orchestrate state gathering, decision making (fast-path then AI), and action execution for individual tasks.
    • Provided process_ai_lifecycle as the main entry point to process all eligible tasks, replacing the legacy post-PR lifecycle.
  • .agents/scripts/supervisor/pulse.sh
    • Modified Phase 3 to conditionally call process_ai_lifecycle if SUPERVISOR_AI_LIFECYCLE is true (default), otherwise falling back to process_post_pr_lifecycle.
    • Wrapped the legacy Phase 3.5 (auto-retry blocked merge-conflict tasks) and Phase 3.6 (escalate rebase-blocked PRs to Opus worker) logic within a conditional block, ensuring they are skipped when the AI lifecycle is active.
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.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 21, 2026

Walkthrough

Introduces a comprehensive AI-driven lifecycle engine for task automation, replacing hardcoded heuristics with intelligent decision-making. The system gathers task state, queries AI for optimal actions, executes those actions, and tracks progress via status tags. Controlled via SUPERVISOR_AI_LIFECYCLE environment toggle.

Changes

Cohort / File(s) Summary
Documentation
.agents/reference/planning-detail.md
Documents new Lifecycle Status Tags system with lifecycle states (dispatched, running, merged, deployed), action states (updating-branch, rebasing), and problem states (behind-main, ci-failed, blocked). Explains AI-driven state tracking and SUPERVISOR_AI_LIFECYCLE toggle.
Lifecycle Engine
.agents/scripts/supervisor/ai-lifecycle.sh
New 980-line AI-centric lifecycle engine with core functions: update_task_status_tag(), gather_task_state(), decide_next_action(), execute_lifecycle_action(), process_task_lifecycle(), fast_path_decision(), process_ai_lifecycle(). Implements state persistence, AI-driven decision making with fallback fast-paths, and 11 action types (merge, rebase, deploy, escalate, etc.).
Supervisor Integration
.agents/scripts/supervisor-helper.sh
Sources ai-lifecycle.sh to enable lifecycle-related functionality at runtime.
Phase 3 Lifecycle Routing
.agents/scripts/supervisor/pulse.sh
Replaces Phase 3 post-PR lifecycle with conditional routing: calls process_ai_lifecycle() when SUPERVISOR_AI_LIFECYCLE=true, otherwise falls back to legacy process_post_pr_lifecycle(). Deactivates legacy Phase 3.5/3.6 auto-retry and escalation logic when AI lifecycle is active. Refines escalation lock paths and logging.

Sequence Diagram(s)

sequenceDiagram
    actor Supervisor
    participant TaskDB as Task State DB
    participant GitRepo as Git/Repo
    participant AIEngine as AI Engine
    participant GitHub as GitHub API
    participant Deploy as Deploy System

    Supervisor->>TaskDB: Fetch task metadata
    TaskDB-->>Supervisor: Task data
    
    Supervisor->>GitRepo: Check worktree status
    GitRepo-->>Supervisor: Branch, commits, status
    
    Supervisor->>GitHub: Fetch PR metadata
    GitHub-->>Supervisor: PR state, reviews, CI status
    
    Supervisor->>Supervisor: gather_task_state() → STATE block
    
    Supervisor->>Supervisor: fast_path_decision(state) → action?
    alt Fast Path Match
        Supervisor->>Supervisor: Use deterministic action
    else No Fast Path
        Supervisor->>AIEngine: decide_next_action(STATE)
        AIEngine-->>Supervisor: {action, reason, status_tag}
    end
    
    Supervisor->>TaskDB: update_task_status_tag(status_tag)
    TaskDB-->>Supervisor: ✓
    
    Supervisor->>Supervisor: execute_lifecycle_action(action)
    
    alt Action: merge_pr
        Supervisor->>GitHub: Merge PR
        GitHub-->>GitRepo: Update main branch
    else Action: update_branch
        Supervisor->>GitRepo: Rebase/merge main
    else Action: deploy
        Supervisor->>Deploy: Execute deployment
        Deploy-->>Supervisor: Deployment result
    else Action: escalate
        Supervisor->>Supervisor: Spawn additional AI worker
    end
    
    Supervisor->>GitRepo: Commit status tag updates
    GitRepo-->>Supervisor: ✓
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~60 minutes

The review demands careful analysis of 980 lines of new AI-driven logic with multiple decision paths, state management, and 11 distinct action handlers. Integration into pulse.sh requires understanding conditional routing and fallback semantics. Logic density is high (AI prompting, JSON parsing, Git/GitHub interactions), and heterogeneous changes across three files necessitate separate reasoning for each function's correctness.

Possibly Related PRs

Poem

🤖 ✨ Tasks now think for themselves with AI's gentle guidance,
Lifecycle states dance through repos with structured precision,
No more heuristic chains—let intelligence decide the path,
Status tags bloom like digital flowers marking progress,
From dispatch to deploy, the supervisor's wisdom flows. 🚀

🚥 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 summarizes the main change: introducing an AI-driven task lifecycle engine to replace hardcoded bash heuristics, which is the primary objective reflected across all modified files.
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 (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/ai-driven-pr-lifecycle

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, 34 code smells

�[0;34m[INFO]�[0m Recent monitoring activity:
Sat Feb 21 14:41:51 UTC 2026: Code review monitoring started
Sat Feb 21 14:41:51 UTC 2026: SonarCloud - Bugs: 0, Vulnerabilities: 0, Code Smells: 34

📈 Current Quality Metrics

  • BUGS: 0
  • CODE SMELLS: 34
  • VULNERABILITIES: 0

Generated on: Sat Feb 21 14:41:54 UTC 2026


Generated by AI DevOps Framework Code Review Monitoring

@sonarqubecloud
Copy link

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 significant and well-designed refactoring by replacing hardcoded bash heuristics with a new AI-driven task lifecycle engine. The implementation is clean and the use of a feature flag in pulse.sh is a great practice. My review focuses on the new ai-lifecycle.sh script, where I've identified a few areas for improvement, primarily concerning error handling and script robustness. There are several instances of error suppression using 2>/dev/null which can mask underlying issues and make debugging difficult. Additionally, a couple of commands could cause the script to exit unexpectedly under set -e, and the JSON parsing from the AI response is a bit brittle. Addressing these points will make the new engine even more resilient.

if [[ -z "$trepo" ]]; then
local escaped_id
escaped_id=$(sql_escape "$task_id")
trepo=$(db "$SUPERVISOR_DB" "SELECT repo FROM tasks WHERE id = '$escaped_id';" 2>/dev/null || echo "")

Choose a reason for hiding this comment

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

critical

The use of 2>/dev/null here, and in many other places throughout this script (e.g., with gh, jq, resolve_ai_cli, parse_pr_url), suppresses potentially critical error messages. If the database query fails due to a lock, corruption, or other issue, the error is hidden, and the script proceeds with an empty trepo variable, leading to silent failures that are difficult to debug. This is a violation of the general rule to avoid blanket error suppression.

To improve robustness, please remove 2>/dev/null and allow errors to be logged or handled. The || echo "" construct already prevents the script from exiting if the db command fails.

Suggested change
trepo=$(db "$SUPERVISOR_DB" "SELECT repo FROM tasks WHERE id = '$escaped_id';" 2>/dev/null || echo "")
trepo=$(db "$SUPERVISOR_DB" "SELECT repo FROM tasks WHERE id = '$escaped_id';" || echo "")
References
  1. Avoid using '2>/dev/null' for blanket suppression of command errors in shell scripts to ensure that authentication, syntax, or system issues remain visible for debugging.


# Extract JSON from response (may have preamble/postamble)
local json_block
json_block=$(printf '%s' "$ai_result" | grep -oE '\{[^}]+\}' | head -1)

Choose a reason for hiding this comment

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

high

The regex \{[^}]+\} used to extract the JSON block is brittle and will fail if the AI's response contains a JSON object with nested objects (e.g., {"key": {"nested_key": "value"}}). While the current prompt asks for a flat object, future changes or slight deviations from the AI could break this parsing. For robust JSON parsing, jq should be used. A common pattern is to first extract the potential JSON string (e.g., with grep -oP) and then pipe it to jq for validation and parsing, handling potential failures gracefully.

Suggested change
json_block=$(printf '%s' "$ai_result" | grep -oE '\{[^}]+\}' | head -1)
json_block=$(printf '%s' "$ai_result" | grep -oP '(\{.*\})' | head -1 | jq -c . 2>/dev/null || echo '{}')
References
  1. To reliably wrap the entire content of a shell variable as a single JSON string, use jq -Rn --arg v "$VAR" '$v'. This is more robust than piping the variable to jq -Rs '.'.
  2. When a shell function is expected to return JSON, it should output a valid empty JSON array ([]) or object ({}) on no results, rather than returning a non-zero exit code or empty string, to ensure consumers of the output (like pipes to jq) do not break.


# Find the task line (open checkbox only — completed tasks don't get status updates)
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.

medium

With set -euo pipefail active, this command pipeline will cause the script to exit if grep doesn't find a match, as grep will return a non-zero exit code. The subsequent check on if [[ -z "$line_num" ]] would never be reached. This violates the style guide's recommendation to guard commands like grep that can fail on no-match scenarios.

To prevent the script from exiting unexpectedly, you should guard the pipeline.

Suggested change
line_num=$(grep -nE "^[[:space:]]*- \[ \] ${task_id}( |$)" "$todo_file" | head -1 | cut -d: -f1)
line_num=$(grep -nE "^[[:space:]]*- \[ \] ${task_id}( |$)" "$todo_file" | head -1 | cut -d: -f1) || true
References
  1. In shell scripts with 'set -e' enabled, use '|| true' to prevent the script from exiting when a command like 'jq' fails on an optional lookup. Do not suppress stderr with '2>/dev/null' so that actual syntax or system errors remain visible for debugging.

# CI still running → wait
if [[ "$pr_ci" == *"pending:"* ]]; then
local pending_count
pending_count=$(printf '%s' "$pr_ci" | grep -oE 'pending:[0-9]+' | cut -d: -f2)

Choose a reason for hiding this comment

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

medium

Similar to a previous comment, this grep command is unguarded. If grep does not find a match for pending:[0-9]+, it will exit with a non-zero status, causing the entire script to terminate due to set -euo pipefail. The logic to handle a zero count in the following if statement will not be executed. The pipeline should be guarded to ensure the script continues gracefully when there are no pending checks.

Suggested change
pending_count=$(printf '%s' "$pr_ci" | grep -oE 'pending:[0-9]+' | cut -d: -f2)
pending_count=$(printf '%s' "$pr_ci" | grep -oE 'pending:[0-9]+' | cut -d: -f2) || true
References
  1. In shell scripts with 'set -e' enabled, use '|| true' to prevent the script from exiting when a command like 'jq' fails on an optional lookup. Do not suppress stderr with '2>/dev/null' so that actual syntax or system errors remain visible for debugging.

@marcusquinn marcusquinn merged commit db434e2 into main Feb 21, 2026
18 of 19 checks passed
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: 5

🤖 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/reference/planning-detail.md:
- Line 65: Replace the unhyphenated compound phrase "decision making" with the
hyphenated "decision-making" in the descriptive sentence that explains the AI
lifecycle engine (the sentence mentioning SUPERVISOR_AI_LIFECYCLE and
cmd_pr_lifecycle) so it reads "intelligence-first decision-making" to correctly
form the compound adjective; update that occurrence in planning-detail.md
wherever the phrase appears.

In @.agents/scripts/supervisor/ai-lifecycle.sh:
- Around line 109-121: The sed replacement can break when task text contains |
or &; before calling sed_inplace replace the line, escape the updated_line so
sed special chars (|, /, &) are backslash-escaped. In other words, transform
updated_line into an escaped variable (used by the existing sed_inplace
invocation) and then call sed_inplace with that escaped value; reference the
existing symbols updated_line, sed_inplace, todo_file and line_num so you escape
updated_line (e.g., replace special chars with backslash-prefixed versions) and
then run sed_inplace "${line_num}s|.*|${escaped_updated_line}|" "$todo_file".
- Around line 431-443: Currently run_deploy_for_task is masked by "|| true", so
failures are ignored and the task is marked deployed; change the logic to check
the command exit status and only call update_task_status_tag "$task_id"
"deployed" and cmd_transition "$task_id" "deployed" when run_deploy_for_task
succeeds, otherwise log the error and mark the task blocked (e.g.,
update_task_status_tag "$task_id" "blocked" and cmd_transition "$task_id"
"blocked") and include the deploy failure details from run_deploy_for_task in
the SUPERVISOR_LOG; update the same pattern at both occurrences that currently
use "|| true" and follow the conditional pattern used by deploy.sh's if !
run_deploy_for_task ... to gate transitions.
- Around line 805-835: Extract pr_review from the task state (like the other
fields: pr_review=$(printf '%s' "$task_state" | grep '^PR_REVIEW:' | cut -d' '
-f2)) and add a guard to the CLEAN and UNSTABLE fast-paths so they only emit
merge actions when the review decision indicates approval (e.g. require
pr_review == "APPROVED"); if pr_review is "CHANGES_REQUESTED", "REVIEW_REQUIRED"
or any non-approved value, do not auto-merge and let the normal review/hold
logic handle the PR.
- Around line 460-480: In the update_branch case, remove the forced empty
parameter "-f expected_head_sha=\"\"" from the gh api PUT to
"repos/${pr_repo_slug}/pulls/${pr_number}/update-branch" so the optional
expected_head_sha is omitted (allowing the API to use the PR's current HEAD);
keep the same gh invocation, logging (log_info/log_success/log_warn), and DB
reset (db ... rebase_attempts = 0) behavior unchanged—only stop sending the
empty expected_head_sha field.

| `status:changes-requested` | Human reviewer requested changes |
| `status:blocked:<reason>` | Cannot proceed, reason given |

The AI lifecycle engine (`SUPERVISOR_AI_LIFECYCLE=true`, default) replaces hardcoded bash heuristics with intelligence-first decision making. For each active task, it gathers real-world state (DB, GitHub PR, CI, git), decides the next action, executes it, and updates the status tag. Set `SUPERVISOR_AI_LIFECYCLE=false` to fall back to the legacy `cmd_pr_lifecycle` bash heuristics.
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

Hyphenate the compound adjective.
“Decision making” should be “decision‑making” in this compound phrase.

✍️ Proposed fix
-The AI lifecycle engine (`SUPERVISOR_AI_LIFECYCLE=true`, default) replaces hardcoded bash heuristics with intelligence-first decision making.
+The AI lifecycle engine (`SUPERVISOR_AI_LIFECYCLE=true`, default) replaces hardcoded bash heuristics with intelligence-first decision-making.
📝 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
The AI lifecycle engine (`SUPERVISOR_AI_LIFECYCLE=true`, default) replaces hardcoded bash heuristics with intelligence-first decision making. For each active task, it gathers real-world state (DB, GitHub PR, CI, git), decides the next action, executes it, and updates the status tag. Set `SUPERVISOR_AI_LIFECYCLE=false` to fall back to the legacy `cmd_pr_lifecycle` bash heuristics.
The AI lifecycle engine (`SUPERVISOR_AI_LIFECYCLE=true`, default) replaces hardcoded bash heuristics with intelligence-first decision-making. For each active task, it gathers real-world state (DB, GitHub PR, CI, git), decides the next action, executes it, and updates the status tag. Set `SUPERVISOR_AI_LIFECYCLE=false` to fall back to the legacy `cmd_pr_lifecycle` bash heuristics.
🧰 Tools
🪛 LanguageTool

[grammar] ~65-~65: Use a hyphen to join words.
Context: ...ristics with intelligence-first decision making. For each active task, it gathers...

(QB_NEW_EN_HYPHEN)

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

In @.agents/reference/planning-detail.md at line 65, Replace the unhyphenated
compound phrase "decision making" with the hyphenated "decision-making" in the
descriptive sentence that explains the AI lifecycle engine (the sentence
mentioning SUPERVISOR_AI_LIFECYCLE and cmd_pr_lifecycle) so it reads
"intelligence-first decision-making" to correctly form the compound adjective;
update that occurrence in planning-detail.md wherever the phrase appears.

Comment on lines +109 to +121
local task_line
task_line=$(sed -n "${line_num}p" "$todo_file")

# Remove existing status: tag if present
local updated_line
updated_line=$(printf '%s' "$task_line" | sed -E 's/ status:[^ ]*//')

# Append new status tag
updated_line="${updated_line} status:${new_status}"

# Replace the line in-place
sed_inplace "${line_num}s|.*|${updated_line}|" "$todo_file"

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

Escape TODO line content before sed replacement.
Task descriptions can contain | or &, which will break the replacement and corrupt TODO.md.

🛠️ Proposed fix
  # Append new status tag
  updated_line="${updated_line} status:${new_status}"

  # Replace the line in-place
- sed_inplace "${line_num}s|.*|${updated_line}|" "$todo_file"
+ local escaped_line
+ escaped_line=$(printf '%s' "$updated_line" | sed -e 's/[\\/&|]/\\&/g')
+ sed_inplace "${line_num}s|.*|${escaped_line}|" "$todo_file"
📝 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 task_line
task_line=$(sed -n "${line_num}p" "$todo_file")
# Remove existing status: tag if present
local updated_line
updated_line=$(printf '%s' "$task_line" | sed -E 's/ status:[^ ]*//')
# Append new status tag
updated_line="${updated_line} status:${new_status}"
# Replace the line in-place
sed_inplace "${line_num}s|.*|${updated_line}|" "$todo_file"
# Append new status tag
updated_line="${updated_line} status:${new_status}"
# Replace the line in-place
local escaped_line
escaped_line=$(printf '%s' "$updated_line" | sed -e 's/[\\/&|]/\\&/g')
sed_inplace "${line_num}s|.*|${escaped_line}|" "$todo_file"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/scripts/supervisor/ai-lifecycle.sh around lines 109 - 121, The sed
replacement can break when task text contains | or &; before calling sed_inplace
replace the line, escape the updated_line so sed special chars (|, /, &) are
backslash-escaped. In other words, transform updated_line into an escaped
variable (used by the existing sed_inplace invocation) and then call sed_inplace
with that escaped value; reference the existing symbols updated_line,
sed_inplace, todo_file and line_num so you escape updated_line (e.g., replace
special chars with backslash-prefixed versions) and then run sed_inplace
"${line_num}s|.*|${escaped_updated_line}|" "$todo_file".

Comment on lines +431 to +443
# Post-merge: pull main, rebase siblings, deploy
git -C "$repo_path" pull --rebase origin main 2>>"$SUPERVISOR_LOG" || true
rebase_sibling_prs_after_merge "$task_id" 2>>"$SUPERVISOR_LOG" || true

# Run postflight + deploy
run_postflight_for_task "$task_id" "$repo_path" 2>>"$SUPERVISOR_LOG" || true
update_task_status_tag "$task_id" "deploying" "$repo_path"
cmd_transition "$task_id" "deploying" 2>>"$SUPERVISOR_LOG" || true

run_deploy_for_task "$task_id" "$repo_path" 2>>"$SUPERVISOR_LOG" || true
cmd_transition "$task_id" "deployed" 2>>"$SUPERVISOR_LOG" || true
update_task_status_tag "$task_id" "deployed" "$repo_path"

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:

#!/bin/bash
# First, let's confirm the file exists and inspect the relevant lines
wc -l .agents/scripts/supervisor/ai-lifecycle.sh

Repository: marcusquinn/aidevops

Length of output: 110


🏁 Script executed:

#!/bin/bash
# Read the specific lines mentioned in the review (431-443)
sed -n '431,443p' .agents/scripts/supervisor/ai-lifecycle.sh

Repository: marcusquinn/aidevops

Length of output: 717


🏁 Script executed:

#!/bin/bash
# Also read lines 581-586
sed -n '581,586p' .agents/scripts/supervisor/ai-lifecycle.sh

Repository: marcusquinn/aidevops

Length of output: 395


🏁 Script executed:

#!/bin/bash
# Find run_deploy_for_task definition and its behavior
rg -n "run_deploy_for_task" .agents/scripts/supervisor/ -g '*.sh' -A 5

Repository: marcusquinn/aidevops

Length of output: 2833


🏁 Script executed:

#!/bin/bash
# Check for ShellCheck availability and run it on the file per coding guidelines
if command -v shellcheck &> /dev/null; then
  shellcheck -x .agents/scripts/supervisor/ai-lifecycle.sh || true
else
  echo "ShellCheck not available"
fi

Repository: marcusquinn/aidevops

Length of output: 46


🏁 Script executed:

#!/bin/bash
# Get context around line 440 to see the full function
sed -n '420,450p' .agents/scripts/supervisor/ai-lifecycle.sh

Repository: marcusquinn/aidevops

Length of output: 1378


🏁 Script executed:

#!/bin/bash
# Get context around line 581 to see the full function
sed -n '570,595p' .agents/scripts/supervisor/ai-lifecycle.sh

Repository: marcusquinn/aidevops

Length of output: 1048


🏁 Script executed:

#!/bin/bash
# Inspect the run_deploy_for_task function definition to confirm it can return non-zero
sed -n '40,80p' .agents/scripts/supervisor/deploy.sh

Repository: marcusquinn/aidevops

Length of output: 1518


Gate task deployment status on deploy success.
run_deploy_for_task failures are masked by || true at two locations (lines 440–442 and 581–583), allowing tasks to transition to deployed with update_task_status_tag and update_todo_on_complete executed despite failed releases. Compare with deploy.sh:698 which correctly uses if ! run_deploy_for_task to handle failures. Update both occurrences to verify deploy success before marking deployed, or transition to blocked with error details.

Suggested fix for both locations
- run_deploy_for_task "$task_id" "$repo_path" 2>>"$SUPERVISOR_LOG" || true
- cmd_transition "$task_id" "deployed" 2>>"$SUPERVISOR_LOG" || true
- update_task_status_tag "$task_id" "deployed" "$repo_path"
+ if run_deploy_for_task "$task_id" "$repo_path" 2>>"$SUPERVISOR_LOG"; then
+   cmd_transition "$task_id" "deployed" 2>>"$SUPERVISOR_LOG" || true
+   update_task_status_tag "$task_id" "deployed" "$repo_path"
+ else
+   log_warn "ai-lifecycle: deploy failed for $task_id"
+   update_task_status_tag "$task_id" "blocked:deploy-failed" "$repo_path"
+   cmd_transition "$task_id" "blocked" --error "Deploy failed" 2>>"$SUPERVISOR_LOG" || true
+   return 1
+ fi
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/scripts/supervisor/ai-lifecycle.sh around lines 431 - 443, Currently
run_deploy_for_task is masked by "|| true", so failures are ignored and the task
is marked deployed; change the logic to check the command exit status and only
call update_task_status_tag "$task_id" "deployed" and cmd_transition "$task_id"
"deployed" when run_deploy_for_task succeeds, otherwise log the error and mark
the task blocked (e.g., update_task_status_tag "$task_id" "blocked" and
cmd_transition "$task_id" "blocked") and include the deploy failure details from
run_deploy_for_task in the SUPERVISOR_LOG; update the same pattern at both
occurrences that currently use "|| true" and follow the conditional pattern used
by deploy.sh's if ! run_deploy_for_task ... to gate transitions.

Comment on lines +460 to +480
update_branch)
log_info "ai-lifecycle: updating PR branch for $task_id via GitHub API"
update_task_status_tag "$task_id" "updating-branch" "$repo_path"

if [[ -z "$pr_number" || -z "$pr_repo_slug" ]]; then
log_warn "ai-lifecycle: no PR number/repo for update_branch on $task_id"
return 1
fi

# Use GitHub API to update the branch (no local git needed)
if gh api "repos/${pr_repo_slug}/pulls/${pr_number}/update-branch" \
-X PUT -f expected_head_sha="" 2>>"$SUPERVISOR_LOG"; then
log_success "ai-lifecycle: branch updated for $task_id — CI will re-run"
update_task_status_tag "$task_id" "ci-running" "$repo_path"
# Reset rebase counter since we used a different strategy
db "$SUPERVISOR_DB" "UPDATE tasks SET rebase_attempts = 0 WHERE id = '$escaped_id';" 2>/dev/null || true
return 0
else
log_warn "ai-lifecycle: GitHub API update-branch failed for $task_id — will try rebase next cycle"
update_task_status_tag "$task_id" "behind-main" "$repo_path"
return 1
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

🌐 Web query:

GitHub REST API pull request update-branch expected_head_sha parameter documentation

💡 Result:

In the GitHub REST API, expected_head_sha is documented as an optional JSON body parameter for the “Update a pull request branch” endpoint:

  • Endpoint: PUT /repos/{owner}/{repo}/pulls/{pull_number}/update-branch
  • expected_head_sha meaning: “The expected SHA of the pull request’s HEAD ref” (i.e., the most recent commit on the PR’s head branch). If you provide it, GitHub will only perform the update if the PR’s current head SHA still matches what you sent—otherwise you get 422 Unprocessable Entity.
  • Default behavior: If omitted, it defaults to the SHA of the pull request’s current HEAD ref.
    This is all described in the REST “pulls” docs under Parameters for “Update a pull request branch”. [1]

Source (official docs): GitHub REST API → Pull requests → “Update a pull request branch” (expected_head_sha). [1]

[1] (docs.github.com)

Citations:


🏁 Script executed:

#!/bin/bash
# Check if ShellCheck is available and run it on the script
if command -v shellcheck &> /dev/null; then
  shellcheck -f gcc .agents/scripts/supervisor/ai-lifecycle.sh 2>&1 | head -100
else
  echo "ShellCheck not found, will attempt to install and run"
  # Try to find the script first
  fd "ai-lifecycle.sh" .agents/scripts/
fi

Repository: marcusquinn/aidevops

Length of output: 46


🏁 Script executed:

#!/bin/bash
# Find the script file
fd "ai-lifecycle.sh" --full-path ".agents/scripts"

Repository: marcusquinn/aidevops

Length of output: 106


🏁 Script executed:

#!/bin/bash
# If found, run ShellCheck on it
script_file=".agents/scripts/supervisor/ai-lifecycle.sh"
if [[ -f "$script_file" ]]; then
  wc -l "$script_file"
  echo "---"
  sed -n '460,480p' "$script_file"
else
  echo "Script not found at expected path"
  # List what's in the .agents directory
  find .agents -name "*.sh" -type f 2>/dev/null | head -20
fi

Repository: marcusquinn/aidevops

Length of output: 1120


🏁 Script executed:

#!/bin/bash
# Run ShellCheck on the script (install if needed)
if ! command -v shellcheck &> /dev/null; then
  apt-get update -qq && apt-get install -qq shellcheck > /dev/null 2>&1
fi
shellcheck -f gcc .agents/scripts/supervisor/ai-lifecycle.sh

Repository: marcusquinn/aidevops

Length of output: 46


Omit expected_head_sha parameter to avoid API rejection.
Passing an empty string causes GitHub to return 422 Unprocessable Entity since the parameter is optional and expects either a valid SHA or to be omitted entirely. When omitted, the API correctly defaults to the PR's current HEAD ref, enabling the fast-path update as intended.

Fix
- if gh api "repos/${pr_repo_slug}/pulls/${pr_number}/update-branch" \
-   -X PUT -f expected_head_sha="" 2>>"$SUPERVISOR_LOG"; then
+ if gh api "repos/${pr_repo_slug}/pulls/${pr_number}/update-branch" \
+   -X PUT 2>>"$SUPERVISOR_LOG"; then
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/scripts/supervisor/ai-lifecycle.sh around lines 460 - 480, In the
update_branch case, remove the forced empty parameter "-f
expected_head_sha=\"\"" from the gh api PUT to
"repos/${pr_repo_slug}/pulls/${pr_number}/update-branch" so the optional
expected_head_sha is omitted (allowing the API to use the PR's current HEAD);
keep the same gh invocation, logging (log_info/log_success/log_warn), and DB
reset (db ... rebase_attempts = 0) behavior unchanged—only stop sending the
empty expected_head_sha field.

Comment on lines +805 to +835
# Extract key fields from state snapshot
local db_status pr_state pr_merge_state pr_ci pr_url
db_status=$(printf '%s' "$task_state" | grep '^DB_STATUS:' | cut -d' ' -f2)
pr_state=$(printf '%s' "$task_state" | grep '^PR_STATE:' | cut -d' ' -f2)
pr_merge_state=$(printf '%s' "$task_state" | grep '^PR_MERGE_STATE:' | cut -d' ' -f2)
pr_ci=$(printf '%s' "$task_state" | grep '^PR_CI:' | cut -d' ' -f2-)
pr_url=$(printf '%s' "$task_state" | grep '^PR_URL:' | cut -d' ' -f2)

# PR already merged → deploy
if [[ "$pr_state" == "MERGED" ]]; then
echo '{"action":"deploy","reason":"PR already merged","status_tag":"deploying"}'
return 0
fi

# No PR and task is complete → mark deployed
if [[ "$db_status" == "complete" && ("$pr_url" == "none" || "$pr_url" == "no_pr" || "$pr_url" == "task_only" || "$pr_url" == "verified_complete") ]]; then
echo '{"action":"mark_deployed","reason":"No PR, task complete","status_tag":"deployed"}'
return 0
fi

# PR is CLEAN (all checks passed, no conflicts) → merge
if [[ "$pr_merge_state" == "CLEAN" ]]; then
echo '{"action":"merge_pr","reason":"CI passed, PR clean","status_tag":"merging"}'
return 0
fi

# PR is UNSTABLE (non-required checks failed) → merge (safe)
if [[ "$pr_merge_state" == "UNSTABLE" ]]; then
echo '{"action":"merge_pr","reason":"Required checks passed (non-required failed)","status_tag":"merging"}'
return 0
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

🧩 Analysis chain

🏁 Script executed:

# First, let's see the full context of the function containing these lines
head -n 850 .agents/scripts/supervisor/ai-lifecycle.sh | tail -n 100

Repository: marcusquinn/aidevops

Length of output: 3601


🏁 Script executed:

# Check if PR_REVIEW field is used elsewhere in the file
rg 'PR_REVIEW' .agents/scripts/supervisor/ai-lifecycle.sh -B 2 -A 2

Repository: marcusquinn/aidevops

Length of output: 228


🏁 Script executed:

# Let's see the function signature and understand its purpose
sed -n '700,810p' .agents/scripts/supervisor/ai-lifecycle.sh

Repository: marcusquinn/aidevops

Length of output: 3951


🏁 Script executed:

# Check for any ShellCheck issues in this file as per coding guidelines
command -v shellcheck >/dev/null 2>&1 && shellcheck .agents/scripts/supervisor/ai-lifecycle.sh -x || echo "ShellCheck not available, searching for syntax issues..."

Repository: marcusquinn/aidevops

Length of output: 46


🏁 Script executed:

# Search for where task_state is populated to understand what fields it contains
rg 'task_state=' .agents/scripts/supervisor/ai-lifecycle.sh -B 1 -A 1 | head -n 30

Repository: marcusquinn/aidevops

Length of output: 312


🏁 Script executed:

# Find the gather_task_state function to confirm PR_REVIEW is populated
rg 'gather_task_state' .agents/scripts/supervisor/ai-lifecycle.sh -A 50 | head -n 80

Repository: marcusquinn/aidevops

Length of output: 2845


🏁 Script executed:

# Check if there are any guards elsewhere that catch CHANGES_REQUESTED or REVIEW_REQUIRED
rg 'CHANGES_REQUESTED|REVIEW_REQUIRED' .agents/scripts/supervisor/ai-lifecycle.sh -B 2 -A 2

Repository: marcusquinn/aidevops

Length of output: 46


🏁 Script executed:

# Validate the suggested shell syntax for the guard conditions
# Let's check if there are similar guard patterns in the file
rg '\[[ ]*\$' .agents/scripts/supervisor/ai-lifecycle.sh | head -n 10

Repository: marcusquinn/aidevops

Length of output: 46


🏁 Script executed:

# Get the complete gather_task_state function to see how pr_review_decision is set
rg 'gather_task_state' .agents/scripts/supervisor/ai-lifecycle.sh -A 100 | grep -A 100 'pr_review_decision'

Repository: marcusquinn/aidevops

Length of output: 5496


🏁 Script executed:

# Check the complete state output formatting
rg 'PR_REVIEW:' .agents/scripts/supervisor/ai-lifecycle.sh -B 5 -A 2

Repository: marcusquinn/aidevops

Length of output: 317


🏁 Script executed:

# Let's verify the shell syntax for the guard by examining similar conditional patterns
sed -n '820,835p' .agents/scripts/supervisor/ai-lifecycle.sh

Repository: marcusquinn/aidevops

Length of output: 765


Fast-path merge must check review decisions before merging.

The CLEAN and UNSTABLE merge paths bypass PR_REVIEW checks entirely. Since mergeStateStatus reflects only CI and conflict state (not reviews), a PR can be CLEAN while having CHANGES_REQUESTED or REVIEW_REQUIRED status. This allows automated merges to override explicit human review feedback.

Extract pr_review from the task state snapshot and guard both merge paths:

Add review guard before fast-path merge
- local db_status pr_state pr_merge_state pr_ci pr_url
+ local db_status pr_state pr_merge_state pr_ci pr_url pr_review
  db_status=$(printf '%s' "$task_state" | grep '^DB_STATUS:' | cut -d' ' -f2)
  pr_state=$(printf '%s' "$task_state" | grep '^PR_STATE:' | cut -d' ' -f2)
  pr_merge_state=$(printf '%s' "$task_state" | grep '^PR_MERGE_STATE:' | cut -d' ' -f2)
  pr_ci=$(printf '%s' "$task_state" | grep '^PR_CI:' | cut -d' ' -f2-)
  pr_url=$(printf '%s' "$task_state" | grep '^PR_URL:' | cut -d' ' -f2)
+ pr_review=$(printf '%s' "$task_state" | grep '^PR_REVIEW:' | cut -d' ' -f2)

  # PR is CLEAN (all checks passed, no conflicts) → merge
  if [[ "$pr_merge_state" == "CLEAN" ]]; then
+   if [[ "$pr_review" == "CHANGES_REQUESTED" || "$pr_review" == "REVIEW_REQUIRED" ]]; then
+     return 1
+   fi
    echo '{"action":"merge_pr","reason":"CI passed, PR clean","status_tag":"merging"}'
    return 0
  fi

  # PR is UNSTABLE (non-required checks failed) → merge (safe)
  if [[ "$pr_merge_state" == "UNSTABLE" ]]; then
+   if [[ "$pr_review" == "CHANGES_REQUESTED" || "$pr_review" == "REVIEW_REQUIRED" ]]; then
+     return 1
+   fi
    echo '{"action":"merge_pr","reason":"Required checks passed (non-required failed)","status_tag":"merging"}'
    return 0
  fi
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/scripts/supervisor/ai-lifecycle.sh around lines 805 - 835, Extract
pr_review from the task state (like the other fields: pr_review=$(printf '%s'
"$task_state" | grep '^PR_REVIEW:' | cut -d' ' -f2)) and add a guard to the
CLEAN and UNSTABLE fast-paths so they only emit merge actions when the review
decision indicates approval (e.g. require pr_review == "APPROVED"); if pr_review
is "CHANGES_REQUESTED", "REVIEW_REQUIRED" or any non-approved value, do not
auto-merge and let the normal review/hold logic handle the PR.

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