diff --git a/scripts/verify-pi-workflows.sh b/scripts/verify-pi-workflows.sh new file mode 100755 index 0000000..22d565f --- /dev/null +++ b/scripts/verify-pi-workflows.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +set -euo pipefail + +WORKFLOW_FILES=( + "skills/create-plan/pi/SKILL.md" + "skills/do-task/pi/SKILL.md" + "skills/implement-plan/pi/SKILL.md" +) + +# These terms cover the Codex-only constructs explicitly called out by the plan. +# Expand this deny-list if later pi workflow ports introduce new harness-specific tokens. + +for file in "${WORKFLOW_FILES[@]}"; do + test -f "$file" + grep -q 'docs/PI-SUPERPOWERS.md' "$file" +done + +test -f skills/create-plan/pi/templates/continuation-runbook.md +test -f skills/create-plan/pi/templates/milestone-plan.md +test -f skills/create-plan/pi/templates/story-tracker.md +test -f skills/do-task/pi/templates/task-plan.md +test -x skills/reviewer-runtime/pi/run-review.sh +test -x skills/reviewer-runtime/pi/notify-telegram.sh + +! rg -n 'update_plan|plan mode|sub-agent|subagents' "${WORKFLOW_FILES[@]}" + +echo "pi workflow skill docs verified" diff --git a/skills/create-plan/pi/SKILL.md b/skills/create-plan/pi/SKILL.md new file mode 100644 index 0000000..b3212c0 --- /dev/null +++ b/skills/create-plan/pi/SKILL.md @@ -0,0 +1,206 @@ +--- +name: create-plan +description: Use when a user asks to create or maintain a structured implementation plan in pi, including milestones, bite-sized stories, and resumable local planning artifacts under ai_plan. +--- + +# Create Plan (Pi) + +Create and maintain a local plan workspace under `ai_plan/` at project root. + +## Shared Setup + +Before using this skill, read [docs/PI-SUPERPOWERS.md](../../../docs/PI-SUPERPOWERS.md). The workflow depends on: + +- Obra Superpowers skills being visible to pi +- the pi reviewer-runtime helper being installed in a supported location + +## Prerequisite Check (MANDATORY) + +Required: + +- `pi --version` +- Superpowers `brainstorming` +- Superpowers `writing-plans` +- pi reviewer runtime helper: + - `.pi/skills/reviewer-runtime/pi/run-review.sh`, or + - `~/.pi/agent/skills/reviewer-runtime/pi/run-review.sh` + +Quick checks for common installs: + +```bash +pi --version +test -f ~/.agents/skills/superpowers/brainstorming/SKILL.md || test -f ~/.pi/agent/skills/superpowers/brainstorming/SKILL.md +test -f ~/.agents/skills/superpowers/writing-plans/SKILL.md || test -f ~/.pi/agent/skills/superpowers/writing-plans/SKILL.md +test -x .pi/skills/reviewer-runtime/pi/run-review.sh || test -x ~/.pi/agent/skills/reviewer-runtime/pi/run-review.sh +``` + +If you use a settings-defined skill path instead of the common locations above, verify it matches [docs/PI-SUPERPOWERS.md](../../../docs/PI-SUPERPOWERS.md) before continuing. + +If any dependency is missing, stop and return: + +`Missing dependency: pi planning requires Superpowers brainstorming/writing-plans skills plus the pi reviewer runtime documented in docs/PI-SUPERPOWERS.md.` + +## Required Workflow Rules + +- Load the relevant workflow skill before entering its phase. If pi did not auto-load it, use `/skill:brainstorming` or `/skill:writing-plans`. +- Announce skill usage explicitly: + - `I've read the [Skill Name] skill and I'm using it to [purpose].` +- Track checklist-style progress inside the plan artifacts that this skill generates. +- Do not use deprecated wrapper CLIs. + +## Process + +### Phase 1: Analyze + +- Explore the codebase and existing patterns. +- Review any current docs, scripts, or variant layouts that affect the plan. + +### Phase 2: Gather Requirements + +- Ask questions one at a time until the scope is clear. +- Confirm constraints, success criteria, dependencies, and what is out of scope. + +### Phase 3: Configure Reviewer + +If the user already specified a reviewer CLI and model, use those values. Otherwise ask: + +1. Which CLI should review the plan? + - `codex` + - `claude` + - `cursor` + - `opencode` + - `skip` +2. Which model? +3. Max review rounds? Default: `10` + +Store `REVIEWER_CLI`, `REVIEWER_MODEL`, and `MAX_ROUNDS` for the review loop. + +### Phase 4: Design + +- Load `brainstorming`. +- Present 2-3 approaches and recommend one. +- Resolve open design questions before the milestone breakdown. + +### Phase 5: Plan + +- Load `writing-plans`. +- Break the work into milestones and bite-sized stories. +- Story IDs should use the `S-101`, `S-102` style. + +### Phase 6: Iterative Plan Review + +Skip this phase if `REVIEWER_CLI=skip`. + +#### Step 1: Generate Session ID + +```bash +REVIEW_ID=$(uuidgen | tr '[:upper:]' '[:lower:]' | head -c 8) +``` + +Use these temp artifacts: + +- `/tmp/plan-${REVIEW_ID}.md` +- `/tmp/plan-review-${REVIEW_ID}.md` +- `/tmp/plan-review-${REVIEW_ID}.json` +- `/tmp/plan-review-${REVIEW_ID}.stderr` +- `/tmp/plan-review-${REVIEW_ID}.status` +- `/tmp/plan-review-${REVIEW_ID}.runner.out` +- `/tmp/plan-review-${REVIEW_ID}.sh` + +Resolve the pi reviewer runtime helper in this order: + +```bash +REVIEWER_RUNTIME="" +for candidate in ".pi/skills/reviewer-runtime/pi/run-review.sh" "$HOME/.pi/agent/skills/reviewer-runtime/pi/run-review.sh"; do + if [ -x "$candidate" ]; then + REVIEWER_RUNTIME="$candidate" + break + fi +done +``` + +#### Step 2: Write The Plan Payload + +Write the full plan to `/tmp/plan-${REVIEW_ID}.md`. + +Reviewer responses must use this structure: + +```text +## Summary +... + +## Findings +### P0 +- ... +### P1 +- ... +### P2 +- ... +### P3 +- ... + +## Verdict +VERDICT: APPROVED +``` + +Rules: + +- Order findings from `P0` to `P3` +- Use `- None.` when a severity has no findings +- `VERDICT: APPROVED` is valid only when no `P0`, `P1`, or `P2` findings remain + +#### Step 3: Submit To Reviewer + +Build a bash command script in `/tmp/plan-review-${REVIEW_ID}.sh` and execute it through the shared helper when present: + +```bash +"$REVIEWER_RUNTIME" \ + --command-file /tmp/plan-review-${REVIEW_ID}.sh \ + --stdout-file /tmp/plan-review-${REVIEW_ID}.runner.out \ + --stderr-file /tmp/plan-review-${REVIEW_ID}.stderr \ + --status-file /tmp/plan-review-${REVIEW_ID}.status +``` + +Fallback to direct execution only if the helper is missing. + +#### Step 4: Wait And Parse Verdict + +- Keep waiting while fresh `state=in-progress note="In progress N"` heartbeats continue to appear. +- Treat `P0`, `P1`, or `P2` as must-fix findings. +- `P3` findings are non-blocking, but fix them when cheap and safe. + +#### Step 5: Revise And Re-Submit + +- Address findings in priority order. +- Rebuild the plan payload. +- Re-submit until approved or `MAX_ROUNDS` is reached. + +### Phase 7: Generate Plan Files + +Once the plan is approved: + +1. Ensure `/ai_plan/` exists in `.gitignore` +2. Create `ai_plan/YYYY-MM-DD-/` +3. Write: + - `original-plan.md` + - `final-transcript.md` + - `milestone-plan.md` + - `story-tracker.md` + - `continuation-runbook.md` +4. Use the template files from this skill's `templates/` directory + +### Phase 8: Telegram Completion Notification + +Resolve the notification helper in this order: + +```bash +TELEGRAM_NOTIFY_RUNTIME="" +for candidate in ".pi/skills/reviewer-runtime/pi/notify-telegram.sh" "$HOME/.pi/agent/skills/reviewer-runtime/pi/notify-telegram.sh"; do + if [ -x "$candidate" ]; then + TELEGRAM_NOTIFY_RUNTIME="$candidate" + break + fi +done +``` + +If the helper exists and both `TELEGRAM_BOT_TOKEN` and `TELEGRAM_CHAT_ID` are configured, send a short completion summary. If not, state that no Telegram completion notification was sent. diff --git a/skills/create-plan/pi/templates/continuation-runbook.md b/skills/create-plan/pi/templates/continuation-runbook.md new file mode 100644 index 0000000..78f1b70 --- /dev/null +++ b/skills/create-plan/pi/templates/continuation-runbook.md @@ -0,0 +1,135 @@ +# Continuation Runbook: [Plan Title] + +## Reference Files (START HERE) + +Upon resumption, these files in this folder are the ONLY source of truth: + +| File | Purpose | When to Use | +|------|---------|-------------| +| `continuation-runbook.md` | Full context reproduction + execution workflow | Read FIRST | +| `story-tracker.md` | Current progress and status | Check/update BEFORE and AFTER every story | +| `milestone-plan.md` | Complete plan with specifications | Reference implementation details | +| `original-plan.md` | Original approved plan | Reference original intent | +| `final-transcript.md` | Final planning transcript | Reference reasoning/context | + +Do NOT reference planner-private files during implementation. + +## Skill Workflow Guardrails + +- Load relevant skills before action. If pi did not auto-load them, use `/skill:`. +- Announce which skill is being used and why. +- If a checklist-driven workflow applies, keep its state current in the plan artifacts. +- Do not use deprecated wrapper CLIs. + +--- + +## Quick Resume Instructions + +1. Read this runbook completely. +2. Check `story-tracker.md`. +3. Find next `pending` story and mark as `in-dev` before starting. +4. Implement the story. +5. Update tracker immediately after each change. + +--- + +## Mandatory Execution Workflow + +Work from this folder (`ai_plan/YYYY-MM-DD-/`) and always follow this order: + +1. Read `continuation-runbook.md` first. +2. Execute stories milestone by milestone. +3. After completing a milestone: + - Run lint/typecheck/tests, prioritizing changed files for speed. + - Commit locally (**DO NOT PUSH**). + - Stop and ask user for feedback. +4. If feedback is provided: + - Apply feedback changes. + - Re-run checks for changed files. + - Commit locally again. + - Ask for milestone approval. +5. Only move to next milestone after explicit approval. +6. After all milestones are completed and approved: + - Ask permission to push. + - If approved, push. + - Mark plan status as `completed`. + +--- + +## Git Note + +`ai_plan/` is intentionally local and must stay gitignored. Do not treat inability to commit plan-file updates inside `ai_plan/` as an error. + +--- + +## Full Context Reproduction + +### Project Overview +[What this project/feature is about] + +### User Requirements +[All gathered requirements] + +### Scope +[In scope / out of scope] + +### Dependencies +[External dependencies, prerequisites, related systems] + +--- + +## Key Specifications + +### Type Definitions +```typescript +// Copy-paste ready type definitions +``` + +### Enums & Constants +```typescript +// All enums/constants needed +``` + +### API Endpoints +```typescript +// Request/response shapes +``` + +--- + +## Critical Design Decisions + +| Decision | Chosen Approach | Alternatives Rejected | Rationale | +|----------|-----------------|----------------------|-----------| +| [Topic] | [What we chose] | [Other options] | [Why] | + +--- + +## Verification Commands + +### Lint (changed files first) +```bash +# example: pnpm eslint +``` + +### Typecheck +```bash +# example: pnpm tsc --noEmit +``` + +### Tests (target changed scope first) +```bash +# example: pnpm test -- +``` + +--- + +## File Quick Reference + +| File | Purpose | +|------|---------| +| `original-plan.md` | Original approved plan | +| `final-transcript.md` | Final planning transcript | +| `milestone-plan.md` | Full specification | +| `story-tracker.md` | Current progress tracker | +| `continuation-runbook.md` | This runbook | diff --git a/skills/create-plan/pi/templates/milestone-plan.md b/skills/create-plan/pi/templates/milestone-plan.md new file mode 100644 index 0000000..9c9b7e9 --- /dev/null +++ b/skills/create-plan/pi/templates/milestone-plan.md @@ -0,0 +1,101 @@ +# [Plan Title] + +## Overview +- **Goal:** [One sentence describing the end state] +- **Created:** YYYY-MM-DD +- **Status:** In Progress | Complete + +## Context + +### Requirements +[Gathered requirements from user questions] + +### Constraints +[Technical, business, or timeline constraints] + +### Success Criteria +[How we know this is complete] + +## Architecture + +### Design Decisions +[Key architectural choices and rationale] + +### Component Relationships +[How pieces fit together] + +### Data Flow +[How data moves through the system] + +## Milestones + +### M1: [Name] +**Description:** [What this milestone achieves] + +**Acceptance Criteria:** +- [ ] [Criterion 1] +- [ ] [Criterion 2] + +**Stories:** S-101, S-102, S-103... + +**Milestone Completion Rule (MANDATORY):** +- Run lint/typecheck/tests for changed files. +- Commit locally (DO NOT push). +- Stop and ask user for feedback. +- Apply feedback, re-check changed files, commit again. +- Move to next milestone only after user approval. + +--- + +### M2: [Name] +**Description:** [What this milestone achieves] + +**Acceptance Criteria:** +- [ ] [Criterion 1] +- [ ] [Criterion 2] + +**Stories:** S-201, S-202, S-203... + +**Milestone Completion Rule (MANDATORY):** +- Run lint/typecheck/tests for changed files. +- Commit locally (DO NOT push). +- Stop and ask user for feedback. +- Apply feedback, re-check changed files, commit again. +- Move to next milestone only after user approval. + +--- + +## Technical Specifications + +### Types & Interfaces +```typescript +// Key type definitions +``` + +### API Contracts +```typescript +// Endpoint signatures, request/response shapes +``` + +### Constants & Enums +```typescript +// Shared constants +``` + +## Files Inventory + +| File | Purpose | Milestone | +|------|---------|-----------| +| `path/to/file.ts` | [What it does] | M1 | +| `path/to/other.ts` | [What it does] | M2 | + +--- + +## Related Plan Files + +This file is part of the plan folder under `ai_plan/`: +- `original-plan.md` - Original approved plan (reference for original intent) +- `final-transcript.md` - Final planning transcript (reference for rationale/context) +- `milestone-plan.md` - This file (full specification) +- `story-tracker.md` - Status tracking (must be kept up to date) +- `continuation-runbook.md` - Resume/execution context (read first) diff --git a/skills/create-plan/pi/templates/story-tracker.md b/skills/create-plan/pi/templates/story-tracker.md new file mode 100644 index 0000000..a346cc8 --- /dev/null +++ b/skills/create-plan/pi/templates/story-tracker.md @@ -0,0 +1,66 @@ +# Story Tracker: [Plan Title] + +## Progress Summary +- **Current Milestone:** M1 +- **Stories Complete:** 0/N +- **Milestones Approved:** 0/M +- **Last Updated:** YYYY-MM-DD + +--- + +## Milestones + +### M1: [Name] + +| Story | Description | Status | Notes | +|-------|-------------|--------|-------| +| S-101 | [Brief description] | pending | | +| S-102 | [Brief description] | pending | | +| S-103 | [Brief description] | pending | | + +**Approval Status:** pending + +--- + +### M2: [Name] + +| Story | Description | Status | Notes | +|-------|-------------|--------|-------| +| S-201 | [Brief description] | pending | | +| S-202 | [Brief description] | pending | | +| S-203 | [Brief description] | pending | | + +**Approval Status:** pending + +--- + +## Status Legend + +| Status | Meaning | +|--------|---------| +| `pending` | Not started | +| `in-dev` | Currently being worked on | +| `completed` | Done - include commit hash in Notes | +| `deferred` | Postponed - include reason in Notes | + +## Update Instructions (MANDATORY) + +Before starting any story: +1. Mark story as `in-dev` +2. Update "Last Updated" + +After completing any story: +1. Mark story as `completed` +2. Add local commit hash to Notes +3. Update "Stories Complete" and "Last Updated" + +At milestone boundary: +1. Run lint/typecheck/tests for changed files +2. Commit (no push) +3. Request feedback +4. Apply feedback, re-check changed files, commit again +5. Mark milestone **Approval Status: approved** only after user confirms +6. Continue only after approval + +After all milestones approved: +- Ask permission to push and then mark plan completed. diff --git a/skills/do-task/pi/SKILL.md b/skills/do-task/pi/SKILL.md new file mode 100644 index 0000000..28c0126 --- /dev/null +++ b/skills/do-task/pi/SKILL.md @@ -0,0 +1,183 @@ +--- +name: do-task +description: Execute a single user-supplied prompt end-to-end in pi with plan review, implementation review, verification, and one persistent task-plan artifact. +--- + +# Do Task (Pi) + +Execute an ad-hoc user prompt end-to-end: parse, clarify, plan, implement, verify, review, commit, and optionally push. + +This variant uses one persistent `task-plan.md` under `ai_plan/` and defaults to the current branch unless the prompt explicitly opts into a worktree workflow. + +## Shared Setup + +Before using this skill, read [docs/PI-SUPERPOWERS.md](../../../docs/PI-SUPERPOWERS.md). This workflow depends on: + +- Superpowers skills being visible to pi +- the pi reviewer-runtime helper being installed in a supported location + +## Prerequisite Check (MANDATORY) + +Required: + +- `pi --version` +- Superpowers `brainstorming` +- Superpowers `test-driven-development` +- Superpowers `verification-before-completion` +- Superpowers `finishing-a-development-branch` +- Superpowers `using-git-worktrees` when the prompt opts into a worktree +- pi reviewer runtime helper +- pi Telegram notifier helper + +Quick checks for common installs: + +```bash +pi --version +test -f ~/.agents/skills/superpowers/brainstorming/SKILL.md || test -f ~/.pi/agent/skills/superpowers/brainstorming/SKILL.md +test -f ~/.agents/skills/superpowers/test-driven-development/SKILL.md || test -f ~/.pi/agent/skills/superpowers/test-driven-development/SKILL.md +test -f ~/.agents/skills/superpowers/verification-before-completion/SKILL.md || test -f ~/.pi/agent/skills/superpowers/verification-before-completion/SKILL.md +test -f ~/.agents/skills/superpowers/finishing-a-development-branch/SKILL.md || test -f ~/.pi/agent/skills/superpowers/finishing-a-development-branch/SKILL.md +test -x .pi/skills/reviewer-runtime/pi/run-review.sh || test -x ~/.pi/agent/skills/reviewer-runtime/pi/run-review.sh +test -x .pi/skills/reviewer-runtime/pi/notify-telegram.sh || test -x ~/.pi/agent/skills/reviewer-runtime/pi/notify-telegram.sh +``` + +If you use settings-defined paths instead of the common locations above, confirm they match [docs/PI-SUPERPOWERS.md](../../../docs/PI-SUPERPOWERS.md) before continuing. + +If any required dependency is missing, stop immediately and return: + +`Missing dependency: pi do-task requires the workflow skills and pi reviewer-runtime setup documented in docs/PI-SUPERPOWERS.md.` + +## Required Workflow Rules + +- Load the relevant workflow skill before entering its phase. If pi did not auto-load it, use `/skill:`. +- Announce skill usage explicitly: + - `I've read the [Skill Name] skill and I'm using it to [purpose].` +- Keep the `task-plan.md` artifact current as work progresses. +- Do not use deprecated wrapper CLIs. + +## Trigger Detection + +Always use this skill for: + +- `/do-task` +- `do this task` +- `do task ...` +- `execute this task` +- `make it so` +- `just do ...` when another skill is not a better fit + +Use current-branch execution by default. Only switch to a worktree when the prompt explicitly asks for one. + +## Process + +### Phase 1: Preflight + +1. Verify the repo: `git rev-parse --is-inside-work-tree` +2. Ensure `/ai_plan/` exists in `.gitignore` +3. Confirm the required workflow skills are available to pi +4. Announce each workflow skill before using it + +### Phase 2: Parse Prompt And Clarify + +1. Capture the user's prompt verbatim +2. Detect whether the prompt is concrete enough to proceed without questions +3. If needed, ask 1-3 short questions one at a time +4. Load `brainstorming` for behavior-changing work unless the task is pure documentation or pure comment/whitespace/rename work + +### Phase 3: Configure Reviewer + +If the user already specified reviewer settings, use them. Otherwise ask: + +1. Reviewer CLI: `codex`, `claude`, `cursor`, `opencode`, or `skip` +2. Reviewer model +3. Max rounds, default `10` + +Store `REVIEWER_CLI`, `REVIEWER_MODEL`, and `MAX_ROUNDS`. + +### Phase 4: Initialize `task-plan.md` + +1. Compute `ai_plan/YYYY-MM-DD-/` +2. Resume if an existing plan folder is active, otherwise create a new one +3. Write `task-plan.md` from this skill's `templates/task-plan.md` +4. Fill `Metadata`, `Prompt`, `Interpretation`, `Assumptions`, `Files`, `Approach`, `TDD Approach`, `Acceptance Criteria`, `Verification`, and `Rollback` +5. Set `Status: draft` + +If the prompt explicitly opts into a worktree, load `using-git-worktrees` before implementation. Otherwise remain on the current branch. + +### Phase 5: Plan Review Loop + +Skip this phase if `REVIEWER_CLI=skip`. + +1. Write a reviewer payload from `task-plan.md` +2. Strip the runtime-only sections before sending it out +3. Run the reviewer through the pi reviewer-runtime helper when available +4. Fix `P0`, `P1`, and `P2` findings before proceeding +5. Keep `P3` findings for optional cleanup +6. Set `Status: plan-approved` when the reviewer approves + +The reviewer response format must be: + +```text +## Summary +... + +## Findings +### P0 +- ... +### P1 +- ... +### P2 +- ... +### P3 +- ... + +## Verdict +VERDICT: APPROVED +``` + +### Phase 6: Execute + +1. Set `Status: implementation-in-progress` +2. Load `test-driven-development` for every behavior-changing edit unless `task-plan.md` explicitly records an allowed skip +3. Update `task-plan.md` as acceptance criteria are completed +4. Do not commit yet + +### Phase 7: Verification Gate + +1. Load `verification-before-completion` +2. Run the commands listed in `task-plan.md` +3. Fix failures and re-run verification until green +4. If verification stalls repeatedly, stop and surface the blocker + +### Phase 8: Implementation Review Loop + +Skip this phase if `REVIEWER_CLI=skip`. + +1. Build a review payload from the approved plan, current diff, and verification output +2. Run the reviewer through the pi reviewer-runtime helper +3. Address `P0`, `P1`, and `P2` findings before approval +4. Fix cheap `P3` findings when safe +5. Set `Status: implementation-approved` when approved + +### Phase 9: Commit And Push Decision + +1. Load `finishing-a-development-branch` +2. Stage only the intended files +3. Create one commit for the task +4. Ask whether to push or keep the work local + +### Phase 10: Telegram Completion Notification + +Resolve the helper in this order: + +```bash +TELEGRAM_NOTIFY_RUNTIME="" +for candidate in ".pi/skills/reviewer-runtime/pi/notify-telegram.sh" "$HOME/.pi/agent/skills/reviewer-runtime/pi/notify-telegram.sh"; do + if [ -x "$candidate" ]; then + TELEGRAM_NOTIFY_RUNTIME="$candidate" + break + fi +done +``` + +If the helper exists and both `TELEGRAM_BOT_TOKEN` and `TELEGRAM_CHAT_ID` are configured, send a short completion summary. Otherwise state that no Telegram completion notification was sent. diff --git a/skills/do-task/pi/templates/task-plan.md b/skills/do-task/pi/templates/task-plan.md new file mode 100644 index 0000000..c1fd273 --- /dev/null +++ b/skills/do-task/pi/templates/task-plan.md @@ -0,0 +1,128 @@ +# Task Plan: [Short Title] + +> **Variant guardrail (pi):** Required workflow skills (`brainstorming`, `test-driven-development`, `verification-before-completion`, `finishing-a-development-branch`, `using-git-worktrees`) must be available to pi as documented in `docs/PI-SUPERPOWERS.md`. Load the relevant workflow skill before entering its matching phase. + +## Metadata + +| Field | Value | +|-------|-------| +| Created | YYYY-MM-DD | +| Slug | YYYY-MM-DD- | +| Runtime | pi | +| Reviewer CLI | codex \| claude \| cursor \| opencode | +| Reviewer Model | | +| MAX_ROUNDS | 10 | +| Branch Strategy | current-branch \| worktree | +| Branch Name | | +| Worktree Path | | +| Status | draft | + +### Status Enum (authoritative) + +| Value | Meaning | +|-------|---------| +| `draft` | Newly created; plan review not yet started | +| `plan-approved` | Plan review loop returned APPROVED | +| `implementation-in-progress` | Phase 6 executing | +| `implementation-approved` | Phase 8 review loop returned APPROVED; awaiting commit | +| `pushed` | Committed + pushed to remote | +| `local-only` | Committed locally; user declined push | +| `aborted-plan-review` | MAX_ROUNDS reached in Phase 5; user aborted | +| `aborted-impl-review` | MAX_ROUNDS reached in Phase 8; user aborted | +| `aborted-verification` | Phase 7 retries exhausted; user aborted | +| `failed` | Hard tooling failure | + +--- + +## Prompt + + + +## Interpretation + + + +## Assumptions + + + +## Files + + + +| Action | Path | Why | +|--------|------|-----| +| | | | + +## Approach + + + +## TDD Approach + + + +## Acceptance Criteria + +- [ ] +- [ ] + +## Verification + + + +## Rollback + + + +--- + +## Runtime State + +```yaml +plan_review_round: 0 +implementation_review_round: 0 +CODEX_PLAN_SESSION_ID: +CODEX_IMPL_SESSION_ID: +CURSOR_PLAN_SESSION_ID: +CURSOR_IMPL_SESSION_ID: +OPENCODE_PLAN_SESSION_ID: +OPENCODE_IMPL_SESSION_ID: +last_phase_entered: +last_round_ts: +last_scan_outcome_plan: +last_scan_outcome_impl: +verification_attempts: 0 +tests_added_count: 0 +tdd_used: false +``` + +## Review History + +| Timestamp (ISO-8601) | Loop | Round | Verdict | Summary | +|----------------------|------|-------|---------|---------| +| | | | | | + +## Final Status + + + +--- + +## Guardrails (do NOT remove) + +- This file is the single persistent artifact for `do-task`. Do not split it or delete it on success. +- `Status` must always match one of the enum values. +- `Runtime State` is updated by the skill, not by the user. +- Review History is append-only. diff --git a/skills/implement-plan/pi/SKILL.md b/skills/implement-plan/pi/SKILL.md new file mode 100644 index 0000000..b8b9ee2 --- /dev/null +++ b/skills/implement-plan/pi/SKILL.md @@ -0,0 +1,214 @@ +--- +name: implement-plan +description: Use when a plan folder created by create-plan must be executed in pi with milestone verification, reviewer gates, local commits, and resumable tracker updates. +--- + +# Implement Plan (Pi) + +Execute an existing plan under `ai_plan/` milestone by milestone, using verification gates, reviewer approval, and local commits after each approved milestone. + +## Shared Setup + +Before using this skill, read [docs/PI-SUPERPOWERS.md](../../../docs/PI-SUPERPOWERS.md). This workflow depends on: + +- Superpowers execution skills being visible to pi +- the pi reviewer-runtime helper being installed in a supported location + +## Prerequisite Check (MANDATORY) + +Required: + +- `pi --version` +- a plan folder under `ai_plan/` +- `continuation-runbook.md` +- `milestone-plan.md` +- `story-tracker.md` +- git worktree support +- Superpowers `executing-plans` +- Superpowers `using-git-worktrees` +- Superpowers `verification-before-completion` +- Superpowers `finishing-a-development-branch` +- pi reviewer runtime helper +- pi Telegram notifier helper + +Quick checks for common installs: + +```bash +pi --version +git worktree list +test -f ~/.agents/skills/superpowers/executing-plans/SKILL.md || test -f ~/.pi/agent/skills/superpowers/executing-plans/SKILL.md +test -f ~/.agents/skills/superpowers/using-git-worktrees/SKILL.md || test -f ~/.pi/agent/skills/superpowers/using-git-worktrees/SKILL.md +test -f ~/.agents/skills/superpowers/verification-before-completion/SKILL.md || test -f ~/.pi/agent/skills/superpowers/verification-before-completion/SKILL.md +test -f ~/.agents/skills/superpowers/finishing-a-development-branch/SKILL.md || test -f ~/.pi/agent/skills/superpowers/finishing-a-development-branch/SKILL.md +test -x .pi/skills/reviewer-runtime/pi/run-review.sh || test -x ~/.pi/agent/skills/reviewer-runtime/pi/run-review.sh +test -x .pi/skills/reviewer-runtime/pi/notify-telegram.sh || test -x ~/.pi/agent/skills/reviewer-runtime/pi/notify-telegram.sh +``` + +If you use settings-defined paths instead of the common locations above, confirm they match [docs/PI-SUPERPOWERS.md](../../../docs/PI-SUPERPOWERS.md) before continuing. + +If any dependency is missing, stop and return: + +`Missing dependency: pi implement-plan requires the execution skills and reviewer-runtime setup documented in docs/PI-SUPERPOWERS.md.` + +## Required Workflow Rules + +- Load the relevant workflow skill before entering its phase. If pi did not auto-load it, use `/skill:`. +- Announce skill usage explicitly: + - `I've read the [Skill Name] skill and I'm using it to [purpose].` +- Update `story-tracker.md` before starting and after completing every story. +- Do not use deprecated wrapper CLIs. + +## Process + +### Phase 1: Locate Plan + +1. Scan `ai_plan/` and identify the target plan folder +2. Read `continuation-runbook.md` first +3. Read `story-tracker.md` to identify resume state +4. Read `milestone-plan.md` for the implementation spec + +### Phase 2: Configure Reviewer + +If the user already provided reviewer settings, use them. Otherwise ask: + +1. Reviewer CLI: `codex`, `claude`, `cursor`, `opencode`, or `skip` +2. Reviewer model +3. Max rounds, default `10` + +Store `REVIEWER_CLI`, `REVIEWER_MODEL`, and `MAX_ROUNDS`. + +### Phase 3: Set Up Workspace + +1. Load `using-git-worktrees` +2. Create or resume the implementation branch/worktree described by the plan +3. Verify baseline setup and tests before changing code + +### Phase 4: Execute Milestones + +For each milestone: + +1. Mark the next story `in-dev` in `story-tracker.md` +2. Implement the story +3. Mark the story `completed` +4. Continue until the milestone stories are done +5. Load `verification-before-completion` +6. Run lint, typecheck, and tests for the changed scope +7. Send the milestone diff and verification output to the reviewer before committing +8. Commit only after approval + +### Phase 5: Milestone Review Loop + +Skip this phase if `REVIEWER_CLI=skip`. + +#### Step 1: Generate Session ID + +```bash +REVIEW_ID=$(uuidgen | tr '[:upper:]' '[:lower:]' | head -c 8) +``` + +Use these temp artifacts: + +- `/tmp/milestone-${REVIEW_ID}.md` +- `/tmp/milestone-review-${REVIEW_ID}.md` +- `/tmp/milestone-review-${REVIEW_ID}.json` +- `/tmp/milestone-review-${REVIEW_ID}.stderr` +- `/tmp/milestone-review-${REVIEW_ID}.status` +- `/tmp/milestone-review-${REVIEW_ID}.runner.out` +- `/tmp/milestone-review-${REVIEW_ID}.sh` + +Resolve the pi reviewer runtime helper in this order: + +```bash +REVIEWER_RUNTIME="" +for candidate in ".pi/skills/reviewer-runtime/pi/run-review.sh" "$HOME/.pi/agent/skills/reviewer-runtime/pi/run-review.sh"; do + if [ -x "$candidate" ]; then + REVIEWER_RUNTIME="$candidate" + break + fi +done +``` + +#### Step 2: Build Review Payload + +Write the milestone spec, acceptance criteria, diff, and verification output to `/tmp/milestone-${REVIEW_ID}.md`. + +Reviewer responses must use this structure: + +```text +## Summary +... + +## Findings +### P0 +- ... +### P1 +- ... +### P2 +- ... +### P3 +- ... + +## Verdict +VERDICT: APPROVED +``` + +Rules: + +- Order findings from `P0` to `P3` +- Use `- None.` when a severity has no findings +- `VERDICT: APPROVED` is valid only when no `P0`, `P1`, or `P2` findings remain + +#### Step 3: Run Review + +Execute the reviewer command script through the helper when available: + +```bash +"$REVIEWER_RUNTIME" \ + --command-file /tmp/milestone-review-${REVIEW_ID}.sh \ + --stdout-file /tmp/milestone-review-${REVIEW_ID}.runner.out \ + --stderr-file /tmp/milestone-review-${REVIEW_ID}.stderr \ + --status-file /tmp/milestone-review-${REVIEW_ID}.status +``` + +Fallback to direct execution only if the helper is missing. + +#### Step 4: Handle Findings + +- Keep waiting while fresh `state=in-progress note="In progress N"` heartbeats continue +- Fix `P0`, `P1`, and `P2` findings before approval +- Fix cheap `P3` findings when safe +- Re-run verification after each revision + +### Phase 6: Commit And Track Approval + +After milestone approval: + +1. Commit the milestone locally +2. Backfill the commit hash into that milestone's story notes +3. Mark the milestone `approved` in `story-tracker.md` +4. Move to the next milestone + +### Phase 7: Finalization + +After all milestones are approved: + +1. Load `finishing-a-development-branch` +2. Run the full verification suite +3. Ask whether to push or keep the work local +4. Mark the plan completed in `story-tracker.md` + +### Phase 8: Telegram Completion Notification + +Resolve the helper in this order: + +```bash +TELEGRAM_NOTIFY_RUNTIME="" +for candidate in ".pi/skills/reviewer-runtime/pi/notify-telegram.sh" "$HOME/.pi/agent/skills/reviewer-runtime/pi/notify-telegram.sh"; do + if [ -x "$candidate" ]; then + TELEGRAM_NOTIFY_RUNTIME="$candidate" + break + fi +done +``` + +If the helper exists and both `TELEGRAM_BOT_TOKEN` and `TELEGRAM_CHAT_ID` are configured, send a short completion summary. Otherwise state that no Telegram completion notification was sent. diff --git a/skills/reviewer-runtime/pi/notify-telegram.sh b/skills/reviewer-runtime/pi/notify-telegram.sh new file mode 100755 index 0000000..d62931c --- /dev/null +++ b/skills/reviewer-runtime/pi/notify-telegram.sh @@ -0,0 +1,101 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Keep this file in sync with skills/reviewer-runtime/notify-telegram.sh. + +DEFAULT_API_BASE_URL="https://api.telegram.org" +DEFAULT_PARSE_MODE="HTML" +MAX_MESSAGE_LENGTH=4096 + +BOT_TOKEN=${TELEGRAM_BOT_TOKEN:-} +CHAT_ID=${TELEGRAM_CHAT_ID:-} +API_BASE_URL=${TELEGRAM_API_BASE_URL:-$DEFAULT_API_BASE_URL} +PARSE_MODE=${TELEGRAM_PARSE_MODE:-$DEFAULT_PARSE_MODE} +MESSAGE="" +MESSAGE_FILE="" + +usage() { + cat <<'EOF' +Usage: + notify-telegram.sh --message [--bot-token ] [--chat-id ] [--api-base-url ] + notify-telegram.sh --message-file [--bot-token ] [--chat-id ] [--api-base-url ] + +Environment fallbacks: + TELEGRAM_BOT_TOKEN + TELEGRAM_CHAT_ID + TELEGRAM_API_BASE_URL + TELEGRAM_PARSE_MODE +EOF +} + +fail_usage() { + echo "Error: $*" >&2 + usage >&2 + exit 2 +} + +parse_args() { + while [[ $# -gt 0 ]]; do + case "$1" in + --bot-token) + BOT_TOKEN=${2:-} + shift 2 + ;; + --chat-id) + CHAT_ID=${2:-} + shift 2 + ;; + --api-base-url) + API_BASE_URL=${2:-} + shift 2 + ;; + --message) + MESSAGE=${2:-} + shift 2 + ;; + --message-file) + MESSAGE_FILE=${2:-} + shift 2 + ;; + --help|-h) + usage + exit 0 + ;; + *) + fail_usage "unknown argument: $1" + ;; + esac + done + + if [[ -n "$MESSAGE" && -n "$MESSAGE_FILE" ]]; then + fail_usage "use either --message or --message-file, not both" + fi + + if [[ -n "$MESSAGE_FILE" ]]; then + [[ -r "$MESSAGE_FILE" ]] || fail_usage "message file is not readable: $MESSAGE_FILE" + MESSAGE=$(<"$MESSAGE_FILE") + fi + + [[ -n "$MESSAGE" ]] || fail_usage "message is required" + [[ -n "$BOT_TOKEN" ]] || fail_usage "bot token is required (use --bot-token or TELEGRAM_BOT_TOKEN)" + [[ -n "$CHAT_ID" ]] || fail_usage "chat id is required (use --chat-id or TELEGRAM_CHAT_ID)" + command -v curl >/dev/null 2>&1 || fail_usage "curl is required" + + if [[ ${#MESSAGE} -gt "$MAX_MESSAGE_LENGTH" ]]; then + MESSAGE=${MESSAGE:0:$MAX_MESSAGE_LENGTH} + fi +} + +main() { + parse_args "$@" + + curl -fsS -X POST \ + "${API_BASE_URL%/}/bot${BOT_TOKEN}/sendMessage" \ + --data-urlencode "chat_id=${CHAT_ID}" \ + --data-urlencode "text=${MESSAGE}" \ + --data-urlencode "parse_mode=${PARSE_MODE}" \ + --data-urlencode "disable_web_page_preview=true" \ + >/dev/null +} + +main "$@" diff --git a/skills/reviewer-runtime/pi/run-review.sh b/skills/reviewer-runtime/pi/run-review.sh new file mode 100755 index 0000000..4476a92 --- /dev/null +++ b/skills/reviewer-runtime/pi/run-review.sh @@ -0,0 +1,360 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Keep this file in sync with skills/reviewer-runtime/run-review.sh. + +DEFAULT_POLL_SECONDS=10 +DEFAULT_HEARTBEAT_SECONDS=60 +DEFAULT_SOFT_TIMEOUT_SECONDS=600 +DEFAULT_STALL_WARNING_SECONDS=300 +DEFAULT_HARD_TIMEOUT_SECONDS=1800 +EXIT_COMPLETED_EMPTY_OUTPUT=80 +EXIT_NEEDS_OPERATOR_DECISION=81 + +COMMAND_FILE="" +STDOUT_FILE="" +STDERR_FILE="" +STATUS_FILE="" +SUCCESS_FILES=() +POLL_SECONDS=$DEFAULT_POLL_SECONDS +HEARTBEAT_SECONDS=$DEFAULT_HEARTBEAT_SECONDS +SOFT_TIMEOUT_SECONDS=$DEFAULT_SOFT_TIMEOUT_SECONDS +STALL_WARNING_SECONDS=$DEFAULT_STALL_WARNING_SECONDS +HARD_TIMEOUT_SECONDS=$DEFAULT_HARD_TIMEOUT_SECONDS + +CHILD_PID="" +USE_GROUP_KILL=0 +INTERRUPTED=0 + +usage() { + cat <<'EOF' +Usage: + run-review.sh \ + --command-file \ + --stdout-file \ + --stderr-file \ + --status-file \ + [--success-file ] \ + [--poll-seconds ] \ + [--heartbeat-seconds ] \ + [--soft-timeout-seconds ] \ + [--stall-warning-seconds ] \ + [--hard-timeout-seconds ] +EOF +} + +fail_usage() { + echo "Error: $*" >&2 + usage >&2 + exit 2 +} + +require_integer() { + local name=$1 + local value=$2 + [[ "$value" =~ ^[0-9]+$ ]] || fail_usage "$name must be an integer" +} + +escape_note() { + local note=$1 + note=${note//$'\n'/ } + note=${note//\"/\'} + printf '%s' "$note" +} + +join_success_files() { + if [[ ${#SUCCESS_FILES[@]} -eq 0 ]]; then + printf '' + return 0 + fi + + local joined="" + local path + + for path in "${SUCCESS_FILES[@]}"; do + if [[ -n "$joined" ]]; then + joined+=", " + fi + joined+="$path" + done + + printf '%s' "$joined" +} + +iso_timestamp() { + date -u +"%Y-%m-%dT%H:%M:%SZ" +} + +elapsed_seconds() { + local now + now=$(date +%s) + printf '%s' $((now - START_TIME)) +} + +file_bytes() { + local path=$1 + if [[ -f "$path" ]]; then + wc -c <"$path" | tr -d '[:space:]' + else + printf '0' + fi +} + +append_status() { + local level=$1 + local state=$2 + local note=$3 + local elapsed pid stdout_bytes stderr_bytes line + + elapsed=$(elapsed_seconds) + pid=${CHILD_PID:-0} + stdout_bytes=$(file_bytes "$STDOUT_FILE") + stderr_bytes=$(file_bytes "$STDERR_FILE") + line="ts=$(iso_timestamp) level=$level state=$state elapsed_s=$elapsed pid=$pid stdout_bytes=$stdout_bytes stderr_bytes=$stderr_bytes note=\"$(escape_note "$note")\"" + + printf '%s\n' "$line" | tee -a "$STATUS_FILE" +} + +ensure_parent_dir() { + local path=$1 + mkdir -p "$(dirname "$path")" +} + +kill_child_process_group() { + if [[ -z "$CHILD_PID" ]]; then + return 0 + fi + + if ! kill -0 "$CHILD_PID" 2>/dev/null; then + return 0 + fi + + if [[ "$USE_GROUP_KILL" -eq 1 ]]; then + kill -TERM -- "-$CHILD_PID" 2>/dev/null || kill -TERM "$CHILD_PID" 2>/dev/null || true + else + kill -TERM "$CHILD_PID" 2>/dev/null || true + fi + + sleep 1 + + if kill -0 "$CHILD_PID" 2>/dev/null; then + if [[ "$USE_GROUP_KILL" -eq 1 ]]; then + kill -KILL -- "-$CHILD_PID" 2>/dev/null || kill -KILL "$CHILD_PID" 2>/dev/null || true + else + kill -KILL "$CHILD_PID" 2>/dev/null || true + fi + fi +} + +handle_signal() { + local signal_name=$1 + INTERRUPTED=1 + append_status error failed "received SIG${signal_name}; terminating reviewer child" + kill_child_process_group + exit 130 +} + +parse_args() { + while [[ $# -gt 0 ]]; do + case "$1" in + --command-file) + COMMAND_FILE=${2:-} + shift 2 + ;; + --stdout-file) + STDOUT_FILE=${2:-} + shift 2 + ;; + --stderr-file) + STDERR_FILE=${2:-} + shift 2 + ;; + --status-file) + STATUS_FILE=${2:-} + shift 2 + ;; + --success-file) + SUCCESS_FILES+=("${2:-}") + shift 2 + ;; + --poll-seconds) + POLL_SECONDS=${2:-} + shift 2 + ;; + --heartbeat-seconds) + HEARTBEAT_SECONDS=${2:-} + shift 2 + ;; + --soft-timeout-seconds) + SOFT_TIMEOUT_SECONDS=${2:-} + shift 2 + ;; + --stall-warning-seconds) + STALL_WARNING_SECONDS=${2:-} + shift 2 + ;; + --hard-timeout-seconds) + HARD_TIMEOUT_SECONDS=${2:-} + shift 2 + ;; + --help|-h) + usage + exit 0 + ;; + *) + fail_usage "unknown argument: $1" + ;; + esac + done + + [[ -n "$COMMAND_FILE" ]] || fail_usage "--command-file is required" + [[ -n "$STDOUT_FILE" ]] || fail_usage "--stdout-file is required" + [[ -n "$STDERR_FILE" ]] || fail_usage "--stderr-file is required" + [[ -n "$STATUS_FILE" ]] || fail_usage "--status-file is required" + + require_integer "poll-seconds" "$POLL_SECONDS" + require_integer "heartbeat-seconds" "$HEARTBEAT_SECONDS" + require_integer "soft-timeout-seconds" "$SOFT_TIMEOUT_SECONDS" + require_integer "stall-warning-seconds" "$STALL_WARNING_SECONDS" + require_integer "hard-timeout-seconds" "$HARD_TIMEOUT_SECONDS" + + [[ "$POLL_SECONDS" -gt 0 ]] || fail_usage "poll-seconds must be > 0" + [[ "$HEARTBEAT_SECONDS" -gt 0 ]] || fail_usage "heartbeat-seconds must be > 0" + [[ "$SOFT_TIMEOUT_SECONDS" -gt 0 ]] || fail_usage "soft-timeout-seconds must be > 0" + [[ "$STALL_WARNING_SECONDS" -gt 0 ]] || fail_usage "stall-warning-seconds must be > 0" + [[ "$HARD_TIMEOUT_SECONDS" -gt 0 ]] || fail_usage "hard-timeout-seconds must be > 0" + [[ "$SOFT_TIMEOUT_SECONDS" -le "$HARD_TIMEOUT_SECONDS" ]] || fail_usage "soft-timeout-seconds must be <= hard-timeout-seconds" + [[ "$STALL_WARNING_SECONDS" -le "$HARD_TIMEOUT_SECONDS" ]] || fail_usage "stall-warning-seconds must be <= hard-timeout-seconds" + + [[ -r "$COMMAND_FILE" ]] || fail_usage "command file is not readable: $COMMAND_FILE" +} + +launch_child() { + if command -v setsid >/dev/null 2>&1; then + setsid bash "$COMMAND_FILE" >"$STDOUT_FILE" 2>"$STDERR_FILE" & + USE_GROUP_KILL=1 + else + bash "$COMMAND_FILE" >"$STDOUT_FILE" 2>"$STDERR_FILE" & + USE_GROUP_KILL=0 + fi + CHILD_PID=$! +} + +main() { + parse_args "$@" + + ensure_parent_dir "$STDOUT_FILE" + ensure_parent_dir "$STDERR_FILE" + ensure_parent_dir "$STATUS_FILE" + : >"$STDOUT_FILE" + : >"$STDERR_FILE" + : >"$STATUS_FILE" + + START_TIME=$(date +%s) + export START_TIME + + trap 'handle_signal INT' INT + trap 'handle_signal TERM' TERM + trap 'if [[ "$INTERRUPTED" -eq 0 ]]; then kill_child_process_group; fi' EXIT + + launch_child + append_status info running-silent "reviewer child launched" + + local last_stdout_bytes=0 + local last_stderr_bytes=0 + local last_output_change_time=$START_TIME + local last_heartbeat_time=$START_TIME + local soft_timeout_logged=0 + local stall_warning_logged=0 + local heartbeat_count=0 + + while kill -0 "$CHILD_PID" 2>/dev/null; do + sleep "$POLL_SECONDS" + + local now elapsed stdout_bytes stderr_bytes note state level + now=$(date +%s) + elapsed=$((now - START_TIME)) + stdout_bytes=$(file_bytes "$STDOUT_FILE") + stderr_bytes=$(file_bytes "$STDERR_FILE") + + if [[ $((now - last_heartbeat_time)) -ge "$HEARTBEAT_SECONDS" ]]; then + heartbeat_count=$((heartbeat_count + 1)) + append_status info in-progress "In progress ${heartbeat_count}" + last_heartbeat_time=$now + fi + + if [[ "$stdout_bytes" -ne "$last_stdout_bytes" || "$stderr_bytes" -ne "$last_stderr_bytes" ]]; then + last_output_change_time=$now + stall_warning_logged=0 + state=running-active + level=info + note="reviewer output changed" + else + local silent_for + silent_for=$((now - last_output_change_time)) + if [[ "$silent_for" -ge "$STALL_WARNING_SECONDS" ]]; then + state=stall-warning + level=warn + note="no output growth for ${silent_for}s; process still alive" + stall_warning_logged=1 + else + state=running-silent + level=info + note="reviewer process alive; waiting for output" + fi + fi + + if [[ "$elapsed" -ge "$SOFT_TIMEOUT_SECONDS" && "$soft_timeout_logged" -eq 0 ]]; then + note="$note; soft timeout reached, continuing while reviewer is alive" + soft_timeout_logged=1 + fi + + append_status "$level" "$state" "$note" + last_stdout_bytes=$stdout_bytes + last_stderr_bytes=$stderr_bytes + + if [[ "$elapsed" -ge "$HARD_TIMEOUT_SECONDS" ]]; then + append_status error needs-operator-decision "hard timeout reached; terminating reviewer child for operator intervention" + kill_child_process_group + trap - EXIT + exit "$EXIT_NEEDS_OPERATOR_DECISION" + fi + done + + local child_exit_code=0 + set +e + wait "$CHILD_PID" + child_exit_code=$? + set -e + trap - EXIT + + local final_stdout_bytes final_stderr_bytes + local success_file success_bytes + final_stdout_bytes=$(file_bytes "$STDOUT_FILE") + final_stderr_bytes=$(file_bytes "$STDERR_FILE") + + if [[ "$child_exit_code" -eq 0 ]]; then + if [[ "$final_stdout_bytes" -gt 0 ]]; then + append_status info completed "reviewer completed successfully" + exit 0 + fi + + if [[ ${#SUCCESS_FILES[@]} -gt 0 ]]; then + for success_file in "${SUCCESS_FILES[@]}"; do + success_bytes=$(file_bytes "$success_file") + if [[ "$success_bytes" -gt 0 ]]; then + append_status info completed "reviewer completed successfully via success file $(join_success_files)" + exit 0 + fi + done + fi + + append_status error completed-empty-output "reviewer exited successfully with empty stdout" + exit "$EXIT_COMPLETED_EMPTY_OUTPUT" + fi + + append_status error failed "reviewer exited with code $child_exit_code" + exit "$child_exit_code" +} + +main "$@"