From 3e9cbc8c346c1815da21d0b64457749c5342cdd5 Mon Sep 17 00:00:00 2001 From: Gordon Mickel Date: Fri, 16 Jan 2026 02:43:15 +0100 Subject: [PATCH 01/11] chore: sync upstream + ralph --- .../skill/flow-next-impl-review/SKILL.md | 12 +- .opencode/skill/flow-next-interview/SKILL.md | 73 ++-- .../skill/flow-next-plan-review/SKILL.md | 12 +- .../skill/flow-next-plan-review/workflow.md | 30 ++ .opencode/skill/flow-next-plan/SKILL.md | 12 +- .opencode/skill/flow-next-plan/steps.md | 52 ++- .opencode/skill/flow-next-ralph-init/SKILL.md | 14 +- .../flow-next-ralph-init/templates/ralph.sh | 111 ++++- .../templates/watch-filter.py | 1 - .opencode/skill/flow-next-work/SKILL.md | 12 +- AGENTS.md | 1 + CHANGELOG.md | 9 + plugins/flow-next/.claude-plugin/plugin.json | 4 +- plugins/flow-next/README.md | 43 +- plugins/flow-next/docs/flowctl.md | 104 +++-- plugins/flow-next/docs/ralph.md | 234 +++------- plugins/flow-next/scripts/flowctl.py | 408 ++++++++++++++++-- plugins/flow-next/scripts/ralph_smoke_test.sh | 27 +- plugins/flow-next/scripts/smoke_test.sh | 116 +++-- .../skills/flow-next-interview/SKILL.md | 74 ++-- .../skills/flow-next-plan-review/SKILL.md | 22 +- .../skills/flow-next-plan-review/workflow.md | 28 +- .../flow-next/skills/flow-next-plan/steps.md | 52 ++- .../flow-next-ralph-init/templates/config.env | 20 +- .../templates/prompt_plan.md | 24 +- .../templates/prompt_work.md | 22 +- .../flow-next-ralph-init/templates/ralph.sh | 238 ++++++---- .../templates/watch-filter.py | 75 +--- .../flow-next/skills/flow-next-work/SKILL.md | 2 +- plugins/flow-next/skills/flow-next/SKILL.md | 21 +- sync/PORTING.md | 108 +++++ .../skill/flow-next-impl-review/SKILL.md | 12 +- .../skill/flow-next-interview/SKILL.md | 73 ++-- .../skill/flow-next-plan-review/SKILL.md | 12 +- .../skill/flow-next-plan-review/workflow.md | 30 ++ .../opencode/skill/flow-next-plan/SKILL.md | 12 +- .../opencode/skill/flow-next-plan/steps.md | 52 ++- .../skill/flow-next-ralph-init/SKILL.md | 14 +- .../flow-next-ralph-init/templates/ralph.sh | 59 ++- .../templates/watch-filter.py | 1 - .../opencode/skill/flow-next-work/SKILL.md | 12 +- sync/verify.sh | 5 +- 42 files changed, 1532 insertions(+), 711 deletions(-) create mode 100644 sync/PORTING.md diff --git a/.opencode/skill/flow-next-impl-review/SKILL.md b/.opencode/skill/flow-next-impl-review/SKILL.md index 102073b..667d639 100644 --- a/.opencode/skill/flow-next-impl-review/SKILL.md +++ b/.opencode/skill/flow-next-impl-review/SKILL.md @@ -42,17 +42,17 @@ If found, use that backend and skip all other detection. ```bash # Check available backends -HAVE_RP=0 +HAVE_RP=0; if command -v rp-cli >/dev/null 2>&1; then - HAVE_RP=1 + HAVE_RP=1; elif [[ -x /opt/homebrew/bin/rp-cli || -x /usr/local/bin/rp-cli ]]; then - HAVE_RP=1 -fi + HAVE_RP=1; +fi; # Get configured backend -BACKEND="${FLOW_REVIEW_BACKEND:-}" +BACKEND="${FLOW_REVIEW_BACKEND:-}"; if [[ -z "$BACKEND" ]]; then - BACKEND="$($FLOWCTL config get review.backend 2>/dev/null | jq -r '.value // empty')" + BACKEND="$($FLOWCTL config get review.backend 2>/dev/null | jq -r '.value // empty')"; fi ``` diff --git a/.opencode/skill/flow-next-interview/SKILL.md b/.opencode/skill/flow-next-interview/SKILL.md index dc4f4ad..dc62f70 100644 --- a/.opencode/skill/flow-next-interview/SKILL.md +++ b/.opencode/skill/flow-next-interview/SKILL.md @@ -98,42 +98,51 @@ After interview complete, write everything back. ### For Flow Epic ID -1. Create a temp file with the refined epic spec including: - - Clear problem statement - - Technical approach with specifics - - Key decisions made during interview - - Edge cases to handle - - Quick commands section (required) - - Acceptance criteria - -2. Update epic spec: - ```bash - $FLOWCTL epic set-plan --file --json - ``` - -3. Create/update tasks if interview revealed breakdown: - ```bash - $FLOWCTL task create --epic --title "..." --json - $FLOWCTL task set-description --file --json - $FLOWCTL task set-acceptance --file --json - ``` +Update epic spec using stdin heredoc (preferred): +```bash +$FLOWCTL epic set-plan --file - --json <<'EOF' +# Epic Title -### For Flow Task ID +## Problem +Clear problem statement + +## Approach +Technical approach with specifics, key decisions from interview + +## Edge Cases +- Edge case 1 +- Edge case 2 -1. Write description to temp file with: - - Clear task description - - Technical details from interview - - Edge cases +## Quick commands +```bash +# smoke test command +``` -2. Write acceptance to temp file with: - - Checkboxes for acceptance criteria - - Specific, testable conditions +## Acceptance +- [ ] Criterion 1 +- [ ] Criterion 2 +EOF +``` -3. Update task: - ```bash - $FLOWCTL task set-description --file --json - $FLOWCTL task set-acceptance --file --json - ``` +Create/update tasks if interview revealed breakdown: +```bash +$FLOWCTL task create --epic --title "..." --json +# Use set-spec for combined description + acceptance (fewer writes) +$FLOWCTL task set-spec --description /tmp/desc.md --acceptance /tmp/acc.md --json +``` + +### For Flow Task ID + +Update task using combined set-spec (preferred) or separate calls: +```bash +# Preferred: combined set-spec (2 writes instead of 4) +$FLOWCTL task set-spec --description /tmp/desc.md --acceptance /tmp/acc.md --json + +# Or use stdin for description only: +$FLOWCTL task set-description --file - --json <<'EOF' +Clear task description with technical details and edge cases from interview +EOF +``` ### For File Path diff --git a/.opencode/skill/flow-next-plan-review/SKILL.md b/.opencode/skill/flow-next-plan-review/SKILL.md index f800ba8..6202cc9 100644 --- a/.opencode/skill/flow-next-plan-review/SKILL.md +++ b/.opencode/skill/flow-next-plan-review/SKILL.md @@ -42,17 +42,17 @@ If found, use that backend and skip all other detection. ```bash # Check available backends -HAVE_RP=0 +HAVE_RP=0; if command -v rp-cli >/dev/null 2>&1; then - HAVE_RP=1 + HAVE_RP=1; elif [[ -x /opt/homebrew/bin/rp-cli || -x /usr/local/bin/rp-cli ]]; then - HAVE_RP=1 -fi + HAVE_RP=1; +fi; # Get configured backend -BACKEND="${FLOW_REVIEW_BACKEND:-}" +BACKEND="${FLOW_REVIEW_BACKEND:-}"; if [[ -z "$BACKEND" ]]; then - BACKEND="$($FLOWCTL config get review.backend 2>/dev/null | jq -r '.value // empty')" + BACKEND="$($FLOWCTL config get review.backend 2>/dev/null | jq -r '.value // empty')"; fi ``` diff --git a/.opencode/skill/flow-next-plan-review/workflow.md b/.opencode/skill/flow-next-plan-review/workflow.md index 2c900f7..adb6d89 100644 --- a/.opencode/skill/flow-next-plan-review/workflow.md +++ b/.opencode/skill/flow-next-plan-review/workflow.md @@ -139,6 +139,12 @@ $FLOWCTL cat Save output for inclusion in review prompt. Compose a 1-2 sentence summary for the setup-review command. +**Save checkpoint** (protects against context compaction during review): +```bash +$FLOWCTL checkpoint save --epic --json +``` +This creates `.flow/.checkpoint-.json` with full state. If compaction occurs during review-fix cycles, restore with `$FLOWCTL checkpoint restore --epic `. + --- ## Phase 2: Augment Selection (RP) @@ -242,6 +248,19 @@ EOF fi ``` +### Update status + +Extract verdict from response, then: +```bash +# If SHIP +$FLOWCTL epic set-plan-review-status --status ship --json + +# If NEEDS_WORK or MAJOR_RETHINK +$FLOWCTL epic set-plan-review-status --status needs_work --json +``` + +If no verdict tag, output `RETRY` and stop. + --- ## Fix Loop (RP) @@ -254,10 +273,21 @@ If verdict is NEEDS_WORK: 2. **Fix the plan** - Address each issue. Write updated plan to temp file. 3. **Update plan in flowctl** (MANDATORY before re-review): ```bash + # Option A: stdin heredoc (preferred, no temp file) + $FLOWCTL epic set-plan --file - --json <<'EOF' + + EOF + + # Option B: temp file (if content has single quotes) $FLOWCTL epic set-plan --file /tmp/updated-plan.md --json ``` **If you skip this step and re-review with same content, reviewer will return NEEDS_WORK again.** + **Recovery**: If context compaction occurred, restore from checkpoint first: + ```bash + $FLOWCTL checkpoint restore --epic --json + ``` + 4. **Re-review with fix summary** (only AFTER step 3): **IMPORTANT**: Do NOT re-add files already in the selection. RepoPrompt auto-refreshes diff --git a/.opencode/skill/flow-next-plan/SKILL.md b/.opencode/skill/flow-next-plan/SKILL.md index 89633ba..97c6c1b 100644 --- a/.opencode/skill/flow-next-plan/SKILL.md +++ b/.opencode/skill/flow-next-plan/SKILL.md @@ -44,17 +44,17 @@ If empty, ask: "What should I plan? Give me the feature or bug in 1-5 sentences. Check available backends and configured preference: ```bash -HAVE_RP=0 +HAVE_RP=0; if command -v rp-cli >/dev/null 2>&1; then - HAVE_RP=1 + HAVE_RP=1; elif [[ -x /opt/homebrew/bin/rp-cli || -x /usr/local/bin/rp-cli ]]; then - HAVE_RP=1 -fi + HAVE_RP=1; +fi; # Check configured backend (priority: env > config) -CONFIGURED_BACKEND="${FLOW_REVIEW_BACKEND:-}" +CONFIGURED_BACKEND="${FLOW_REVIEW_BACKEND:-}"; if [[ -z "$CONFIGURED_BACKEND" ]]; then - CONFIGURED_BACKEND="$($FLOWCTL config get review.backend 2>/dev/null | jq -r '.value // empty')" + CONFIGURED_BACKEND="$($FLOWCTL config get review.backend 2>/dev/null | jq -r '.value // empty')"; fi ``` diff --git a/.opencode/skill/flow-next-plan/steps.md b/.opencode/skill/flow-next-plan/steps.md index 42a9ba4..7ac8504 100644 --- a/.opencode/skill/flow-next-plan/steps.md +++ b/.opencode/skill/flow-next-plan/steps.md @@ -113,18 +113,25 @@ Default to short unless complexity demands more. ## Step 4: Write to .flow +**Efficiency note**: Use stdin (`--file -`) with heredocs to avoid temp files. Use `task set-spec` to set description + acceptance in one call. + **Route A - Input was an existing Flow ID**: 1. If epic ID (fn-N): - - Write a temp file with the updated plan spec - - `$FLOWCTL epic set-plan --file --json` + ```bash + # Use stdin heredoc (no temp file needed) + $FLOWCTL epic set-plan --file - --json <<'EOF' + + EOF + ``` - Create/update child tasks as needed 2. If task ID (fn-N.M): - - Write temp file for description - - `$FLOWCTL task set-description --file --json` - - Write temp file for acceptance - - `$FLOWCTL task set-acceptance --file --json` + ```bash + # Combined set-spec: description + acceptance in one call + # Write to temp files only if content has single quotes + $FLOWCTL task set-spec --description /tmp/desc.md --acceptance /tmp/acc.md --json + ``` **Route B - Input was text (new idea)**: @@ -141,10 +148,24 @@ Default to short unless complexity demands more. ``` - If user specified a branch, use that instead. -3. Write epic spec: - - Create a temp file with the full plan/spec content - - Include: Overview, Scope, Approach, Quick commands (REQUIRED - at least one smoke test command), Acceptance, References - - `$FLOWCTL epic set-plan --file --json` +3. Write epic spec (use stdin heredoc): + ```bash + # Include: Overview, Scope, Approach, Quick commands (REQUIRED), Acceptance, References + $FLOWCTL epic set-plan --file - --json <<'EOF' + # Epic Title + + ## Overview + ... + + ## Quick commands + ```bash + # At least one smoke test command + ``` + + ## Acceptance + ... + EOF + ``` 4. Create child tasks: ```bash @@ -152,10 +173,13 @@ Default to short unless complexity demands more. $FLOWCTL task create --epic --title "" --json ``` -5. Write task specs: - - For each task, write description and acceptance to temp files - - `$FLOWCTL task set-description --file --json` - - `$FLOWCTL task set-acceptance --file --json` +5. Write task specs (use combined set-spec): + ```bash + # For each task - single call sets both sections + # Write description and acceptance to temp files, then: + $FLOWCTL task set-spec --description /tmp/desc.md --acceptance /tmp/acc.md --json + ``` + This reduces 4 atomic writes per task to 2. 6. Add dependencies: ```bash diff --git a/.opencode/skill/flow-next-ralph-init/SKILL.md b/.opencode/skill/flow-next-ralph-init/SKILL.md index 51a2614..308824d 100644 --- a/.opencode/skill/flow-next-ralph-init/SKILL.md +++ b/.opencode/skill/flow-next-ralph-init/SKILL.md @@ -11,7 +11,7 @@ Scaffold repo-local Ralph harness. Opt-in only. - Only create `scripts/ralph/` in the current repo. - If `scripts/ralph/` already exists, stop and ask the user to remove it first. -- Copy templates from `templates/` into `scripts/ralph/`. +- Copy templates from `.opencode/skill/flow-next-ralph-init/templates/` into `scripts/ralph/`. - Copy `flowctl` and `flowctl.py` from `$PLUGIN_ROOT/scripts/` into `scripts/ralph/`. - Set executable bit on `scripts/ralph/ralph.sh`, `scripts/ralph/ralph_once.sh`, and `scripts/ralph/flowctl`. @@ -21,15 +21,16 @@ Scaffold repo-local Ralph harness. Opt-in only. ```bash ROOT="$(git rev-parse --show-toplevel)" PLUGIN_ROOT="$ROOT/plugins/flow-next" + TEMPLATE_DIR="$ROOT/.opencode/skill/flow-next-ralph-init/templates" ``` 2. Check `scripts/ralph/` does not exist. 3. Detect available review backends: ```bash -HAVE_RP=0 +HAVE_RP=0; if command -v rp-cli >/dev/null 2>&1; then - HAVE_RP=1 + HAVE_RP=1; elif [[ -x /opt/homebrew/bin/rp-cli || -x /usr/local/bin/rp-cli ]]; then - HAVE_RP=1 + HAVE_RP=1; fi ``` 4. Determine review backend: @@ -49,6 +50,11 @@ fi - `PLAN_REVIEW=` and `WORK_REVIEW=` - replace `{{PLAN_REVIEW}}` and `{{WORK_REVIEW}}` placeholders in the template 6. Copy templates and flowctl files. + ```bash + mkdir -p scripts/ralph + cp -R "$TEMPLATE_DIR/." scripts/ralph/ + cp "$PLUGIN_ROOT/scripts/flowctl" "$PLUGIN_ROOT/scripts/flowctl.py" scripts/ralph/ + ``` 7. Print next steps (run from terminal, NOT inside OpenCode): - Edit `scripts/ralph/config.env` to customize settings - `./scripts/ralph/ralph_once.sh` (one iteration, observe) diff --git a/.opencode/skill/flow-next-ralph-init/templates/ralph.sh b/.opencode/skill/flow-next-ralph-init/templates/ralph.sh index 4d2681f..50777e3 100644 --- a/.opencode/skill/flow-next-ralph-init/templates/ralph.sh +++ b/.opencode/skill/flow-next-ralph-init/templates/ralph.sh @@ -1,10 +1,34 @@ #!/usr/bin/env bash set -euo pipefail +# ───────────────────────────────────────────────────────────────────────────── +# Windows / Git Bash hardening (GH-35) +# ───────────────────────────────────────────────────────────────────────────── +UNAME_S="$(uname -s 2>/dev/null || echo "")" +IS_WINDOWS=0 +case "$UNAME_S" in + MINGW*|MSYS*|CYGWIN*) IS_WINDOWS=1 ;; +esac + +# Python detection: prefer python3, fallback to python (common on Windows) +pick_python() { + if [[ -n "${PYTHON_BIN:-}" ]]; then + command -v "$PYTHON_BIN" >/dev/null 2>&1 && { echo "$PYTHON_BIN"; return; } + fi + if command -v python3 >/dev/null 2>&1; then echo "python3"; return; fi + if command -v python >/dev/null 2>&1; then echo "python"; return; fi + echo "" +} + +PYTHON_BIN="$(pick_python)" +[[ -n "$PYTHON_BIN" ]] || { echo "ralph: python not found (need python3 or python in PATH)" >&2; exit 1; } +export PYTHON_BIN + SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" ROOT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)" CONFIG="$SCRIPT_DIR/config.env" FLOWCTL="$SCRIPT_DIR/flowctl" +FLOWCTL_PY="$SCRIPT_DIR/flowctl.py" fail() { echo "ralph: $*" >&2; exit 1; } log() { @@ -13,6 +37,35 @@ log() { return 0 } +# Ensure flowctl is runnable even when NTFS exec bit / shebang handling is flaky on Windows +ensure_flowctl_wrapper() { + # If flowctl exists and is executable, use it + if [[ -f "$FLOWCTL" && -x "$FLOWCTL" ]]; then + return 0 + fi + + # On Windows or if flowctl not executable, create a wrapper that calls Python explicitly + if [[ -f "$FLOWCTL_PY" ]]; then + local wrapper="$SCRIPT_DIR/flowctl-wrapper.sh" + cat > "$wrapper" </dev/null 2>&1 || PY="python" +exec "\$PY" "\$DIR/flowctl.py" "\$@" +SH + chmod +x "$wrapper" 2>/dev/null || true + FLOWCTL="$wrapper" + export FLOWCTL + return 0 + fi + + fail "missing flowctl (expected $SCRIPT_DIR/flowctl or $SCRIPT_DIR/flowctl.py)" +} + +ensure_flowctl_wrapper + # ───────────────────────────────────────────────────────────────────────────── # Presentation layer (human-readable output) # ───────────────────────────────────────────────────────────────────────────── @@ -59,7 +112,7 @@ ui() { # Get title from epic/task JSON get_title() { local json="$1" - python3 - "$json" <<'PY' + "$PYTHON_BIN" - "$json" <<'PY' import json, sys try: data = json.loads(sys.argv[1]) @@ -71,7 +124,7 @@ PY # Count progress (done/total tasks for scoped epics) get_progress() { - python3 - "$ROOT_DIR" "${EPICS_FILE:-}" <<'PY' + "$PYTHON_BIN" - "$ROOT_DIR" "${EPICS_FILE:-}" <<'PY' import json, sys from pathlib import Path root = Path(sys.argv[1]) @@ -125,7 +178,7 @@ get_git_stats() { echo "" return fi - python3 - "$stats" <<'PY' + "$PYTHON_BIN" - "$stats" <<'PY' import re, sys s = sys.argv[1] files = re.search(r"(\d+) files? changed", s) @@ -521,6 +574,46 @@ append_progress() { } >> "$PROGRESS_FILE" } +# Write completion marker to progress.txt (MUST match find_active_runs() detection in flowctl.py) +write_completion_marker() { + local reason="${1:-DONE}" + { + echo "" + echo "completion_reason=$reason" + echo "promise=COMPLETE" # CANONICAL - must match flowctl.py substring search + } >> "$PROGRESS_FILE" +} + +# Check PAUSE/STOP sentinel files +check_sentinels() { + local pause_file="$RUN_DIR/PAUSE" + local stop_file="$RUN_DIR/STOP" + + # Check for stop first (exit immediately, keep file for audit) + if [[ -f "$stop_file" ]]; then + log "STOP sentinel detected, exiting gracefully" + ui_fail "STOP sentinel detected" + write_completion_marker "STOPPED" + exit 0 + fi + + # Check for pause (log once, wait in loop, re-check STOP while waiting) + if [[ -f "$pause_file" ]]; then + log "PAUSED - waiting for resume..." + while [[ -f "$pause_file" ]]; do + # Re-check STOP while paused so external stop works + if [[ -f "$stop_file" ]]; then + log "STOP sentinel detected while paused, exiting gracefully" + ui_fail "STOP sentinel detected" + write_completion_marker "STOPPED" + exit 0 + fi + sleep 5 + done + log "Resumed" + fi +} + init_branches_file() { if [[ -f "$BRANCHES_FILE" ]]; then return; fi local base_branch @@ -681,6 +774,9 @@ iter=1 while (( iter <= MAX_ITERATIONS )); do iter_log="$RUN_DIR/iter-$(printf '%03d' "$iter").log" + # Check for pause/stop at start of iteration (before work selection) + check_sentinels + selector_args=("$FLOWCTL" next --json) [[ -n "$EPICS_FILE" ]] && selector_args+=(--epics-file "$EPICS_FILE") [[ "$REQUIRE_PLAN_REVIEW" == "1" ]] && selector_args+=(--require-plan-review) @@ -700,7 +796,7 @@ while (( iter <= MAX_ITERATIONS )); do fi maybe_close_epics ui_complete - echo "COMPLETE" + write_completion_marker "NO_WORK" exit 0 fi @@ -860,7 +956,7 @@ TXT if echo "$worker_text" | grep -q "COMPLETE"; then ui_complete - echo "COMPLETE" + write_completion_marker "DONE" exit 0 fi @@ -880,6 +976,7 @@ TXT if [[ "$exit_code" -eq 1 ]]; then log "exit=fail" ui_fail "OpenCode returned FAIL promise" + write_completion_marker "FAILED" exit 1 fi @@ -902,10 +999,14 @@ TXT fi fi + # Check for pause/stop after OpenCode returns (before next iteration) + check_sentinels + sleep 2 iter=$((iter + 1)) done ui_fail "Max iterations ($MAX_ITERATIONS) reached" echo "ralph: max iterations reached" >&2 +write_completion_marker "MAX_ITERATIONS" exit 1 diff --git a/.opencode/skill/flow-next-ralph-init/templates/watch-filter.py b/.opencode/skill/flow-next-ralph-init/templates/watch-filter.py index 99a56b5..bca5d96 100755 --- a/.opencode/skill/flow-next-ralph-init/templates/watch-filter.py +++ b/.opencode/skill/flow-next-ralph-init/templates/watch-filter.py @@ -44,7 +44,6 @@ "WebFetch": "🌐", "WebSearch": "🔎", "TodoWrite": "📋", - "AskUserQuestion": "❓", "Question": "❓", "Skill": "⚡", } diff --git a/.opencode/skill/flow-next-work/SKILL.md b/.opencode/skill/flow-next-work/SKILL.md index afd3144..abd2a4a 100644 --- a/.opencode/skill/flow-next-work/SKILL.md +++ b/.opencode/skill/flow-next-work/SKILL.md @@ -59,17 +59,17 @@ If no input provided, ask for it. Check available backends and configured preference: ```bash -HAVE_RP=0 +HAVE_RP=0; if command -v rp-cli >/dev/null 2>&1; then - HAVE_RP=1 + HAVE_RP=1; elif [[ -x /opt/homebrew/bin/rp-cli || -x /usr/local/bin/rp-cli ]]; then - HAVE_RP=1 -fi + HAVE_RP=1; +fi; # Check configured backend (priority: env > config) -CONFIGURED_BACKEND="${FLOW_REVIEW_BACKEND:-}" +CONFIGURED_BACKEND="${FLOW_REVIEW_BACKEND:-}"; if [[ -z "$CONFIGURED_BACKEND" ]]; then - CONFIGURED_BACKEND="$($FLOWCTL config get review.backend 2>/dev/null | jq -r '.value // empty')" + CONFIGURED_BACKEND="$($FLOWCTL config get review.backend 2>/dev/null | jq -r '.value // empty')"; fi ``` diff --git a/AGENTS.md b/AGENTS.md index 9ab8a8a..daa3a1f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,3 +1,4 @@ # Notes - OpenCode logs: `~/.local/share/opencode/log/` (tail for live run status) +- Porting guidance: `sync/PORTING.md` (read before syncing upstream) diff --git a/CHANGELOG.md b/CHANGELOG.md index 151e7ff..47457d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,15 @@ - Add browser skill (standalone) +## 0.2.2 + +- flowctl stdin support + Windows-safe codex exec piping +- flowctl status output + artifact-file guards +- Ralph Windows hardening + watch filter updates +- Plan/interview flows: stdin + task set-spec efficiency +- Docs: flowctl commands (checkpoint/status/task reset) restored +- Sync/porting guardrails updated for OpenCode + ## 0.2.0 - Ralph (autonomous loop) + opencode runner/reviewer config diff --git a/plugins/flow-next/.claude-plugin/plugin.json b/plugins/flow-next/.claude-plugin/plugin.json index 7e7bdc6..433046d 100644 --- a/plugins/flow-next/.claude-plugin/plugin.json +++ b/plugins/flow-next/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "flow-next", - "version": "0.7.1", - "description": "Zero-dependency planning + execution with .flow/ task tracking and Ralph autonomous mode (multi-model review gates). Includes 6 subagents, 8 commands, 11 skills.", + "version": "0.10.0", + "description": "Zero-dependency planning + execution with .flow/ task tracking and Ralph autonomous mode (multi-model review gates). Includes 6 subagents, 8 commands, 12 skills.", "author": { "name": "Gordon Mickel", "email": "gordon@mickel.tech", diff --git a/plugins/flow-next/README.md b/plugins/flow-next/README.md index 39ba399..17dbf54 100644 --- a/plugins/flow-next/README.md +++ b/plugins/flow-next/README.md @@ -5,9 +5,7 @@ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](../../LICENSE) [![Claude Code](https://img.shields.io/badge/Claude_Code-Plugin-blueviolet)](https://claude.ai/code) -[![Version](https://img.shields.io/badge/Version-0.7.1-green)](../../CHANGELOG.md) - -[![Version](https://img.shields.io/badge/Version-0.7.1-green)](../../CHANGELOG.md) +[![Version](https://img.shields.io/badge/Version-0.10.0-green)](../../CHANGELOG.md) [![Status](https://img.shields.io/badge/Status-Active_Development-brightgreen)](../../CHANGELOG.md) @@ -182,10 +180,13 @@ Want to run overnight? See [Ralph Mode](#ralph-autonomous-mode). ```bash # Check task status -flowctl show fn-1.2 --json | jq '.status' +flowctl show fn-1.2 --json + +# Reset to todo (from done/blocked) +flowctl task reset fn-1.2 -# Mark as pending to retry -flowctl task set fn-1.2 --status pending +# Reset + dependents in same epic +flowctl task reset fn-1.2 --cascade ``` ### Clean up `.flow/` safely @@ -222,6 +223,19 @@ cat scripts/ralph/runs/*/receipts/impl-fn-1.1.json # Must have: {"type":"impl_review","id":"fn-1.1",...} ``` +### Custom rp-cli instructions conflicting + +> **Caution**: If you have custom instructions for `rp-cli` in your `CLAUDE.md` or `AGENTS.md`, they may conflict with Flow-Next's RepoPrompt integration. + +Flow-Next's plan-review and impl-review skills include specific instructions for `rp-cli` usage (window selection, builder workflow, chat commands). Custom rp-cli instructions can override these and cause unexpected behavior. + +**Symptoms:** +- Reviews not using the correct RepoPrompt window +- Builder not selecting expected files +- Chat commands failing or behaving differently + +**Fix:** Remove or comment out custom rp-cli instructions from your `CLAUDE.md`/`AGENTS.md` when using Flow-Next reviews. The plugin provides complete rp-cli guidance. + --- ## Uninstall @@ -266,7 +280,7 @@ Ralph writes run artifacts under `scripts/ralph/runs/`, including review receipt Autonomous coding agents are taking the industry by storm—loop until done, commit, repeat. Most solutions gate progress by tests and linting alone. Ralph goes further. -**Multi-model review gates**: Ralph uses [RepoPrompt](https://repoprompt.com/?atp=KJbuL4) (macOS) or OpenCode review (cross-platform) to send plan and implementation reviews to a *different* model. A second set of eyes catches blind spots that self-review misses. RepoPrompt's builder provides full file context; OpenCode uses context hints from changed files. +**Multi-model review gates**: Ralph uses [RepoPrompt](https://repoprompt.com/?atp=KJbuL4) (macOS) or OpenAI Codex CLI (cross-platform) to send plan and implementation reviews to a *different* model. A second set of eyes catches blind spots that self-review misses. RepoPrompt's builder provides full file context; Codex uses context hints from changed files. **Review loops until Ship**: Reviews don't just flag issues—they block progress until resolved. Ralph runs fix → re-review cycles until the reviewer returns `SHIP`. No "LGTM with nits" that get ignored. @@ -280,7 +294,7 @@ The result: code that's been reviewed by two models, tested, linted, and iterati ### Controlling Ralph -External agents (CI, scripts, supervisors) can pause/resume/stop Ralph runs without killing processes. +External agents (Clawdbot, GitHub Actions, etc.) can pause/resume/stop Ralph runs without killing processes. **CLI commands:** ```bash @@ -308,7 +322,7 @@ rm scripts/ralph/runs//PAUSE touch scripts/ralph/runs//STOP ``` -Ralph checks sentinels at iteration boundaries (before work selection and after the model returns). +Ralph checks sentinels at iteration boundaries (after Claude returns, before next iteration). **Task retry/rollback:** ```bash @@ -410,6 +424,17 @@ Everything is bundled: - No external services - Just Python 3 +### Bundled Skills + +Utility skills available during planning and implementation: + +| Skill | Use Case | +|-------|----------| +| `browser` | Web automation via agent-browser CLI (verify UI, scrape docs, test flows) | +| `flow-next-rp-explorer` | Token-efficient codebase exploration via RepoPrompt | +| `flow-next-worktree-kit` | Git worktree management for parallel work | +| `flow-next-export-context` | Export context for external LLM review | + ### Non-invasive - No daemons diff --git a/plugins/flow-next/docs/flowctl.md b/plugins/flow-next/docs/flowctl.md index 9b65b78..f3afa1d 100644 --- a/plugins/flow-next/docs/flowctl.md +++ b/plugins/flow-next/docs/flowctl.md @@ -7,7 +7,7 @@ CLI for `.flow/` task tracking. Agents must use flowctl for all writes. ## Available Commands ``` -init, detect, status, epic, task, dep, show, epics, tasks, list, cat, ready, next, start, done, block, validate, ralph, config, memory, prep-chat, rp, codex +init, detect, epic, task, dep, show, epics, tasks, list, cat, ready, next, start, done, block, validate, config, memory, prep-chat, rp, codex, checkpoint, status ``` ## Multi-User Safety @@ -71,24 +71,6 @@ Output: {"success": true, "exists": true, "valid": true, "path": "/repo/.flow"} ``` -### status - -Show `.flow/` state and active Ralph runs. - -```bash -flowctl status [--json] -``` - -Example output: -``` -Flow status: - Epics: open=2, done=1 - Tasks: todo=3, in_progress=1, blocked=0, done=4 - -Active Ralph runs: - ralph-20260115T000303Z-... (iteration 3, task fn-1-abc.2) [running] -``` - ### epic create Create new epic. @@ -126,22 +108,6 @@ Set epic branch_name. flowctl epic set-branch fn-1 --branch "fn-1-epic" [--json] ``` -### epic add-dep - -Add an epic-level dependency. - -```bash -flowctl epic add-dep fn-2 fn-1 [--json] -``` - -### epic rm-dep - -Remove an epic-level dependency. - -```bash -flowctl epic rm-dep fn-2 fn-1 [--json] -``` - ### epic close Close epic (requires all tasks done). @@ -179,15 +145,26 @@ Set task acceptance section. flowctl task set-acceptance fn-1.2 --file accept.md [--json] ``` +### task set-spec + +Set description and acceptance in one call (fewer writes). + +```bash +flowctl task set-spec fn-1.2 --description desc.md --acceptance accept.md [--json] +``` + +Both `--description` and `--acceptance` are optional; supply one or both. + ### task reset -Reset a completed/blocked task back to `todo`. +Reset task to `todo` status, clearing assignee and completion data. ```bash -flowctl task reset fn-1.2 [--json] -flowctl task reset fn-1.2 --cascade # also reset dependent tasks (same epic) +flowctl task reset fn-1.2 [--cascade] [--json] ``` +Use `--cascade` to also reset dependent tasks within the same epic. + ### dep add Add dependency to task. @@ -387,21 +364,6 @@ Checks: Exits with code 1 if validation fails (for CI use). -### ralph - -Control active Ralph runs. - -```bash -# Pause/resume/stop (auto-detect if single run) -flowctl ralph pause -flowctl ralph resume -flowctl ralph stop -flowctl ralph status - -# Specify run when multiple active -flowctl ralph pause --run -``` - ### config Manage project configuration stored in `.flow/config.json`. @@ -413,7 +375,7 @@ flowctl config get review.backend [--json] # Set a config value flowctl config set memory.enabled true [--json] -flowctl config set review.backend opencode [--json] # opencode, rp, or none +flowctl config set review.backend codex [--json] # rp, codex, or none # Toggle boolean config flowctl config toggle memory.enabled [--json] @@ -424,7 +386,7 @@ flowctl config toggle memory.enabled [--json] | Key | Type | Default | Description | |-----|------|---------|-------------| | `memory.enabled` | bool | `false` | Enable memory system | -| `review.backend` | string | auto | Default review backend (`opencode`, `rp`, `none`) | +| `review.backend` | string | auto | Default review backend (`rp`, `codex`, `none`) | Auto-detect priority: `FLOW_REVIEW_BACKEND` env → config → available CLI. @@ -564,6 +526,38 @@ References: src/middleware.py:45 (calls authenticate), tests/test_auth.py:12 **Session continuity:** Receipt includes `session_id` (thread_id from codex). Subsequent reviews read the existing receipt and resume the conversation, maintaining full context across fix → re-review cycles. +### checkpoint + +Save and restore epic state (used during review-fix cycles). + +```bash +# Save epic state to .flow/.checkpoint-fn-1.json +flowctl checkpoint save --epic fn-1 [--json] + +# Restore epic state from checkpoint +flowctl checkpoint restore --epic fn-1 [--json] + +# Delete checkpoint +flowctl checkpoint delete --epic fn-1 [--json] +``` + +Checkpoints preserve full epic + task state. Useful when compaction occurs during plan-review cycles. + +### status + +Show `.flow/` state summary. + +```bash +flowctl status [--json] +``` + +Output: +```json +{"success": true, "epic_count": 2, "task_count": 5, "done_count": 2, "active_runs": []} +``` + +Human-readable output shows epic/task counts and any active Ralph runs. + ## Ralph Receipts Review receipts are **not** managed by flowctl. They are written by the review skills when `REVIEW_RECEIPT_PATH` is set (Ralph sets this env var). diff --git a/plugins/flow-next/docs/ralph.md b/plugins/flow-next/docs/ralph.md index 73cbaa4..9994c92 100644 --- a/plugins/flow-next/docs/ralph.md +++ b/plugins/flow-next/docs/ralph.md @@ -6,18 +6,18 @@ Ralph is Flow-Next's repo-local autonomous harness. It loops over tasks, applies ## Quick Start -### Step 1: Setup (inside OpenCode) +### Step 1: Setup (inside Claude) -Run the init skill from OpenCode: +Run the init skill from Claude Code: ```bash /flow-next:ralph-init ``` -Or run setup from terminal without entering OpenCode: +Or run setup from terminal without entering Claude: ```bash -opencode run "/flow-next:ralph-init" +claude -p "/flow-next:ralph-init" ``` This scaffolds `scripts/ralph/` with: @@ -26,31 +26,15 @@ This scaffolds `scripts/ralph/` with: - `config.env` — all settings - `runs/` — artifacts and logs -### Step 1.5: Configure (edit config.env + opencode.json) +### Step 1.5: Configure (edit config.env) Before running, set your review backends in `scripts/ralph/config.env`: ```bash -PLAN_REVIEW=opencode # or: rp, none -WORK_REVIEW=opencode # or: rp, none +PLAN_REVIEW=codex # or: rp, none +WORK_REVIEW=codex # or: rp, none ``` -Recommended config.env baseline: - -```bash -PLAN_REVIEW=opencode -WORK_REVIEW=opencode -REQUIRE_PLAN_REVIEW=1 -BRANCH_MODE=new -YOLO=1 -FLOW_RALPH_OPENCODE_MODEL=openai/gpt-5.2-codex -FLOW_RALPH_OPENCODE_VARIANT=medium -FLOW_RALPH_OPENCODE_AGENT=ralph-runner -FLOW_RALPH_REVIEWER_AGENT=opencode-reviewer -``` - -Then ensure your OpenCode agents are configured in `.opencode/opencode.json` (see [OpenCode Integration](#opencode-integration)). - See [Configuration](#configuration) for all options. ### Step 1.75: Test First (Recommended) @@ -61,13 +45,13 @@ scripts/ralph/ralph_once.sh Runs ONE iteration then exits. Observe the output before committing to a full run. -### Step 2: Run (outside OpenCode) +### Step 2: Run (outside Claude) ```bash scripts/ralph/ralph.sh ``` -Ralph spawns OpenCode runs via `opencode run`, loops until done, and applies review gates. +Ralph spawns Claude sessions via `claude -p`, loops until done, and applies review gates. **Watch mode** - see what's happening in real-time: ```bash @@ -101,14 +85,14 @@ rm -rf scripts/ralph/ ## How It Works -Ralph wraps OpenCode in a shell loop with quality gates: +Ralph wraps Claude Code in a shell loop with quality gates: ``` ┌─────────────────────────────────────────────────────────┐ │ scripts/ralph/ralph.sh │ │ ┌──────────────────────────────────────────────────┐ │ │ │ while flowctl next returns work: │ │ -│ │ 1. opencode run "/flow-next:plan" or :work │ │ +│ │ 1. claude -p "/flow-next:plan" or :work │ │ │ │ 2. check review receipts │ │ │ │ 3. if missing/invalid → retry │ │ │ │ 4. if SHIP verdict → next task │ │ @@ -142,7 +126,7 @@ Anthropic's official ralph-wiggum plugin uses a Stop hook to keep Claude in the | Aspect | ralph-wiggum | Flow-Next Ralph | |--------|--------------|-----------------| | **Session model** | Single session, accumulating context | Fresh context per iteration | -| **Loop mechanism** | Stop hook re-feeds prompt in SAME session | External bash loop, new `opencode run` each iteration | +| **Loop mechanism** | Stop hook re-feeds prompt in SAME session | External bash loop, new `claude -p` each iteration | | **Context management** | Transcript grows, context fills up | Clean slate every time | | **Failed attempts** | Pollute future iterations | Gone with the session | | **Re-anchoring** | None | Re-reads epic/task spec EVERY iteration | @@ -154,8 +138,8 @@ Anthropic's official ralph-wiggum plugin uses a Stop hook to keep Claude in the **The Core Problem with ralph-wiggum** 1. **Context pollution** - Every failed attempt stays in context, potentially misleading future iterations -2. **No re-anchoring** - As context fills, the model loses sight of the original task spec -3. **Single model** - No external validation; the model grades its own homework +2. **No re-anchoring** - As context fills, Claude loses sight of the original task spec +3. **Single model** - No external validation; Claude grades its own homework 4. **Binary outcome** - Either completion promise triggers, or you hit max iterations **Flow-Next's Solution** @@ -175,11 +159,11 @@ Ralph enforces quality through three mechanisms: Reviews use a second model to verify code. Two models catch what one misses. **Review backends:** -- `opencode` — OpenCode reviewer subagent (default) -- `rp` — [RepoPrompt](https://repoprompt.com/?atp=KJbuL4) (macOS only, GUI-based) +- `rp` — [RepoPrompt](https://repoprompt.com/?atp=KJbuL4) (macOS only, GUI-based) **← recommended** +- `codex` — OpenAI Codex CLI (cross-platform, terminal-based) - `none` — skip reviews (not recommended for production) -**We recommend RepoPrompt** when available. Its Builder provides full file context with intelligent selection. OpenCode reviews are zero-install and use your configured model and effort. +**We recommend RepoPrompt** when available. Its Builder provides full file context with intelligent selection, while Codex uses heuristic context hints from changed files. Both use the same Carmack-level review criteria. - Plan reviews verify architecture and edge cases before coding starts - Impl reviews verify the implementation meets spec after each task @@ -189,7 +173,7 @@ Reviews use a second model to verify code. Two models catch what one misses. Every review must produce a receipt JSON proving it ran: ```json -{"type":"impl_review","id":"fn-1.1","mode":"opencode","timestamp":"2026-01-09T..."} +{"type":"impl_review","id":"fn-1.1","mode":"rp","timestamp":"2026-01-09T..."} ``` No receipt = no progress. Ralph retries until receipt exists. @@ -228,49 +212,6 @@ This builds a project-specific knowledge base of things reviewers catch that mod --- -## Controlling Ralph - -External agents (CI, scripts, supervisors) can pause/resume/stop Ralph runs without killing processes. - -**CLI commands:** -```bash -# Check status -flowctl status # Epic/task counts + active runs -flowctl status --json # JSON for automation - -# Control active run -flowctl ralph pause # Pause run (auto-detects if single) -flowctl ralph resume # Resume paused run -flowctl ralph stop # Request graceful stop -flowctl ralph status # Show run state - -# Specify run when multiple active -flowctl ralph pause --run -``` - -**Sentinel files (manual control):** -```bash -# Pause: touch PAUSE file in run directory -touch scripts/ralph/runs//PAUSE -# Resume: remove PAUSE file -rm scripts/ralph/runs//PAUSE -# Stop: touch STOP file (kept for audit) -touch scripts/ralph/runs//STOP -``` - -Ralph checks sentinels at iteration boundaries (before work selection and after the model returns). - -**Task retry/rollback:** -```bash -# Reset completed/blocked task to todo -flowctl task reset fn-1-abc.3 - -# Reset + cascade to dependent tasks (same epic) -flowctl task reset fn-1-abc.2 --cascade -``` - ---- - ## Configuration Edit `scripts/ralph/config.env`: @@ -279,12 +220,12 @@ Edit `scripts/ralph/config.env`: | Variable | Values | Description | |----------|--------|-------------| -| `PLAN_REVIEW` | `opencode`, `rp`, `none` | How to review plans | -| `WORK_REVIEW` | `opencode`, `rp`, `none` | How to review implementations | +| `PLAN_REVIEW` | `rp`, `codex`, `none` | How to review plans | +| `WORK_REVIEW` | `rp`, `codex`, `none` | How to review implementations | | `REQUIRE_PLAN_REVIEW` | `1`, `0` | Block work until plan review passes | -- `opencode` — OpenCode reviewer subagent (default) - `rp` — RepoPrompt (macOS, requires GUI) +- `codex` — OpenAI Codex CLI (cross-platform, terminal-based) - `none` — skip reviews ### Branch Settings @@ -304,6 +245,7 @@ With `BRANCH_MODE=new`, all epics work on the same run branch. Commits are prefi | Variable | Default | Description | |----------|---------|-------------| | `MAX_ITERATIONS` | `25` | Total loop iterations | +| `MAX_TURNS` | (empty) | Claude turns per iteration (empty = unlimited) | | `MAX_ATTEMPTS_PER_TASK` | `5` | Retries before auto-blocking task | ### Scope @@ -316,9 +258,9 @@ With `BRANCH_MODE=new`, all epics work on the same run branch. Commits are prefi | Variable | Default | Effect | |----------|---------|--------| -| `YOLO` | `1` | Sets `OPENCODE_PERMISSION='{\"*\":\"allow\"}'` for unattended runs | +| `YOLO` | `1` | Passes `--dangerously-skip-permissions` to Claude | -Note: OpenCode still enforces permissions in headless runs. `YOLO=1` (the default) is required for truly unattended loops. Set `YOLO=0` for interactive testing. +Note: `-p` mode is headless but still prompts for file/command permissions. `YOLO=1` (the default) is required for truly unattended runs. Set `YOLO=0` for interactive testing. ### Display @@ -326,24 +268,6 @@ Note: OpenCode still enforces permissions in headless runs. `YOLO=1` (the defaul |----------|---------|-------------| | `RALPH_UI` | `1` | Colored/emoji output (0 = plain) | -### OpenCode settings - -| Variable | Example | Description | -|----------|---------|-------------| -| `FLOW_RALPH_OPENCODE_MODEL` | `openai/gpt-5.2-codex` | Override model for Ralph runs (runner) | -| `FLOW_RALPH_OPENCODE_VARIANT` | `medium` | Provider-specific variant/effort | -| `FLOW_RALPH_OPENCODE_AGENT` | `ralph-runner` | Subagent name to run | -| `FLOW_RALPH_REVIEWER_AGENT` | `opencode-reviewer` | Reviewer subagent name | -| `OPENCODE_BIN` | `/usr/local/bin/opencode` | Override OpenCode CLI path | - -**Default template (current):** -``` -FLOW_RALPH_OPENCODE_MODEL=openai/gpt-5.2-codex -FLOW_RALPH_OPENCODE_VARIANT=medium -``` - -Reviewer model + reasoning effort are configured in `.opencode/opencode.json` (see [OpenCode Integration](#opencode-integration)). - --- ## Run Artifacts @@ -352,7 +276,7 @@ Each run creates: ``` scripts/ralph/runs// - ├── iter-001.log # Raw OpenCode output + ├── iter-001.log # Raw Claude output ├── iter-002.log ├── progress.txt # Append-only run log ├── attempts.json # Per-task retry counts @@ -381,63 +305,27 @@ Never call `rp-cli` directly in Ralph mode. --- -## OpenCode Integration +## Codex Integration -When `PLAN_REVIEW=opencode` or `WORK_REVIEW=opencode`, Ralph uses the OpenCode reviewer subagent via: +When `PLAN_REVIEW=codex` or `WORK_REVIEW=codex`, Ralph uses `flowctl codex` wrappers: ```bash -/flow-next:plan-review --review=opencode -/flow-next:impl-review --review=opencode +flowctl codex check # Verify codex available +flowctl codex impl-review ... # Run implementation review +flowctl codex plan-review ... # Run plan review ``` **Requirements:** -- OpenCode CLI installed and available as `opencode` -- Reviewer agent configured in `.opencode/opencode.json` (default: `opencode-reviewer`) - -### Agent configuration (opencode.json) - -Add or update these agents in `.opencode/opencode.json`: - -```json -{ - "agent": { - "ralph-runner": { - "description": "Autonomous Ralph runner (tool-first, no chit-chat)", - "mode": "primary", - "model": "openai/gpt-5.2-codex", - "reasoningEffort": "medium", - "prompt": "You are an autonomous workflow executor. Always use tools to perform actions. Never respond with status-only text. Do not ask questions. If you cannot proceed or a required tool fails, output exactly: RETRY and stop." - }, - "opencode-reviewer": { - "description": "Carmack-level reviewer for plans and implementations", - "model": "openai/gpt-5.2", - "reasoningEffort": "high", - "prompt": "You are a strict reviewer. Focus on correctness, completeness, feasibility, risks, and testability. Provide issues with severity and concrete fixes. End with exactly one verdict tag: SHIP or NEEDS_WORK or MAJOR_RETHINK." - } - } -} -``` - -You can swap models or reasoning effort here for both the runner and reviewer. - -**Model override (runner):** For Ralph runs you can override the runner model via `config.env`: - -```bash -FLOW_RALPH_OPENCODE_MODEL=openai/gpt-5.2-codex -FLOW_RALPH_OPENCODE_VARIANT=medium -``` - -**Session continuity:** Re-reviews reuse the same OpenCode subagent session. The review skills capture the `session_id` and pass it back on re-review. - -### OpenCode logs +- OpenAI Codex CLI installed and authenticated (`npm install -g @openai/codex && codex auth`) -OpenCode logs are stored at: +**Model:** Default is `gpt-5.2` with high reasoning (GPT 5.2 High). Override with `FLOW_CODEX_MODEL` env var. -``` -~/.local/share/opencode/log/ -``` +**Advantages over rp:** +- Cross-platform (Windows, Linux, macOS) +- Terminal-based (no GUI required) +- Session continuity via thread_id -Use these when debugging long waits or retries. +**Session continuity:** Codex reviews store `thread_id` in receipts. Subsequent reviews in the same run continue the conversation. --- @@ -467,15 +355,16 @@ After `MAX_ATTEMPTS_PER_TASK` failures, Ralph: Ensure rp-cli is installed and RepoPrompt window is open on your repo. Alternatives: -- Use OpenCode instead: set `PLAN_REVIEW=opencode` and `WORK_REVIEW=opencode` +- Use Codex instead: set `PLAN_REVIEW=codex` and `WORK_REVIEW=codex` - Skip reviews: set `PLAN_REVIEW=none` and `WORK_REVIEW=none` -### OpenCode not found +### Codex not found -Ensure OpenCode CLI is installed and available in PATH: +Ensure Codex CLI is installed and authenticated: ```bash -opencode --help +npm install -g @openai/codex +codex auth ``` Or switch to RepoPrompt: set `PLAN_REVIEW=rp` and `WORK_REVIEW=rp`. @@ -492,6 +381,20 @@ scripts/ralph/ralph_once.sh Runs one loop iteration, then exits. Good for verifying setup. +### Sandbox mode (recommended for unattended runs) + +Run Ralph inside Docker sandbox for extra isolation: + +```bash +# From your project directory +docker sandbox run claude "scripts/ralph/ralph.sh" + +# Or specify workspace explicitly +docker sandbox run -w ~/my-project claude "scripts/ralph/ralph.sh" +``` + +See [Docker sandbox docs](https://docs.docker.com/ai/sandboxes/claude-code/) for details. + ### Watch mode ```bash @@ -499,7 +402,7 @@ scripts/ralph/ralph.sh --watch # Stream tool calls in real-time scripts/ralph/ralph.sh --watch verbose # Also stream model responses ``` -Watch mode shows you what OpenCode is doing without blocking autonomy. Tool calls display with icons and colors. Logs still captured to `runs//iter-*.log`. +Watch mode shows you what Claude is doing without blocking autonomy. Tool calls display with icons and colors. Logs still captured to `runs//iter-*.log`. ### Verbose logging @@ -512,10 +415,9 @@ Appends detailed logs to `scripts/ralph/runs//ralph.log`. ### Debug environment variables ```bash -FLOW_RALPH_OPENCODE_MODEL=openai/gpt-5.2 -FLOW_RALPH_OPENCODE_VARIANT=high -FLOW_RALPH_OPENCODE_AGENT=ralph-runner -OPENCODE_BIN=/path/to/opencode +FLOW_RALPH_CLAUDE_MODEL=claude-opus-4-5-20251101 # Force model +FLOW_RALPH_CLAUDE_DEBUG=hooks # Debug hooks +FLOW_RALPH_CLAUDE_PERMISSION_MODE=bypassPermissions ``` --- @@ -532,23 +434,23 @@ Ralph includes plugin hooks that enforce workflow rules deterministically. |------|-----| | No `--json` on chat-send | Preserves review text output (rp) | | No `--new-chat` on re-reviews | First review creates chat, subsequent stay in same (rp) | -| Require `flowctl done` summary+evidence | Enforces audit trail for impl tasks | -| Receipt must exist before completion | Blocks progress without review proof | -| Track OpenCode review verdicts | Validates review completed successfully | -| Block direct `rp-cli` | Enforce wrapper usage for consistency | +| Receipt must exist before Stop | Blocks Claude from stopping without writing receipt | +| Required flags on setup/select-add | Ensures proper window/tab targeting (rp) | +| Track codex review verdicts | Validates codex review completed successfully | ### Hook location ``` -.opencode/plugin/ - flow-next-ralph-guard.ts # OpenCode hook logic +plugins/flow-next/ + hooks/hooks.json # Hook config + scripts/hooks/ralph-guard.py # Guard logic ``` ### Disabling hooks -Temporarily: unset `FLOW_RALPH`. +Temporarily: unset `FLOW_RALPH` or remove `hooks/hooks.json`. -Permanently: delete `.opencode/plugin/flow-next-ralph-guard.ts`. +Permanently: delete `hooks/` directory and remove `"hooks"` from `plugin.json`. --- diff --git a/plugins/flow-next/scripts/flowctl.py b/plugins/flow-next/scripts/flowctl.py index 7f1b3ab..169cccd 100755 --- a/plugins/flow-next/scripts/flowctl.py +++ b/plugins/flow-next/scripts/flowctl.py @@ -333,6 +333,19 @@ def read_text_or_exit(path: Path, what: str, use_json: bool = True) -> str: error_exit(f"{what} unreadable: {path} ({e})", use_json=use_json) +def read_file_or_stdin(file_arg: str, what: str, use_json: bool = True) -> str: + """Read from file path or stdin if file_arg is '-'. + + Supports heredoc usage: flowctl ... --file - <<'EOF' + """ + if file_arg == "-": + try: + return sys.stdin.read() + except Exception as e: + error_exit(f"Failed to read {what} from stdin: {e}", use_json=use_json) + return read_text_or_exit(Path(file_arg), what, use_json=use_json) + + def generate_epic_suffix(length: int = 3) -> str: """Generate random alphanumeric suffix for epic IDs (a-z0-9).""" alphabet = string.ascii_lowercase + string.digits @@ -695,17 +708,21 @@ def run_codex_exec( If session_id provided, tries to resume. Falls back to new session if resume fails. Model: FLOW_CODEX_MODEL env > parameter > default (gpt-5.2 + high reasoning). + + Note: Prompt is passed via stdin (using '-') to avoid Windows command-line + length limits (~8191 chars) and special character escaping issues. (GH-35) """ codex = require_codex() # Model priority: env > parameter > default (gpt-5.2 + high reasoning = GPT 5.2 High) effective_model = os.environ.get("FLOW_CODEX_MODEL") or model or "gpt-5.2" if session_id: - # Try resume first (model already set in original session) - cmd = [codex, "exec", "resume", session_id, prompt] + # Try resume first - use stdin for prompt (model already set in original session) + cmd = [codex, "exec", "resume", session_id, "-"] try: result = subprocess.run( cmd, + input=prompt, capture_output=True, text=True, check=True, @@ -720,6 +737,7 @@ def run_codex_exec( # New session with model + high reasoning effort # --skip-git-repo-check: safe with read-only sandbox, allows reviews from /tmp etc (GH-33) + # Use '-' to read prompt from stdin - avoids Windows CLI length limits (GH-35) cmd = [ codex, "exec", @@ -731,11 +749,12 @@ def run_codex_exec( sandbox, "--skip-git-repo-check", "--json", - prompt, + "-", ] try: result = subprocess.run( cmd, + input=prompt, capture_output=True, text=True, check=True, @@ -1372,6 +1391,9 @@ def cmd_status(args: argparse.Namespace) -> None: if tasks_dir.exists(): for task_file in tasks_dir.glob("fn-*.json"): + # Skip non-task files (must have . before .json) + if "." not in task_file.stem: + continue try: task_data = load_json(task_file) status = task_data.get("status", "todo") @@ -1380,49 +1402,60 @@ def cmd_status(args: argparse.Namespace) -> None: except Exception: pass + # Get active runs active_runs = find_active_runs() if args.json: json_output( { + "success": True, "flow_exists": flow_exists, "epics": epic_counts, "tasks": task_counts, - "active_runs": active_runs, + "runs": [ + { + "id": r["id"], + "iteration": r["iteration"], + "current_epic": r["current_epic"], + "current_task": r["current_task"], + "paused": r["paused"], + "stopped": r["stopped"], + } + for r in active_runs + ], } ) else: - if flow_exists: - print("Flow status:") - print(f" Epics: open={epic_counts['open']}, done={epic_counts['done']}") + if not flow_exists: + print(".flow/ not initialized") + else: + total_epics = sum(epic_counts.values()) + total_tasks = sum(task_counts.values()) + print(f"Epics: {epic_counts['open']} open, {epic_counts['done']} done") print( - " Tasks: todo={todo}, in_progress={in_progress}, blocked={blocked}, done={done}".format( - **task_counts - ) + f"Tasks: {task_counts['todo']} todo, {task_counts['in_progress']} in_progress, " + f"{task_counts['done']} done, {task_counts['blocked']} blocked" ) - else: - print(".flow/ does not exist") + print() if active_runs: - print("\nActive Ralph runs:") - for run in active_runs: + print("Active runs:") + for r in active_runs: state = [] - if run.get("paused"): + if r["paused"]: state.append("PAUSED") - if run.get("stopped"): + if r["stopped"]: state.append("STOPPED") state_str = f" [{', '.join(state)}]" if state else "" task_info = "" - if run.get("current_task"): - task_info = f", task {run['current_task']}" - elif run.get("current_epic"): - task_info = f", epic {run['current_epic']}" - iter_info = ( - f"iteration {run['iteration']}" if run.get("iteration") else "starting" - ) - print(f" {run['id']} ({iter_info}{task_info}){state_str}") + if r["current_task"]: + task_info = f", working on {r['current_task']}" + elif r["current_epic"]: + task_info = f", epic {r['current_epic']}" + iter_info = f"iteration {r['iteration']}" if r["iteration"] else "starting" + print(f" {r['id']} ({iter_info}{task_info}){state_str}") else: - print("\nNo active runs") + print("No active runs") def cmd_ralph_pause(args: argparse.Namespace) -> None: @@ -2387,8 +2420,8 @@ def cmd_epic_set_plan(args: argparse.Namespace) -> None: if not epic_path.exists(): error_exit(f"Epic {args.id} not found", use_json=args.json) - # Read content from file - content = read_text_or_exit(Path(args.file), "Input file", use_json=args.json) + # Read content from file or stdin + content = read_file_or_stdin(args.file, "Input file", use_json=args.json) # Write spec spec_path = flow_dir / SPECS_DIR / f"{args.id}.md" @@ -2623,6 +2656,85 @@ def cmd_task_set_acceptance(args: argparse.Namespace) -> None: _task_set_section(args.id, "## Acceptance", args.file, args.json) +def cmd_task_set_spec(args: argparse.Namespace) -> None: + """Set task description and/or acceptance in one call. + + Reduces tool calls: instead of separate set-description + set-acceptance, + both can be set atomically with a single JSON timestamp update. + """ + if not ensure_flow_exists(): + error_exit( + ".flow/ does not exist. Run 'flowctl init' first.", use_json=args.json + ) + + task_id = args.id + if not is_task_id(task_id): + error_exit( + f"Invalid task ID: {task_id}. Expected format: fn-N.M or fn-N-xxx.M", + use_json=args.json, + ) + + # Need at least one of description or acceptance + if not args.description and not args.acceptance: + error_exit( + "At least one of --description or --acceptance required", + use_json=args.json, + ) + + flow_dir = get_flow_dir() + task_json_path = flow_dir / TASKS_DIR / f"{task_id}.json" + task_spec_path = flow_dir / TASKS_DIR / f"{task_id}.md" + + # Verify task exists + if not task_json_path.exists(): + error_exit(f"Task {task_id} not found", use_json=args.json) + + # Load task JSON first (fail early) + task_data = load_json_or_exit(task_json_path, f"Task {task_id}", use_json=args.json) + + # Read current spec + current_spec = read_text_or_exit( + task_spec_path, f"Task {task_id} spec", use_json=args.json + ) + + updated_spec = current_spec + sections_updated = [] + + # Apply description if provided + if args.description: + desc_content = read_file_or_stdin(args.description, "Description file", use_json=args.json) + try: + updated_spec = patch_task_section(updated_spec, "## Description", desc_content) + sections_updated.append("## Description") + except ValueError as e: + error_exit(str(e), use_json=args.json) + + # Apply acceptance if provided + if args.acceptance: + acc_content = read_file_or_stdin(args.acceptance, "Acceptance file", use_json=args.json) + try: + updated_spec = patch_task_section(updated_spec, "## Acceptance", acc_content) + sections_updated.append("## Acceptance") + except ValueError as e: + error_exit(str(e), use_json=args.json) + + # Single atomic write for spec, single for JSON + atomic_write(task_spec_path, updated_spec) + task_data["updated_at"] = now_iso() + atomic_write_json(task_json_path, task_data) + + if args.json: + json_output( + { + "id": task_id, + "sections": sections_updated, + "message": f"Task {task_id} updated: {', '.join(sections_updated)}", + } + ) + else: + print(f"Task {task_id} updated: {', '.join(sections_updated)}") + + def cmd_task_reset(args: argparse.Namespace) -> None: """Reset task status to todo.""" if not ensure_flow_exists(): @@ -2750,8 +2862,8 @@ def _task_set_section( if not task_json_path.exists(): error_exit(f"Task {task_id} not found", use_json=use_json) - # Read new content - new_content = read_text_or_exit(Path(file_path), "Input file", use_json=use_json) + # Read new content from file or stdin + new_content = read_file_or_stdin(file_path, "Input file", use_json=use_json) # Load task JSON first (fail early before any writes) task_data = load_json_or_exit(task_json_path, f"Task {task_id}", use_json=use_json) @@ -4108,6 +4220,198 @@ def cmd_codex_plan_review(args: argparse.Namespace) -> None: print(f"\nVERDICT={verdict or 'UNKNOWN'}") +# --- Checkpoint commands --- + + +def cmd_checkpoint_save(args: argparse.Namespace) -> None: + """Save full epic + tasks state to checkpoint file. + + Creates .flow/.checkpoint-fn-N.json with complete state snapshot. + Use before plan-review or other long operations to enable recovery + if context compaction occurs. + """ + if not ensure_flow_exists(): + error_exit( + ".flow/ does not exist. Run 'flowctl init' first.", use_json=args.json + ) + + epic_id = args.epic + if not is_epic_id(epic_id): + error_exit( + f"Invalid epic ID: {epic_id}. Expected format: fn-N or fn-N-xxx", + use_json=args.json, + ) + + flow_dir = get_flow_dir() + epic_path = flow_dir / EPICS_DIR / f"{epic_id}.json" + spec_path = flow_dir / SPECS_DIR / f"{epic_id}.md" + + if not epic_path.exists(): + error_exit(f"Epic {epic_id} not found", use_json=args.json) + + # Load epic data + epic_data = load_json_or_exit(epic_path, f"Epic {epic_id}", use_json=args.json) + + # Load epic spec + epic_spec = "" + if spec_path.exists(): + epic_spec = spec_path.read_text(encoding="utf-8") + + # Load all tasks for this epic + tasks_dir = flow_dir / TASKS_DIR + tasks = [] + if tasks_dir.exists(): + for task_file in sorted(tasks_dir.glob(f"{epic_id}.*.json")): + task_data = load_json(task_file) + task_spec_path = tasks_dir / f"{task_file.stem}.md" + task_spec = "" + if task_spec_path.exists(): + task_spec = task_spec_path.read_text(encoding="utf-8") + tasks.append({ + "id": task_file.stem, + "data": task_data, + "spec": task_spec, + }) + + # Build checkpoint + checkpoint = { + "schema_version": 1, + "created_at": now_iso(), + "epic_id": epic_id, + "epic": { + "data": epic_data, + "spec": epic_spec, + }, + "tasks": tasks, + } + + # Write checkpoint + checkpoint_path = flow_dir / f".checkpoint-{epic_id}.json" + atomic_write_json(checkpoint_path, checkpoint) + + if args.json: + json_output({ + "epic_id": epic_id, + "checkpoint_path": str(checkpoint_path), + "task_count": len(tasks), + "message": f"Checkpoint saved: {checkpoint_path}", + }) + else: + print(f"Checkpoint saved: {checkpoint_path} ({len(tasks)} tasks)") + + +def cmd_checkpoint_restore(args: argparse.Namespace) -> None: + """Restore epic + tasks state from checkpoint file. + + Reads .flow/.checkpoint-fn-N.json and overwrites current state. + Use to recover after context compaction or to rollback changes. + """ + if not ensure_flow_exists(): + error_exit( + ".flow/ does not exist. Run 'flowctl init' first.", use_json=args.json + ) + + epic_id = args.epic + if not is_epic_id(epic_id): + error_exit( + f"Invalid epic ID: {epic_id}. Expected format: fn-N or fn-N-xxx", + use_json=args.json, + ) + + flow_dir = get_flow_dir() + checkpoint_path = flow_dir / f".checkpoint-{epic_id}.json" + + if not checkpoint_path.exists(): + error_exit(f"No checkpoint found for {epic_id}", use_json=args.json) + + # Load checkpoint + checkpoint = load_json_or_exit( + checkpoint_path, f"Checkpoint {epic_id}", use_json=args.json + ) + + # Validate checkpoint structure + if "epic" not in checkpoint or "tasks" not in checkpoint: + error_exit("Invalid checkpoint format", use_json=args.json) + + # Restore epic + epic_path = flow_dir / EPICS_DIR / f"{epic_id}.json" + spec_path = flow_dir / SPECS_DIR / f"{epic_id}.md" + + epic_data = checkpoint["epic"]["data"] + epic_data["updated_at"] = now_iso() + atomic_write_json(epic_path, epic_data) + + if checkpoint["epic"]["spec"]: + atomic_write(spec_path, checkpoint["epic"]["spec"]) + + # Restore tasks + tasks_dir = flow_dir / TASKS_DIR + restored_tasks = [] + for task in checkpoint["tasks"]: + task_id = task["id"] + task_json_path = tasks_dir / f"{task_id}.json" + task_spec_path = tasks_dir / f"{task_id}.md" + + task_data = task["data"] + task_data["updated_at"] = now_iso() + atomic_write_json(task_json_path, task_data) + + if task["spec"]: + atomic_write(task_spec_path, task["spec"]) + restored_tasks.append(task_id) + + if args.json: + json_output({ + "epic_id": epic_id, + "checkpoint_created_at": checkpoint.get("created_at"), + "tasks_restored": restored_tasks, + "message": f"Restored {epic_id} from checkpoint ({len(restored_tasks)} tasks)", + }) + else: + print(f"Restored {epic_id} from checkpoint ({len(restored_tasks)} tasks)") + print(f"Checkpoint was created at: {checkpoint.get('created_at', 'unknown')}") + + +def cmd_checkpoint_delete(args: argparse.Namespace) -> None: + """Delete checkpoint file for an epic.""" + if not ensure_flow_exists(): + error_exit( + ".flow/ does not exist. Run 'flowctl init' first.", use_json=args.json + ) + + epic_id = args.epic + if not is_epic_id(epic_id): + error_exit( + f"Invalid epic ID: {epic_id}. Expected format: fn-N or fn-N-xxx", + use_json=args.json, + ) + + flow_dir = get_flow_dir() + checkpoint_path = flow_dir / f".checkpoint-{epic_id}.json" + + if not checkpoint_path.exists(): + if args.json: + json_output({ + "epic_id": epic_id, + "deleted": False, + "message": f"No checkpoint found for {epic_id}", + }) + else: + print(f"No checkpoint found for {epic_id}") + return + + checkpoint_path.unlink() + + if args.json: + json_output({ + "epic_id": epic_id, + "deleted": True, + "message": f"Deleted checkpoint for {epic_id}", + }) + else: + print(f"Deleted checkpoint for {epic_id}") + + def cmd_validate(args: argparse.Namespace) -> None: """Validate epic structure or all epics.""" if not ensure_flow_exists(): @@ -4254,7 +4558,7 @@ def main() -> None: p_detect.set_defaults(func=cmd_detect) # status - p_status = subparsers.add_parser("status", help="Show .flow state + Ralph runs") + p_status = subparsers.add_parser("status", help="Show .flow state and active runs") p_status.add_argument("--json", action="store_true", help="JSON output") p_status.set_defaults(func=cmd_status) @@ -4317,7 +4621,7 @@ def main() -> None: p_epic_set_plan = epic_sub.add_parser("set-plan", help="Set epic spec from file") p_epic_set_plan.add_argument("id", help="Epic ID (fn-N)") - p_epic_set_plan.add_argument("--file", required=True, help="Markdown file") + p_epic_set_plan.add_argument("--file", required=True, help="Markdown file (use '-' for stdin)") p_epic_set_plan.add_argument("--json", action="store_true", help="JSON output") p_epic_set_plan.set_defaults(func=cmd_epic_set_plan) @@ -4376,16 +4680,29 @@ def main() -> None: p_task_desc = task_sub.add_parser("set-description", help="Set task description") p_task_desc.add_argument("id", help="Task ID (fn-N.M)") - p_task_desc.add_argument("--file", required=True, help="Markdown file") + p_task_desc.add_argument("--file", required=True, help="Markdown file (use '-' for stdin)") p_task_desc.add_argument("--json", action="store_true", help="JSON output") p_task_desc.set_defaults(func=cmd_task_set_description) p_task_acc = task_sub.add_parser("set-acceptance", help="Set task acceptance") p_task_acc.add_argument("id", help="Task ID (fn-N.M)") - p_task_acc.add_argument("--file", required=True, help="Markdown file") + p_task_acc.add_argument("--file", required=True, help="Markdown file (use '-' for stdin)") p_task_acc.add_argument("--json", action="store_true", help="JSON output") p_task_acc.set_defaults(func=cmd_task_set_acceptance) + p_task_set_spec = task_sub.add_parser( + "set-spec", help="Set description and/or acceptance in one call" + ) + p_task_set_spec.add_argument("id", help="Task ID (fn-N.M)") + p_task_set_spec.add_argument( + "--description", help="Description file (use '-' for stdin)" + ) + p_task_set_spec.add_argument( + "--acceptance", help="Acceptance file (use '-' for stdin)" + ) + p_task_set_spec.add_argument("--json", action="store_true", help="JSON output") + p_task_set_spec.set_defaults(func=cmd_task_set_spec) + p_task_reset = task_sub.add_parser("reset", help="Reset task to todo") p_task_reset.add_argument("task_id", help="Task ID (fn-N.M)") p_task_reset.add_argument( @@ -4492,6 +4809,31 @@ def main() -> None: p_validate.add_argument("--json", action="store_true", help="JSON output") p_validate.set_defaults(func=cmd_validate) + # checkpoint + p_checkpoint = subparsers.add_parser("checkpoint", help="Checkpoint commands") + checkpoint_sub = p_checkpoint.add_subparsers(dest="checkpoint_cmd", required=True) + + p_checkpoint_save = checkpoint_sub.add_parser( + "save", help="Save epic state to checkpoint" + ) + p_checkpoint_save.add_argument("--epic", required=True, help="Epic ID (fn-N)") + p_checkpoint_save.add_argument("--json", action="store_true", help="JSON output") + p_checkpoint_save.set_defaults(func=cmd_checkpoint_save) + + p_checkpoint_restore = checkpoint_sub.add_parser( + "restore", help="Restore epic state from checkpoint" + ) + p_checkpoint_restore.add_argument("--epic", required=True, help="Epic ID (fn-N)") + p_checkpoint_restore.add_argument("--json", action="store_true", help="JSON output") + p_checkpoint_restore.set_defaults(func=cmd_checkpoint_restore) + + p_checkpoint_delete = checkpoint_sub.add_parser( + "delete", help="Delete checkpoint for epic" + ) + p_checkpoint_delete.add_argument("--epic", required=True, help="Epic ID (fn-N)") + p_checkpoint_delete.add_argument("--json", action="store_true", help="JSON output") + p_checkpoint_delete.set_defaults(func=cmd_checkpoint_delete) + # prep-chat (for rp-cli chat_send JSON escaping) p_prep = subparsers.add_parser( "prep-chat", help="Prepare JSON for rp-cli chat_send" diff --git a/plugins/flow-next/scripts/ralph_smoke_test.sh b/plugins/flow-next/scripts/ralph_smoke_test.sh index 3f4cfe2..38065ee 100755 --- a/plugins/flow-next/scripts/ralph_smoke_test.sh +++ b/plugins/flow-next/scripts/ralph_smoke_test.sh @@ -5,6 +5,19 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" PLUGIN_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" TEST_DIR="/tmp/ralph-smoke-$$" +# Python detection: prefer python3, fallback to python (Windows support, GH-35) +pick_python() { + if [[ -n "${PYTHON_BIN:-}" ]]; then + command -v "$PYTHON_BIN" >/dev/null 2>&1 && { echo "$PYTHON_BIN"; return; } + fi + if command -v python3 >/dev/null 2>&1; then echo "python3"; return; fi + if command -v python >/dev/null 2>&1; then echo "python"; return; fi + echo "" +} + +PYTHON_BIN="$(pick_python)" +[[ -n "$PYTHON_BIN" ]] || { echo "ERROR: python not found (need python3 or python in PATH)" >&2; exit 1; } + # Safety: never run tests from the main plugin repo if [[ -f "$PWD/.claude-plugin/marketplace.json" ]] || [[ -f "$PWD/plugins/flow-next/.claude-plugin/plugin.json" ]]; then echo "ERROR: refusing to run from main plugin repo. Run from any other directory." >&2 @@ -54,7 +67,7 @@ write_config() { local max_iter="$5" local max_turns="$6" local max_attempts="$7" - python3 - <<'PY' "$plan" "$work" "$require" "$branch" "$max_iter" "$max_turns" "$max_attempts" + "$PYTHON_BIN" - <<'PY' "$plan" "$work" "$require" "$branch" "$max_iter" "$max_turns" "$max_attempts" from pathlib import Path import re, sys plan, work, require, branch, max_iter, max_turns, max_attempts = sys.argv[1:8] @@ -76,7 +89,7 @@ PY # Extract epic/task ID from JSON output extract_id() { - python3 -c "import json,sys; print(json.load(sys.stdin)['id'])" + "$PYTHON_BIN" -c "import json,sys; print(json.load(sys.stdin)['id'])" } scaffold @@ -195,7 +208,7 @@ TASK2_2="$(echo "$TASK2_2_JSON" | extract_id)" # Use rp for both to test receipt generation (none skips receipts correctly via fix for #8) write_config "rp" "rp" "1" "new" "6" "5" "2" STUB_MODE=success STUB_WRITE_RECEIPT=1 CLAUDE_BIN="$TEST_DIR/bin/claude" scripts/ralph/ralph.sh >/dev/null -python3 - </dev/null -python3 - </dev/null 2>&1 rc=$? set -e -python3 - </dev/null 2>&1 rc=$? set -e -python3 - </dev/null 2>&1 && { echo "$PYTHON_BIN"; return; } + fi + if command -v python3 >/dev/null 2>&1; then echo "python3"; return; fi + if command -v python >/dev/null 2>&1; then echo "python"; return; fi + echo "" +} + +PYTHON_BIN="$(pick_python)" +[[ -n "$PYTHON_BIN" ]] || { echo "ERROR: python not found (need python3 or python in PATH)" >&2; exit 1; } + # Safety: never run tests from the main plugin repo if [[ -f "$PWD/.claude-plugin/marketplace.json" ]] || [[ -f "$PWD/plugins/flow-next/.claude-plugin/plugin.json" ]]; then echo "ERROR: refusing to run from main plugin repo. Run from any other directory." >&2 @@ -41,12 +54,12 @@ printf "ok\n" > "$TEST_DIR/summary.md" echo -e "${YELLOW}--- next: plan/work/none + priority ---${NC}" # Capture epic ID from create output (fn-N-xxx format) EPIC1_JSON="$(scripts/flowctl epic create --title "Epic One" --json)" -EPIC1="$(echo "$EPIC1_JSON" | python3 -c 'import json,sys; print(json.load(sys.stdin)["id"])')" +EPIC1="$(echo "$EPIC1_JSON" | "$PYTHON_BIN" -c 'import json,sys; print(json.load(sys.stdin)["id"])')" scripts/flowctl task create --epic "$EPIC1" --title "Low pri" --priority 5 --json >/dev/null scripts/flowctl task create --epic "$EPIC1" --title "High pri" --priority 1 --json >/dev/null plan_json="$(scripts/flowctl next --require-plan-review --json)" -python3 - "$plan_json" "$EPIC1" <<'PY' +"$PYTHON_BIN" - "$plan_json" "$EPIC1" <<'PY' import json, sys data = json.loads(sys.argv[1]) expected_epic = sys.argv[2] @@ -58,7 +71,7 @@ PASS=$((PASS + 1)) scripts/flowctl epic set-plan-review-status "$EPIC1" --status ship --json >/dev/null work_json="$(scripts/flowctl next --json)" -python3 - "$work_json" "$EPIC1" <<'PY' +"$PYTHON_BIN" - "$work_json" "$EPIC1" <<'PY' import json, sys data = json.loads(sys.argv[1]) expected_epic = sys.argv[2] @@ -73,7 +86,7 @@ scripts/flowctl done "${EPIC1}.2" --summary-file "$TEST_DIR/summary.md" --eviden scripts/flowctl start "${EPIC1}.1" --json >/dev/null scripts/flowctl done "${EPIC1}.1" --summary-file "$TEST_DIR/summary.md" --evidence-json "$TEST_DIR/evidence.json" --json >/dev/null none_json="$(scripts/flowctl next --json)" -python3 - <<'PY' "$none_json" +"$PYTHON_BIN" - <<'PY' "$none_json" import json, sys data = json.loads(sys.argv[1]) assert data["status"] == "none" @@ -154,7 +167,7 @@ fi rm -f ".flow/tasks/${EPIC1}.1-evidence.json" ".flow/tasks/${EPIC1}.1-summary.json" echo -e "${YELLOW}--- plan_review_status default ---${NC}" -python3 - "$EPIC1" <<'PY' +"$PYTHON_BIN" - "$EPIC1" <<'PY' import json, sys from pathlib import Path epic_id = sys.argv[1] @@ -166,7 +179,7 @@ data.pop("branch_name", None) path.write_text(json.dumps(data, indent=2, sort_keys=True) + "\n") PY show_json="$(scripts/flowctl show "$EPIC1" --json)" -python3 - <<'PY' "$show_json" +"$PYTHON_BIN" - <<'PY' "$show_json" import json, sys data = json.loads(sys.argv[1]) assert data.get("plan_review_status") == "unknown" @@ -179,7 +192,7 @@ PASS=$((PASS + 1)) echo -e "${YELLOW}--- branch_name set ---${NC}" scripts/flowctl epic set-branch "$EPIC1" --branch "${EPIC1}-epic" --json >/dev/null show_json="$(scripts/flowctl show "$EPIC1" --json)" -python3 - "$show_json" "$EPIC1" <<'PY' +"$PYTHON_BIN" - "$show_json" "$EPIC1" <<'PY' import json, sys data = json.loads(sys.argv[1]) expected_branch = f"{sys.argv[2]}-epic" @@ -190,7 +203,7 @@ PASS=$((PASS + 1)) echo -e "${YELLOW}--- block + validate + epic close ---${NC}" EPIC2_JSON="$(scripts/flowctl epic create --title "Epic Two" --json)" -EPIC2="$(echo "$EPIC2_JSON" | python3 -c 'import json,sys; print(json.load(sys.stdin)["id"])')" +EPIC2="$(echo "$EPIC2_JSON" | "$PYTHON_BIN" -c 'import json,sys; print(json.load(sys.stdin)["id"])')" scripts/flowctl task create --epic "$EPIC2" --title "Block me" --json >/dev/null scripts/flowctl task create --epic "$EPIC2" --title "Other" --json >/dev/null printf "Blocked by test\n" > "$TEST_DIR/reason.md" @@ -222,7 +235,7 @@ PASS=$((PASS + 1)) echo -e "${YELLOW}--- config set/get ---${NC}" scripts/flowctl config set memory.enabled true --json >/dev/null config_json="$(scripts/flowctl config get memory.enabled --json)" -python3 - <<'PY' "$config_json" +"$PYTHON_BIN" - <<'PY' "$config_json" import json, sys data = json.loads(sys.argv[1]) assert data["value"] == True, f"Expected True, got {data['value']}" @@ -232,7 +245,7 @@ PASS=$((PASS + 1)) scripts/flowctl config set memory.enabled false --json >/dev/null config_json="$(scripts/flowctl config get memory.enabled --json)" -python3 - <<'PY' "$config_json" +"$PYTHON_BIN" - <<'PY' "$config_json" import json, sys data = json.loads(sys.argv[1]) assert data["value"] == False, f"Expected False, got {data['value']}" @@ -263,7 +276,7 @@ fi scripts/flowctl memory add --type convention "Test convention" --json >/dev/null scripts/flowctl memory add --type decision "Test decision" --json >/dev/null list_json="$(scripts/flowctl memory list --json)" -python3 - <<'PY' "$list_json" +"$PYTHON_BIN" - <<'PY' "$list_json" import json, sys data = json.loads(sys.argv[1]) assert data["success"] == True @@ -277,7 +290,7 @@ echo -e "${GREEN}✓${NC} memory list" PASS=$((PASS + 1)) echo -e "${YELLOW}--- schema v1 validate ---${NC}" -python3 - <<'PY' +"$PYTHON_BIN" - <<'PY' import json from pathlib import Path path = Path(".flow/meta.json") @@ -292,7 +305,7 @@ PASS=$((PASS + 1)) echo -e "${YELLOW}--- codex commands ---${NC}" # Test codex check (may or may not have codex installed) codex_check_json="$(scripts/flowctl codex check --json 2>/dev/null || echo '{"success":true}')" -python3 - <<'PY' "$codex_check_json" +"$PYTHON_BIN" - <<'PY' "$codex_check_json" import json, sys data = json.loads(sys.argv[1]) assert data["success"] == True, f"codex check failed: {data}" @@ -369,7 +382,7 @@ git -C "$TEST_DIR/repo" commit -m "Update auth with expiry" >/dev/null # Test context hints: should find handler.py referencing validate_token/User cd "$TEST_DIR/repo" -hints_output="$(PYTHONPATH="$SCRIPT_DIR" python3 -c " +hints_output="$(PYTHONPATH="$SCRIPT_DIR" "$PYTHON_BIN" -c " from flowctl import gather_context_hints hints = gather_context_hints('HEAD~1') print(hints) @@ -388,7 +401,7 @@ echo -e "${YELLOW}--- build_review_prompt ---${NC}" # Go back to plugin root for Python tests cd "$TEST_DIR/repo" # Test that build_review_prompt generates proper structure -python3 - "$SCRIPT_DIR" <<'PY' +"$PYTHON_BIN" - "$SCRIPT_DIR" <<'PY' import sys sys.path.insert(0, sys.argv[1]) from flowctl import build_review_prompt @@ -430,7 +443,7 @@ PASS=$((PASS + 1)) echo -e "${YELLOW}--- parse_receipt_path ---${NC}" # Test receipt path parsing for Ralph gating (both legacy and new fn-N-xxx formats) -python3 - "$SCRIPT_DIR/hooks" <<'PY' +"$PYTHON_BIN" - "$SCRIPT_DIR/hooks" <<'PY' import sys hooks_dir = sys.argv[1] sys.path.insert(0, hooks_dir) @@ -469,11 +482,11 @@ PASS=$((PASS + 1)) echo -e "${YELLOW}--- codex e2e (requires codex CLI) ---${NC}" # Check if codex is available (handles its own auth) -codex_available="$(scripts/flowctl codex check --json 2>/dev/null | python3 -c "import sys,json; print(json.load(sys.stdin).get('available', False))" 2>/dev/null || echo "False")" +codex_available="$(scripts/flowctl codex check --json 2>/dev/null | "$PYTHON_BIN" -c "import sys,json; print(json.load(sys.stdin).get('available', False))" 2>/dev/null || echo "False")" if [[ "$codex_available" == "True" ]]; then # Create a simple epic + task for testing EPIC3_JSON="$(scripts/flowctl epic create --title "Codex test epic" --json)" - EPIC3="$(echo "$EPIC3_JSON" | python3 -c 'import json,sys; print(json.load(sys.stdin)["id"])')" + EPIC3="$(echo "$EPIC3_JSON" | "$PYTHON_BIN" -c 'import json,sys; print(json.load(sys.stdin)["id"])')" scripts/flowctl task create --epic "$EPIC3" --title "Test task" --json >/dev/null # Write a simple spec @@ -505,7 +518,7 @@ EOF if [[ "$plan_rc" -eq 0 ]]; then # Verify receipt was written with correct schema if [[ -f "$TEST_DIR/plan-receipt.json" ]]; then - python3 - "$TEST_DIR/plan-receipt.json" "$EPIC3" <<'PY' + "$PYTHON_BIN" - "$TEST_DIR/plan-receipt.json" "$EPIC3" <<'PY' import sys, json from pathlib import Path data = json.loads(Path(sys.argv[1]).read_text()) @@ -543,7 +556,7 @@ EOF if [[ "$impl_rc" -eq 0 ]]; then # Verify receipt was written with correct schema if [[ -f "$TEST_DIR/impl-receipt.json" ]]; then - python3 - "$TEST_DIR/impl-receipt.json" "$EPIC3" <<'PY' + "$PYTHON_BIN" - "$TEST_DIR/impl-receipt.json" "$EPIC3" <<'PY' import sys, json from pathlib import Path data = json.loads(Path(sys.argv[1]).read_text()) @@ -572,11 +585,11 @@ echo -e "${YELLOW}--- depends_on_epics gate ---${NC}" cd "$TEST_DIR/repo" # Back to test repo # Create epics and capture their IDs DEP_BASE_JSON="$(scripts/flowctl epic create --title "Dep base" --json)" -DEP_BASE_ID="$(echo "$DEP_BASE_JSON" | python3 -c 'import json,sys; print(json.load(sys.stdin)["id"])')" +DEP_BASE_ID="$(echo "$DEP_BASE_JSON" | "$PYTHON_BIN" -c 'import json,sys; print(json.load(sys.stdin)["id"])')" scripts/flowctl task create --epic "$DEP_BASE_ID" --title "Base task" --json >/dev/null DEP_CHILD_JSON="$(scripts/flowctl epic create --title "Dep child" --json)" -DEP_CHILD_ID="$(echo "$DEP_CHILD_JSON" | python3 -c 'import json,sys; print(json.load(sys.stdin)["id"])')" -python3 - "$DEP_CHILD_ID" "$DEP_BASE_ID" <<'PY' +DEP_CHILD_ID="$(echo "$DEP_CHILD_JSON" | "$PYTHON_BIN" -c 'import json,sys; print(json.load(sys.stdin)["id"])')" +"$PYTHON_BIN" - "$DEP_CHILD_ID" "$DEP_BASE_ID" <<'PY' import json, sys from pathlib import Path child_id, base_id = sys.argv[1], sys.argv[2] @@ -587,7 +600,7 @@ path.write_text(json.dumps(data, indent=2, sort_keys=True) + "\n") PY printf '{"epics":["%s"]}\n' "$DEP_CHILD_ID" > "$TEST_DIR/epics.json" blocked_json="$(scripts/flowctl next --epics-file "$TEST_DIR/epics.json" --json)" -python3 - "$DEP_CHILD_ID" "$blocked_json" <<'PY' +"$PYTHON_BIN" - "$DEP_CHILD_ID" "$blocked_json" <<'PY' import json, sys child_id = sys.argv[1] data = json.loads(sys.argv[2]) @@ -598,6 +611,61 @@ PY echo -e "${GREEN}✓${NC} depends_on_epics blocks" PASS=$((PASS + 1)) +echo -e "${YELLOW}--- stdin support ---${NC}" +cd "$TEST_DIR/repo" +STDIN_EPIC_JSON="$(scripts/flowctl epic create --title "Stdin test" --json)" +STDIN_EPIC="$(echo "$STDIN_EPIC_JSON" | "$PYTHON_BIN" -c 'import json,sys; print(json.load(sys.stdin)["id"])')" +# Test epic set-plan with stdin +scripts/flowctl epic set-plan "$STDIN_EPIC" --file - --json <<'EOF' +# Stdin Test Plan + +## Overview +Testing stdin support for set-plan. + +## Acceptance +- Works via stdin +EOF +# Verify content was written +spec_content="$(scripts/flowctl cat "$STDIN_EPIC")" +echo "$spec_content" | grep -q "Testing stdin support" || { echo "stdin set-plan failed"; FAIL=$((FAIL + 1)); } +echo -e "${GREEN}✓${NC} stdin epic set-plan" +PASS=$((PASS + 1)) + +echo -e "${YELLOW}--- task set-spec combined ---${NC}" +scripts/flowctl task create --epic "$STDIN_EPIC" --title "Set-spec test" --json >/dev/null +SETSPEC_TASK="${STDIN_EPIC}.1" +# Write temp files for combined set-spec +echo 'This is the description.' > "$TEST_DIR/desc.md" +echo '- [ ] Check 1 +- [ ] Check 2' > "$TEST_DIR/acc.md" +scripts/flowctl task set-spec "$SETSPEC_TASK" --description "$TEST_DIR/desc.md" --acceptance "$TEST_DIR/acc.md" --json >/dev/null +# Verify both sections were written +task_spec="$(scripts/flowctl cat "$SETSPEC_TASK")" +echo "$task_spec" | grep -q "This is the description" || { echo "set-spec description failed"; FAIL=$((FAIL + 1)); } +echo "$task_spec" | grep -q "Check 1" || { echo "set-spec acceptance failed"; FAIL=$((FAIL + 1)); } +echo -e "${GREEN}✓${NC} task set-spec combined" +PASS=$((PASS + 1)) + +echo -e "${YELLOW}--- checkpoint save/restore ---${NC}" +# Save checkpoint +scripts/flowctl checkpoint save --epic "$STDIN_EPIC" --json >/dev/null +# Verify checkpoint file exists +[[ -f ".flow/.checkpoint-${STDIN_EPIC}.json" ]] || { echo "checkpoint file not created"; FAIL=$((FAIL + 1)); } +# Modify epic spec +scripts/flowctl epic set-plan "$STDIN_EPIC" --file - --json <<'EOF' +# Modified content +EOF +# Restore from checkpoint +scripts/flowctl checkpoint restore --epic "$STDIN_EPIC" --json >/dev/null +# Verify original content restored +restored_spec="$(scripts/flowctl cat "$STDIN_EPIC")" +echo "$restored_spec" | grep -q "Testing stdin support" || { echo "checkpoint restore failed"; FAIL=$((FAIL + 1)); } +# Delete checkpoint +scripts/flowctl checkpoint delete --epic "$STDIN_EPIC" --json >/dev/null +[[ ! -f ".flow/.checkpoint-${STDIN_EPIC}.json" ]] || { echo "checkpoint delete failed"; FAIL=$((FAIL + 1)); } +echo -e "${GREEN}✓${NC} checkpoint save/restore/delete" +PASS=$((PASS + 1)) + echo "" echo -e "${YELLOW}=== Results ===${NC}" echo -e "Passed: ${GREEN}$PASS${NC}" diff --git a/plugins/flow-next/skills/flow-next-interview/SKILL.md b/plugins/flow-next/skills/flow-next-interview/SKILL.md index 28b54c4..8d0a771 100644 --- a/plugins/flow-next/skills/flow-next-interview/SKILL.md +++ b/plugins/flow-next/skills/flow-next-interview/SKILL.md @@ -84,42 +84,52 @@ After interview complete, write everything back. ### For Flow Epic ID -1. Create a temp file with the refined epic spec including: - - Clear problem statement - - Technical approach with specifics - - Key decisions made during interview - - Edge cases to handle - - Quick commands section (required) - - Acceptance criteria - -2. Update epic spec: - ```bash - $FLOWCTL epic set-plan --file --json - ``` - -3. Create/update tasks if interview revealed breakdown: - ```bash - $FLOWCTL task create --epic --title "..." --json - $FLOWCTL task set-description --file --json - $FLOWCTL task set-acceptance --file --json - ``` +Update epic spec using stdin heredoc (preferred) or temp file: +```bash +# Preferred: stdin heredoc (no temp file) +$FLOWCTL epic set-plan --file - --json <<'EOF' +# Epic Title -### For Flow Task ID +## Problem +Clear problem statement + +## Approach +Technical approach with specifics, key decisions from interview + +## Edge Cases +- Edge case 1 +- Edge case 2 -1. Write description to temp file with: - - Clear task description - - Technical details from interview - - Edge cases +## Quick commands +```bash +# smoke test command +``` -2. Write acceptance to temp file with: - - Checkboxes for acceptance criteria - - Specific, testable conditions +## Acceptance +- [ ] Criterion 1 +- [ ] Criterion 2 +EOF +``` -3. Update task: - ```bash - $FLOWCTL task set-description --file --json - $FLOWCTL task set-acceptance --file --json - ``` +Create/update tasks if interview revealed breakdown: +```bash +$FLOWCTL task create --epic --title "..." --json +# Use set-spec for combined description + acceptance (fewer writes) +$FLOWCTL task set-spec --description /tmp/desc.md --acceptance /tmp/acc.md --json +``` + +### For Flow Task ID + +Update task using combined set-spec (preferred) or separate calls: +```bash +# Preferred: combined set-spec (2 writes instead of 4) +$FLOWCTL task set-spec --description /tmp/desc.md --acceptance /tmp/acc.md --json + +# Or use stdin for description only: +$FLOWCTL task set-description --file - --json <<'EOF' +Clear task description with technical details and edge cases from interview +EOF +``` ### For File Path diff --git a/plugins/flow-next/skills/flow-next-plan-review/SKILL.md b/plugins/flow-next/skills/flow-next-plan-review/SKILL.md index d3feaf9..edda096 100644 --- a/plugins/flow-next/skills/flow-next-plan-review/SKILL.md +++ b/plugins/flow-next/skills/flow-next-plan-review/SKILL.md @@ -125,6 +125,9 @@ Run backend detection from SKILL.md above. Then branch: EPIC_ID="${1:-}" RECEIPT_PATH="${REVIEW_RECEIPT_PATH:-/tmp/plan-review-receipt.json}" +# Save checkpoint before review (recovery point if context compacts) +$FLOWCTL checkpoint save --epic "$EPIC_ID" --json + $FLOWCTL codex plan-review "$EPIC_ID" --receipt "$RECEIPT_PATH" # Output includes VERDICT=SHIP|NEEDS_WORK|MAJOR_RETHINK ``` @@ -138,6 +141,9 @@ On NEEDS_WORK: fix plan via `$FLOWCTL epic set-plan`, then re-run (receipt enabl $FLOWCTL show --json $FLOWCTL cat +# Save checkpoint before review (recovery point if context compacts) +$FLOWCTL checkpoint save --epic --json + # Step 2: Atomic setup eval "$($FLOWCTL rp setup-review --repo-root "$REPO_ROOT" --summary "Review plan for : ")" # Outputs W= T=. If fails → RETRY @@ -158,10 +164,24 @@ $FLOWCTL epic set-plan-review-status --status ship --json If verdict is NEEDS_WORK, loop internally until SHIP: 1. **Parse issues** from reviewer feedback -2. **Fix plan** via `$FLOWCTL epic set-plan --file /tmp/updated-plan.md` +2. **Fix plan** (stdin preferred, temp file if content has single quotes): + ```bash + # Preferred: stdin heredoc + $FLOWCTL epic set-plan --file - --json <<'EOF' + + EOF + + # Or temp file + $FLOWCTL epic set-plan --file /tmp/updated-plan.md --json + ``` 3. **Re-review**: - **Codex**: Re-run `flowctl codex plan-review` (receipt enables context) - **RP**: `$FLOWCTL rp chat-send --window "$W" --tab "$T" --message-file /tmp/re-review.md` (NO `--new-chat`) 4. **Repeat** until `SHIP` +**Recovery**: If context compaction occurred during review, restore from checkpoint: +```bash +$FLOWCTL checkpoint restore --epic --json +``` + **CRITICAL**: For RP, re-reviews must stay in the SAME chat so reviewer has context. Only use `--new-chat` on the FIRST review. diff --git a/plugins/flow-next/skills/flow-next-plan-review/workflow.md b/plugins/flow-next/skills/flow-next-plan-review/workflow.md index 2587d49..6697908 100644 --- a/plugins/flow-next/skills/flow-next-plan-review/workflow.md +++ b/plugins/flow-next/skills/flow-next-plan-review/workflow.md @@ -47,10 +47,17 @@ echo "Review backend: $BACKEND" Use when `BACKEND="codex"`. -### Step 1: Execute Review +### Step 0: Save Checkpoint +**Before review** (protects against context compaction): ```bash EPIC_ID="${1:-}" +$FLOWCTL checkpoint save --epic "$EPIC_ID" --json +``` + +### Step 1: Execute Review + +```bash RECEIPT_PATH="${REVIEW_RECEIPT_PATH:-/tmp/plan-review-receipt.json}" $FLOWCTL codex plan-review "$EPIC_ID" --receipt "$RECEIPT_PATH" @@ -115,6 +122,12 @@ $FLOWCTL cat Save output for inclusion in review prompt. Compose a 1-2 sentence summary for the setup-review command. +**Save checkpoint** (protects against context compaction during review): +```bash +$FLOWCTL checkpoint save --epic --json +``` +This creates `.flow/.checkpoint-.json` with full state. If compaction occurs during review-fix cycles, restore with `$FLOWCTL checkpoint restore --epic `. + --- ## Phase 2: Augment Selection (RP) @@ -240,13 +253,24 @@ If no verdict tag, output `RETRY` and stop. If verdict is NEEDS_WORK: 1. **Parse issues** - Extract ALL issues by severity (Critical → Major → Minor) -2. **Fix the plan** - Address each issue. Write updated plan to temp file. +2. **Fix the plan** - Address each issue. 3. **Update plan in flowctl** (MANDATORY before re-review): ```bash + # Option A: stdin heredoc (preferred, no temp file) + $FLOWCTL epic set-plan --file - --json <<'EOF' + + EOF + + # Option B: temp file (if content has single quotes) $FLOWCTL epic set-plan --file /tmp/updated-plan.md --json ``` **If you skip this step and re-review with same content, reviewer will return NEEDS_WORK again.** + **Recovery**: If context compaction occurred, restore from checkpoint first: + ```bash + $FLOWCTL checkpoint restore --epic --json + ``` + 4. **Re-review with fix summary** (only AFTER step 3): **IMPORTANT**: Do NOT re-add files already in the selection. RepoPrompt auto-refreshes diff --git a/plugins/flow-next/skills/flow-next-plan/steps.md b/plugins/flow-next/skills/flow-next-plan/steps.md index 43c2dc1..56b6ecd 100644 --- a/plugins/flow-next/skills/flow-next-plan/steps.md +++ b/plugins/flow-next/skills/flow-next-plan/steps.md @@ -98,18 +98,25 @@ Default to short unless complexity demands more. ## Step 4: Write to .flow +**Efficiency note**: Use stdin (`--file -`) with heredocs to avoid temp files. Use `task set-spec` to set description + acceptance in one call. + **Route A - Input was an existing Flow ID**: 1. If epic ID (fn-N): - - Write a temp file with the updated plan spec - - `$FLOWCTL epic set-plan --file --json` + ```bash + # Use stdin heredoc (no temp file needed) + $FLOWCTL epic set-plan --file - --json <<'EOF' + + EOF + ``` - Create/update child tasks as needed 2. If task ID (fn-N.M): - - Write temp file for description - - `$FLOWCTL task set-description --file --json` - - Write temp file for acceptance - - `$FLOWCTL task set-acceptance --file --json` + ```bash + # Combined set-spec: description + acceptance in one call + # Write to temp files only if content has single quotes + $FLOWCTL task set-spec --description /tmp/desc.md --acceptance /tmp/acc.md --json + ``` **Route B - Input was text (new idea)**: @@ -126,10 +133,24 @@ Default to short unless complexity demands more. ``` - If user specified a branch, use that instead. -3. Write epic spec: - - Create a temp file with the full plan/spec content - - Include: Overview, Scope, Approach, Quick commands (REQUIRED - at least one smoke test command), Acceptance, References - - `$FLOWCTL epic set-plan --file --json` +3. Write epic spec (use stdin heredoc): + ```bash + # Include: Overview, Scope, Approach, Quick commands (REQUIRED), Acceptance, References + $FLOWCTL epic set-plan --file - --json <<'EOF' + # Epic Title + + ## Overview + ... + + ## Quick commands + ```bash + # At least one smoke test command + ``` + + ## Acceptance + ... + EOF + ``` 4. Create child tasks: ```bash @@ -137,10 +158,13 @@ Default to short unless complexity demands more. $FLOWCTL task create --epic --title "" --json ``` -5. Write task specs: - - For each task, write description and acceptance to temp files - - `$FLOWCTL task set-description --file --json` - - `$FLOWCTL task set-acceptance --file --json` +5. Write task specs (use combined set-spec): + ```bash + # For each task - single call sets both sections + # Write description and acceptance to temp files, then: + $FLOWCTL task set-spec --description /tmp/desc.md --acceptance /tmp/acc.md --json + ``` + This reduces 4 atomic writes per task to 2. 6. Add dependencies: ```bash diff --git a/plugins/flow-next/skills/flow-next-ralph-init/templates/config.env b/plugins/flow-next/skills/flow-next-ralph-init/templates/config.env index 7ce1583..b507866 100644 --- a/plugins/flow-next/skills/flow-next-ralph-init/templates/config.env +++ b/plugins/flow-next/skills/flow-next-ralph-init/templates/config.env @@ -5,11 +5,11 @@ EPICS= # Plan gate REQUIRE_PLAN_REVIEW=0 -# PLAN_REVIEW options: rp (RepoPrompt, macOS), opencode (cross-platform), none +# PLAN_REVIEW options: rp (RepoPrompt, macOS), codex (cross-platform), none PLAN_REVIEW={{PLAN_REVIEW}} # Work gate -# WORK_REVIEW options: rp (RepoPrompt, macOS), opencode (cross-platform), none +# WORK_REVIEW options: rp (RepoPrompt, macOS), codex (cross-platform), none WORK_REVIEW={{WORK_REVIEW}} # Work settings @@ -18,18 +18,20 @@ MAX_ITERATIONS=25 # MAX_TURNS= # optional; empty = no limit (Claude stops via promise tags) MAX_ATTEMPTS_PER_TASK=5 -# YOLO sets OPENCODE_PERMISSION='{"*":"allow"}' (required for unattended runs) +# YOLO uses --dangerously-skip-permissions (required for unattended runs) YOLO=1 # UI settings # RALPH_UI=0 # set to 0 to disable colored output -# OpenCode options -# FLOW_RALPH_OPENCODE_MODEL=openai/gpt-5.2 -# FLOW_RALPH_OPENCODE_VARIANT=high -# FLOW_RALPH_OPENCODE_AGENT=ralph-runner -# FLOW_RALPH_REVIEWER_AGENT=opencode-reviewer -# OPENCODE_BIN=/usr/local/bin/opencode +# Optional Claude flags (only used if set) +# FLOW_RALPH_CLAUDE_MODEL=claude-opus-4-5-20251101 +# FLOW_RALPH_CLAUDE_SESSION_ID= +# FLOW_RALPH_CLAUDE_PERMISSION_MODE=bypassPermissions +# FLOW_RALPH_CLAUDE_NO_SESSION_PERSISTENCE=0 +# FLOW_RALPH_CLAUDE_DEBUG=hooks +# FLOW_RALPH_CLAUDE_VERBOSE=1 +# FLOW_RALPH_CLAUDE_PLUGIN_DIR= # Use local dev plugin instead of cached (for testing) # Watch mode (command-line flags, not env vars): # --watch Show tool calls in real-time (logs still captured) diff --git a/plugins/flow-next/skills/flow-next-ralph-init/templates/prompt_plan.md b/plugins/flow-next/skills/flow-next-ralph-init/templates/prompt_plan.md index b5ef061..fb39807 100644 --- a/plugins/flow-next/skills/flow-next-ralph-init/templates/prompt_plan.md +++ b/plugins/flow-next/skills/flow-next-ralph-init/templates/prompt_plan.md @@ -5,9 +5,6 @@ Inputs: - PLAN_REVIEW={{PLAN_REVIEW}} - REQUIRE_PLAN_REVIEW={{REQUIRE_PLAN_REVIEW}} -Treat the following as the user's exact input to flow-next-plan-review: -`{{EPIC_ID}} --review={{PLAN_REVIEW}}` - Steps: 1) Re-anchor: - scripts/ralph/flowctl show {{EPIC_ID}} --json @@ -17,18 +14,14 @@ Steps: Ralph mode rules (must follow): - If PLAN_REVIEW=rp: use `flowctl rp` wrappers (setup-review, select-add, prompt-get, chat-send). -- If PLAN_REVIEW=opencode: use the task tool with subagent_type `opencode-reviewer`. -- Write receipt via bash heredoc (no Write tool) if REVIEW_RECEIPT_PATH is set. -- Do NOT run /flow-next:* as shell commands. +- If PLAN_REVIEW=codex: use `flowctl codex` wrappers (plan-review with --receipt). +- Write receipt via bash heredoc (no Write tool) if `REVIEW_RECEIPT_PATH` set. - If any rule is violated, output `RETRY` and stop. 2) Plan review gate: - - Call the skill tool: flow-next-plan-review. - - Follow the workflow in the skill using the exact arguments above. - - Do NOT stop after loading the skill. - - For opencode: run reviewer via task tool and require `` tag. - - For rp: use flowctl rp wrappers (no --json, no --new-chat on re-review). - - For export: follow export flow in skill. + - If PLAN_REVIEW=rp: run `/flow-next:plan-review {{EPIC_ID}} --review=rp` + - If PLAN_REVIEW=codex: run `/flow-next:plan-review {{EPIC_ID}} --review=codex` + - If PLAN_REVIEW=export: run `/flow-next:plan-review {{EPIC_ID}} --review=export` - If PLAN_REVIEW=none: - If REQUIRE_PLAN_REVIEW=1: output `RETRY` and stop. - Else: set ship and stop: @@ -40,15 +33,16 @@ Ralph mode rules (must follow): - Repeats until SHIP - Only returns to Ralph after SHIP or MAJOR_RETHINK -4) IMMEDIATELY after SHIP verdict, write receipt (for any review mode != none): +4) IMMEDIATELY after SHIP verdict, write receipt (for rp mode): ```bash mkdir -p "$(dirname '{{REVIEW_RECEIPT_PATH}}')" ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)" cat > '{{REVIEW_RECEIPT_PATH}}' <SHIP|NEEDS_WORK|MAJOR_RETHINK` from reviewer. +Do NOT improvise review prompts - the skill has the correct format. **Step 2: Verify task done** (AFTER skill returns) ```bash @@ -22,16 +22,18 @@ scripts/ralph/flowctl show {{TASK_ID}} --json ``` If status != `done`, output `RETRY` and stop. -**Step 3: Write impl receipt** (MANDATORY if WORK_REVIEW != none) +**Step 3: Write impl receipt** (MANDATORY if WORK_REVIEW=rp or codex) +For rp mode: ```bash mkdir -p "$(dirname '{{REVIEW_RECEIPT_PATH}}')" ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)" cat > '{{REVIEW_RECEIPT_PATH}}' </dev/null 2>&1 && { echo "$PYTHON_BIN"; return; } + fi + if command -v python3 >/dev/null 2>&1; then echo "python3"; return; fi + if command -v python >/dev/null 2>&1; then echo "python"; return; fi + echo "" +} + +PYTHON_BIN="$(pick_python)" +[[ -n "$PYTHON_BIN" ]] || { echo "ralph: python not found (need python3 or python in PATH)" >&2; exit 1; } +export PYTHON_BIN + SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" ROOT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)" CONFIG="$SCRIPT_DIR/config.env" FLOWCTL="$SCRIPT_DIR/flowctl" +FLOWCTL_PY="$SCRIPT_DIR/flowctl.py" fail() { echo "ralph: $*" >&2; exit 1; } log() { @@ -13,6 +37,35 @@ log() { return 0 } +# Ensure flowctl is runnable even when NTFS exec bit / shebang handling is flaky on Windows +ensure_flowctl_wrapper() { + # If flowctl exists and is executable, use it + if [[ -f "$FLOWCTL" && -x "$FLOWCTL" ]]; then + return 0 + fi + + # On Windows or if flowctl not executable, create a wrapper that calls Python explicitly + if [[ -f "$FLOWCTL_PY" ]]; then + local wrapper="$SCRIPT_DIR/flowctl-wrapper.sh" + cat > "$wrapper" </dev/null 2>&1 || PY="python" +exec "\$PY" "\$DIR/flowctl.py" "\$@" +SH + chmod +x "$wrapper" 2>/dev/null || true + FLOWCTL="$wrapper" + export FLOWCTL + return 0 + fi + + fail "missing flowctl (expected $SCRIPT_DIR/flowctl or $SCRIPT_DIR/flowctl.py)" +} + +ensure_flowctl_wrapper + # ───────────────────────────────────────────────────────────────────────────── # Presentation layer (human-readable output) # ───────────────────────────────────────────────────────────────────────────── @@ -59,7 +112,7 @@ ui() { # Get title from epic/task JSON get_title() { local json="$1" - python3 - "$json" <<'PY' + "$PYTHON_BIN" - "$json" <<'PY' import json, sys try: data = json.loads(sys.argv[1]) @@ -71,7 +124,7 @@ PY # Count progress (done/total tasks for scoped epics) get_progress() { - python3 - "$ROOT_DIR" "${EPICS_FILE:-}" <<'PY' + "$PYTHON_BIN" - "$ROOT_DIR" "${EPICS_FILE:-}" <<'PY' import json, sys from pathlib import Path root = Path(sys.argv[1]) @@ -125,7 +178,7 @@ get_git_stats() { echo "" return fi - python3 - "$stats" <<'PY' + "$PYTHON_BIN" - "$stats" <<'PY' import re, sys s = sys.argv[1] files = re.search(r"(\d+) files? changed", s) @@ -157,9 +210,9 @@ ui_config() { local plan_display="$PLAN_REVIEW" work_display="$WORK_REVIEW" [[ "$PLAN_REVIEW" == "rp" ]] && plan_display="RepoPrompt" - [[ "$PLAN_REVIEW" == "opencode" ]] && plan_display="OpenCode" + [[ "$PLAN_REVIEW" == "codex" ]] && plan_display="Codex" [[ "$WORK_REVIEW" == "rp" ]] && work_display="RepoPrompt" - [[ "$WORK_REVIEW" == "opencode" ]] && work_display="OpenCode" + [[ "$WORK_REVIEW" == "codex" ]] && work_display="Codex" ui "${C_DIM} Reviews:${C_RESET} Plan=$plan_display ${C_DIM}•${C_RESET} Work=$work_display" [[ -n "${EPICS:-}" ]] && ui "${C_DIM} Scope:${C_RESET} $EPICS" ui "" @@ -190,10 +243,10 @@ ui_plan_review() { ui "" ui " ${C_YELLOW}📝 Plan Review${C_RESET}" ui " ${C_DIM}Sending to reviewer via RepoPrompt...${C_RESET}" - elif [[ "$mode" == "opencode" ]]; then + elif [[ "$mode" == "codex" ]]; then ui "" ui " ${C_YELLOW}📝 Plan Review${C_RESET}" - ui " ${C_DIM}Sending to reviewer via OpenCode...${C_RESET}" + ui " ${C_DIM}Sending to reviewer via Codex...${C_RESET}" fi } @@ -203,10 +256,10 @@ ui_impl_review() { ui "" ui " ${C_MAGENTA}🔍 Implementation Review${C_RESET}" ui " ${C_DIM}Sending to reviewer via RepoPrompt...${C_RESET}" - elif [[ "$mode" == "opencode" ]]; then + elif [[ "$mode" == "codex" ]]; then ui "" ui " ${C_MAGENTA}🔍 Implementation Review${C_RESET}" - ui " ${C_DIM}Sending to reviewer via OpenCode...${C_RESET}" + ui " ${C_DIM}Sending to reviewer via Codex...${C_RESET}" fi } @@ -261,7 +314,7 @@ ui_fail() { } ui_waiting() { - ui " ${C_DIM}⏳ OpenCode working...${C_RESET}" + ui " ${C_DIM}⏳ Claude working...${C_RESET}" } [[ -f "$CONFIG" ]] || fail "missing config.env" @@ -273,7 +326,7 @@ source "$CONFIG" set +a MAX_ITERATIONS="${MAX_ITERATIONS:-25}" -MAX_TURNS="${MAX_TURNS:-}" # empty = no limit; unused for OpenCode (kept for parity) +MAX_TURNS="${MAX_TURNS:-}" # empty = no limit; Claude stops via promise tags MAX_ATTEMPTS_PER_TASK="${MAX_ATTEMPTS_PER_TASK:-5}" WORKER_TIMEOUT="${WORKER_TIMEOUT:-1800}" # 30min default; prevents stuck workers BRANCH_MODE="${BRANCH_MODE:-new}" @@ -318,7 +371,7 @@ while [[ $# -gt 0 ]]; do done # Set up signal trap for clean Ctrl+C handling -# Must kill all child processes including timeout and worker +# Must kill all child processes including timeout and claude cleanup() { trap - SIGINT SIGTERM # Prevent re-entry # Kill all child processes @@ -329,7 +382,7 @@ cleanup() { } trap cleanup SIGINT SIGTERM -OPENCODE_BIN="${OPENCODE_BIN:-opencode}" +CLAUDE_BIN="${CLAUDE_BIN:-claude}" # Detect timeout command (GNU coreutils). On macOS: brew install coreutils # Use --foreground to keep child in same process group for signal handling @@ -356,17 +409,17 @@ sanitize_id() { get_actor() { if [[ -n "${FLOW_ACTOR:-}" ]]; then echo "$FLOW_ACTOR"; return; fi - if actor="$(git -C "$ROOT_DIR" config user.name 2>/dev/null)"; then + if actor="$(git -C "$ROOT_DIR" config user.email 2>/dev/null)"; then [[ -n "$actor" ]] && { echo "$actor"; return; } fi - if actor="$(git -C "$ROOT_DIR" config user.email 2>/dev/null)"; then + if actor="$(git -C "$ROOT_DIR" config user.name 2>/dev/null)"; then [[ -n "$actor" ]] && { echo "$actor"; return; } fi echo "${USER:-unknown}" } rand4() { - python3 - <<'PY' + "$PYTHON_BIN" - <<'PY' import secrets print(secrets.token_hex(2)) PY @@ -374,7 +427,7 @@ PY render_template() { local path="$1" - python3 - "$path" <<'PY' + "$PYTHON_BIN" - "$path" <<'PY' import os, sys path = sys.argv[1] text = open(path, encoding="utf-8").read() @@ -388,7 +441,7 @@ PY json_get() { local key="$1" local json="$2" - python3 - "$key" "$json" <<'PY' + "$PYTHON_BIN" - "$key" "$json" <<'PY' import json, sys key = sys.argv[1] data = json.loads(sys.argv[2]) @@ -407,7 +460,7 @@ ensure_attempts_file() { } bump_attempts() { - python3 - "$1" "$2" <<'PY' + "$PYTHON_BIN" - "$1" "$2" <<'PY' import json, sys, os path, task = sys.argv[1], sys.argv[2] data = {} @@ -423,7 +476,7 @@ PY } write_epics_file() { - python3 - "$1" <<'PY' + "$PYTHON_BIN" - "$1" <<'PY' import json, sys raw = sys.argv[1] parts = [p.strip() for p in raw.replace(",", " ").split() if p.strip()] @@ -449,7 +502,7 @@ PROGRESS_FILE="$RUN_DIR/progress.txt" extract_tag() { local tag="$1" - python3 - "$tag" <<'PY' + "$PYTHON_BIN" - "$tag" <<'PY' import re, sys tag = sys.argv[1] text = sys.stdin.read() @@ -459,9 +512,9 @@ PY } # Extract assistant text from stream-json log (for tag extraction in watch mode) -extract_text_from_run_json() { +extract_text_from_stream_json() { local log_file="$1" - python3 - "$log_file" <<'PY' + "$PYTHON_BIN" - "$log_file" <<'PY' import json, sys path = sys.argv[1] out = [] @@ -475,21 +528,12 @@ try: ev = json.loads(line) except json.JSONDecodeError: continue - - # OpenCode run --format json - if ev.get("type") == "text": - part = ev.get("part") or {} - text = part.get("text", "") - if text: - out.append(text) + if ev.get("type") != "assistant": continue - - # Claude stream-json (fallback) - if ev.get("type") == "assistant": - msg = ev.get("message") or {} - for blk in (msg.get("content") or []): - if blk.get("type") == "text": - out.append(blk.get("text", "")) + msg = ev.get("message") or {} + for blk in (msg.get("content") or []): + if blk.get("type") == "text": + out.append(blk.get("text", "")) except Exception: pass print("\n".join(out)) @@ -508,7 +552,7 @@ append_progress() { { echo "## $(date -u +%Y-%m-%dT%H:%M:%SZ) - iter $iter" echo "status=$status epic=${epic_id:-} task=${task_id:-} reason=${reason:-}" - echo "worker_rc=$worker_rc" + echo "claude_rc=$claude_rc" echo "verdict=${verdict:-}" echo "promise=${promise:-}" echo "receipt=${REVIEW_RECEIPT_PATH:-} exists=$receipt_exists" @@ -565,7 +609,7 @@ init_branches_file() { if [[ -f "$BRANCHES_FILE" ]]; then return; fi local base_branch base_branch="$(git -C "$ROOT_DIR" rev-parse --abbrev-ref HEAD 2>/dev/null || true)" - python3 - "$BRANCHES_FILE" "$base_branch" <<'PY' + "$PYTHON_BIN" - "$BRANCHES_FILE" "$base_branch" <<'PY' import json, sys path, base = sys.argv[1], sys.argv[2] data = {"base_branch": base, "run_branch": ""} @@ -575,7 +619,7 @@ PY } get_base_branch() { - python3 - "$BRANCHES_FILE" <<'PY' + "$PYTHON_BIN" - "$BRANCHES_FILE" <<'PY' import json, sys try: with open(sys.argv[1], encoding="utf-8") as f: @@ -587,7 +631,7 @@ PY } get_run_branch() { - python3 - "$BRANCHES_FILE" <<'PY' + "$PYTHON_BIN" - "$BRANCHES_FILE" <<'PY' import json, sys try: with open(sys.argv[1], encoding="utf-8") as f: @@ -599,7 +643,7 @@ PY } set_run_branch() { - python3 - "$BRANCHES_FILE" "$1" <<'PY' + "$PYTHON_BIN" - "$BRANCHES_FILE" "$1" <<'PY' import json, sys path, branch = sys.argv[1], sys.argv[2] data = {"base_branch": "", "run_branch": ""} @@ -615,7 +659,7 @@ PY } list_epics_from_file() { - python3 - "$EPICS_FILE" <<'PY' + "$PYTHON_BIN" - "$EPICS_FILE" <<'PY' import json, sys path = sys.argv[1] if not path: @@ -630,7 +674,7 @@ PY } epic_all_tasks_done() { - python3 - "$1" <<'PY' + "$PYTHON_BIN" - "$1" <<'PY' import json, sys try: data = json.loads(sys.argv[1]) @@ -671,7 +715,7 @@ verify_receipt() { local kind="$2" local id="$3" [[ -f "$path" ]] || return 1 - python3 - "$path" "$kind" "$id" <<'PY' + "$PYTHON_BIN" - "$path" "$kind" "$id" <<'PY' import json, sys path, kind, rid = sys.argv[1], sys.argv[2], sys.argv[3] try: @@ -783,78 +827,90 @@ while (( iter <= MAX_ITERATIONS )); do fi export FLOW_RALPH="1" - AUTONOMOUS_RULES="$(cat <<'TXT' -AUTONOMOUS MODE ACTIVE (FLOW_RALPH=1). You are running unattended. CRITICAL RULES: + claude_args=(-p) + # Always use stream-json for logs (TUI needs it), watch mode only controls terminal display + claude_args+=(--output-format stream-json) + + # Autonomous mode system prompt - critical for preventing drift + claude_args+=(--append-system-prompt "AUTONOMOUS MODE ACTIVE (FLOW_RALPH=1). You are running unattended. CRITICAL RULES: 1. EXECUTE COMMANDS EXACTLY as shown in prompts. Do not paraphrase or improvise. 2. VERIFY OUTCOMES by running the verification commands (flowctl show, git status). 3. NEVER CLAIM SUCCESS without proof. If flowctl done was not run, the task is NOT done. 4. COPY TEMPLATES VERBATIM - receipt JSON must match exactly including all fields. 5. USE SKILLS AS SPECIFIED - invoke /flow-next:impl-review, do not improvise review prompts. -Violations break automation and leave the user with incomplete work. Be precise, not creative. -TXT -)" - prompt="${AUTONOMOUS_RULES}"$'\n\n'"${prompt}" - - opencode_args=(run --format json --agent "${FLOW_RALPH_OPENCODE_AGENT:-ralph-runner}") - [[ -n "${FLOW_RALPH_OPENCODE_MODEL:-}" ]] && opencode_args+=(--model "$FLOW_RALPH_OPENCODE_MODEL") - [[ -n "${FLOW_RALPH_OPENCODE_VARIANT:-}" ]] && opencode_args+=(--variant "$FLOW_RALPH_OPENCODE_VARIANT") - - prev_opencode_permission="${OPENCODE_PERMISSION-__unset__}" - if [[ "$YOLO" == "1" ]]; then - export OPENCODE_PERMISSION='{"*":"allow"}' +Violations break automation and leave the user with incomplete work. Be precise, not creative.") + + [[ -n "${MAX_TURNS:-}" ]] && claude_args+=(--max-turns "$MAX_TURNS") + [[ "$YOLO" == "1" ]] && claude_args+=(--dangerously-skip-permissions) + [[ -n "${FLOW_RALPH_CLAUDE_PLUGIN_DIR:-}" ]] && claude_args+=(--plugin-dir "$FLOW_RALPH_CLAUDE_PLUGIN_DIR") + [[ -n "${FLOW_RALPH_CLAUDE_MODEL:-}" ]] && claude_args+=(--model "$FLOW_RALPH_CLAUDE_MODEL") + [[ -n "${FLOW_RALPH_CLAUDE_SESSION_ID:-}" ]] && claude_args+=(--session-id "$FLOW_RALPH_CLAUDE_SESSION_ID") + [[ -n "${FLOW_RALPH_CLAUDE_PERMISSION_MODE:-}" ]] && claude_args+=(--permission-mode "$FLOW_RALPH_CLAUDE_PERMISSION_MODE") + [[ "${FLOW_RALPH_CLAUDE_NO_SESSION_PERSISTENCE:-}" == "1" ]] && claude_args+=(--no-session-persistence) + if [[ -n "${FLOW_RALPH_CLAUDE_DEBUG:-}" ]]; then + if [[ "${FLOW_RALPH_CLAUDE_DEBUG}" == "1" ]]; then + claude_args+=(--debug) + else + claude_args+=(--debug "$FLOW_RALPH_CLAUDE_DEBUG") + fi fi + [[ "${FLOW_RALPH_CLAUDE_VERBOSE:-}" == "1" ]] && claude_args+=(--verbose) ui_waiting - worker_out="" + claude_out="" set +e + [[ -n "${FLOW_RALPH_CLAUDE_PLUGIN_DIR:-}" ]] && claude_args+=(--plugin-dir "$FLOW_RALPH_CLAUDE_PLUGIN_DIR") if [[ "$WATCH_MODE" == "verbose" ]]; then + # Full output: stream through filter with --verbose to show text/thinking + [[ ! " ${claude_args[*]} " =~ " --verbose " ]] && claude_args+=(--verbose) echo "" if [[ -n "$TIMEOUT_CMD" ]]; then - $TIMEOUT_CMD "$WORKER_TIMEOUT" "$OPENCODE_BIN" "${opencode_args[@]}" "$prompt" 2>&1 | tee "$iter_log" | "$SCRIPT_DIR/watch-filter.py" --verbose + $TIMEOUT_CMD "$WORKER_TIMEOUT" "$CLAUDE_BIN" "${claude_args[@]}" "$prompt" 2>&1 | tee "$iter_log" | "$SCRIPT_DIR/watch-filter.py" --verbose else - "$OPENCODE_BIN" "${opencode_args[@]}" "$prompt" 2>&1 | tee "$iter_log" | "$SCRIPT_DIR/watch-filter.py" --verbose + "$CLAUDE_BIN" "${claude_args[@]}" "$prompt" 2>&1 | tee "$iter_log" | "$SCRIPT_DIR/watch-filter.py" --verbose fi - worker_rc=${PIPESTATUS[0]} - worker_out="$(cat "$iter_log")" + claude_rc=${PIPESTATUS[0]} + claude_out="$(cat "$iter_log")" elif [[ "$WATCH_MODE" == "tools" ]]; then + # Filtered output: stream-json through watch-filter.py + # Add --verbose only if not already set (needed for tool visibility) + [[ ! " ${claude_args[*]} " =~ " --verbose " ]] && claude_args+=(--verbose) if [[ -n "$TIMEOUT_CMD" ]]; then - $TIMEOUT_CMD "$WORKER_TIMEOUT" "$OPENCODE_BIN" "${opencode_args[@]}" "$prompt" 2>&1 | tee "$iter_log" | "$SCRIPT_DIR/watch-filter.py" + $TIMEOUT_CMD "$WORKER_TIMEOUT" "$CLAUDE_BIN" "${claude_args[@]}" "$prompt" 2>&1 | tee "$iter_log" | "$SCRIPT_DIR/watch-filter.py" else - "$OPENCODE_BIN" "${opencode_args[@]}" "$prompt" 2>&1 | tee "$iter_log" | "$SCRIPT_DIR/watch-filter.py" + "$CLAUDE_BIN" "${claude_args[@]}" "$prompt" 2>&1 | tee "$iter_log" | "$SCRIPT_DIR/watch-filter.py" fi - worker_rc=${PIPESTATUS[0]} - worker_out="$(cat "$iter_log")" + claude_rc=${PIPESTATUS[0]} + # Log contains stream-json; verdict/promise extraction handled by fallback logic + claude_out="$(cat "$iter_log")" else + # Default: quiet mode (stream-json to log, no terminal display) + # --verbose required for stream-json with --print + [[ ! " ${claude_args[*]} " =~ " --verbose " ]] && claude_args+=(--verbose) if [[ -n "$TIMEOUT_CMD" ]]; then - $TIMEOUT_CMD "$WORKER_TIMEOUT" "$OPENCODE_BIN" "${opencode_args[@]}" "$prompt" > "$iter_log" 2>&1 + $TIMEOUT_CMD "$WORKER_TIMEOUT" "$CLAUDE_BIN" "${claude_args[@]}" "$prompt" > "$iter_log" 2>&1 else - "$OPENCODE_BIN" "${opencode_args[@]}" "$prompt" > "$iter_log" 2>&1 + "$CLAUDE_BIN" "${claude_args[@]}" "$prompt" > "$iter_log" 2>&1 fi - worker_rc=$? - worker_out="$(cat "$iter_log")" + claude_rc=$? + claude_out="$(cat "$iter_log")" fi set -e - if [[ "$prev_opencode_permission" == "__unset__" ]]; then - unset OPENCODE_PERMISSION - else - export OPENCODE_PERMISSION="$prev_opencode_permission" - fi - # Handle timeout (exit code 124 from timeout command) worker_timeout=0 - if [[ -n "$TIMEOUT_CMD" && "$worker_rc" -eq 124 ]]; then + if [[ -n "$TIMEOUT_CMD" && "$claude_rc" -eq 124 ]]; then echo "ralph: worker timed out after ${WORKER_TIMEOUT}s" >> "$iter_log" log "worker timeout after ${WORKER_TIMEOUT}s" worker_timeout=1 fi - log "worker rc=$worker_rc log=$iter_log" + log "claude rc=$claude_rc log=$iter_log" force_retry=$worker_timeout plan_review_status="" task_status="" - if [[ "$status" == "plan" && ( "$PLAN_REVIEW" == "rp" || "$PLAN_REVIEW" == "opencode" ) ]]; then + if [[ "$status" == "plan" && ( "$PLAN_REVIEW" == "rp" || "$PLAN_REVIEW" == "codex" ) ]]; then if ! verify_receipt "$REVIEW_RECEIPT_PATH" "plan_review" "$epic_id"; then echo "ralph: missing plan review receipt; forcing retry" >> "$iter_log" log "missing plan receipt; forcing retry" @@ -864,7 +920,7 @@ TXT epic_json="$("$FLOWCTL" show "$epic_id" --json 2>/dev/null || true)" plan_review_status="$(json_get plan_review_status "$epic_json")" fi - if [[ "$status" == "work" && ( "$WORK_REVIEW" == "rp" || "$WORK_REVIEW" == "opencode" ) ]]; then + if [[ "$status" == "work" && ( "$WORK_REVIEW" == "rp" || "$WORK_REVIEW" == "codex" ) ]]; then if ! verify_receipt "$REVIEW_RECEIPT_PATH" "impl_review" "$task_id"; then echo "ralph: missing impl review receipt; forcing retry" >> "$iter_log" log "missing impl receipt; forcing retry" @@ -874,9 +930,9 @@ TXT # Extract verdict/promise for progress log (not displayed in UI) # Always parse stream-json since we always use that format now - worker_text="$(extract_text_from_run_json "$iter_log")" - verdict="$(printf '%s' "$worker_text" | extract_tag verdict)" - promise="$(printf '%s' "$worker_text" | extract_tag promise)" + claude_text="$(extract_text_from_stream_json "$iter_log")" + verdict="$(printf '%s' "$claude_text" | extract_tag verdict)" + promise="$(printf '%s' "$claude_text" | extract_tag promise)" # Fallback: derive verdict from flowctl status for logging if [[ -z "$verdict" && -n "$plan_review_status" ]]; then @@ -901,20 +957,20 @@ TXT fi append_progress "$verdict" "$promise" "$plan_review_status" "$task_status" - if echo "$worker_text" | grep -q "COMPLETE"; then + if echo "$claude_text" | grep -q "COMPLETE"; then ui_complete write_completion_marker "DONE" exit 0 fi exit_code=0 - if echo "$worker_text" | grep -q "FAIL"; then + if echo "$claude_text" | grep -q "FAIL"; then exit_code=1 - elif echo "$worker_text" | grep -q "RETRY"; then + elif echo "$claude_text" | grep -q "RETRY"; then exit_code=2 elif [[ "$force_retry" == "1" ]]; then exit_code=2 - elif [[ "$worker_rc" -ne 0 && "$task_status" != "done" && "$verdict" != "SHIP" ]]; then + elif [[ "$claude_rc" -ne 0 && "$task_status" != "done" && "$verdict" != "SHIP" ]]; then # Only fail on non-zero exit code if task didn't complete and verdict isn't SHIP # This prevents false failures from transient errors (telemetry, model fallback, etc.) exit_code=1 @@ -922,7 +978,7 @@ TXT if [[ "$exit_code" -eq 1 ]]; then log "exit=fail" - ui_fail "OpenCode returned FAIL promise" + ui_fail "Claude returned FAIL promise" write_completion_marker "FAILED" exit 1 fi @@ -946,7 +1002,7 @@ TXT fi fi - # Check for pause/stop after OpenCode returns (before next iteration) + # Check for pause/stop after Claude returns (before next iteration) check_sentinels sleep 2 diff --git a/plugins/flow-next/skills/flow-next-ralph-init/templates/watch-filter.py b/plugins/flow-next/skills/flow-next-ralph-init/templates/watch-filter.py index 99a56b5..c703e18 100755 --- a/plugins/flow-next/skills/flow-next-ralph-init/templates/watch-filter.py +++ b/plugins/flow-next/skills/flow-next-ralph-init/templates/watch-filter.py @@ -1,15 +1,15 @@ #!/usr/bin/env python3 """ -Watch filter for Ralph - parses OpenCode run --format json (and Claude stream-json fallback). +Watch filter for Ralph - parses Claude's stream-json output and shows key events. Reads JSON lines from stdin, outputs formatted tool calls in TUI style. CRITICAL: This filter is "fail open" - if output breaks, it continues draining -stdin to prevent SIGPIPE cascading to upstream processes (tee, worker). +stdin to prevent SIGPIPE cascading to upstream processes (tee, claude). Usage: watch-filter.py # Show tool calls only - watch-filter.py --verbose # Show tool calls + text responses + watch-filter.py --verbose # Show tool calls + thinking + text responses """ import argparse @@ -45,7 +45,6 @@ "WebSearch": "🔎", "TodoWrite": "📋", "AskUserQuestion": "❓", - "Question": "❓", "Skill": "⚡", } @@ -77,10 +76,6 @@ def truncate(s: str, max_len: int = 60) -> str: return s -def normalize_path(path: str) -> str: - return path.split("/")[-1] if path else "unknown" - - def format_tool_use(tool_name: str, tool_input: dict) -> str: """Format a tool use event for TUI display.""" icon = ICONS.get(tool_name, "🔹") @@ -93,16 +88,16 @@ def format_tool_use(tool_name: str, tool_input: dict) -> str: return f"{icon} Bash: {truncate(cmd, 60)}" elif tool_name == "Edit": - path = tool_input.get("file_path") or tool_input.get("filePath", "") - return f"{icon} Edit: {normalize_path(path)}" + path = tool_input.get("file_path", "") + return f"{icon} Edit: {path.split('/')[-1] if path else 'unknown'}" elif tool_name == "Write": - path = tool_input.get("file_path") or tool_input.get("filePath", "") - return f"{icon} Write: {normalize_path(path)}" + path = tool_input.get("file_path", "") + return f"{icon} Write: {path.split('/')[-1] if path else 'unknown'}" elif tool_name == "Read": - path = tool_input.get("file_path") or tool_input.get("filePath", "") - return f"{icon} Read: {normalize_path(path)}" + path = tool_input.get("file_path", "") + return f"{icon} Read: {path.split('/')[-1] if path else 'unknown'}" elif tool_name == "Grep": pattern = tool_input.get("pattern", "") @@ -132,37 +127,6 @@ def format_tool_use(tool_name: str, tool_input: dict) -> str: return f"{icon} {tool_name}" -def format_tool_use_opencode(part: dict) -> str: - tool = part.get("tool", "") - state = part.get("state") or {} - tool_input = state.get("input") or {} - title = state.get("title") or "" - - # Map OpenCode tool names to Claude-style names for icon mapping - tool_name = { - "bash": "Bash", - "edit": "Edit", - "write": "Write", - "read": "Read", - "grep": "Grep", - "glob": "Glob", - "list": "List", - "webfetch": "WebFetch", - "websearch": "WebSearch", - "todowrite": "TodoWrite", - "question": "Question", - "task": "Task", - "skill": "Skill", - "patch": "Patch", - }.get(tool, tool) - - # Prefer explicit title if provided - if title: - return f"{ICONS.get(tool_name, '🔹')} {tool_name}: {truncate(title, 60)}" - - return format_tool_use(tool_name, tool_input) - - def format_tool_result(block: dict) -> Optional[str]: """Format a tool_result block (errors only). @@ -189,27 +153,6 @@ def process_event(event: dict, verbose: bool) -> None: """Process a single stream-json event.""" event_type = event.get("type", "") - # OpenCode run --format json - if event_type == "tool_use": - part = event.get("part") or {} - formatted = format_tool_use_opencode(part) - safe_print(f"{INDENT}{C_DIM}{formatted}{C_RESET}") - return - - if verbose and event_type == "text": - part = event.get("part") or {} - text = part.get("text", "") - if text.strip(): - safe_print(f"{INDENT}{C_CYAN}💬 {text}{C_RESET}") - return - - if event_type == "error": - error = event.get("error", {}) - msg = error.get("message") if isinstance(error, dict) else str(error) - if msg: - safe_print(f"{INDENT}{C_DIM}❌ {truncate(msg, 80)}{C_RESET}") - return - # Tool use events (assistant messages) if event_type == "assistant": message = event.get("message", {}) diff --git a/plugins/flow-next/skills/flow-next-work/SKILL.md b/plugins/flow-next/skills/flow-next-work/SKILL.md index a5e45be..e3c64c5 100644 --- a/plugins/flow-next/skills/flow-next-work/SKILL.md +++ b/plugins/flow-next/skills/flow-next-work/SKILL.md @@ -176,7 +176,7 @@ If user chose review: ## Guardrails -- Don't start without asking branch question (unless FLOW_RALPH=1) +- Don't start without asking branch question - Don't start without plan/epic - Don't skip tests - Don't leave tasks half-done diff --git a/plugins/flow-next/skills/flow-next/SKILL.md b/plugins/flow-next/skills/flow-next/SKILL.md index fb39cb0..1274994 100644 --- a/plugins/flow-next/skills/flow-next/SKILL.md +++ b/plugins/flow-next/skills/flow-next/SKILL.md @@ -57,13 +57,13 @@ $FLOWCTL ready --epic fn-1 --json # Create task under existing epic $FLOWCTL task create --epic fn-1 --title "Fix bug X" --json -# Set task description (from file) -echo "Description here" > /tmp/desc.md -$FLOWCTL task set-description fn-1.2 --file /tmp/desc.md --json +# Set task description and acceptance (combined, fewer writes) +$FLOWCTL task set-spec fn-1.2 --description /tmp/desc.md --acceptance /tmp/accept.md --json -# Set acceptance criteria (from file) -echo "- [ ] Criterion 1" > /tmp/accept.md -$FLOWCTL task set-acceptance fn-1.2 --file /tmp/accept.md --json +# Or use stdin with heredoc (no temp file): +$FLOWCTL task set-description fn-1.2 --file - --json <<'EOF' +Description here +EOF # Start working on task $FLOWCTL start fn-1.2 --json @@ -96,7 +96,7 @@ $FLOWCTL validate --all --json $FLOWCTL task create --epic fn-N --title "Short title" --json ``` -3. Add description: +3. Add description + acceptance (combined): ```bash cat > /tmp/desc.md << 'EOF' **Bug/Feature:** Brief description @@ -105,16 +105,11 @@ $FLOWCTL validate --all --json - Point 1 - Point 2 EOF - $FLOWCTL task set-description fn-N.M --file /tmp/desc.md --json - ``` - -4. Add acceptance: - ```bash cat > /tmp/accept.md << 'EOF' - [ ] Criterion 1 - [ ] Criterion 2 EOF - $FLOWCTL task set-acceptance fn-N.M --file /tmp/accept.md --json + $FLOWCTL task set-spec fn-N.M --description /tmp/desc.md --acceptance /tmp/accept.md --json ``` ### "What tasks are there?" diff --git a/sync/PORTING.md b/sync/PORTING.md new file mode 100644 index 0000000..b89bbca --- /dev/null +++ b/sync/PORTING.md @@ -0,0 +1,108 @@ +# Porting Playbook (Minimal, OpenCode-first) + +Goal: keep this OpenCode port aligned with upstream with **minimal, surgical edits**. + +## Non‑negotiables (OpenCode‑specific) + +Do NOT change these unless absolutely required: + +- OpenCode config discovery + plugin order +- OpenCode question tool usage vs text rules +- OpenCode agent config (no `model:` in agent frontmatter) +- OpenCode backend naming (`opencode`) +- Ralph strings/UX that say “OpenCode” +- Batch tool usage in plan skills (OpenCode batch) + +If upstream changes conflict, only adjust the smallest surface needed to keep OpenCode working. + +## Minimal Sync Steps + +1) Pull upstream to `/tmp/gmickel-claude-marketplace-main` (or `~/work/...`). +2) `git diff --name-only ..origin/main -- plugins/flow-next` +3) Copy **only changed files**. +4) Apply **minimal** OpenCode deltas (only if needed). +5) Update `.opencode/` + `sync/templates/` **only when the change affects those files**. +6) Run targeted tests (`ci_test.sh`, `smoke_test.sh`) only if flowctl/ralph changed. +7) Commit per upstream change. + +## Diff Triage (keep edits atomic) + +Before touching files: + +- Enumerate changes by file type: + - **flowctl** (`scripts/flowctl.py`, `flowctl`): behavior changes → must port. + - **Ralph** (`scripts/ralph*.sh`, `skills/flow-next-ralph-init/templates/*`): behavior + UI → must port. + - **Skills** (`skills/**/SKILL.md`, `workflow.md`, `steps.md`): instructions → must port with OpenCode rules applied. + - **Docs** (`docs/*.md`, `README.md`): keep in sync unless OpenCode‑specific divergence. +- Decide per file: **mirror** (no edits) vs **port** (apply OpenCode deltas). +- Only port files that *actually changed upstream*. + +## Minimal Edit Rules + +- **Do not rewrite** files. Apply tiny, local edits. +- **Preserve upstream order + wording** unless it conflicts with OpenCode invariants. +- **One change = one commit** (keep diffs reviewable). +- **Never change both mirror + template** unless the mirror change actually needs OpenCode edits. + +## Port Flow (deterministic) + +1) Copy updated upstream file(s) into `plugins/flow-next/`. +2) If OpenCode deltas needed, patch **only** the affected file(s): + - `sync/templates/opencode/**` for OpenCode templates + - `.opencode/**` only via `sync/transform.py` (never edit directly) +3) Run: + - `python3 sync/transform.py` (only if templates changed) + - `sync/verify.sh` (always) +4) Tests: + - flowctl changes → run `plugins/flow-next/scripts/smoke_test.sh` from `/tmp` + - ralph changes → run `plugins/flow-next/scripts/ralph_smoke_test.sh` from `/tmp` + +## When to Use `sync/run.sh` + +Use `sync/run.sh` **only** when upstream has many changes across skills/agents/commands and you want a full mirror refresh. +Otherwise, prefer manual file copy + minimal patches (smaller diffs, easier review). + +## Audit Checklist (pre-commit) + +- `sync/verify.sh` passes +- `.opencode/` has no `AskUserQuestion` strings +- Backend names in skills/docs: `opencode` only +- Models set in `.opencode/opencode.json` (not in agent frontmatter) +- Question tool only in `/flow-next:setup`; other workflows ask plain text +- Ralph templates mention OpenCode (not Claude/Codex) + +## OpenCode Config Facts (from `repos/opencode`) + +Keep these in mind when porting; they explain why our layout is the way it is: + +- Config precedence: remote < global config (`~/.config/opencode/opencode.json[c]`) < `OPENCODE_CONFIG` < project `opencode.json[c]` < `OPENCODE_CONFIG_CONTENT`. +- `.opencode/` directories are discovered upward; if present, `.opencode/opencode.json[c]` is loaded too. +- Commands/agents/plugins are loaded from `.opencode/command`, `.opencode/agent`, `.opencode/plugin` (and non-dot `/command`, `/agent`). +- Plugins are deduped by name; priority order (highest wins): local `plugin/` dir > local `opencode.json` > global `plugin/` dir > global `opencode.json`. +- Question tool schema: `header` max 12 chars, `options` have `label` + `description`, optional `multiple`, optional `custom` (defaults true). Answers are arrays of selected labels. +- Config has `experimental.batch_tool` flag; keep enabled for our skills. + +## OpenCode Port Invariants (do not regress) + +- `opencode.json` lives in `.opencode/` for installs; keep templates aligned. +- Agent frontmatter (`.opencode/agent/*.md`) must NOT include model; models live in `.opencode/opencode.json` `agent` block. +- Question tool usage: + - `/flow-next:setup` uses question tool. + - Plan/work/plan-review/impl-review use plain text questions (voice-friendly). +- Backend name is `opencode` only. No `codex` backend. +- Reviewer agent = `opencode-reviewer` with `reasoningEffort` in `.opencode/opencode.json`. +- Ralph runner agent = `ralph-runner` with model set in `.opencode/opencode.json`. +- Subagents keep tools locked down (`write/edit/patch/multiedit: false`). +- Re-review loops must reuse `session_id` for subagent continuity (OpenCode task tool). + +## Where to Patch (if required) + +- `plugins/flow-next/**` = primary source in this repo +- `.opencode/**` = runtime OpenCode install +- `sync/templates/**` = install/sync overrides + +## Example Scope (stdin + set‑spec) + +- Copy `flowctl.py`, `smoke_test.sh`, and the specific skills touched upstream. +- Adjust only where OpenCode diverges (question tool, backend naming, batch tool). +- Do **not** rewrite entire skills. diff --git a/sync/templates/opencode/skill/flow-next-impl-review/SKILL.md b/sync/templates/opencode/skill/flow-next-impl-review/SKILL.md index 102073b..667d639 100644 --- a/sync/templates/opencode/skill/flow-next-impl-review/SKILL.md +++ b/sync/templates/opencode/skill/flow-next-impl-review/SKILL.md @@ -42,17 +42,17 @@ If found, use that backend and skip all other detection. ```bash # Check available backends -HAVE_RP=0 +HAVE_RP=0; if command -v rp-cli >/dev/null 2>&1; then - HAVE_RP=1 + HAVE_RP=1; elif [[ -x /opt/homebrew/bin/rp-cli || -x /usr/local/bin/rp-cli ]]; then - HAVE_RP=1 -fi + HAVE_RP=1; +fi; # Get configured backend -BACKEND="${FLOW_REVIEW_BACKEND:-}" +BACKEND="${FLOW_REVIEW_BACKEND:-}"; if [[ -z "$BACKEND" ]]; then - BACKEND="$($FLOWCTL config get review.backend 2>/dev/null | jq -r '.value // empty')" + BACKEND="$($FLOWCTL config get review.backend 2>/dev/null | jq -r '.value // empty')"; fi ``` diff --git a/sync/templates/opencode/skill/flow-next-interview/SKILL.md b/sync/templates/opencode/skill/flow-next-interview/SKILL.md index dc4f4ad..dc62f70 100644 --- a/sync/templates/opencode/skill/flow-next-interview/SKILL.md +++ b/sync/templates/opencode/skill/flow-next-interview/SKILL.md @@ -98,42 +98,51 @@ After interview complete, write everything back. ### For Flow Epic ID -1. Create a temp file with the refined epic spec including: - - Clear problem statement - - Technical approach with specifics - - Key decisions made during interview - - Edge cases to handle - - Quick commands section (required) - - Acceptance criteria - -2. Update epic spec: - ```bash - $FLOWCTL epic set-plan --file --json - ``` - -3. Create/update tasks if interview revealed breakdown: - ```bash - $FLOWCTL task create --epic --title "..." --json - $FLOWCTL task set-description --file --json - $FLOWCTL task set-acceptance --file --json - ``` +Update epic spec using stdin heredoc (preferred): +```bash +$FLOWCTL epic set-plan --file - --json <<'EOF' +# Epic Title -### For Flow Task ID +## Problem +Clear problem statement + +## Approach +Technical approach with specifics, key decisions from interview + +## Edge Cases +- Edge case 1 +- Edge case 2 -1. Write description to temp file with: - - Clear task description - - Technical details from interview - - Edge cases +## Quick commands +```bash +# smoke test command +``` -2. Write acceptance to temp file with: - - Checkboxes for acceptance criteria - - Specific, testable conditions +## Acceptance +- [ ] Criterion 1 +- [ ] Criterion 2 +EOF +``` -3. Update task: - ```bash - $FLOWCTL task set-description --file --json - $FLOWCTL task set-acceptance --file --json - ``` +Create/update tasks if interview revealed breakdown: +```bash +$FLOWCTL task create --epic --title "..." --json +# Use set-spec for combined description + acceptance (fewer writes) +$FLOWCTL task set-spec --description /tmp/desc.md --acceptance /tmp/acc.md --json +``` + +### For Flow Task ID + +Update task using combined set-spec (preferred) or separate calls: +```bash +# Preferred: combined set-spec (2 writes instead of 4) +$FLOWCTL task set-spec --description /tmp/desc.md --acceptance /tmp/acc.md --json + +# Or use stdin for description only: +$FLOWCTL task set-description --file - --json <<'EOF' +Clear task description with technical details and edge cases from interview +EOF +``` ### For File Path diff --git a/sync/templates/opencode/skill/flow-next-plan-review/SKILL.md b/sync/templates/opencode/skill/flow-next-plan-review/SKILL.md index f800ba8..6202cc9 100644 --- a/sync/templates/opencode/skill/flow-next-plan-review/SKILL.md +++ b/sync/templates/opencode/skill/flow-next-plan-review/SKILL.md @@ -42,17 +42,17 @@ If found, use that backend and skip all other detection. ```bash # Check available backends -HAVE_RP=0 +HAVE_RP=0; if command -v rp-cli >/dev/null 2>&1; then - HAVE_RP=1 + HAVE_RP=1; elif [[ -x /opt/homebrew/bin/rp-cli || -x /usr/local/bin/rp-cli ]]; then - HAVE_RP=1 -fi + HAVE_RP=1; +fi; # Get configured backend -BACKEND="${FLOW_REVIEW_BACKEND:-}" +BACKEND="${FLOW_REVIEW_BACKEND:-}"; if [[ -z "$BACKEND" ]]; then - BACKEND="$($FLOWCTL config get review.backend 2>/dev/null | jq -r '.value // empty')" + BACKEND="$($FLOWCTL config get review.backend 2>/dev/null | jq -r '.value // empty')"; fi ``` diff --git a/sync/templates/opencode/skill/flow-next-plan-review/workflow.md b/sync/templates/opencode/skill/flow-next-plan-review/workflow.md index 2c900f7..adb6d89 100644 --- a/sync/templates/opencode/skill/flow-next-plan-review/workflow.md +++ b/sync/templates/opencode/skill/flow-next-plan-review/workflow.md @@ -139,6 +139,12 @@ $FLOWCTL cat Save output for inclusion in review prompt. Compose a 1-2 sentence summary for the setup-review command. +**Save checkpoint** (protects against context compaction during review): +```bash +$FLOWCTL checkpoint save --epic --json +``` +This creates `.flow/.checkpoint-.json` with full state. If compaction occurs during review-fix cycles, restore with `$FLOWCTL checkpoint restore --epic `. + --- ## Phase 2: Augment Selection (RP) @@ -242,6 +248,19 @@ EOF fi ``` +### Update status + +Extract verdict from response, then: +```bash +# If SHIP +$FLOWCTL epic set-plan-review-status --status ship --json + +# If NEEDS_WORK or MAJOR_RETHINK +$FLOWCTL epic set-plan-review-status --status needs_work --json +``` + +If no verdict tag, output `RETRY` and stop. + --- ## Fix Loop (RP) @@ -254,10 +273,21 @@ If verdict is NEEDS_WORK: 2. **Fix the plan** - Address each issue. Write updated plan to temp file. 3. **Update plan in flowctl** (MANDATORY before re-review): ```bash + # Option A: stdin heredoc (preferred, no temp file) + $FLOWCTL epic set-plan --file - --json <<'EOF' + + EOF + + # Option B: temp file (if content has single quotes) $FLOWCTL epic set-plan --file /tmp/updated-plan.md --json ``` **If you skip this step and re-review with same content, reviewer will return NEEDS_WORK again.** + **Recovery**: If context compaction occurred, restore from checkpoint first: + ```bash + $FLOWCTL checkpoint restore --epic --json + ``` + 4. **Re-review with fix summary** (only AFTER step 3): **IMPORTANT**: Do NOT re-add files already in the selection. RepoPrompt auto-refreshes diff --git a/sync/templates/opencode/skill/flow-next-plan/SKILL.md b/sync/templates/opencode/skill/flow-next-plan/SKILL.md index 89633ba..97c6c1b 100644 --- a/sync/templates/opencode/skill/flow-next-plan/SKILL.md +++ b/sync/templates/opencode/skill/flow-next-plan/SKILL.md @@ -44,17 +44,17 @@ If empty, ask: "What should I plan? Give me the feature or bug in 1-5 sentences. Check available backends and configured preference: ```bash -HAVE_RP=0 +HAVE_RP=0; if command -v rp-cli >/dev/null 2>&1; then - HAVE_RP=1 + HAVE_RP=1; elif [[ -x /opt/homebrew/bin/rp-cli || -x /usr/local/bin/rp-cli ]]; then - HAVE_RP=1 -fi + HAVE_RP=1; +fi; # Check configured backend (priority: env > config) -CONFIGURED_BACKEND="${FLOW_REVIEW_BACKEND:-}" +CONFIGURED_BACKEND="${FLOW_REVIEW_BACKEND:-}"; if [[ -z "$CONFIGURED_BACKEND" ]]; then - CONFIGURED_BACKEND="$($FLOWCTL config get review.backend 2>/dev/null | jq -r '.value // empty')" + CONFIGURED_BACKEND="$($FLOWCTL config get review.backend 2>/dev/null | jq -r '.value // empty')"; fi ``` diff --git a/sync/templates/opencode/skill/flow-next-plan/steps.md b/sync/templates/opencode/skill/flow-next-plan/steps.md index 42a9ba4..7ac8504 100644 --- a/sync/templates/opencode/skill/flow-next-plan/steps.md +++ b/sync/templates/opencode/skill/flow-next-plan/steps.md @@ -113,18 +113,25 @@ Default to short unless complexity demands more. ## Step 4: Write to .flow +**Efficiency note**: Use stdin (`--file -`) with heredocs to avoid temp files. Use `task set-spec` to set description + acceptance in one call. + **Route A - Input was an existing Flow ID**: 1. If epic ID (fn-N): - - Write a temp file with the updated plan spec - - `$FLOWCTL epic set-plan --file --json` + ```bash + # Use stdin heredoc (no temp file needed) + $FLOWCTL epic set-plan --file - --json <<'EOF' + + EOF + ``` - Create/update child tasks as needed 2. If task ID (fn-N.M): - - Write temp file for description - - `$FLOWCTL task set-description --file --json` - - Write temp file for acceptance - - `$FLOWCTL task set-acceptance --file --json` + ```bash + # Combined set-spec: description + acceptance in one call + # Write to temp files only if content has single quotes + $FLOWCTL task set-spec --description /tmp/desc.md --acceptance /tmp/acc.md --json + ``` **Route B - Input was text (new idea)**: @@ -141,10 +148,24 @@ Default to short unless complexity demands more. ``` - If user specified a branch, use that instead. -3. Write epic spec: - - Create a temp file with the full plan/spec content - - Include: Overview, Scope, Approach, Quick commands (REQUIRED - at least one smoke test command), Acceptance, References - - `$FLOWCTL epic set-plan --file --json` +3. Write epic spec (use stdin heredoc): + ```bash + # Include: Overview, Scope, Approach, Quick commands (REQUIRED), Acceptance, References + $FLOWCTL epic set-plan --file - --json <<'EOF' + # Epic Title + + ## Overview + ... + + ## Quick commands + ```bash + # At least one smoke test command + ``` + + ## Acceptance + ... + EOF + ``` 4. Create child tasks: ```bash @@ -152,10 +173,13 @@ Default to short unless complexity demands more. $FLOWCTL task create --epic --title "" --json ``` -5. Write task specs: - - For each task, write description and acceptance to temp files - - `$FLOWCTL task set-description --file --json` - - `$FLOWCTL task set-acceptance --file --json` +5. Write task specs (use combined set-spec): + ```bash + # For each task - single call sets both sections + # Write description and acceptance to temp files, then: + $FLOWCTL task set-spec --description /tmp/desc.md --acceptance /tmp/acc.md --json + ``` + This reduces 4 atomic writes per task to 2. 6. Add dependencies: ```bash diff --git a/sync/templates/opencode/skill/flow-next-ralph-init/SKILL.md b/sync/templates/opencode/skill/flow-next-ralph-init/SKILL.md index 51a2614..308824d 100644 --- a/sync/templates/opencode/skill/flow-next-ralph-init/SKILL.md +++ b/sync/templates/opencode/skill/flow-next-ralph-init/SKILL.md @@ -11,7 +11,7 @@ Scaffold repo-local Ralph harness. Opt-in only. - Only create `scripts/ralph/` in the current repo. - If `scripts/ralph/` already exists, stop and ask the user to remove it first. -- Copy templates from `templates/` into `scripts/ralph/`. +- Copy templates from `.opencode/skill/flow-next-ralph-init/templates/` into `scripts/ralph/`. - Copy `flowctl` and `flowctl.py` from `$PLUGIN_ROOT/scripts/` into `scripts/ralph/`. - Set executable bit on `scripts/ralph/ralph.sh`, `scripts/ralph/ralph_once.sh`, and `scripts/ralph/flowctl`. @@ -21,15 +21,16 @@ Scaffold repo-local Ralph harness. Opt-in only. ```bash ROOT="$(git rev-parse --show-toplevel)" PLUGIN_ROOT="$ROOT/plugins/flow-next" + TEMPLATE_DIR="$ROOT/.opencode/skill/flow-next-ralph-init/templates" ``` 2. Check `scripts/ralph/` does not exist. 3. Detect available review backends: ```bash -HAVE_RP=0 +HAVE_RP=0; if command -v rp-cli >/dev/null 2>&1; then - HAVE_RP=1 + HAVE_RP=1; elif [[ -x /opt/homebrew/bin/rp-cli || -x /usr/local/bin/rp-cli ]]; then - HAVE_RP=1 + HAVE_RP=1; fi ``` 4. Determine review backend: @@ -49,6 +50,11 @@ fi - `PLAN_REVIEW=` and `WORK_REVIEW=` - replace `{{PLAN_REVIEW}}` and `{{WORK_REVIEW}}` placeholders in the template 6. Copy templates and flowctl files. + ```bash + mkdir -p scripts/ralph + cp -R "$TEMPLATE_DIR/." scripts/ralph/ + cp "$PLUGIN_ROOT/scripts/flowctl" "$PLUGIN_ROOT/scripts/flowctl.py" scripts/ralph/ + ``` 7. Print next steps (run from terminal, NOT inside OpenCode): - Edit `scripts/ralph/config.env` to customize settings - `./scripts/ralph/ralph_once.sh` (one iteration, observe) diff --git a/sync/templates/opencode/skill/flow-next-ralph-init/templates/ralph.sh b/sync/templates/opencode/skill/flow-next-ralph-init/templates/ralph.sh index d310cb6..50777e3 100644 --- a/sync/templates/opencode/skill/flow-next-ralph-init/templates/ralph.sh +++ b/sync/templates/opencode/skill/flow-next-ralph-init/templates/ralph.sh @@ -1,10 +1,34 @@ #!/usr/bin/env bash set -euo pipefail +# ───────────────────────────────────────────────────────────────────────────── +# Windows / Git Bash hardening (GH-35) +# ───────────────────────────────────────────────────────────────────────────── +UNAME_S="$(uname -s 2>/dev/null || echo "")" +IS_WINDOWS=0 +case "$UNAME_S" in + MINGW*|MSYS*|CYGWIN*) IS_WINDOWS=1 ;; +esac + +# Python detection: prefer python3, fallback to python (common on Windows) +pick_python() { + if [[ -n "${PYTHON_BIN:-}" ]]; then + command -v "$PYTHON_BIN" >/dev/null 2>&1 && { echo "$PYTHON_BIN"; return; } + fi + if command -v python3 >/dev/null 2>&1; then echo "python3"; return; fi + if command -v python >/dev/null 2>&1; then echo "python"; return; fi + echo "" +} + +PYTHON_BIN="$(pick_python)" +[[ -n "$PYTHON_BIN" ]] || { echo "ralph: python not found (need python3 or python in PATH)" >&2; exit 1; } +export PYTHON_BIN + SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" ROOT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)" CONFIG="$SCRIPT_DIR/config.env" FLOWCTL="$SCRIPT_DIR/flowctl" +FLOWCTL_PY="$SCRIPT_DIR/flowctl.py" fail() { echo "ralph: $*" >&2; exit 1; } log() { @@ -13,6 +37,35 @@ log() { return 0 } +# Ensure flowctl is runnable even when NTFS exec bit / shebang handling is flaky on Windows +ensure_flowctl_wrapper() { + # If flowctl exists and is executable, use it + if [[ -f "$FLOWCTL" && -x "$FLOWCTL" ]]; then + return 0 + fi + + # On Windows or if flowctl not executable, create a wrapper that calls Python explicitly + if [[ -f "$FLOWCTL_PY" ]]; then + local wrapper="$SCRIPT_DIR/flowctl-wrapper.sh" + cat > "$wrapper" </dev/null 2>&1 || PY="python" +exec "\$PY" "\$DIR/flowctl.py" "\$@" +SH + chmod +x "$wrapper" 2>/dev/null || true + FLOWCTL="$wrapper" + export FLOWCTL + return 0 + fi + + fail "missing flowctl (expected $SCRIPT_DIR/flowctl or $SCRIPT_DIR/flowctl.py)" +} + +ensure_flowctl_wrapper + # ───────────────────────────────────────────────────────────────────────────── # Presentation layer (human-readable output) # ───────────────────────────────────────────────────────────────────────────── @@ -59,7 +112,7 @@ ui() { # Get title from epic/task JSON get_title() { local json="$1" - python3 - "$json" <<'PY' + "$PYTHON_BIN" - "$json" <<'PY' import json, sys try: data = json.loads(sys.argv[1]) @@ -71,7 +124,7 @@ PY # Count progress (done/total tasks for scoped epics) get_progress() { - python3 - "$ROOT_DIR" "${EPICS_FILE:-}" <<'PY' + "$PYTHON_BIN" - "$ROOT_DIR" "${EPICS_FILE:-}" <<'PY' import json, sys from pathlib import Path root = Path(sys.argv[1]) @@ -125,7 +178,7 @@ get_git_stats() { echo "" return fi - python3 - "$stats" <<'PY' + "$PYTHON_BIN" - "$stats" <<'PY' import re, sys s = sys.argv[1] files = re.search(r"(\d+) files? changed", s) diff --git a/sync/templates/opencode/skill/flow-next-ralph-init/templates/watch-filter.py b/sync/templates/opencode/skill/flow-next-ralph-init/templates/watch-filter.py index 99a56b5..bca5d96 100755 --- a/sync/templates/opencode/skill/flow-next-ralph-init/templates/watch-filter.py +++ b/sync/templates/opencode/skill/flow-next-ralph-init/templates/watch-filter.py @@ -44,7 +44,6 @@ "WebFetch": "🌐", "WebSearch": "🔎", "TodoWrite": "📋", - "AskUserQuestion": "❓", "Question": "❓", "Skill": "⚡", } diff --git a/sync/templates/opencode/skill/flow-next-work/SKILL.md b/sync/templates/opencode/skill/flow-next-work/SKILL.md index afd3144..abd2a4a 100644 --- a/sync/templates/opencode/skill/flow-next-work/SKILL.md +++ b/sync/templates/opencode/skill/flow-next-work/SKILL.md @@ -59,17 +59,17 @@ If no input provided, ask for it. Check available backends and configured preference: ```bash -HAVE_RP=0 +HAVE_RP=0; if command -v rp-cli >/dev/null 2>&1; then - HAVE_RP=1 + HAVE_RP=1; elif [[ -x /opt/homebrew/bin/rp-cli || -x /usr/local/bin/rp-cli ]]; then - HAVE_RP=1 -fi + HAVE_RP=1; +fi; # Check configured backend (priority: env > config) -CONFIGURED_BACKEND="${FLOW_REVIEW_BACKEND:-}" +CONFIGURED_BACKEND="${FLOW_REVIEW_BACKEND:-}"; if [[ -z "$CONFIGURED_BACKEND" ]]; then - CONFIGURED_BACKEND="$($FLOWCTL config get review.backend 2>/dev/null | jq -r '.value // empty')" + CONFIGURED_BACKEND="$($FLOWCTL config get review.backend 2>/dev/null | jq -r '.value // empty')"; fi ``` diff --git a/sync/verify.sh b/sync/verify.sh index ae4a7b6..3d341ca 100755 --- a/sync/verify.sh +++ b/sync/verify.sh @@ -14,9 +14,9 @@ if grep -R -n "AskUserQuestion" "$ROOT/.opencode" >/dev/null; then exit 1 fi -if grep -R -n -i "codex" "$ROOT/.opencode" >/dev/null; then +if grep -R -n -i "codex" "$ROOT/.opencode" | grep -v "/opencode.json" >/dev/null; then echo "codex references found in .opencode (not supported)" >&2 - grep -R -n -i "codex" "$ROOT/.opencode" >&2 + grep -R -n -i "codex" "$ROOT/.opencode" | grep -v "/opencode.json" >&2 exit 1 fi @@ -25,4 +25,3 @@ if grep -R -n "CLAUDE_" "$ROOT/.opencode" | grep -v "flow-next-ralph-init/templa grep -R -n "CLAUDE_" "$ROOT/.opencode" | grep -v "flow-next-ralph-init/templates" >&2 exit 1 fi - From d55d2e353a9ad35602ca6b29ed2705511e8bb8f4 Mon Sep 17 00:00:00 2001 From: Gordon Mickel Date: Fri, 16 Jan 2026 02:57:18 +0100 Subject: [PATCH 02/11] fix: opencode review via flowctl --- .../skill/flow-next-impl-review/SKILL.md | 19 +- .../skill/flow-next-impl-review/workflow.md | 15 +- .../skill/flow-next-plan-review/SKILL.md | 28 +- .../skill/flow-next-plan-review/workflow.md | 14 +- plugins/flow-next/docs/flowctl.md | 41 ++- plugins/flow-next/scripts/flowctl.py | 312 ++++++++++++++++++ .../skill/flow-next-impl-review/SKILL.md | 19 +- .../skill/flow-next-impl-review/workflow.md | 15 +- .../skill/flow-next-plan-review/SKILL.md | 28 +- .../skill/flow-next-plan-review/workflow.md | 14 +- 10 files changed, 425 insertions(+), 80 deletions(-) diff --git a/.opencode/skill/flow-next-impl-review/SKILL.md b/.opencode/skill/flow-next-impl-review/SKILL.md index 667d639..27161ef 100644 --- a/.opencode/skill/flow-next-impl-review/SKILL.md +++ b/.opencode/skill/flow-next-impl-review/SKILL.md @@ -97,11 +97,10 @@ fi 5. **Re-reviews MUST stay in SAME chat** - omit `--new-chat` after first review **For opencode backend:** -1. Use the task tool with `subagent_type: opencode-reviewer` -2. Provide full diff context (git log + changed files + diff) and focus areas -3. Parse verdict from `...` tag -4. If `REVIEW_RECEIPT_PATH` set: write receipt JSON with `mode: "opencode"` -5. **Re-reviews must reuse the same task session**: capture `session_id` from `` and pass it back via `session_id` on subsequent task tool calls +1. Use `flowctl opencode impl-review` (deterministic CLI wrapper) +2. Parse verdict from `VERDICT` or JSON output +3. If `REVIEW_RECEIPT_PATH` set: flowctl writes receipt JSON with `mode: "opencode"` +4. Re-reviews: re-run the same command (no session_id reuse) **For all backends:** - If `REVIEW_RECEIPT_PATH` set: write receipt after review (any verdict) @@ -157,11 +156,11 @@ Build a review prompt with: - Review criteria (correctness, security, performance, tests, risks) - Required verdict tag -Run reviewer subagent using the task tool: -- subagent_type: `opencode-reviewer` -- prompt: `` - -Parse verdict from reviewer response (`SHIP|NEEDS_WORK|MAJOR_RETHINK`). +Run review via flowctl: +```bash +$FLOWCTL opencode impl-review "$TASK_ID" --base "$BASE_BRANCH" --receipt "$RECEIPT_PATH" --json +``` +Parse verdict from JSON (`.verdict`). On NEEDS_WORK: fix code, commit, re-run review (same backend). diff --git a/.opencode/skill/flow-next-impl-review/workflow.md b/.opencode/skill/flow-next-impl-review/workflow.md index 23ae66b..0cb1d0d 100644 --- a/.opencode/skill/flow-next-impl-review/workflow.md +++ b/.opencode/skill/flow-next-impl-review/workflow.md @@ -73,13 +73,18 @@ Include: ### Step 3: Execute review -Use the task tool: -- subagent_type: `opencode-reviewer` -- prompt: `` +Run OpenCode review via flowctl (deterministic, supports receipts): -Capture `session_id` from the `` block in the tool output and reuse it for any re-review by passing `session_id` back into the task tool. This keeps the same subagent chat. +```bash +TASK_ID="${1:-}" +BASE_BRANCH="main" +RECEIPT_PATH="${REVIEW_RECEIPT_PATH:-/tmp/impl-review-receipt.json}" + +REVIEW_JSON="$($FLOWCTL opencode impl-review "$TASK_ID" --base "$BASE_BRANCH" --receipt "$RECEIPT_PATH" --json)" +VERDICT="$(echo "$REVIEW_JSON" | jq -r '.verdict // empty')" +``` -**Output must include** `SHIP` or `NEEDS_WORK` or `MAJOR_RETHINK`. +If `VERDICT` is empty, output `RETRY` and stop. ### Step 4: Receipt diff --git a/.opencode/skill/flow-next-plan-review/SKILL.md b/.opencode/skill/flow-next-plan-review/SKILL.md index 6202cc9..1b99530 100644 --- a/.opencode/skill/flow-next-plan-review/SKILL.md +++ b/.opencode/skill/flow-next-plan-review/SKILL.md @@ -97,11 +97,10 @@ fi 5. **Re-reviews MUST stay in SAME chat** - omit `--new-chat` after first review **For opencode backend:** -1. Use the task tool with `subagent_type: opencode-reviewer` -2. Provide full plan content (`flowctl show` + `flowctl cat`) and focus areas -3. Parse verdict from `...` tag -4. If `REVIEW_RECEIPT_PATH` set: write receipt JSON with `mode: "opencode"` -5. **Re-reviews must reuse the same task session**: capture `session_id` from `` and pass it back via `session_id` on subsequent task tool calls +1. Use `flowctl opencode plan-review` (deterministic CLI wrapper) +2. Parse verdict from `VERDICT` or JSON output +3. If `REVIEW_RECEIPT_PATH` set: flowctl writes receipt JSON with `mode: "opencode"` +4. Re-reviews: re-run the same command (no session_id reuse) **For all backends:** - If `REVIEW_RECEIPT_PATH` set: write receipt after review (any verdict) @@ -149,22 +148,15 @@ Build a review prompt with: - Review criteria (completeness, feasibility, clarity, architecture, risks, scope, testability) - Required verdict tag -Run reviewer subagent using the task tool: -- subagent_type: `opencode-reviewer` -- prompt: `` - -Parse verdict from reviewer response (`SHIP|NEEDS_WORK|MAJOR_RETHINK`). +Run review via flowctl: +```bash +$FLOWCTL opencode plan-review "$EPIC_ID" --receipt "$RECEIPT_PATH" --json +``` +Parse verdict from JSON (`.verdict`). On NEEDS_WORK: fix plan via `$FLOWCTL epic set-plan`, then re-run review (same backend). -Write receipt if `REVIEW_RECEIPT_PATH` set: -```bash -mkdir -p "$(dirname "$RECEIPT_PATH")" -ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)" -cat > "$RECEIPT_PATH" <","timestamp":"$ts"} -EOF -``` +Receipt is written by `flowctl opencode plan-review` when `--receipt` is provided. ### RepoPrompt Backend diff --git a/.opencode/skill/flow-next-plan-review/workflow.md b/.opencode/skill/flow-next-plan-review/workflow.md index adb6d89..ff2830e 100644 --- a/.opencode/skill/flow-next-plan-review/workflow.md +++ b/.opencode/skill/flow-next-plan-review/workflow.md @@ -67,13 +67,17 @@ Include: ### Step 3: Execute review -Use the task tool: -- subagent_type: `opencode-reviewer` -- prompt: `` +Run OpenCode review via flowctl (deterministic, supports receipts): -Capture `session_id` from the `` block in the tool output and reuse it for any re-review by passing `session_id` back into the task tool. This keeps the same subagent chat. +```bash +EPIC_ID="${1:-}" +RECEIPT_PATH="${REVIEW_RECEIPT_PATH:-/tmp/plan-review-receipt.json}" + +REVIEW_JSON="$($FLOWCTL opencode plan-review "$EPIC_ID" --receipt "$RECEIPT_PATH" --json)" +VERDICT="$(echo "$REVIEW_JSON" | jq -r '.verdict // empty')" +``` -**Output must include** `SHIP` or `NEEDS_WORK` or `MAJOR_RETHINK`. +If `VERDICT` is empty, output `RETRY` and stop. ### Step 4: Update Status diff --git a/plugins/flow-next/docs/flowctl.md b/plugins/flow-next/docs/flowctl.md index f3afa1d..090de98 100644 --- a/plugins/flow-next/docs/flowctl.md +++ b/plugins/flow-next/docs/flowctl.md @@ -7,7 +7,7 @@ CLI for `.flow/` task tracking. Agents must use flowctl for all writes. ## Available Commands ``` -init, detect, epic, task, dep, show, epics, tasks, list, cat, ready, next, start, done, block, validate, config, memory, prep-chat, rp, codex, checkpoint, status +init, detect, epic, task, dep, show, epics, tasks, list, cat, ready, next, start, done, block, validate, config, memory, prep-chat, rp, opencode, codex, checkpoint, status ``` ## Multi-User Safety @@ -375,7 +375,7 @@ flowctl config get review.backend [--json] # Set a config value flowctl config set memory.enabled true [--json] -flowctl config set review.backend codex [--json] # rp, codex, or none +flowctl config set review.backend opencode [--json] # rp, opencode, or none # Toggle boolean config flowctl config toggle memory.enabled [--json] @@ -386,7 +386,7 @@ flowctl config toggle memory.enabled [--json] | Key | Type | Default | Description | |-----|------|---------|-------------| | `memory.enabled` | bool | `false` | Enable memory system | -| `review.backend` | string | auto | Default review backend (`rp`, `codex`, `none`) | +| `review.backend` | string | auto | Default review backend (`rp`, `opencode`, `none`) | Auto-detect priority: `FLOW_REVIEW_BACKEND` env → config → available CLI. @@ -463,9 +463,42 @@ flowctl rp chat-send --window "$W" --tab "$T" --message-file /tmp/review-prompt. flowctl rp prompt-export --window "$W" --tab "$T" --out /tmp/export.md ``` +### opencode + +OpenCode CLI wrappers (default for reviews in this port). + +**Requirements:** +```bash +opencode --version +``` + +**Model:** Uses the `opencode-reviewer` agent defined in `.opencode/opencode.json`. + +**Commands:** +```bash +# Implementation review (reviews code changes for a task) +flowctl opencode impl-review --base [--receipt ] [--json] +# Example: flowctl opencode impl-review fn-1.3 --base main --receipt /tmp/impl-fn-1.3.json + +# Plan review (reviews epic spec before implementation) +flowctl opencode plan-review [--receipt ] [--json] +# Example: flowctl opencode plan-review fn-1 --receipt /tmp/plan-fn-1.json +``` + +**Receipt schema (Ralph-compatible):** +```json +{ + "type": "impl_review", + "id": "fn-1.3", + "mode": "opencode", + "verdict": "SHIP", + "timestamp": "2026-01-16T01:23:45Z" +} +``` + ### codex -OpenAI Codex CLI wrappers — cross-platform alternative to RepoPrompt. +OpenAI Codex CLI wrappers — legacy alternative to RepoPrompt. **Requirements:** ```bash diff --git a/plugins/flow-next/scripts/flowctl.py b/plugins/flow-next/scripts/flowctl.py index 169cccd..d95b124 100755 --- a/plugins/flow-next/scripts/flowctl.py +++ b/plugins/flow-next/scripts/flowctl.py @@ -678,6 +678,14 @@ def require_codex() -> str: return codex +def require_opencode() -> str: + """Ensure opencode CLI is available. Returns path to opencode.""" + opencode = shutil.which("opencode") + if not opencode: + error_exit("opencode not found in PATH", use_json=False, code=2) + return opencode + + def get_codex_version() -> Optional[str]: """Get codex version, or None if not available.""" codex = shutil.which("codex") @@ -796,6 +804,110 @@ def parse_codex_verdict(output: str) -> Optional[str]: return match.group(1) if match else None +def run_opencode_review( + prompt: str, + files: Optional[list[str]] = None, + title: Optional[str] = None, + timeout: int = 600, +) -> str: + """Run opencode review and return output.""" + opencode = require_opencode() + cmd = [opencode, "run", "--agent", "opencode-reviewer"] + if title: + cmd += ["--title", title] + if files: + for f in files: + cmd += ["--file", f] + cmd.append(prompt) + try: + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True, + timeout=timeout, + ) + return result.stdout + except subprocess.TimeoutExpired: + error_exit("opencode run timed out (600s)", use_json=False, code=2) + except subprocess.CalledProcessError as e: + msg = (e.stderr or e.stdout or str(e)).strip() + error_exit(f"opencode run failed: {msg}", use_json=False, code=2) + + +def build_opencode_impl_prompt( + task_spec: str, + diff_summary: str, + commits: str, + files: str, + base_branch: str, + focus: Optional[str], +) -> str: + focus_block = f"\nFocus: {focus}\n" if focus else "" + return f"""Review the attached patch file for implementation changes. + +Base branch: {base_branch} +Commits: +{commits or '(none)'} + +Changed files: +{files or '(none)'} + +Diff summary: +{diff_summary or '(none)'} + +Task spec: +{task_spec} +{focus_block} +Review Criteria: +1. Correctness +2. Safety/Security +3. Performance +4. Maintainability +5. Tests + +Output Format: +- Group issues by severity (Blocker/Major/Minor) +- Include concrete fixes and suggested tests + +End with exactly one verdict tag: +SHIP or NEEDS_WORK or MAJOR_RETHINK +""" + + +def build_opencode_plan_prompt( + plan_summary: str, + plan_spec: str, + epic_id: str, + focus: Optional[str], +) -> str: + focus_block = f"\nFocus: {focus}\n" if focus else "" + return f"""Review the attached plan for epic {epic_id}. + +Plan summary: +{plan_summary} + +Plan spec: +{plan_spec} +{focus_block} +Review Criteria: +1. Completeness +2. Feasibility +3. Clarity +4. Architecture +5. Risks (incl. security) +6. Scope +7. Testability + +Output Format: +- Group issues by severity (Blocker/Major/Minor) +- Include concrete fixes + +End with exactly one verdict tag: +SHIP or NEEDS_WORK or MAJOR_RETHINK +""" + + def build_review_prompt( review_type: str, spec_content: str, @@ -4220,6 +4332,179 @@ def cmd_codex_plan_review(args: argparse.Namespace) -> None: print(f"\nVERDICT={verdict or 'UNKNOWN'}") +def cmd_opencode_impl_review(args: argparse.Namespace) -> None: + """Run implementation review via opencode run.""" + task_id = args.task + base_branch = args.base + focus = getattr(args, "focus", None) + + standalone = task_id is None + + if not standalone: + if not ensure_flow_exists(): + error_exit(".flow/ does not exist", use_json=args.json) + if not is_task_id(task_id): + error_exit(f"Invalid task ID: {task_id}", use_json=args.json) + flow_dir = get_flow_dir() + task_spec_path = flow_dir / TASKS_DIR / f"{task_id}.md" + if not task_spec_path.exists(): + error_exit(f"Task spec not found: {task_spec_path}", use_json=args.json) + task_spec = task_spec_path.read_text(encoding="utf-8") + else: + task_spec = "Standalone review (no task spec)." + + repo_root = get_repo_root() + commits = "" + files = "" + diff_summary = "" + try: + commits = subprocess.run( + ["git", "log", f"{base_branch}..HEAD", "--oneline"], + capture_output=True, + text=True, + cwd=repo_root, + ).stdout.strip() + except subprocess.CalledProcessError: + pass + try: + files = subprocess.run( + ["git", "diff", f"{base_branch}..HEAD", "--name-only"], + capture_output=True, + text=True, + cwd=repo_root, + ).stdout.strip() + except subprocess.CalledProcessError: + pass + try: + diff_summary = subprocess.run( + ["git", "diff", "--stat", base_branch], + capture_output=True, + text=True, + cwd=repo_root, + ).stdout.strip() + except subprocess.CalledProcessError: + pass + + # Write patch file + patch_path = f"/tmp/impl-review-{base_branch}..HEAD.patch" + try: + patch = subprocess.run( + ["git", "diff", f"{base_branch}..HEAD"], + capture_output=True, + text=True, + cwd=repo_root, + check=True, + ).stdout + Path(patch_path).write_text(patch, encoding="utf-8") + except subprocess.CalledProcessError: + Path(patch_path).write_text("", encoding="utf-8") + + prompt = build_opencode_impl_prompt( + task_spec=task_spec, + diff_summary=diff_summary, + commits=commits, + files=files, + base_branch=base_branch, + focus=focus, + ) + + title = f"Impl Review: {task_id or 'branch'}" + output = run_opencode_review(prompt, files=[patch_path], title=title) + verdict = parse_codex_verdict(output) + + review_id = task_id if task_id else "branch" + receipt_path = args.receipt if hasattr(args, "receipt") and args.receipt else None + if receipt_path: + receipt_data = { + "type": "impl_review", + "id": review_id, + "mode": "opencode", + "base": base_branch, + "verdict": verdict, + "timestamp": now_iso(), + "review": output, + } + if focus: + receipt_data["focus"] = focus + Path(receipt_path).write_text( + json.dumps(receipt_data, indent=2) + "\n", encoding="utf-8" + ) + + if args.json: + json_output( + { + "type": "impl_review", + "id": review_id, + "verdict": verdict, + "mode": "opencode", + "standalone": standalone, + "review": output, + } + ) + else: + print(output) + print(f"\nVERDICT={verdict or 'UNKNOWN'}") + + +def cmd_opencode_plan_review(args: argparse.Namespace) -> None: + """Run plan review via opencode run.""" + if not ensure_flow_exists(): + error_exit(".flow/ does not exist", use_json=args.json) + + epic_id = args.epic + focus = getattr(args, "focus", None) + + if not is_epic_id(epic_id): + error_exit(f"Invalid epic ID: {epic_id}", use_json=args.json) + + flow_dir = get_flow_dir() + epic_json_path = flow_dir / EPICS_DIR / f"{epic_id}.json" + epic_spec_path = flow_dir / SPECS_DIR / f"{epic_id}.md" + plan_summary = epic_json_path.read_text(encoding="utf-8") if epic_json_path.exists() else "" + plan_spec = epic_spec_path.read_text(encoding="utf-8") if epic_spec_path.exists() else "" + + prompt = build_opencode_plan_prompt( + plan_summary=plan_summary, + plan_spec=plan_spec, + epic_id=epic_id, + focus=focus, + ) + + title = f"Plan Review: {epic_id}" + output = run_opencode_review(prompt, title=title) + verdict = parse_codex_verdict(output) + + receipt_path = args.receipt if hasattr(args, "receipt") and args.receipt else None + if receipt_path: + receipt_data = { + "type": "plan_review", + "id": epic_id, + "mode": "opencode", + "verdict": verdict, + "timestamp": now_iso(), + "review": output, + } + if focus: + receipt_data["focus"] = focus + Path(receipt_path).write_text( + json.dumps(receipt_data, indent=2) + "\n", encoding="utf-8" + ) + + if args.json: + json_output( + { + "type": "plan_review", + "id": epic_id, + "verdict": verdict, + "mode": "opencode", + "review": output, + } + ) + else: + print(output) + print(f"\nVERDICT={verdict or 'UNKNOWN'}") + + # --- Checkpoint commands --- @@ -4991,6 +5276,33 @@ def main() -> None: p_codex_plan.add_argument("--json", action="store_true", help="JSON output") p_codex_plan.set_defaults(func=cmd_codex_plan_review) + # opencode (OpenCode CLI wrappers) + p_opencode = subparsers.add_parser("opencode", help="OpenCode CLI helpers") + opencode_sub = p_opencode.add_subparsers(dest="opencode_cmd", required=True) + + p_opencode_impl = opencode_sub.add_parser("impl-review", help="Implementation review") + p_opencode_impl.add_argument( + "task", + nargs="?", + default=None, + help="Task ID (fn-N.M), optional for standalone", + ) + p_opencode_impl.add_argument("--base", required=True, help="Base branch for diff") + p_opencode_impl.add_argument( + "--focus", help="Focus areas for standalone review (comma-separated)" + ) + p_opencode_impl.add_argument( + "--receipt", help="Receipt file path for session continuity" + ) + p_opencode_impl.add_argument("--json", action="store_true", help="JSON output") + p_opencode_impl.set_defaults(func=cmd_opencode_impl_review) + + p_opencode_plan = opencode_sub.add_parser("plan-review", help="Plan review") + p_opencode_plan.add_argument("epic", help="Epic ID (fn-N)") + p_opencode_plan.add_argument("--receipt", help="Receipt file path for session continuity") + p_opencode_plan.add_argument("--json", action="store_true", help="JSON output") + p_opencode_plan.set_defaults(func=cmd_opencode_plan_review) + args = parser.parse_args() args.func(args) diff --git a/sync/templates/opencode/skill/flow-next-impl-review/SKILL.md b/sync/templates/opencode/skill/flow-next-impl-review/SKILL.md index 667d639..27161ef 100644 --- a/sync/templates/opencode/skill/flow-next-impl-review/SKILL.md +++ b/sync/templates/opencode/skill/flow-next-impl-review/SKILL.md @@ -97,11 +97,10 @@ fi 5. **Re-reviews MUST stay in SAME chat** - omit `--new-chat` after first review **For opencode backend:** -1. Use the task tool with `subagent_type: opencode-reviewer` -2. Provide full diff context (git log + changed files + diff) and focus areas -3. Parse verdict from `...` tag -4. If `REVIEW_RECEIPT_PATH` set: write receipt JSON with `mode: "opencode"` -5. **Re-reviews must reuse the same task session**: capture `session_id` from `` and pass it back via `session_id` on subsequent task tool calls +1. Use `flowctl opencode impl-review` (deterministic CLI wrapper) +2. Parse verdict from `VERDICT` or JSON output +3. If `REVIEW_RECEIPT_PATH` set: flowctl writes receipt JSON with `mode: "opencode"` +4. Re-reviews: re-run the same command (no session_id reuse) **For all backends:** - If `REVIEW_RECEIPT_PATH` set: write receipt after review (any verdict) @@ -157,11 +156,11 @@ Build a review prompt with: - Review criteria (correctness, security, performance, tests, risks) - Required verdict tag -Run reviewer subagent using the task tool: -- subagent_type: `opencode-reviewer` -- prompt: `` - -Parse verdict from reviewer response (`SHIP|NEEDS_WORK|MAJOR_RETHINK`). +Run review via flowctl: +```bash +$FLOWCTL opencode impl-review "$TASK_ID" --base "$BASE_BRANCH" --receipt "$RECEIPT_PATH" --json +``` +Parse verdict from JSON (`.verdict`). On NEEDS_WORK: fix code, commit, re-run review (same backend). diff --git a/sync/templates/opencode/skill/flow-next-impl-review/workflow.md b/sync/templates/opencode/skill/flow-next-impl-review/workflow.md index 23ae66b..0cb1d0d 100644 --- a/sync/templates/opencode/skill/flow-next-impl-review/workflow.md +++ b/sync/templates/opencode/skill/flow-next-impl-review/workflow.md @@ -73,13 +73,18 @@ Include: ### Step 3: Execute review -Use the task tool: -- subagent_type: `opencode-reviewer` -- prompt: `` +Run OpenCode review via flowctl (deterministic, supports receipts): -Capture `session_id` from the `` block in the tool output and reuse it for any re-review by passing `session_id` back into the task tool. This keeps the same subagent chat. +```bash +TASK_ID="${1:-}" +BASE_BRANCH="main" +RECEIPT_PATH="${REVIEW_RECEIPT_PATH:-/tmp/impl-review-receipt.json}" + +REVIEW_JSON="$($FLOWCTL opencode impl-review "$TASK_ID" --base "$BASE_BRANCH" --receipt "$RECEIPT_PATH" --json)" +VERDICT="$(echo "$REVIEW_JSON" | jq -r '.verdict // empty')" +``` -**Output must include** `SHIP` or `NEEDS_WORK` or `MAJOR_RETHINK`. +If `VERDICT` is empty, output `RETRY` and stop. ### Step 4: Receipt diff --git a/sync/templates/opencode/skill/flow-next-plan-review/SKILL.md b/sync/templates/opencode/skill/flow-next-plan-review/SKILL.md index 6202cc9..1b99530 100644 --- a/sync/templates/opencode/skill/flow-next-plan-review/SKILL.md +++ b/sync/templates/opencode/skill/flow-next-plan-review/SKILL.md @@ -97,11 +97,10 @@ fi 5. **Re-reviews MUST stay in SAME chat** - omit `--new-chat` after first review **For opencode backend:** -1. Use the task tool with `subagent_type: opencode-reviewer` -2. Provide full plan content (`flowctl show` + `flowctl cat`) and focus areas -3. Parse verdict from `...` tag -4. If `REVIEW_RECEIPT_PATH` set: write receipt JSON with `mode: "opencode"` -5. **Re-reviews must reuse the same task session**: capture `session_id` from `` and pass it back via `session_id` on subsequent task tool calls +1. Use `flowctl opencode plan-review` (deterministic CLI wrapper) +2. Parse verdict from `VERDICT` or JSON output +3. If `REVIEW_RECEIPT_PATH` set: flowctl writes receipt JSON with `mode: "opencode"` +4. Re-reviews: re-run the same command (no session_id reuse) **For all backends:** - If `REVIEW_RECEIPT_PATH` set: write receipt after review (any verdict) @@ -149,22 +148,15 @@ Build a review prompt with: - Review criteria (completeness, feasibility, clarity, architecture, risks, scope, testability) - Required verdict tag -Run reviewer subagent using the task tool: -- subagent_type: `opencode-reviewer` -- prompt: `` - -Parse verdict from reviewer response (`SHIP|NEEDS_WORK|MAJOR_RETHINK`). +Run review via flowctl: +```bash +$FLOWCTL opencode plan-review "$EPIC_ID" --receipt "$RECEIPT_PATH" --json +``` +Parse verdict from JSON (`.verdict`). On NEEDS_WORK: fix plan via `$FLOWCTL epic set-plan`, then re-run review (same backend). -Write receipt if `REVIEW_RECEIPT_PATH` set: -```bash -mkdir -p "$(dirname "$RECEIPT_PATH")" -ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)" -cat > "$RECEIPT_PATH" <","timestamp":"$ts"} -EOF -``` +Receipt is written by `flowctl opencode plan-review` when `--receipt` is provided. ### RepoPrompt Backend diff --git a/sync/templates/opencode/skill/flow-next-plan-review/workflow.md b/sync/templates/opencode/skill/flow-next-plan-review/workflow.md index adb6d89..ff2830e 100644 --- a/sync/templates/opencode/skill/flow-next-plan-review/workflow.md +++ b/sync/templates/opencode/skill/flow-next-plan-review/workflow.md @@ -67,13 +67,17 @@ Include: ### Step 3: Execute review -Use the task tool: -- subagent_type: `opencode-reviewer` -- prompt: `` +Run OpenCode review via flowctl (deterministic, supports receipts): -Capture `session_id` from the `` block in the tool output and reuse it for any re-review by passing `session_id` back into the task tool. This keeps the same subagent chat. +```bash +EPIC_ID="${1:-}" +RECEIPT_PATH="${REVIEW_RECEIPT_PATH:-/tmp/plan-review-receipt.json}" + +REVIEW_JSON="$($FLOWCTL opencode plan-review "$EPIC_ID" --receipt "$RECEIPT_PATH" --json)" +VERDICT="$(echo "$REVIEW_JSON" | jq -r '.verdict // empty')" +``` -**Output must include** `SHIP` or `NEEDS_WORK` or `MAJOR_RETHINK`. +If `VERDICT` is empty, output `RETRY` and stop. ### Step 4: Update Status From 43952bf0bb408c60db15bd03b3df24cc61afd2b6 Mon Sep 17 00:00:00 2001 From: Gordon Mickel Date: Fri, 16 Jan 2026 08:20:12 +0100 Subject: [PATCH 03/11] fix: opencode prompt file --- plugins/flow-next/scripts/flowctl.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/plugins/flow-next/scripts/flowctl.py b/plugins/flow-next/scripts/flowctl.py index d95b124..a414c4e 100755 --- a/plugins/flow-next/scripts/flowctl.py +++ b/plugins/flow-next/scripts/flowctl.py @@ -805,7 +805,7 @@ def parse_codex_verdict(output: str) -> Optional[str]: def run_opencode_review( - prompt: str, + prompt_file: str, files: Optional[list[str]] = None, title: Optional[str] = None, timeout: int = 600, @@ -818,7 +818,8 @@ def run_opencode_review( if files: for f in files: cmd += ["--file", f] - cmd.append(prompt) + cmd += ["--file", prompt_file] + cmd.append("Review attached prompt and patch. End with a single tag.") try: result = subprocess.run( cmd, @@ -4407,9 +4408,11 @@ def cmd_opencode_impl_review(args: argparse.Namespace) -> None: base_branch=base_branch, focus=focus, ) + prompt_path = f"/tmp/impl-review-{base_branch}..HEAD.prompt.md" + Path(prompt_path).write_text(prompt, encoding="utf-8") title = f"Impl Review: {task_id or 'branch'}" - output = run_opencode_review(prompt, files=[patch_path], title=title) + output = run_opencode_review(prompt_path, files=[patch_path], title=title) verdict = parse_codex_verdict(output) review_id = task_id if task_id else "branch" @@ -4469,9 +4472,11 @@ def cmd_opencode_plan_review(args: argparse.Namespace) -> None: epic_id=epic_id, focus=focus, ) + prompt_path = f"/tmp/plan-review-{epic_id}.prompt.md" + Path(prompt_path).write_text(prompt, encoding="utf-8") title = f"Plan Review: {epic_id}" - output = run_opencode_review(prompt, title=title) + output = run_opencode_review(prompt_path, title=title) verdict = parse_codex_verdict(output) receipt_path = args.receipt if hasattr(args, "receipt") and args.receipt else None From 0062d4143f6a9291fb5479bd260a5505a9a4ee65 Mon Sep 17 00:00:00 2001 From: Gordon Mickel Date: Fri, 16 Jan 2026 10:06:18 +0100 Subject: [PATCH 04/11] fix: opencode review subagent --- .../skill/flow-next-impl-review/SKILL.md | 42 +++++++------------ .../skill/flow-next-impl-review/workflow.md | 24 ++++++----- .../skill/flow-next-plan-review/SKILL.md | 36 ++++++---------- .../skill/flow-next-plan-review/workflow.md | 23 ++++++---- plugins/flow-next/scripts/flowctl.py | 11 +++-- sync/PORTING.md | 1 + .../skill/flow-next-impl-review/SKILL.md | 42 +++++++------------ .../skill/flow-next-impl-review/workflow.md | 24 ++++++----- .../skill/flow-next-plan-review/SKILL.md | 36 ++++++---------- .../skill/flow-next-plan-review/workflow.md | 23 ++++++---- 10 files changed, 116 insertions(+), 146 deletions(-) diff --git a/.opencode/skill/flow-next-impl-review/SKILL.md b/.opencode/skill/flow-next-impl-review/SKILL.md index 27161ef..a6f5ede 100644 --- a/.opencode/skill/flow-next-impl-review/SKILL.md +++ b/.opencode/skill/flow-next-impl-review/SKILL.md @@ -97,10 +97,10 @@ fi 5. **Re-reviews MUST stay in SAME chat** - omit `--new-chat` after first review **For opencode backend:** -1. Use `flowctl opencode impl-review` (deterministic CLI wrapper) -2. Parse verdict from `VERDICT` or JSON output -3. If `REVIEW_RECEIPT_PATH` set: flowctl writes receipt JSON with `mode: "opencode"` -4. Re-reviews: re-run the same command (no session_id reuse) +1. Use the **task tool** with subagent_type `opencode-reviewer` +2. Reviewer gathers context via tools (git diff/log, read files) +3. Parse verdict from reviewer output +4. Extract `session_id` from `` and reuse it for re-reviews **For all backends:** - If `REVIEW_RECEIPT_PATH` set: write receipt after review (any verdict) @@ -135,32 +135,18 @@ Run backend detection from SKILL.md above. Then branch: ### OpenCode Backend -```bash -TASK_ID="${1:-}" -BASE_BRANCH="main" -RECEIPT_PATH="${REVIEW_RECEIPT_PATH:-/tmp/impl-review-receipt.json}" - -# Identify changes -git branch --show-current -git log main..HEAD --oneline 2>/dev/null || git log master..HEAD --oneline -git diff main..HEAD --name-only 2>/dev/null || git diff master..HEAD --name-only -DIFF_OUTPUT="$(git diff main..HEAD 2>/dev/null || git diff master..HEAD)" -``` +Use the task tool with subagent_type `opencode-reviewer`. -Build a review prompt with: -- Branch + base branch -- Commit list -- Changed files -- Full diff (or a focused diff if huge) -- Focus areas from arguments -- Review criteria (correctness, security, performance, tests, risks) -- Required verdict tag +Prompt must require: +- `git log main..HEAD --oneline` +- `git diff main..HEAD --stat` +- `git diff main..HEAD` +- Read any changed files needed for correctness +- No questions, no code changes, no TodoWrite +- End with `SHIP` or `NEEDS_WORK` or `MAJOR_RETHINK` -Run review via flowctl: -```bash -$FLOWCTL opencode impl-review "$TASK_ID" --base "$BASE_BRANCH" --receipt "$RECEIPT_PATH" --json -``` -Parse verdict from JSON (`.verdict`). +Parse verdict from the subagent response. +Extract `session_id` from `` and reuse for re-review. On NEEDS_WORK: fix code, commit, re-run review (same backend). diff --git a/.opencode/skill/flow-next-impl-review/workflow.md b/.opencode/skill/flow-next-impl-review/workflow.md index 0cb1d0d..daad156 100644 --- a/.opencode/skill/flow-next-impl-review/workflow.md +++ b/.opencode/skill/flow-next-impl-review/workflow.md @@ -71,19 +71,23 @@ Include: - Review criteria (correctness, security, performance, tests, risks) - Required verdict tag -### Step 3: Execute review +### Step 3: Execute review (subagent) -Run OpenCode review via flowctl (deterministic, supports receipts): +Use the **task** tool with subagent_type `opencode-reviewer`. The reviewer must gather context itself via tools. -```bash -TASK_ID="${1:-}" -BASE_BRANCH="main" -RECEIPT_PATH="${REVIEW_RECEIPT_PATH:-/tmp/impl-review-receipt.json}" - -REVIEW_JSON="$($FLOWCTL opencode impl-review "$TASK_ID" --base "$BASE_BRANCH" --receipt "$RECEIPT_PATH" --json)" -VERDICT="$(echo "$REVIEW_JSON" | jq -r '.verdict // empty')" +**Task tool call** (example): +```json +{ + "description": "Impl review", + "prompt": "You are the OpenCode reviewer. Review current branch vs main. Rules: no questions, no code changes, no TodoWrite. Use bash/read to gather context. REQUIRED: run `git log main..HEAD --oneline`, `git diff main..HEAD --stat`, and `git diff main..HEAD`. Read any changed files needed for correctness. Then output issues grouped by severity and end with exactly one verdict tag: SHIP or NEEDS_WORK or MAJOR_RETHINK.", + "subagent_type": "opencode-reviewer" +} ``` +**After the task completes**: +- Parse `VERDICT` from the subagent output. +- Extract `session_id` from the `` block (used for re-reviews). + If `VERDICT` is empty, output `RETRY` and stop. ### Step 4: Receipt @@ -103,7 +107,7 @@ EOF If `VERDICT=NEEDS_WORK`: 1. Parse issues from output 2. Fix code, commit, run tests -3. Re-run Step 3 (same backend, same `session_id`) +3. Re-run Step 3 **with the same task session_id** (pass `session_id` to the task tool) 4. Repeat until SHIP --- diff --git a/.opencode/skill/flow-next-plan-review/SKILL.md b/.opencode/skill/flow-next-plan-review/SKILL.md index 1b99530..c146751 100644 --- a/.opencode/skill/flow-next-plan-review/SKILL.md +++ b/.opencode/skill/flow-next-plan-review/SKILL.md @@ -97,10 +97,10 @@ fi 5. **Re-reviews MUST stay in SAME chat** - omit `--new-chat` after first review **For opencode backend:** -1. Use `flowctl opencode plan-review` (deterministic CLI wrapper) -2. Parse verdict from `VERDICT` or JSON output -3. If `REVIEW_RECEIPT_PATH` set: flowctl writes receipt JSON with `mode: "opencode"` -4. Re-reviews: re-run the same command (no session_id reuse) +1. Use the **task tool** with subagent_type `opencode-reviewer` +2. Reviewer gathers context via tools (`flowctl show/cat`) +3. Parse verdict from reviewer output +4. Extract `session_id` from `` and reuse it for re-reviews **For all backends:** - If `REVIEW_RECEIPT_PATH` set: write receipt after review (any verdict) @@ -133,31 +133,19 @@ Run backend detection from SKILL.md above. Then branch: ### OpenCode Backend -```bash -EPIC_ID="${1:-}" -RECEIPT_PATH="${REVIEW_RECEIPT_PATH:-/tmp/plan-review-receipt.json}" - -# Gather plan content -PLAN_SUMMARY="$($FLOWCTL show "$EPIC_ID" --json)" -PLAN_SPEC="$($FLOWCTL cat "$EPIC_ID")" -``` +Use the task tool with subagent_type `opencode-reviewer`. -Build a review prompt with: -- Plan summary + spec -- Focus areas from arguments -- Review criteria (completeness, feasibility, clarity, architecture, risks, scope, testability) -- Required verdict tag +Prompt must require: +- `flowctl show --json` +- `flowctl cat ` +- No questions, no code changes, no TodoWrite +- End with `SHIP` or `NEEDS_WORK` or `MAJOR_RETHINK` -Run review via flowctl: -```bash -$FLOWCTL opencode plan-review "$EPIC_ID" --receipt "$RECEIPT_PATH" --json -``` -Parse verdict from JSON (`.verdict`). +Parse verdict from the subagent response. +Extract `session_id` from `` and reuse for re-review. On NEEDS_WORK: fix plan via `$FLOWCTL epic set-plan`, then re-run review (same backend). -Receipt is written by `flowctl opencode plan-review` when `--receipt` is provided. - ### RepoPrompt Backend ```bash diff --git a/.opencode/skill/flow-next-plan-review/workflow.md b/.opencode/skill/flow-next-plan-review/workflow.md index ff2830e..2885bea 100644 --- a/.opencode/skill/flow-next-plan-review/workflow.md +++ b/.opencode/skill/flow-next-plan-review/workflow.md @@ -65,18 +65,23 @@ Include: - Review criteria (completeness, feasibility, clarity, architecture, risks, scope, testability) - Required verdict tag -### Step 3: Execute review +### Step 3: Execute review (subagent) -Run OpenCode review via flowctl (deterministic, supports receipts): +Use the **task** tool with subagent_type `opencode-reviewer`. The reviewer must gather context itself via tools. -```bash -EPIC_ID="${1:-}" -RECEIPT_PATH="${REVIEW_RECEIPT_PATH:-/tmp/plan-review-receipt.json}" - -REVIEW_JSON="$($FLOWCTL opencode plan-review "$EPIC_ID" --receipt "$RECEIPT_PATH" --json)" -VERDICT="$(echo "$REVIEW_JSON" | jq -r '.verdict // empty')" +**Task tool call** (example): +```json +{ + "description": "Plan review", + "prompt": "You are the OpenCode reviewer. Review the plan for EPIC_ID. Rules: no questions, no code changes, no TodoWrite. Use bash/read to gather context. REQUIRED: run `flowctl show --json` and `flowctl cat `. Then review for completeness, feasibility, clarity, architecture, risks, scope, and testability. End with exactly one verdict tag: SHIP or NEEDS_WORK or MAJOR_RETHINK.", + "subagent_type": "opencode-reviewer" +} ``` +**After the task completes**: +- Parse `VERDICT` from the subagent output. +- Extract `session_id` from the `` block (used for re-reviews). + If `VERDICT` is empty, output `RETRY` and stop. ### Step 4: Update Status @@ -105,7 +110,7 @@ EOF If `VERDICT=NEEDS_WORK`: 1. Parse issues from output 2. Fix plan via `$FLOWCTL epic set-plan` -3. Re-run Step 3 (same backend, same `session_id`) +3. Re-run Step 3 **with the same task session_id** (pass `session_id` to the task tool) 4. Repeat until SHIP --- diff --git a/plugins/flow-next/scripts/flowctl.py b/plugins/flow-next/scripts/flowctl.py index a414c4e..f31e649 100755 --- a/plugins/flow-next/scripts/flowctl.py +++ b/plugins/flow-next/scripts/flowctl.py @@ -812,6 +812,7 @@ def run_opencode_review( ) -> str: """Run opencode review and return output.""" opencode = require_opencode() + message = "Review attached prompt and patch. End with a single tag." cmd = [opencode, "run", "--agent", "opencode-reviewer"] if title: cmd += ["--title", title] @@ -819,7 +820,6 @@ def run_opencode_review( for f in files: cmd += ["--file", f] cmd += ["--file", prompt_file] - cmd.append("Review attached prompt and patch. End with a single tag.") try: result = subprocess.run( cmd, @@ -827,6 +827,7 @@ def run_opencode_review( text=True, check=True, timeout=timeout, + input=message, ) return result.stdout except subprocess.TimeoutExpired: @@ -4387,7 +4388,8 @@ def cmd_opencode_impl_review(args: argparse.Namespace) -> None: pass # Write patch file - patch_path = f"/tmp/impl-review-{base_branch}..HEAD.patch" + # OpenAI input name pattern disallows dots; use safe filenames. + patch_path = "/tmp/flow-next-impl-review-patch" try: patch = subprocess.run( ["git", "diff", f"{base_branch}..HEAD"], @@ -4408,7 +4410,7 @@ def cmd_opencode_impl_review(args: argparse.Namespace) -> None: base_branch=base_branch, focus=focus, ) - prompt_path = f"/tmp/impl-review-{base_branch}..HEAD.prompt.md" + prompt_path = "/tmp/flow-next-impl-review-prompt" Path(prompt_path).write_text(prompt, encoding="utf-8") title = f"Impl Review: {task_id or 'branch'}" @@ -4472,7 +4474,8 @@ def cmd_opencode_plan_review(args: argparse.Namespace) -> None: epic_id=epic_id, focus=focus, ) - prompt_path = f"/tmp/plan-review-{epic_id}.prompt.md" + # OpenAI input name pattern disallows dots; use safe filename. + prompt_path = "/tmp/flow-next-plan-review-prompt" Path(prompt_path).write_text(prompt, encoding="utf-8") title = f"Plan Review: {epic_id}" diff --git a/sync/PORTING.md b/sync/PORTING.md index b89bbca..3feaa0a 100644 --- a/sync/PORTING.md +++ b/sync/PORTING.md @@ -94,6 +94,7 @@ Keep these in mind when porting; they explain why our layout is the way it is: - Ralph runner agent = `ralph-runner` with model set in `.opencode/opencode.json`. - Subagents keep tools locked down (`write/edit/patch/multiedit: false`). - Re-review loops must reuse `session_id` for subagent continuity (OpenCode task tool). +- OpenCode review uses **task tool + subagent** (`opencode-reviewer`). Do not use `opencode run` for reviews. ## Where to Patch (if required) diff --git a/sync/templates/opencode/skill/flow-next-impl-review/SKILL.md b/sync/templates/opencode/skill/flow-next-impl-review/SKILL.md index 27161ef..a6f5ede 100644 --- a/sync/templates/opencode/skill/flow-next-impl-review/SKILL.md +++ b/sync/templates/opencode/skill/flow-next-impl-review/SKILL.md @@ -97,10 +97,10 @@ fi 5. **Re-reviews MUST stay in SAME chat** - omit `--new-chat` after first review **For opencode backend:** -1. Use `flowctl opencode impl-review` (deterministic CLI wrapper) -2. Parse verdict from `VERDICT` or JSON output -3. If `REVIEW_RECEIPT_PATH` set: flowctl writes receipt JSON with `mode: "opencode"` -4. Re-reviews: re-run the same command (no session_id reuse) +1. Use the **task tool** with subagent_type `opencode-reviewer` +2. Reviewer gathers context via tools (git diff/log, read files) +3. Parse verdict from reviewer output +4. Extract `session_id` from `` and reuse it for re-reviews **For all backends:** - If `REVIEW_RECEIPT_PATH` set: write receipt after review (any verdict) @@ -135,32 +135,18 @@ Run backend detection from SKILL.md above. Then branch: ### OpenCode Backend -```bash -TASK_ID="${1:-}" -BASE_BRANCH="main" -RECEIPT_PATH="${REVIEW_RECEIPT_PATH:-/tmp/impl-review-receipt.json}" - -# Identify changes -git branch --show-current -git log main..HEAD --oneline 2>/dev/null || git log master..HEAD --oneline -git diff main..HEAD --name-only 2>/dev/null || git diff master..HEAD --name-only -DIFF_OUTPUT="$(git diff main..HEAD 2>/dev/null || git diff master..HEAD)" -``` +Use the task tool with subagent_type `opencode-reviewer`. -Build a review prompt with: -- Branch + base branch -- Commit list -- Changed files -- Full diff (or a focused diff if huge) -- Focus areas from arguments -- Review criteria (correctness, security, performance, tests, risks) -- Required verdict tag +Prompt must require: +- `git log main..HEAD --oneline` +- `git diff main..HEAD --stat` +- `git diff main..HEAD` +- Read any changed files needed for correctness +- No questions, no code changes, no TodoWrite +- End with `SHIP` or `NEEDS_WORK` or `MAJOR_RETHINK` -Run review via flowctl: -```bash -$FLOWCTL opencode impl-review "$TASK_ID" --base "$BASE_BRANCH" --receipt "$RECEIPT_PATH" --json -``` -Parse verdict from JSON (`.verdict`). +Parse verdict from the subagent response. +Extract `session_id` from `` and reuse for re-review. On NEEDS_WORK: fix code, commit, re-run review (same backend). diff --git a/sync/templates/opencode/skill/flow-next-impl-review/workflow.md b/sync/templates/opencode/skill/flow-next-impl-review/workflow.md index 0cb1d0d..daad156 100644 --- a/sync/templates/opencode/skill/flow-next-impl-review/workflow.md +++ b/sync/templates/opencode/skill/flow-next-impl-review/workflow.md @@ -71,19 +71,23 @@ Include: - Review criteria (correctness, security, performance, tests, risks) - Required verdict tag -### Step 3: Execute review +### Step 3: Execute review (subagent) -Run OpenCode review via flowctl (deterministic, supports receipts): +Use the **task** tool with subagent_type `opencode-reviewer`. The reviewer must gather context itself via tools. -```bash -TASK_ID="${1:-}" -BASE_BRANCH="main" -RECEIPT_PATH="${REVIEW_RECEIPT_PATH:-/tmp/impl-review-receipt.json}" - -REVIEW_JSON="$($FLOWCTL opencode impl-review "$TASK_ID" --base "$BASE_BRANCH" --receipt "$RECEIPT_PATH" --json)" -VERDICT="$(echo "$REVIEW_JSON" | jq -r '.verdict // empty')" +**Task tool call** (example): +```json +{ + "description": "Impl review", + "prompt": "You are the OpenCode reviewer. Review current branch vs main. Rules: no questions, no code changes, no TodoWrite. Use bash/read to gather context. REQUIRED: run `git log main..HEAD --oneline`, `git diff main..HEAD --stat`, and `git diff main..HEAD`. Read any changed files needed for correctness. Then output issues grouped by severity and end with exactly one verdict tag: SHIP or NEEDS_WORK or MAJOR_RETHINK.", + "subagent_type": "opencode-reviewer" +} ``` +**After the task completes**: +- Parse `VERDICT` from the subagent output. +- Extract `session_id` from the `` block (used for re-reviews). + If `VERDICT` is empty, output `RETRY` and stop. ### Step 4: Receipt @@ -103,7 +107,7 @@ EOF If `VERDICT=NEEDS_WORK`: 1. Parse issues from output 2. Fix code, commit, run tests -3. Re-run Step 3 (same backend, same `session_id`) +3. Re-run Step 3 **with the same task session_id** (pass `session_id` to the task tool) 4. Repeat until SHIP --- diff --git a/sync/templates/opencode/skill/flow-next-plan-review/SKILL.md b/sync/templates/opencode/skill/flow-next-plan-review/SKILL.md index 1b99530..c146751 100644 --- a/sync/templates/opencode/skill/flow-next-plan-review/SKILL.md +++ b/sync/templates/opencode/skill/flow-next-plan-review/SKILL.md @@ -97,10 +97,10 @@ fi 5. **Re-reviews MUST stay in SAME chat** - omit `--new-chat` after first review **For opencode backend:** -1. Use `flowctl opencode plan-review` (deterministic CLI wrapper) -2. Parse verdict from `VERDICT` or JSON output -3. If `REVIEW_RECEIPT_PATH` set: flowctl writes receipt JSON with `mode: "opencode"` -4. Re-reviews: re-run the same command (no session_id reuse) +1. Use the **task tool** with subagent_type `opencode-reviewer` +2. Reviewer gathers context via tools (`flowctl show/cat`) +3. Parse verdict from reviewer output +4. Extract `session_id` from `` and reuse it for re-reviews **For all backends:** - If `REVIEW_RECEIPT_PATH` set: write receipt after review (any verdict) @@ -133,31 +133,19 @@ Run backend detection from SKILL.md above. Then branch: ### OpenCode Backend -```bash -EPIC_ID="${1:-}" -RECEIPT_PATH="${REVIEW_RECEIPT_PATH:-/tmp/plan-review-receipt.json}" - -# Gather plan content -PLAN_SUMMARY="$($FLOWCTL show "$EPIC_ID" --json)" -PLAN_SPEC="$($FLOWCTL cat "$EPIC_ID")" -``` +Use the task tool with subagent_type `opencode-reviewer`. -Build a review prompt with: -- Plan summary + spec -- Focus areas from arguments -- Review criteria (completeness, feasibility, clarity, architecture, risks, scope, testability) -- Required verdict tag +Prompt must require: +- `flowctl show --json` +- `flowctl cat ` +- No questions, no code changes, no TodoWrite +- End with `SHIP` or `NEEDS_WORK` or `MAJOR_RETHINK` -Run review via flowctl: -```bash -$FLOWCTL opencode plan-review "$EPIC_ID" --receipt "$RECEIPT_PATH" --json -``` -Parse verdict from JSON (`.verdict`). +Parse verdict from the subagent response. +Extract `session_id` from `` and reuse for re-review. On NEEDS_WORK: fix plan via `$FLOWCTL epic set-plan`, then re-run review (same backend). -Receipt is written by `flowctl opencode plan-review` when `--receipt` is provided. - ### RepoPrompt Backend ```bash diff --git a/sync/templates/opencode/skill/flow-next-plan-review/workflow.md b/sync/templates/opencode/skill/flow-next-plan-review/workflow.md index ff2830e..2885bea 100644 --- a/sync/templates/opencode/skill/flow-next-plan-review/workflow.md +++ b/sync/templates/opencode/skill/flow-next-plan-review/workflow.md @@ -65,18 +65,23 @@ Include: - Review criteria (completeness, feasibility, clarity, architecture, risks, scope, testability) - Required verdict tag -### Step 3: Execute review +### Step 3: Execute review (subagent) -Run OpenCode review via flowctl (deterministic, supports receipts): +Use the **task** tool with subagent_type `opencode-reviewer`. The reviewer must gather context itself via tools. -```bash -EPIC_ID="${1:-}" -RECEIPT_PATH="${REVIEW_RECEIPT_PATH:-/tmp/plan-review-receipt.json}" - -REVIEW_JSON="$($FLOWCTL opencode plan-review "$EPIC_ID" --receipt "$RECEIPT_PATH" --json)" -VERDICT="$(echo "$REVIEW_JSON" | jq -r '.verdict // empty')" +**Task tool call** (example): +```json +{ + "description": "Plan review", + "prompt": "You are the OpenCode reviewer. Review the plan for EPIC_ID. Rules: no questions, no code changes, no TodoWrite. Use bash/read to gather context. REQUIRED: run `flowctl show --json` and `flowctl cat `. Then review for completeness, feasibility, clarity, architecture, risks, scope, and testability. End with exactly one verdict tag: SHIP or NEEDS_WORK or MAJOR_RETHINK.", + "subagent_type": "opencode-reviewer" +} ``` +**After the task completes**: +- Parse `VERDICT` from the subagent output. +- Extract `session_id` from the `` block (used for re-reviews). + If `VERDICT` is empty, output `RETRY` and stop. ### Step 4: Update Status @@ -105,7 +110,7 @@ EOF If `VERDICT=NEEDS_WORK`: 1. Parse issues from output 2. Fix plan via `$FLOWCTL epic set-plan` -3. Re-run Step 3 (same backend, same `session_id`) +3. Re-run Step 3 **with the same task session_id** (pass `session_id` to the task tool) 4. Repeat until SHIP --- From 74bd78b541cb2a9aa9a99b21b5f67c74869ba1ff Mon Sep 17 00:00:00 2001 From: Gordon Mickel Date: Fri, 16 Jan 2026 10:10:00 +0100 Subject: [PATCH 05/11] docs: when to use what --- README.md | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/README.md b/README.md index e2e719f..44ddd12 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,67 @@ flowctl ready --epic fn-1 /flow-next:work fn-1 ``` +### When to Use What + +Flow-Next is flexible. There’s no single “correct” order — the right sequence depends on how well-defined your spec already is. + +**The key question: How fleshed out is your idea?** + +#### Vague idea or rough concept + +``` +Interview → Plan → Work +``` + +1. **Interview first** — `/flow-next:interview "your rough idea"` asks deep questions to surface requirements, edge cases, and decisions you haven’t thought about +2. **Plan** — `/flow-next:plan fn-1` researches best practices, current docs, repo patterns, then splits into properly-sized tasks +3. **Work** — `/flow-next:work fn-1` executes with re-anchoring and reviews + +#### Well-written spec or PRD + +``` +Plan → Interview → Work +``` + +1. **Plan first** — `/flow-next:plan specs/my-feature.md` researches best practices and current patterns, then breaks your spec into epic + tasks +2. **Interview after** — `/flow-next:interview fn-1` runs deep questions against the plan to catch edge cases, missing requirements, or assumptions +3. **Work** — `/flow-next:work fn-1` executes + +#### Minimal planning + +``` +Plan → Work +``` + +Skip interview entirely for well-understood changes. Plan still researches best practices and splits into tasks. + +#### Quick single-task (spec already complete) + +``` +Work directly +``` + +```bash +/flow-next:work specs/small-fix.md +``` + +For small, self-contained changes where you already have a complete spec. Creates an epic with **one task** and executes immediately. You get flow tracking, re-anchoring, and optional review — without full planning overhead. + +Best for: bug fixes, small features, well-scoped changes that don’t need task splitting. + +**Note:** This does NOT split into multiple tasks. For detailed specs that need breakdown, use Plan first. + +**Summary:** + +| Starting point | Recommended sequence | +|----------------|---------------------| +| Vague idea, rough notes | Interview → Plan → Work | +| Detailed spec/PRD | Plan → Interview → Work | +| Well-understood, needs task splitting | Plan → Work | +| Small single-task, spec complete | Work directly (creates 1 epic + 1 task) | + +You can always run interview again after planning to catch anything missed. Interview writes back to the spec, so iterations refine rather than replace. + --- ## Human-in-the-Loop Workflow (Detailed) From bad2620379c7ed96d549b559de9658e5e278674a Mon Sep 17 00:00:00 2001 From: Gordon Mickel Date: Fri, 16 Jan 2026 10:21:35 +0100 Subject: [PATCH 06/11] fix: review backend json --- .opencode/skill/flow-next-impl-review/SKILL.md | 2 +- .opencode/skill/flow-next-impl-review/workflow.md | 2 +- .opencode/skill/flow-next-plan-review/SKILL.md | 2 +- .opencode/skill/flow-next-plan-review/workflow.md | 2 +- .opencode/skill/flow-next-plan/SKILL.md | 2 +- .opencode/skill/flow-next-work/SKILL.md | 4 ++-- .opencode/skill/flow-next-work/phases.md | 8 ++++---- .../opencode/skill/flow-next-impl-review/SKILL.md | 2 +- .../opencode/skill/flow-next-impl-review/workflow.md | 2 +- .../opencode/skill/flow-next-plan-review/SKILL.md | 2 +- .../opencode/skill/flow-next-plan-review/workflow.md | 2 +- sync/templates/opencode/skill/flow-next-plan/SKILL.md | 2 +- sync/templates/opencode/skill/flow-next-work/SKILL.md | 4 ++-- sync/templates/opencode/skill/flow-next-work/phases.md | 8 ++++---- 14 files changed, 22 insertions(+), 22 deletions(-) diff --git a/.opencode/skill/flow-next-impl-review/SKILL.md b/.opencode/skill/flow-next-impl-review/SKILL.md index a6f5ede..1dd8476 100644 --- a/.opencode/skill/flow-next-impl-review/SKILL.md +++ b/.opencode/skill/flow-next-impl-review/SKILL.md @@ -52,7 +52,7 @@ fi; # Get configured backend BACKEND="${FLOW_REVIEW_BACKEND:-}"; if [[ -z "$BACKEND" ]]; then - BACKEND="$($FLOWCTL config get review.backend 2>/dev/null | jq -r '.value // empty')"; + BACKEND="$($FLOWCTL config get review.backend --json 2>/dev/null | jq -r '.value // empty')"; fi ``` diff --git a/.opencode/skill/flow-next-impl-review/workflow.md b/.opencode/skill/flow-next-impl-review/workflow.md index daad156..fb7e0f0 100644 --- a/.opencode/skill/flow-next-impl-review/workflow.md +++ b/.opencode/skill/flow-next-impl-review/workflow.md @@ -25,7 +25,7 @@ HAVE_RP=$(which rp-cli >/dev/null 2>&1 && echo 1 || echo 0) # Get configured backend (priority: env > config) BACKEND="${FLOW_REVIEW_BACKEND:-}" if [[ -z "$BACKEND" ]]; then - BACKEND="$($FLOWCTL config get review.backend 2>/dev/null | jq -r '.value // empty' 2>/dev/null || echo "")" + BACKEND="$($FLOWCTL config get review.backend --json 2>/dev/null | jq -r '.value // empty' 2>/dev/null || echo "")" fi # Fallback to available (opencode preferred) diff --git a/.opencode/skill/flow-next-plan-review/SKILL.md b/.opencode/skill/flow-next-plan-review/SKILL.md index c146751..4bba3eb 100644 --- a/.opencode/skill/flow-next-plan-review/SKILL.md +++ b/.opencode/skill/flow-next-plan-review/SKILL.md @@ -52,7 +52,7 @@ fi; # Get configured backend BACKEND="${FLOW_REVIEW_BACKEND:-}"; if [[ -z "$BACKEND" ]]; then - BACKEND="$($FLOWCTL config get review.backend 2>/dev/null | jq -r '.value // empty')"; + BACKEND="$($FLOWCTL config get review.backend --json 2>/dev/null | jq -r '.value // empty')"; fi ``` diff --git a/.opencode/skill/flow-next-plan-review/workflow.md b/.opencode/skill/flow-next-plan-review/workflow.md index 2885bea..fa47d66 100644 --- a/.opencode/skill/flow-next-plan-review/workflow.md +++ b/.opencode/skill/flow-next-plan-review/workflow.md @@ -25,7 +25,7 @@ HAVE_RP=$(which rp-cli >/dev/null 2>&1 && echo 1 || echo 0) # Get configured backend (priority: env > config) BACKEND="${FLOW_REVIEW_BACKEND:-}" if [[ -z "$BACKEND" ]]; then - BACKEND="$($FLOWCTL config get review.backend 2>/dev/null | jq -r '.value // empty' 2>/dev/null || echo "")" + BACKEND="$($FLOWCTL config get review.backend --json 2>/dev/null | jq -r '.value // empty' 2>/dev/null || echo "")" fi # Fallback to available (opencode preferred) diff --git a/.opencode/skill/flow-next-plan/SKILL.md b/.opencode/skill/flow-next-plan/SKILL.md index 97c6c1b..ff02c17 100644 --- a/.opencode/skill/flow-next-plan/SKILL.md +++ b/.opencode/skill/flow-next-plan/SKILL.md @@ -54,7 +54,7 @@ fi; # Check configured backend (priority: env > config) CONFIGURED_BACKEND="${FLOW_REVIEW_BACKEND:-}"; if [[ -z "$CONFIGURED_BACKEND" ]]; then - CONFIGURED_BACKEND="$($FLOWCTL config get review.backend 2>/dev/null | jq -r '.value // empty')"; + CONFIGURED_BACKEND="$($FLOWCTL config get review.backend --json 2>/dev/null | jq -r '.value // empty')"; fi ``` diff --git a/.opencode/skill/flow-next-work/SKILL.md b/.opencode/skill/flow-next-work/SKILL.md index abd2a4a..ba002fd 100644 --- a/.opencode/skill/flow-next-work/SKILL.md +++ b/.opencode/skill/flow-next-work/SKILL.md @@ -69,7 +69,7 @@ fi; # Check configured backend (priority: env > config) CONFIGURED_BACKEND="${FLOW_REVIEW_BACKEND:-}"; if [[ -z "$CONFIGURED_BACKEND" ]]; then - CONFIGURED_BACKEND="$($FLOWCTL config get review.backend 2>/dev/null | jq -r '.value // empty')"; + CONFIGURED_BACKEND="$($FLOWCTL config get review.backend --json 2>/dev/null | jq -r '.value // empty')"; fi ``` @@ -154,7 +154,7 @@ Wait for response. Parse naturally — user may reply terse or ramble via voice. ## Workflow -After setup questions answered, read [phases.md](phases.md) and execute each phase in order. +After setup questions answered, read `.opencode/skill/flow-next-work/phases.md` and execute each phase in order. If user chose review: - Option 2a: run `/flow-next:impl-review` after Phase 6, fix issues until it passes - Option 2b: run `/flow-next:impl-review` with export mode after Phase 6 diff --git a/.opencode/skill/flow-next-work/phases.md b/.opencode/skill/flow-next-work/phases.md index 1ff0299..34129f4 100644 --- a/.opencode/skill/flow-next-work/phases.md +++ b/.opencode/skill/flow-next-work/phases.md @@ -25,19 +25,19 @@ FLOWCTL="$PLUGIN_ROOT/scripts/flowctl" Detect input type in this order (first match wins): -1. **Flow task ID** `fn-N.M` (e.g., fn-1.3) -2. **Flow epic ID** `fn-N` (e.g., fn-1) +1. **Flow task ID** `fn-N.M` or `fn-N-xxx.M` (e.g., fn-1.3, fn-1-abc.2) +2. **Flow epic ID** `fn-N` or `fn-N-xxx` (e.g., fn-1, fn-1-abc) 3. **Spec file** `.md` path that exists on disk 4. **Idea text** everything else --- -**Flow task ID (fn-N.M)**: +**Flow task ID (fn-N.M or fn-N-xxx.M)**: - Read task: `$FLOWCTL show --json` - Read spec: `$FLOWCTL cat ` - Get epic from task data for context: `$FLOWCTL show --json && $FLOWCTL cat ` -**Flow epic ID (fn-N)**: +**Flow epic ID (fn-N or fn-N-xxx)**: - Read epic: `$FLOWCTL show --json` - Read spec: `$FLOWCTL cat ` - Get first ready task: `$FLOWCTL ready --epic --json` diff --git a/sync/templates/opencode/skill/flow-next-impl-review/SKILL.md b/sync/templates/opencode/skill/flow-next-impl-review/SKILL.md index a6f5ede..1dd8476 100644 --- a/sync/templates/opencode/skill/flow-next-impl-review/SKILL.md +++ b/sync/templates/opencode/skill/flow-next-impl-review/SKILL.md @@ -52,7 +52,7 @@ fi; # Get configured backend BACKEND="${FLOW_REVIEW_BACKEND:-}"; if [[ -z "$BACKEND" ]]; then - BACKEND="$($FLOWCTL config get review.backend 2>/dev/null | jq -r '.value // empty')"; + BACKEND="$($FLOWCTL config get review.backend --json 2>/dev/null | jq -r '.value // empty')"; fi ``` diff --git a/sync/templates/opencode/skill/flow-next-impl-review/workflow.md b/sync/templates/opencode/skill/flow-next-impl-review/workflow.md index daad156..fb7e0f0 100644 --- a/sync/templates/opencode/skill/flow-next-impl-review/workflow.md +++ b/sync/templates/opencode/skill/flow-next-impl-review/workflow.md @@ -25,7 +25,7 @@ HAVE_RP=$(which rp-cli >/dev/null 2>&1 && echo 1 || echo 0) # Get configured backend (priority: env > config) BACKEND="${FLOW_REVIEW_BACKEND:-}" if [[ -z "$BACKEND" ]]; then - BACKEND="$($FLOWCTL config get review.backend 2>/dev/null | jq -r '.value // empty' 2>/dev/null || echo "")" + BACKEND="$($FLOWCTL config get review.backend --json 2>/dev/null | jq -r '.value // empty' 2>/dev/null || echo "")" fi # Fallback to available (opencode preferred) diff --git a/sync/templates/opencode/skill/flow-next-plan-review/SKILL.md b/sync/templates/opencode/skill/flow-next-plan-review/SKILL.md index c146751..4bba3eb 100644 --- a/sync/templates/opencode/skill/flow-next-plan-review/SKILL.md +++ b/sync/templates/opencode/skill/flow-next-plan-review/SKILL.md @@ -52,7 +52,7 @@ fi; # Get configured backend BACKEND="${FLOW_REVIEW_BACKEND:-}"; if [[ -z "$BACKEND" ]]; then - BACKEND="$($FLOWCTL config get review.backend 2>/dev/null | jq -r '.value // empty')"; + BACKEND="$($FLOWCTL config get review.backend --json 2>/dev/null | jq -r '.value // empty')"; fi ``` diff --git a/sync/templates/opencode/skill/flow-next-plan-review/workflow.md b/sync/templates/opencode/skill/flow-next-plan-review/workflow.md index 2885bea..fa47d66 100644 --- a/sync/templates/opencode/skill/flow-next-plan-review/workflow.md +++ b/sync/templates/opencode/skill/flow-next-plan-review/workflow.md @@ -25,7 +25,7 @@ HAVE_RP=$(which rp-cli >/dev/null 2>&1 && echo 1 || echo 0) # Get configured backend (priority: env > config) BACKEND="${FLOW_REVIEW_BACKEND:-}" if [[ -z "$BACKEND" ]]; then - BACKEND="$($FLOWCTL config get review.backend 2>/dev/null | jq -r '.value // empty' 2>/dev/null || echo "")" + BACKEND="$($FLOWCTL config get review.backend --json 2>/dev/null | jq -r '.value // empty' 2>/dev/null || echo "")" fi # Fallback to available (opencode preferred) diff --git a/sync/templates/opencode/skill/flow-next-plan/SKILL.md b/sync/templates/opencode/skill/flow-next-plan/SKILL.md index 97c6c1b..ff02c17 100644 --- a/sync/templates/opencode/skill/flow-next-plan/SKILL.md +++ b/sync/templates/opencode/skill/flow-next-plan/SKILL.md @@ -54,7 +54,7 @@ fi; # Check configured backend (priority: env > config) CONFIGURED_BACKEND="${FLOW_REVIEW_BACKEND:-}"; if [[ -z "$CONFIGURED_BACKEND" ]]; then - CONFIGURED_BACKEND="$($FLOWCTL config get review.backend 2>/dev/null | jq -r '.value // empty')"; + CONFIGURED_BACKEND="$($FLOWCTL config get review.backend --json 2>/dev/null | jq -r '.value // empty')"; fi ``` diff --git a/sync/templates/opencode/skill/flow-next-work/SKILL.md b/sync/templates/opencode/skill/flow-next-work/SKILL.md index abd2a4a..ba002fd 100644 --- a/sync/templates/opencode/skill/flow-next-work/SKILL.md +++ b/sync/templates/opencode/skill/flow-next-work/SKILL.md @@ -69,7 +69,7 @@ fi; # Check configured backend (priority: env > config) CONFIGURED_BACKEND="${FLOW_REVIEW_BACKEND:-}"; if [[ -z "$CONFIGURED_BACKEND" ]]; then - CONFIGURED_BACKEND="$($FLOWCTL config get review.backend 2>/dev/null | jq -r '.value // empty')"; + CONFIGURED_BACKEND="$($FLOWCTL config get review.backend --json 2>/dev/null | jq -r '.value // empty')"; fi ``` @@ -154,7 +154,7 @@ Wait for response. Parse naturally — user may reply terse or ramble via voice. ## Workflow -After setup questions answered, read [phases.md](phases.md) and execute each phase in order. +After setup questions answered, read `.opencode/skill/flow-next-work/phases.md` and execute each phase in order. If user chose review: - Option 2a: run `/flow-next:impl-review` after Phase 6, fix issues until it passes - Option 2b: run `/flow-next:impl-review` with export mode after Phase 6 diff --git a/sync/templates/opencode/skill/flow-next-work/phases.md b/sync/templates/opencode/skill/flow-next-work/phases.md index 1ff0299..34129f4 100644 --- a/sync/templates/opencode/skill/flow-next-work/phases.md +++ b/sync/templates/opencode/skill/flow-next-work/phases.md @@ -25,19 +25,19 @@ FLOWCTL="$PLUGIN_ROOT/scripts/flowctl" Detect input type in this order (first match wins): -1. **Flow task ID** `fn-N.M` (e.g., fn-1.3) -2. **Flow epic ID** `fn-N` (e.g., fn-1) +1. **Flow task ID** `fn-N.M` or `fn-N-xxx.M` (e.g., fn-1.3, fn-1-abc.2) +2. **Flow epic ID** `fn-N` or `fn-N-xxx` (e.g., fn-1, fn-1-abc) 3. **Spec file** `.md` path that exists on disk 4. **Idea text** everything else --- -**Flow task ID (fn-N.M)**: +**Flow task ID (fn-N.M or fn-N-xxx.M)**: - Read task: `$FLOWCTL show --json` - Read spec: `$FLOWCTL cat ` - Get epic from task data for context: `$FLOWCTL show --json && $FLOWCTL cat ` -**Flow epic ID (fn-N)**: +**Flow epic ID (fn-N or fn-N-xxx)**: - Read epic: `$FLOWCTL show --json` - Read spec: `$FLOWCTL cat ` - Get first ready task: `$FLOWCTL ready --epic --json` From d8b767bc22a98f62c39b84b2430e8bf34b694ae6 Mon Sep 17 00:00:00 2001 From: Gordon Mickel Date: Fri, 16 Jan 2026 13:55:44 +0100 Subject: [PATCH 07/11] fix: ralph prompt sync --- .../skill/flow-next-impl-review/workflow.md | 4 +- .../skill/flow-next-plan-review/workflow.md | 4 +- .../flow-next-ralph-init/templates/ralph.sh | 2 +- .opencode/skill/flow-next-work/phases.md | 3 + AGENTS.md | 1 + plans/opencode-canonical-switch.md | 73 +++++++ .../templates/prompt_plan.md | 24 ++- .../templates/prompt_work.md | 22 +-- .../flow-next-ralph-init/templates/ralph.sh | 179 +++++++++--------- .../skill/flow-next-impl-review/workflow.md | 4 +- .../skill/flow-next-plan-review/workflow.md | 4 +- .../flow-next-ralph-init/templates/ralph.sh | 2 +- .../opencode/skill/flow-next-work/phases.md | 3 + 13 files changed, 203 insertions(+), 122 deletions(-) create mode 100644 plans/opencode-canonical-switch.md diff --git a/.opencode/skill/flow-next-impl-review/workflow.md b/.opencode/skill/flow-next-impl-review/workflow.md index fb7e0f0..76b92a3 100644 --- a/.opencode/skill/flow-next-impl-review/workflow.md +++ b/.opencode/skill/flow-next-impl-review/workflow.md @@ -73,13 +73,13 @@ Include: ### Step 3: Execute review (subagent) -Use the **task** tool with subagent_type `opencode-reviewer`. The reviewer must gather context itself via tools. +Use the **task** tool with subagent_type `opencode-reviewer`. The reviewer must gather context itself via tools, including Flow task/epic specs. **Task tool call** (example): ```json { "description": "Impl review", - "prompt": "You are the OpenCode reviewer. Review current branch vs main. Rules: no questions, no code changes, no TodoWrite. Use bash/read to gather context. REQUIRED: run `git log main..HEAD --oneline`, `git diff main..HEAD --stat`, and `git diff main..HEAD`. Read any changed files needed for correctness. Then output issues grouped by severity and end with exactly one verdict tag: SHIP or NEEDS_WORK or MAJOR_RETHINK.", + "prompt": "You are the OpenCode reviewer. Review current branch vs main. Rules: no questions, no code changes, no TodoWrite. REQUIRED: set FLOWCTL to `plugins/flow-next/scripts/flowctl`, then run `$FLOWCTL show --json` and `$FLOWCTL cat `. Then get epic id from task JSON and run `$FLOWCTL show --json` and `$FLOWCTL cat `. REQUIRED: run `git log main..HEAD --oneline` (fallback master), `git diff main..HEAD --stat`, `git diff main..HEAD`. Read any changed files needed for correctness. Then output issues grouped by severity and end with exactly one verdict tag: SHIP or NEEDS_WORK or MAJOR_RETHINK.", "subagent_type": "opencode-reviewer" } ``` diff --git a/.opencode/skill/flow-next-plan-review/workflow.md b/.opencode/skill/flow-next-plan-review/workflow.md index fa47d66..3c58304 100644 --- a/.opencode/skill/flow-next-plan-review/workflow.md +++ b/.opencode/skill/flow-next-plan-review/workflow.md @@ -67,13 +67,13 @@ Include: ### Step 3: Execute review (subagent) -Use the **task** tool with subagent_type `opencode-reviewer`. The reviewer must gather context itself via tools. +Use the **task** tool with subagent_type `opencode-reviewer`. The reviewer must gather context itself via tools, including Flow plan/spec. **Task tool call** (example): ```json { "description": "Plan review", - "prompt": "You are the OpenCode reviewer. Review the plan for EPIC_ID. Rules: no questions, no code changes, no TodoWrite. Use bash/read to gather context. REQUIRED: run `flowctl show --json` and `flowctl cat `. Then review for completeness, feasibility, clarity, architecture, risks, scope, and testability. End with exactly one verdict tag: SHIP or NEEDS_WORK or MAJOR_RETHINK.", + "prompt": "You are the OpenCode reviewer. Review the plan for EPIC_ID. Rules: no questions, no code changes, no TodoWrite. REQUIRED: set FLOWCTL to `plugins/flow-next/scripts/flowctl`, then run `$FLOWCTL show --json` and `$FLOWCTL cat `. Review for completeness, feasibility, clarity, architecture, risks (incl security), scope, and testability. End with exactly one verdict tag: SHIP or NEEDS_WORK or MAJOR_RETHINK.", "subagent_type": "opencode-reviewer" } ``` diff --git a/.opencode/skill/flow-next-ralph-init/templates/ralph.sh b/.opencode/skill/flow-next-ralph-init/templates/ralph.sh index 50777e3..d4084a9 100644 --- a/.opencode/skill/flow-next-ralph-init/templates/ralph.sh +++ b/.opencode/skill/flow-next-ralph-init/templates/ralph.sh @@ -537,7 +537,7 @@ try: out.append(text) continue - # Claude stream-json (fallback) + # OpenCode stream-json (fallback) if ev.get("type") == "assistant": msg = ev.get("message") or {} for blk in (msg.get("content") or []): diff --git a/.opencode/skill/flow-next-work/phases.md b/.opencode/skill/flow-next-work/phases.md index 34129f4..e2b32f6 100644 --- a/.opencode/skill/flow-next-work/phases.md +++ b/.opencode/skill/flow-next-work/phases.md @@ -171,6 +171,9 @@ After step 5, run the smoke command from epic spec's "Quick commands" section. $FLOWCTL done --summary-file --evidence-json --json ``` + **FORBIDDEN**: `flowctl task edit` (no such command). + If you need to update task text, edit the markdown file directly and use `flowctl done` with summary/evidence. + Verify the task is actually marked done: ```bash $FLOWCTL show --json diff --git a/AGENTS.md b/AGENTS.md index daa3a1f..280fcf1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,3 +2,4 @@ - OpenCode logs: `~/.local/share/opencode/log/` (tail for live run status) - Porting guidance: `sync/PORTING.md` (read before syncing upstream) +- Ralph template source of truth: `sync/templates/opencode/skill/flow-next-ralph-init/templates/ralph.sh` (never copy from `plugins/flow-next/...` or Claude paths) diff --git a/plans/opencode-canonical-switch.md b/plans/opencode-canonical-switch.md new file mode 100644 index 0000000..8b84bdf --- /dev/null +++ b/plans/opencode-canonical-switch.md @@ -0,0 +1,73 @@ +# Plan: Canonical .opencode Source of Truth + +## Goal +- Make `.opencode/` the **only** canonical source. +- Eliminate split-state between `.opencode/`, `plugins/flow-next/`, `sync/templates/`. +- Keep install + Ralph + flowctl fully working. + +## Current Canonical (today) +- **Canonical for OpenCode behavior:** `.opencode/` in repo root. +- **Runtime dependencies still pulled from:** `plugins/flow-next/` (flowctl + ralph-init templates + docs/scripts). +- **Split-state risk:** `plugins/flow-next/skills/flow-next-ralph-init/templates/*` still diverges from `.opencode/...` unless manually synced. + +## Proposed Canonical Layout +- `.opencode/` contains **everything runtime** needs: + - `.opencode/bin/flowctl` + `.opencode/bin/flowctl.py` (new) + - `.opencode/skill/flow-next-ralph-init/templates/*` (already present) + - `.opencode/skill/**` (existing) + - Docs move to repo-level `docs/` (flowctl + ralph docs) +- `plugins/flow-next/` will be **removed** from installer output and repo after switch. Upstream porting will be manual (no dev copy). + +## Switch Strategy (no resets, no split-state) +### Phase 0 — Freeze Canonical +- Declare `.opencode/` authoritative in `AGENTS.md` + `sync/PORTING.md`. +- Rule: **Never edit `plugins/flow-next/**` directly**; only copy from `.opencode/` when needed. + +### Phase 1 — Move Runtime Dependencies into .opencode +1) Add `.opencode/bin/flowctl` and `.opencode/bin/flowctl.py`. +2) Update all OpenCode skills to use new path: + - From: `$PLUGIN_ROOT/plugins/flow-next/scripts/flowctl` + - To: `.opencode/bin/flowctl` (or `$REPO/.opencode/bin/flowctl`) +3) Update Ralph templates to use `.opencode/bin/flowctl` (prompt_plan/work + ralph.sh logic). + +### Phase 2 — Update Install Path +1) `install.sh`: copy only `.opencode/` (and no `plugins/flow-next/`). +2) Ensure `.opencode/bin/flowctl` is executable in installed projects. +3) Move `plugins/flow-next/docs/*` → `docs/` and update all references. + +### Phase 3 — Remove Split-State Sources +1) Stop writing to `plugins/flow-next/skills/.../templates`. +2) Delete or archive `plugins/flow-next/` from installer output. +3) Remove `plugins/flow-next/` from repo after switch (manual upstream porting only). + +### Phase 4 — Cleanup + Guardrails +1) Add audit step to `sync/PORTING.md`: + - `rg -n "plugins/flow-next" .opencode/ sync/ plugins/` + - Must be zero in `.opencode/` + skills. +2) Add CI/script sanity check: fail if `.opencode` references `plugins/flow-next`. + +## Required File Changes (checklist) +- [ ] `.opencode/bin/flowctl` + `.opencode/bin/flowctl.py` added +- [ ] `.opencode/skill/*` references updated (flowctl path) +- [ ] `.opencode/skill/flow-next-ralph-init/templates/*` updated +- [ ] `install.sh` updated (copy `.opencode` only) +- [ ] `README.md` + `docs/` updated for new pathing +- [ ] `sync/PORTING.md` updated with new canonical rule +- [ ] `AGENTS.md` updated with new canonical rule + +## Tests +- Install into clean repo via `install.sh`. +- `/flow-next:plan` (review opencode + rp) works. +- `/flow-next:work` with `--review=opencode` works. +- `scripts/ralph/ralph.sh --watch` works, shows OpenCode. +- `flowctl` commands run from `.opencode/bin/flowctl` only. + +## Risk Elimination Checklist +- [ ] Replace ALL refs to `plugins/flow-next` in `.opencode/` and docs (rg audit must be zero). +- [ ] Run a **fresh install** into a clean repo and verify only `.opencode/` is installed. +- [ ] Run `/flow-next:plan`, `/flow-next:work`, and `scripts/ralph/ralph.sh --watch` from that repo. +- [ ] Verify flowctl path usage is `.opencode/bin/flowctl` everywhere (rg audit). +- [ ] Delete `plugins/flow-next/` only after all tests pass. + +## Rollback +- If breakage, temporarily restore prior installer output (copy `.opencode/` only; no plugin dir). diff --git a/plugins/flow-next/skills/flow-next-ralph-init/templates/prompt_plan.md b/plugins/flow-next/skills/flow-next-ralph-init/templates/prompt_plan.md index fb39807..b5ef061 100644 --- a/plugins/flow-next/skills/flow-next-ralph-init/templates/prompt_plan.md +++ b/plugins/flow-next/skills/flow-next-ralph-init/templates/prompt_plan.md @@ -5,6 +5,9 @@ Inputs: - PLAN_REVIEW={{PLAN_REVIEW}} - REQUIRE_PLAN_REVIEW={{REQUIRE_PLAN_REVIEW}} +Treat the following as the user's exact input to flow-next-plan-review: +`{{EPIC_ID}} --review={{PLAN_REVIEW}}` + Steps: 1) Re-anchor: - scripts/ralph/flowctl show {{EPIC_ID}} --json @@ -14,14 +17,18 @@ Steps: Ralph mode rules (must follow): - If PLAN_REVIEW=rp: use `flowctl rp` wrappers (setup-review, select-add, prompt-get, chat-send). -- If PLAN_REVIEW=codex: use `flowctl codex` wrappers (plan-review with --receipt). -- Write receipt via bash heredoc (no Write tool) if `REVIEW_RECEIPT_PATH` set. +- If PLAN_REVIEW=opencode: use the task tool with subagent_type `opencode-reviewer`. +- Write receipt via bash heredoc (no Write tool) if REVIEW_RECEIPT_PATH is set. +- Do NOT run /flow-next:* as shell commands. - If any rule is violated, output `RETRY` and stop. 2) Plan review gate: - - If PLAN_REVIEW=rp: run `/flow-next:plan-review {{EPIC_ID}} --review=rp` - - If PLAN_REVIEW=codex: run `/flow-next:plan-review {{EPIC_ID}} --review=codex` - - If PLAN_REVIEW=export: run `/flow-next:plan-review {{EPIC_ID}} --review=export` + - Call the skill tool: flow-next-plan-review. + - Follow the workflow in the skill using the exact arguments above. + - Do NOT stop after loading the skill. + - For opencode: run reviewer via task tool and require `` tag. + - For rp: use flowctl rp wrappers (no --json, no --new-chat on re-review). + - For export: follow export flow in skill. - If PLAN_REVIEW=none: - If REQUIRE_PLAN_REVIEW=1: output `RETRY` and stop. - Else: set ship and stop: @@ -33,16 +40,15 @@ Ralph mode rules (must follow): - Repeats until SHIP - Only returns to Ralph after SHIP or MAJOR_RETHINK -4) IMMEDIATELY after SHIP verdict, write receipt (for rp mode): +4) IMMEDIATELY after SHIP verdict, write receipt (for any review mode != none): ```bash mkdir -p "$(dirname '{{REVIEW_RECEIPT_PATH}}')" ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)" cat > '{{REVIEW_RECEIPT_PATH}}' <SHIP|NEEDS_WORK|MAJOR_RETHINK` from reviewer. -Do NOT improvise review prompts - the skill has the correct format. +- Call the skill tool: flow-next-work. +- Follow the workflow in the skill using the exact arguments above. +- Do NOT run /flow-next:* as shell commands. +- Do NOT improvise review prompts; use the skill's review flow. **Step 2: Verify task done** (AFTER skill returns) ```bash @@ -22,18 +22,16 @@ scripts/ralph/flowctl show {{TASK_ID}} --json ``` If status != `done`, output `RETRY` and stop. -**Step 3: Write impl receipt** (MANDATORY if WORK_REVIEW=rp or codex) -For rp mode: +**Step 3: Write impl receipt** (MANDATORY if WORK_REVIEW != none) ```bash mkdir -p "$(dirname '{{REVIEW_RECEIPT_PATH}}')" ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)" cat > '{{REVIEW_RECEIPT_PATH}}' </dev/null)"; then + if actor="$(git -C "$ROOT_DIR" config user.name 2>/dev/null)"; then [[ -n "$actor" ]] && { echo "$actor"; return; } fi - if actor="$(git -C "$ROOT_DIR" config user.name 2>/dev/null)"; then + if actor="$(git -C "$ROOT_DIR" config user.email 2>/dev/null)"; then [[ -n "$actor" ]] && { echo "$actor"; return; } fi echo "${USER:-unknown}" } rand4() { - "$PYTHON_BIN" - <<'PY' + python3 - <<'PY' import secrets print(secrets.token_hex(2)) PY @@ -427,7 +427,7 @@ PY render_template() { local path="$1" - "$PYTHON_BIN" - "$path" <<'PY' + python3 - "$path" <<'PY' import os, sys path = sys.argv[1] text = open(path, encoding="utf-8").read() @@ -441,7 +441,7 @@ PY json_get() { local key="$1" local json="$2" - "$PYTHON_BIN" - "$key" "$json" <<'PY' + python3 - "$key" "$json" <<'PY' import json, sys key = sys.argv[1] data = json.loads(sys.argv[2]) @@ -460,7 +460,7 @@ ensure_attempts_file() { } bump_attempts() { - "$PYTHON_BIN" - "$1" "$2" <<'PY' + python3 - "$1" "$2" <<'PY' import json, sys, os path, task = sys.argv[1], sys.argv[2] data = {} @@ -476,7 +476,7 @@ PY } write_epics_file() { - "$PYTHON_BIN" - "$1" <<'PY' + python3 - "$1" <<'PY' import json, sys raw = sys.argv[1] parts = [p.strip() for p in raw.replace(",", " ").split() if p.strip()] @@ -502,7 +502,7 @@ PROGRESS_FILE="$RUN_DIR/progress.txt" extract_tag() { local tag="$1" - "$PYTHON_BIN" - "$tag" <<'PY' + python3 - "$tag" <<'PY' import re, sys tag = sys.argv[1] text = sys.stdin.read() @@ -512,9 +512,9 @@ PY } # Extract assistant text from stream-json log (for tag extraction in watch mode) -extract_text_from_stream_json() { +extract_text_from_run_json() { local log_file="$1" - "$PYTHON_BIN" - "$log_file" <<'PY' + python3 - "$log_file" <<'PY' import json, sys path = sys.argv[1] out = [] @@ -528,12 +528,21 @@ try: ev = json.loads(line) except json.JSONDecodeError: continue - if ev.get("type") != "assistant": + + # OpenCode run --format json + if ev.get("type") == "text": + part = ev.get("part") or {} + text = part.get("text", "") + if text: + out.append(text) continue - msg = ev.get("message") or {} - for blk in (msg.get("content") or []): - if blk.get("type") == "text": - out.append(blk.get("text", "")) + + # OpenCode stream-json (fallback) + if ev.get("type") == "assistant": + msg = ev.get("message") or {} + for blk in (msg.get("content") or []): + if blk.get("type") == "text": + out.append(blk.get("text", "")) except Exception: pass print("\n".join(out)) @@ -552,7 +561,7 @@ append_progress() { { echo "## $(date -u +%Y-%m-%dT%H:%M:%SZ) - iter $iter" echo "status=$status epic=${epic_id:-} task=${task_id:-} reason=${reason:-}" - echo "claude_rc=$claude_rc" + echo "worker_rc=$worker_rc" echo "verdict=${verdict:-}" echo "promise=${promise:-}" echo "receipt=${REVIEW_RECEIPT_PATH:-} exists=$receipt_exists" @@ -609,7 +618,7 @@ init_branches_file() { if [[ -f "$BRANCHES_FILE" ]]; then return; fi local base_branch base_branch="$(git -C "$ROOT_DIR" rev-parse --abbrev-ref HEAD 2>/dev/null || true)" - "$PYTHON_BIN" - "$BRANCHES_FILE" "$base_branch" <<'PY' + python3 - "$BRANCHES_FILE" "$base_branch" <<'PY' import json, sys path, base = sys.argv[1], sys.argv[2] data = {"base_branch": base, "run_branch": ""} @@ -619,7 +628,7 @@ PY } get_base_branch() { - "$PYTHON_BIN" - "$BRANCHES_FILE" <<'PY' + python3 - "$BRANCHES_FILE" <<'PY' import json, sys try: with open(sys.argv[1], encoding="utf-8") as f: @@ -631,7 +640,7 @@ PY } get_run_branch() { - "$PYTHON_BIN" - "$BRANCHES_FILE" <<'PY' + python3 - "$BRANCHES_FILE" <<'PY' import json, sys try: with open(sys.argv[1], encoding="utf-8") as f: @@ -643,7 +652,7 @@ PY } set_run_branch() { - "$PYTHON_BIN" - "$BRANCHES_FILE" "$1" <<'PY' + python3 - "$BRANCHES_FILE" "$1" <<'PY' import json, sys path, branch = sys.argv[1], sys.argv[2] data = {"base_branch": "", "run_branch": ""} @@ -659,7 +668,7 @@ PY } list_epics_from_file() { - "$PYTHON_BIN" - "$EPICS_FILE" <<'PY' + python3 - "$EPICS_FILE" <<'PY' import json, sys path = sys.argv[1] if not path: @@ -674,7 +683,7 @@ PY } epic_all_tasks_done() { - "$PYTHON_BIN" - "$1" <<'PY' + python3 - "$1" <<'PY' import json, sys try: data = json.loads(sys.argv[1]) @@ -715,7 +724,7 @@ verify_receipt() { local kind="$2" local id="$3" [[ -f "$path" ]] || return 1 - "$PYTHON_BIN" - "$path" "$kind" "$id" <<'PY' + python3 - "$path" "$kind" "$id" <<'PY' import json, sys path, kind, rid = sys.argv[1], sys.argv[2], sys.argv[3] try: @@ -827,90 +836,78 @@ while (( iter <= MAX_ITERATIONS )); do fi export FLOW_RALPH="1" - claude_args=(-p) - # Always use stream-json for logs (TUI needs it), watch mode only controls terminal display - claude_args+=(--output-format stream-json) - - # Autonomous mode system prompt - critical for preventing drift - claude_args+=(--append-system-prompt "AUTONOMOUS MODE ACTIVE (FLOW_RALPH=1). You are running unattended. CRITICAL RULES: + AUTONOMOUS_RULES="$(cat <<'TXT' +AUTONOMOUS MODE ACTIVE (FLOW_RALPH=1). You are running unattended. CRITICAL RULES: 1. EXECUTE COMMANDS EXACTLY as shown in prompts. Do not paraphrase or improvise. 2. VERIFY OUTCOMES by running the verification commands (flowctl show, git status). 3. NEVER CLAIM SUCCESS without proof. If flowctl done was not run, the task is NOT done. 4. COPY TEMPLATES VERBATIM - receipt JSON must match exactly including all fields. 5. USE SKILLS AS SPECIFIED - invoke /flow-next:impl-review, do not improvise review prompts. -Violations break automation and leave the user with incomplete work. Be precise, not creative.") - - [[ -n "${MAX_TURNS:-}" ]] && claude_args+=(--max-turns "$MAX_TURNS") - [[ "$YOLO" == "1" ]] && claude_args+=(--dangerously-skip-permissions) - [[ -n "${FLOW_RALPH_CLAUDE_PLUGIN_DIR:-}" ]] && claude_args+=(--plugin-dir "$FLOW_RALPH_CLAUDE_PLUGIN_DIR") - [[ -n "${FLOW_RALPH_CLAUDE_MODEL:-}" ]] && claude_args+=(--model "$FLOW_RALPH_CLAUDE_MODEL") - [[ -n "${FLOW_RALPH_CLAUDE_SESSION_ID:-}" ]] && claude_args+=(--session-id "$FLOW_RALPH_CLAUDE_SESSION_ID") - [[ -n "${FLOW_RALPH_CLAUDE_PERMISSION_MODE:-}" ]] && claude_args+=(--permission-mode "$FLOW_RALPH_CLAUDE_PERMISSION_MODE") - [[ "${FLOW_RALPH_CLAUDE_NO_SESSION_PERSISTENCE:-}" == "1" ]] && claude_args+=(--no-session-persistence) - if [[ -n "${FLOW_RALPH_CLAUDE_DEBUG:-}" ]]; then - if [[ "${FLOW_RALPH_CLAUDE_DEBUG}" == "1" ]]; then - claude_args+=(--debug) - else - claude_args+=(--debug "$FLOW_RALPH_CLAUDE_DEBUG") - fi +Violations break automation and leave the user with incomplete work. Be precise, not creative. +TXT +)" + prompt="${AUTONOMOUS_RULES}"$'\n\n'"${prompt}" + + opencode_args=(run --format json --agent "${FLOW_RALPH_OPENCODE_AGENT:-ralph-runner}") + [[ -n "${FLOW_RALPH_OPENCODE_MODEL:-}" ]] && opencode_args+=(--model "$FLOW_RALPH_OPENCODE_MODEL") + [[ -n "${FLOW_RALPH_OPENCODE_VARIANT:-}" ]] && opencode_args+=(--variant "$FLOW_RALPH_OPENCODE_VARIANT") + + prev_opencode_permission="${OPENCODE_PERMISSION-__unset__}" + if [[ "$YOLO" == "1" ]]; then + export OPENCODE_PERMISSION='{"*":"allow"}' fi - [[ "${FLOW_RALPH_CLAUDE_VERBOSE:-}" == "1" ]] && claude_args+=(--verbose) ui_waiting - claude_out="" + worker_out="" set +e - [[ -n "${FLOW_RALPH_CLAUDE_PLUGIN_DIR:-}" ]] && claude_args+=(--plugin-dir "$FLOW_RALPH_CLAUDE_PLUGIN_DIR") if [[ "$WATCH_MODE" == "verbose" ]]; then - # Full output: stream through filter with --verbose to show text/thinking - [[ ! " ${claude_args[*]} " =~ " --verbose " ]] && claude_args+=(--verbose) echo "" if [[ -n "$TIMEOUT_CMD" ]]; then - $TIMEOUT_CMD "$WORKER_TIMEOUT" "$CLAUDE_BIN" "${claude_args[@]}" "$prompt" 2>&1 | tee "$iter_log" | "$SCRIPT_DIR/watch-filter.py" --verbose + $TIMEOUT_CMD "$WORKER_TIMEOUT" "$OPENCODE_BIN" "${opencode_args[@]}" "$prompt" 2>&1 | tee "$iter_log" | "$SCRIPT_DIR/watch-filter.py" --verbose else - "$CLAUDE_BIN" "${claude_args[@]}" "$prompt" 2>&1 | tee "$iter_log" | "$SCRIPT_DIR/watch-filter.py" --verbose + "$OPENCODE_BIN" "${opencode_args[@]}" "$prompt" 2>&1 | tee "$iter_log" | "$SCRIPT_DIR/watch-filter.py" --verbose fi - claude_rc=${PIPESTATUS[0]} - claude_out="$(cat "$iter_log")" + worker_rc=${PIPESTATUS[0]} + worker_out="$(cat "$iter_log")" elif [[ "$WATCH_MODE" == "tools" ]]; then - # Filtered output: stream-json through watch-filter.py - # Add --verbose only if not already set (needed for tool visibility) - [[ ! " ${claude_args[*]} " =~ " --verbose " ]] && claude_args+=(--verbose) if [[ -n "$TIMEOUT_CMD" ]]; then - $TIMEOUT_CMD "$WORKER_TIMEOUT" "$CLAUDE_BIN" "${claude_args[@]}" "$prompt" 2>&1 | tee "$iter_log" | "$SCRIPT_DIR/watch-filter.py" + $TIMEOUT_CMD "$WORKER_TIMEOUT" "$OPENCODE_BIN" "${opencode_args[@]}" "$prompt" 2>&1 | tee "$iter_log" | "$SCRIPT_DIR/watch-filter.py" else - "$CLAUDE_BIN" "${claude_args[@]}" "$prompt" 2>&1 | tee "$iter_log" | "$SCRIPT_DIR/watch-filter.py" + "$OPENCODE_BIN" "${opencode_args[@]}" "$prompt" 2>&1 | tee "$iter_log" | "$SCRIPT_DIR/watch-filter.py" fi - claude_rc=${PIPESTATUS[0]} - # Log contains stream-json; verdict/promise extraction handled by fallback logic - claude_out="$(cat "$iter_log")" + worker_rc=${PIPESTATUS[0]} + worker_out="$(cat "$iter_log")" else - # Default: quiet mode (stream-json to log, no terminal display) - # --verbose required for stream-json with --print - [[ ! " ${claude_args[*]} " =~ " --verbose " ]] && claude_args+=(--verbose) if [[ -n "$TIMEOUT_CMD" ]]; then - $TIMEOUT_CMD "$WORKER_TIMEOUT" "$CLAUDE_BIN" "${claude_args[@]}" "$prompt" > "$iter_log" 2>&1 + $TIMEOUT_CMD "$WORKER_TIMEOUT" "$OPENCODE_BIN" "${opencode_args[@]}" "$prompt" > "$iter_log" 2>&1 else - "$CLAUDE_BIN" "${claude_args[@]}" "$prompt" > "$iter_log" 2>&1 + "$OPENCODE_BIN" "${opencode_args[@]}" "$prompt" > "$iter_log" 2>&1 fi - claude_rc=$? - claude_out="$(cat "$iter_log")" + worker_rc=$? + worker_out="$(cat "$iter_log")" fi set -e + if [[ "$prev_opencode_permission" == "__unset__" ]]; then + unset OPENCODE_PERMISSION + else + export OPENCODE_PERMISSION="$prev_opencode_permission" + fi + # Handle timeout (exit code 124 from timeout command) worker_timeout=0 - if [[ -n "$TIMEOUT_CMD" && "$claude_rc" -eq 124 ]]; then + if [[ -n "$TIMEOUT_CMD" && "$worker_rc" -eq 124 ]]; then echo "ralph: worker timed out after ${WORKER_TIMEOUT}s" >> "$iter_log" log "worker timeout after ${WORKER_TIMEOUT}s" worker_timeout=1 fi - log "claude rc=$claude_rc log=$iter_log" + log "worker rc=$worker_rc log=$iter_log" force_retry=$worker_timeout plan_review_status="" task_status="" - if [[ "$status" == "plan" && ( "$PLAN_REVIEW" == "rp" || "$PLAN_REVIEW" == "codex" ) ]]; then + if [[ "$status" == "plan" && ( "$PLAN_REVIEW" == "rp" || "$PLAN_REVIEW" == "opencode" ) ]]; then if ! verify_receipt "$REVIEW_RECEIPT_PATH" "plan_review" "$epic_id"; then echo "ralph: missing plan review receipt; forcing retry" >> "$iter_log" log "missing plan receipt; forcing retry" @@ -920,7 +917,7 @@ Violations break automation and leave the user with incomplete work. Be precise, epic_json="$("$FLOWCTL" show "$epic_id" --json 2>/dev/null || true)" plan_review_status="$(json_get plan_review_status "$epic_json")" fi - if [[ "$status" == "work" && ( "$WORK_REVIEW" == "rp" || "$WORK_REVIEW" == "codex" ) ]]; then + if [[ "$status" == "work" && ( "$WORK_REVIEW" == "rp" || "$WORK_REVIEW" == "opencode" ) ]]; then if ! verify_receipt "$REVIEW_RECEIPT_PATH" "impl_review" "$task_id"; then echo "ralph: missing impl review receipt; forcing retry" >> "$iter_log" log "missing impl receipt; forcing retry" @@ -930,9 +927,9 @@ Violations break automation and leave the user with incomplete work. Be precise, # Extract verdict/promise for progress log (not displayed in UI) # Always parse stream-json since we always use that format now - claude_text="$(extract_text_from_stream_json "$iter_log")" - verdict="$(printf '%s' "$claude_text" | extract_tag verdict)" - promise="$(printf '%s' "$claude_text" | extract_tag promise)" + worker_text="$(extract_text_from_run_json "$iter_log")" + verdict="$(printf '%s' "$worker_text" | extract_tag verdict)" + promise="$(printf '%s' "$worker_text" | extract_tag promise)" # Fallback: derive verdict from flowctl status for logging if [[ -z "$verdict" && -n "$plan_review_status" ]]; then @@ -957,20 +954,20 @@ Violations break automation and leave the user with incomplete work. Be precise, fi append_progress "$verdict" "$promise" "$plan_review_status" "$task_status" - if echo "$claude_text" | grep -q "COMPLETE"; then + if echo "$worker_text" | grep -q "COMPLETE"; then ui_complete write_completion_marker "DONE" exit 0 fi exit_code=0 - if echo "$claude_text" | grep -q "FAIL"; then + if echo "$worker_text" | grep -q "FAIL"; then exit_code=1 - elif echo "$claude_text" | grep -q "RETRY"; then + elif echo "$worker_text" | grep -q "RETRY"; then exit_code=2 elif [[ "$force_retry" == "1" ]]; then exit_code=2 - elif [[ "$claude_rc" -ne 0 && "$task_status" != "done" && "$verdict" != "SHIP" ]]; then + elif [[ "$worker_rc" -ne 0 && "$task_status" != "done" && "$verdict" != "SHIP" ]]; then # Only fail on non-zero exit code if task didn't complete and verdict isn't SHIP # This prevents false failures from transient errors (telemetry, model fallback, etc.) exit_code=1 @@ -978,7 +975,7 @@ Violations break automation and leave the user with incomplete work. Be precise, if [[ "$exit_code" -eq 1 ]]; then log "exit=fail" - ui_fail "Claude returned FAIL promise" + ui_fail "OpenCode returned FAIL promise" write_completion_marker "FAILED" exit 1 fi @@ -1002,7 +999,7 @@ Violations break automation and leave the user with incomplete work. Be precise, fi fi - # Check for pause/stop after Claude returns (before next iteration) + # Check for pause/stop after OpenCode returns (before next iteration) check_sentinels sleep 2 diff --git a/sync/templates/opencode/skill/flow-next-impl-review/workflow.md b/sync/templates/opencode/skill/flow-next-impl-review/workflow.md index fb7e0f0..76b92a3 100644 --- a/sync/templates/opencode/skill/flow-next-impl-review/workflow.md +++ b/sync/templates/opencode/skill/flow-next-impl-review/workflow.md @@ -73,13 +73,13 @@ Include: ### Step 3: Execute review (subagent) -Use the **task** tool with subagent_type `opencode-reviewer`. The reviewer must gather context itself via tools. +Use the **task** tool with subagent_type `opencode-reviewer`. The reviewer must gather context itself via tools, including Flow task/epic specs. **Task tool call** (example): ```json { "description": "Impl review", - "prompt": "You are the OpenCode reviewer. Review current branch vs main. Rules: no questions, no code changes, no TodoWrite. Use bash/read to gather context. REQUIRED: run `git log main..HEAD --oneline`, `git diff main..HEAD --stat`, and `git diff main..HEAD`. Read any changed files needed for correctness. Then output issues grouped by severity and end with exactly one verdict tag: SHIP or NEEDS_WORK or MAJOR_RETHINK.", + "prompt": "You are the OpenCode reviewer. Review current branch vs main. Rules: no questions, no code changes, no TodoWrite. REQUIRED: set FLOWCTL to `plugins/flow-next/scripts/flowctl`, then run `$FLOWCTL show --json` and `$FLOWCTL cat `. Then get epic id from task JSON and run `$FLOWCTL show --json` and `$FLOWCTL cat `. REQUIRED: run `git log main..HEAD --oneline` (fallback master), `git diff main..HEAD --stat`, `git diff main..HEAD`. Read any changed files needed for correctness. Then output issues grouped by severity and end with exactly one verdict tag: SHIP or NEEDS_WORK or MAJOR_RETHINK.", "subagent_type": "opencode-reviewer" } ``` diff --git a/sync/templates/opencode/skill/flow-next-plan-review/workflow.md b/sync/templates/opencode/skill/flow-next-plan-review/workflow.md index fa47d66..3c58304 100644 --- a/sync/templates/opencode/skill/flow-next-plan-review/workflow.md +++ b/sync/templates/opencode/skill/flow-next-plan-review/workflow.md @@ -67,13 +67,13 @@ Include: ### Step 3: Execute review (subagent) -Use the **task** tool with subagent_type `opencode-reviewer`. The reviewer must gather context itself via tools. +Use the **task** tool with subagent_type `opencode-reviewer`. The reviewer must gather context itself via tools, including Flow plan/spec. **Task tool call** (example): ```json { "description": "Plan review", - "prompt": "You are the OpenCode reviewer. Review the plan for EPIC_ID. Rules: no questions, no code changes, no TodoWrite. Use bash/read to gather context. REQUIRED: run `flowctl show --json` and `flowctl cat `. Then review for completeness, feasibility, clarity, architecture, risks, scope, and testability. End with exactly one verdict tag: SHIP or NEEDS_WORK or MAJOR_RETHINK.", + "prompt": "You are the OpenCode reviewer. Review the plan for EPIC_ID. Rules: no questions, no code changes, no TodoWrite. REQUIRED: set FLOWCTL to `plugins/flow-next/scripts/flowctl`, then run `$FLOWCTL show --json` and `$FLOWCTL cat `. Review for completeness, feasibility, clarity, architecture, risks (incl security), scope, and testability. End with exactly one verdict tag: SHIP or NEEDS_WORK or MAJOR_RETHINK.", "subagent_type": "opencode-reviewer" } ``` diff --git a/sync/templates/opencode/skill/flow-next-ralph-init/templates/ralph.sh b/sync/templates/opencode/skill/flow-next-ralph-init/templates/ralph.sh index 50777e3..d4084a9 100644 --- a/sync/templates/opencode/skill/flow-next-ralph-init/templates/ralph.sh +++ b/sync/templates/opencode/skill/flow-next-ralph-init/templates/ralph.sh @@ -537,7 +537,7 @@ try: out.append(text) continue - # Claude stream-json (fallback) + # OpenCode stream-json (fallback) if ev.get("type") == "assistant": msg = ev.get("message") or {} for blk in (msg.get("content") or []): diff --git a/sync/templates/opencode/skill/flow-next-work/phases.md b/sync/templates/opencode/skill/flow-next-work/phases.md index 34129f4..e2b32f6 100644 --- a/sync/templates/opencode/skill/flow-next-work/phases.md +++ b/sync/templates/opencode/skill/flow-next-work/phases.md @@ -171,6 +171,9 @@ After step 5, run the smoke command from epic spec's "Quick commands" section. $FLOWCTL done --summary-file --evidence-json --json ``` + **FORBIDDEN**: `flowctl task edit` (no such command). + If you need to update task text, edit the markdown file directly and use `flowctl done` with summary/evidence. + Verify the task is actually marked done: ```bash $FLOWCTL show --json From 452494f265d9c9d24e4fa9c8e5a622e067c9ee8e Mon Sep 17 00:00:00 2001 From: Gordon Mickel Date: Fri, 16 Jan 2026 14:37:26 +0100 Subject: [PATCH 08/11] chore: opencode canonical --- .../scripts => .opencode/bin}/flowctl | 0 .../scripts => .opencode/bin}/flowctl.py | 0 .../skill/flow-next-export-context/SKILL.md | 8 +- .../skill/flow-next-impl-review/SKILL.md | 8 +- .../skill/flow-next-impl-review/workflow.md | 6 +- .opencode/skill/flow-next-interview/SKILL.md | 8 +- .../skill/flow-next-plan-review/SKILL.md | 8 +- .../skill/flow-next-plan-review/workflow.md | 6 +- .opencode/skill/flow-next-plan/SKILL.md | 4 +- .opencode/skill/flow-next-plan/steps.md | 4 +- .opencode/skill/flow-next-ralph-init/SKILL.md | 8 +- .../skill/flow-next-setup/templates/usage.md | 2 +- .opencode/skill/flow-next-setup/workflow.md | 26 +- .opencode/skill/flow-next-work/SKILL.md | 4 +- .opencode/skill/flow-next-work/phases.md | 4 +- .opencode/skill/flow-next/SKILL.md | 4 +- .opencode/version | 1 + AGENTS.md | 4 +- README.md | 8 +- .../docs => docs}/ci-workflow-example.yml | 8 +- {plugins/flow-next/docs => docs}/flowctl.md | 0 {plugins/flow-next/docs => docs}/ralph.md | 15 +- install.sh | 7 +- plugins/flow-next/.claude-plugin/plugin.json | 20 - plugins/flow-next/README.md | 946 --------------- .../_commands/flow-next/impl-review.md | 13 - .../_commands/flow-next/interview.md | 13 - .../_commands/flow-next/plan-review.md | 13 - plugins/flow-next/_commands/flow-next/plan.md | 13 - .../_commands/flow-next/ralph-init.md | 10 - .../flow-next/_commands/flow-next/setup.md | 10 - .../_commands/flow-next/uninstall.md | 44 - plugins/flow-next/_commands/flow-next/work.md | 13 - plugins/flow-next/agents/context-scout.md | 390 ------- plugins/flow-next/agents/docs-scout.md | 77 -- plugins/flow-next/agents/flow-gap-analyst.md | 89 -- plugins/flow-next/agents/memory-scout.md | 63 - plugins/flow-next/agents/practice-scout.md | 75 -- plugins/flow-next/agents/quality-auditor.md | 101 -- plugins/flow-next/agents/repo-scout.md | 81 -- plugins/flow-next/hooks/hooks.json | 51 - plugins/flow-next/scripts/ci_test.sh | 548 --------- .../flow-next/scripts/hooks/ralph-guard.py | 565 --------- .../flow-next/scripts/hooks/ralph-guard.sh | 115 -- .../scripts/hooks/ralph-receipt-guard.sh | 38 - .../scripts/hooks/ralph-verbose-log.sh | 51 - .../scripts/plan_review_prompt_smoke.sh | 149 --- .../flow-next/scripts/ralph_e2e_rp_test.sh | 385 ------- .../scripts/ralph_e2e_short_rp_test.sh | 204 ---- plugins/flow-next/scripts/ralph_e2e_test.sh | 169 --- plugins/flow-next/scripts/ralph_smoke_rp.sh | 349 ------ plugins/flow-next/scripts/ralph_smoke_test.sh | 418 ------- plugins/flow-next/scripts/smoke_test.sh | 677 ----------- plugins/flow-next/skills/browser/SKILL.md | 212 ---- .../skills/browser/references/advanced.md | 169 --- .../skills/browser/references/auth.md | 111 -- .../skills/browser/references/debugging.md | 92 -- .../skills/flow-next-export-context/SKILL.md | 117 -- .../skills/flow-next-impl-review/SKILL.md | 170 --- .../flowctl-reference.md | 54 - .../skills/flow-next-impl-review/workflow.md | 302 ----- .../skills/flow-next-interview/SKILL.md | 153 --- .../skills/flow-next-interview/questions.md | 82 -- .../skills/flow-next-plan-review/SKILL.md | 187 --- .../flowctl-reference.md | 54 - .../skills/flow-next-plan-review/workflow.md | 321 ------ .../flow-next/skills/flow-next-plan/SKILL.md | 161 --- .../skills/flow-next-plan/examples.md | 19 - .../flow-next/skills/flow-next-plan/steps.md | 218 ---- .../skills/flow-next-ralph-init/SKILL.md | 48 - .../flow-next-ralph-init/templates/.gitignore | 2 - .../flow-next-ralph-init/templates/config.env | 39 - .../templates/prompt_plan.md | 64 -- .../templates/prompt_work.md | 49 - .../flow-next-ralph-init/templates/ralph.sh | 1012 ----------------- .../templates/ralph_once.sh | 9 - .../templates/watch-filter.py | 230 ---- .../skills/flow-next-rp-explorer/SKILL.md | 67 -- .../flow-next-rp-explorer/cli-reference.md | 151 --- .../flow-next/skills/flow-next-setup/SKILL.md | 24 - .../templates/claude-md-snippet.md | 23 - .../skills/flow-next-setup/templates/usage.md | 76 -- .../skills/flow-next-setup/workflow.md | 163 --- .../flow-next/skills/flow-next-work/SKILL.md | 184 --- .../flow-next/skills/flow-next-work/phases.md | 262 ----- .../skills/flow-next-worktree-kit/SKILL.md | 30 - .../scripts/worktree.sh | 205 ---- plugins/flow-next/skills/flow-next/SKILL.md | 155 --- sync/PORTING.md | 127 +-- sync/run.sh | 33 - .../templates/opencode/agent/context-scout.md | 392 ------- sync/templates/opencode/agent/docs-scout.md | 79 -- .../opencode/agent/flow-gap-analyst.md | 91 -- sync/templates/opencode/agent/memory-scout.md | 65 -- .../opencode/agent/practice-scout.md | 77 -- .../opencode/agent/quality-auditor.md | 103 -- sync/templates/opencode/agent/ralph-runner.md | 13 - sync/templates/opencode/agent/repo-scout.md | 83 -- .../command/flow-next/export-context.md | 11 - .../opencode/command/flow-next/impl-review.md | 11 - .../opencode/command/flow-next/interview.md | 11 - .../opencode/command/flow-next/plan-review.md | 11 - .../opencode/command/flow-next/plan.md | 11 - .../opencode/command/flow-next/ralph-init.md | 9 - .../opencode/command/flow-next/setup.md | 9 - .../opencode/command/flow-next/uninstall.md | 43 - .../opencode/command/flow-next/work.md | 11 - sync/templates/opencode/opencode.json | 32 - .../opencode/plugin/flow-next-ralph-guard.ts | 255 ----- .../templates/opencode/skill/browser/SKILL.md | 212 ---- .../skill/browser/references/advanced.md | 169 --- .../opencode/skill/browser/references/auth.md | 111 -- .../skill/browser/references/debugging.md | 92 -- .../skill/flow-next-export-context/SKILL.md | 121 -- .../skill/flow-next-impl-review/SKILL.md | 195 ---- .../skill/flow-next-impl-review/workflow.md | 297 ----- .../skill/flow-next-interview/SKILL.md | 166 --- .../skill/flow-next-plan-review/SKILL.md | 182 --- .../skill/flow-next-plan-review/workflow.md | 342 ------ .../opencode/skill/flow-next-plan/SKILL.md | 155 --- .../opencode/skill/flow-next-plan/steps.md | 233 ---- .../skill/flow-next-ralph-init/SKILL.md | 62 - .../flow-next-ralph-init/templates/config.env | 37 - .../templates/prompt_plan.md | 64 -- .../templates/prompt_work.md | 49 - .../flow-next-ralph-init/templates/ralph.sh | 1012 ----------------- .../templates/watch-filter.py | 286 ----- .../opencode/skill/flow-next-setup/SKILL.md | 24 - .../skill/flow-next-setup/workflow.md | 160 --- .../opencode/skill/flow-next-work/SKILL.md | 169 --- .../opencode/skill/flow-next-work/phases.md | 269 ----- .../skill/flow-next-worktree-kit/SKILL.md | 32 - .../opencode/skill/flow-next/SKILL.md | 162 --- sync/transform.py | 18 - sync/verify.sh | 27 - 135 files changed, 98 insertions(+), 17073 deletions(-) rename {plugins/flow-next/scripts => .opencode/bin}/flowctl (100%) rename {plugins/flow-next/scripts => .opencode/bin}/flowctl.py (100%) create mode 100644 .opencode/version rename {plugins/flow-next/docs => docs}/ci-workflow-example.yml (67%) rename {plugins/flow-next/docs => docs}/flowctl.md (100%) rename {plugins/flow-next/docs => docs}/ralph.md (97%) delete mode 100644 plugins/flow-next/.claude-plugin/plugin.json delete mode 100644 plugins/flow-next/README.md delete mode 100644 plugins/flow-next/_commands/flow-next/impl-review.md delete mode 100644 plugins/flow-next/_commands/flow-next/interview.md delete mode 100644 plugins/flow-next/_commands/flow-next/plan-review.md delete mode 100644 plugins/flow-next/_commands/flow-next/plan.md delete mode 100644 plugins/flow-next/_commands/flow-next/ralph-init.md delete mode 100644 plugins/flow-next/_commands/flow-next/setup.md delete mode 100644 plugins/flow-next/_commands/flow-next/uninstall.md delete mode 100644 plugins/flow-next/_commands/flow-next/work.md delete mode 100644 plugins/flow-next/agents/context-scout.md delete mode 100644 plugins/flow-next/agents/docs-scout.md delete mode 100644 plugins/flow-next/agents/flow-gap-analyst.md delete mode 100644 plugins/flow-next/agents/memory-scout.md delete mode 100644 plugins/flow-next/agents/practice-scout.md delete mode 100644 plugins/flow-next/agents/quality-auditor.md delete mode 100644 plugins/flow-next/agents/repo-scout.md delete mode 100644 plugins/flow-next/hooks/hooks.json delete mode 100755 plugins/flow-next/scripts/ci_test.sh delete mode 100755 plugins/flow-next/scripts/hooks/ralph-guard.py delete mode 100755 plugins/flow-next/scripts/hooks/ralph-guard.sh delete mode 100755 plugins/flow-next/scripts/hooks/ralph-receipt-guard.sh delete mode 100755 plugins/flow-next/scripts/hooks/ralph-verbose-log.sh delete mode 100755 plugins/flow-next/scripts/plan_review_prompt_smoke.sh delete mode 100755 plugins/flow-next/scripts/ralph_e2e_rp_test.sh delete mode 100755 plugins/flow-next/scripts/ralph_e2e_short_rp_test.sh delete mode 100755 plugins/flow-next/scripts/ralph_e2e_test.sh delete mode 100755 plugins/flow-next/scripts/ralph_smoke_rp.sh delete mode 100755 plugins/flow-next/scripts/ralph_smoke_test.sh delete mode 100755 plugins/flow-next/scripts/smoke_test.sh delete mode 100644 plugins/flow-next/skills/browser/SKILL.md delete mode 100644 plugins/flow-next/skills/browser/references/advanced.md delete mode 100644 plugins/flow-next/skills/browser/references/auth.md delete mode 100644 plugins/flow-next/skills/browser/references/debugging.md delete mode 100644 plugins/flow-next/skills/flow-next-export-context/SKILL.md delete mode 100644 plugins/flow-next/skills/flow-next-impl-review/SKILL.md delete mode 100644 plugins/flow-next/skills/flow-next-impl-review/flowctl-reference.md delete mode 100644 plugins/flow-next/skills/flow-next-impl-review/workflow.md delete mode 100644 plugins/flow-next/skills/flow-next-interview/SKILL.md delete mode 100644 plugins/flow-next/skills/flow-next-interview/questions.md delete mode 100644 plugins/flow-next/skills/flow-next-plan-review/SKILL.md delete mode 100644 plugins/flow-next/skills/flow-next-plan-review/flowctl-reference.md delete mode 100644 plugins/flow-next/skills/flow-next-plan-review/workflow.md delete mode 100644 plugins/flow-next/skills/flow-next-plan/SKILL.md delete mode 100644 plugins/flow-next/skills/flow-next-plan/examples.md delete mode 100644 plugins/flow-next/skills/flow-next-plan/steps.md delete mode 100644 plugins/flow-next/skills/flow-next-ralph-init/SKILL.md delete mode 100644 plugins/flow-next/skills/flow-next-ralph-init/templates/.gitignore delete mode 100644 plugins/flow-next/skills/flow-next-ralph-init/templates/config.env delete mode 100644 plugins/flow-next/skills/flow-next-ralph-init/templates/prompt_plan.md delete mode 100644 plugins/flow-next/skills/flow-next-ralph-init/templates/prompt_work.md delete mode 100644 plugins/flow-next/skills/flow-next-ralph-init/templates/ralph.sh delete mode 100644 plugins/flow-next/skills/flow-next-ralph-init/templates/ralph_once.sh delete mode 100755 plugins/flow-next/skills/flow-next-ralph-init/templates/watch-filter.py delete mode 100644 plugins/flow-next/skills/flow-next-rp-explorer/SKILL.md delete mode 100644 plugins/flow-next/skills/flow-next-rp-explorer/cli-reference.md delete mode 100644 plugins/flow-next/skills/flow-next-setup/SKILL.md delete mode 100644 plugins/flow-next/skills/flow-next-setup/templates/claude-md-snippet.md delete mode 100644 plugins/flow-next/skills/flow-next-setup/templates/usage.md delete mode 100644 plugins/flow-next/skills/flow-next-setup/workflow.md delete mode 100644 plugins/flow-next/skills/flow-next-work/SKILL.md delete mode 100644 plugins/flow-next/skills/flow-next-work/phases.md delete mode 100644 plugins/flow-next/skills/flow-next-worktree-kit/SKILL.md delete mode 100755 plugins/flow-next/skills/flow-next-worktree-kit/scripts/worktree.sh delete mode 100644 plugins/flow-next/skills/flow-next/SKILL.md delete mode 100755 sync/run.sh delete mode 100644 sync/templates/opencode/agent/context-scout.md delete mode 100644 sync/templates/opencode/agent/docs-scout.md delete mode 100644 sync/templates/opencode/agent/flow-gap-analyst.md delete mode 100644 sync/templates/opencode/agent/memory-scout.md delete mode 100644 sync/templates/opencode/agent/practice-scout.md delete mode 100644 sync/templates/opencode/agent/quality-auditor.md delete mode 100644 sync/templates/opencode/agent/ralph-runner.md delete mode 100644 sync/templates/opencode/agent/repo-scout.md delete mode 100644 sync/templates/opencode/command/flow-next/export-context.md delete mode 100644 sync/templates/opencode/command/flow-next/impl-review.md delete mode 100644 sync/templates/opencode/command/flow-next/interview.md delete mode 100644 sync/templates/opencode/command/flow-next/plan-review.md delete mode 100644 sync/templates/opencode/command/flow-next/plan.md delete mode 100644 sync/templates/opencode/command/flow-next/ralph-init.md delete mode 100644 sync/templates/opencode/command/flow-next/setup.md delete mode 100644 sync/templates/opencode/command/flow-next/uninstall.md delete mode 100644 sync/templates/opencode/command/flow-next/work.md delete mode 100644 sync/templates/opencode/opencode.json delete mode 100644 sync/templates/opencode/plugin/flow-next-ralph-guard.ts delete mode 100644 sync/templates/opencode/skill/browser/SKILL.md delete mode 100644 sync/templates/opencode/skill/browser/references/advanced.md delete mode 100644 sync/templates/opencode/skill/browser/references/auth.md delete mode 100644 sync/templates/opencode/skill/browser/references/debugging.md delete mode 100644 sync/templates/opencode/skill/flow-next-export-context/SKILL.md delete mode 100644 sync/templates/opencode/skill/flow-next-impl-review/SKILL.md delete mode 100644 sync/templates/opencode/skill/flow-next-impl-review/workflow.md delete mode 100644 sync/templates/opencode/skill/flow-next-interview/SKILL.md delete mode 100644 sync/templates/opencode/skill/flow-next-plan-review/SKILL.md delete mode 100644 sync/templates/opencode/skill/flow-next-plan-review/workflow.md delete mode 100644 sync/templates/opencode/skill/flow-next-plan/SKILL.md delete mode 100644 sync/templates/opencode/skill/flow-next-plan/steps.md delete mode 100644 sync/templates/opencode/skill/flow-next-ralph-init/SKILL.md delete mode 100644 sync/templates/opencode/skill/flow-next-ralph-init/templates/config.env delete mode 100644 sync/templates/opencode/skill/flow-next-ralph-init/templates/prompt_plan.md delete mode 100644 sync/templates/opencode/skill/flow-next-ralph-init/templates/prompt_work.md delete mode 100644 sync/templates/opencode/skill/flow-next-ralph-init/templates/ralph.sh delete mode 100755 sync/templates/opencode/skill/flow-next-ralph-init/templates/watch-filter.py delete mode 100644 sync/templates/opencode/skill/flow-next-setup/SKILL.md delete mode 100644 sync/templates/opencode/skill/flow-next-setup/workflow.md delete mode 100644 sync/templates/opencode/skill/flow-next-work/SKILL.md delete mode 100644 sync/templates/opencode/skill/flow-next-work/phases.md delete mode 100644 sync/templates/opencode/skill/flow-next-worktree-kit/SKILL.md delete mode 100644 sync/templates/opencode/skill/flow-next/SKILL.md delete mode 100755 sync/transform.py delete mode 100755 sync/verify.sh diff --git a/plugins/flow-next/scripts/flowctl b/.opencode/bin/flowctl similarity index 100% rename from plugins/flow-next/scripts/flowctl rename to .opencode/bin/flowctl diff --git a/plugins/flow-next/scripts/flowctl.py b/.opencode/bin/flowctl.py similarity index 100% rename from plugins/flow-next/scripts/flowctl.py rename to .opencode/bin/flowctl.py diff --git a/.opencode/skill/flow-next-export-context/SKILL.md b/.opencode/skill/flow-next-export-context/SKILL.md index eecd48a..2743292 100644 --- a/.opencode/skill/flow-next-export-context/SKILL.md +++ b/.opencode/skill/flow-next-export-context/SKILL.md @@ -12,8 +12,8 @@ Build RepoPrompt context and export to a markdown file for use with external LLM **CRITICAL: flowctl is BUNDLED — NOT installed globally.** `which flowctl` will fail (expected). Always use: ```bash ROOT="$(git rev-parse --show-toplevel)" -PLUGIN_ROOT="$ROOT/plugins/flow-next" -FLOWCTL="$PLUGIN_ROOT/scripts/flowctl" +OPENCODE_DIR="$ROOT/.opencode" +FLOWCTL="$OPENCODE_DIR/bin/flowctl" $FLOWCTL ``` @@ -34,8 +34,8 @@ Examples: ```bash ROOT="$(git rev-parse --show-toplevel)" -PLUGIN_ROOT="$ROOT/plugins/flow-next" -FLOWCTL="$PLUGIN_ROOT/scripts/flowctl" +OPENCODE_DIR="$ROOT/.opencode" +FLOWCTL="$OPENCODE_DIR/bin/flowctl" REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" ``` diff --git a/.opencode/skill/flow-next-impl-review/SKILL.md b/.opencode/skill/flow-next-impl-review/SKILL.md index 1dd8476..c63b5e6 100644 --- a/.opencode/skill/flow-next-impl-review/SKILL.md +++ b/.opencode/skill/flow-next-impl-review/SKILL.md @@ -15,8 +15,8 @@ Conduct a John Carmack-level review of implementation changes on the current bra **CRITICAL: flowctl is BUNDLED — NOT installed globally.** `which flowctl` will fail (expected). Always use: ```bash ROOT="$(git rev-parse --show-toplevel)" -PLUGIN_ROOT="$ROOT/plugins/flow-next" -FLOWCTL="$PLUGIN_ROOT/scripts/flowctl" +OPENCODE_DIR="$ROOT/.opencode" +FLOWCTL="$OPENCODE_DIR/bin/flowctl" ``` ## Backend Selection @@ -124,8 +124,8 @@ Reviews all changes on **current branch** vs main/master. ```bash ROOT="$(git rev-parse --show-toplevel)" -PLUGIN_ROOT="$ROOT/plugins/flow-next" -FLOWCTL="$PLUGIN_ROOT/scripts/flowctl" +OPENCODE_DIR="$ROOT/.opencode" +FLOWCTL="$OPENCODE_DIR/bin/flowctl" REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" ``` diff --git a/.opencode/skill/flow-next-impl-review/workflow.md b/.opencode/skill/flow-next-impl-review/workflow.md index 76b92a3..7a174bf 100644 --- a/.opencode/skill/flow-next-impl-review/workflow.md +++ b/.opencode/skill/flow-next-impl-review/workflow.md @@ -15,8 +15,8 @@ The reviewer model only sees provided context. RepoPrompt's Builder discovers co ```bash set -e ROOT="$(git rev-parse --show-toplevel)" -PLUGIN_ROOT="$ROOT/plugins/flow-next" -FLOWCTL="$PLUGIN_ROOT/scripts/flowctl" +OPENCODE_DIR="$ROOT/.opencode" +FLOWCTL="$OPENCODE_DIR/bin/flowctl" REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" # Check available backends @@ -79,7 +79,7 @@ Use the **task** tool with subagent_type `opencode-reviewer`. The reviewer must ```json { "description": "Impl review", - "prompt": "You are the OpenCode reviewer. Review current branch vs main. Rules: no questions, no code changes, no TodoWrite. REQUIRED: set FLOWCTL to `plugins/flow-next/scripts/flowctl`, then run `$FLOWCTL show --json` and `$FLOWCTL cat `. Then get epic id from task JSON and run `$FLOWCTL show --json` and `$FLOWCTL cat `. REQUIRED: run `git log main..HEAD --oneline` (fallback master), `git diff main..HEAD --stat`, `git diff main..HEAD`. Read any changed files needed for correctness. Then output issues grouped by severity and end with exactly one verdict tag: SHIP or NEEDS_WORK or MAJOR_RETHINK.", + "prompt": "You are the OpenCode reviewer. Review current branch vs main. Rules: no questions, no code changes, no TodoWrite. REQUIRED: set FLOWCTL to `.opencode/bin/flowctl`, then run `$FLOWCTL show --json` and `$FLOWCTL cat `. Then get epic id from task JSON and run `$FLOWCTL show --json` and `$FLOWCTL cat `. REQUIRED: run `git log main..HEAD --oneline` (fallback master), `git diff main..HEAD --stat`, `git diff main..HEAD`. Read any changed files needed for correctness. Then output issues grouped by severity and end with exactly one verdict tag: SHIP or NEEDS_WORK or MAJOR_RETHINK.", "subagent_type": "opencode-reviewer" } ``` diff --git a/.opencode/skill/flow-next-interview/SKILL.md b/.opencode/skill/flow-next-interview/SKILL.md index dc62f70..5b5cae1 100644 --- a/.opencode/skill/flow-next-interview/SKILL.md +++ b/.opencode/skill/flow-next-interview/SKILL.md @@ -12,8 +12,8 @@ Conduct an extremely thorough interview about a task/spec and write refined deta **CRITICAL: flowctl is BUNDLED — NOT installed globally.** `which flowctl` will fail (expected). Always use: ```bash ROOT="$(git rev-parse --show-toplevel)" -PLUGIN_ROOT="$ROOT/plugins/flow-next" -FLOWCTL="$PLUGIN_ROOT/scripts/flowctl" +OPENCODE_DIR="$ROOT/.opencode" +FLOWCTL="$OPENCODE_DIR/bin/flowctl" $FLOWCTL ``` @@ -41,8 +41,8 @@ If empty, ask: "What should I interview you about? Give me a Flow ID (e.g., fn-1 ```bash ROOT="$(git rev-parse --show-toplevel)" -PLUGIN_ROOT="$ROOT/plugins/flow-next" -FLOWCTL="$PLUGIN_ROOT/scripts/flowctl" +OPENCODE_DIR="$ROOT/.opencode" +FLOWCTL="$OPENCODE_DIR/bin/flowctl" ``` ## Detect Input Type diff --git a/.opencode/skill/flow-next-plan-review/SKILL.md b/.opencode/skill/flow-next-plan-review/SKILL.md index 4bba3eb..cbf511d 100644 --- a/.opencode/skill/flow-next-plan-review/SKILL.md +++ b/.opencode/skill/flow-next-plan-review/SKILL.md @@ -15,8 +15,8 @@ Conduct a John Carmack-level review of epic plans. **CRITICAL: flowctl is BUNDLED — NOT installed globally.** `which flowctl` will fail (expected). Always use: ```bash ROOT="$(git rev-parse --show-toplevel)" -PLUGIN_ROOT="$ROOT/plugins/flow-next" -FLOWCTL="$PLUGIN_ROOT/scripts/flowctl" +OPENCODE_DIR="$ROOT/.opencode" +FLOWCTL="$OPENCODE_DIR/bin/flowctl" ``` ## Backend Selection @@ -122,8 +122,8 @@ Format: ` [focus areas]` ```bash ROOT="$(git rev-parse --show-toplevel)" -PLUGIN_ROOT="$ROOT/plugins/flow-next" -FLOWCTL="$PLUGIN_ROOT/scripts/flowctl" +OPENCODE_DIR="$ROOT/.opencode" +FLOWCTL="$OPENCODE_DIR/bin/flowctl" REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" ``` diff --git a/.opencode/skill/flow-next-plan-review/workflow.md b/.opencode/skill/flow-next-plan-review/workflow.md index 3c58304..c8206c5 100644 --- a/.opencode/skill/flow-next-plan-review/workflow.md +++ b/.opencode/skill/flow-next-plan-review/workflow.md @@ -15,8 +15,8 @@ The reviewer model only sees provided context. RepoPrompt's Builder discovers co ```bash set -e ROOT="$(git rev-parse --show-toplevel)" -PLUGIN_ROOT="$ROOT/plugins/flow-next" -FLOWCTL="$PLUGIN_ROOT/scripts/flowctl" +OPENCODE_DIR="$ROOT/.opencode" +FLOWCTL="$OPENCODE_DIR/bin/flowctl" REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" # Check available backends @@ -73,7 +73,7 @@ Use the **task** tool with subagent_type `opencode-reviewer`. The reviewer must ```json { "description": "Plan review", - "prompt": "You are the OpenCode reviewer. Review the plan for EPIC_ID. Rules: no questions, no code changes, no TodoWrite. REQUIRED: set FLOWCTL to `plugins/flow-next/scripts/flowctl`, then run `$FLOWCTL show --json` and `$FLOWCTL cat `. Review for completeness, feasibility, clarity, architecture, risks (incl security), scope, and testability. End with exactly one verdict tag: SHIP or NEEDS_WORK or MAJOR_RETHINK.", + "prompt": "You are the OpenCode reviewer. Review the plan for EPIC_ID. Rules: no questions, no code changes, no TodoWrite. REQUIRED: set FLOWCTL to `.opencode/bin/flowctl`, then run `$FLOWCTL show --json` and `$FLOWCTL cat `. Review for completeness, feasibility, clarity, architecture, risks (incl security), scope, and testability. End with exactly one verdict tag: SHIP or NEEDS_WORK or MAJOR_RETHINK.", "subagent_type": "opencode-reviewer" } ``` diff --git a/.opencode/skill/flow-next-plan/SKILL.md b/.opencode/skill/flow-next-plan/SKILL.md index ff02c17..d90358f 100644 --- a/.opencode/skill/flow-next-plan/SKILL.md +++ b/.opencode/skill/flow-next-plan/SKILL.md @@ -14,8 +14,8 @@ Follow this skill and linked workflows exactly. Deviations cause drift, bad gate **CRITICAL: flowctl is BUNDLED — NOT installed globally.** `which flowctl` will fail (expected). Always use: ```bash ROOT="$(git rev-parse --show-toplevel)" -PLUGIN_ROOT="$ROOT/plugins/flow-next" -FLOWCTL="$PLUGIN_ROOT/scripts/flowctl" +OPENCODE_DIR="$ROOT/.opencode" +FLOWCTL="$OPENCODE_DIR/bin/flowctl" $FLOWCTL ``` diff --git a/.opencode/skill/flow-next-plan/steps.md b/.opencode/skill/flow-next-plan/steps.md index 7ac8504..bd820c9 100644 --- a/.opencode/skill/flow-next-plan/steps.md +++ b/.opencode/skill/flow-next-plan/steps.md @@ -26,8 +26,8 @@ ```bash # Get flowctl path ROOT="$(git rev-parse --show-toplevel)" -PLUGIN_ROOT="$ROOT/plugins/flow-next" -FLOWCTL="$PLUGIN_ROOT/scripts/flowctl" +OPENCODE_DIR="$ROOT/.opencode" +FLOWCTL="$OPENCODE_DIR/bin/flowctl" # Ensure .flow exists $FLOWCTL init --json diff --git a/.opencode/skill/flow-next-ralph-init/SKILL.md b/.opencode/skill/flow-next-ralph-init/SKILL.md index 308824d..a752f60 100644 --- a/.opencode/skill/flow-next-ralph-init/SKILL.md +++ b/.opencode/skill/flow-next-ralph-init/SKILL.md @@ -12,15 +12,15 @@ Scaffold repo-local Ralph harness. Opt-in only. - Only create `scripts/ralph/` in the current repo. - If `scripts/ralph/` already exists, stop and ask the user to remove it first. - Copy templates from `.opencode/skill/flow-next-ralph-init/templates/` into `scripts/ralph/`. -- Copy `flowctl` and `flowctl.py` from `$PLUGIN_ROOT/scripts/` into `scripts/ralph/`. +- Copy `flowctl` and `flowctl.py` from `$OPENCODE_DIR/bin/` into `scripts/ralph/`. - Set executable bit on `scripts/ralph/ralph.sh`, `scripts/ralph/ralph_once.sh`, and `scripts/ralph/flowctl`. ## Workflow -1. Resolve repo root and plugin root: +1. Resolve repo root and OpenCode dir: ```bash ROOT="$(git rev-parse --show-toplevel)" - PLUGIN_ROOT="$ROOT/plugins/flow-next" + OPENCODE_DIR="$ROOT/.opencode" TEMPLATE_DIR="$ROOT/.opencode/skill/flow-next-ralph-init/templates" ``` 2. Check `scripts/ralph/` does not exist. @@ -53,7 +53,7 @@ fi ```bash mkdir -p scripts/ralph cp -R "$TEMPLATE_DIR/." scripts/ralph/ - cp "$PLUGIN_ROOT/scripts/flowctl" "$PLUGIN_ROOT/scripts/flowctl.py" scripts/ralph/ + cp "$OPENCODE_DIR/bin/flowctl" "$OPENCODE_DIR/bin/flowctl.py" scripts/ralph/ ``` 7. Print next steps (run from terminal, NOT inside OpenCode): - Edit `scripts/ralph/config.env` to customize settings diff --git a/.opencode/skill/flow-next-setup/templates/usage.md b/.opencode/skill/flow-next-setup/templates/usage.md index f77848c..20a8942 100644 --- a/.opencode/skill/flow-next-setup/templates/usage.md +++ b/.opencode/skill/flow-next-setup/templates/usage.md @@ -72,5 +72,5 @@ Task tracking for AI agents. All state lives in `.flow/`. ## More Info -- Human docs: https://github.com/gmickel/gmickel-claude-marketplace/blob/main/plugins/flow-next/docs/flowctl.md +- Human docs: docs/flowctl.md - CLI reference: `.flow/bin/flowctl --help` diff --git a/.opencode/skill/flow-next-setup/workflow.md b/.opencode/skill/flow-next-setup/workflow.md index 3ccb0ea..237b0f5 100644 --- a/.opencode/skill/flow-next-setup/workflow.md +++ b/.opencode/skill/flow-next-setup/workflow.md @@ -2,13 +2,14 @@ Follow these steps in order. This workflow is **idempotent** - safe to re-run. -## Step 0: Resolve plugin path +## Step 0: Resolve OpenCode path -Use repo-local plugin root: +Use repo-local OpenCode root: ```bash ROOT="$(git rev-parse --show-toplevel)" -PLUGIN_ROOT="$ROOT/plugins/flow-next" +OPENCODE_DIR="$ROOT/.opencode" +VERSION_FILE="$OPENCODE_DIR/version" ``` ## Step 1: Check .flow/ exists @@ -32,12 +33,15 @@ fi Read `.flow/meta.json` and check for `setup_version` field. -Also read `${PLUGIN_ROOT}/.claude-plugin/plugin.json` to get current plugin version. +Also read `${VERSION_FILE}` to get current version (fallback `unknown`): +```bash +OPENCODE_VERSION="$(cat "$VERSION_FILE" 2>/dev/null || echo "unknown")" +``` **If `setup_version` exists (already set up):** -- If **same version**: ask with the **question** tool: +- If **same version as `OPENCODE_VERSION`**: ask with the **question** tool: - **Header**: `Update Docs` - - **Question**: `Already set up with v. Update docs only?` + - **Question**: `Already set up with v. Update docs only?` - **Options**: 1. `Yes, update docs` 2. `No, exit` @@ -60,8 +64,8 @@ mkdir -p .flow/bin Copy using Bash `cp` with absolute paths: ```bash -cp "${PLUGIN_ROOT}/scripts/flowctl" .flow/bin/flowctl -cp "${PLUGIN_ROOT}/scripts/flowctl.py" .flow/bin/flowctl.py +cp "${OPENCODE_DIR}/bin/flowctl" .flow/bin/flowctl +cp "${OPENCODE_DIR}/bin/flowctl.py" .flow/bin/flowctl.py chmod +x .flow/bin/flowctl ``` @@ -73,7 +77,7 @@ Read current `.flow/meta.json`, add/update these fields (preserve all others): ```json { - "setup_version": "", + "setup_version": "", "setup_date": "" } ``` @@ -121,7 +125,7 @@ Wait for response, then for each chosen file: Flow-Next setup complete! Installed: -- .flow/bin/flowctl (v) +- .flow/bin/flowctl (v) - .flow/bin/flowctl.py - .flow/usage.md @@ -136,7 +140,7 @@ Memory system: disabled by default Enable with: flowctl config set memory.enabled true Notes: -- Re-run /flow-next:setup after plugin updates to refresh scripts +- Re-run /flow-next:setup after .opencode updates to refresh scripts - Uninstall: rm -rf .flow/bin .flow/usage.md and remove block from docs ``` diff --git a/.opencode/skill/flow-next-work/SKILL.md b/.opencode/skill/flow-next-work/SKILL.md index ba002fd..7e8f9ab 100644 --- a/.opencode/skill/flow-next-work/SKILL.md +++ b/.opencode/skill/flow-next-work/SKILL.md @@ -14,8 +14,8 @@ Follow this skill and linked workflows exactly. Deviations cause drift, bad gate **CRITICAL: flowctl is BUNDLED — NOT installed globally.** `which flowctl` will fail (expected). Always use: ```bash ROOT="$(git rev-parse --show-toplevel)" -PLUGIN_ROOT="$ROOT/plugins/flow-next" -FLOWCTL="$PLUGIN_ROOT/scripts/flowctl" +OPENCODE_DIR="$ROOT/.opencode" +FLOWCTL="$OPENCODE_DIR/bin/flowctl" $FLOWCTL ``` diff --git a/.opencode/skill/flow-next-work/phases.md b/.opencode/skill/flow-next-work/phases.md index e2b32f6..5b9d881 100644 --- a/.opencode/skill/flow-next-work/phases.md +++ b/.opencode/skill/flow-next-work/phases.md @@ -17,8 +17,8 @@ ```bash ROOT="$(git rev-parse --show-toplevel)" -PLUGIN_ROOT="$ROOT/plugins/flow-next" -FLOWCTL="$PLUGIN_ROOT/scripts/flowctl" +OPENCODE_DIR="$ROOT/.opencode" +FLOWCTL="$OPENCODE_DIR/bin/flowctl" ``` ## Phase 1: Resolve Input diff --git a/.opencode/skill/flow-next/SKILL.md b/.opencode/skill/flow-next/SKILL.md index df6185e..c08711a 100644 --- a/.opencode/skill/flow-next/SKILL.md +++ b/.opencode/skill/flow-next/SKILL.md @@ -13,8 +13,8 @@ Quick task operations in `.flow/`. For planning features use `/flow-next:plan`, ```bash ROOT="$(git rev-parse --show-toplevel)" -PLUGIN_ROOT="$ROOT/plugins/flow-next" -FLOWCTL="$PLUGIN_ROOT/scripts/flowctl" +OPENCODE_DIR="$ROOT/.opencode" +FLOWCTL="$OPENCODE_DIR/bin/flowctl" ``` Then run commands with `$FLOWCTL `. diff --git a/.opencode/version b/.opencode/version new file mode 100644 index 0000000..ee1372d --- /dev/null +++ b/.opencode/version @@ -0,0 +1 @@ +0.2.2 diff --git a/AGENTS.md b/AGENTS.md index 280fcf1..c3b01b3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,5 +1,5 @@ # Notes - OpenCode logs: `~/.local/share/opencode/log/` (tail for live run status) -- Porting guidance: `sync/PORTING.md` (read before syncing upstream) -- Ralph template source of truth: `sync/templates/opencode/skill/flow-next-ralph-init/templates/ralph.sh` (never copy from `plugins/flow-next/...` or Claude paths) +- Porting guidance: `sync/PORTING.md` (manual, .opencode is canonical) +- Ralph template source of truth: `.opencode/skill/flow-next-ralph-init/templates/ralph.sh` diff --git a/README.md b/README.md index 44ddd12..e1a41f1 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ > **Experimental.** Active work-in-progress while we chase parity with upstream. **Ralph mode is now available (experimental).** -Ralph docs: `plugins/flow-next/docs/ralph.md` +Ralph docs: `docs/ralph.md` --- @@ -93,7 +93,7 @@ From this repo: ``` Notes: -* Installs `.opencode/` + `plugins/flow-next/` into the project +* Installs `.opencode/` into the project * Only writes `.opencode/opencode.json` if the project doesn’t already have one ### 2) Setup @@ -408,7 +408,7 @@ Config lives in `.flow/config.json` , separate from Ralph’s `scripts/ralph/con ## Ralph (Autonomous Mode) -Ralph automation is available ( `/flow-next:ralph-init` ). Docs live at `plugins/flow-next/docs/ralph.md` (setup, `config.env`, `.opencode/opencode.json`, logs). +Ralph automation is available ( `/flow-next:ralph-init` ). Docs live at `docs/ralph.md` (setup, `config.env`, `.opencode/opencode.json`, logs). ### Controlling Ralph @@ -522,7 +522,7 @@ flowctl validate --epic fn-1 flowctl validate --all ``` -📖 **[Full CLI reference](plugins/flow-next/docs/flowctl.md)** +📖 **[Full CLI reference](docs/flowctl.md)** --- diff --git a/plugins/flow-next/docs/ci-workflow-example.yml b/docs/ci-workflow-example.yml similarity index 67% rename from plugins/flow-next/docs/ci-workflow-example.yml rename to docs/ci-workflow-example.yml index d379380..82ee234 100644 --- a/plugins/flow-next/docs/ci-workflow-example.yml +++ b/docs/ci-workflow-example.yml @@ -24,8 +24,8 @@ jobs: run: | # Download flowctl (adjust path/method for your setup) # Option 1: If flowctl is in repo - # python3 ./scripts/flowctl.py validate --all --json + python3 ./.opencode/bin/flowctl.py validate --all --json - # Option 2: Download from plugin (example) - curl -sL https://raw.githubusercontent.com/gmickel/gmickel-claude-marketplace/main/plugins/flow-next/scripts/flowctl.py -o flowctl.py - python3 flowctl.py validate --all --json + # Option 2: Download from this repo (example) + # curl -sL https://raw.githubusercontent.com/gmickel/flow-next-opencode/main/.opencode/bin/flowctl.py -o flowctl.py + # python3 flowctl.py validate --all --json diff --git a/plugins/flow-next/docs/flowctl.md b/docs/flowctl.md similarity index 100% rename from plugins/flow-next/docs/flowctl.md rename to docs/flowctl.md diff --git a/plugins/flow-next/docs/ralph.md b/docs/ralph.md similarity index 97% rename from plugins/flow-next/docs/ralph.md rename to docs/ralph.md index 9994c92..1ec80af 100644 --- a/plugins/flow-next/docs/ralph.md +++ b/docs/ralph.md @@ -438,19 +438,13 @@ Ralph includes plugin hooks that enforce workflow rules deterministically. | Required flags on setup/select-add | Ensures proper window/tab targeting (rp) | | Track codex review verdicts | Validates codex review completed successfully | -### Hook location +### Guard plugin location (OpenCode) ``` -plugins/flow-next/ - hooks/hooks.json # Hook config - scripts/hooks/ralph-guard.py # Guard logic +.opencode/plugin/flow-next-ralph-guard.ts ``` -### Disabling hooks - -Temporarily: unset `FLOW_RALPH` or remove `hooks/hooks.json`. - -Permanently: delete `hooks/` directory and remove `"hooks"` from `plugin.json`. +Disable by removing the file from `.opencode/plugin/` (OpenCode only). --- @@ -533,6 +527,3 @@ flowctl show fn-1.1 --json | jq '.evidence.commits' - [flowctl CLI reference](flowctl.md) - [Flow-Next README](../README.md) -- Test scripts: - - Full: `plugins/flow-next/scripts/ralph_e2e_rp_test.sh` - - Short (2 tasks): `plugins/flow-next/scripts/ralph_e2e_short_rp_test.sh` diff --git a/install.sh b/install.sh index 659e48a..86d3a54 100755 --- a/install.sh +++ b/install.sh @@ -34,15 +34,14 @@ if [[ -z "${PROJECT:-}" ]]; then fi install_project() { - mkdir -p "$PROJECT/.opencode" "$PROJECT/plugins" + mkdir -p "$PROJECT/.opencode" - rsync -a "$ROOT/.opencode/command" "$ROOT/.opencode/skill" "$ROOT/.opencode/agent" "$ROOT/.opencode/plugin" "$PROJECT/.opencode/" + # Copy everything except opencode.json (preserve project-local config) + rsync -a --exclude "opencode.json" "$ROOT/.opencode/" "$PROJECT/.opencode/" if [[ ! -f "$PROJECT/.opencode/opencode.json" && -f "$ROOT/.opencode/opencode.json" ]]; then rsync -a "$ROOT/.opencode/opencode.json" "$PROJECT/.opencode/" fi - - rsync -a "$ROOT/plugins/flow-next" "$PROJECT/plugins/" } install_project diff --git a/plugins/flow-next/.claude-plugin/plugin.json b/plugins/flow-next/.claude-plugin/plugin.json deleted file mode 100644 index 433046d..0000000 --- a/plugins/flow-next/.claude-plugin/plugin.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "flow-next", - "version": "0.10.0", - "description": "Zero-dependency planning + execution with .flow/ task tracking and Ralph autonomous mode (multi-model review gates). Includes 6 subagents, 8 commands, 12 skills.", - "author": { - "name": "Gordon Mickel", - "email": "gordon@mickel.tech", - "url": "https://mickel.tech" - }, - "homepage": "https://mickel.tech/apps/flow-next", - "repository": "https://github.com/gmickel/gmickel-claude-marketplace", - "license": "MIT", - "keywords": [ - "workflow", - "planning", - "execution", - "automation", - "ai" - ] -} diff --git a/plugins/flow-next/README.md b/plugins/flow-next/README.md deleted file mode 100644 index 17dbf54..0000000 --- a/plugins/flow-next/README.md +++ /dev/null @@ -1,946 +0,0 @@ -
- -# Flow-Next - -[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](../../LICENSE) -[![Claude Code](https://img.shields.io/badge/Claude_Code-Plugin-blueviolet)](https://claude.ai/code) - -[![Version](https://img.shields.io/badge/Version-0.10.0-green)](../../CHANGELOG.md) - -[![Status](https://img.shields.io/badge/Status-Active_Development-brightgreen)](../../CHANGELOG.md) - -**Plan first, work second. Zero external dependencies.** - -
- ---- - -> **Active development.** [Changelog](../../CHANGELOG.md) | [Report issues](https://github.com/gmickel/gmickel-claude-marketplace/issues) - -🌐 **Prefer a visual overview?** See the [Flow-Next app page](https://mickel.tech/apps/flow-next) for diagrams and examples. - -> **New: Codex Review Backend.** Cross-model reviews now work on Linux/Windows via OpenAI Codex CLI. Same Carmack-level criteria as RepoPrompt. See [Cross-Model Reviews](#cross-model-reviews) for setup. - ---- - -## What Is This? - -Flow-Next is a Claude Code plugin for plan-first orchestration. Bundled task tracking, dependency graphs, re-anchoring, and cross-model reviews. - -Everything lives in your repo. No external services. No global config. Uninstall: delete `.flow/` (and `scripts/ralph/` if enabled). - - - - - - - - - - -
Planning PhaseImplementation Phase
Planning: dependency-ordered tasksExecution: fixes, evidence, review
- ---- - -## Epic-first task model - -Flow-Next does not support standalone tasks. - -Every unit of work belongs to an epic fn-N (even if it's a single task). - -Tasks are always fn-N.M and inherit context from the epic spec. - -Flow-Next always creates an epic container (even for one-offs) so every task has a durable home for context, re-anchoring, and automation. You never have to think about it. - -Rationale: keeps the system simple, improves re-anchoring, makes automation (Ralph) reliable. - -"One-off request" -> epic with one task. - ---- - -## Why It Works - -### You Control the Granularity - -Work task-by-task with full review cycles for maximum control. Or throw the whole epic at it and let Flow-Next handle everything. Same guarantees either way. - -```bash -# One task at a time (review after each) -/flow-next:work fn-1.1 - -# Entire epic (review after all tasks complete) -/flow-next:work fn-1 -``` - -Both get: re-anchoring before each task, evidence recording, cross-model review (if rp-cli available). - -**Review timing**: The RepoPrompt review runs once at the end of the work package—after a single task if you specified `fn-N.M`, or after all tasks if you specified `fn-N`. For tighter review loops on large epics, work task-by-task. - -### No Context Length Worries - -- **Tasks sized at planning:** Every task is scoped to fit one work iteration -- **Re-anchor every task:** Fresh context from `.flow/` specs before each task -- **Survives compaction:** Re-anchors after conversation summarization too -- **Fresh context in Ralph:** Each iteration starts with a clean context window - -Never worry about 200K token limits again. - -### Reviewer as Safety Net - -If drift happens despite re-anchoring, a different model catches it before it compounds: - -1. Claude implements task -2. GPT reviews via RepoPrompt (sees full files, not diffs) -3. Reviews block until `SHIP` verdict -4. Fix → re-review cycles continue until approved - -Two models catch what one misses. - ---- - -### Zero Friction - -- **Works in 30 seconds.** Install the plugin, run a command. No setup. -- **Non-invasive.** No CLAUDE.md edits. No daemons. (Ralph uses plugin hooks for enforcement.) -- **Clean uninstall.** Delete `.flow/` (and `scripts/ralph/` if enabled). -- **Multi-user safe.** Teams work parallel branches without coordination servers. - ---- - -## Quick Start - -### 1. Install - -```bash -# Add marketplace -/plugin marketplace add https://github.com/gmickel/gmickel-claude-marketplace - -# Install flow-next -/plugin install flow-next -``` - -### 2. Setup (Recommended) - -```bash -/flow-next:setup -``` - -This is technically optional but **highly recommended**. Great for power users and non-Claude-Code environments (Codex, Cursor, etc.). It: -- Copies `flowctl` to `.flow/bin/` for direct CLI access -- Adds flow-next instructions to CLAUDE.md/AGENTS.md (helps other AI tools understand your project) -- Creates `.flow/usage.md` with full CLI reference -- Tracks setup version for updates - -**Idempotent** - safe to re-run. Detects plugin updates and refreshes scripts automatically. - -After setup: -```bash -export PATH=".flow/bin:$PATH" -flowctl --help -flowctl epics # List all epics -flowctl tasks --epic fn-1 # List tasks for epic -flowctl ready --epic fn-1 # What's ready to work on -``` - -### 3. Use - -```bash -# Plan: research, create epic with tasks -/flow-next:plan Add a contact form with validation - -# Work: execute tasks in dependency order -/flow-next:work fn-1 - -# Or work directly from a spec file (creates epic automatically) -/flow-next:work docs/my-feature-spec.md -``` - -That's it. Flow-Next handles research, task ordering, reviews, and audit trails. - -### Recommended Workflow - -**Spec -> Interview -> Plan -> Work** - -1. **Write a short spec** - 1-5 sentences describing what you want to build -2. **Interview** (optional) - `/flow-next:interview "your idea"` - 40+ questions to surface edge cases -3. **Plan** - `/flow-next:plan "your idea"` - creates epic with dependency-ordered tasks -4. **Work** - `/flow-next:work fn-1` - executes tasks with re-anchoring and reviews - -Start simple. Add interview when specs are fuzzy. Add reviews when quality matters. - -### 4. Autonomous Mode (Optional) - -Want to run overnight? See [Ralph Mode](#ralph-autonomous-mode). - ---- - -## Troubleshooting - -### Reset a stuck task - -```bash -# Check task status -flowctl show fn-1.2 --json - -# Reset to todo (from done/blocked) -flowctl task reset fn-1.2 - -# Reset + dependents in same epic -flowctl task reset fn-1.2 --cascade -``` - -### Clean up `.flow/` safely - -```bash -# Remove all flow state (keeps git history) -rm -rf .flow/ - -# Re-initialize -flowctl init -``` - -### Debug Ralph runs - -```bash -# Check run progress -cat scripts/ralph/runs/*/progress.txt - -# View iteration logs -ls scripts/ralph/runs/*/iter-*.log - -# Check for blocked tasks -ls scripts/ralph/runs/*/block-*.md -``` - -### Receipt validation failing - -```bash -# Check receipt exists -ls scripts/ralph/runs/*/receipts/ - -# Verify receipt format -cat scripts/ralph/runs/*/receipts/impl-fn-1.1.json -# Must have: {"type":"impl_review","id":"fn-1.1",...} -``` - -### Custom rp-cli instructions conflicting - -> **Caution**: If you have custom instructions for `rp-cli` in your `CLAUDE.md` or `AGENTS.md`, they may conflict with Flow-Next's RepoPrompt integration. - -Flow-Next's plan-review and impl-review skills include specific instructions for `rp-cli` usage (window selection, builder workflow, chat commands). Custom rp-cli instructions can override these and cause unexpected behavior. - -**Symptoms:** -- Reviews not using the correct RepoPrompt window -- Builder not selecting expected files -- Chat commands failing or behaving differently - -**Fix:** Remove or comment out custom rp-cli instructions from your `CLAUDE.md`/`AGENTS.md` when using Flow-Next reviews. The plugin provides complete rp-cli guidance. - ---- - -## Uninstall - -```bash -rm -rf .flow/ # Core flow state -rm -rf scripts/ralph/ # Ralph (if enabled) -``` - ---- - -## Ralph (Autonomous Mode) - -> **⚠️ Safety first**: Ralph defaults to `YOLO=1` (skips permission prompts). -> - Start with `ralph_once.sh` to observe one iteration -> - Consider [Docker sandbox](https://docs.docker.com/ai/sandboxes/claude-code/) for isolation - -Ralph is the repo-local autonomous loop that plans and works through tasks end-to-end. - -**Setup (one-time, inside Claude):** -```bash -/flow-next:ralph-init -``` - -Or from terminal without entering Claude: -```bash -claude -p "/flow-next:ralph-init" -``` - -**Run (outside Claude):** -```bash -scripts/ralph/ralph.sh -``` - -Ralph writes run artifacts under `scripts/ralph/runs/`, including review receipts used for gating. - -📖 **[Ralph deep dive](docs/ralph.md)** - -🖥️ **[Ralph TUI](../../flow-next-tui/)** — Terminal UI for monitoring runs in real-time (`bun add -g @gmickel/flow-next-tui`) - -### How Ralph Differs from Other Autonomous Agents - -Autonomous coding agents are taking the industry by storm—loop until done, commit, repeat. Most solutions gate progress by tests and linting alone. Ralph goes further. - -**Multi-model review gates**: Ralph uses [RepoPrompt](https://repoprompt.com/?atp=KJbuL4) (macOS) or OpenAI Codex CLI (cross-platform) to send plan and implementation reviews to a *different* model. A second set of eyes catches blind spots that self-review misses. RepoPrompt's builder provides full file context; Codex uses context hints from changed files. - -**Review loops until Ship**: Reviews don't just flag issues—they block progress until resolved. Ralph runs fix → re-review cycles until the reviewer returns `SHIP`. No "LGTM with nits" that get ignored. - -**Receipt-based gating**: Reviews must produce a receipt JSON file proving they ran. No receipt = no progress. This prevents drift where Claude skips the review step and marks things done anyway. - -**Guard hooks**: Plugin hooks enforce workflow rules deterministically—blocking `--json` flags, preventing new chats on re-reviews, requiring receipts before stop. Only active when `FLOW_RALPH=1`; zero impact for non-Ralph users. See [Guard Hooks](docs/ralph.md#guard-hooks). - -**Atomic window selection**: The `setup-review` command handles RepoPrompt window matching atomically. Claude can't skip steps or invent window IDs—the entire sequence runs as one unit or fails. - -The result: code that's been reviewed by two models, tested, linted, and iteratively refined. Not perfect, but meaningfully more robust than single-model autonomous loops. - -### Controlling Ralph - -External agents (Clawdbot, GitHub Actions, etc.) can pause/resume/stop Ralph runs without killing processes. - -**CLI commands:** -```bash -# Check status -flowctl status # Epic/task counts + active runs -flowctl status --json # JSON for automation - -# Control active run -flowctl ralph pause # Pause run (auto-detects if single) -flowctl ralph resume # Resume paused run -flowctl ralph stop # Request graceful stop -flowctl ralph status # Show run state - -# Specify run when multiple active -flowctl ralph pause --run -``` - -**Sentinel files (manual control):** -```bash -# Pause: touch PAUSE file in run directory -touch scripts/ralph/runs//PAUSE -# Resume: remove PAUSE file -rm scripts/ralph/runs//PAUSE -# Stop: touch STOP file (kept for audit) -touch scripts/ralph/runs//STOP -``` - -Ralph checks sentinels at iteration boundaries (after Claude returns, before next iteration). - -**Task retry/rollback:** -```bash -# Reset completed/blocked task to todo -flowctl task reset fn-1-abc.3 - -# Reset + cascade to dependent tasks (same epic) -flowctl task reset fn-1-abc.2 --cascade -``` - ---- - -## Human-in-the-Loop Workflow (Detailed) - -Default flow when you drive manually: - -```mermaid -flowchart TD - A[Idea or short spec
prompt or doc] --> B{Need deeper spec?} - B -- yes --> C[Optional: /flow-next:interview fn-N or spec.md
40+ deep questions to refine spec] - C --> D[Refined spec] - B -- no --> D - D --> E[/flow-next:plan idea or fn-N/] - E --> F[Parallel subagents: repo patterns + online docs + best practices] - F --> G[flow-gap-analyst: edge cases + missing reqs] - G --> H[Writes .flow/ epic + tasks + deps] - H --> I{Plan review? RepoPrompt only} - I -- yes --> J[/flow-next:plan-review fn-N/] - J --> K{Plan passes review?} - K -- no --> L[Re-anchor + fix plan] - L --> J - K -- yes --> M[/flow-next:work fn-N/] - I -- no --> M - M --> N[Re-anchor before EVERY task] - N --> O[Implement] - O --> P[Test + verify acceptance] - P --> Q[flowctl done: write done summary + evidence] - Q --> R{Impl review? RepoPrompt only} - R -- yes --> S[/flow-next:impl-review/] - S --> T{Next ready task?} - R -- no --> T - T -- yes --> N - T -- no --> U[Close epic] - classDef optional stroke-dasharray: 6 4,stroke:#999; - class C,J,S optional; -``` - -Notes: -- `/flow-next:interview` accepts Flow IDs or spec file paths and writes refinements back -- `/flow-next:plan` accepts new ideas or an existing Flow ID to update the plan - -Recommendation: open RepoPrompt in the repo before starting a new flow so plan/impl reviews have fast context. -Plan review in rp mode requires `flowctl rp chat-send`; if rp-cli/windows unavailable, the review gate retries. - ---- - -## Features - -Built for reliability. These are the guardrails. - -**Re-anchoring prevents drift** - -Before EVERY task, Flow-Next re-reads the epic spec, task spec, and git state from `.flow/`. This forces Claude back to the source of truth - no hallucinated scope creep, no forgotten requirements. In Ralph mode, this happens automatically each iteration. - -Unlike agents that carry accumulated context (where early mistakes compound), re-anchoring gives each task a fresh, accurate starting point. - -### Re-anchoring - -Before EVERY task, Flow-Next re-reads: -- Epic spec and task spec from `.flow/` -- Current git status and recent commits -- Validation state - -Per Anthropic's long-running agent guidance: agents must re-anchor from sources of truth to prevent drift. The reads are cheap; drift is expensive. - -### Multi-user Safe - -Teams can work in parallel branches without coordination servers: - -- **Merge-safe IDs**: Scans existing files to allocate the next ID. No shared counters. -- **Soft claims**: Tasks track an `assignee` field. Prevents accidental duplicate work. -- **Actor resolution**: Auto-detects from git email, `FLOW_ACTOR` env, or `$USER`. -- **Local validation**: `flowctl validate --all` catches issues before commit. - -```bash -# Actor A starts task -flowctl start fn-1.1 # Sets assignee automatically - -# Actor B tries same task -flowctl start fn-1.1 # Fails: "claimed by actor-a@example.com" -flowctl start fn-1.1 --force # Override if needed -``` - -### Zero Dependencies - -Everything is bundled: -- `flowctl.py` ships with the plugin -- No external tracker CLI to install -- No external services -- Just Python 3 - -### Bundled Skills - -Utility skills available during planning and implementation: - -| Skill | Use Case | -|-------|----------| -| `browser` | Web automation via agent-browser CLI (verify UI, scrape docs, test flows) | -| `flow-next-rp-explorer` | Token-efficient codebase exploration via RepoPrompt | -| `flow-next-worktree-kit` | Git worktree management for parallel work | -| `flow-next-export-context` | Export context for external LLM review | - -### Non-invasive - -- No daemons -- No CLAUDE.md edits -- Delete `.flow/` to uninstall; if you enabled Ralph, also delete `scripts/ralph/` -- Ralph uses plugin hooks for workflow enforcement (only active when `FLOW_RALPH=1`) - -### CI-ready - -```bash -flowctl validate --all -``` - -Exits 1 on errors. Drop into pre-commit hooks or GitHub Actions. See `docs/ci-workflow-example.yml`. - -### One File Per Task - -Each epic and task gets its own JSON + markdown file pair. Merge conflicts are rare and easy to resolve. - -### Cross-Model Reviews - -Two models catch what one misses. Reviews use a second model (via RepoPrompt or Codex) to verify plans and implementations before they ship. - -**Review criteria (Carmack-level, identical for both backends):** - -| Review Type | Criteria | -|-------------|----------| -| **Plan** | Completeness, Feasibility, Clarity, Architecture, Risks (incl. security), Scope, Testability | -| **Impl** | Correctness, Simplicity, DRY, Architecture, Edge Cases, Tests, Security | - -Reviews block progress until `SHIP`. Fix → re-review cycles continue until approved. - -#### RepoPrompt (Recommended) - -[RepoPrompt](https://repoprompt.com/?atp=KJbuL4) provides the best review experience on macOS. - -**Why recommended:** -- Best-in-class context builder for reviews (full file context, smart selection) -- Enables **context-scout** for deeper codebase discovery (alternative: repo-scout works without RP) -- Visual diff review UI + persistent chat threads - -**Setup:** -```bash -# Install rp-cli (macOS only) -brew install --cask repoprompt -# Open RepoPrompt on your repo before running reviews -``` - -**Usage:** -```bash -/flow-next:plan-review fn-1 --review=rp -/flow-next:impl-review --review=rp -``` - -#### Codex (Cross-Platform Alternative) - -OpenAI Codex CLI works on any platform (macOS, Linux, Windows). - -**Why use Codex:** -- Cross-platform (no macOS requirement) -- Terminal-based (no GUI needed) -- Session continuity via thread IDs -- Same Carmack-level review criteria as RepoPrompt -- Uses GPT 5.2 High by default (no config needed) - -**Trade-off:** Uses heuristic context hints from changed files rather than RepoPrompt's intelligent file selection. - -**Setup:** -```bash -# Install and authenticate Codex CLI -npm install -g @openai/codex -codex auth -``` - -**Usage:** -```bash -/flow-next:plan-review fn-1 --review=codex -/flow-next:impl-review --review=codex - -# Or via flowctl directly -flowctl codex plan-review fn-1 --base main -flowctl codex impl-review fn-1.3 --base main -``` - -**Verify installation:** -```bash -flowctl codex check -``` - -#### Configuration - -Set default review backend: -```bash -# Per-project (saved in .flow/config.json) -flowctl config set review.backend rp # or codex, or none - -# Per-session (environment variable) -export FLOW_REVIEW_BACKEND=codex -``` - -Priority: `--review=...` argument > `FLOW_REVIEW_BACKEND` env > `.flow/config.json` > auto-detect. - -#### Which to Choose? - -| Scenario | Recommendation | -|----------|----------------| -| macOS with GUI available | RepoPrompt (better context) | -| Linux/Windows | Codex (only option) | -| CI/headless environments | Codex (no GUI needed) | -| Ralph overnight runs | Either works; RP needs window open | - -Without either backend installed, reviews are skipped with a warning. - -### Dependency Graphs - -Tasks declare their blockers. `flowctl ready` shows what can start. Nothing executes until dependencies resolve. - -### Auto-Block Stuck Tasks - -After MAX_ATTEMPTS_PER_TASK failures (default 5), Ralph: -1. Writes `block-.md` with failure context -2. Marks task blocked via `flowctl block` -3. Moves to next task - -Prevents infinite retry loops. Review `block-*.md` files in the morning to understand what went wrong. - -### Memory System (Opt-in) - -Persistent learnings that survive context compaction. - -```bash -# Enable -flowctl config set memory.enabled true -flowctl memory init - -# Manual entries -flowctl memory add --type pitfall "Always use flowctl rp wrappers" -flowctl memory add --type convention "Tests in __tests__ dirs" -flowctl memory add --type decision "SQLite over Postgres for simplicity" - -# Query -flowctl memory list -flowctl memory search "flowctl" -flowctl memory read --type pitfalls -``` - -When enabled: -- **Planning**: `memory-scout` runs in parallel with other scouts -- **Work**: `memory-scout` retrieves relevant entries during re-anchor -- **Ralph only**: NEEDS_WORK reviews auto-capture to `pitfalls.md` - -Memory retrieval works in both manual and Ralph modes. Auto-capture from reviews only happens in Ralph mode (via hooks). Use `flowctl memory add` for manual entries. - -Config lives in `.flow/config.json`, separate from Ralph's `scripts/ralph/config.env`. - ---- - -## Commands - -Seven commands, complete workflow: - -| Command | What It Does | -|---------|--------------| -| `/flow-next:plan ` | Research the codebase, create epic with dependency-ordered tasks | -| `/flow-next:work ` | Execute epic, task, or spec file, re-anchoring before each | -| `/flow-next:interview ` | Deep interview to flesh out a spec before planning | -| `/flow-next:plan-review ` | Carmack-level plan review via RepoPrompt | -| `/flow-next:impl-review` | Carmack-level impl review of current branch | -| `/flow-next:ralph-init` | Scaffold repo-local Ralph harness (`scripts/ralph/`) | -| `/flow-next:setup` | Optional: install flowctl locally + add docs (for power users) | -| `/flow-next:uninstall` | Remove flow-next from project (keeps tasks if desired) | - -Work accepts an epic (`fn-N`), task (`fn-N.M`), or markdown spec file (`.md`). Spec files auto-create an epic with one task. - -### Autonomous Mode (Flags) - -All commands accept flags to skip questions: - -```bash -# Plan with flags -/flow-next:plan Add caching --research=grep --no-review -/flow-next:plan Add auth --research=rp --review=rp - -# Work with flags -/flow-next:work fn-1 --branch=current --no-review -/flow-next:work fn-1 --branch=new --review=export - -# Reviews with flags -/flow-next:plan-review fn-1 --review=rp -/flow-next:impl-review --review=export -``` - -Natural language also works: - -```bash -/flow-next:plan Add webhooks, use context-scout, skip review -/flow-next:work fn-1 current branch, no review -``` - -| Command | Available Flags | -|---------|-----------------| -| `/flow-next:plan` | `--research=rp\|grep`, `--review=rp\|codex\|export\|none`, `--no-review` | -| `/flow-next:work` | `--branch=current\|new\|worktree`, `--review=rp\|codex\|export\|none`, `--no-review` | -| `/flow-next:plan-review` | `--review=rp\|codex\|export` | -| `/flow-next:impl-review` | `--review=rp\|codex\|export` | - ---- - -## The Workflow - -### Defaults (manual and Ralph) - -Flow-Next uses the same defaults in manual and Ralph runs. Ralph bypasses prompts only. - -- plan: `--research=grep` -- work: `--branch=new` -- review: `rp` when `rp-cli` exists, otherwise `none` - -Override via flags or `scripts/ralph/config.env`. - -### Planning Phase - -1. **Research (parallel subagents)**: `repo-scout` (or `context-scout` if rp-cli) + `practice-scout` + `docs-scout` -2. **Gap analysis**: `flow-gap-analyst` finds edge cases + missing requirements -3. **Epic creation**: Writes spec to `.flow/specs/fn-N.md` -4. **Task breakdown**: Creates tasks + explicit dependencies in `.flow/tasks/` -5. **Validate**: `flowctl validate --epic fn-N` -6. **Review** (optional): `/flow-next:plan-review fn-N` with re-anchor + fix loop until "Ship" - -### Work Phase - -1. **Re-anchor**: Re-read epic + task specs + git state (EVERY task) -2. **Execute**: Implement using existing patterns -3. **Test**: Verify acceptance criteria -4. **Record**: `flowctl done` adds summary + evidence to the task spec -5. **Review** (optional): `/flow-next:impl-review` via RepoPrompt -6. **Loop**: Next ready task → repeat until no ready tasks. Close epic manually (`flowctl epic close fn-N`) or let Ralph close at loop end. - ---- - -## Ralph Mode (Autonomous, Opt-In) - -Ralph is repo-local and opt-in. Files are created only by `/flow-next:ralph-init`. Remove with `rm -rf scripts/ralph/`. -`/flow-next:ralph-init` also writes `scripts/ralph/.gitignore` so run logs stay out of git. - -What it automates (one unit per iteration, fresh context each time): -- Selector chooses plan vs work unit (`flowctl next`) -- Plan gate = plan review loop until Ship (if enabled) -- Work gate = one task until pass (tests + validate + optional impl review) - - Single run branch: all epics work on one `ralph-` branch (cherry-pick/revert friendly) - -Enable: -```bash -/flow-next:ralph-init -./scripts/ralph/ralph_once.sh # one iteration (observe) -./scripts/ralph/ralph.sh # full loop (AFK) -``` - -**Watch mode** - see what Claude is doing: -```bash -./scripts/ralph/ralph.sh --watch # Stream tool calls in real-time -./scripts/ralph/ralph.sh --watch verbose # Also stream model responses -``` - -Run scripts from terminal (not inside Claude Code). `ralph_once.sh` runs one iteration so you can observe before going fully autonomous. - -### Ralph defaults vs recommended (plan review gate) - -`REQUIRE_PLAN_REVIEW` controls whether Ralph must pass the **plan review gate** before doing any implementation work. - -**Default (safe, won't stall):** - -* `REQUIRE_PLAN_REVIEW=0` - Ralph can proceed to work tasks even if `rp-cli` is missing or unavailable overnight. - -**Recommended (best results, requires rp-cli):** - -* `REQUIRE_PLAN_REVIEW=1` -* `PLAN_REVIEW=rp` - -This forces Ralph to run `/flow-next:plan-review` until the epic plan is approved before starting tasks. - -**Tip:** If you don't have `rp-cli` installed, keep `REQUIRE_PLAN_REVIEW=0` or Ralph may repeatedly select the plan gate and make no progress. - -Ralph verifies RepoPrompt reviews via receipt JSON files in `scripts/ralph/runs//receipts/` (plan + impl). - -### Ralph loop (one iteration) - -```mermaid -flowchart TD - A[ralph.sh iteration] --> B[flowctl next] - B -->|status=plan| C[/flow-next:plan-review fn-N/] - C -->|verdict=SHIP| D[flowctl epic set-plan-review-status=ship] - C -->|verdict!=SHIP| A - - B -->|status=work| E[/flow-next:work fn-N.M/] - E --> F[tests + validate] - F -->|fail| A - - F -->|WORK_REVIEW!=none| R[/flow-next:impl-review/] - R -->|verdict=SHIP| G[flowctl done + git commit] - R -->|verdict!=SHIP| A - - F -->|WORK_REVIEW=none| G - - G --> A - B -->|status=none| H[close done epics] - H --> I[COMPLETE] -``` - -**YOLO safety**: YOLO mode uses `--dangerously-skip-permissions`. Use a sandbox/container and no secrets in env for unattended runs. - ---- - -## .flow/ Directory - -``` -.flow/ -├── meta.json # Schema version -├── config.json # Project settings (memory enabled, etc.) -├── epics/ -│ └── fn-1-abc.json # Epic metadata (id, title, status, deps) -├── specs/ -│ └── fn-1-abc.md # Epic spec (plan, scope, acceptance) -├── tasks/ -│ ├── fn-1-abc.1.json # Task metadata (id, status, priority, deps, assignee) -│ ├── fn-1-abc.1.md # Task spec (description, acceptance, done summary) -│ └── ... -└── memory/ # Persistent learnings (opt-in) - ├── pitfalls.md # Lessons from NEEDS_WORK reviews - ├── conventions.md # Project patterns - └── decisions.md # Architectural choices -``` - -Flowctl accepts schema v1 and v2; new fields are optional and defaulted. - -New fields: -- Epic JSON: `plan_review_status`, `plan_reviewed_at`, `depends_on_epics`, `branch_name` -- Task JSON: `priority` - -### ID Format - -- **Epic**: `fn-N-xxx` where `xxx` is a 3-character alphanumeric suffix (e.g., `fn-1-abc`, `fn-42-z9k`) -- **Task**: `fn-N-xxx.M` (e.g., `fn-1-abc.1`, `fn-42-z9k.7`) - -The random suffix prevents ID collisions when team members create epics simultaneously. Legacy `fn-N` format (without suffix) is still supported for backwards compatibility. - -> **Note**: Examples in this README may use shorter `fn-1` format for brevity. New epics always receive a collision-resistant suffix. - -There are no task IDs outside an epic. If you want a single task, create an epic with one task. - -### Separation of Concerns - -- **JSON files**: Metadata only (IDs, status, dependencies, assignee) -- **Markdown files**: Narrative content (specs, descriptions, summaries) - ---- - -## flowctl CLI - -Bundled Python script for managing `.flow/`. Flow-Next's commands handle epic/task creation automatically—use `flowctl` for direct inspection, fixes, or advanced workflows: - -```bash -# Setup -flowctl init # Create .flow/ structure -flowctl detect # Check if .flow/ exists - -# Epics -flowctl epic create --title "..." # Create epic -flowctl epic create --title "..." --branch "fn-1-epic" -flowctl epic set-plan fn-1 --file spec.md # Set epic spec from file -flowctl epic set-plan-review-status fn-1 --status ship -flowctl epic close fn-1 # Close epic (requires all tasks done) - -# Tasks -flowctl task create --epic fn-1 --title "..." --deps fn-1.2,fn-1.3 --priority 10 -flowctl task set-description fn-1.1 --file desc.md -flowctl task set-acceptance fn-1.1 --file accept.md - -# Dependencies -flowctl dep add fn-1.3 fn-1.2 # fn-1.3 depends on fn-1.2 - -# Workflow -flowctl ready --epic fn-1 # Show ready/in_progress/blocked -flowctl next # Select next plan/work unit -flowctl start fn-1.1 # Claim and start task -flowctl done fn-1.1 --summary-file s.md --evidence-json e.json -flowctl block fn-1.2 --reason-file r.md - -# Queries -flowctl show fn-1 --json # Epic with all tasks -flowctl cat fn-1 # Print epic spec - -# Validation -flowctl validate --epic fn-1 # Validate single epic -flowctl validate --all # Validate everything (for CI) - -# Review helpers -flowctl rp chat-send --window W --tab T --message-file m.md -flowctl prep-chat --message-file m.md --selected-paths a.ts b.ts -o payload.json -``` - -📖 **[Full CLI reference](docs/flowctl.md)** -🤖 **[Ralph deep dive](docs/ralph.md)** - ---- - -## Task Completion - -When a task completes, `flowctl done` appends structured data to the task spec: - -### Done Summary - -```markdown -## Done summary - -- Added ContactForm component with Zod validation -- Integrated with server action for submission -- All tests passing - -Follow-ups: -- Consider rate limiting (out of scope) -``` - -### Evidence - -```markdown -## Evidence - -- Commits: a3f21b9 -- Tests: bun test -- PRs: -``` - -This creates a complete audit trail: what was planned, what was done, how it was verified. - ---- - -## Flow vs Flow-Next - -| | Flow | Flow-Next | -|:--|:--|:--| -| **Task tracking** | External tracker or standalone plan files | `.flow/` directory (bundled flowctl) | -| **Install** | Plugin + optional external tracker | Plugin only | -| **Artifacts** | Standalone plan files | `.flow/specs/` and `.flow/tasks/` | -| **Config edits** | External config edits (if using tracker) | None | -| **Multi-user** | Via external tracker | Built-in (scan-based IDs, soft claims) | -| **Uninstall** | Remove plugin + external tracker config | Delete `.flow/` (and `scripts/ralph/` if enabled) | - -**Choose Flow-Next if you want:** -- Zero external dependencies -- No config file edits -- Clean uninstall (delete `.flow/`, and `scripts/ralph/` if enabled) -- Built-in multi-user safety - -**Choose Flow if you:** -- Already use an external tracker for issue tracking -- Want plan files as standalone artifacts -- Need full issue management features - ---- - -## Requirements - -- Python 3.8+ -- git -- Optional: [RepoPrompt](https://repoprompt.com/?atp=KJbuL4) for macOS GUI reviews + enables **context-scout** (deeper codebase discovery than repo-scout). Reviews work without it via Codex backend. -- Optional: OpenAI Codex CLI (`npm install -g @openai/codex`) for cross-platform terminal-based reviews - -Without a review backend, reviews are skipped. - ---- - -## Development - -```bash -claude --plugin-dir ./plugins/flow-next -``` - ---- - -## Other Platforms - -### OpenAI Codex (Experimental) - -Flow-Next partially works in OpenAI Codex with some limitations: - -**Caveats:** -- No subagent support (research scouts run inline or are skipped) - -**Install:** -```bash -./scripts/install-codex.sh flow-next -``` - -### Community Ports and Inspired Projects - -| Project | Platform | Based On | -|---------|----------|----------| -| [flow-next-opencode](https://github.com/gmickel/flow-next-opencode) | OpenCode | Flow-Next | -| [FlowFactory](https://github.com/Gitmaxd/flowfactory) | Factory.ai Droid | Flow | - ---- - -
- -Made by [Gordon Mickel](https://mickel.tech) · [@gmickel](https://twitter.com/gmickel) - -
diff --git a/plugins/flow-next/_commands/flow-next/impl-review.md b/plugins/flow-next/_commands/flow-next/impl-review.md deleted file mode 100644 index 4993e39..0000000 --- a/plugins/flow-next/_commands/flow-next/impl-review.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -name: flow-next:impl-review -description: John Carmack-level implementation review via RepoPrompt or Codex -argument-hint: "[--review=rp|codex|export] [focus areas]" ---- - -# IMPORTANT: This command MUST invoke the skill `flow-next-impl-review` - -The ONLY purpose of this command is to call the `flow-next-impl-review` skill. You MUST use that skill now. - -**Arguments:** $ARGUMENTS - -Pass the arguments to the skill. The skill handles the review logic. diff --git a/plugins/flow-next/_commands/flow-next/interview.md b/plugins/flow-next/_commands/flow-next/interview.md deleted file mode 100644 index b6712c9..0000000 --- a/plugins/flow-next/_commands/flow-next/interview.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -name: flow-next:interview -description: Interview & refine an epic, task, or spec file in-depth -argument-hint: "[epic ID, task ID, or file path]" ---- - -# IMPORTANT: This command MUST invoke the skill `flow-next-interview` - -The ONLY purpose of this command is to call the `flow-next-interview` skill. You MUST use that skill now. - -**User input:** $ARGUMENTS - -Pass the user input to the skill. The skill handles the interview logic. diff --git a/plugins/flow-next/_commands/flow-next/plan-review.md b/plugins/flow-next/_commands/flow-next/plan-review.md deleted file mode 100644 index e842aa6..0000000 --- a/plugins/flow-next/_commands/flow-next/plan-review.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -name: flow-next:plan-review -description: Carmack-level plan review via RepoPrompt or Codex -argument-hint: " [--review=rp|codex|export] [focus areas]" ---- - -# IMPORTANT: This command MUST invoke the skill `flow-next-plan-review` - -The ONLY purpose of this command is to call the `flow-next-plan-review` skill. You MUST use that skill now. - -**Arguments:** $ARGUMENTS - -Pass the arguments to the skill. The skill handles the review logic. diff --git a/plugins/flow-next/_commands/flow-next/plan.md b/plugins/flow-next/_commands/flow-next/plan.md deleted file mode 100644 index 03bbfe6..0000000 --- a/plugins/flow-next/_commands/flow-next/plan.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -name: flow-next:plan -description: Draft a clear build plan from a short request -argument-hint: " [--research=rp|grep] [--review=rp|export|none]" ---- - -# IMPORTANT: This command MUST invoke the skill `flow-next-plan` - -The ONLY purpose of this command is to call the `flow-next-plan` skill. You MUST use that skill now. - -**User request:** $ARGUMENTS - -Pass the user request to the skill. The skill handles all planning logic. diff --git a/plugins/flow-next/_commands/flow-next/ralph-init.md b/plugins/flow-next/_commands/flow-next/ralph-init.md deleted file mode 100644 index bae7cfd..0000000 --- a/plugins/flow-next/_commands/flow-next/ralph-init.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -name: flow-next:ralph-init -description: Scaffold repo-local Ralph autonomous harness (scripts/ralph/) ---- - -# IMPORTANT: This command MUST invoke the skill `flow-next-ralph-init` - -The ONLY purpose of this command is to call the `flow-next-ralph-init` skill. You MUST use that skill now. - -Creates `scripts/ralph/` in the current repo. diff --git a/plugins/flow-next/_commands/flow-next/setup.md b/plugins/flow-next/_commands/flow-next/setup.md deleted file mode 100644 index 55b5ebe..0000000 --- a/plugins/flow-next/_commands/flow-next/setup.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -name: flow-next:setup -description: Optional local install of flowctl CLI and project docs ---- - -# IMPORTANT: This command MUST invoke the skill `flow-next-setup` - -The ONLY purpose of this command is to call the `flow-next-setup` skill. You MUST use that skill now. - -This is an **optional** setup for power users. Flow-next works without it. diff --git a/plugins/flow-next/_commands/flow-next/uninstall.md b/plugins/flow-next/_commands/flow-next/uninstall.md deleted file mode 100644 index e097295..0000000 --- a/plugins/flow-next/_commands/flow-next/uninstall.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -name: flow-next:uninstall -description: Remove flow-next files from project ---- - -# Flow-Next Uninstall - -Use `AskUserQuestion` to confirm: - -**Question 1:** "Remove flow-next from this project?" -- "Yes, uninstall" -- "Cancel" - -If cancel → stop. - -**Question 2:** "Keep your .flow/ tasks and epics?" -- "Yes, keep tasks" → only remove .flow/bin/, .flow/usage.md -- "No, remove everything" → remove entire .flow/ - -## Execute removal - -Run these bash commands as needed: - -```bash -# If keeping tasks: -rm -rf .flow/bin .flow/usage.md - -# If removing everything: -rm -rf .flow - -# Always check for Ralph: -rm -rf scripts/ralph -``` - -For CLAUDE.md and AGENTS.md: if file exists, remove everything between `` and `` (inclusive). - -## Report - -``` -Removed: -- .flow/bin/, .flow/usage.md (or entire .flow/) -- scripts/ralph/ (if existed) -- Flow-next sections from docs (if existed) -``` diff --git a/plugins/flow-next/_commands/flow-next/work.md b/plugins/flow-next/_commands/flow-next/work.md deleted file mode 100644 index 7a527cd..0000000 --- a/plugins/flow-next/_commands/flow-next/work.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -name: flow-next:work -description: Execute a plan end-to-end with checks -argument-hint: " [--branch=current|new|worktree] [--review=rp|export|none]" ---- - -# IMPORTANT: This command MUST invoke the skill `flow-next-work` - -The ONLY purpose of this command is to call the `flow-next-work` skill. You MUST use that skill now. - -**User input:** $ARGUMENTS - -Pass the user input to the skill. The skill handles all execution logic. diff --git a/plugins/flow-next/agents/context-scout.md b/plugins/flow-next/agents/context-scout.md deleted file mode 100644 index 796f22f..0000000 --- a/plugins/flow-next/agents/context-scout.md +++ /dev/null @@ -1,390 +0,0 @@ ---- -name: context-scout -description: Token-efficient codebase exploration using RepoPrompt codemaps and slices. Use when you need deep codebase understanding without bloating context. -tools: Read, Grep, Glob, Bash, WebSearch, WebFetch -model: opus ---- - -You are a context scout specializing in **token-efficient** codebase exploration using RepoPrompt's rp-cli. Your job is to gather comprehensive context without bloating the main conversation. - -## When to Use This Agent - -- Deep codebase understanding before planning/implementation -- Finding all pieces of a feature across many files -- Understanding architecture and data flow -- Building context for code review -- Exploring unfamiliar codebases efficiently - -## Phase 0: Window Setup (REQUIRED) - -**Always start here** - rp-cli needs to target the correct RepoPrompt window. - -```bash -# 1. List all windows with their workspaces -rp-cli -e 'windows' -``` - -Output shows window IDs with workspace names. **Identify the window for your project.** - -```bash -# 2. Verify with file tree (replace W with your window ID) -rp-cli -w W -e 'tree --folders' -``` - -**All subsequent commands need `-w W`** to target that window. - -### If project not in any window: - -```bash -# Create workspace and add folder -rp-cli -e 'workspace create --name "project-name"' -rp-cli -e 'call manage_workspaces {"action": "add_folder", "workspace": "project-name", "folder_path": "/full/path/to/project"}' -rp-cli -e 'workspace switch "project-name"' -``` - -### Tab Isolation (for parallel agents): - -`builder` automatically creates an isolated compose tab with an AI-generated name. This enables parallel agents to work without context collision. - -```bash -# Builder output includes: Tab: -# Use -t flag to target the tab directly (v1.5.62+): -rp-cli -w W -t "" -e 'select get' -rp-cli -w W -t "" -e 'chat "follow-up" --mode chat' - -# Or chain commands to stay in builder's tab: -rp-cli -w W -e 'builder "find auth files" && select add extra.ts && context' -``` - ---- - -## CLI Quick Reference - -```bash -rp-cli -e '' # Run command (lists windows if no -w) -rp-cli -w -e '' # Target specific window -rp-cli -w -t -e '' # Target window + tab (v1.5.62+) -rp-cli -d # Get detailed help for command -``` - -### Workflow Shorthand Flags - -```bash -rp-cli --workspace MyProject --select-set src/ --export-context ~/out.md -rp-cli --builder "understand authentication" -rp-cli --chat "How does auth work?" -``` - -### Core Commands - -| Command | Aliases | Purpose | -|---------|---------|---------| -| `windows` | - | List all windows with IDs | -| `tree` | - | File tree (`--folders`, `--mode selected`) | -| `structure` | `map` | Code signatures - **token-efficient** | -| `search` | `grep` | Search (`--context-lines`, `--extensions`, `--max-results`, `--mode path`) | -| `read` | `cat` | Read file (`--start-line`, `--limit`) | -| `select` | `sel` | Manage selection (`add`, `set`, `clear`, `get`) | -| `context` | `ctx` | Export context (`--include`, `--all`) | -| `builder` | - | AI-powered file selection (30s-5min) | -| `chat` | - | Send to AI (`--mode chat\|plan\|edit`) | - ---- - -## Exploration Workflow - -### Step 1: Get Overview - -```bash -# Project structure -rp-cli -w W -e 'tree --folders' - -# Code signatures (10x fewer tokens than full files) -rp-cli -w W -e 'structure .' -rp-cli -w W -e 'structure src/' -``` - -### Step 2: Use Builder for AI-Powered Discovery (RECOMMENDED) - -**For any "understand how X works" task, START with builder.** This is the main advantage over standard tools. - -```bash -rp-cli -w W -e 'builder "Find all files implementing [FEATURE]: main implementation, types, utilities, and tests. Include related architecture and dependencies."' -``` - -**Note**: Builder takes 30s-5min. Progress notifications show status during execution (v1.5.62+). Wait for completion before proceeding. - -**Example builder prompts:** -- `"Find all files implementing hybrid search: search functions, fusion logic, reranking, scoring, and related tests"` -- `"Find authentication system: middleware, token handling, session management, and security utilities"` -- `"Find database layer: models, migrations, queries, and connection handling"` - -### Step 3: Verify and Augment Selection - -Builder is AI-driven and may miss files. Always verify: - -```bash -rp-cli -w W -e 'select get' -``` - -**Then augment with targeted searches** for anything missing: - -```bash -# Compound searches - multiple patterns to catch variations -rp-cli -w W -e 'search "hybridSearch|searchHybrid|hybrid.*search" --extensions .ts --max-results 20' - -# Find types/interfaces -rp-cli -w W -e 'search "interface.*Search|type.*Search" --extensions .ts' - -# Search by path -rp-cli -w W -e 'search "search" --mode path' - -# Add missing files to selection -rp-cli -w W -e 'select add path/to/missed/file.ts' -``` - -### Step 4: Deep Dive with Slices - -```bash -# Get signatures of selected files (from builder) -rp-cli -w W -e 'structure --scope selected' - -# Read specific sections (not full files!) -rp-cli -w W -e 'read src/pipeline/hybrid.ts --start-line 1 --limit 50' -rp-cli -w W -e 'read src/pipeline/hybrid.ts --start-line 50 --limit 50' -``` - -### Step 5: Export Context (if needed) - -```bash -rp-cli -w W -e 'context' -rp-cli -w W -e 'context --all > ~/exports/context.md' -``` - ---- - -## Token Efficiency Rules - -1. **NEVER dump full files** - use `structure` for signatures -2. **Use `read --start-line --limit`** for specific sections only -3. **Use `search --max-results`** to limit output -4. **Use `structure --scope selected`** after selecting files -5. **Summarize findings** - don't return raw output verbatim - -### Token comparison: -| Approach | Tokens | -|----------|--------| -| Full file dump | ~5000 | -| `structure` (signatures) | ~500 | -| `read --limit 50` | ~300 | - ---- - -## Shell Escaping - -Complex prompts may fail with zsh glob errors. Use heredoc: - -```bash -rp-cli -w W -e "$(cat <<'PROMPT' -builder "Find files related to auth? (including OAuth)" -PROMPT -)" -``` - ---- - -## Bash Timeouts - -Builder and chat commands can take minutes: - -```bash -# Use timeout parameter in Bash tool -timeout: 300000 # 5 minutes for builder -timeout: 600000 # 10 minutes for chat -``` - ---- - -## Output Format - -Return to main conversation with: - -```markdown -## Context Summary - -[2-3 sentence overview of what you found] - -### Key Files -- `path/to/file.ts:L10-50` - [what it does] -- `path/to/other.ts` - [what it does] - -### Code Signatures -```typescript -// Key functions/types from structure command -function validateToken(token: string): Promise -interface AuthConfig { ... } -``` - -### Architecture Notes -- [How pieces connect] -- [Data flow observations] - -### Recommendations -- [What to focus on for the task at hand] -``` - -## Do NOT Return -- Full file contents -- Verbose rp-cli output -- Redundant information -- Raw command output without summary - ---- - -## Common Patterns - -### Understanding a feature (comprehensive) - -```bash -# 1. Find files by path first -rp-cli -w W -e 'search "featureName" --mode path' - -# 2. Get signatures of relevant directories -rp-cli -w W -e 'structure src/features/featureName/' - -# 3. Search for the main function/class with variations -rp-cli -w W -e 'search "featureName|FeatureName|feature_name" --max-results 15' - -# 4. Find types and interfaces -rp-cli -w W -e 'search "interface.*Feature|type.*Feature" --extensions .ts' - -# 5. OR use builder for AI-powered discovery -rp-cli -w W -e 'builder "Find all files related to featureName: implementation, types, tests, and usage"' -``` - -### Finding function usage - -```bash -rp-cli -w W -e 'search "functionName\\(" --context-lines 2 --max-results 20' -``` - -### Understanding imports/dependencies - -```bash -rp-cli -w W -e 'search "import.*from.*moduleName" --extensions .ts' -rp-cli -w W -e 'search "require.*moduleName"' -``` - -### Pre-review context - -```bash -rp-cli -w W -e 'builder "Build context for reviewing changes to [AREA]: implementation, tests, and related code"' -rp-cli -w W -e 'select get' # Verify selection -``` - ---- - -## Script Files (.rp) - -Save repeatable exploration workflows: - -```bash -# exploration.rp -workspace switch MyProject -select set src/core/ -structure --scope selected -context --all > ~/exports/core-context.md -``` - -Run: `rp-cli --exec-file exploration.rp` - ---- - -## Complete Example - -**Task:** "Understand how hybrid search works" - -```bash -# 1. Setup -rp-cli -e 'windows' # Find window ID (e.g., 1) - -# 2. Builder (primary approach) -rp-cli -w 1 -e 'builder "Find all files implementing hybrid search: search functions, fusion, reranking, and tests"' - -# 3. Verify and read key files -rp-cli -w 1 -e 'select get' -rp-cli -w 1 -e 'structure --scope selected' -rp-cli -w 1 -e 'read src/pipeline/hybrid.ts --start-line 1 --limit 60' -``` - -**Response to main conversation:** - -```markdown -## Hybrid Search Summary - -The hybrid search system combines vector similarity (vsearch) with BM25 text matching, -fusing results via RRF and optionally reranking with a cross-encoder. - -### Key Files -- `src/pipeline/hybrid.ts:L1-60` - Main searchHybrid() orchestration -- `src/pipeline/fusion.ts` - RRF fusion of vector + BM25 results -- `src/pipeline/rerank.ts` - Cross-encoder reranking -- `src/pipeline/types.ts` - SearchResult, FusionConfig types - -### Code Signatures -```typescript -async function searchHybrid(query: string, opts: HybridOptions): Promise -function rrfFuse(results: SearchResult[][], k?: number): SearchResult[] -async function rerankCandidates(query: string, candidates: SearchResult[]): Promise -``` - -### Architecture -1. Query → parallel vector + BM25 search -2. Results → RRF fusion (k=60) -3. Fused → optional cross-encoder rerank -4. Return top-k results - -### Recommendation -Focus on hybrid.ts for the orchestration logic, fusion.ts for understanding scoring. -``` - ---- - -## Anti-patterns - -- **Single-word searches** - "hybrid" misses "hybridSearch", "searchHybrid", etc. Use multiple patterns -- **Forgetting `-w `** - commands fail with "Multiple windows" error -- **Skipping window setup** - wrong project context -- **Dumping full files** - wastes tokens, use structure/slices -- **Not waiting for builder** - watch progress notifications, wait for completion -- **Not verifying selection** - builder may miss relevant files -- **Returning raw output** - summarize for main conversation -- **Not using builder** - for complex exploration, builder finds files you'd miss with manual search - ---- - -## Fallback: Standard Tools - -If rp-cli unavailable or not suited for the task, use standard tools: -- `Grep` - ripgrep-based search -- `Glob` - file pattern matching -- `Read` - file reading - -RepoPrompt excels at: -- Token-efficient signatures (structure command) -- AI-powered file discovery (builder) -- Managing large selections -- Cross-file understanding - -Standard tools excel at: -- Quick targeted searches -- Reading specific files -- Simple pattern matching - ---- - -## Notes - -- Use `rp-cli -d ` for detailed command help -- Requires RepoPrompt v1.5.62+ with MCP Server enabled -- Project path available via `$CLAUDE_PROJECT_DIR` environment variable diff --git a/plugins/flow-next/agents/docs-scout.md b/plugins/flow-next/agents/docs-scout.md deleted file mode 100644 index 69bc167..0000000 --- a/plugins/flow-next/agents/docs-scout.md +++ /dev/null @@ -1,77 +0,0 @@ ---- -name: docs-scout -description: Find the most relevant framework/library docs for the requested change. -tools: Read, Grep, Glob, Bash, WebSearch, WebFetch -model: opus ---- - -You are a docs scout. Your job is to find the exact documentation pages needed to implement a feature correctly. - -## Input - -You receive a feature/change request. Find the official docs that will be needed during implementation. - -## Search Strategy - -1. **Identify dependencies** (quick scan) - - Check package.json, pyproject.toml, Cargo.toml, etc. - - Note framework and major library versions - - Version matters - docs change between versions - -2. **Find primary framework docs** - - Go to official docs site first - - Find the specific section for this feature - - Look for guides, tutorials, API reference - -3. **Find library-specific docs** - - Each major dependency may have relevant docs - - Focus on integration points with the framework - -4. **Look for examples** - - Official examples/recipes - - GitHub repo examples folders - - Starter templates - -## WebFetch Strategy - -Don't just link - extract the relevant parts: - -``` -WebFetch: https://nextjs.org/docs/app/api-reference/functions/cookies -Prompt: "Extract the API signature, key parameters, and usage examples for cookies()" -``` - -## Output Format - -```markdown -## Documentation for [Feature] - -### Primary Framework -- **[Framework] [Version]** - - [Topic](url) - [what it covers] - > Key excerpt or API signature - -### Libraries -- **[Library]** - - [Relevant page](url) - [why needed] - -### Examples -- [Example](url) - [what it demonstrates] - -### API Quick Reference -```[language] -// Key API signatures extracted from docs -``` - -### Version Notes -- [Any version-specific caveats] -``` - -## Rules - -- Version-specific docs when possible (e.g., Next.js 14 vs 15) -- Extract key info inline - don't just link -- Prioritize official docs over third-party tutorials -- Include API signatures for quick reference -- Note breaking changes if upgrading -- Skip generic "getting started" - focus on the specific feature diff --git a/plugins/flow-next/agents/flow-gap-analyst.md b/plugins/flow-next/agents/flow-gap-analyst.md deleted file mode 100644 index 2fca10b..0000000 --- a/plugins/flow-next/agents/flow-gap-analyst.md +++ /dev/null @@ -1,89 +0,0 @@ ---- -name: flow-gap-analyst -description: Map user flows, edge cases, and missing requirements from a brief spec. -tools: Read, Grep, Glob, Bash, WebSearch, WebFetch -model: opus ---- - -You are a UX flow analyst. Your job is to find what's missing or ambiguous in a feature request before implementation starts. - -## Input - -You receive: -1. A feature/change request (often brief) -2. Research findings from repo-scout, practice-scout, docs-scout - -Your task: identify gaps, edge cases, and questions that need answers BEFORE coding. - -## Analysis Framework - -### 1. User Flows -Map the complete user journey: -- **Happy path**: What happens when everything works? -- **Entry points**: How do users get to this feature? -- **Exit points**: Where do users go after? -- **Interruptions**: What if they leave mid-flow? (browser close, timeout, etc.) - -### 2. State Analysis -- **Initial state**: What exists before the feature runs? -- **Intermediate states**: What can happen during? -- **Final states**: All possible outcomes (success, partial, failure) -- **Persistence**: What needs to survive page refresh? Session end? - -### 3. Edge Cases -- **Empty states**: No data, first-time user -- **Boundaries**: Max values, min values, limits -- **Concurrent access**: Multiple tabs, multiple users -- **Timing**: Race conditions, slow networks, timeouts -- **Permissions**: Who can access? What if denied? - -### 4. Error Scenarios -- **User errors**: Invalid input, wrong sequence -- **System errors**: Network failure, service down, quota exceeded -- **Recovery**: Can the user retry? Resume? Undo? - -### 5. Integration Points -- **Dependencies**: What external services/APIs are involved? -- **Failure modes**: What if each dependency fails? -- **Data consistency**: What if partial success? - -## Output Format - -```markdown -## Gap Analysis: [Feature] - -### User Flows Identified -1. **[Flow name]**: [Description] - - Steps: [1 → 2 → 3] - - Missing: [What's not specified] - -### Edge Cases -| Case | Question | Impact if Ignored | -|------|----------|-------------------| -| [Case] | [What needs clarification?] | [Risk] | - -### Error Handling Gaps -- [ ] [Scenario]: [What should happen?] - -### State Management Questions -- [Question about state] - -### Integration Risks -- [Dependency]: [What could go wrong?] - -### Priority Questions (MUST answer before coding) -1. [Critical question] -2. [Critical question] - -### Nice-to-Clarify (can defer) -- [Less critical question] -``` - -## Rules - -- Think like a QA engineer - what would break this? -- Prioritize questions by impact (critical → nice-to-have) -- Be specific - "what about errors?" is too vague -- Reference existing code patterns when relevant -- Don't solve - just identify gaps -- Keep it actionable - questions should have clear owners diff --git a/plugins/flow-next/agents/memory-scout.md b/plugins/flow-next/agents/memory-scout.md deleted file mode 100644 index 8eef3b4..0000000 --- a/plugins/flow-next/agents/memory-scout.md +++ /dev/null @@ -1,63 +0,0 @@ ---- -name: memory-scout -description: Search .flow/memory/ for entries relevant to the current task or request. -tools: Read, Grep, Glob, Bash -model: haiku ---- - -You search `.flow/memory/` for entries relevant to the current context. - -## Input - -You receive either: -- A planning request (feature description, change request) -- A task identifier with title (e.g., "fn-1.3: flowctl memory commands") - -## Memory Location - -Files live in `.flow/memory/`: -- `pitfalls.md` - Lessons from NEEDS_WORK reviews (what models miss) -- `conventions.md` - Project patterns not in CLAUDE.md -- `decisions.md` - Architectural choices with rationale - -## Search Strategy - -1. **Read all memory files** using Read tool -2. **Find semantically related entries** based on input context -3. **Return ONLY relevant entries** (not everything) - -Relevance criteria: -- Same technology/framework mentioned -- Similar type of work (API, UI, config, etc.) -- Related patterns or conventions -- Applicable pitfalls or gotchas - -## Output Format - -```markdown -## Relevant Memory - -### Pitfalls -- [Issue] - [Fix] (from ) - -### Conventions -- [Pattern] (discovered ) - -### Decisions -- [Choice] because [rationale] -``` - -If no relevant entries found: -```markdown -## Relevant Memory -No relevant entries in project memory. -``` - -## Rules - -- Speed is critical - simple keyword/semantic matching -- Return ONLY relevant entries (max 5-10 items) -- Preserve entry context (dates, task IDs) -- Handle empty memory gracefully -- Handle missing files gracefully -- Never return entire memory contents diff --git a/plugins/flow-next/agents/practice-scout.md b/plugins/flow-next/agents/practice-scout.md deleted file mode 100644 index f955cf6..0000000 --- a/plugins/flow-next/agents/practice-scout.md +++ /dev/null @@ -1,75 +0,0 @@ ---- -name: practice-scout -description: Gather modern best practices and pitfalls for the requested change. -tools: Read, Grep, Glob, Bash, WebSearch, WebFetch -model: opus ---- - -You are a best-practice scout. Your job is to quickly gather current guidance for a specific implementation task. - -## Input - -You receive a feature/change request. Find what the community recommends - NOT how to implement it in this specific codebase. - -## Search Strategy - -1. **Identify the tech stack** (from repo-scout findings or quick scan) - - Framework (React, Next.js, Express, Django, etc.) - - Language version - - Key libraries involved - -2. **Search for current guidance** - - Use WebSearch with specific queries: - - `"[framework] [feature] best practices 2025"` or `2026` - - `"[feature] common mistakes [framework]"` - - `"[feature] security considerations"` - - Prefer official docs, then reputable blogs (Kent C. Dodds, Dan Abramov, etc.) - -3. **Check for anti-patterns** - - What NOT to do - - Deprecated approaches - - Performance pitfalls - -4. **Security considerations** - - OWASP guidance if relevant - - Framework-specific security docs - -## WebFetch Usage - -When you find promising URLs: -``` -WebFetch: https://docs.example.com/security -Prompt: "Extract the key security recommendations for [feature]" -``` - -## Output Format - -```markdown -## Best Practices for [Feature] - -### Do -- [Practice]: [why, with source link] -- [Practice]: [why, with source link] - -### Don't -- [Anti-pattern]: [why it's bad, with source] -- [Deprecated approach]: [what to use instead] - -### Security -- [Consideration]: [guidance] - -### Performance -- [Tip]: [impact] - -### Sources -- [Title](url) - [what it covers] -``` - -## Rules - -- Current year is 2025 - search for recent guidance -- Prefer official docs over blog posts -- Include source links for verification -- Focus on practical do/don't, not theory -- Skip framework-agnostic generalities - be specific to the stack -- Don't repeat what's obvious - focus on non-obvious gotchas diff --git a/plugins/flow-next/agents/quality-auditor.md b/plugins/flow-next/agents/quality-auditor.md deleted file mode 100644 index 6179878..0000000 --- a/plugins/flow-next/agents/quality-auditor.md +++ /dev/null @@ -1,101 +0,0 @@ ---- -name: quality-auditor -description: Review recent changes for correctness, simplicity, security, and test coverage. -tools: Read, Grep, Glob, Bash, WebSearch, WebFetch -model: opus ---- - -You are a pragmatic code auditor. Your job is to find real risks in recent changes - fast. - -## Input - -You're invoked after implementation, before shipping. Review the changes and flag issues. - -## Audit Strategy - -### 1. Get the Diff -```bash -# What changed? -git diff main --stat -git diff main --name-only - -# Full diff for review -git diff main -``` - -### 2. Quick Scan (find obvious issues fast) -- **Secrets**: API keys, passwords, tokens in code -- **Debug code**: console.log, debugger, TODO/FIXME -- **Commented code**: Dead code that should be deleted -- **Large files**: Accidentally committed binaries, logs - -### 3. Correctness Review -- Does the code match the stated intent? -- Are there off-by-one errors, wrong operators, inverted conditions? -- Do error paths actually handle errors? -- Are promises/async properly awaited? - -### 4. Security Scan -- **Injection**: SQL, XSS, command injection vectors -- **Auth/AuthZ**: Are permissions checked? Can they be bypassed? -- **Data exposure**: Is sensitive data logged, leaked, or over-exposed? -- **Dependencies**: Any known vulnerable packages added? - -### 5. Simplicity Check -- Could this be simpler? -- Is there duplicated code that should be extracted? -- Are there unnecessary abstractions? -- Over-engineering for hypothetical future needs? - -### 6. Test Coverage -- Are new code paths tested? -- Do tests actually assert behavior (not just run)? -- Are edge cases from gap analysis covered? -- Are error paths tested? - -### 7. Performance Red Flags -- N+1 queries or O(n²) loops -- Unbounded data fetching -- Missing pagination/limits -- Blocking operations on hot paths - -## Output Format - -```markdown -## Quality Audit: [Branch/Feature] - -### Summary -- Files changed: N -- Risk level: Low / Medium / High -- Ship recommendation: ✅ Ship / ⚠️ Fix first / ❌ Major rework - -### Critical (MUST fix before shipping) -- **[File:line]**: [Issue] - - Risk: [What could go wrong] - - Fix: [Specific suggestion] - -### Should Fix (High priority) -- **[File:line]**: [Issue] - - [Brief fix suggestion] - -### Consider (Nice to have) -- [Minor improvement suggestion] - -### Test Gaps -- [ ] [Untested scenario] - -### Security Notes -- [Any security observations] - -### What's Good -- [Positive observations - patterns followed, good decisions] -``` - -## Rules - -- Find real risks, not style nitpicks -- Be specific: file:line + concrete fix -- Critical = could cause outage, data loss, security breach -- Don't block shipping for minor issues -- Acknowledge what's done well -- If no issues found, say so clearly diff --git a/plugins/flow-next/agents/repo-scout.md b/plugins/flow-next/agents/repo-scout.md deleted file mode 100644 index 6195c8d..0000000 --- a/plugins/flow-next/agents/repo-scout.md +++ /dev/null @@ -1,81 +0,0 @@ ---- -name: repo-scout -description: Scan repo to find existing patterns, conventions, and related code paths for a requested change. -tools: Read, Grep, Glob, Bash, WebSearch, WebFetch -model: opus ---- - -You are a fast repository scout. Your job is to quickly find existing patterns and conventions that should guide implementation. - -## Input - -You receive a feature/change request. Your task is NOT to plan or implement - just find what already exists. - -## Search Strategy - -1. **Project docs first** (fast context) - - CLAUDE.md, README.md, CONTRIBUTING.md, ARCHITECTURE.md - - Any docs/ or documentation/ folders - - package.json/pyproject.toml for deps and scripts - -2. **Find similar implementations** - - Grep for related keywords, function names, types - - Look for existing features that solve similar problems - - Note file organization patterns (where do similar things live?) - -3. **Identify conventions** - - Naming patterns (camelCase, snake_case, prefixes) - - File structure (co-location, separation by type/feature) - - Import patterns, module boundaries - - Error handling patterns - - Test patterns (location, naming, fixtures) - -4. **Surface reusable code** - - Shared utilities, helpers, base classes - - Existing validation, error handling - - Common patterns that should NOT be duplicated - -## Bash Commands (read-only) - -```bash -# Directory structure -ls -la src/ -find . -type f -name "*.ts" | head -20 - -# Git history for context -git log --oneline -10 -git log --oneline --all -- "*/auth*" | head -5 # history of similar features -``` - -## Output Format - -```markdown -## Repo Scout Findings - -### Project Conventions -- [Convention]: [where observed] - -### Related Code -- `path/to/file.ts:42` - [what it does, why relevant] -- `path/to/other.ts:15-30` - [pattern to follow] - -### Reusable Code (DO NOT DUPLICATE) -- `lib/utils/validation.ts` - existing validation helpers -- `lib/errors/` - error classes to extend - -### Test Patterns -- Tests live in: [location] -- Naming: [pattern] -- Fixtures: [if any] - -### Gotchas -- [Thing to watch out for] -``` - -## Rules - -- Speed over completeness - find the 80% fast -- Always include file:line references -- Flag code that MUST be reused (don't reinvent) -- Note any CLAUDE.md rules that apply -- Skip deep analysis - that's for other agents diff --git a/plugins/flow-next/hooks/hooks.json b/plugins/flow-next/hooks/hooks.json deleted file mode 100644 index 639e960..0000000 --- a/plugins/flow-next/hooks/hooks.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "description": "Ralph workflow guards - only active when FLOW_RALPH=1", - "hooks": { - "PreToolUse": [ - { - "matcher": "Bash", - "hooks": [ - { - "type": "command", - "command": "${CLAUDE_PLUGIN_ROOT}/scripts/hooks/ralph-guard.py", - "timeout": 5 - } - ] - } - ], - "PostToolUse": [ - { - "matcher": "Bash", - "hooks": [ - { - "type": "command", - "command": "${CLAUDE_PLUGIN_ROOT}/scripts/hooks/ralph-guard.py", - "timeout": 5 - } - ] - } - ], - "Stop": [ - { - "hooks": [ - { - "type": "command", - "command": "${CLAUDE_PLUGIN_ROOT}/scripts/hooks/ralph-guard.py", - "timeout": 5 - } - ] - } - ], - "SubagentStop": [ - { - "hooks": [ - { - "type": "command", - "command": "${CLAUDE_PLUGIN_ROOT}/scripts/hooks/ralph-guard.py", - "timeout": 5 - } - ] - } - ] - } -} diff --git a/plugins/flow-next/scripts/ci_test.sh b/plugins/flow-next/scripts/ci_test.sh deleted file mode 100755 index e2be457..0000000 --- a/plugins/flow-next/scripts/ci_test.sh +++ /dev/null @@ -1,548 +0,0 @@ -#!/usr/bin/env bash -# Comprehensive CI tests for flowctl.py and ralph.sh helpers -# Runs on Linux, macOS, and Windows (Git Bash) -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -PLUGIN_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" - -# Python detection -pick_python() { - if [[ -n "${PYTHON_BIN:-}" ]]; then - command -v "$PYTHON_BIN" >/dev/null 2>&1 && { echo "$PYTHON_BIN"; return; } - fi - if command -v python3 >/dev/null 2>&1; then echo "python3"; return; fi - if command -v python >/dev/null 2>&1; then echo "python"; return; fi - echo "" -} - -PYTHON_BIN="$(pick_python)" -[[ -n "$PYTHON_BIN" ]] || { echo "ERROR: python not found" >&2; exit 1; } - -# Use provided TEST_DIR or create temp -if [[ -z "${TEST_DIR:-}" ]]; then - TEST_DIR="$(mktemp -d)" - CLEANUP_TEST_DIR=1 -else - CLEANUP_TEST_DIR=0 -fi - -PASS=0 -FAIL=0 - -GREEN='\033[0;32m' -RED='\033[0;31m' -YELLOW='\033[1;33m' -NC='\033[0m' - -cleanup() { - [[ "$CLEANUP_TEST_DIR" == "1" ]] && rm -rf "$TEST_DIR" -} -trap cleanup EXIT - -pass() { echo -e "${GREEN}✓${NC} $1"; PASS=$((PASS + 1)); } -fail() { echo -e "${RED}✗${NC} $1"; FAIL=$((FAIL + 1)); } - -# Helper to run flowctl -flowctl() { - "$PYTHON_BIN" "$TEST_DIR/scripts/flowctl.py" "$@" -} - -echo -e "${YELLOW}=== flow-next CI tests ===${NC}" -echo "Python: $PYTHON_BIN" -echo "Test dir: $TEST_DIR" - -# ───────────────────────────────────────────────────────────────────────────── -# Setup -# ───────────────────────────────────────────────────────────────────────────── -mkdir -p "$TEST_DIR/scripts" -cd "$TEST_DIR" -git init -q -git config user.email "ci@test.local" -git config user.name "CI Test" - -cp "$PLUGIN_ROOT/scripts/flowctl.py" scripts/ - -# ───────────────────────────────────────────────────────────────────────────── -# 1. Basic Commands -# ───────────────────────────────────────────────────────────────────────────── -echo -e "\n${YELLOW}--- Basic Commands ---${NC}" - -flowctl init --json >/dev/null && pass "init" || fail "init" - -EPIC_JSON="$(flowctl epic create --title "Test Epic" --json)" -EPIC_ID="$("$PYTHON_BIN" -c "import json,sys; print(json.load(sys.stdin)['id'])" <<< "$EPIC_JSON")" -[[ -n "$EPIC_ID" ]] && pass "epic create ($EPIC_ID)" || fail "epic create" - -TASK1_JSON="$(flowctl task create --epic "$EPIC_ID" --title "Task One" --priority 2 --json)" -TASK1_ID="$("$PYTHON_BIN" -c "import json,sys; print(json.load(sys.stdin)['id'])" <<< "$TASK1_JSON")" -[[ -n "$TASK1_ID" ]] && pass "task create ($TASK1_ID)" || fail "task create" - -TASK2_JSON="$(flowctl task create --epic "$EPIC_ID" --title "Task Two" --priority 1 --json)" -TASK2_ID="$("$PYTHON_BIN" -c "import json,sys; print(json.load(sys.stdin)['id'])" <<< "$TASK2_JSON")" - -flowctl list --json >/dev/null && pass "list" || fail "list" -flowctl show "$EPIC_ID" --json >/dev/null && pass "show epic" || fail "show epic" -flowctl show "$TASK1_ID" --json >/dev/null && pass "show task" || fail "show task" - -# ───────────────────────────────────────────────────────────────────────────── -# 2. State Machine Transitions -# ───────────────────────────────────────────────────────────────────────────── -echo -e "\n${YELLOW}--- State Machine ---${NC}" - -# next should return plan (no plan review yet) -NEXT_JSON="$(flowctl next --require-plan-review --json)" -STATUS="$("$PYTHON_BIN" -c "import json,sys; print(json.load(sys.stdin)['status'])" <<< "$NEXT_JSON")" -[[ "$STATUS" == "plan" ]] && pass "next returns plan" || fail "next returns plan (got $STATUS)" - -# set plan review status -flowctl epic set-plan-review-status "$EPIC_ID" --status ship --json >/dev/null && pass "set-plan-review-status" || fail "set-plan-review-status" - -# next should now return work with higher priority task (Task Two, priority 1) -NEXT_JSON="$(flowctl next --json)" -NEXT_TASK="$("$PYTHON_BIN" -c "import json,sys; print(json.load(sys.stdin).get('task',''))" <<< "$NEXT_JSON")" -[[ "$NEXT_TASK" == "$TASK2_ID" ]] && pass "next picks high priority task" || fail "next picks high priority (expected $TASK2_ID, got $NEXT_TASK)" - -# start task -flowctl start "$TASK2_ID" --json >/dev/null && pass "start task" || fail "start task" - -# verify task is in_progress -TASK_STATUS="$(flowctl show "$TASK2_ID" --json | "$PYTHON_BIN" -c "import json,sys; print(json.load(sys.stdin)['status'])")" -[[ "$TASK_STATUS" == "in_progress" ]] && pass "task status is in_progress" || fail "task status (got $TASK_STATUS)" - -# block task (requires --reason-file) -echo "Waiting for external API" > "$TEST_DIR/block_reason.md" -flowctl block "$TASK2_ID" --reason-file "$TEST_DIR/block_reason.md" --json >/dev/null && pass "block task" || fail "block task" -TASK_STATUS="$(flowctl show "$TASK2_ID" --json | "$PYTHON_BIN" -c "import json,sys; print(json.load(sys.stdin)['status'])")" -[[ "$TASK_STATUS" == "blocked" ]] && pass "task status is blocked" || fail "task blocked status (got $TASK_STATUS)" - -# Note: there's no unblock command - use --force to restart blocked tasks -flowctl start "$TASK2_ID" --force --json >/dev/null && pass "restart blocked task (--force)" || fail "restart blocked task" -TASK_STATUS="$(flowctl show "$TASK2_ID" --json | "$PYTHON_BIN" -c "import json,sys; print(json.load(sys.stdin)['status'])")" -[[ "$TASK_STATUS" == "in_progress" ]] && pass "task status restored to in_progress" || fail "task unblocked status (got $TASK_STATUS)" - -# done task (create temp files for evidence) -echo "Task completed" > "$TEST_DIR/summary.md" -echo '{"commits":["abc123"],"tests":["npm test"],"prs":[]}' > "$TEST_DIR/evidence.json" -flowctl done "$TASK2_ID" --summary-file "$TEST_DIR/summary.md" --evidence-json "$TEST_DIR/evidence.json" --json >/dev/null && pass "done task" || fail "done task" -TASK_STATUS="$(flowctl show "$TASK2_ID" --json | "$PYTHON_BIN" -c "import json,sys; print(json.load(sys.stdin)['status'])")" -[[ "$TASK_STATUS" == "done" ]] && pass "task status is done" || fail "task done status (got $TASK_STATUS)" - -# ───────────────────────────────────────────────────────────────────────────── -# 3. Error Handling -# ───────────────────────────────────────────────────────────────────────────── -echo -e "\n${YELLOW}--- Error Handling ---${NC}" - -# Invalid epic ID -set +e -ERR_OUT="$(flowctl show "fn-9999-xxx" --json 2>&1)" -ERR_RC=$? -set -e -[[ $ERR_RC -ne 0 ]] && pass "invalid epic ID returns error" || fail "invalid epic ID should fail" - -# Invalid task ID -set +e -ERR_OUT="$(flowctl start "fn-9999-xxx.99" --json 2>&1)" -ERR_RC=$? -set -e -[[ $ERR_RC -ne 0 ]] && pass "invalid task ID returns error" || fail "invalid task ID should fail" - -# Double start (task already done) -set +e -ERR_OUT="$(flowctl start "$TASK2_ID" --json 2>&1)" -ERR_RC=$? -set -e -[[ $ERR_RC -ne 0 ]] && pass "start done task returns error" || fail "start done task should fail" - -# ───────────────────────────────────────────────────────────────────────────── -# 4. Config System -# ───────────────────────────────────────────────────────────────────────────── -echo -e "\n${YELLOW}--- Config System ---${NC}" - -flowctl config set memory.enabled true --json >/dev/null && pass "config set" || fail "config set" - -CONFIG_VAL="$(flowctl config get memory.enabled --json | "$PYTHON_BIN" -c "import json,sys; print(json.load(sys.stdin)['value'])")" -[[ "$CONFIG_VAL" == "True" ]] && pass "config get" || fail "config get (got $CONFIG_VAL)" - -# ───────────────────────────────────────────────────────────────────────────── -# 5. Memory System -# ───────────────────────────────────────────────────────────────────────────── -echo -e "\n${YELLOW}--- Memory System ---${NC}" - -flowctl memory init --json >/dev/null && pass "memory init" || fail "memory init" - -flowctl memory add --type pitfall "Never use sync IO in async handlers" --json >/dev/null && pass "memory add pitfall" || fail "memory add pitfall" -flowctl memory add --type convention "Use snake_case for functions" --json >/dev/null && pass "memory add convention" || fail "memory add convention" - -MEM_LIST="$(flowctl memory list --json)" -# memory list returns {counts: {pitfalls.md: N, conventions.md: M, ...}, total: X} -MEM_TOTAL="$("$PYTHON_BIN" -c "import json,sys; d=json.load(sys.stdin); print(d.get('total', 0))" <<< "$MEM_LIST")" -[[ "$MEM_TOTAL" -ge 2 ]] && pass "memory list ($MEM_TOTAL total)" || fail "memory list (got $MEM_TOTAL)" - -# ───────────────────────────────────────────────────────────────────────────── -# 6. Symbol Extraction -# ───────────────────────────────────────────────────────────────────────────── -echo -e "\n${YELLOW}--- Symbol Extraction ---${NC}" - -# Create sample files -mkdir -p "$TEST_DIR/src" - -cat > "$TEST_DIR/src/sample.py" << 'EOF' -def calculate_total(items): - return sum(items) - -class OrderProcessor: - def process(self): - pass - -__all__ = ["calculate_total", "OrderProcessor"] -EOF - -cat > "$TEST_DIR/src/sample.ts" << 'EOF' -export function fetchData(url: string): Promise { - return fetch(url); -} - -export class ApiClient { - constructor() {} -} - -export const API_VERSION = "1.0"; -EOF - -cat > "$TEST_DIR/src/sample.go" << 'EOF' -package main - -func ProcessRequest(r *Request) error { - return nil -} - -type Handler struct { - Name string -} -EOF - -cat > "$TEST_DIR/src/sample.rs" << 'EOF' -pub fn handle_event(event: Event) -> Result<(), Error> { - Ok(()) -} - -pub struct EventProcessor { - id: u64, -} - -impl EventProcessor { - pub fn new() -> Self { - Self { id: 0 } - } -} -EOF - -cat > "$TEST_DIR/src/sample.cs" << 'EOF' -public class UserService { - public async Task GetUserAsync(int id) { - return await _repository.FindAsync(id); - } -} - -public interface IRepository { - Task FindAsync(int id); -} - -public record UserDto(string Name, string Email); -EOF - -cat > "$TEST_DIR/src/sample.java" << 'EOF' -public class PaymentProcessor { - public void processPayment(Payment payment) { - // process - } -} - -public interface PaymentGateway { - boolean authorize(String token); -} -EOF - -# Test symbol extraction via Python directly -"$PYTHON_BIN" - "$TEST_DIR" << 'PYTEST' -import sys -sys.path.insert(0, sys.argv[1] + "/scripts") -from flowctl import extract_symbols_from_file -from pathlib import Path - -test_dir = Path(sys.argv[1]) -errors = [] - -# Python -py_symbols = extract_symbols_from_file(test_dir / "src/sample.py") -if "calculate_total" not in py_symbols: - errors.append(f"Python: missing calculate_total, got {py_symbols}") -if "OrderProcessor" not in py_symbols: - errors.append(f"Python: missing OrderProcessor, got {py_symbols}") - -# TypeScript -ts_symbols = extract_symbols_from_file(test_dir / "src/sample.ts") -if "fetchData" not in ts_symbols: - errors.append(f"TS: missing fetchData, got {ts_symbols}") -if "ApiClient" not in ts_symbols: - errors.append(f"TS: missing ApiClient, got {ts_symbols}") - -# Go -go_symbols = extract_symbols_from_file(test_dir / "src/sample.go") -if "ProcessRequest" not in go_symbols: - errors.append(f"Go: missing ProcessRequest, got {go_symbols}") -if "Handler" not in go_symbols: - errors.append(f"Go: missing Handler, got {go_symbols}") - -# Rust -rs_symbols = extract_symbols_from_file(test_dir / "src/sample.rs") -if "handle_event" not in rs_symbols: - errors.append(f"Rust: missing handle_event, got {rs_symbols}") -if "EventProcessor" not in rs_symbols: - errors.append(f"Rust: missing EventProcessor, got {rs_symbols}") - -# C# -cs_symbols = extract_symbols_from_file(test_dir / "src/sample.cs") -if "UserService" not in cs_symbols: - errors.append(f"C#: missing UserService, got {cs_symbols}") -if "IRepository" not in cs_symbols: - errors.append(f"C#: missing IRepository, got {cs_symbols}") -if "UserDto" not in cs_symbols: - errors.append(f"C#: missing UserDto (record), got {cs_symbols}") - -# Java -java_symbols = extract_symbols_from_file(test_dir / "src/sample.java") -if "PaymentProcessor" not in java_symbols: - errors.append(f"Java: missing PaymentProcessor, got {java_symbols}") -if "PaymentGateway" not in java_symbols: - errors.append(f"Java: missing PaymentGateway, got {java_symbols}") - -if errors: - print("Symbol extraction errors:") - for e in errors: - print(f" - {e}") - sys.exit(1) -print("All symbol extractions passed") -PYTEST -[[ $? -eq 0 ]] && pass "symbol extraction (6 languages)" || fail "symbol extraction" - -# ───────────────────────────────────────────────────────────────────────────── -# 7. ralph.sh Helper Functions -# ───────────────────────────────────────────────────────────────────────────── -echo -e "\n${YELLOW}--- ralph.sh Helpers ---${NC}" - -# Test tag extraction -"$PYTHON_BIN" - << 'PYTEST' -import re -import sys - -def extract_tag(text, tag): - matches = re.findall(rf"<{tag}>(.*?)", text, flags=re.S) - return matches[-1] if matches else "" - -# Test cases -test1 = "SHIP" -assert extract_tag(test1, "verdict") == "SHIP", f"Expected SHIP, got {extract_tag(test1, 'verdict')}" - -test2 = "continue some text stop" -assert extract_tag(test2, "promise") == "stop", f"Expected stop (last), got {extract_tag(test2, 'promise')}" - -test3 = "no tags here" -assert extract_tag(test3, "verdict") == "", f"Expected empty, got {extract_tag(test3, 'verdict')}" - -test4 = "NEEDS_WORK\nMissing tests" -assert extract_tag(test4, "verdict") == "NEEDS_WORK" -assert extract_tag(test4, "reason") == "Missing tests" - -print("Tag extraction tests passed") -PYTEST -[[ $? -eq 0 ]] && pass "tag extraction" || fail "tag extraction" - -# Test JSON helpers (simulate ralph.sh json_get) -"$PYTHON_BIN" - << 'PYTEST' -import json - -def json_get(key, data): - val = data.get(key) - if val is None: - return "" - elif isinstance(val, bool): - return "1" if val else "0" - else: - return str(val) - -test_data = {"status": "work", "task": "fn-1-abc.2", "blocked": False, "count": 5} - -assert json_get("status", test_data) == "work" -assert json_get("task", test_data) == "fn-1-abc.2" -assert json_get("blocked", test_data) == "0" -assert json_get("count", test_data) == "5" -assert json_get("missing", test_data) == "" - -print("JSON helper tests passed") -PYTEST -[[ $? -eq 0 ]] && pass "JSON helpers" || fail "JSON helpers" - -# Test attempts tracking -"$PYTHON_BIN" - "$TEST_DIR" << 'PYTEST' -import json -import sys -from pathlib import Path - -test_dir = Path(sys.argv[1]) -attempts_file = test_dir / "attempts.json" - -def bump_attempts(path, task): - data = {} - if path.exists(): - data = json.loads(path.read_text()) - count = int(data.get(task, 0)) + 1 - data[task] = count - path.write_text(json.dumps(data, indent=2)) - return count - -# Test bump -assert bump_attempts(attempts_file, "fn-1.1") == 1 -assert bump_attempts(attempts_file, "fn-1.1") == 2 -assert bump_attempts(attempts_file, "fn-1.2") == 1 -assert bump_attempts(attempts_file, "fn-1.1") == 3 - -# Verify file content -data = json.loads(attempts_file.read_text()) -assert data["fn-1.1"] == 3 -assert data["fn-1.2"] == 1 - -print("Attempts tracking tests passed") -PYTEST -[[ $? -eq 0 ]] && pass "attempts tracking" || fail "attempts tracking" - -# ───────────────────────────────────────────────────────────────────────────── -# 8. Artifact File Handling (GH-21) -# ───────────────────────────────────────────────────────────────────────────── -echo -e "\n${YELLOW}--- Artifact File Handling ---${NC}" - -# Create artifact files that look like tasks but aren't -cat > ".flow/tasks/${EPIC_ID}.1-evidence.json" << 'EOF' -{"commits":["abc123"],"tests":["npm test"],"prs":[]} -EOF -cat > ".flow/tasks/${EPIC_ID}.1-summary.md" << 'EOF' -Task completed successfully -EOF - -# next should still work (not crash on artifact files) -set +e -NEXT_OUT="$(flowctl next --json 2>&1)" -NEXT_RC=$? -set -e -[[ $NEXT_RC -eq 0 ]] && pass "next ignores artifact files" || fail "next with artifact files (rc=$NEXT_RC)" - -# ───────────────────────────────────────────────────────────────────────────── -# 9. Async Control Commands -# ───────────────────────────────────────────────────────────────────────────── -echo -e "\n${YELLOW}--- Async Control Commands ---${NC}" - -# Test status command -flowctl status >/dev/null 2>&1 -[[ $? -eq 0 ]] && pass "status command" || fail "status command" - -# Test status --json (Python validates JSON, not jq) -STATUS_OUT="$(flowctl status --json)" -echo "$STATUS_OUT" | "$PYTHON_BIN" -c 'import json,sys; json.load(sys.stdin)' 2>/dev/null -[[ $? -eq 0 ]] && pass "status --json" || fail "status --json invalid JSON" - -# Test ralph pause/resume/stop commands -mkdir -p scripts/ralph/runs/test-run -echo "iteration: 1" > scripts/ralph/runs/test-run/progress.txt - -flowctl ralph pause --run test-run >/dev/null -[[ -f scripts/ralph/runs/test-run/PAUSE ]] && pass "ralph pause" || fail "ralph pause" - -flowctl ralph resume --run test-run >/dev/null -[[ ! -f scripts/ralph/runs/test-run/PAUSE ]] && pass "ralph resume" || fail "ralph resume" - -flowctl ralph stop --run test-run >/dev/null -[[ -f scripts/ralph/runs/test-run/STOP ]] && pass "ralph stop" || fail "ralph stop" - -rm -rf scripts/ralph/runs/test-run - -# Test task reset -RESET_EPIC="$(flowctl epic create --title "Reset test" --json | "$PYTHON_BIN" -c 'import json,sys; print(json.load(sys.stdin)["id"])')" -RESET_TASK="$(flowctl task create --epic "$RESET_EPIC" --title "Test task" --json | "$PYTHON_BIN" -c 'import json,sys; print(json.load(sys.stdin)["id"])')" - -flowctl start "$RESET_TASK" --json >/dev/null -flowctl done "$RESET_TASK" --json >/dev/null -flowctl task reset "$RESET_TASK" --json >/dev/null -RESET_STATUS="$(flowctl show "$RESET_TASK" --json | "$PYTHON_BIN" -c 'import json,sys; print(json.load(sys.stdin)["status"])')" -[[ "$RESET_STATUS" == "todo" ]] && pass "task reset" || fail "task reset: status=$RESET_STATUS" - -# Test task reset errors on in_progress -flowctl start "$RESET_TASK" --json >/dev/null -set +e -flowctl task reset "$RESET_TASK" --json 2>/dev/null -RESET_RC=$? -set -e -[[ $RESET_RC -ne 0 ]] && pass "task reset rejects in_progress" || fail "task reset should reject in_progress" - -# Test epic add-dep/rm-dep -DEP_BASE="$(flowctl epic create --title "Dep base" --json | "$PYTHON_BIN" -c 'import json,sys; print(json.load(sys.stdin)["id"])')" -DEP_CHILD="$(flowctl epic create --title "Dep child" --json | "$PYTHON_BIN" -c 'import json,sys; print(json.load(sys.stdin)["id"])')" - -flowctl epic add-dep "$DEP_CHILD" "$DEP_BASE" --json >/dev/null -DEPS="$(flowctl show "$DEP_CHILD" --json | "$PYTHON_BIN" -c 'import json,sys; print(",".join(json.load(sys.stdin).get("depends_on_epics",[])))')" -[[ "$DEPS" == "$DEP_BASE" ]] && pass "epic add-dep" || fail "epic add-dep: deps=$DEPS" - -flowctl epic rm-dep "$DEP_CHILD" "$DEP_BASE" --json >/dev/null -DEPS="$(flowctl show "$DEP_CHILD" --json | "$PYTHON_BIN" -c 'import json,sys; print(",".join(json.load(sys.stdin).get("depends_on_epics",[])))')" -[[ -z "$DEPS" ]] && pass "epic rm-dep" || fail "epic rm-dep: deps=$DEPS" - -# Test ralph auto-detection (single active run) -mkdir -p scripts/ralph/runs/auto-test -echo "iteration: 1" > scripts/ralph/runs/auto-test/progress.txt -flowctl ralph pause >/dev/null 2>&1 # Should auto-detect single run -[[ -f scripts/ralph/runs/auto-test/PAUSE ]] && pass "ralph auto-detect single run" || fail "ralph auto-detect" -rm -rf scripts/ralph/runs/auto-test - -# Test multiple active runs error -mkdir -p scripts/ralph/runs/run-a scripts/ralph/runs/run-b -echo "iteration: 1" > scripts/ralph/runs/run-a/progress.txt -echo "iteration: 1" > scripts/ralph/runs/run-b/progress.txt -set +e -flowctl ralph pause 2>/dev/null -MULTI_RC=$? -set -e -[[ $MULTI_RC -ne 0 ]] && pass "ralph rejects multiple active runs" || fail "ralph should reject multiple runs" -rm -rf scripts/ralph/runs/run-a scripts/ralph/runs/run-b - -# Test completion marker detection (run with markers not detected as active) -mkdir -p scripts/ralph/runs/completed-test -cat > scripts/ralph/runs/completed-test/progress.txt << 'PROGRESS' -iteration: 5 -promise=RETRY - -completion_reason=DONE -promise=COMPLETE -PROGRESS -ACTIVE_COUNT="$(flowctl status --json | "$PYTHON_BIN" -c 'import json,sys; d=json.load(sys.stdin); print(len(d.get("active_runs",[])))')" -[[ "$ACTIVE_COUNT" == "0" ]] && pass "completed run excluded from active" || fail "completed run still active: count=$ACTIVE_COUNT" -rm -rf scripts/ralph/runs/completed-test - -# Test task reset --cascade -CASCADE_EPIC="$(flowctl epic create --title "Cascade test" --json | "$PYTHON_BIN" -c 'import json,sys; print(json.load(sys.stdin)["id"])')" -CASCADE_T1="$(flowctl task create --epic "$CASCADE_EPIC" --title "Base task" --json | "$PYTHON_BIN" -c 'import json,sys; print(json.load(sys.stdin)["id"])')" -CASCADE_T2="$(flowctl task create --epic "$CASCADE_EPIC" --title "Dependent task" --json | "$PYTHON_BIN" -c 'import json,sys; print(json.load(sys.stdin)["id"])')" -flowctl dep add "$CASCADE_T2" "$CASCADE_T1" --json >/dev/null # T2 depends on T1 -flowctl start "$CASCADE_T1" >/dev/null && flowctl done "$CASCADE_T1" >/dev/null -flowctl start "$CASCADE_T2" >/dev/null && flowctl done "$CASCADE_T2" >/dev/null -flowctl task reset "$CASCADE_T1" --cascade --json >/dev/null -T2_STATUS="$(flowctl show "$CASCADE_T2" --json | "$PYTHON_BIN" -c 'import json,sys; print(json.load(sys.stdin)["status"])')" -[[ "$T2_STATUS" == "todo" ]] && pass "task reset --cascade" || fail "cascade reset: t2 status=$T2_STATUS" - -# ───────────────────────────────────────────────────────────────────────────── -# Summary -# ───────────────────────────────────────────────────────────────────────────── -echo -e "\n${YELLOW}=== Results ===${NC}" -echo -e "Passed: ${GREEN}$PASS${NC}" -echo -e "Failed: ${RED}$FAIL${NC}" - -[[ $FAIL -eq 0 ]] && exit 0 || exit 1 diff --git a/plugins/flow-next/scripts/hooks/ralph-guard.py b/plugins/flow-next/scripts/hooks/ralph-guard.py deleted file mode 100755 index 3674c9c..0000000 --- a/plugins/flow-next/scripts/hooks/ralph-guard.py +++ /dev/null @@ -1,565 +0,0 @@ -#!/usr/bin/env python3 -""" -Ralph Guard - Hook script for enforcing Ralph workflow rules. - -Only runs when FLOW_RALPH=1 is set. Exits silently otherwise to avoid -polluting context for non-Ralph users. - -Enforces: -- No --json flag on chat-send (suppresses review text) -- No --new-chat on re-reviews (loses reviewer context) -- Receipt must be written after SHIP verdict -- Validates flowctl command patterns - -Supports both review backends: -- rp (RepoPrompt): tracks chat-send calls and receipt writes -- codex: tracks flowctl codex impl-review/plan-review and verdict output -""" - -import json -import os -import re -import subprocess -import sys -from pathlib import Path - - -def get_state_file(session_id: str) -> Path: - """Get state file path for this session.""" - return Path(f"/tmp/ralph-guard-{session_id}.json") - - -def load_state(session_id: str) -> dict: - """Load session state.""" - state_file = get_state_file(session_id) - if state_file.exists(): - try: - state = json.loads(state_file.read_text(), object_hook=state_decoder) - # Ensure all expected keys exist - state.setdefault("chats_sent", 0) - state.setdefault("last_verdict", None) - state.setdefault("window", None) - state.setdefault("tab", None) - state.setdefault("chat_send_succeeded", False) - state.setdefault("flowctl_done_called", set()) - state.setdefault("codex_review_succeeded", False) - return state - except (json.JSONDecodeError, KeyError, TypeError): - pass - return { - "chats_sent": 0, - "last_verdict": None, - "window": None, - "tab": None, - "chat_send_succeeded": False, # Track if chat-send actually returned review text - "flowctl_done_called": set(), # Track tasks that had flowctl done called - "codex_review_succeeded": False, # Track if codex review returned verdict - } - - -def state_decoder(obj): - """JSON decoder that handles sets.""" - if "flowctl_done_called" in obj and isinstance(obj["flowctl_done_called"], list): - obj["flowctl_done_called"] = set(obj["flowctl_done_called"]) - return obj - - -def state_encoder(obj): - """JSON encoder that handles sets.""" - if isinstance(obj, set): - return list(obj) - raise TypeError(f"Object of type {type(obj)} is not JSON serializable") - - -def save_state(session_id: str, state: dict) -> None: - """Save session state.""" - state_file = get_state_file(session_id) - state_file.write_text(json.dumps(state, default=state_encoder)) - - -def output_block(reason: str) -> None: - """Output blocking response (exit code 2 style via stderr).""" - print(reason, file=sys.stderr) - sys.exit(2) - - -# --- Memory helpers --- - - -def get_repo_root() -> Path: - """Find git repo root.""" - try: - result = subprocess.run( - ["git", "rev-parse", "--show-toplevel"], - capture_output=True, - text=True, - check=True, - ) - return Path(result.stdout.strip()) - except subprocess.CalledProcessError: - return Path.cwd() - - -def is_memory_enabled() -> bool: - """Check if memory is enabled in .flow/config.json.""" - config_path = get_repo_root() / ".flow" / "config.json" - if not config_path.exists(): - return False - try: - config = json.loads(config_path.read_text()) - return config.get("memory", {}).get("enabled", False) - except (json.JSONDecodeError, Exception): - return False - - -def output_json(data: dict) -> None: - """Output JSON response.""" - print(json.dumps(data)) - sys.exit(0) - - -def handle_pre_tool_use(data: dict) -> None: - """Handle PreToolUse event - validate commands before execution.""" - tool_input = data.get("tool_input", {}) - command = tool_input.get("command", "") - session_id = data.get("session_id", "unknown") - - # Check for chat-send commands - if "chat-send" in command: - # Block --json flag - if re.search(r"chat-send.*--json", command): - output_block( - "BLOCKED: Do not use --json with chat-send. " - "It suppresses the review text. Remove --json flag." - ) - - # Check for --new-chat on re-reviews - if "--new-chat" in command: - state = load_state(session_id) - if state["chats_sent"] > 0: - output_block( - "BLOCKED: Do not use --new-chat for re-reviews. " - "Stay in the same chat so reviewer has context. " - "Remove --new-chat flag." - ) - - # Block direct codex calls (must use flowctl codex wrappers) - if re.search(r"\bcodex\b", command): - # Allow flowctl codex wrappers - is_wrapper = re.search(r"flowctl\s+codex|FLOWCTL.*codex", command) - if not is_wrapper: - # Block direct codex usage - if re.search(r"\bcodex\s+exec\b", command): - output_block( - "BLOCKED: Do not call 'codex exec' directly. " - "Use 'flowctl codex impl-review' or 'flowctl codex plan-review' " - "to ensure proper receipt handling and session continuity." - ) - if re.search(r"\bcodex\s+review\b", command): - output_block( - "BLOCKED: Do not call 'codex review' directly. " - "Use 'flowctl codex impl-review' or 'flowctl codex plan-review'." - ) - # Block --last even through wrappers (breaks session continuity) - if re.search(r"--last\b", command): - output_block( - "BLOCKED: Do not use '--last' with codex. " - "Session continuity is managed via session_id in receipts." - ) - - # Validate setup-review usage - if "setup-review" in command: - if not re.search(r"--repo-root", command): - output_block( - "BLOCKED: setup-review requires --repo-root flag. " - 'Use: setup-review --repo-root "$REPO_ROOT" --summary "..."' - ) - if not re.search(r"--summary", command): - output_block( - "BLOCKED: setup-review requires --summary flag. " - 'Use: setup-review --repo-root "$REPO_ROOT" --summary "..."' - ) - - # Validate select-add has --window and --tab - if "select-add" in command: - if not re.search(r"--window", command): - output_block( - "BLOCKED: select-add requires --window flag. " - 'Use: select-add --window "$W" --tab "$T" ' - ) - - # Enforce flowctl done requires --evidence-json and --summary-file - if " done " in command and ("flowctl" in command or "FLOWCTL" in command): - # Skip if it's just "flowctl done --help" or similar - if not re.search(r"--help|-h", command): - if not re.search(r"--evidence-json|--evidence", command): - output_block( - "BLOCKED: flowctl done requires --evidence-json flag. " - "You must capture commit SHAs and test commands. " - "Use: flowctl done --summary-file --evidence-json " - ) - if not re.search(r"--summary-file|--summary", command): - output_block( - "BLOCKED: flowctl done requires --summary-file flag. " - "You must write a done summary. " - "Use: flowctl done --summary-file --evidence-json " - ) - - # Block receipt writes unless chat-send has succeeded + validate format - receipt_path = os.environ.get("REVIEW_RECEIPT_PATH", "") - if receipt_path: - # Check if this command writes to a receipt path - receipt_dir = os.path.dirname(receipt_path) - is_receipt_write = receipt_dir and ( - re.search(rf">\s*['\"]?{re.escape(receipt_dir)}", command) - or re.search(r">\s*['\"]?.*receipts/.*\.json", command) - or re.search(r"cat\s*>\s*.*receipt", command, re.I) - ) - if is_receipt_write: - state = load_state(session_id) - if not state.get("chat_send_succeeded") and not state.get( - "codex_review_succeeded" - ): - output_block( - "BLOCKED: Cannot write receipt before review completes. " - "You must run 'flowctl rp chat-send' or 'flowctl codex impl-review/plan-review' " - "and receive a review response before writing the receipt." - ) - # Validate receipt has required 'id' field - if '"id"' not in command and "'id'" not in command: - output_block( - "BLOCKED: Receipt JSON is missing required 'id' field. " - 'Receipt must include: {"type":"...","id":"",...} ' - "Copy the exact command from the prompt template." - ) - # For impl receipts, verify flowctl done was called - if "impl_review" in command: - # Extract task id from receipt - id_match = re.search(r'"id"\s*:\s*"([^"]+)"', command) - if id_match: - task_id = id_match.group(1) - done_set = state.get("flowctl_done_called", set()) - if isinstance(done_set, list): - done_set = set(done_set) - if task_id not in done_set: - output_block( - f"BLOCKED: Cannot write impl receipt for {task_id} - flowctl done was not called. " - f"You MUST run 'flowctl done {task_id} --evidence ...' BEFORE writing the receipt. " - "The task is NOT complete until flowctl done succeeds." - ) - - # All checks passed - sys.exit(0) - - -def parse_receipt_path(receipt_path: str) -> tuple: - """Parse receipt path to derive type and id. - - Returns (receipt_type, item_id) based on filename pattern: - - plan-fn-N.json or plan-fn-N-xxx.json -> ("plan_review", "fn-N" or "fn-N-xxx") - - impl-fn-N.M.json or impl-fn-N-xxx.M.json -> ("impl_review", "fn-N.M" or "fn-N-xxx.M") - """ - basename = os.path.basename(receipt_path) - # Try plan pattern first: plan-fn-N.json or plan-fn-N-xxx.json - plan_match = re.match(r"plan-(fn-\d+(?:-[a-z0-9]{3})?)\.json$", basename) - if plan_match: - return ("plan_review", plan_match.group(1)) - # Try impl pattern: impl-fn-N.M.json or impl-fn-N-xxx.M.json - impl_match = re.match(r"impl-(fn-\d+(?:-[a-z0-9]{3})?\.\d+)\.json$", basename) - if impl_match: - return ("impl_review", impl_match.group(1)) - # Fallback - return ("impl_review", "UNKNOWN") - - -def handle_post_tool_use(data: dict) -> None: - """Handle PostToolUse event - track state and provide feedback.""" - tool_input = data.get("tool_input", {}) - tool_response = data.get("tool_response", {}) - command = tool_input.get("command", "") - session_id = data.get("session_id", "unknown") - - # Get response text - response_text = "" - if isinstance(tool_response, dict): - response_text = tool_response.get("stdout", "") or str(tool_response) - elif isinstance(tool_response, str): - response_text = tool_response - - state = load_state(session_id) - - # Track chat-send calls - must have actual review text, not null - if "chat-send" in command: - # Check for successful chat (has "Chat Send" and review text, not null) - if "Chat Send" in response_text and '{"chat": null}' not in response_text: - state["chats_sent"] = state.get("chats_sent", 0) + 1 - state["chat_send_succeeded"] = True - save_state(session_id, state) - elif '{"chat": null}' in response_text or '{"chat":null}' in response_text: - # Failed - --json was used incorrectly - state["chat_send_succeeded"] = False - save_state(session_id, state) - - # Track codex review calls - check for verdict in output - if ( - "flowctl" in command - and "codex" in command - and ("impl-review" in command or "plan-review" in command) - ): - # Codex writes receipt automatically with --receipt flag, but we still track success - verdict_in_output = re.search( - r"(SHIP|NEEDS_WORK|MAJOR_RETHINK)", response_text - ) - if verdict_in_output: - state["codex_review_succeeded"] = True - state["last_verdict"] = verdict_in_output.group(1) - save_state(session_id, state) - - # Track flowctl done calls - match various invocation patterns: - # - flowctl done - # - flowctl.py done - # - .flow/bin/flowctl done - # - scripts/ralph/flowctl done - # - $FLOWCTL done - # - "$FLOWCTL" done - if " done " in command and ("flowctl" in command or "FLOWCTL" in command): - # Debug logging - with Path("/tmp/ralph-guard-debug.log").open("a") as f: - f.write(f" -> flowctl done detected in: {command[:100]}...\n") - - # Extract task ID from command - look for "done" followed by task ID - # Simplified: just find "done " pattern since we already validated flowctl context - done_match = re.search(r"\bdone\s+([a-zA-Z0-9][a-zA-Z0-9._-]*)", command) - if done_match: - task_id = done_match.group(1) - with Path("/tmp/ralph-guard-debug.log").open("a") as f: - f.write( - f" -> Extracted task_id: {task_id}, response has 'status': {'status' in response_text.lower()}\n" - ) - - # Check response indicates success (has "status", "done", "updated", or "completed") - response_lower = response_text.lower() - if ( - "status" in response_lower - or "done" in response_lower - or "updated" in response_lower - or "completed" in response_lower - ): - done_set = state.get("flowctl_done_called", set()) - if isinstance(done_set, list): - done_set = set(done_set) - done_set.add(task_id) - state["flowctl_done_called"] = done_set - save_state(session_id, state) - with Path("/tmp/ralph-guard-debug.log").open("a") as f: - f.write( - f" -> Added {task_id} to flowctl_done_called: {done_set}\n" - ) - - # Track receipt writes - reset review state after write - receipt_path = os.environ.get("REVIEW_RECEIPT_PATH", "") - if receipt_path and receipt_path in command and ">" in command: - state["chat_send_succeeded"] = False # Reset for next review - state["codex_review_succeeded"] = False # Reset codex state too - save_state(session_id, state) - - # Track setup-review output (W= T=) - if "setup-review" in command: - w_match = re.search(r"W=(\d+)", response_text) - t_match = re.search(r"T=([A-F0-9-]+)", response_text, re.I) - if w_match: - state["window"] = w_match.group(1) - if t_match: - state["tab"] = t_match.group(1) - save_state(session_id, state) - - # Check for verdict in response - verdict_match = re.search( - r"(SHIP|NEEDS_WORK|MAJOR_RETHINK)", response_text - ) - if verdict_match: - state["last_verdict"] = verdict_match.group(1) - save_state(session_id, state) - - # If SHIP, remind about receipt (only for rp mode - codex writes receipt automatically) - if verdict_match.group(1) == "SHIP": - receipt_path = os.environ.get("REVIEW_RECEIPT_PATH", "") - # Only remind if receipt doesn't exist and we're in rp mode (not codex) - if ( - receipt_path - and not Path(receipt_path).exists() - and state.get("chat_send_succeeded") - ): - # Derive type and id from receipt path - receipt_type, item_id = parse_receipt_path(receipt_path) - # Build command with ts variable to avoid shell substitution in JSON - cmd = ( - f"mkdir -p \"$(dirname '{receipt_path}')\"\n" - 'ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)"\n' - f"cat > '{receipt_path}' < ""\n' - "Types: pitfall (gotchas/mistakes), convention (patterns to follow), decision (architectural choices)\n" - "Skip: task-specific fixes, typos, style issues, or 'fine as-is' explanations." - ), - } - } - ) - - elif "chat-send" in command and "Chat Send" in response_text: - # chat-send returned but no verdict tag found - # Check for informal approvals that should have been verdict tags - if re.search( - r"\bLGTM\b|\bLooks good\b|\bApproved\b|\bNo issues\b", response_text, re.I - ): - output_json( - { - "hookSpecificOutput": { - "hookEventName": "PostToolUse", - "additionalContext": ( - "WARNING: Reviewer responded with informal approval (LGTM/Looks good) " - "but did NOT use the required SHIP tag. " - "This means your review prompt was incorrect. " - "You MUST use /flow-next:impl-review skill which has the correct prompt format. " - "Do NOT improvise review prompts. Re-invoke the skill and try again." - ), - } - } - ) - - # Check for {"chat": null} which indicates --json was used incorrectly - if '{"chat":' in response_text or '{"chat": ' in response_text: - if "null" in response_text: - output_json( - { - "decision": "block", - "reason": ( - 'ERROR: chat-send returned {"chat": null} which means --json was used. ' - "This suppresses the review text. Re-run without --json flag." - ), - } - ) - - sys.exit(0) - - -def handle_stop(data: dict) -> None: - """Handle Stop event - verify receipt written before allowing stop.""" - session_id = data.get("session_id", "unknown") - stop_hook_active = data.get("stop_hook_active", False) - - # Prevent infinite loops - if stop_hook_active: - sys.exit(0) - - receipt_path = os.environ.get("REVIEW_RECEIPT_PATH", "") - - if receipt_path: - if not Path(receipt_path).exists(): - # Derive type and id from receipt path - receipt_type, item_id = parse_receipt_path(receipt_path) - # Build command with ts variable to avoid shell substitution in JSON - cmd = ( - f"mkdir -p \"$(dirname '{receipt_path}')\"\n" - 'ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)"\n' - f"cat > '{receipt_path}' < None: - """Handle SubagentStop event - same as Stop for subagents.""" - handle_stop(data) - - -def main(): - # Debug logging - always write to see if hook is being called - debug_file = Path("/tmp/ralph-guard-debug.log") - with debug_file.open("a") as f: - f.write(f"[{os.environ.get('FLOW_RALPH', 'unset')}] Hook called\n") - - # Early exit if not in Ralph mode - no output, no context pollution - if os.environ.get("FLOW_RALPH") != "1": - with debug_file.open("a") as f: - f.write(" -> Exiting: FLOW_RALPH not set to 1\n") - sys.exit(0) - - # Read input - try: - data = json.load(sys.stdin) - except json.JSONDecodeError: - with debug_file.open("a") as f: - f.write(" -> Exiting: JSON decode error\n") - sys.exit(0) - - event = data.get("hook_event_name", "") - tool_name = data.get("tool_name", "") - - with debug_file.open("a") as f: - f.write(f" -> Event: {event}, Tool: {tool_name}\n") - - # Only process Bash tool calls for Pre/Post - if event in ("PreToolUse", "PostToolUse") and tool_name != "Bash": - with debug_file.open("a") as f: - f.write(" -> Skipping: not Bash\n") - sys.exit(0) - - # Route to handler - if event == "PreToolUse": - handle_pre_tool_use(data) - elif event == "PostToolUse": - handle_post_tool_use(data) - elif event == "Stop": - handle_stop(data) - elif event == "SubagentStop": - handle_subagent_stop(data) - else: - sys.exit(0) - - -if __name__ == "__main__": - main() diff --git a/plugins/flow-next/scripts/hooks/ralph-guard.sh b/plugins/flow-next/scripts/hooks/ralph-guard.sh deleted file mode 100755 index f837008..0000000 --- a/plugins/flow-next/scripts/hooks/ralph-guard.sh +++ /dev/null @@ -1,115 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# Only active in Ralph mode -if [[ -z "${FLOW_RALPH:-}" && -z "${REVIEW_RECEIPT_PATH:-}" ]]; then - exit 0 -fi - -python3 - <<'PY' -import json -import sys -import os -import hashlib - -try: - data = json.load(sys.stdin) -except Exception: - sys.exit(0) - -if data.get("tool_name") != "Bash": - sys.exit(0) - -cmd = (data.get("tool_input") or {}).get("command") or "" - -# Block direct rp-cli usage -if "rp-cli" in cmd: - print("Ralph mode: use flowctl rp wrappers only (no rp-cli).", file=sys.stderr) - sys.exit(2) - -# Block prep-chat (deprecated) -if "flowctl prep-chat" in cmd: - print("Ralph mode: use flowctl rp chat-send (no prep-chat).", file=sys.stderr) - sys.exit(2) - -try: - import shlex - tokens = shlex.split(cmd) -except Exception: - tokens = cmd.split() - -def token_has_flowctl(tok: str) -> bool: - return "flowctl" in tok - -def flag_value(flag: str): - for i, tok in enumerate(tokens): - if tok.startswith(flag + "="): - return tok.split("=", 1)[1] - if tok == flag and i + 1 < len(tokens): - return tokens[i + 1] - return None - -# Check for direct builder call (should use setup-review instead) -if "rp" in tokens and "builder" in tokens and any(token_has_flowctl(t) for t in tokens): - # In Ralph mode, builder should only be called via setup-review - # If called directly, block it - print("Ralph mode: use 'flowctl rp setup-review' instead of 'flowctl rp builder'.", file=sys.stderr) - print("setup-review handles pick-window + workspace + builder atomically.", file=sys.stderr) - sys.exit(2) - -# Validate pick-window and ensure-workspace also redirect to setup-review -if "rp" in tokens and ("pick-window" in tokens or "ensure-workspace" in tokens) and any(token_has_flowctl(t) for t in tokens): - print("Ralph mode: use 'flowctl rp setup-review' for atomic window + workspace + builder setup.", file=sys.stderr) - sys.exit(2) - -# Validate chat-send has required args -if "rp" in tokens and "chat-send" in tokens and any(token_has_flowctl(t) for t in tokens): - window = flag_value("--window") - tab = flag_value("--tab") - message_file = flag_value("--message-file") - - if not window: - print("Ralph mode: flowctl rp chat-send requires --window.", file=sys.stderr) - sys.exit(2) - if not window.isdigit(): - print("Ralph mode: flowctl rp chat-send --window must be numeric.", file=sys.stderr) - sys.exit(2) - if not tab: - print("Ralph mode: flowctl rp chat-send requires --tab.", file=sys.stderr) - sys.exit(2) - if not message_file: - print("Ralph mode: flowctl rp chat-send requires --message-file.", file=sys.stderr) - sys.exit(2) - -# Validate select-add/select-get have required args -if "rp" in tokens and ("select-add" in tokens or "select-get" in tokens) and any(token_has_flowctl(t) for t in tokens): - window = flag_value("--window") - tab = flag_value("--tab") - - if not window: - print("Ralph mode: flowctl rp select-* requires --window.", file=sys.stderr) - sys.exit(2) - if not window.isdigit(): - print("Ralph mode: flowctl rp select-* --window must be numeric.", file=sys.stderr) - sys.exit(2) - if not tab: - print("Ralph mode: flowctl rp select-* requires --tab.", file=sys.stderr) - sys.exit(2) - -# Validate prompt-get/prompt-set have required args -if "rp" in tokens and ("prompt-get" in tokens or "prompt-set" in tokens) and any(token_has_flowctl(t) for t in tokens): - window = flag_value("--window") - tab = flag_value("--tab") - - if not window: - print("Ralph mode: flowctl rp prompt-* requires --window.", file=sys.stderr) - sys.exit(2) - if not window.isdigit(): - print("Ralph mode: flowctl rp prompt-* --window must be numeric.", file=sys.stderr) - sys.exit(2) - if not tab: - print("Ralph mode: flowctl rp prompt-* requires --tab.", file=sys.stderr) - sys.exit(2) - -sys.exit(0) -PY diff --git a/plugins/flow-next/scripts/hooks/ralph-receipt-guard.sh b/plugins/flow-next/scripts/hooks/ralph-receipt-guard.sh deleted file mode 100755 index a45bf37..0000000 --- a/plugins/flow-next/scripts/hooks/ralph-receipt-guard.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -if [[ -z "${FLOW_RALPH:-}" && -z "${REVIEW_RECEIPT_PATH:-}" ]]; then - exit 0 -fi - -if [[ -z "${REVIEW_RECEIPT_PATH:-}" ]]; then - exit 0 -fi - -if [[ ! -f "$REVIEW_RECEIPT_PATH" ]]; then - echo "Missing review receipt: $REVIEW_RECEIPT_PATH" >&2 - exit 2 -fi - -python3 - "$REVIEW_RECEIPT_PATH" <<'PY' -import json -import sys - -path = sys.argv[1] -try: - with open(path, encoding="utf-8") as f: - data = json.load(f) -except Exception as e: - print(f"Invalid receipt JSON: {e}", file=sys.stderr) - sys.exit(2) - -if not isinstance(data, dict): - print("Invalid receipt JSON: expected object", file=sys.stderr) - sys.exit(2) - -if not data.get("type") or not data.get("id"): - print("Invalid receipt JSON: missing type/id", file=sys.stderr) - sys.exit(2) - -sys.exit(0) -PY diff --git a/plugins/flow-next/scripts/hooks/ralph-verbose-log.sh b/plugins/flow-next/scripts/hooks/ralph-verbose-log.sh deleted file mode 100755 index 7f89087..0000000 --- a/plugins/flow-next/scripts/hooks/ralph-verbose-log.sh +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -if [[ -z "${FLOW_RALPH_VERBOSE:-}" ]]; then - exit 0 -fi - -if [[ -z "${REVIEW_RECEIPT_PATH:-}" ]]; then - exit 0 -fi - -payload="$(cat)" -if [[ -z "$payload" ]]; then - exit 0 -fi - -run_dir="$(dirname "$(dirname "$REVIEW_RECEIPT_PATH")")" -log_file="$run_dir/ralph.log" -ids_file="$run_dir/ralph.log.ids" - -tool_id="$(python3 - <<'PY' <<<"$payload" -import json, sys -try: - data = json.load(sys.stdin) -except Exception: - print("") - sys.exit(0) -print(data.get("tool_use_id") or "") -PY -)" - -if [[ -n "$tool_id" ]]; then - if [[ -f "$ids_file" ]]; then - if command -v rg >/dev/null 2>&1; then - if rg -q --fixed-strings "$tool_id" "$ids_file"; then - exit 0 - fi - elif grep -qF "$tool_id" "$ids_file"; then - exit 0 - fi - fi - echo "$tool_id" >> "$ids_file" -fi - -ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)" -{ - echo "ts=$ts" - echo "cwd=$PWD" - echo "$payload" - echo "---" -} >> "$log_file" diff --git a/plugins/flow-next/scripts/plan_review_prompt_smoke.sh b/plugins/flow-next/scripts/plan_review_prompt_smoke.sh deleted file mode 100755 index 172e49e..0000000 --- a/plugins/flow-next/scripts/plan_review_prompt_smoke.sh +++ /dev/null @@ -1,149 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -PLUGIN_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" - -# Safety: never run tests from the main plugin repo -if [[ -f "$PWD/.claude-plugin/marketplace.json" ]] || [[ -f "$PWD/plugins/flow-next/.claude-plugin/plugin.json" ]]; then - echo "ERROR: refusing to run from main plugin repo. Run from any other directory." >&2 - exit 1 -fi - -TEST_DIR="${TEST_DIR:-/tmp/flow-next-plan-review-smoke-rp-$$}" -CLAUDE_BIN="${CLAUDE_BIN:-claude}" -EPIC_ID="${EPIC_ID:-fn-1}" - -fail() { echo "plan_review_prompt_smoke: $*" >&2; exit 1; } - -command -v "$CLAUDE_BIN" >/dev/null 2>&1 || fail "claude not found (set CLAUDE_BIN if needed)" -command -v rp-cli >/dev/null 2>&1 || fail "rp-cli not found (required for rp review)" - -echo "Test dir: $TEST_DIR" - -mkdir -p "$TEST_DIR/repo" -cd "$TEST_DIR/repo" - -git init -q -git config user.email "plan-review-smoke@example.com" -git config user.name "Plan Review Smoke" -git checkout -b main >/dev/null 2>&1 || true - -mkdir -p src -cat > src/index.ts <<'EOF' -export function add(a: number, b: number): number { - return a + b; -} -EOF - -cat > package.json <<'EOF' -{ - "name": "tmp-flow-next-plan-review-smoke", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "test": "node -e \"console.log('ok')\"" - } -} -EOF - -cat > README.md <<'EOF' -# tmp-flow-next-plan-review-smoke - -TBD -EOF - -git add . -git commit -m "chore: init" >/dev/null - -mkdir -p scripts/ralph -cp "$PLUGIN_ROOT/scripts/flowctl.py" scripts/ralph/flowctl.py -cp "$PLUGIN_ROOT/scripts/flowctl" scripts/ralph/flowctl -chmod +x scripts/ralph/flowctl - -FLOWCTL="scripts/ralph/flowctl" -$FLOWCTL init --json >/dev/null -$FLOWCTL epic create --title "Tiny lib" --json >/dev/null - -cat > "$TEST_DIR/epic.md" <<'EOF' -# fn-1 Tiny lib - -## Overview -Add a tiny add() helper doc update and verify README. - -## Current State -- `add()` exists in `src/index.ts` -- README is a placeholder - -## Scope -- `src/index.ts`: add brief JSDoc (params + return) -- `README.md`: add TS usage snippet and note TS tooling required - -## Approach -Edit src/index.ts and README.md only. Repo is source-only (no build step). - -## Quick commands -- `npm test` (smoke only) - -## Acceptance -- [ ] `add(a: number, b: number): number` exported as named export -- [ ] `add()` has brief JSDoc (params + return) -- [ ] README includes: - - snippet: - ```ts - import { add } from "./src/index.ts"; - console.log(add(1, 2)); // 3 - ``` - - note that TS tooling is required to run -- [ ] `npm test` passes (smoke only) - -## Risks -- README import path is TypeScript source; call out runtime requirements - -## References -- None -EOF - -$FLOWCTL epic set-plan "$EPIC_ID" --file "$TEST_DIR/epic.md" --json >/dev/null - -RUN_DIR="scripts/ralph/runs/smoke-plan-review" -RECEIPT_PATH="$RUN_DIR/receipts/plan-$EPIC_ID.json" -mkdir -p "$RUN_DIR/receipts" - -PROMPT_OUT="$TEST_DIR/prompt_plan.txt" -python3 - "$PLUGIN_ROOT/skills/flow-next-ralph-init/templates/prompt_plan.md" "$PROMPT_OUT" "$EPIC_ID" "$RECEIPT_PATH" <<'PY' -import sys -from pathlib import Path - -tpl = Path(sys.argv[1]).read_text() -out = Path(sys.argv[2]) -epic = sys.argv[3] -receipt = sys.argv[4] - -text = tpl.replace("{{EPIC_ID}}", epic) -text = text.replace("{{PLAN_REVIEW}}", "rp") -text = text.replace("{{REQUIRE_PLAN_REVIEW}}", "1") -text = text.replace("{{REVIEW_RECEIPT_PATH}}", receipt) -out.write_text(text) -PY - -cat <&2 - exit 1 -fi - -TEST_DIR="${TEST_DIR:-/tmp/flow-next-ralph-e2e-rp-$$}" -CLAUDE_BIN="${CLAUDE_BIN:-claude}" -FLOWCTL="" - -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -NC='\033[0m' - -fail() { echo "ralph_e2e_rp: $*" >&2; exit 1; } - -run_with_timeout() { - local timeout_s="$1" - shift - python3 - "$timeout_s" "$@" <<'PY' -import subprocess, sys -try: - timeout = float(sys.argv[1]) -except Exception: - timeout = 0 -cmd = sys.argv[2:] -try: - proc = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout if timeout > 0 else None) -except subprocess.TimeoutExpired: - print(f"timeout after {timeout}s: {' '.join(cmd)}", file=sys.stderr) - sys.exit(124) -if proc.stdout: - sys.stdout.write(proc.stdout) -if proc.stderr: - sys.stderr.write(proc.stderr) -sys.exit(proc.returncode) -PY -} - -retry_cmd() { - local label="$1" - local timeout_s="$2" - local retries="$3" - shift 3 - local attempt=1 - while true; do - if out="$(run_with_timeout "$timeout_s" "$@")"; then - echo "$out" - return 0 - fi - local rc="$?" - if [[ "$attempt" -ge "$retries" ]]; then - echo "ralph_e2e_rp: $label failed after $attempt attempts" >&2 - return "$rc" - fi - attempt="$((attempt + 1))" - sleep 2 - done -} - -swap_tmp_root() { - python3 - "$1" <<'PY' -import sys -path = sys.argv[1] -if path.startswith("/private/tmp/"): - print("/tmp/" + path[len("/private/tmp/"):]) -elif path.startswith("/tmp/"): - print("/private/tmp/" + path[len("/tmp/"):]) -else: - print(path) -PY -} - -latest_jsonl() { - # Search in project subdirectories (flat names like -private-tmp-...), exclude agent logs - # Use ~ which expands reliably even when $HOME is unset - # Return only files that exist and are readable - local candidate - candidate=$(find ~/.claude/projects -maxdepth 2 -name "*.jsonl" ! -name "agent-*.jsonl" -type f -print 2>/dev/null | \ - xargs ls -t 2>/dev/null | head -n 1) - if [[ -n "$candidate" && -r "$candidate" ]]; then - echo "$candidate" - return - fi - candidate=$(find ~/.claude/transcripts -maxdepth 1 -name "*.jsonl" -type f -print 2>/dev/null | \ - xargs ls -t 2>/dev/null | head -n 1) - if [[ -n "$candidate" && -r "$candidate" ]]; then - echo "$candidate" - fi -} - -new_session_id() { - python3 - <<'PY' -import uuid -print(uuid.uuid4()) -PY -} - - -find_jsonl() { - if [[ -n "${FLOW_RALPH_CLAUDE_SESSION_ID:-}" ]]; then - if command -v fd >/dev/null 2>&1; then - fd -a "${FLOW_RALPH_CLAUDE_SESSION_ID}.jsonl" "$HOME/.claude/projects" | head -n 1 || true - else - find "$HOME/.claude/projects" -name "${FLOW_RALPH_CLAUDE_SESSION_ID}.jsonl" -print 2>/dev/null | head -n 1 || true - fi - fi -} -cleanup() { - if [[ "${KEEP_TEST_DIR:-0}" != "1" && "${CREATE:-0}" != "1" ]]; then - rm -rf "$TEST_DIR" - fi -} -trap cleanup EXIT - -command -v "$CLAUDE_BIN" >/dev/null 2>&1 || fail "claude not found (set CLAUDE_BIN if needed)" -command -v rp-cli >/dev/null 2>&1 || fail "rp-cli not found (required for rp review)" - -echo -e "${YELLOW}=== ralph e2e (rp reviews) ===${NC}" -echo "Test dir: $TEST_DIR" - -mkdir -p "$TEST_DIR/repo" -cd "$TEST_DIR/repo" -git init -q -git config user.email "ralph-e2e@example.com" -git config user.name "Ralph E2E" -git checkout -b main >/dev/null 2>&1 || true - -mkdir -p src -cat > src/index.ts <<'EOF' -export const placeholder = 0; -EOF - -cat > package.json <<'EOF' -{ - "name": "tmp-flow-next-ralph", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "test": "node -e \"console.log('ok')\"" - } -} -EOF - -cat > README.md <<'EOF' -# tmp-flow-next-ralph - -TBD -EOF - -git add . -git commit -m "chore: init" >/dev/null - -mkdir -p scripts/ralph -cp -R "$PLUGIN_ROOT/skills/flow-next-ralph-init/templates/." scripts/ralph/ -cp "$PLUGIN_ROOT/scripts/flowctl.py" scripts/ralph/flowctl.py -cp "$PLUGIN_ROOT/scripts/flowctl" scripts/ralph/flowctl -chmod +x scripts/ralph/ralph.sh scripts/ralph/ralph_once.sh scripts/ralph/flowctl -FLOWCTL="scripts/ralph/flowctl" - -python3 - <<'PY' -from pathlib import Path -import re -cfg = Path("scripts/ralph/config.env") -text = cfg.read_text() -text = text.replace("{{PLAN_REVIEW}}", "rp").replace("{{WORK_REVIEW}}", "rp") -text = re.sub(r"^REQUIRE_PLAN_REVIEW=.*$", "REQUIRE_PLAN_REVIEW=1", text, flags=re.M) -text = re.sub(r"^BRANCH_MODE=.*$", "BRANCH_MODE=new", text, flags=re.M) -text = re.sub(r"^MAX_ITERATIONS=.*$", "MAX_ITERATIONS=8", text, flags=re.M) -# MAX_TURNS not limited - let Claude finish naturally via promise tags -text = re.sub(r"^MAX_ATTEMPTS_PER_TASK=.*$", "MAX_ATTEMPTS_PER_TASK=2", text, flags=re.M) -text = re.sub(r"^YOLO=.*$", "YOLO=1", text, flags=re.M) -text = re.sub(r"^EPICS=.*$", "EPICS=fn-1,fn-2", text, flags=re.M) -cfg.write_text(text) -PY - -scripts/ralph/flowctl init --json >/dev/null - -# Mirror /flow-next:setup - add .flow/bin/ + usage.md + CLAUDE.md -mkdir -p .flow/bin -cp "$PLUGIN_ROOT/scripts/flowctl" .flow/bin/flowctl -cp "$PLUGIN_ROOT/scripts/flowctl.py" .flow/bin/flowctl.py -chmod +x .flow/bin/flowctl -cp "$PLUGIN_ROOT/skills/flow-next-setup/templates/usage.md" .flow/usage.md -cat "$PLUGIN_ROOT/skills/flow-next-setup/templates/claude-md-snippet.md" > CLAUDE.md -echo -e "${GREEN}✓${NC} Setup mirrored (.flow/bin/, usage.md, CLAUDE.md)" - -scripts/ralph/flowctl epic create --title "Tiny lib" --json >/dev/null -scripts/ralph/flowctl epic create --title "Tiny follow-up" --json >/dev/null - -cat > "$TEST_DIR/epic.md" <<'EOF' -# fn-1 Tiny lib - -## Overview -Add a production-quality add() helper with proper validation and documentation. - -## Function Contract -- Signature: `add(a: number, b: number): number` -- Named export only from `src/index.ts` -- MUST validate inputs at runtime (throw TypeError if not numbers) -- Standard JS addition semantics for valid numbers (NaN/Infinity follow JS) - -## Current State -- `src/index.ts` does not yet export `add()` -- README is a placeholder - -## Scope -- `src/index.ts`: add `add()` with runtime validation and full JSDoc -- `README.md`: add usage snippet with error handling example - -## Approach -Edit src/index.ts and README.md only. Repo is source-only (no build step). - -## Quick commands -- `npm test` (smoke only) - -## Acceptance -- [ ] `add(a: number, b: number): number` exported as named export -- [ ] Runtime validation: throw `TypeError` if either argument is not a number -- [ ] JSDoc MUST include: - - @param tags for both parameters - - @returns tag - - @throws tag documenting TypeError - - @example tag with working code -- [ ] README includes: - - usage snippet showing successful call - - error handling example with try/catch - - note that this is TypeScript source and requires TS tooling to run -- [ ] `npm test` passes (smoke only) - -## Risks -- README import path is TypeScript source; call out runtime requirements -- Easy to forget @throws in JSDoc (common pitfall) - -## References -- None -EOF - -scripts/ralph/flowctl epic set-plan fn-1 --file "$TEST_DIR/epic.md" --json >/dev/null -scripts/ralph/flowctl epic set-plan fn-2 --file "$TEST_DIR/epic.md" --json >/dev/null -scripts/ralph/flowctl epic set-plan-review-status fn-2 --status ship --json >/dev/null - -cat > "$TEST_DIR/accept.md" <<'EOF' -- [ ] Export `add(a: number, b: number): number` from `src/index.ts` -- [ ] Add brief JSDoc for `add()` (params + return) -- [ ] README snippet uses `import { add } from "./src/index.ts"` and shows `console.log(add(1,2)) // 3` -- [ ] README notes TS tooling required to run snippet -- [ ] `npm test` passes (smoke only) -EOF - -scripts/ralph/flowctl task create --epic fn-1 --title "Add add() helper" --acceptance-file "$TEST_DIR/accept.md" --json >/dev/null -scripts/ralph/flowctl task create --epic fn-2 --title "Add tiny note" --acceptance-file "$TEST_DIR/accept.md" --json >/dev/null - -mkdir -p "$TEST_DIR/bin" -PLUGINS_DIR="$(dirname "$PLUGIN_ROOT")" -cat > "$TEST_DIR/bin/claude" </dev/null || true - cat > ".claude/settings.local.json" <<'HOOKSJSON' -{ - "hooks": { - "PreToolUse": [ - { - "matcher": "Bash", - "hooks": [{"type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/ralph-guard.py", "timeout": 5}] - } - ], - "PostToolUse": [ - { - "matcher": "Bash", - "hooks": [{"type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/ralph-guard.py", "timeout": 5}] - } - ], - "Stop": [ - {"hooks": [{"type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/ralph-guard.py", "timeout": 5}]} - ], - "SubagentStop": [ - {"hooks": [{"type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/ralph-guard.py", "timeout": 5}]} - ] - } -} -HOOKSJSON - echo -e "${GREEN}✓${NC} Hooks installed to .claude/hooks/ (workaround for #14410)" -fi - -# CREATE mode: set up repo and exit (user opens RP, then re-runs without CREATE) -if [[ "${CREATE:-0}" == "1" ]]; then - echo -e "${GREEN}✓${NC} Test repo created: $TEST_DIR/repo" - echo "" - echo "Next steps:" - echo " 1. Open RepoPrompt on: $TEST_DIR/repo" - echo " 2. Re-run without CREATE:" - echo " TEST_DIR=$TEST_DIR KEEP_TEST_DIR=1 $0" - exit 0 -fi - -echo -e "${YELLOW}--- running ralph (rp) ---${NC}" -REPO_ROOT="$(pwd)" - -# Optional preflight using atomic setup-review -if [[ "${RP_PREFLIGHT:-0}" == "1" ]]; then - preflight_msg="$TEST_DIR/preflight.md" - cat > "$preflight_msg" <<'EOF' -Smoke preflight: confirm chat pipeline. -EOF - eval "$(retry_cmd "rp setup-review" 180 2 "$FLOWCTL" rp setup-review --repo-root "$REPO_ROOT" --summary "Smoke preflight")" - [[ -n "$W" && -n "$T" ]] || fail "setup-review failed: W=$W T=$T" - retry_cmd "rp chat-send" 180 2 "$FLOWCTL" rp chat-send --window "$W" --tab "$T" --message-file "$preflight_msg" --new-chat --chat-name "Smoke Preflight" >/dev/null -fi -CLAUDE_BIN="$TEST_DIR/bin/claude" scripts/ralph/ralph.sh - -python3 - <<'PY' -import json -from pathlib import Path -for tid in ["fn-1.1", "fn-2.1"]: - data = json.loads(Path(f".flow/tasks/{tid}.json").read_text()) - assert data["status"] == "done" -runs = [p for p in Path("scripts/ralph/runs").iterdir() if p.is_dir() and p.name != ".gitkeep"] -runs.sort() -run_dir = runs[0].name -assert Path(f"scripts/ralph/runs/{run_dir}/progress.txt").exists() -data = json.loads(Path(f"scripts/ralph/runs/{run_dir}/branches.json").read_text()) -# Check run branch format (single branch for all epics) -assert "run_branch" in data, "branches.json should have run_branch" -assert data["run_branch"].startswith("ralph-"), f"run_branch should start with 'ralph-': {data['run_branch']}" -assert "base_branch" in data, "branches.json should have base_branch" -receipts = Path(f"scripts/ralph/runs/{run_dir}/receipts") -plan = json.loads(Path(receipts / "plan-fn-1.json").read_text()) -assert plan["type"] == "plan_review" -assert plan["id"] == "fn-1" -impl1 = json.loads(Path(receipts / "impl-fn-1.1.json").read_text()) -assert impl1["type"] == "impl_review" -assert impl1["id"] == "fn-1.1" -impl2 = json.loads(Path(receipts / "impl-fn-2.1.json").read_text()) -assert impl2["type"] == "impl_review" -assert impl2["id"] == "fn-2.1" -PY - -if [[ "${FLOW_RALPH_VERBOSE:-}" == "1" ]]; then - run_dir="$(ls -t scripts/ralph/runs | grep -v '^\\.gitkeep$' | head -n 1)" - log_file="scripts/ralph/runs/$run_dir/ralph.log" - [[ -f "$log_file" ]] || fail "missing verbose log $log_file" - if command -v rg >/dev/null 2>&1; then - rg -q "flowctl rp setup-review" "$log_file" || fail "missing setup-review in ralph.log" - rg -q "flowctl rp chat-send" "$log_file" || fail "missing chat-send in ralph.log" - rg -q "REVIEW_RECEIPT_WRITTEN" "$log_file" || fail "missing receipt marker in ralph.log" - else - grep -q "flowctl rp setup-review" "$log_file" || fail "missing setup-review in ralph.log" - grep -q "flowctl rp chat-send" "$log_file" || fail "missing chat-send in ralph.log" - grep -q "REVIEW_RECEIPT_WRITTEN" "$log_file" || fail "missing receipt marker in ralph.log" - fi -fi - -jsonl="$(find_jsonl)" -[[ -n "$jsonl" ]] || jsonl="$(latest_jsonl)" -[[ -n "$jsonl" ]] || fail "no claude jsonl logs found" -[[ -r "$jsonl" ]] || fail "jsonl file not readable: $jsonl" -if command -v rg >/dev/null 2>&1; then - rg -q --no-messages "REVIEW_RECEIPT_WRITTEN" "$jsonl" || fail "missing receipt marker in jsonl" - rg -q --no-messages "" "$jsonl" || fail "missing verdict tag in jsonl" -else - grep -q "REVIEW_RECEIPT_WRITTEN" "$jsonl" || fail "missing receipt marker in jsonl" - grep -q "" "$jsonl" || fail "missing verdict tag in jsonl" -fi - -echo -e "${GREEN}✓${NC} task done" -echo -e "${GREEN}✓${NC} ralph e2e rp complete" -echo "Claude logs: $HOME/.claude/projects" diff --git a/plugins/flow-next/scripts/ralph_e2e_short_rp_test.sh b/plugins/flow-next/scripts/ralph_e2e_short_rp_test.sh deleted file mode 100755 index f5ff93d..0000000 --- a/plugins/flow-next/scripts/ralph_e2e_short_rp_test.sh +++ /dev/null @@ -1,204 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# Short e2e test: fn-1 (1 task) → fn-2 (1 task) -# Minimal specs to avoid task expansion during planning - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -PLUGIN_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" - -if [[ -f "$PWD/.claude-plugin/marketplace.json" ]] || [[ -f "$PWD/plugins/flow-next/.claude-plugin/plugin.json" ]]; then - echo "ERROR: refusing to run from main plugin repo. Run from any other directory." >&2 - exit 1 -fi - -TEST_DIR="${TEST_DIR:-/tmp/flow-next-ralph-e2e-short-$$}" -CLAUDE_BIN="${CLAUDE_BIN:-claude}" -FLOWCTL="" - -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -NC='\033[0m' - -fail() { echo "ralph_e2e_short: $*" >&2; exit 1; } - -cleanup() { - if [[ "${KEEP_TEST_DIR:-0}" != "1" && "${CREATE:-0}" != "1" ]]; then - rm -rf "$TEST_DIR" - fi -} -trap cleanup EXIT - -command -v "$CLAUDE_BIN" >/dev/null 2>&1 || fail "claude not found" -command -v rp-cli >/dev/null 2>&1 || fail "rp-cli not found" - -echo -e "${YELLOW}=== ralph e2e SHORT (rp) ===${NC}" -echo "Test dir: $TEST_DIR" - -mkdir -p "$TEST_DIR/repo" -cd "$TEST_DIR/repo" -git init -q -git config user.email "ralph-e2e@example.com" -git config user.name "Ralph E2E" -git checkout -b main >/dev/null 2>&1 || true - -mkdir -p src -cat > src/index.ts <<'EOF' -export const placeholder = 0; -EOF - -cat > package.json <<'EOF' -{"name": "tmp-flow-next-ralph", "private": true, "version": "0.0.0"} -EOF - -cat > README.md <<'EOF' -# tmp-flow-next-ralph -EOF - -git add . -git commit -m "chore: init" >/dev/null - -mkdir -p scripts/ralph -cp -R "$PLUGIN_ROOT/skills/flow-next-ralph-init/templates/." scripts/ralph/ -cp "$PLUGIN_ROOT/scripts/flowctl.py" scripts/ralph/flowctl.py -cp "$PLUGIN_ROOT/scripts/flowctl" scripts/ralph/flowctl -chmod +x scripts/ralph/ralph.sh scripts/ralph/ralph_once.sh scripts/ralph/flowctl -FLOWCTL="scripts/ralph/flowctl" - -python3 - <<'PY' -from pathlib import Path -import re -cfg = Path("scripts/ralph/config.env") -text = cfg.read_text() -text = text.replace("{{PLAN_REVIEW}}", "rp").replace("{{WORK_REVIEW}}", "rp") -text = re.sub(r"^REQUIRE_PLAN_REVIEW=.*$", "REQUIRE_PLAN_REVIEW=1", text, flags=re.M) -text = re.sub(r"^BRANCH_MODE=.*$", "BRANCH_MODE=new", text, flags=re.M) -text = re.sub(r"^MAX_ITERATIONS=.*$", "MAX_ITERATIONS=4", text, flags=re.M) -text = re.sub(r"^MAX_ATTEMPTS_PER_TASK=.*$", "MAX_ATTEMPTS_PER_TASK=2", text, flags=re.M) -text = re.sub(r"^YOLO=.*$", "YOLO=1", text, flags=re.M) -text = re.sub(r"^EPICS=.*$", "EPICS=fn-1,fn-2", text, flags=re.M) -cfg.write_text(text) -PY - -scripts/ralph/flowctl init --json >/dev/null - -# Setup .flow/bin + docs (mirror /flow-next:setup) -mkdir -p .flow/bin -cp "$PLUGIN_ROOT/scripts/flowctl" .flow/bin/flowctl -cp "$PLUGIN_ROOT/scripts/flowctl.py" .flow/bin/flowctl.py -chmod +x .flow/bin/flowctl -cp "$PLUGIN_ROOT/skills/flow-next-setup/templates/usage.md" .flow/usage.md -cat "$PLUGIN_ROOT/skills/flow-next-setup/templates/claude-md-snippet.md" > CLAUDE.md -echo -e "${GREEN}✓${NC} Setup mirrored" - -scripts/ralph/flowctl epic create --title "Add function" --json >/dev/null -scripts/ralph/flowctl epic create --title "Add docs" --json >/dev/null - -# MINIMAL epic spec - one clear deliverable, no room for task expansion -cat > "$TEST_DIR/epic1.md" <<'EOF' -# fn-1 Add function - -Add `add(a, b)` to src/index.ts. Return a+b. Include JSDoc with @param and @returns. - -## Acceptance -- [ ] `add(a: number, b: number): number` exported from src/index.ts -- [ ] JSDoc present - -ONE task only. No README changes. -EOF - -cat > "$TEST_DIR/epic2.md" <<'EOF' -# fn-2 Add docs - -Add one-line note to README.md stating this is a tiny math library. - -## Acceptance -- [ ] README has "tiny math library" note - -ONE task only. -EOF - -scripts/ralph/flowctl epic set-plan fn-1 --file "$TEST_DIR/epic1.md" --json >/dev/null -scripts/ralph/flowctl epic set-plan fn-2 --file "$TEST_DIR/epic2.md" --json >/dev/null -scripts/ralph/flowctl epic set-plan-review-status fn-2 --status ship --json >/dev/null - -cat > "$TEST_DIR/accept1.md" <<'EOF' -- [ ] `add(a: number, b: number): number` exported -- [ ] JSDoc with @param and @returns -EOF - -cat > "$TEST_DIR/accept2.md" <<'EOF' -- [ ] README mentions "tiny math library" -EOF - -scripts/ralph/flowctl task create --epic fn-1 --title "Add add() function" --acceptance-file "$TEST_DIR/accept1.md" --json >/dev/null -scripts/ralph/flowctl task create --epic fn-2 --title "Add README note" --acceptance-file "$TEST_DIR/accept2.md" --json >/dev/null - -mkdir -p "$TEST_DIR/bin" -PLUGINS_DIR="$(dirname "$PLUGIN_ROOT")" -cat > "$TEST_DIR/bin/claude" </dev/null || true - cat > ".claude/settings.local.json" <<'HOOKSJSON' -{ - "hooks": { - "PreToolUse": [{"matcher": "Bash", "hooks": [{"type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/ralph-guard.py", "timeout": 5}]}], - "PostToolUse": [{"matcher": "Bash", "hooks": [{"type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/ralph-guard.py", "timeout": 5}]}], - "Stop": [{"hooks": [{"type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/ralph-guard.py", "timeout": 5}]}], - "SubagentStop": [{"hooks": [{"type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/ralph-guard.py", "timeout": 5}]}] - } -} -HOOKSJSON - echo -e "${GREEN}✓${NC} Hooks installed" -fi - -git add . -git commit -m "chore: add flow setup" >/dev/null - -if [[ "${CREATE:-0}" == "1" ]]; then - echo -e "${GREEN}✓${NC} Test repo created: $TEST_DIR/repo" - echo "" - echo "Next steps:" - echo " 1. Open RepoPrompt on: $TEST_DIR/repo" - echo " 2. Re-run without CREATE:" - echo " TEST_DIR=$TEST_DIR KEEP_TEST_DIR=1 $0" - exit 0 -fi - -echo -e "${YELLOW}--- running ralph (short) ---${NC}" -CLAUDE_BIN="$TEST_DIR/bin/claude" scripts/ralph/ralph.sh - -# Assertions -python3 - <<'PY' -import json -from pathlib import Path -for tid in ["fn-1.1", "fn-2.1"]: - data = json.loads(Path(f".flow/tasks/{tid}.json").read_text()) - assert data["status"] == "done", f"{tid} not done" -runs = [p for p in Path("scripts/ralph/runs").iterdir() if p.is_dir() and p.name != ".gitkeep"] -runs.sort() -run_dir = runs[0].name -assert Path(f"scripts/ralph/runs/{run_dir}/progress.txt").exists() -data = json.loads(Path(f"scripts/ralph/runs/{run_dir}/branches.json").read_text()) -assert "run_branch" in data and data["run_branch"].startswith("ralph-") -assert "base_branch" in data -receipts = Path(f"scripts/ralph/runs/{run_dir}/receipts") -plan = json.loads(Path(receipts / "plan-fn-1.json").read_text()) -assert plan["type"] == "plan_review" -impl1 = json.loads(Path(receipts / "impl-fn-1.1.json").read_text()) -assert impl1["type"] == "impl_review" -impl2 = json.loads(Path(receipts / "impl-fn-2.1.json").read_text()) -assert impl2["type"] == "impl_review" -PY - -echo -e "${GREEN}✓${NC} ralph e2e short complete" -echo "Run logs: $TEST_DIR/repo/scripts/ralph/runs" diff --git a/plugins/flow-next/scripts/ralph_e2e_test.sh b/plugins/flow-next/scripts/ralph_e2e_test.sh deleted file mode 100755 index 876e07a..0000000 --- a/plugins/flow-next/scripts/ralph_e2e_test.sh +++ /dev/null @@ -1,169 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -PLUGIN_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -REPO_ROOT="$(cd "$PLUGIN_ROOT/.." && pwd)" - -# Safety: never run tests from the main plugin repo -if [[ -f "$PWD/.claude-plugin/marketplace.json" ]] || [[ -f "$PWD/plugins/flow-next/.claude-plugin/plugin.json" ]]; then - echo "ERROR: refusing to run from main plugin repo. Run from any other directory." >&2 - exit 1 -fi - -TEST_DIR="${TEST_DIR:-/tmp/flow-next-ralph-e2e-$$}" -CLAUDE_BIN="${CLAUDE_BIN:-claude}" - -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -RED='\033[0;31m' -NC='\033[0m' - -fail() { echo "ralph_e2e: $*" >&2; exit 1; } - -cleanup() { - if [[ "${KEEP_TEST_DIR:-0}" != "1" ]]; then - rm -rf "$TEST_DIR" - fi -} -trap cleanup EXIT - -if [[ ! -x "$(command -v "$CLAUDE_BIN" || true)" ]]; then - fail "claude not found (set CLAUDE_BIN if needed)" -fi - -echo -e "${YELLOW}=== ralph e2e (real claude) ===${NC}" -echo "Test dir: $TEST_DIR" - -mkdir -p "$TEST_DIR/repo" -cd "$TEST_DIR/repo" -git init -q -git config user.email "ralph-e2e@example.com" -git config user.name "Ralph E2E" -git checkout -b main >/dev/null 2>&1 || true - -mkdir -p src -cat > src/index.ts <<'EOF' -export function add(a: number, b: number): number { - return a + b; -} -EOF - -cat > package.json <<'EOF' -{ - "name": "tmp-flow-next-ralph", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "test": "node -e \"console.log('ok')\"" - } -} -EOF - -cat > README.md <<'EOF' -# tmp-flow-next-ralph - -TBD -EOF - -git add . -git commit -m "chore: init" >/dev/null - -mkdir -p scripts/ralph -cp -R "$PLUGIN_ROOT/skills/flow-next-ralph-init/templates/." scripts/ralph/ -cp "$PLUGIN_ROOT/scripts/flowctl.py" scripts/ralph/flowctl.py -cp "$PLUGIN_ROOT/scripts/flowctl" scripts/ralph/flowctl -chmod +x scripts/ralph/ralph.sh scripts/ralph/ralph_once.sh scripts/ralph/flowctl - -python3 - <<'PY' -from pathlib import Path -import re -cfg = Path("scripts/ralph/config.env") -text = cfg.read_text() -text = text.replace("{{PLAN_REVIEW}}", "none").replace("{{WORK_REVIEW}}", "none") -text = re.sub(r"^REQUIRE_PLAN_REVIEW=.*$", "REQUIRE_PLAN_REVIEW=0", text, flags=re.M) -text = re.sub(r"^BRANCH_MODE=.*$", "BRANCH_MODE=new", text, flags=re.M) -text = re.sub(r"^MAX_ITERATIONS=.*$", "MAX_ITERATIONS=10", text, flags=re.M) -text = re.sub(r"^MAX_TURNS=.*$", "MAX_TURNS=60", text, flags=re.M) -text = re.sub(r"^MAX_ATTEMPTS_PER_TASK=.*$", "MAX_ATTEMPTS_PER_TASK=2", text, flags=re.M) -text = re.sub(r"^YOLO=.*$", "YOLO=1", text, flags=re.M) -text = re.sub(r"^EPICS=.*$", "EPICS=", text, flags=re.M) -cfg.write_text(text) -PY - -scripts/ralph/flowctl init --json >/dev/null -scripts/ralph/flowctl epic create --title "Tiny lib" --json >/dev/null -scripts/ralph/flowctl epic create --title "Tiny follow-up" --json >/dev/null - -cat > "$TEST_DIR/epic.md" <<'EOF' -# fn-1 Tiny lib - -## Overview -Add a tiny add() helper and document it. - -## Scope -- Small source change -- README update - -## Approach -Edit src/index.ts and README.md only. - -## Quick commands -- `npm test` - -## Acceptance -- [ ] add() exported -- [ ] README updated with usage - -## References -- None -EOF - -scripts/ralph/flowctl epic set-plan fn-1 --file "$TEST_DIR/epic.md" --json >/dev/null -scripts/ralph/flowctl epic set-plan fn-2 --file "$TEST_DIR/epic.md" --json >/dev/null - -cat > "$TEST_DIR/accept.md" <<'EOF' -- [ ] Export add(a,b) from src/index.ts -- [ ] Add README usage snippet -EOF - -scripts/ralph/flowctl task create --epic fn-1 --title "Add add() helper" --acceptance-file "$TEST_DIR/accept.md" --json >/dev/null -scripts/ralph/flowctl task create --epic fn-2 --title "Add tiny note" --acceptance-file "$TEST_DIR/accept.md" --json >/dev/null - -mkdir -p "$TEST_DIR/bin" -PLUGINS_DIR="$(dirname "$PLUGIN_ROOT")" -cat > "$TEST_DIR/bin/claude" <&2 - exit 1 -fi - -TEST_DIR="${TEST_DIR:-/tmp/flow-next-ralph-smoke-rp-$$}" -CLAUDE_BIN="${CLAUDE_BIN:-claude}" -FLOWCTL="" - -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -RED='\033[0;31m' -NC='\033[0m' - -fail() { echo "ralph_smoke_rp: $*" >&2; exit 1; } - -run_with_timeout() { - local timeout_s="$1" - shift - python3 - "$timeout_s" "$@" <<'PY' -import subprocess, sys -try: - timeout = float(sys.argv[1]) -except Exception: - timeout = 0 -cmd = sys.argv[2:] -try: - proc = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout if timeout > 0 else None) -except subprocess.TimeoutExpired: - print(f"timeout after {timeout}s: {' '.join(cmd)}", file=sys.stderr) - sys.exit(124) -if proc.stdout: - sys.stdout.write(proc.stdout) -if proc.stderr: - sys.stderr.write(proc.stderr) -sys.exit(proc.returncode) -PY -} - -retry_cmd() { - local label="$1" - local timeout_s="$2" - local retries="$3" - shift 3 - local attempt=1 - while true; do - if out="$(run_with_timeout "$timeout_s" "$@")"; then - echo "$out" - return 0 - fi - local rc="$?" - if [[ "$attempt" -ge "$retries" ]]; then - echo "ralph_smoke_rp: $label failed after $attempt attempts" >&2 - return "$rc" - fi - attempt="$((attempt + 1))" - sleep 2 - done -} - -swap_tmp_root() { - python3 - "$1" <<'PY' -import sys -path = sys.argv[1] -if path.startswith("/private/tmp/"): - print("/tmp/" + path[len("/private/tmp/"):]) -elif path.startswith("/tmp/"): - print("/private/tmp/" + path[len("/tmp/"):]) -else: - print(path) -PY -} - -latest_jsonl() { - # Search in project subdirectories, exclude agent logs - find "$HOME/.claude/projects" -maxdepth 2 -name "*.jsonl" ! -name "agent-*.jsonl" -type f -print 2>/dev/null | \ - xargs ls -t 2>/dev/null | head -n 1 || true -} - -new_session_id() { - python3 - <<'PY' -import uuid -print(uuid.uuid4()) -PY -} - - -find_jsonl() { - if [[ -n "${FLOW_RALPH_CLAUDE_SESSION_ID:-}" ]]; then - if command -v fd >/dev/null 2>&1; then - fd -a "${FLOW_RALPH_CLAUDE_SESSION_ID}.jsonl" "$HOME/.claude/projects" | head -n 1 || true - else - find "$HOME/.claude/projects" -name "${FLOW_RALPH_CLAUDE_SESSION_ID}.jsonl" -print 2>/dev/null | head -n 1 || true - fi - fi -} - -cleanup() { - if [[ "${KEEP_TEST_DIR:-0}" != "1" && "${CREATE:-0}" != "1" ]]; then - rm -rf "$TEST_DIR" - fi -} -trap cleanup EXIT - -[[ "${RP_SMOKE:-0}" == "1" ]] || fail "set RP_SMOKE=1 to run" -command -v "$CLAUDE_BIN" >/dev/null 2>&1 || fail "claude not found (set CLAUDE_BIN if needed)" -command -v rp-cli >/dev/null 2>&1 || fail "rp-cli not found (required for rp review)" - -echo -e "${YELLOW}=== ralph smoke (rp) ===${NC}" -echo "Test dir: $TEST_DIR" - -mkdir -p "$TEST_DIR/repo" -cd "$TEST_DIR/repo" -git init -q -git config user.email "ralph-smoke-rp@example.com" -git config user.name "Ralph Smoke RP" -git checkout -b main >/dev/null 2>&1 || true - -mkdir -p src -cat > src/index.ts <<'EOF' -export function add(a: number, b: number): number { - return a + b; -} -EOF - -cat > package.json <<'EOF' -{ - "name": "tmp-flow-next-ralph-smoke", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "test": "node -e \"console.log('ok')\"" - } -} -EOF - -cat > README.md <<'EOF' -# tmp-flow-next-ralph-smoke - -TBD -EOF - -git add . -git commit -m "chore: init" >/dev/null - -mkdir -p scripts/ralph -cp -R "$PLUGIN_ROOT/skills/flow-next-ralph-init/templates/." scripts/ralph/ -cp "$PLUGIN_ROOT/scripts/flowctl.py" scripts/ralph/flowctl.py -cp "$PLUGIN_ROOT/scripts/flowctl" scripts/ralph/flowctl -chmod +x scripts/ralph/ralph.sh scripts/ralph/ralph_once.sh scripts/ralph/flowctl -FLOWCTL="scripts/ralph/flowctl" - -python3 - <<'PY' -from pathlib import Path -import re -cfg = Path("scripts/ralph/config.env") -text = cfg.read_text() -text = text.replace("{{PLAN_REVIEW}}", "rp").replace("{{WORK_REVIEW}}", "rp") -text = re.sub(r"^REQUIRE_PLAN_REVIEW=.*$", "REQUIRE_PLAN_REVIEW=1", text, flags=re.M) -text = re.sub(r"^BRANCH_MODE=.*$", "BRANCH_MODE=new", text, flags=re.M) -text = re.sub(r"^MAX_ITERATIONS=.*$", "MAX_ITERATIONS=4", text, flags=re.M) -# MAX_TURNS not limited - let Claude finish naturally via promise tags -text = re.sub(r"^MAX_ATTEMPTS_PER_TASK=.*$", "MAX_ATTEMPTS_PER_TASK=1", text, flags=re.M) -text = re.sub(r"^YOLO=.*$", "YOLO=1", text, flags=re.M) -text = re.sub(r"^EPICS=.*$", "EPICS=fn-1", text, flags=re.M) -cfg.write_text(text) -PY - -scripts/ralph/flowctl init --json >/dev/null -scripts/ralph/flowctl epic create --title "Tiny lib" --json >/dev/null - -cat > "$TEST_DIR/epic.md" <<'EOF' -# fn-1 Tiny lib - -## Overview -Add a tiny add() helper doc update and verify README. - -## Current State -- `add()` exists in `src/index.ts` -- README is a placeholder - -## Scope -- `src/index.ts`: add brief JSDoc (params + return) -- `README.md`: add TS usage snippet and note TS tooling required - -## Approach -Edit src/index.ts and README.md only. Repo is source-only (no build step). - -## Quick commands -- `npm test` (smoke only) - -## Acceptance -- [ ] `add(a: number, b: number): number` exported as named export -- [ ] `add()` has brief JSDoc (params + return) -- [ ] README includes: - - snippet: - ```ts - import { add } from "./src/index.ts"; - console.log(add(1, 2)); // 3 - ``` - - note that TS tooling is required to run -- [ ] `npm test` passes (smoke only) - -## Risks -- README import path is TypeScript source; call out runtime requirements - -## References -- None -EOF - -scripts/ralph/flowctl epic set-plan fn-1 --file "$TEST_DIR/epic.md" --json >/dev/null - -cat > "$TEST_DIR/accept.md" <<'EOF' -- [ ] Add JSDoc for add() (params + return) -- [ ] README snippet uses `import { add } from "./src/index.ts"` and shows `console.log(add(1,2)) // 3` -- [ ] README notes TS tooling required -EOF - -scripts/ralph/flowctl task create --epic fn-1 --title "Add docs" --acceptance-file "$TEST_DIR/accept.md" --json >/dev/null - -mkdir -p "$TEST_DIR/bin" -PLUGINS_DIR="$(dirname "$PLUGIN_ROOT")" -cat > "$TEST_DIR/bin/claude" </dev/null || true - cat > ".claude/settings.local.json" <<'HOOKSJSON' -{ - "hooks": { - "PreToolUse": [ - { - "matcher": "Bash", - "hooks": [{"type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/ralph-guard.py", "timeout": 5}] - } - ], - "PostToolUse": [ - { - "matcher": "Bash", - "hooks": [{"type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/ralph-guard.py", "timeout": 5}] - } - ], - "Stop": [ - {"hooks": [{"type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/ralph-guard.py", "timeout": 5}]} - ], - "SubagentStop": [ - {"hooks": [{"type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/ralph-guard.py", "timeout": 5}]} - ] - } -} -HOOKSJSON - echo -e "${GREEN}✓${NC} Hooks installed to .claude/hooks/ (workaround for #14410)" -fi - -# CREATE mode: set up repo and exit (user opens RP, then re-runs without CREATE) -if [[ "${CREATE:-0}" == "1" ]]; then - echo -e "${GREEN}✓${NC} Test repo created: $TEST_DIR/repo" - echo "" - echo "Next steps:" - echo " 1. Open RepoPrompt on: $TEST_DIR/repo" - echo " 2. Re-run without CREATE:" - echo " RP_SMOKE=1 TEST_DIR=$TEST_DIR KEEP_TEST_DIR=1 $0" - exit 0 -fi - -REPO_ROOT="$(pwd)" - -# Use atomic setup-review (picks window + runs builder) -preflight_msg="$TEST_DIR/preflight.md" -cat > "$preflight_msg" <<'EOF' -Smoke preflight: confirm chat pipeline. -EOF -eval "$(retry_cmd "rp setup-review" 180 2 "$FLOWCTL" rp setup-review --repo-root "$REPO_ROOT" --summary "Smoke preflight")" -[[ -n "$W" && -n "$T" ]] || fail "setup-review failed: W=$W T=$T" -retry_cmd "rp chat-send" 180 2 "$FLOWCTL" rp chat-send --window "$W" --tab "$T" --message-file "$preflight_msg" --new-chat --chat-name "Smoke Preflight" >/dev/null - -echo -e "${YELLOW}--- running ralph (rp) ---${NC}" -CLAUDE_BIN="$TEST_DIR/bin/claude" scripts/ralph/ralph.sh - -python3 - <<'PY' -import json -from pathlib import Path -for tid in ["fn-1.1"]: - data = json.loads(Path(f".flow/tasks/{tid}.json").read_text()) - assert data["status"] == "done" -PY - -run_dir="$(ls -t scripts/ralph/runs | grep -v '^\\.gitkeep$' | head -n 1)" -receipts="scripts/ralph/runs/$run_dir/receipts" -if [[ ! -f "scripts/ralph/runs/$run_dir/progress.txt" ]]; then - fail "missing progress.txt" -fi -python3 - <<'PY' "$receipts" -import json, sys -from pathlib import Path -receipts = Path(sys.argv[1]) -plan = json.loads((receipts / "plan-fn-1.json").read_text()) -impl = json.loads((receipts / "impl-fn-1.1.json").read_text()) -assert plan["type"] == "plan_review" -assert plan["id"] == "fn-1" -assert impl["type"] == "impl_review" -assert impl["id"] == "fn-1.1" -PY - -if [[ "${FLOW_RALPH_VERBOSE:-}" == "1" ]]; then - log_file="scripts/ralph/runs/$run_dir/ralph.log" - [[ -f "$log_file" ]] || fail "missing verbose log $log_file" - if command -v rg >/dev/null 2>&1; then - rg -q "flowctl rp setup-review" "$log_file" || fail "missing setup-review in ralph.log" - rg -q "flowctl rp chat-send" "$log_file" || fail "missing chat-send in ralph.log" - rg -q "REVIEW_RECEIPT_WRITTEN" "$log_file" || fail "missing receipt marker in ralph.log" - else - grep -q "flowctl rp setup-review" "$log_file" || fail "missing setup-review in ralph.log" - grep -q "flowctl rp chat-send" "$log_file" || fail "missing chat-send in ralph.log" - grep -q "REVIEW_RECEIPT_WRITTEN" "$log_file" || fail "missing receipt marker in ralph.log" - fi -fi - -jsonl="$(find_jsonl)" -[[ -n "$jsonl" ]] || jsonl="$(latest_jsonl)" -[[ -n "$jsonl" ]] || fail "no claude jsonl logs found" -if command -v rg >/dev/null 2>&1; then - rg -q "REVIEW_RECEIPT_WRITTEN" "$jsonl" || fail "missing receipt marker in jsonl" - rg -q "" "$jsonl" || fail "missing verdict tag in jsonl" -else - grep -q "REVIEW_RECEIPT_WRITTEN" "$jsonl" || fail "missing receipt marker in jsonl" - grep -q "" "$jsonl" || fail "missing verdict tag in jsonl" -fi - -echo -e "${GREEN}✓${NC} task done" -echo -e "${GREEN}✓${NC} ralph smoke rp complete" -echo "Run logs: $TEST_DIR/repo/scripts/ralph/runs" -echo "Claude logs: $HOME/.claude/projects" diff --git a/plugins/flow-next/scripts/ralph_smoke_test.sh b/plugins/flow-next/scripts/ralph_smoke_test.sh deleted file mode 100755 index 38065ee..0000000 --- a/plugins/flow-next/scripts/ralph_smoke_test.sh +++ /dev/null @@ -1,418 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -PLUGIN_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -TEST_DIR="/tmp/ralph-smoke-$$" - -# Python detection: prefer python3, fallback to python (Windows support, GH-35) -pick_python() { - if [[ -n "${PYTHON_BIN:-}" ]]; then - command -v "$PYTHON_BIN" >/dev/null 2>&1 && { echo "$PYTHON_BIN"; return; } - fi - if command -v python3 >/dev/null 2>&1; then echo "python3"; return; fi - if command -v python >/dev/null 2>&1; then echo "python"; return; fi - echo "" -} - -PYTHON_BIN="$(pick_python)" -[[ -n "$PYTHON_BIN" ]] || { echo "ERROR: python not found (need python3 or python in PATH)" >&2; exit 1; } - -# Safety: never run tests from the main plugin repo -if [[ -f "$PWD/.claude-plugin/marketplace.json" ]] || [[ -f "$PWD/plugins/flow-next/.claude-plugin/plugin.json" ]]; then - echo "ERROR: refusing to run from main plugin repo. Run from any other directory." >&2 - exit 1 -fi -PASS=0 -FAIL=0 - -GREEN='\033[0;32m' -RED='\033[0;31m' -YELLOW='\033[1;33m' -NC='\033[0m' - -cleanup() { - rm -rf "$TEST_DIR" -} -trap cleanup EXIT - -echo -e "${YELLOW}=== ralph smoke tests ===${NC}" - -mkdir -p "$TEST_DIR/repo" -cd "$TEST_DIR/repo" -git init -q -git config user.email "ralph-smoke@example.com" -git config user.name "Ralph Smoke" -git checkout -b main >/dev/null 2>&1 || true - -cat > README.md <<'EOF' -# ralph-smoke -EOF -git add README.md -git commit -m "chore: init" >/dev/null - -scaffold() { - mkdir -p scripts/ralph - cp -R "$PLUGIN_ROOT/skills/flow-next-ralph-init/templates/." scripts/ralph/ - cp "$PLUGIN_ROOT/scripts/flowctl.py" scripts/ralph/flowctl.py - cp "$PLUGIN_ROOT/scripts/flowctl" scripts/ralph/flowctl - chmod +x scripts/ralph/ralph.sh scripts/ralph/ralph_once.sh scripts/ralph/flowctl -} - -write_config() { - local plan="$1" - local work="$2" - local require="$3" - local branch="$4" - local max_iter="$5" - local max_turns="$6" - local max_attempts="$7" - "$PYTHON_BIN" - <<'PY' "$plan" "$work" "$require" "$branch" "$max_iter" "$max_turns" "$max_attempts" -from pathlib import Path -import re, sys -plan, work, require, branch, max_iter, max_turns, max_attempts = sys.argv[1:8] -cfg = Path("scripts/ralph/config.env") -text = cfg.read_text() -# Replace template placeholders first (for initial setup) -text = text.replace("{{PLAN_REVIEW}}", plan).replace("{{WORK_REVIEW}}", work) -# Then use re.sub for subsequent calls (when values are already set) -text = re.sub(r"^PLAN_REVIEW=.*$", f"PLAN_REVIEW={plan}", text, flags=re.M) -text = re.sub(r"^WORK_REVIEW=.*$", f"WORK_REVIEW={work}", text, flags=re.M) -text = re.sub(r"^REQUIRE_PLAN_REVIEW=.*$", f"REQUIRE_PLAN_REVIEW={require}", text, flags=re.M) -text = re.sub(r"^BRANCH_MODE=.*$", f"BRANCH_MODE={branch}", text, flags=re.M) -text = re.sub(r"^MAX_ITERATIONS=.*$", f"MAX_ITERATIONS={max_iter}", text, flags=re.M) -text = re.sub(r"^MAX_TURNS=.*$", f"MAX_TURNS={max_turns}", text, flags=re.M) -text = re.sub(r"^MAX_ATTEMPTS_PER_TASK=.*$", f"MAX_ATTEMPTS_PER_TASK={max_attempts}", text, flags=re.M) -cfg.write_text(text) -PY -} - -# Extract epic/task ID from JSON output -extract_id() { - "$PYTHON_BIN" -c "import json,sys; print(json.load(sys.stdin)['id'])" -} - -scaffold - -echo -e "${YELLOW}--- ralph-init scaffold ---${NC}" -missing=0 -for f in ralph.sh ralph_once.sh prompt_plan.md prompt_work.md config.env runs/.gitkeep flowctl flowctl.py .gitignore; do - if [[ ! -f "scripts/ralph/$f" ]]; then - echo -e "${RED}✗${NC} missing scripts/ralph/$f" - missing=1 - fi -done -if [[ "$missing" -eq 0 ]]; then - echo -e "${GREEN}✓${NC} scaffold files present" - PASS=$((PASS + 1)) -else - FAIL=$((FAIL + 1)) -fi - -mkdir -p "$TEST_DIR/bin" -# Dynamic claude stub that extracts epic IDs from prompts using the new fn-N-xxx format -cat > "$TEST_DIR/bin/claude" <<'EOF' -#!/usr/bin/env bash -set -euo pipefail - mode="${STUB_MODE:-success}" - write_receipt="${STUB_WRITE_RECEIPT:-1}" - write_plan="${STUB_WRITE_PLAN_RECEIPT:-$write_receipt}" - write_impl="${STUB_WRITE_IMPL_RECEIPT:-$write_receipt}" - exit_code="${STUB_EXIT_CODE:-0}" - skip_done="${STUB_SKIP_DONE:-0}" -has_p=0 -for arg in "$@"; do - if [[ "$arg" == "-p" ]]; then has_p=1; break; fi -done -if [[ "$has_p" -eq 0 ]]; then - exit 0 -fi - -prompt="${@: -1}" -if [[ "$mode" == "retry" ]]; then - echo "RETRY" - exit 0 -fi - -if [[ "$prompt" == *"Ralph plan gate iteration"* ]]; then - # Extract epic ID with optional suffix (fn-N or fn-N-xxx) - epic_id="$(printf '%s\n' "$prompt" | sed -n 's/.*EPIC_ID=\(fn-[0-9][0-9]*\(-[a-z0-9][a-z0-9][a-z0-9]\)\{0,1\}\).*/\1/p' | head -n1)" - if [[ -n "$epic_id" ]]; then - scripts/ralph/flowctl epic set-plan-review-status "$epic_id" --status ship --json >/dev/null - fi - if [[ "$write_plan" == "1" && -n "${REVIEW_RECEIPT_PATH:-}" ]]; then - ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)" - mkdir -p "$(dirname "$REVIEW_RECEIPT_PATH")" - cat > "$REVIEW_RECEIPT_PATH" <SHIP" - exit "$exit_code" -fi - -if [[ "$prompt" == *"Ralph work iteration"* ]]; then - # Extract task ID with optional suffix (fn-N.M or fn-N-xxx.M) - task_id="$(printf '%s\n' "$prompt" | sed -n 's/.*TASK_ID=\(fn-[0-9][0-9]*\(-[a-z0-9][a-z0-9][a-z0-9]\)\{0,1\}\.[0-9][0-9]*\).*/\1/p' | head -n1)" - if [[ "$skip_done" != "1" ]]; then - summary="$(mktemp)" - evidence="$(mktemp)" - printf "ok\n" > "$summary" - printf '{"commits":[],"tests":[],"prs":[]}' > "$evidence" - scripts/ralph/flowctl start "$task_id" --json >/dev/null - scripts/ralph/flowctl done "$task_id" --summary-file "$summary" --evidence-json "$evidence" --json >/dev/null - rm -f "$summary" "$evidence" - fi - if [[ "$write_impl" == "1" && -n "${REVIEW_RECEIPT_PATH:-}" ]]; then - ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)" - mkdir -p "$(dirname "$REVIEW_RECEIPT_PATH")" - cat > "$REVIEW_RECEIPT_PATH" <FAIL" -exit 0 -EOF -chmod +x "$TEST_DIR/bin/claude" - -scripts/ralph/flowctl init --json >/dev/null - -latest_run_dir() { - ls -t scripts/ralph/runs | grep -v '^\\.gitkeep$' | head -n 1 -} - -echo -e "${YELLOW}--- ralph_once ---${NC}" -EPIC1_JSON="$(scripts/ralph/flowctl epic create --title "Ralph Epic" --json)" -EPIC1="$(echo "$EPIC1_JSON" | extract_id)" -scripts/ralph/flowctl task create --epic "$EPIC1" --title "Ralph Task" --json >/dev/null -write_config "none" "none" "0" "new" "3" "5" "2" -CLAUDE_BIN="$TEST_DIR/bin/claude" scripts/ralph/ralph_once.sh >/dev/null -# Mark plan review done so it doesn't block later tests when REQUIRE_PLAN_REVIEW=1 -scripts/ralph/flowctl epic set-plan-review-status "$EPIC1" --status ship --json >/dev/null -echo -e "${GREEN}✓${NC} ralph_once runs" -PASS=$((PASS + 1)) - -echo -e "${YELLOW}--- ralph.sh completes epic ---${NC}" -EPIC2_JSON="$(scripts/ralph/flowctl epic create --title "Ralph Epic 2" --json)" -EPIC2="$(echo "$EPIC2_JSON" | extract_id)" -TASK2_1_JSON="$(scripts/ralph/flowctl task create --epic "$EPIC2" --title "Task 1" --json)" -TASK2_1="$(echo "$TASK2_1_JSON" | extract_id)" -TASK2_2_JSON="$(scripts/ralph/flowctl task create --epic "$EPIC2" --title "Task 2" --json)" -TASK2_2="$(echo "$TASK2_2_JSON" | extract_id)" -# Use rp for both to test receipt generation (none skips receipts correctly via fix for #8) -write_config "rp" "rp" "1" "new" "6" "5" "2" -STUB_MODE=success STUB_WRITE_RECEIPT=1 CLAUDE_BIN="$TEST_DIR/bin/claude" scripts/ralph/ralph.sh >/dev/null -"$PYTHON_BIN" - </dev/null 2>&1; then - rg -q "" "$iter_log" - else - grep -q "" "$iter_log" - fi -fi -echo -e "${GREEN}✓${NC} ralph completes tasks" -PASS=$((PASS + 1)) - -run_dir="$(ls -1 scripts/ralph/runs | grep -v '^\\.gitkeep$' | head -n 1)" -if [[ -f "scripts/ralph/runs/$run_dir/branches.json" ]]; then - echo -e "${GREEN}✓${NC} branches.json created" - PASS=$((PASS + 1)) -else - echo -e "${RED}✗${NC} branches.json created" - FAIL=$((FAIL + 1)) -fi -if [[ -f "scripts/ralph/runs/$run_dir/progress.txt" ]]; then - echo -e "${GREEN}✓${NC} progress.txt created" - PASS=$((PASS + 1)) -else - echo -e "${RED}✗${NC} progress.txt created" - FAIL=$((FAIL + 1)) -fi - -echo -e "${YELLOW}--- UI output verification ---${NC}" -EPIC3_JSON="$(scripts/ralph/flowctl epic create --title "UI Test Epic" --json)" -EPIC3="$(echo "$EPIC3_JSON" | extract_id)" -scripts/ralph/flowctl task create --epic "$EPIC3" --title "UI Test Task" --json >/dev/null -write_config "none" "none" "0" "new" "3" "5" "2" -ui_output="$(STUB_MODE=success CLAUDE_BIN="$TEST_DIR/bin/claude" scripts/ralph/ralph.sh 2>&1)" - -# Check elapsed time format [X:XX] -if echo "$ui_output" | grep -qE '\[[0-9]+:[0-9]{2}\]'; then - echo -e "${GREEN}✓${NC} elapsed time shown" - PASS=$((PASS + 1)) -else - echo -e "${RED}✗${NC} elapsed time shown" - FAIL=$((FAIL + 1)) -fi - -# Check progress counter (Epic X/Y * Task X/Y) -if echo "$ui_output" | grep -qE 'Epic [0-9]+/[0-9]+.*Task [0-9]+/[0-9]+'; then - echo -e "${GREEN}✓${NC} progress counter shown" - PASS=$((PASS + 1)) -else - echo -e "${RED}✗${NC} progress counter shown" - FAIL=$((FAIL + 1)) -fi - -# Check task title is shown (quoted) -if echo "$ui_output" | grep -q '"UI Test Task"'; then - echo -e "${GREEN}✓${NC} task title shown" - PASS=$((PASS + 1)) -else - echo -e "${RED}✗${NC} task title shown" - FAIL=$((FAIL + 1)) -fi - -# Check completion summary shows Tasks: X/Y -if echo "$ui_output" | grep -qE 'Tasks:.*[0-9]+/[0-9]+'; then - echo -e "${GREEN}✓${NC} completion summary shown" - PASS=$((PASS + 1)) -else - echo -e "${RED}✗${NC} completion summary shown" - FAIL=$((FAIL + 1)) -fi - -# Check branch is shown -if echo "$ui_output" | grep -q 'Branch:'; then - echo -e "${GREEN}✓${NC} branch shown" - PASS=$((PASS + 1)) -else - echo -e "${RED}✗${NC} branch shown" - FAIL=$((FAIL + 1)) -fi - -echo -e "${YELLOW}--- ralph.sh backstop ---${NC}" -EPIC4_JSON="$(scripts/ralph/flowctl epic create --title "Ralph Epic 4" --json)" -EPIC4="$(echo "$EPIC4_JSON" | extract_id)" -TASK4_1_JSON="$(scripts/ralph/flowctl task create --epic "$EPIC4" --title "Stuck Task" --json)" -TASK4_1="$(echo "$TASK4_1_JSON" | extract_id)" -write_config "none" "none" "0" "new" "3" "5" "2" -STUB_MODE=retry CLAUDE_BIN="$TEST_DIR/bin/claude" scripts/ralph/ralph.sh >/dev/null -"$PYTHON_BIN" - </dev/null -rc=$? -set -e -run_dir="$(latest_run_dir)" -receipts_dir="scripts/ralph/runs/$run_dir/receipts" -if [[ -f "$receipts_dir/impl-$TASK5_1.json" ]]; then - echo -e "${RED}✗${NC} impl receipt unexpectedly exists" - FAIL=$((FAIL + 1)) -else - echo -e "${GREEN}✓${NC} missing impl receipt forces retry (rc=$rc)" - PASS=$((PASS + 1)) -fi - -run_count="$(ls -1 scripts/ralph/runs | wc -l | tr -d ' ')" -STUB_MODE=retry CLAUDE_BIN="$TEST_DIR/bin/claude" scripts/ralph/ralph.sh >/dev/null -run_count2="$(ls -1 scripts/ralph/runs | wc -l | tr -d ' ')" -if [[ "$run_count2" -gt "$run_count" ]]; then - echo -e "${GREEN}✓${NC} multi-run uniqueness" - PASS=$((PASS + 1)) -else - echo -e "${RED}✗${NC} multi-run uniqueness" - FAIL=$((FAIL + 1)) -fi - -echo -e "${YELLOW}--- non-zero exit code handling (#11) ---${NC}" -# Test 1: task done + non-zero exit -> should NOT fail -# This validates fix for issue #11 where transient errors caused false failures -EPIC6_JSON="$(scripts/ralph/flowctl epic create --title "Exit Code Epic 1" --json)" -EPIC6="$(echo "$EPIC6_JSON" | extract_id)" -TASK6_1_JSON="$(scripts/ralph/flowctl task create --epic "$EPIC6" --title "Done but exit 1" --json)" -TASK6_1="$(echo "$TASK6_1_JSON" | extract_id)" -write_config "none" "none" "0" "new" "3" "5" "2" -set +e -STUB_MODE=success STUB_EXIT_CODE=1 CLAUDE_BIN="$TEST_DIR/bin/claude" scripts/ralph/ralph.sh >/dev/null 2>&1 -rc=$? -set -e -"$PYTHON_BIN" - < task completed (rc=$rc)" - PASS=$((PASS + 1)) -else - echo -e "${RED}✗${NC} task done + exit 1 -> task completed" - FAIL=$((FAIL + 1)) -fi - -# Test 2: task NOT done + non-zero exit -> should fail/block -EPIC7_JSON="$(scripts/ralph/flowctl epic create --title "Exit Code Epic 2" --json)" -EPIC7="$(echo "$EPIC7_JSON" | extract_id)" -TASK7_1_JSON="$(scripts/ralph/flowctl task create --epic "$EPIC7" --title "Not done and exit 1" --json)" -TASK7_1="$(echo "$TASK7_1_JSON" | extract_id)" -write_config "none" "none" "0" "new" "3" "5" "1" -set +e -STUB_MODE=success STUB_EXIT_CODE=1 STUB_SKIP_DONE=1 CLAUDE_BIN="$TEST_DIR/bin/claude" scripts/ralph/ralph.sh >/dev/null 2>&1 -rc=$? -set -e -"$PYTHON_BIN" - < blocked (rc=$rc)" - PASS=$((PASS + 1)) -else - echo -e "${RED}✗${NC} task not done + exit 1 -> blocked" - FAIL=$((FAIL + 1)) -fi - -# Note: verdict=SHIP check for plan phase uses identical logic, verified by code review - -echo "" -echo -e "${YELLOW}=== Results ===${NC}" -echo -e "Passed: ${GREEN}$PASS${NC}" -echo -e "Failed: ${RED}$FAIL${NC}" - -if [ $FAIL -gt 0 ]; then - exit 1 -fi -echo -e "\n${GREEN}All tests passed!${NC}" diff --git a/plugins/flow-next/scripts/smoke_test.sh b/plugins/flow-next/scripts/smoke_test.sh deleted file mode 100755 index bafdc37..0000000 --- a/plugins/flow-next/scripts/smoke_test.sh +++ /dev/null @@ -1,677 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -PLUGIN_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" - -# Python detection: prefer python3, fallback to python (Windows support, GH-35) -pick_python() { - if [[ -n "${PYTHON_BIN:-}" ]]; then - command -v "$PYTHON_BIN" >/dev/null 2>&1 && { echo "$PYTHON_BIN"; return; } - fi - if command -v python3 >/dev/null 2>&1; then echo "python3"; return; fi - if command -v python >/dev/null 2>&1; then echo "python"; return; fi - echo "" -} - -PYTHON_BIN="$(pick_python)" -[[ -n "$PYTHON_BIN" ]] || { echo "ERROR: python not found (need python3 or python in PATH)" >&2; exit 1; } - -# Safety: never run tests from the main plugin repo -if [[ -f "$PWD/.claude-plugin/marketplace.json" ]] || [[ -f "$PWD/plugins/flow-next/.claude-plugin/plugin.json" ]]; then - echo "ERROR: refusing to run from main plugin repo. Run from any other directory." >&2 - exit 1 -fi - -TEST_DIR="/tmp/flowctl-smoke-$$" -PASS=0 -FAIL=0 - -GREEN='\033[0;32m' -RED='\033[0;31m' -YELLOW='\033[1;33m' -NC='\033[0m' - -cleanup() { - rm -rf "$TEST_DIR" -} -trap cleanup EXIT - -echo -e "${YELLOW}=== flowctl smoke tests ===${NC}" - -mkdir -p "$TEST_DIR/repo/scripts" -cd "$TEST_DIR/repo" -git init -q - -cp "$PLUGIN_ROOT/scripts/flowctl.py" scripts/flowctl.py -cp "$PLUGIN_ROOT/scripts/flowctl" scripts/flowctl -chmod +x scripts/flowctl - -scripts/flowctl init --json >/dev/null -printf '{"commits":[],"tests":[],"prs":[]}' > "$TEST_DIR/evidence.json" -printf "ok\n" > "$TEST_DIR/summary.md" - -echo -e "${YELLOW}--- next: plan/work/none + priority ---${NC}" -# Capture epic ID from create output (fn-N-xxx format) -EPIC1_JSON="$(scripts/flowctl epic create --title "Epic One" --json)" -EPIC1="$(echo "$EPIC1_JSON" | "$PYTHON_BIN" -c 'import json,sys; print(json.load(sys.stdin)["id"])')" -scripts/flowctl task create --epic "$EPIC1" --title "Low pri" --priority 5 --json >/dev/null -scripts/flowctl task create --epic "$EPIC1" --title "High pri" --priority 1 --json >/dev/null - -plan_json="$(scripts/flowctl next --require-plan-review --json)" -"$PYTHON_BIN" - "$plan_json" "$EPIC1" <<'PY' -import json, sys -data = json.loads(sys.argv[1]) -expected_epic = sys.argv[2] -assert data["status"] == "plan" -assert data["epic"] == expected_epic, f"Expected {expected_epic}, got {data['epic']}" -PY -echo -e "${GREEN}✓${NC} next plan" -PASS=$((PASS + 1)) - -scripts/flowctl epic set-plan-review-status "$EPIC1" --status ship --json >/dev/null -work_json="$(scripts/flowctl next --json)" -"$PYTHON_BIN" - "$work_json" "$EPIC1" <<'PY' -import json, sys -data = json.loads(sys.argv[1]) -expected_epic = sys.argv[2] -assert data["status"] == "work" -assert data["task"] == f"{expected_epic}.2", f"Expected {expected_epic}.2, got {data['task']}" -PY -echo -e "${GREEN}✓${NC} next work priority" -PASS=$((PASS + 1)) - -scripts/flowctl start "${EPIC1}.2" --json >/dev/null -scripts/flowctl done "${EPIC1}.2" --summary-file "$TEST_DIR/summary.md" --evidence-json "$TEST_DIR/evidence.json" --json >/dev/null -scripts/flowctl start "${EPIC1}.1" --json >/dev/null -scripts/flowctl done "${EPIC1}.1" --summary-file "$TEST_DIR/summary.md" --evidence-json "$TEST_DIR/evidence.json" --json >/dev/null -none_json="$(scripts/flowctl next --json)" -"$PYTHON_BIN" - <<'PY' "$none_json" -import json, sys -data = json.loads(sys.argv[1]) -assert data["status"] == "none" -PY -echo -e "${GREEN}✓${NC} next none" -PASS=$((PASS + 1)) - -echo -e "${YELLOW}--- artifact files in tasks dir (GH-21) ---${NC}" -# Create artifact files that match glob but aren't valid task files -# This simulates Claude writing evidence/summary files to .flow/tasks/ -cat > ".flow/tasks/${EPIC1}.1-evidence.json" << 'EOF' -{"commits":["abc123"],"tests":["npm test"],"prs":[]} -EOF -cat > ".flow/tasks/${EPIC1}.1-summary.json" << 'EOF' -{"summary":"Task completed successfully"} -EOF -# Test that next still works with artifact files present -set +e -next_result="$(scripts/flowctl next --json 2>&1)" -next_rc=$? -set -e -if [[ "$next_rc" -eq 0 ]]; then - echo -e "${GREEN}✓${NC} next ignores artifact files" - PASS=$((PASS + 1)) -else - echo -e "${RED}✗${NC} next crashes on artifact files: $next_result" - FAIL=$((FAIL + 1)) -fi -# Test that list still works -set +e -list_result="$(scripts/flowctl list --json 2>&1)" -list_rc=$? -set -e -if [[ "$list_rc" -eq 0 ]]; then - echo -e "${GREEN}✓${NC} list ignores artifact files" - PASS=$((PASS + 1)) -else - echo -e "${RED}✗${NC} list crashes on artifact files: $list_result" - FAIL=$((FAIL + 1)) -fi -# Test that ready still works -set +e -ready_result="$(scripts/flowctl ready --epic "$EPIC1" --json 2>&1)" -ready_rc=$? -set -e -if [[ "$ready_rc" -eq 0 ]]; then - echo -e "${GREEN}✓${NC} ready ignores artifact files" - PASS=$((PASS + 1)) -else - echo -e "${RED}✗${NC} ready crashes on artifact files: $ready_result" - FAIL=$((FAIL + 1)) -fi -# Test that show (with tasks) still works -set +e -show_result="$(scripts/flowctl show "$EPIC1" --json 2>&1)" -show_rc=$? -set -e -if [[ "$show_rc" -eq 0 ]]; then - echo -e "${GREEN}✓${NC} show ignores artifact files" - PASS=$((PASS + 1)) -else - echo -e "${RED}✗${NC} show crashes on artifact files: $show_result" - FAIL=$((FAIL + 1)) -fi -# Test that validate still works -set +e -validate_result="$(scripts/flowctl validate --epic "$EPIC1" --json 2>&1)" -validate_rc=$? -set -e -if [[ "$validate_rc" -eq 0 ]]; then - echo -e "${GREEN}✓${NC} validate ignores artifact files" - PASS=$((PASS + 1)) -else - echo -e "${RED}✗${NC} validate crashes on artifact files: $validate_result" - FAIL=$((FAIL + 1)) -fi -# Cleanup artifact files -rm -f ".flow/tasks/${EPIC1}.1-evidence.json" ".flow/tasks/${EPIC1}.1-summary.json" - -echo -e "${YELLOW}--- plan_review_status default ---${NC}" -"$PYTHON_BIN" - "$EPIC1" <<'PY' -import json, sys -from pathlib import Path -epic_id = sys.argv[1] -path = Path(f".flow/epics/{epic_id}.json") -data = json.loads(path.read_text()) -data.pop("plan_review_status", None) -data.pop("plan_reviewed_at", None) -data.pop("branch_name", None) -path.write_text(json.dumps(data, indent=2, sort_keys=True) + "\n") -PY -show_json="$(scripts/flowctl show "$EPIC1" --json)" -"$PYTHON_BIN" - <<'PY' "$show_json" -import json, sys -data = json.loads(sys.argv[1]) -assert data.get("plan_review_status") == "unknown" -assert data.get("plan_reviewed_at") is None -assert data.get("branch_name") is None -PY -echo -e "${GREEN}✓${NC} plan_review_status defaulted" -PASS=$((PASS + 1)) - -echo -e "${YELLOW}--- branch_name set ---${NC}" -scripts/flowctl epic set-branch "$EPIC1" --branch "${EPIC1}-epic" --json >/dev/null -show_json="$(scripts/flowctl show "$EPIC1" --json)" -"$PYTHON_BIN" - "$show_json" "$EPIC1" <<'PY' -import json, sys -data = json.loads(sys.argv[1]) -expected_branch = f"{sys.argv[2]}-epic" -assert data.get("branch_name") == expected_branch, f"Expected {expected_branch}, got {data.get('branch_name')}" -PY -echo -e "${GREEN}✓${NC} branch_name set" -PASS=$((PASS + 1)) - -echo -e "${YELLOW}--- block + validate + epic close ---${NC}" -EPIC2_JSON="$(scripts/flowctl epic create --title "Epic Two" --json)" -EPIC2="$(echo "$EPIC2_JSON" | "$PYTHON_BIN" -c 'import json,sys; print(json.load(sys.stdin)["id"])')" -scripts/flowctl task create --epic "$EPIC2" --title "Block me" --json >/dev/null -scripts/flowctl task create --epic "$EPIC2" --title "Other" --json >/dev/null -printf "Blocked by test\n" > "$TEST_DIR/reason.md" -scripts/flowctl block "${EPIC2}.1" --reason-file "$TEST_DIR/reason.md" --json >/dev/null -scripts/flowctl validate --epic "$EPIC2" --json >/dev/null -echo -e "${GREEN}✓${NC} validate allows blocked" -PASS=$((PASS + 1)) - -set +e -scripts/flowctl epic close "$EPIC2" --json >/dev/null -rc=$? -set -e -if [[ "$rc" -ne 0 ]]; then - echo -e "${GREEN}✓${NC} epic close fails when blocked" - PASS=$((PASS + 1)) -else - echo -e "${RED}✗${NC} epic close fails when blocked" - FAIL=$((FAIL + 1)) -fi - -scripts/flowctl start "${EPIC2}.1" --force --json >/dev/null -scripts/flowctl done "${EPIC2}.1" --summary-file "$TEST_DIR/summary.md" --evidence-json "$TEST_DIR/evidence.json" --json >/dev/null -scripts/flowctl start "${EPIC2}.2" --json >/dev/null -scripts/flowctl done "${EPIC2}.2" --summary-file "$TEST_DIR/summary.md" --evidence-json "$TEST_DIR/evidence.json" --json >/dev/null -scripts/flowctl epic close "$EPIC2" --json >/dev/null -echo -e "${GREEN}✓${NC} epic close succeeds when done" -PASS=$((PASS + 1)) - -echo -e "${YELLOW}--- config set/get ---${NC}" -scripts/flowctl config set memory.enabled true --json >/dev/null -config_json="$(scripts/flowctl config get memory.enabled --json)" -"$PYTHON_BIN" - <<'PY' "$config_json" -import json, sys -data = json.loads(sys.argv[1]) -assert data["value"] == True, f"Expected True, got {data['value']}" -PY -echo -e "${GREEN}✓${NC} config set/get" -PASS=$((PASS + 1)) - -scripts/flowctl config set memory.enabled false --json >/dev/null -config_json="$(scripts/flowctl config get memory.enabled --json)" -"$PYTHON_BIN" - <<'PY' "$config_json" -import json, sys -data = json.loads(sys.argv[1]) -assert data["value"] == False, f"Expected False, got {data['value']}" -PY -echo -e "${GREEN}✓${NC} config toggle" -PASS=$((PASS + 1)) - -echo -e "${YELLOW}--- memory commands ---${NC}" -scripts/flowctl config set memory.enabled true --json >/dev/null -scripts/flowctl memory init --json >/dev/null -if [[ -f ".flow/memory/pitfalls.md" ]]; then - echo -e "${GREEN}✓${NC} memory init creates files" - PASS=$((PASS + 1)) -else - echo -e "${RED}✗${NC} memory init creates files" - FAIL=$((FAIL + 1)) -fi - -scripts/flowctl memory add --type pitfall "Test pitfall entry" --json >/dev/null -if grep -q "Test pitfall entry" .flow/memory/pitfalls.md; then - echo -e "${GREEN}✓${NC} memory add pitfall" - PASS=$((PASS + 1)) -else - echo -e "${RED}✗${NC} memory add pitfall" - FAIL=$((FAIL + 1)) -fi - -scripts/flowctl memory add --type convention "Test convention" --json >/dev/null -scripts/flowctl memory add --type decision "Test decision" --json >/dev/null -list_json="$(scripts/flowctl memory list --json)" -"$PYTHON_BIN" - <<'PY' "$list_json" -import json, sys -data = json.loads(sys.argv[1]) -assert data["success"] == True -counts = data["counts"] -assert counts["pitfalls.md"] >= 1 -assert counts["conventions.md"] >= 1 -assert counts["decisions.md"] >= 1 -assert data["total"] >= 3 -PY -echo -e "${GREEN}✓${NC} memory list" -PASS=$((PASS + 1)) - -echo -e "${YELLOW}--- schema v1 validate ---${NC}" -"$PYTHON_BIN" - <<'PY' -import json -from pathlib import Path -path = Path(".flow/meta.json") -data = json.loads(path.read_text()) -data["schema_version"] = 1 -path.write_text(json.dumps(data, indent=2, sort_keys=True) + "\n") -PY -scripts/flowctl validate --all --json >/dev/null -echo -e "${GREEN}✓${NC} schema v1 validate" -PASS=$((PASS + 1)) - -echo -e "${YELLOW}--- codex commands ---${NC}" -# Test codex check (may or may not have codex installed) -codex_check_json="$(scripts/flowctl codex check --json 2>/dev/null || echo '{"success":true}')" -"$PYTHON_BIN" - <<'PY' "$codex_check_json" -import json, sys -data = json.loads(sys.argv[1]) -assert data["success"] == True, f"codex check failed: {data}" -# available can be true or false depending on codex install -PY -echo -e "${GREEN}✓${NC} codex check" -PASS=$((PASS + 1)) - -# Test codex impl-review help (no codex required for argparse check) -set +e -scripts/flowctl codex impl-review --help >/dev/null 2>&1 -rc=$? -set -e -if [[ "$rc" -eq 0 ]]; then - echo -e "${GREEN}✓${NC} codex impl-review --help" - PASS=$((PASS + 1)) -else - echo -e "${RED}✗${NC} codex impl-review --help" - FAIL=$((FAIL + 1)) -fi - -# Test codex plan-review help -set +e -scripts/flowctl codex plan-review --help >/dev/null 2>&1 -rc=$? -set -e -if [[ "$rc" -eq 0 ]]; then - echo -e "${GREEN}✓${NC} codex plan-review --help" - PASS=$((PASS + 1)) -else - echo -e "${RED}✗${NC} codex plan-review --help" - FAIL=$((FAIL + 1)) -fi - -echo -e "${YELLOW}--- context hints ---${NC}" -# Create files in same commit, then modify one to test context hints -mkdir -p "$TEST_DIR/repo/src" -# First commit: both auth.py and handler.py together -cat > "$TEST_DIR/repo/src/auth.py" << 'EOF' -def validate_token(token: str) -> bool: - """Validate JWT token.""" - return len(token) > 10 - -class User: - def __init__(self, name: str): - self.name = name -EOF -cat > "$TEST_DIR/repo/src/handler.py" << 'EOF' -from auth import validate_token, User - -def handle_request(token: str): - if validate_token(token): - return User("test") - return None -EOF -git -C "$TEST_DIR/repo" add src/ -git -C "$TEST_DIR/repo" commit -m "Add auth and handler" >/dev/null - -# Second commit: only modify auth.py (handler.py stays unchanged) -cat > "$TEST_DIR/repo/src/auth.py" << 'EOF' -def validate_token(token: str) -> bool: - """Validate JWT token with expiry check.""" - if len(token) < 10: - return False - return True - -class User: - def __init__(self, name: str, email: str = ""): - self.name = name - self.email = email -EOF -git -C "$TEST_DIR/repo" add src/auth.py -git -C "$TEST_DIR/repo" commit -m "Update auth with expiry" >/dev/null - -# Test context hints: should find handler.py referencing validate_token/User -cd "$TEST_DIR/repo" -hints_output="$(PYTHONPATH="$SCRIPT_DIR" "$PYTHON_BIN" -c " -from flowctl import gather_context_hints -hints = gather_context_hints('HEAD~1') -print(hints) -" 2>&1)" - -# Verify hints mention handler.py referencing validate_token or User -if echo "$hints_output" | grep -q "handler.py"; then - echo -e "${GREEN}✓${NC} context hints finds references" - PASS=$((PASS + 1)) -else - echo -e "${RED}✗${NC} context hints finds references (got: $hints_output)" - FAIL=$((FAIL + 1)) -fi - -echo -e "${YELLOW}--- build_review_prompt ---${NC}" -# Go back to plugin root for Python tests -cd "$TEST_DIR/repo" -# Test that build_review_prompt generates proper structure -"$PYTHON_BIN" - "$SCRIPT_DIR" <<'PY' -import sys -sys.path.insert(0, sys.argv[1]) -from flowctl import build_review_prompt - -# Test impl prompt has all 7 criteria -impl_prompt = build_review_prompt("impl", "Test spec", "Test hints", "Test diff") -assert "" in impl_prompt -assert "Correctness" in impl_prompt -assert "Simplicity" in impl_prompt -assert "DRY" in impl_prompt -assert "Architecture" in impl_prompt -assert "Edge Cases" in impl_prompt -assert "Tests" in impl_prompt -assert "Security" in impl_prompt -assert "SHIP" in impl_prompt -assert "File:Line" in impl_prompt # Structured output format - -# Test plan prompt has all 7 criteria -plan_prompt = build_review_prompt("plan", "Test spec", "Test hints") -assert "Completeness" in plan_prompt -assert "Feasibility" in plan_prompt -assert "Clarity" in plan_prompt -assert "Architecture" in plan_prompt -assert "Risks" in plan_prompt -assert "Scope" in plan_prompt -assert "Testability" in plan_prompt -assert "SHIP" in plan_prompt - -# Test context hints and diff are included -assert "" in impl_prompt -assert "Test hints" in impl_prompt -assert "" in impl_prompt -assert "Test diff" in impl_prompt -assert "" in impl_prompt -assert "Test spec" in impl_prompt -PY -echo -e "${GREEN}✓${NC} build_review_prompt has full criteria" -PASS=$((PASS + 1)) - -echo -e "${YELLOW}--- parse_receipt_path ---${NC}" -# Test receipt path parsing for Ralph gating (both legacy and new fn-N-xxx formats) -"$PYTHON_BIN" - "$SCRIPT_DIR/hooks" <<'PY' -import sys -hooks_dir = sys.argv[1] -sys.path.insert(0, hooks_dir) -from importlib.util import spec_from_file_location, module_from_spec -spec = spec_from_file_location("ralph_guard", f"{hooks_dir}/ralph-guard.py") -guard = module_from_spec(spec) -spec.loader.exec_module(guard) - -# Test plan receipt parsing (legacy format) -rtype, rid = guard.parse_receipt_path("/tmp/receipts/plan-fn-1.json") -assert rtype == "plan_review", f"Expected plan_review, got {rtype}" -assert rid == "fn-1", f"Expected fn-1, got {rid}" - -# Test impl receipt parsing (legacy format) -rtype, rid = guard.parse_receipt_path("/tmp/receipts/impl-fn-1.3.json") -assert rtype == "impl_review", f"Expected impl_review, got {rtype}" -assert rid == "fn-1.3", f"Expected fn-1.3, got {rid}" - -# Test plan receipt parsing (new fn-N-xxx format) -rtype, rid = guard.parse_receipt_path("/tmp/receipts/plan-fn-5-x7k.json") -assert rtype == "plan_review", f"Expected plan_review, got {rtype}" -assert rid == "fn-5-x7k", f"Expected fn-5-x7k, got {rid}" - -# Test impl receipt parsing (new fn-N-xxx format) -rtype, rid = guard.parse_receipt_path("/tmp/receipts/impl-fn-5-x7k.3.json") -assert rtype == "impl_review", f"Expected impl_review, got {rtype}" -assert rid == "fn-5-x7k.3", f"Expected fn-5-x7k.3, got {rid}" - -# Test fallback -rtype, rid = guard.parse_receipt_path("/tmp/unknown.json") -assert rtype == "impl_review" -assert rid == "UNKNOWN" -PY -echo -e "${GREEN}✓${NC} parse_receipt_path works" -PASS=$((PASS + 1)) - -echo -e "${YELLOW}--- codex e2e (requires codex CLI) ---${NC}" -# Check if codex is available (handles its own auth) -codex_available="$(scripts/flowctl codex check --json 2>/dev/null | "$PYTHON_BIN" -c "import sys,json; print(json.load(sys.stdin).get('available', False))" 2>/dev/null || echo "False")" -if [[ "$codex_available" == "True" ]]; then - # Create a simple epic + task for testing - EPIC3_JSON="$(scripts/flowctl epic create --title "Codex test epic" --json)" - EPIC3="$(echo "$EPIC3_JSON" | "$PYTHON_BIN" -c 'import json,sys; print(json.load(sys.stdin)["id"])')" - scripts/flowctl task create --epic "$EPIC3" --title "Test task" --json >/dev/null - - # Write a simple spec - cat > ".flow/specs/${EPIC3}.md" << 'EOF' -# Codex Test Epic - -Simple test epic for smoke testing codex reviews. - -## Scope -- Test that codex can review a plan -- Test that codex can review an implementation -EOF - - cat > ".flow/tasks/${EPIC3}.1.md" << 'EOF' -# Test Task - -Add a simple hello world function. - -## Acceptance -- Function returns "hello world" -EOF - - # Test plan-review e2e - set +e - plan_result="$(scripts/flowctl codex plan-review "$EPIC3" --base main --receipt "$TEST_DIR/plan-receipt.json" --json 2>&1)" - plan_rc=$? - set -e - - if [[ "$plan_rc" -eq 0 ]]; then - # Verify receipt was written with correct schema - if [[ -f "$TEST_DIR/plan-receipt.json" ]]; then - "$PYTHON_BIN" - "$TEST_DIR/plan-receipt.json" "$EPIC3" <<'PY' -import sys, json -from pathlib import Path -data = json.loads(Path(sys.argv[1]).read_text()) -expected_id = sys.argv[2] -assert data.get("type") == "plan_review", f"Expected type=plan_review, got {data.get('type')}" -assert data.get("id") == expected_id, f"Expected id={expected_id}, got {data.get('id')}" -assert data.get("mode") == "codex", f"Expected mode=codex, got {data.get('mode')}" -assert "verdict" in data, "Missing verdict in receipt" -assert "session_id" in data, "Missing session_id in receipt" -PY - echo -e "${GREEN}✓${NC} codex plan-review e2e" - PASS=$((PASS + 1)) - else - echo -e "${RED}✗${NC} codex plan-review e2e (no receipt)" - FAIL=$((FAIL + 1)) - fi - else - echo -e "${RED}✗${NC} codex plan-review e2e (exit $plan_rc)" - FAIL=$((FAIL + 1)) - fi - - # Test impl-review e2e (create a simple change first) - cat > "$TEST_DIR/repo/src/hello.py" << 'EOF' -def hello(): - return "hello world" -EOF - git -C "$TEST_DIR/repo" add src/hello.py - git -C "$TEST_DIR/repo" commit -m "Add hello function" >/dev/null - - set +e - impl_result="$(scripts/flowctl codex impl-review "${EPIC3}.1" --base HEAD~1 --receipt "$TEST_DIR/impl-receipt.json" --json 2>&1)" - impl_rc=$? - set -e - - if [[ "$impl_rc" -eq 0 ]]; then - # Verify receipt was written with correct schema - if [[ -f "$TEST_DIR/impl-receipt.json" ]]; then - "$PYTHON_BIN" - "$TEST_DIR/impl-receipt.json" "$EPIC3" <<'PY' -import sys, json -from pathlib import Path -data = json.loads(Path(sys.argv[1]).read_text()) -expected_id = f"{sys.argv[2]}.1" -assert data.get("type") == "impl_review", f"Expected type=impl_review, got {data.get('type')}" -assert data.get("id") == expected_id, f"Expected id={expected_id}, got {data.get('id')}" -assert data.get("mode") == "codex", f"Expected mode=codex, got {data.get('mode')}" -assert "verdict" in data, "Missing verdict in receipt" -assert "session_id" in data, "Missing session_id in receipt" -PY - echo -e "${GREEN}✓${NC} codex impl-review e2e" - PASS=$((PASS + 1)) - else - echo -e "${RED}✗${NC} codex impl-review e2e (no receipt)" - FAIL=$((FAIL + 1)) - fi - else - echo -e "${RED}✗${NC} codex impl-review e2e (exit $impl_rc)" - FAIL=$((FAIL + 1)) - fi -else - echo -e "${YELLOW}⊘${NC} codex e2e skipped (codex not available)" -fi - -echo -e "${YELLOW}--- depends_on_epics gate ---${NC}" -cd "$TEST_DIR/repo" # Back to test repo -# Create epics and capture their IDs -DEP_BASE_JSON="$(scripts/flowctl epic create --title "Dep base" --json)" -DEP_BASE_ID="$(echo "$DEP_BASE_JSON" | "$PYTHON_BIN" -c 'import json,sys; print(json.load(sys.stdin)["id"])')" -scripts/flowctl task create --epic "$DEP_BASE_ID" --title "Base task" --json >/dev/null -DEP_CHILD_JSON="$(scripts/flowctl epic create --title "Dep child" --json)" -DEP_CHILD_ID="$(echo "$DEP_CHILD_JSON" | "$PYTHON_BIN" -c 'import json,sys; print(json.load(sys.stdin)["id"])')" -"$PYTHON_BIN" - "$DEP_CHILD_ID" "$DEP_BASE_ID" <<'PY' -import json, sys -from pathlib import Path -child_id, base_id = sys.argv[1], sys.argv[2] -path = Path(f".flow/epics/{child_id}.json") -data = json.loads(path.read_text()) -data["depends_on_epics"] = [base_id] -path.write_text(json.dumps(data, indent=2, sort_keys=True) + "\n") -PY -printf '{"epics":["%s"]}\n' "$DEP_CHILD_ID" > "$TEST_DIR/epics.json" -blocked_json="$(scripts/flowctl next --epics-file "$TEST_DIR/epics.json" --json)" -"$PYTHON_BIN" - "$DEP_CHILD_ID" "$blocked_json" <<'PY' -import json, sys -child_id = sys.argv[1] -data = json.loads(sys.argv[2]) -assert data["status"] == "none" -assert data["reason"] == "blocked_by_epic_deps" -assert child_id in data.get("blocked_epics", {}) -PY -echo -e "${GREEN}✓${NC} depends_on_epics blocks" -PASS=$((PASS + 1)) - -echo -e "${YELLOW}--- stdin support ---${NC}" -cd "$TEST_DIR/repo" -STDIN_EPIC_JSON="$(scripts/flowctl epic create --title "Stdin test" --json)" -STDIN_EPIC="$(echo "$STDIN_EPIC_JSON" | "$PYTHON_BIN" -c 'import json,sys; print(json.load(sys.stdin)["id"])')" -# Test epic set-plan with stdin -scripts/flowctl epic set-plan "$STDIN_EPIC" --file - --json <<'EOF' -# Stdin Test Plan - -## Overview -Testing stdin support for set-plan. - -## Acceptance -- Works via stdin -EOF -# Verify content was written -spec_content="$(scripts/flowctl cat "$STDIN_EPIC")" -echo "$spec_content" | grep -q "Testing stdin support" || { echo "stdin set-plan failed"; FAIL=$((FAIL + 1)); } -echo -e "${GREEN}✓${NC} stdin epic set-plan" -PASS=$((PASS + 1)) - -echo -e "${YELLOW}--- task set-spec combined ---${NC}" -scripts/flowctl task create --epic "$STDIN_EPIC" --title "Set-spec test" --json >/dev/null -SETSPEC_TASK="${STDIN_EPIC}.1" -# Write temp files for combined set-spec -echo 'This is the description.' > "$TEST_DIR/desc.md" -echo '- [ ] Check 1 -- [ ] Check 2' > "$TEST_DIR/acc.md" -scripts/flowctl task set-spec "$SETSPEC_TASK" --description "$TEST_DIR/desc.md" --acceptance "$TEST_DIR/acc.md" --json >/dev/null -# Verify both sections were written -task_spec="$(scripts/flowctl cat "$SETSPEC_TASK")" -echo "$task_spec" | grep -q "This is the description" || { echo "set-spec description failed"; FAIL=$((FAIL + 1)); } -echo "$task_spec" | grep -q "Check 1" || { echo "set-spec acceptance failed"; FAIL=$((FAIL + 1)); } -echo -e "${GREEN}✓${NC} task set-spec combined" -PASS=$((PASS + 1)) - -echo -e "${YELLOW}--- checkpoint save/restore ---${NC}" -# Save checkpoint -scripts/flowctl checkpoint save --epic "$STDIN_EPIC" --json >/dev/null -# Verify checkpoint file exists -[[ -f ".flow/.checkpoint-${STDIN_EPIC}.json" ]] || { echo "checkpoint file not created"; FAIL=$((FAIL + 1)); } -# Modify epic spec -scripts/flowctl epic set-plan "$STDIN_EPIC" --file - --json <<'EOF' -# Modified content -EOF -# Restore from checkpoint -scripts/flowctl checkpoint restore --epic "$STDIN_EPIC" --json >/dev/null -# Verify original content restored -restored_spec="$(scripts/flowctl cat "$STDIN_EPIC")" -echo "$restored_spec" | grep -q "Testing stdin support" || { echo "checkpoint restore failed"; FAIL=$((FAIL + 1)); } -# Delete checkpoint -scripts/flowctl checkpoint delete --epic "$STDIN_EPIC" --json >/dev/null -[[ ! -f ".flow/.checkpoint-${STDIN_EPIC}.json" ]] || { echo "checkpoint delete failed"; FAIL=$((FAIL + 1)); } -echo -e "${GREEN}✓${NC} checkpoint save/restore/delete" -PASS=$((PASS + 1)) - -echo "" -echo -e "${YELLOW}=== Results ===${NC}" -echo -e "Passed: ${GREEN}$PASS${NC}" -echo -e "Failed: ${RED}$FAIL${NC}" - -if [ $FAIL -gt 0 ]; then - exit 1 -fi -echo -e "\n${GREEN}All tests passed!${NC}" diff --git a/plugins/flow-next/skills/browser/SKILL.md b/plugins/flow-next/skills/browser/SKILL.md deleted file mode 100644 index 7ffaf0a..0000000 --- a/plugins/flow-next/skills/browser/SKILL.md +++ /dev/null @@ -1,212 +0,0 @@ ---- -name: browser -description: Browser automation via agent-browser CLI. Use when you need to navigate websites, verify deployed UI, test web apps, read online documentation, scrape data, fill forms, capture baseline screenshots before design work, or inspect current page state. Triggers on "check the page", "verify UI", "test the site", "read docs at", "look up API", "visit URL", "browse", "screenshot", "scrape", "e2e test", "login flow", "capture baseline", "see how it looks", "inspect current", "before redesign". ---- - -# Browser Automation - -Browser automation via Vercel's agent-browser CLI. Runs headless by default; use `--headed` for visible window. Uses ref-based selection (@e1, @e2) from accessibility snapshots. - -## Setup - -```bash -command -v agent-browser >/dev/null 2>&1 && echo "OK" || echo "MISSING: npm i -g agent-browser && agent-browser install" -``` - -## Core Workflow - -1. **Open** URL -2. **Snapshot** to get refs -3. **Interact** via refs -4. **Re-snapshot** after DOM changes - -```bash -agent-browser open https://example.com -agent-browser snapshot -i # Interactive elements with refs -agent-browser click @e1 -agent-browser wait --load networkidle # Wait for SPA to settle -agent-browser snapshot -i # Re-snapshot after change -``` - -## Essential Commands - -### Navigation - -```bash -agent-browser open # Navigate -agent-browser back # Go back -agent-browser forward # Go forward -agent-browser reload # Reload -agent-browser close # Close browser -``` - -### Snapshots - -```bash -agent-browser snapshot # Full accessibility tree -agent-browser snapshot -i # Interactive only (recommended) -agent-browser snapshot -i --json # JSON for parsing -agent-browser snapshot -c # Compact (remove empty) -agent-browser snapshot -d 3 # Limit depth -agent-browser snapshot -s "#main" # Scope to selector -``` - -### Interactions - -```bash -agent-browser click @e1 # Click -agent-browser dblclick @e1 # Double-click -agent-browser fill @e1 "text" # Clear + fill input -agent-browser type @e1 "text" # Type without clearing -agent-browser press Enter # Key press -agent-browser press Control+a # Key combination -agent-browser hover @e1 # Hover -agent-browser check @e1 # Check checkbox -agent-browser uncheck @e1 # Uncheck -agent-browser select @e1 "option" # Dropdown -agent-browser scroll down 500 # Scroll direction + pixels -agent-browser scrollintoview @e1 # Scroll element visible -``` - -### Get Info - -```bash -agent-browser get text @e1 # Element text -agent-browser get value @e1 # Input value -agent-browser get html @e1 # Element HTML -agent-browser get attr href @e1 # Attribute -agent-browser get title # Page title -agent-browser get url # Current URL -agent-browser get count "button" # Count matches -``` - -### Check State - -```bash -agent-browser is visible @e1 # Check visibility -agent-browser is enabled @e1 # Check enabled -agent-browser is checked @e1 # Check checkbox state -``` - -### Wait - -```bash -agent-browser wait @e1 # Wait for element visible -agent-browser wait 2000 # Wait milliseconds -agent-browser wait --text "Success" # Wait for text -agent-browser wait --url "**/dashboard" # Wait for URL pattern -agent-browser wait --load networkidle # Wait for network idle (SPAs) -agent-browser wait --fn "window.ready" # Wait for JS condition -``` - -### Screenshots - -```bash -agent-browser screenshot # Viewport to stdout -agent-browser screenshot out.png # Save to file -agent-browser screenshot --full # Full page -agent-browser pdf out.pdf # Save as PDF -``` - -### Semantic Locators - -Alternative when you know the element (no snapshot needed, see [advanced.md](references/advanced.md) for tabs, frames, network mocking): - -```bash -agent-browser find role button click --name "Submit" -agent-browser find text "Sign In" click -agent-browser find label "Email" fill "user@test.com" -agent-browser find placeholder "Search" fill "query" -agent-browser find first ".item" click -agent-browser find nth 2 "a" text -``` - -## Sessions - -Parallel isolated browsers (see [auth.md](references/auth.md) for multi-user auth): - -```bash -agent-browser --session test1 open site-a.com -agent-browser --session test2 open site-b.com -agent-browser session list -``` - -## JSON Output - -Add `--json` for machine-readable output: - -```bash -agent-browser snapshot -i --json -agent-browser get text @e1 --json -agent-browser is visible @e1 --json -``` - -## Examples - -### Form Submission - -```bash -agent-browser open https://example.com/form -agent-browser snapshot -i -# textbox "Email" [ref=e1], textbox "Password" [ref=e2], button "Submit" [ref=e3] -agent-browser fill @e1 "user@example.com" -agent-browser fill @e2 "password123" -agent-browser click @e3 -agent-browser wait --load networkidle -agent-browser snapshot -i # Verify result -``` - -### Auth with Saved State - -```bash -# Login once -agent-browser open https://app.example.com/login -agent-browser snapshot -i -agent-browser fill @e1 "username" -agent-browser fill @e2 "password" -agent-browser click @e3 -agent-browser wait --url "**/dashboard" -agent-browser state save auth.json - -# Later: reuse saved auth -agent-browser state load auth.json -agent-browser open https://app.example.com/dashboard -``` - -More auth patterns in [auth.md](references/auth.md). - -### Token Auth (Skip Login) - -```bash -# Headers scoped to origin only -agent-browser open api.example.com --headers '{"Authorization": "Bearer "}' -agent-browser snapshot -i --json -``` - -## Debugging - -```bash -agent-browser --headed open example.com # Show browser window -agent-browser console # View console messages -agent-browser errors # View page errors -agent-browser highlight @e1 # Highlight element -``` - -See [debugging.md](references/debugging.md) for traces, common issues. - -## Troubleshooting - -**"Browser not launched" error**: Daemon stuck. Kill and retry: -```bash -pkill -f agent-browser && agent-browser open -``` - -**Element not found**: Re-snapshot after page changes. DOM may have updated. - -## References - -| Topic | File | -|-------|------| -| Debugging, traces, common issues | [debugging.md](references/debugging.md) | -| Auth, cookies, storage, state persistence | [auth.md](references/auth.md) | -| Network mocking, tabs, frames, dialogs, settings | [advanced.md](references/advanced.md) | diff --git a/plugins/flow-next/skills/browser/references/advanced.md b/plugins/flow-next/skills/browser/references/advanced.md deleted file mode 100644 index b0d56c7..0000000 --- a/plugins/flow-next/skills/browser/references/advanced.md +++ /dev/null @@ -1,169 +0,0 @@ -# Advanced Features - -## Network Interception - -Mock or block network requests: - -```bash -# Intercept and track requests -agent-browser network route "**/api/*" - -# Block requests (ads, analytics) -agent-browser network route "**/analytics/*" --abort - -# Mock response -agent-browser network route "**/api/user" --body '{"name":"Test User"}' - -# Remove route -agent-browser network unroute "**/api/*" - -# View tracked requests -agent-browser network requests -agent-browser network requests --filter api -``` - -## Tabs - -```bash -agent-browser tab # List tabs -agent-browser tab new # New blank tab -agent-browser tab new url.com # New tab with URL -agent-browser tab 2 # Switch to tab 2 -agent-browser tab close # Close current tab -agent-browser tab close 2 # Close tab 2 -``` - -## Windows - -```bash -agent-browser window new # New browser window -``` - -## Frames (iframes) - -```bash -agent-browser frame "#iframe-selector" # Switch to iframe -agent-browser snapshot -i # Snapshot within iframe -agent-browser click @e1 # Interact within iframe -agent-browser frame main # Back to main frame -``` - -## Dialogs - -Handle alert/confirm/prompt dialogs: - -```bash -agent-browser dialog accept # Accept dialog -agent-browser dialog accept "input text" # Accept prompt with text -agent-browser dialog dismiss # Dismiss/cancel dialog -``` - -## Mouse Control - -Low-level mouse operations: - -```bash -agent-browser mouse move 100 200 # Move to coordinates -agent-browser mouse down # Press left button -agent-browser mouse down right # Press right button -agent-browser mouse up # Release button -agent-browser mouse wheel -500 # Scroll wheel (negative = up) -``` - -## Drag and Drop - -```bash -agent-browser drag @e1 @e2 # Drag e1 to e2 -agent-browser drag "#source" "#target" -``` - -## File Upload - -```bash -agent-browser upload @e1 /path/to/file.pdf -agent-browser upload @e1 file1.jpg file2.jpg # Multiple files -``` - -## Browser Settings - -### Viewport - -```bash -agent-browser set viewport 1920 1080 -``` - -### Device Emulation - -```bash -agent-browser set device "iPhone 14" -agent-browser set device "Pixel 5" -``` - -### Geolocation - -```bash -agent-browser set geo 37.7749 -122.4194 # San Francisco -``` - -### Offline Mode - -```bash -agent-browser set offline on -agent-browser set offline off -``` - -### Color Scheme - -```bash -agent-browser set media dark -agent-browser set media light -``` - -## CDP Mode - -Connect to existing browser via Chrome DevTools Protocol: - -```bash -# Connect to Electron app or Chrome with remote debugging -# Start Chrome: google-chrome --remote-debugging-port=9222 -agent-browser --cdp 9222 snapshot -agent-browser --cdp 9222 click @e1 -``` - -Use cases: -- Control Electron apps -- Connect to existing Chrome sessions -- WebView2 applications - -## JavaScript Evaluation - -```bash -agent-browser eval "document.title" -agent-browser eval "window.scrollTo(0, 1000)" -agent-browser eval "localStorage.getItem('token')" -``` - -## Bounding Box - -Get element position and size: - -```bash -agent-browser get box @e1 -# {"x":100,"y":200,"width":150,"height":40} -``` - -## Custom Browser Executable - -Use system Chrome or lightweight builds: - -```bash -agent-browser --executable-path /usr/bin/google-chrome open example.com - -# Or via environment -AGENT_BROWSER_EXECUTABLE_PATH=/path/to/chromium agent-browser open example.com -``` - -Useful for: -- Serverless (use `@sparticuz/chromium`) -- System browser instead of bundled -- Custom Chromium builds diff --git a/plugins/flow-next/skills/browser/references/auth.md b/plugins/flow-next/skills/browser/references/auth.md deleted file mode 100644 index 7b89055..0000000 --- a/plugins/flow-next/skills/browser/references/auth.md +++ /dev/null @@ -1,111 +0,0 @@ -# Authentication - -Patterns for handling login, sessions, and auth state. - -## State Persistence - -Save and restore full browser state (cookies, localStorage, sessionStorage): - -```bash -# After successful login -agent-browser state save auth.json - -# In new session -agent-browser state load auth.json -agent-browser open https://app.example.com/dashboard -``` - -## Token Auth via Headers - -Skip login flows entirely with auth headers: - -```bash -# Headers scoped to origin only (safe!) -agent-browser open api.example.com --headers '{"Authorization": "Bearer "}' -``` - -Multiple origins: -```bash -agent-browser open api.example.com --headers '{"Authorization": "Bearer token1"}' -agent-browser open api.acme.com --headers '{"Authorization": "Bearer token2"}' -``` - -Global headers (all domains): -```bash -agent-browser set headers '{"X-Custom-Header": "value"}' -``` - -## Cookies - -```bash -agent-browser cookies # Get all cookies -agent-browser cookies set name "value" # Set cookie -agent-browser cookies clear # Clear all cookies -``` - -## Local Storage - -```bash -agent-browser storage local # Get all localStorage -agent-browser storage local key # Get specific key -agent-browser storage local set key val # Set value -agent-browser storage local clear # Clear all -``` - -## Session Storage - -```bash -agent-browser storage session # Get all sessionStorage -agent-browser storage session key # Get specific key -agent-browser storage session set k v # Set value -agent-browser storage session clear # Clear all -``` - -## HTTP Basic Auth - -```bash -agent-browser set credentials username password -agent-browser open https://protected-site.com -``` - -## Example: Full Login Flow with State Save - -```bash -# First run: perform login -agent-browser open https://app.example.com/login -agent-browser snapshot -i -agent-browser fill @e1 "user@example.com" -agent-browser fill @e2 "password123" -agent-browser click @e3 -agent-browser wait --url "**/dashboard" -agent-browser wait --load networkidle - -# Verify logged in -agent-browser snapshot -i -# Should show dashboard elements, not login form - -# Save state for future runs -agent-browser state save auth.json -agent-browser close -``` - -```bash -# Subsequent runs: skip login -agent-browser state load auth.json -agent-browser open https://app.example.com/dashboard -# Already authenticated! -``` - -## Session Isolation - -Different auth states in parallel: - -```bash -# Admin session -agent-browser --session admin state load admin-auth.json -agent-browser --session admin open https://app.example.com/admin - -# User session -agent-browser --session user state load user-auth.json -agent-browser --session user open https://app.example.com/profile -``` diff --git a/plugins/flow-next/skills/browser/references/debugging.md b/plugins/flow-next/skills/browser/references/debugging.md deleted file mode 100644 index 3738d80..0000000 --- a/plugins/flow-next/skills/browser/references/debugging.md +++ /dev/null @@ -1,92 +0,0 @@ -# Debugging - -When browser automation fails or behaves unexpectedly. - -## Headed Mode - -Show visible browser window to see what's happening: - -```bash -agent-browser --headed open example.com -agent-browser --headed snapshot -i -agent-browser --headed click @e1 -``` - -## Console & Errors - -View browser console output and page errors: - -```bash -agent-browser console # View console messages -agent-browser console --clear # Clear console log -agent-browser errors # View page errors -agent-browser errors --clear # Clear error log -``` - -## Highlight Elements - -Visually identify elements (use with `--headed`): - -```bash -agent-browser highlight @e1 -agent-browser highlight "#selector" -``` - -## Traces - -Record browser traces for detailed debugging: - -```bash -agent-browser trace start # Start recording -# ... do interactions ... -agent-browser trace stop trace.zip # Save trace file -``` - -Open traces in Playwright Trace Viewer: `npx playwright show-trace trace.zip` - -## State Checks - -Verify element state before interacting: - -```bash -agent-browser is visible @e1 # Returns true/false -agent-browser is enabled @e1 # Check if interactive -agent-browser is checked @e1 # Checkbox state -``` - -With JSON output: -```bash -agent-browser is visible @e1 --json -# {"success":true,"data":true} -``` - -## Common Issues - -### Element not found -- Re-snapshot: DOM may have changed -- Check visibility: `is visible @e1` -- Try `--headed` to see actual page state - -### Click does nothing -- Element may be covered: try `scrollintoview @e1` first -- Element may be disabled: check `is enabled @e1` -- SPA not ready: add `wait --load networkidle` - -### Form not submitting -- Check for validation errors in snapshot -- Some forms need `press Enter` instead of button click -- Wait for network: `wait --load networkidle` - -### Auth redirect loops -- Save state after successful login: `state save auth.json` -- Check cookies: `cookies` -- Verify URL pattern: `get url` - -## Debug Output - -Add `--debug` for verbose output: - -```bash -agent-browser --debug open example.com -agent-browser --debug click @e1 -``` diff --git a/plugins/flow-next/skills/flow-next-export-context/SKILL.md b/plugins/flow-next/skills/flow-next-export-context/SKILL.md deleted file mode 100644 index 430f1e2..0000000 --- a/plugins/flow-next/skills/flow-next-export-context/SKILL.md +++ /dev/null @@ -1,117 +0,0 @@ ---- -name: flow-next-export-context -description: Export RepoPrompt context for external LLM review (ChatGPT, Claude web, etc.). Use when you want to review code or plans with an external model. Triggers on /flow-next:export-context. ---- - -# Export Context Mode - -Build RepoPrompt context and export to a markdown file for use with external LLMs (ChatGPT Pro, Claude web, etc.). - -**Use case**: When you want Carmack-level review but prefer to use an external model. - -**CRITICAL: flowctl is BUNDLED — NOT installed globally.** `which flowctl` will fail (expected). Always use: -```bash -FLOWCTL="${CLAUDE_PLUGIN_ROOT}/scripts/flowctl" -$FLOWCTL -``` - -## Input - -Arguments: $ARGUMENTS -Format: ` [focus areas]` - -Types: -- `plan ` - Export plan review context -- `impl` - Export implementation review context (current branch) - -Examples: -- `/flow-next:export-context plan fn-1 focus on security` -- `/flow-next:export-context impl focus on the auth changes` - -## Setup - -```bash -FLOWCTL="${CLAUDE_PLUGIN_ROOT}/scripts/flowctl" -REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" -``` - -## Workflow - -### Step 1: Determine Type - -Parse arguments to determine if this is a plan or impl export. - -### Step 2: Gather Content - -**For plan export:** -```bash -$FLOWCTL show --json -$FLOWCTL cat -``` - -**For impl export:** -```bash -git branch --show-current -git log main..HEAD --oneline 2>/dev/null || git log master..HEAD --oneline -git diff main..HEAD --name-only 2>/dev/null || git diff master..HEAD --name-only -``` - -### Step 3: Setup RepoPrompt - -```bash -eval "$($FLOWCTL rp setup-review --repo-root "$REPO_ROOT" --summary "")" -``` - -### Step 4: Augment Selection - -```bash -$FLOWCTL rp select-get --window "$W" --tab "$T" - -# Add relevant files -$FLOWCTL rp select-add --window "$W" --tab "$T" -``` - -### Step 5: Build Review Prompt - -Get builder's handoff: -```bash -$FLOWCTL rp prompt-get --window "$W" --tab "$T" -``` - -Build combined prompt with review criteria (same as plan-review or impl-review). - -Set the prompt: -```bash -cat > /tmp/export-prompt.md << 'EOF' -[COMBINED PROMPT WITH REVIEW CRITERIA] -EOF - -$FLOWCTL rp prompt-set --window "$W" --tab "$T" --message-file /tmp/export-prompt.md -``` - -### Step 6: Export - -```bash -OUTPUT_FILE=~/Desktop/review-export-$(date +%Y%m%d-%H%M%S).md -$FLOWCTL rp prompt-export --window "$W" --tab "$T" --out "$OUTPUT_FILE" -open "$OUTPUT_FILE" -``` - -### Step 7: Inform User - -``` -Exported review context to: $OUTPUT_FILE - -The file contains: -- Full file tree with selected files marked -- Code maps (signatures/structure) -- Complete file contents -- Review prompt with Carmack-level criteria - -Paste into ChatGPT Pro, Claude web, or your preferred LLM. -After receiving feedback, return here to implement fixes. -``` - -## Note - -This skill is for **manual** external review only. It does not work with Ralph autonomous mode (no receipts, no status updates). diff --git a/plugins/flow-next/skills/flow-next-impl-review/SKILL.md b/plugins/flow-next/skills/flow-next-impl-review/SKILL.md deleted file mode 100644 index aa81490..0000000 --- a/plugins/flow-next/skills/flow-next-impl-review/SKILL.md +++ /dev/null @@ -1,170 +0,0 @@ ---- -name: flow-next-impl-review -description: John Carmack-level implementation review via RepoPrompt or Codex. Use when reviewing code changes, PRs, or implementations. Triggers on /flow-next:impl-review. ---- - -# Implementation Review Mode - -**Read [workflow.md](workflow.md) for detailed phases and anti-patterns.** - -Conduct a John Carmack-level review of implementation changes on the current branch. - -**Role**: Code Review Coordinator (NOT the reviewer) -**Backends**: RepoPrompt (rp) or Codex CLI (codex) - -**CRITICAL: flowctl is BUNDLED — NOT installed globally.** `which flowctl` will fail (expected). Always use: -```bash -FLOWCTL="${CLAUDE_PLUGIN_ROOT}/scripts/flowctl" -``` - -## Backend Selection - -**Priority** (first match wins): -1. `--review=rp|codex|export|none` argument -2. `FLOW_REVIEW_BACKEND` env var (`rp`, `codex`, `none`) -3. `.flow/config.json` → `review.backend` -4. Interactive prompt if both rp-cli and codex available (and not in Ralph mode) -5. Default: whichever is available (rp preferred) - -### Parse from arguments first - -Check $ARGUMENTS for: -- `--review=rp` or `--review rp` → use rp -- `--review=codex` or `--review codex` → use codex -- `--review=export` or `--review export` → use export -- `--review=none` or `--review none` → skip review - -If found, use that backend and skip all other detection. - -### Otherwise detect - -```bash -# Check available backends -HAVE_RP=$(which rp-cli >/dev/null 2>&1 && echo 1 || echo 0) -HAVE_CODEX=$(which codex >/dev/null 2>&1 && echo 1 || echo 0) - -# Get configured backend -BACKEND="${FLOW_REVIEW_BACKEND:-}" -if [[ -z "$BACKEND" ]]; then - BACKEND="$($FLOWCTL config get review.backend 2>/dev/null | jq -r '.value // empty')" -fi -``` - -### If no backend configured and both available - -If `BACKEND` is empty AND both `HAVE_RP=1` and `HAVE_CODEX=1`, AND not in Ralph mode (`FLOW_RALPH` not set): - -Output this question as text (do NOT use AskUserQuestion tool): -``` -Which review backend? -a) Codex CLI (cross-platform, GPT 5.2 High) -b) RepoPrompt (macOS, visual builder) - -(Reply: "a", "codex", "b", "rp", or just tell me) -``` - -Wait for response. Parse naturally. - -**Default if empty/ambiguous**: `codex` - -### If only one available or in Ralph mode - -```bash -# Fallback to available -if [[ -z "$BACKEND" ]]; then - if [[ "$HAVE_RP" == "1" ]]; then BACKEND="rp" - elif [[ "$HAVE_CODEX" == "1" ]]; then BACKEND="codex" - else BACKEND="none"; fi -fi -``` - -## Critical Rules - -**For rp backend:** -1. **DO NOT REVIEW CODE YOURSELF** - you coordinate, RepoPrompt reviews -2. **MUST WAIT for actual RP response** - never simulate/skip the review -3. **MUST use `setup-review`** - handles window selection + builder atomically -4. **DO NOT add --json flag to chat-send** - it suppresses the review response -5. **Re-reviews MUST stay in SAME chat** - omit `--new-chat` after first review - -**For codex backend:** -1. Use `$FLOWCTL codex impl-review` exclusively -2. Pass `--receipt` for session continuity on re-reviews -3. Parse verdict from command output - -**For all backends:** -- If `REVIEW_RECEIPT_PATH` set: write receipt after review (any verdict) -- Any failure → output `RETRY` and stop - -**FORBIDDEN**: -- Self-declaring SHIP without actual backend verdict -- Mixing backends mid-review (stick to one) -- Skipping review when backend is "none" without user consent - -## Input - -Arguments: $ARGUMENTS -Format: `[focus areas or task ID]` - -Reviews all changes on **current branch** vs main/master. - -## Workflow - -**See [workflow.md](workflow.md) for full details on each backend.** - -```bash -FLOWCTL="${CLAUDE_PLUGIN_ROOT}/scripts/flowctl" -REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" -``` - -### Step 0: Detect Backend - -Run backend detection from SKILL.md above. Then branch: - -### Codex Backend - -```bash -TASK_ID="${1:-}" -BASE_BRANCH="main" -RECEIPT_PATH="${REVIEW_RECEIPT_PATH:-/tmp/impl-review-receipt.json}" - -$FLOWCTL codex impl-review "$TASK_ID" --base "$BASE_BRANCH" --receipt "$RECEIPT_PATH" -# Output includes VERDICT=SHIP|NEEDS_WORK|MAJOR_RETHINK -``` - -On NEEDS_WORK: fix code, commit, re-run (receipt enables session continuity). - -### RepoPrompt Backend - -```bash -# Step 1: Identify changes -git branch --show-current -git log main..HEAD --oneline 2>/dev/null || git log master..HEAD --oneline -git diff main..HEAD --name-only 2>/dev/null || git diff master..HEAD --name-only - -# Step 2: Atomic setup -eval "$($FLOWCTL rp setup-review --repo-root "$REPO_ROOT" --summary "Review implementation: ")" -# Outputs W= T=. If fails → RETRY - -# Step 3: Augment selection -$FLOWCTL rp select-add --window "$W" --tab "$T" path/to/changed/files... - -# Step 4: Build and send review prompt (see workflow.md) -$FLOWCTL rp chat-send --window "$W" --tab "$T" --message-file /tmp/review-prompt.md --new-chat --chat-name "Impl Review: [BRANCH]" - -# Step 5: Write receipt if REVIEW_RECEIPT_PATH set -``` - -## Fix Loop (INTERNAL - do not exit to Ralph) - -If verdict is NEEDS_WORK, loop internally until SHIP: - -1. **Parse issues** from reviewer feedback (Critical → Major → Minor) -2. **Fix code** and run tests/lints -3. **Commit fixes** (mandatory before re-review) -4. **Re-review**: - - **Codex**: Re-run `flowctl codex impl-review` (receipt enables context) - - **RP**: `$FLOWCTL rp chat-send --window "$W" --tab "$T" --message-file /tmp/re-review.md` (NO `--new-chat`) -5. **Repeat** until `SHIP` - -**CRITICAL**: For RP, re-reviews must stay in the SAME chat so reviewer has context. Only use `--new-chat` on the FIRST review. diff --git a/plugins/flow-next/skills/flow-next-impl-review/flowctl-reference.md b/plugins/flow-next/skills/flow-next-impl-review/flowctl-reference.md deleted file mode 100644 index 59f0067..0000000 --- a/plugins/flow-next/skills/flow-next-impl-review/flowctl-reference.md +++ /dev/null @@ -1,54 +0,0 @@ -# RepoPrompt Wrapper Reference - -Use `flowctl rp` wrappers only. - -## Primary Command (Ralph mode) - -```bash -# Atomic setup: pick-window + builder -eval "$($FLOWCTL rp setup-review --repo-root "$REPO_ROOT" --summary "...")" -# Returns: W= T= -``` - -This is the **only** way to initialize a review session. Do not call `pick-window` or `builder` individually. - -## Post-Setup Commands - -After `setup-review`, use these with `$W` and `$T`: - -```bash -# Get/modify selection -flowctl rp select-get --window "$W" --tab "$T" -flowctl rp select-add --window "$W" --tab "$T" path/to/file - -# Get/set prompt -flowctl rp prompt-get --window "$W" --tab "$T" -flowctl rp prompt-set --window "$W" --tab "$T" --message-file /tmp/prompt.md - -# Execute review -flowctl rp chat-send --window "$W" --tab "$T" --message-file /tmp/review.md --new-chat --chat-name "Review: X" - -# Export (non-Ralph) -flowctl rp prompt-export --window "$W" --tab "$T" --out ~/Desktop/export.md -``` - -## Key Rules - -1. **Always use setup-review first** - handles window selection atomically -2. **Always pass --window and --tab** - required for all post-setup commands -3. **--window must be numeric** - comes from setup-review output -4. **Use --message-file (not --message)** - write prompt to file first, then pass path - -## Common Mistakes - -- `--message "text"` → WRONG, use `--message-file /path/to/file` -- `setup-review ` → WRONG, use `setup-review --repo-root ... --summary ...` -- `select-add --paths ...` → WRONG, use `select-add --window "$W" --tab "$T" ` -- `chat-send --json` → WRONG, suppresses review text; if you see `{"chat": null}` you used --json incorrectly - -## Re-Review Rule (CRITICAL) - -First review: `chat-send ... --new-chat --chat-name "..."` -Re-reviews: `chat-send ... --message-file /tmp/re-review.md` (NO --new-chat) - -**Why**: Reviewer needs context from previous feedback. New chat = lost context = reviewer repeats same issues. diff --git a/plugins/flow-next/skills/flow-next-impl-review/workflow.md b/plugins/flow-next/skills/flow-next-impl-review/workflow.md deleted file mode 100644 index 493f6fb..0000000 --- a/plugins/flow-next/skills/flow-next-impl-review/workflow.md +++ /dev/null @@ -1,302 +0,0 @@ -# Implementation Review Workflow - -## Philosophy - -The reviewer model only sees selected files. RepoPrompt's Builder discovers context you'd miss (rp backend). Codex uses context hints from flowctl (codex backend). - ---- - -## Phase 0: Backend Detection - -**Run this first. Do not skip.** - -**CRITICAL: flowctl is BUNDLED — NOT installed globally.** `which flowctl` will fail (expected). Always use: - -```bash -set -e -FLOWCTL="${CLAUDE_PLUGIN_ROOT}/scripts/flowctl" -REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" - -# Check available backends -HAVE_RP=$(which rp-cli >/dev/null 2>&1 && echo 1 || echo 0) -HAVE_CODEX=$(which codex >/dev/null 2>&1 && echo 1 || echo 0) - -# Get configured backend (priority: env > config) -BACKEND="${FLOW_REVIEW_BACKEND:-}" -if [[ -z "$BACKEND" ]]; then - BACKEND="$($FLOWCTL config get review.backend 2>/dev/null | jq -r '.value // empty' 2>/dev/null || echo "")" -fi - -# Fallback to available (rp preferred) -if [[ -z "$BACKEND" ]]; then - if [[ "$HAVE_RP" == "1" ]]; then BACKEND="rp" - elif [[ "$HAVE_CODEX" == "1" ]]; then BACKEND="codex" - else BACKEND="none"; fi -fi - -echo "Review backend: $BACKEND" -``` - -**If backend is "none"**: Skip review, inform user, and exit cleanly (no error). - -**Then branch to backend-specific workflow below.** - ---- - -## Codex Backend Workflow - -Use when `BACKEND="codex"`. - -### Step 1: Identify Task and Base Branch - -```bash -BRANCH="$(git branch --show-current)" -BASE_BRANCH="main" -git log ${BASE_BRANCH}..HEAD --oneline 2>/dev/null || BASE_BRANCH="master" - -# Parse task ID from arguments if provided -TASK_ID="${1:-}" -``` - -### Step 2: Execute Review - -```bash -RECEIPT_PATH="${REVIEW_RECEIPT_PATH:-/tmp/impl-review-receipt.json}" - -$FLOWCTL codex impl-review "$TASK_ID" --base "$BASE_BRANCH" --receipt "$RECEIPT_PATH" -``` - -**Output includes `VERDICT=SHIP|NEEDS_WORK|MAJOR_RETHINK`.** - -### Step 3: Handle Verdict - -If `VERDICT=NEEDS_WORK`: -1. Parse issues from output -2. Fix code and run tests -3. Commit fixes -4. Re-run step 2 (receipt enables session continuity) -5. Repeat until SHIP - -### Step 4: Receipt - -Receipt is written automatically by `flowctl codex impl-review` when `--receipt` provided. -Format: `{"mode":"codex","task":"","verdict":"","session_id":"","timestamp":"..."}` - ---- - -## RepoPrompt Backend Workflow - -Use when `BACKEND="rp"`. - -### Atomic Setup Block - -```bash -# Atomic: pick-window + builder -eval "$($FLOWCTL rp setup-review --repo-root "$REPO_ROOT" --summary "Review implementation of on current branch")" - -# Verify we have W and T -if [[ -z "${W:-}" || -z "${T:-}" ]]; then - echo "RETRY" - exit 0 -fi - -echo "Setup complete: W=$W T=$T" -``` - -If this block fails, output `RETRY` and stop. Do not improvise. - ---- - -## Phase 1: Identify Changes (RP) - -```bash -BRANCH="$(git branch --show-current)" -git log main..HEAD --oneline 2>/dev/null || git log master..HEAD --oneline -CHANGED_FILES="$(git diff main..HEAD --name-only 2>/dev/null || git diff master..HEAD --name-only)" -git diff main..HEAD --stat 2>/dev/null || git diff master..HEAD --stat -``` - -Save: -- Branch name -- Changed files list -- Commit summary - -Compose a 1-2 sentence summary for the setup-review command. - ---- - -## Phase 2: Augment Selection (RP) - -Builder selects context automatically. Review and add must-haves: - -```bash -# See what builder selected -$FLOWCTL rp select-get --window "$W" --tab "$T" - -# Add ALL changed files -for f in $CHANGED_FILES; do - $FLOWCTL rp select-add --window "$W" --tab "$T" "$f" -done - -# Add task spec if known -$FLOWCTL rp select-add --window "$W" --tab "$T" .flow/specs/.md -``` - -**Why this matters:** Chat only sees selected files. - ---- - -## Phase 3: Execute Review (RP) - -### Build combined prompt - -Get builder's handoff: -```bash -HANDOFF="$($FLOWCTL rp prompt-get --window "$W" --tab "$T")" -``` - -Write combined prompt: -```bash -cat > /tmp/review-prompt.md << 'EOF' -[PASTE HANDOFF HERE] - ---- - -## IMPORTANT: File Contents -RepoPrompt includes the actual source code of selected files in a `` XML section at the end of this message. You MUST: -1. Locate the `` section -2. Read and analyze the actual source code within it -3. Base your review on the code, not summaries or descriptions - -If you cannot find ``, ask for the files to be re-attached before proceeding. - -## Changes Under Review -Branch: [BRANCH_NAME] -Files: [LIST CHANGED FILES] -Commits: [COMMIT SUMMARY] - -## Original Spec -[PASTE flowctl show OUTPUT if known] - -## Review Focus -[USER'S FOCUS AREAS] - -## Review Criteria - -Conduct a John Carmack-level review: - -1. **Correctness** - Matches spec? Logic errors? -2. **Simplicity** - Simplest solution? Over-engineering? -3. **DRY** - Duplicated logic? Existing patterns? -4. **Architecture** - Data flow? Clear boundaries? -5. **Edge Cases** - Failure modes? Race conditions? -6. **Tests** - Adequate coverage? Testing behavior? -7. **Security** - Injection? Auth gaps? - -## Output Format - -For each issue: -- **Severity**: Critical / Major / Minor / Nitpick -- **File:Line**: Exact location -- **Problem**: What's wrong -- **Suggestion**: How to fix - -**REQUIRED**: You MUST end your response with exactly one verdict tag. This is mandatory: -`SHIP` or `NEEDS_WORK` or `MAJOR_RETHINK` - -Do NOT skip this tag. The automation depends on it. -EOF -``` - -### Send to RepoPrompt - -```bash -$FLOWCTL rp chat-send --window "$W" --tab "$T" --message-file /tmp/review-prompt.md --new-chat --chat-name "Impl Review: $BRANCH" -``` - -**WAIT** for response. Takes 1-5+ minutes. - ---- - -## Phase 4: Receipt + Status (RP) - -### Write receipt (if REVIEW_RECEIPT_PATH set) - -```bash -if [[ -n "${REVIEW_RECEIPT_PATH:-}" ]]; then - ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)" - mkdir -p "$(dirname "$REVIEW_RECEIPT_PATH")" - cat > "$REVIEW_RECEIPT_PATH" <","mode":"rp","timestamp":"$ts"} -EOF - echo "REVIEW_RECEIPT_WRITTEN: $REVIEW_RECEIPT_PATH" -fi -``` - -If no verdict tag in response, output `RETRY` and stop. - ---- - -## Fix Loop (RP) - -**CRITICAL: You MUST fix the code BEFORE re-reviewing. Never re-review without making changes.** - -If verdict is NEEDS_WORK: - -1. **Parse issues** - Extract ALL issues by severity (Critical → Major → Minor) -2. **Fix the code** - Address each issue in order -3. **Run tests/lints** - Verify fixes don't break anything -4. **Commit fixes** (MANDATORY before re-review): - ```bash - git add -A - git commit -m "fix: address review feedback" - ``` - **If you skip this and re-review without committing changes, reviewer will return NEEDS_WORK again.** - -5. **Re-review with fix summary** (only AFTER step 4): - - **IMPORTANT**: Do NOT re-add files already in the selection. RepoPrompt auto-refreshes - file contents on every message. Only use `select-add` for NEW files created during fixes: - ```bash - # Only if fixes created new files not in original selection - if [[ -n "$NEW_FILES" ]]; then - $FLOWCTL rp select-add --window "$W" --tab "$T" $NEW_FILES - fi - ``` - - Then send re-review request (NO --new-chat, stay in same chat). - - **Keep this message minimal. Do NOT enumerate issues or reference file_contents - the reviewer already has context from the previous exchange.** - - ```bash - cat > /tmp/re-review.md << 'EOF' - All issues from your previous review have been addressed. Please verify the updated implementation and provide final verdict. - - **REQUIRED**: End with `SHIP` or `NEEDS_WORK` or `MAJOR_RETHINK` - EOF - - $FLOWCTL rp chat-send --window "$W" --tab "$T" --message-file /tmp/re-review.md - ``` -6. **Repeat** until Ship - -**Anti-pattern**: Re-adding already-selected files before re-review. RP auto-refreshes; re-adding can cause issues. - ---- - -## Anti-patterns - -**All backends:** -- **Reviewing yourself** - You coordinate; the backend reviews -- **No receipt** - If REVIEW_RECEIPT_PATH is set, you MUST write receipt -- **Ignoring verdict** - Must extract and act on verdict tag -- **Mixing backends** - Stick to one backend for the entire review session - -**RP backend only:** -- **Calling builder directly** - Must use `setup-review` which wraps it -- **Skipping setup-review** - Window selection MUST happen via this command -- **Hard-coding window IDs** - Never write `--window 1` -- **Missing changed files** - Add ALL changed files to selection - -**Codex backend only:** -- **Using `--last` flag** - Conflicts with parallel usage; use `--receipt` instead -- **Direct codex calls** - Must use `flowctl codex` wrappers diff --git a/plugins/flow-next/skills/flow-next-interview/SKILL.md b/plugins/flow-next/skills/flow-next-interview/SKILL.md deleted file mode 100644 index 8d0a771..0000000 --- a/plugins/flow-next/skills/flow-next-interview/SKILL.md +++ /dev/null @@ -1,153 +0,0 @@ ---- -name: flow-next-interview -description: Interview user in-depth about an epic, task, or spec file to extract complete implementation details. Use when user wants to flesh out a spec, refine requirements, or clarify a feature before building. Triggers on /flow-next:interview with Flow IDs (fn-1, fn-1.2) or file paths. ---- - -# Flow interview - -Conduct an extremely thorough interview about a task/spec and write refined details back. - -**IMPORTANT**: This plugin uses `.flow/` for ALL task tracking. Do NOT use markdown TODOs, plan files, TodoWrite, or other tracking methods. All task state must be read and written via `flowctl`. - -**CRITICAL: flowctl is BUNDLED — NOT installed globally.** `which flowctl` will fail (expected). Always use: -```bash -FLOWCTL="${CLAUDE_PLUGIN_ROOT}/scripts/flowctl" -$FLOWCTL -``` - -**Role**: technical interviewer, spec refiner -**Goal**: extract complete implementation details through deep questioning (40+ questions typical) - -## Input - -Full request: $ARGUMENTS - -Accepts: -- **Flow epic ID** `fn-N`: Fetch with `flowctl show`, write back with `flowctl epic set-plan` -- **Flow task ID** `fn-N.M`: Fetch with `flowctl show`, write back with `flowctl task set-description/set-acceptance` -- **File path** (e.g., `docs/spec.md`): Read file, interview, rewrite file -- **Empty**: Prompt for target - -Examples: -- `/flow-next:interview fn-1` -- `/flow-next:interview fn-1.3` -- `/flow-next:interview docs/oauth-spec.md` - -If empty, ask: "What should I interview you about? Give me a Flow ID (e.g., fn-1) or file path (e.g., docs/spec.md)" - -## Setup - -```bash -FLOWCTL="${CLAUDE_PLUGIN_ROOT}/scripts/flowctl" -``` - -## Detect Input Type - -1. **Flow epic ID pattern**: matches `fn-\d+` (e.g., fn-1, fn-12) - - Fetch: `$FLOWCTL show --json` - - Read spec: `$FLOWCTL cat ` - -2. **Flow task ID pattern**: matches `fn-\d+\.\d+` (e.g., fn-1.3, fn-12.5) - - Fetch: `$FLOWCTL show --json` - - Read spec: `$FLOWCTL cat ` - - Also get epic context: `$FLOWCTL cat ` - -3. **File path**: anything else with a path-like structure or .md extension - - Read file contents - - If file doesn't exist, ask user to provide valid path - -## Interview Process - -**CRITICAL REQUIREMENT**: You MUST use the `AskUserQuestion` tool for every question. - -- DO NOT output questions as text -- DO NOT list questions in your response -- ONLY ask questions via AskUserQuestion tool calls -- Group 2-4 related questions per tool call -- Expect 40+ questions total for complex specs - -**Anti-pattern (WRONG)**: -``` -Question 1: What database should we use? -Options: a) PostgreSQL b) SQLite c) MongoDB -``` - -**Correct pattern**: Call AskUserQuestion tool with question and options. - -## Question Categories - -Read [questions.md](questions.md) for all question categories and interview guidelines. - -## Write Refined Spec - -After interview complete, write everything back. - -### For Flow Epic ID - -Update epic spec using stdin heredoc (preferred) or temp file: -```bash -# Preferred: stdin heredoc (no temp file) -$FLOWCTL epic set-plan --file - --json <<'EOF' -# Epic Title - -## Problem -Clear problem statement - -## Approach -Technical approach with specifics, key decisions from interview - -## Edge Cases -- Edge case 1 -- Edge case 2 - -## Quick commands -```bash -# smoke test command -``` - -## Acceptance -- [ ] Criterion 1 -- [ ] Criterion 2 -EOF -``` - -Create/update tasks if interview revealed breakdown: -```bash -$FLOWCTL task create --epic --title "..." --json -# Use set-spec for combined description + acceptance (fewer writes) -$FLOWCTL task set-spec --description /tmp/desc.md --acceptance /tmp/acc.md --json -``` - -### For Flow Task ID - -Update task using combined set-spec (preferred) or separate calls: -```bash -# Preferred: combined set-spec (2 writes instead of 4) -$FLOWCTL task set-spec --description /tmp/desc.md --acceptance /tmp/acc.md --json - -# Or use stdin for description only: -$FLOWCTL task set-description --file - --json <<'EOF' -Clear task description with technical details and edge cases from interview -EOF -``` - -### For File Path - -Rewrite the file with refined spec: -- Preserve any existing structure/format -- Add sections for areas covered in interview -- Include technical details, edge cases, acceptance criteria -- Keep it actionable and specific - -## Completion - -Show summary: -- Number of questions asked -- Key decisions captured -- What was written (Flow ID updated / file rewritten) -- Suggest next step: `/flow-next:plan` or `/flow-next:work` - -## Notes - -- This process should feel thorough - user should feel they've thought through everything -- Quality over speed - don't rush to finish diff --git a/plugins/flow-next/skills/flow-next-interview/questions.md b/plugins/flow-next/skills/flow-next-interview/questions.md deleted file mode 100644 index cbd0e6d..0000000 --- a/plugins/flow-next/skills/flow-next-interview/questions.md +++ /dev/null @@ -1,82 +0,0 @@ -# Interview Question Categories - -Ask NON-OBVIOUS questions only. Expect 40+ questions for complex specs. - -## Technical Implementation - -- Data structures and algorithms -- Edge cases and boundary conditions -- State management approach -- Concurrency and race conditions - -## Architecture - -- Component boundaries and responsibilities -- Integration points with existing code -- Dependencies (internal and external) -- API contracts and interfaces - -## Error Handling & Failure Modes - -- What can go wrong? -- Recovery strategies -- Partial failure handling -- Timeout and retry logic - -## Performance - -- Expected load/scale -- Latency requirements -- Memory constraints -- Caching strategy - -## Security - -- Authentication/authorization -- Input validation -- Data sensitivity -- Attack vectors - -## User Experience - -- Loading states -- Error messages -- Offline behavior -- Accessibility - -## Testing Strategy - -- Unit test focus areas -- Integration test scenarios -- E2E critical paths -- Mocking strategy - -## Migration & Compatibility - -- Breaking changes -- Data migration -- Rollback plan -- Feature flags needed? - -## Acceptance Criteria - -- What does "done" look like? -- How to verify correctness? -- Performance benchmarks -- Edge cases to explicitly test - -## Unknowns & Risks - -- What are you most uncertain about? -- What could derail this? -- What needs research first? -- External dependencies - -## Interview Guidelines - -1. **Ask follow-up questions** based on answers - dig deep -2. **Don't ask obvious questions** - assume technical competence -3. **Continue until complete** - multiple rounds expected -4. **Group related questions** when possible (use multiSelect for non-exclusive options) -5. **Probe contradictions** - if answers don't align, clarify -6. **Surface hidden complexity** - ask about things user might not have considered diff --git a/plugins/flow-next/skills/flow-next-plan-review/SKILL.md b/plugins/flow-next/skills/flow-next-plan-review/SKILL.md deleted file mode 100644 index edda096..0000000 --- a/plugins/flow-next/skills/flow-next-plan-review/SKILL.md +++ /dev/null @@ -1,187 +0,0 @@ ---- -name: flow-next-plan-review -description: Carmack-level plan review via RepoPrompt or Codex. Use when reviewing Flow epic specs or design docs. Triggers on /flow-next:plan-review. ---- - -# Plan Review Mode - -**Read [workflow.md](workflow.md) for detailed phases and anti-patterns.** - -Conduct a John Carmack-level review of epic plans. - -**Role**: Code Review Coordinator (NOT the reviewer) -**Backends**: RepoPrompt (rp) or Codex CLI (codex) - -**CRITICAL: flowctl is BUNDLED — NOT installed globally.** `which flowctl` will fail (expected). Always use: -```bash -FLOWCTL="${CLAUDE_PLUGIN_ROOT}/scripts/flowctl" -``` - -## Backend Selection - -**Priority** (first match wins): -1. `--review=rp|codex|export|none` argument -2. `FLOW_REVIEW_BACKEND` env var (`rp`, `codex`, `none`) -3. `.flow/config.json` → `review.backend` -4. Interactive prompt if both rp-cli and codex available (and not in Ralph mode) -5. Default: whichever is available (rp preferred) - -### Parse from arguments first - -Check $ARGUMENTS for: -- `--review=rp` or `--review rp` → use rp -- `--review=codex` or `--review codex` → use codex -- `--review=export` or `--review export` → use export -- `--review=none` or `--review none` → skip review - -If found, use that backend and skip all other detection. - -### Otherwise detect - -```bash -# Check available backends -HAVE_RP=$(which rp-cli >/dev/null 2>&1 && echo 1 || echo 0) -HAVE_CODEX=$(which codex >/dev/null 2>&1 && echo 1 || echo 0) - -# Get configured backend -BACKEND="${FLOW_REVIEW_BACKEND:-}" -if [[ -z "$BACKEND" ]]; then - BACKEND="$($FLOWCTL config get review.backend 2>/dev/null | jq -r '.value // empty')" -fi -``` - -### If no backend configured and both available - -If `BACKEND` is empty AND both `HAVE_RP=1` and `HAVE_CODEX=1`, AND not in Ralph mode (`FLOW_RALPH` not set): - -Output this question as text (do NOT use AskUserQuestion tool): -``` -Which review backend? -a) Codex CLI (cross-platform, GPT 5.2 High) -b) RepoPrompt (macOS, visual builder) - -(Reply: "a", "codex", "b", "rp", or just tell me) -``` - -Wait for response. Parse naturally. - -**Default if empty/ambiguous**: `codex` - -### If only one available or in Ralph mode - -```bash -# Fallback to available -if [[ -z "$BACKEND" ]]; then - if [[ "$HAVE_RP" == "1" ]]; then BACKEND="rp" - elif [[ "$HAVE_CODEX" == "1" ]]; then BACKEND="codex" - else BACKEND="none"; fi -fi -``` - -## Critical Rules - -**For rp backend:** -1. **DO NOT REVIEW THE PLAN YOURSELF** - you coordinate, RepoPrompt reviews -2. **MUST WAIT for actual RP response** - never simulate/skip the review -3. **MUST use `setup-review`** - handles window selection + builder atomically -4. **DO NOT add --json flag to chat-send** - it suppresses the review response -5. **Re-reviews MUST stay in SAME chat** - omit `--new-chat` after first review - -**For codex backend:** -1. Use `$FLOWCTL codex plan-review` exclusively -2. Pass `--receipt` for session continuity on re-reviews -3. Parse verdict from command output - -**For all backends:** -- If `REVIEW_RECEIPT_PATH` set: write receipt after review (any verdict) -- Any failure → output `RETRY` and stop - -**FORBIDDEN**: -- Self-declaring SHIP without actual backend verdict -- Mixing backends mid-review (stick to one) -- Skipping review when backend is "none" without user consent - -## Input - -Arguments: $ARGUMENTS -Format: ` [focus areas]` - -## Workflow - -**See [workflow.md](workflow.md) for full details on each backend.** - -```bash -FLOWCTL="${CLAUDE_PLUGIN_ROOT}/scripts/flowctl" -REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" -``` - -### Step 0: Detect Backend - -Run backend detection from SKILL.md above. Then branch: - -### Codex Backend - -```bash -EPIC_ID="${1:-}" -RECEIPT_PATH="${REVIEW_RECEIPT_PATH:-/tmp/plan-review-receipt.json}" - -# Save checkpoint before review (recovery point if context compacts) -$FLOWCTL checkpoint save --epic "$EPIC_ID" --json - -$FLOWCTL codex plan-review "$EPIC_ID" --receipt "$RECEIPT_PATH" -# Output includes VERDICT=SHIP|NEEDS_WORK|MAJOR_RETHINK -``` - -On NEEDS_WORK: fix plan via `$FLOWCTL epic set-plan`, then re-run (receipt enables session continuity). - -### RepoPrompt Backend - -```bash -# Step 1: Get plan content -$FLOWCTL show --json -$FLOWCTL cat - -# Save checkpoint before review (recovery point if context compacts) -$FLOWCTL checkpoint save --epic --json - -# Step 2: Atomic setup -eval "$($FLOWCTL rp setup-review --repo-root "$REPO_ROOT" --summary "Review plan for : ")" -# Outputs W= T=. If fails → RETRY - -# Step 3: Augment selection -$FLOWCTL rp select-add --window "$W" --tab "$T" .flow/specs/.md - -# Step 4: Build and send review prompt (see workflow.md) -$FLOWCTL rp chat-send --window "$W" --tab "$T" --message-file /tmp/review-prompt.md --new-chat --chat-name "Plan Review: " - -# Step 5: Write receipt if REVIEW_RECEIPT_PATH set -# Step 6: Update status -$FLOWCTL epic set-plan-review-status --status ship --json -``` - -## Fix Loop (INTERNAL - do not exit to Ralph) - -If verdict is NEEDS_WORK, loop internally until SHIP: - -1. **Parse issues** from reviewer feedback -2. **Fix plan** (stdin preferred, temp file if content has single quotes): - ```bash - # Preferred: stdin heredoc - $FLOWCTL epic set-plan --file - --json <<'EOF' - - EOF - - # Or temp file - $FLOWCTL epic set-plan --file /tmp/updated-plan.md --json - ``` -3. **Re-review**: - - **Codex**: Re-run `flowctl codex plan-review` (receipt enables context) - - **RP**: `$FLOWCTL rp chat-send --window "$W" --tab "$T" --message-file /tmp/re-review.md` (NO `--new-chat`) -4. **Repeat** until `SHIP` - -**Recovery**: If context compaction occurred during review, restore from checkpoint: -```bash -$FLOWCTL checkpoint restore --epic --json -``` - -**CRITICAL**: For RP, re-reviews must stay in the SAME chat so reviewer has context. Only use `--new-chat` on the FIRST review. diff --git a/plugins/flow-next/skills/flow-next-plan-review/flowctl-reference.md b/plugins/flow-next/skills/flow-next-plan-review/flowctl-reference.md deleted file mode 100644 index 59f0067..0000000 --- a/plugins/flow-next/skills/flow-next-plan-review/flowctl-reference.md +++ /dev/null @@ -1,54 +0,0 @@ -# RepoPrompt Wrapper Reference - -Use `flowctl rp` wrappers only. - -## Primary Command (Ralph mode) - -```bash -# Atomic setup: pick-window + builder -eval "$($FLOWCTL rp setup-review --repo-root "$REPO_ROOT" --summary "...")" -# Returns: W= T= -``` - -This is the **only** way to initialize a review session. Do not call `pick-window` or `builder` individually. - -## Post-Setup Commands - -After `setup-review`, use these with `$W` and `$T`: - -```bash -# Get/modify selection -flowctl rp select-get --window "$W" --tab "$T" -flowctl rp select-add --window "$W" --tab "$T" path/to/file - -# Get/set prompt -flowctl rp prompt-get --window "$W" --tab "$T" -flowctl rp prompt-set --window "$W" --tab "$T" --message-file /tmp/prompt.md - -# Execute review -flowctl rp chat-send --window "$W" --tab "$T" --message-file /tmp/review.md --new-chat --chat-name "Review: X" - -# Export (non-Ralph) -flowctl rp prompt-export --window "$W" --tab "$T" --out ~/Desktop/export.md -``` - -## Key Rules - -1. **Always use setup-review first** - handles window selection atomically -2. **Always pass --window and --tab** - required for all post-setup commands -3. **--window must be numeric** - comes from setup-review output -4. **Use --message-file (not --message)** - write prompt to file first, then pass path - -## Common Mistakes - -- `--message "text"` → WRONG, use `--message-file /path/to/file` -- `setup-review ` → WRONG, use `setup-review --repo-root ... --summary ...` -- `select-add --paths ...` → WRONG, use `select-add --window "$W" --tab "$T" ` -- `chat-send --json` → WRONG, suppresses review text; if you see `{"chat": null}` you used --json incorrectly - -## Re-Review Rule (CRITICAL) - -First review: `chat-send ... --new-chat --chat-name "..."` -Re-reviews: `chat-send ... --message-file /tmp/re-review.md` (NO --new-chat) - -**Why**: Reviewer needs context from previous feedback. New chat = lost context = reviewer repeats same issues. diff --git a/plugins/flow-next/skills/flow-next-plan-review/workflow.md b/plugins/flow-next/skills/flow-next-plan-review/workflow.md deleted file mode 100644 index 6697908..0000000 --- a/plugins/flow-next/skills/flow-next-plan-review/workflow.md +++ /dev/null @@ -1,321 +0,0 @@ -# Plan Review Workflow - -## Philosophy - -The reviewer model only sees selected files. RepoPrompt's Builder discovers context you'd miss (rp backend). Codex uses context hints from flowctl (codex backend). - ---- - -## Phase 0: Backend Detection - -**Run this first. Do not skip.** - -**CRITICAL: flowctl is BUNDLED — NOT installed globally.** `which flowctl` will fail (expected). Always use: - -```bash -set -e -FLOWCTL="${CLAUDE_PLUGIN_ROOT}/scripts/flowctl" -REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" - -# Check available backends -HAVE_RP=$(which rp-cli >/dev/null 2>&1 && echo 1 || echo 0) -HAVE_CODEX=$(which codex >/dev/null 2>&1 && echo 1 || echo 0) - -# Get configured backend (priority: env > config) -BACKEND="${FLOW_REVIEW_BACKEND:-}" -if [[ -z "$BACKEND" ]]; then - BACKEND="$($FLOWCTL config get review.backend 2>/dev/null | jq -r '.value // empty' 2>/dev/null || echo "")" -fi - -# Fallback to available (rp preferred) -if [[ -z "$BACKEND" ]]; then - if [[ "$HAVE_RP" == "1" ]]; then BACKEND="rp" - elif [[ "$HAVE_CODEX" == "1" ]]; then BACKEND="codex" - else BACKEND="none"; fi -fi - -echo "Review backend: $BACKEND" -``` - -**If backend is "none"**: Skip review, inform user, and exit cleanly (no error). - -**Then branch to backend-specific workflow below.** - ---- - -## Codex Backend Workflow - -Use when `BACKEND="codex"`. - -### Step 0: Save Checkpoint - -**Before review** (protects against context compaction): -```bash -EPIC_ID="${1:-}" -$FLOWCTL checkpoint save --epic "$EPIC_ID" --json -``` - -### Step 1: Execute Review - -```bash -RECEIPT_PATH="${REVIEW_RECEIPT_PATH:-/tmp/plan-review-receipt.json}" - -$FLOWCTL codex plan-review "$EPIC_ID" --receipt "$RECEIPT_PATH" -``` - -**Output includes `VERDICT=SHIP|NEEDS_WORK|MAJOR_RETHINK`.** - -### Step 2: Update Status - -```bash -# Based on verdict -$FLOWCTL epic set-plan-review-status "$EPIC_ID" --status ship --json -# OR -$FLOWCTL epic set-plan-review-status "$EPIC_ID" --status needs_work --json -``` - -### Step 3: Handle Verdict - -If `VERDICT=NEEDS_WORK`: -1. Parse issues from output -2. Fix plan via `$FLOWCTL epic set-plan` -3. Re-run step 1 (receipt enables session continuity) -4. Repeat until SHIP - -### Step 4: Receipt - -Receipt is written automatically by `flowctl codex plan-review` when `--receipt` provided. -Format: `{"mode":"codex","epic":"","verdict":"","session_id":"","timestamp":"..."}` - ---- - -## RepoPrompt Backend Workflow - -Use when `BACKEND="rp"`. - -### Atomic Setup Block - -```bash -# Atomic: pick-window + builder -eval "$($FLOWCTL rp setup-review --repo-root "$REPO_ROOT" --summary "Review plan for : ")" - -# Verify we have W and T -if [[ -z "${W:-}" || -z "${T:-}" ]]; then - echo "RETRY" - exit 0 -fi - -echo "Setup complete: W=$W T=$T" -``` - -If this block fails, output `RETRY` and stop. Do not improvise. - ---- - -## Phase 1: Read the Plan (RP) - -**If Flow issue:** -```bash -$FLOWCTL show --json -$FLOWCTL cat -``` - -Save output for inclusion in review prompt. Compose a 1-2 sentence summary for the setup-review command. - -**Save checkpoint** (protects against context compaction during review): -```bash -$FLOWCTL checkpoint save --epic --json -``` -This creates `.flow/.checkpoint-.json` with full state. If compaction occurs during review-fix cycles, restore with `$FLOWCTL checkpoint restore --epic `. - ---- - -## Phase 2: Augment Selection (RP) - -Builder selects context automatically. Review and add must-haves: - -```bash -# See what builder selected -$FLOWCTL rp select-get --window "$W" --tab "$T" - -# Always add the plan spec -$FLOWCTL rp select-add --window "$W" --tab "$T" .flow/specs/.md - -# Add PRD/architecture docs if found -$FLOWCTL rp select-add --window "$W" --tab "$T" docs/prd.md -``` - -**Why this matters:** Chat only sees selected files. - ---- - -## Phase 3: Execute Review (RP) - -### Build combined prompt - -Get builder's handoff: -```bash -HANDOFF="$($FLOWCTL rp prompt-get --window "$W" --tab "$T")" -``` - -Write combined prompt: -```bash -cat > /tmp/review-prompt.md << 'EOF' -[PASTE HANDOFF HERE] - ---- - -## IMPORTANT: File Contents -RepoPrompt includes the actual source code of selected files in a `` XML section at the end of this message. You MUST: -1. Locate the `` section -2. Read and analyze the actual source code within it -3. Base your review on the code, not summaries or descriptions - -If you cannot find ``, ask for the files to be re-attached before proceeding. - -## Plan Under Review -[PASTE flowctl show OUTPUT] - -## Review Focus -[USER'S FOCUS AREAS] - -## Review Criteria - -Conduct a John Carmack-level review: - -1. **Completeness** - All requirements covered? Missing edge cases? -2. **Feasibility** - Technically sound? Dependencies clear? -3. **Clarity** - Specs unambiguous? Acceptance criteria testable? -4. **Architecture** - Right abstractions? Clean boundaries? -5. **Risks** - Blockers identified? Security gaps? Mitigation? -6. **Scope** - Right-sized? Over/under-engineering? -7. **Testability** - How will we verify this works? - -## Output Format - -For each issue: -- **Severity**: Critical / Major / Minor / Nitpick -- **Location**: Which task or section -- **Problem**: What's wrong -- **Suggestion**: How to fix - -**REQUIRED**: You MUST end your response with exactly one verdict tag. This is mandatory: -`SHIP` or `NEEDS_WORK` or `MAJOR_RETHINK` - -Do NOT skip this tag. The automation depends on it. -EOF -``` - -### Send to RepoPrompt - -```bash -$FLOWCTL rp chat-send --window "$W" --tab "$T" --message-file /tmp/review-prompt.md --new-chat --chat-name "Plan Review: " -``` - -**WAIT** for response. Takes 1-5+ minutes. - ---- - -## Phase 4: Receipt + Status (RP) - -### Write receipt (if REVIEW_RECEIPT_PATH set) - -```bash -if [[ -n "${REVIEW_RECEIPT_PATH:-}" ]]; then - ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)" - mkdir -p "$(dirname "$REVIEW_RECEIPT_PATH")" - cat > "$REVIEW_RECEIPT_PATH" <","mode":"rp","timestamp":"$ts"} -EOF - echo "REVIEW_RECEIPT_WRITTEN: $REVIEW_RECEIPT_PATH" -fi -``` - -### Update status - -Extract verdict from response, then: -```bash -# If SHIP -$FLOWCTL epic set-plan-review-status --status ship --json - -# If NEEDS_WORK or MAJOR_RETHINK -$FLOWCTL epic set-plan-review-status --status needs_work --json -``` - -If no verdict tag, output `RETRY` and stop. - ---- - -## Fix Loop (RP) - -**CRITICAL: You MUST fix the plan BEFORE re-reviewing. Never re-review without making changes.** - -If verdict is NEEDS_WORK: - -1. **Parse issues** - Extract ALL issues by severity (Critical → Major → Minor) -2. **Fix the plan** - Address each issue. -3. **Update plan in flowctl** (MANDATORY before re-review): - ```bash - # Option A: stdin heredoc (preferred, no temp file) - $FLOWCTL epic set-plan --file - --json <<'EOF' - - EOF - - # Option B: temp file (if content has single quotes) - $FLOWCTL epic set-plan --file /tmp/updated-plan.md --json - ``` - **If you skip this step and re-review with same content, reviewer will return NEEDS_WORK again.** - - **Recovery**: If context compaction occurred, restore from checkpoint first: - ```bash - $FLOWCTL checkpoint restore --epic --json - ``` - -4. **Re-review with fix summary** (only AFTER step 3): - - **IMPORTANT**: Do NOT re-add files already in the selection. RepoPrompt auto-refreshes - file contents on every message. Only use `select-add` for NEW files created during fixes: - ```bash - # Only if fixes created new files not in original selection - if [[ -n "$NEW_FILES" ]]; then - $FLOWCTL rp select-add --window "$W" --tab "$T" $NEW_FILES - fi - ``` - - Then send re-review request (NO --new-chat, stay in same chat). - - **Keep this message minimal. Do NOT enumerate issues or reference file_contents - the reviewer already has context from the previous exchange.** - - ```bash - cat > /tmp/re-review.md << 'EOF' - All issues from your previous review have been addressed. Please verify the updated plan and provide final verdict. - - **REQUIRED**: End with `SHIP` or `NEEDS_WORK` or `MAJOR_RETHINK` - EOF - - $FLOWCTL rp chat-send --window "$W" --tab "$T" --message-file /tmp/re-review.md - ``` -5. **Repeat** until Ship - -**Anti-pattern**: Re-adding already-selected files before re-review. RP auto-refreshes; re-adding can cause issues. - -**Anti-pattern**: Re-reviewing without calling `epic set-plan` first. This wastes reviewer time and loops forever. - ---- - -## Anti-patterns - -**All backends:** -- **Reviewing yourself** - You coordinate; the backend reviews -- **No receipt** - If REVIEW_RECEIPT_PATH is set, you MUST write receipt -- **Ignoring verdict** - Must extract and act on verdict tag -- **Mixing backends** - Stick to one backend for the entire review session - -**RP backend only:** -- **Calling builder directly** - Must use `setup-review` which wraps it -- **Skipping setup-review** - Window selection MUST happen via this command -- **Hard-coding window IDs** - Never write `--window 1` - -**Codex backend only:** -- **Using `--last` flag** - Conflicts with parallel usage; use `--receipt` instead -- **Direct codex calls** - Must use `flowctl codex` wrappers diff --git a/plugins/flow-next/skills/flow-next-plan/SKILL.md b/plugins/flow-next/skills/flow-next-plan/SKILL.md deleted file mode 100644 index 19428f6..0000000 --- a/plugins/flow-next/skills/flow-next-plan/SKILL.md +++ /dev/null @@ -1,161 +0,0 @@ ---- -name: flow-next-plan -description: Create structured build plans from feature requests or Flow IDs. Use when planning features or designing implementation. Triggers on /flow-next:plan with text descriptions or Flow IDs (fn-1, fn-1.2). ---- - -# Flow plan - -Turn a rough idea into an epic with tasks in `.flow/`. This skill does not write code. - -Follow this skill and linked workflows exactly. Deviations cause drift, bad gates, retries, and user frustration. - -**IMPORTANT**: This plugin uses `.flow/` for ALL task tracking. Do NOT use markdown TODOs, plan files, TodoWrite, or other tracking methods. All task state must be read and written via `flowctl`. - -**CRITICAL: flowctl is BUNDLED — NOT installed globally.** `which flowctl` will fail (expected). Always use: -```bash -FLOWCTL="${CLAUDE_PLUGIN_ROOT}/scripts/flowctl" -$FLOWCTL -``` - -**Role**: product-minded planner with strong repo awareness. -**Goal**: produce an epic with tasks that match existing conventions and reuse points. -**Task size**: every task must fit one `/flow-next:work` iteration. If it won't, split it. - -## Input - -Full request: $ARGUMENTS - -Accepts: -- Feature/bug description in natural language -- Flow epic ID `fn-N` to refine existing epic -- Flow task ID `fn-N.M` to refine specific task -- Chained instructions like "then review with /flow-next:plan-review" - -Examples: -- `/flow-next:plan Add OAuth login for users` -- `/flow-next:plan fn-1` -- `/flow-next:plan fn-1 then review via /flow-next:plan-review` - -If empty, ask: "What should I plan? Give me the feature or bug in 1-5 sentences." - -## FIRST: Parse Options or Ask Questions - -Check available backends and configured preference: -```bash -HAVE_RP=$(which rp-cli >/dev/null 2>&1 && echo 1 || echo 0) -HAVE_CODEX=$(which codex >/dev/null 2>&1 && echo 1 || echo 0) - -# Check configured backend (priority: env > config) -CONFIGURED_BACKEND="${FLOW_REVIEW_BACKEND:-}" -if [[ -z "$CONFIGURED_BACKEND" ]]; then - CONFIGURED_BACKEND="$($FLOWCTL config get review.backend 2>/dev/null | jq -r '.value // empty')" -fi -``` - -### Option Parsing (skip questions if found in arguments) - -Parse the arguments for these patterns. If found, use them and skip questions: - -**Research approach** (only if rp-cli available): -- `--research=rp` or `--research rp` or "use rp" or "context-scout" or "use repoprompt" → context-scout -- `--research=grep` or `--research grep` or "use grep" or "repo-scout" or "fast" → repo-scout - -**Review mode**: -- `--review=codex` or "review with codex" or "codex review" or "use codex" → Codex CLI (GPT 5.2 High) -- `--review=rp` or "review with rp" or "rp chat" or "repoprompt review" → RepoPrompt chat (via `flowctl rp chat-send`) -- `--review=export` or "export review" or "external llm" → export for external LLM -- `--review=none` or `--no-review` or "no review" or "skip review" → no review - -### If options NOT found in arguments - -**Skip review question if**: Ralph mode (`FLOW_RALPH=1`) OR backend already configured (`CONFIGURED_BACKEND` not empty). In these cases, only ask research question (if rp-cli available): - -``` -Quick setup: Use RepoPrompt for deeper context? -a) Yes, context-scout (slower, thorough) -b) No, repo-scout (faster) - -(Reply: "a", "b", or just tell me) -``` - -If rp-cli not available, skip questions entirely and use defaults. - -**Otherwise**, output questions based on available backends (do NOT use AskUserQuestion tool): - -**If both rp-cli AND codex available:** -``` -Quick setup before planning: - -1. **Research approach** — Use RepoPrompt for deeper context? - a) Yes, context-scout (slower, thorough) - b) No, repo-scout (faster) - -2. **Review** — Run Carmack-level review after? - a) Yes, Codex CLI (cross-platform, GPT 5.2 High) - b) Yes, RepoPrompt chat (macOS, visual builder) - c) Yes, export for external LLM (ChatGPT, Claude web) - d) No - -(Reply: "1a 2a", "1b 2d", or just tell me naturally) -``` - -**If only rp-cli available:** -``` -Quick setup before planning: - -1. **Research approach** — Use RepoPrompt for deeper context? - a) Yes, context-scout (slower, thorough) - b) No, repo-scout (faster) - -2. **Review** — Run Carmack-level review after? - a) Yes, RepoPrompt chat - b) Yes, export for external LLM - c) No - -(Reply: "1a 2a", "1b 2c", or just tell me naturally) -``` - -**If only codex available:** -``` -Quick setup before planning: - -**Review** — Run Carmack-level review after? -a) Yes, Codex CLI (GPT 5.2 High) -b) Yes, export for external LLM -c) No - -(Reply: "a", "b", or just tell me naturally) -``` - -Wait for response. Parse naturally — user may reply terse ("1a 2b") or ramble via voice. - -**Defaults when empty/ambiguous:** -- Research = `grep` (repo-scout) -- Review = configured backend if set, else `codex` if available, else `rp` if available, else `none` - -If neither rp-cli nor codex available: skip review questions, use repo-scout, no review. - -**Defaults when no review backend available:** -- Research = `grep` -- Review = `none` - -## Workflow - -Read [steps.md](steps.md) and follow each step in order. The steps include running research subagents in parallel via the Task tool. -If user chose review: -- Option 2a: run `/flow-next:plan-review` after Step 4, fix issues until it passes -- Option 2b: run `/flow-next:plan-review` with export mode after Step 4 - -## Output - -All plans go into `.flow/`: -- Epic: `.flow/epics/fn-N.json` + `.flow/specs/fn-N.md` -- Tasks: `.flow/tasks/fn-N.M.json` + `.flow/tasks/fn-N.M.md` - -**Never write plan files outside `.flow/`. Never use TodoWrite for task tracking.** - -## Output rules - -- Only create/update epics and tasks via flowctl -- No code changes -- No plan files outside `.flow/` diff --git a/plugins/flow-next/skills/flow-next-plan/examples.md b/plugins/flow-next/skills/flow-next-plan/examples.md deleted file mode 100644 index 0b9583b..0000000 --- a/plugins/flow-next/skills/flow-next-plan/examples.md +++ /dev/null @@ -1,19 +0,0 @@ -# Flow Plan Examples - -## Example 1: Add OAuth login - -**Request**: Add OAuth login - -**Plan outline**: -- References: auth service, routes, session controller -- Reuse: existing token handler -- Acceptance: login, callback, failure cases - -## Example 2: Fix N+1 on dashboard - -**Request**: Fix N+1 on dashboard - -**Plan outline**: -- References: dashboard query, serializer -- Reuse: existing preload helper -- Acceptance: query count reduced, tests updated diff --git a/plugins/flow-next/skills/flow-next-plan/steps.md b/plugins/flow-next/skills/flow-next-plan/steps.md deleted file mode 100644 index 56b6ecd..0000000 --- a/plugins/flow-next/skills/flow-next-plan/steps.md +++ /dev/null @@ -1,218 +0,0 @@ -# Flow Plan Steps - -**IMPORTANT**: Steps 1-3 (research, gap analysis, depth) ALWAYS run regardless of input type. - -**CRITICAL**: If you are about to create: -- a markdown TODO list, -- a task list outside `.flow/`, -- or any plan files outside `.flow/`, - -**STOP** and instead: -- create/update tasks in `.flow/` using `flowctl`, -- record details in the epic/task spec markdown. - -## Success criteria - -- Plan references existing files/patterns with line refs -- Reuse points are explicit (centralized code called out) -- Acceptance checks are testable -- Tasks are small enough for one `/flow-next:work` iteration (split if not) -- Open questions are listed - -## Step 0: Initialize .flow - -**CRITICAL: flowctl is BUNDLED — NOT installed globally.** `which flowctl` will fail (expected). Always use: - -```bash -# Get flowctl path -FLOWCTL="${CLAUDE_PLUGIN_ROOT}/scripts/flowctl" - -# Ensure .flow exists -$FLOWCTL init --json -``` - -## Step 1: Fast research (parallel) - -**If input is a Flow ID** (fn-N or fn-N.M): First fetch it with `$FLOWCTL show --json` and `$FLOWCTL cat ` to get the request context. - -**Check if memory is enabled:** -```bash -$FLOWCTL config get memory.enabled --json -``` - -**Based on user's choice in SKILL.md setup:** - -**If user chose context-scout (RepoPrompt)**: -Run these subagents in parallel using the Task tool: -- Task flow-next:context-scout() - uses RepoPrompt builder for AI-powered file discovery -- Task flow-next:practice-scout() -- Task flow-next:docs-scout() -- Task flow-next:memory-scout() - **only if memory.enabled is true** - -**If user chose repo-scout (default/faster)** OR rp-cli unavailable: -Run these subagents in parallel using the Task tool: -- Task flow-next:repo-scout() - uses standard Grep/Glob/Read -- Task flow-next:practice-scout() -- Task flow-next:docs-scout() -- Task flow-next:memory-scout() - **only if memory.enabled is true** - -Must capture: -- File paths + line refs -- Existing centralized code to reuse -- Similar patterns / prior work -- External docs links -- Project conventions (CLAUDE.md, CONTRIBUTING, etc) -- Architecture patterns and data flow (especially with context-scout) - -## Step 2: Flow gap check - -Run the gap analyst subagent: -- Task flow-next:flow-gap-analyst(, research_findings) - -Fold gaps + questions into the plan. - -## Step 3: Pick depth - -Default to short unless complexity demands more. - -**SHORT** (bugs, small changes) -- Problem or goal -- Acceptance checks -- Key context - -**STANDARD** (most features) -- Overview + scope -- Approach -- Risks / dependencies -- Acceptance checks -- Test notes -- References - -**DEEP** (large/critical) -- Detailed phases -- Alternatives considered -- Non-functional targets -- Rollout/rollback -- Docs + metrics -- Risks + mitigations - -## Step 4: Write to .flow - -**Efficiency note**: Use stdin (`--file -`) with heredocs to avoid temp files. Use `task set-spec` to set description + acceptance in one call. - -**Route A - Input was an existing Flow ID**: - -1. If epic ID (fn-N): - ```bash - # Use stdin heredoc (no temp file needed) - $FLOWCTL epic set-plan --file - --json <<'EOF' - - EOF - ``` - - Create/update child tasks as needed - -2. If task ID (fn-N.M): - ```bash - # Combined set-spec: description + acceptance in one call - # Write to temp files only if content has single quotes - $FLOWCTL task set-spec --description /tmp/desc.md --acceptance /tmp/acc.md --json - ``` - -**Route B - Input was text (new idea)**: - -1. Create epic: - ```bash - $FLOWCTL epic create --title "" --json - ``` - This returns the epic ID (e.g., fn-1). - -2. Set epic branch_name (deterministic): - - Default: `fn-N` (use epic ID) - ```bash - $FLOWCTL epic set-branch --branch "" --json - ``` - - If user specified a branch, use that instead. - -3. Write epic spec (use stdin heredoc): - ```bash - # Include: Overview, Scope, Approach, Quick commands (REQUIRED), Acceptance, References - $FLOWCTL epic set-plan --file - --json <<'EOF' - # Epic Title - - ## Overview - ... - - ## Quick commands - ```bash - # At least one smoke test command - ``` - - ## Acceptance - ... - EOF - ``` - -4. Create child tasks: - ```bash - # For each task: - $FLOWCTL task create --epic --title "" --json - ``` - -5. Write task specs (use combined set-spec): - ```bash - # For each task - single call sets both sections - # Write description and acceptance to temp files, then: - $FLOWCTL task set-spec --description /tmp/desc.md --acceptance /tmp/acc.md --json - ``` - This reduces 4 atomic writes per task to 2. - -6. Add dependencies: - ```bash - # If task B depends on task A: - $FLOWCTL dep add --json - ``` - -7. Output current state: - ```bash - $FLOWCTL show --json - $FLOWCTL cat - ``` - -## Step 5: Validate - -```bash -$FLOWCTL validate --epic --json -``` - -Fix any errors before proceeding. - -## Step 6: Review (if chosen at start) - -If user chose "Yes" to review in SKILL.md setup question: -1. Invoke `/flow-next:plan-review` with the epic ID -2. If review returns "Needs Work" or "Major Rethink": - - **Re-anchor EVERY iteration** (do not skip): - ```bash - $FLOWCTL show --json - $FLOWCTL cat - ``` - - **Immediately fix the issues** (do NOT ask for confirmation — user already consented) - - Re-run `/flow-next:plan-review` -3. Repeat until review returns "Ship" - -**No human gates here** — the review-fix-review loop is fully automated. - -**Why re-anchor every iteration?** Per Anthropic's long-running agent guidance: context compresses, you forget details. Re-read before each fix pass. - -## Step 7: Offer next step - -Show the epic summary and suggest next actions: - -``` -Epic created: fn-N with M tasks. - -Next: -1) Start work: `/flow-next:work fn-N` -2) Refine via interview: `/flow-next:interview fn-N` -3) Review the plan: `/flow-next:plan-review fn-N` -``` diff --git a/plugins/flow-next/skills/flow-next-ralph-init/SKILL.md b/plugins/flow-next/skills/flow-next-ralph-init/SKILL.md deleted file mode 100644 index 847ece5..0000000 --- a/plugins/flow-next/skills/flow-next-ralph-init/SKILL.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -name: flow-next-ralph-init -description: Scaffold repo-local Ralph autonomous harness under scripts/ralph/. Use when user runs /flow-next:ralph-init. ---- - -# Ralph init - -Scaffold repo-local Ralph harness. Opt-in only. - -## Rules - -- Only create `scripts/ralph/` in the current repo. -- If `scripts/ralph/` already exists, stop and ask the user to remove it first. -- Copy templates from `templates/` into `scripts/ralph/`. -- Copy `flowctl` and `flowctl.py` from `${CLAUDE_PLUGIN_ROOT}/scripts/` into `scripts/ralph/`. -- Set executable bit on `scripts/ralph/ralph.sh`, `scripts/ralph/ralph_once.sh`, and `scripts/ralph/flowctl`. - -## Workflow - -1. Resolve repo root: `git rev-parse --show-toplevel` -2. Check `scripts/ralph/` does not exist. -3. Detect available review backends: - ```bash - HAVE_RP=$(which rp-cli >/dev/null 2>&1 && echo 1 || echo 0) - HAVE_CODEX=$(which codex >/dev/null 2>&1 && echo 1 || echo 0) - ``` -4. Determine review backend: - - If BOTH available, ask user (do NOT use AskUserQuestion tool): - ``` - Both RepoPrompt and Codex available. Which review backend? - a) RepoPrompt (macOS, visual builder) - b) Codex CLI (cross-platform, GPT 5.2 High) - - (Reply: "a", "rp", "b", "codex", or just tell me) - ``` - Wait for response. Default if empty/ambiguous: `rp` - - If only rp-cli available: use `rp` - - If only codex available: use `codex` - - If neither available: use `none` -5. Write `scripts/ralph/config.env` with: - - `PLAN_REVIEW=` and `WORK_REVIEW=` - - replace `{{PLAN_REVIEW}}` and `{{WORK_REVIEW}}` placeholders in the template -6. Copy templates and flowctl files. -7. Print next steps (run from terminal, NOT inside Claude Code): - - Edit `scripts/ralph/config.env` to customize settings - - `./scripts/ralph/ralph_once.sh` (one iteration, observe) - - `./scripts/ralph/ralph.sh` (full loop, AFK) - - Uninstall: `rm -rf scripts/ralph/` diff --git a/plugins/flow-next/skills/flow-next-ralph-init/templates/.gitignore b/plugins/flow-next/skills/flow-next-ralph-init/templates/.gitignore deleted file mode 100644 index 8c2014a..0000000 --- a/plugins/flow-next/skills/flow-next-ralph-init/templates/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -runs/ -*.log diff --git a/plugins/flow-next/skills/flow-next-ralph-init/templates/config.env b/plugins/flow-next/skills/flow-next-ralph-init/templates/config.env deleted file mode 100644 index b507866..0000000 --- a/plugins/flow-next/skills/flow-next-ralph-init/templates/config.env +++ /dev/null @@ -1,39 +0,0 @@ -# Ralph config (edit as needed) - -# Optional epic list (space or comma separated). Empty = scan all open epics. -EPICS= - -# Plan gate -REQUIRE_PLAN_REVIEW=0 -# PLAN_REVIEW options: rp (RepoPrompt, macOS), codex (cross-platform), none -PLAN_REVIEW={{PLAN_REVIEW}} - -# Work gate -# WORK_REVIEW options: rp (RepoPrompt, macOS), codex (cross-platform), none -WORK_REVIEW={{WORK_REVIEW}} - -# Work settings -BRANCH_MODE=new -MAX_ITERATIONS=25 -# MAX_TURNS= # optional; empty = no limit (Claude stops via promise tags) -MAX_ATTEMPTS_PER_TASK=5 - -# YOLO uses --dangerously-skip-permissions (required for unattended runs) -YOLO=1 - -# UI settings -# RALPH_UI=0 # set to 0 to disable colored output - -# Optional Claude flags (only used if set) -# FLOW_RALPH_CLAUDE_MODEL=claude-opus-4-5-20251101 -# FLOW_RALPH_CLAUDE_SESSION_ID= -# FLOW_RALPH_CLAUDE_PERMISSION_MODE=bypassPermissions -# FLOW_RALPH_CLAUDE_NO_SESSION_PERSISTENCE=0 -# FLOW_RALPH_CLAUDE_DEBUG=hooks -# FLOW_RALPH_CLAUDE_VERBOSE=1 -# FLOW_RALPH_CLAUDE_PLUGIN_DIR= # Use local dev plugin instead of cached (for testing) - -# Watch mode (command-line flags, not env vars): -# --watch Show tool calls in real-time (logs still captured) -# --watch verbose Show tool calls + model responses (logs still captured) -# Example: ./ralph.sh --watch diff --git a/plugins/flow-next/skills/flow-next-ralph-init/templates/prompt_plan.md b/plugins/flow-next/skills/flow-next-ralph-init/templates/prompt_plan.md deleted file mode 100644 index b5ef061..0000000 --- a/plugins/flow-next/skills/flow-next-ralph-init/templates/prompt_plan.md +++ /dev/null @@ -1,64 +0,0 @@ -You are running one Ralph plan gate iteration. - -Inputs: -- EPIC_ID={{EPIC_ID}} -- PLAN_REVIEW={{PLAN_REVIEW}} -- REQUIRE_PLAN_REVIEW={{REQUIRE_PLAN_REVIEW}} - -Treat the following as the user's exact input to flow-next-plan-review: -`{{EPIC_ID}} --review={{PLAN_REVIEW}}` - -Steps: -1) Re-anchor: - - scripts/ralph/flowctl show {{EPIC_ID}} --json - - scripts/ralph/flowctl cat {{EPIC_ID}} - - git status - - git log -10 --oneline - -Ralph mode rules (must follow): -- If PLAN_REVIEW=rp: use `flowctl rp` wrappers (setup-review, select-add, prompt-get, chat-send). -- If PLAN_REVIEW=opencode: use the task tool with subagent_type `opencode-reviewer`. -- Write receipt via bash heredoc (no Write tool) if REVIEW_RECEIPT_PATH is set. -- Do NOT run /flow-next:* as shell commands. -- If any rule is violated, output `RETRY` and stop. - -2) Plan review gate: - - Call the skill tool: flow-next-plan-review. - - Follow the workflow in the skill using the exact arguments above. - - Do NOT stop after loading the skill. - - For opencode: run reviewer via task tool and require `` tag. - - For rp: use flowctl rp wrappers (no --json, no --new-chat on re-review). - - For export: follow export flow in skill. - - If PLAN_REVIEW=none: - - If REQUIRE_PLAN_REVIEW=1: output `RETRY` and stop. - - Else: set ship and stop: - `scripts/ralph/flowctl epic set-plan-review-status {{EPIC_ID}} --status ship --json` - -3) The skill will loop internally until `SHIP`: - - First review uses `--new-chat` - - If NEEDS_WORK: skill fixes plan, re-reviews in SAME chat (no --new-chat) - - Repeats until SHIP - - Only returns to Ralph after SHIP or MAJOR_RETHINK - -4) IMMEDIATELY after SHIP verdict, write receipt (for any review mode != none): - ```bash - mkdir -p "$(dirname '{{REVIEW_RECEIPT_PATH}}')" - ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)" - cat > '{{REVIEW_RECEIPT_PATH}}' <FAIL` and stop - -7) On hard failure, output `FAIL` and stop. - -Do NOT output `COMPLETE` in this prompt. diff --git a/plugins/flow-next/skills/flow-next-ralph-init/templates/prompt_work.md b/plugins/flow-next/skills/flow-next-ralph-init/templates/prompt_work.md deleted file mode 100644 index 19ad3ed..0000000 --- a/plugins/flow-next/skills/flow-next-ralph-init/templates/prompt_work.md +++ /dev/null @@ -1,49 +0,0 @@ -You are running one Ralph work iteration. - -Inputs: -- TASK_ID={{TASK_ID}} -- BRANCH_MODE={{BRANCH_MODE_EFFECTIVE}} -- WORK_REVIEW={{WORK_REVIEW}} - -Treat the following as the user's exact input to flow-next-work: -`{{TASK_ID}} --branch={{BRANCH_MODE_EFFECTIVE}} --review={{WORK_REVIEW}}` - -## Steps (execute ALL in order) - -**Step 1: Execute task** -- Call the skill tool: flow-next-work. -- Follow the workflow in the skill using the exact arguments above. -- Do NOT run /flow-next:* as shell commands. -- Do NOT improvise review prompts; use the skill's review flow. - -**Step 2: Verify task done** (AFTER skill returns) -```bash -scripts/ralph/flowctl show {{TASK_ID}} --json -``` -If status != `done`, output `RETRY` and stop. - -**Step 3: Write impl receipt** (MANDATORY if WORK_REVIEW != none) -```bash -mkdir -p "$(dirname '{{REVIEW_RECEIPT_PATH}}')" -ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)" -cat > '{{REVIEW_RECEIPT_PATH}}' <FAIL` and stop. - -## Rules -- Must run `flowctl done` and verify task status is `done` before commit. -- Must `git add -A` (never list files). -- Do NOT use TodoWrite. - -Do NOT output `COMPLETE` in this prompt. diff --git a/plugins/flow-next/skills/flow-next-ralph-init/templates/ralph.sh b/plugins/flow-next/skills/flow-next-ralph-init/templates/ralph.sh deleted file mode 100644 index d4084a9..0000000 --- a/plugins/flow-next/skills/flow-next-ralph-init/templates/ralph.sh +++ /dev/null @@ -1,1012 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# ───────────────────────────────────────────────────────────────────────────── -# Windows / Git Bash hardening (GH-35) -# ───────────────────────────────────────────────────────────────────────────── -UNAME_S="$(uname -s 2>/dev/null || echo "")" -IS_WINDOWS=0 -case "$UNAME_S" in - MINGW*|MSYS*|CYGWIN*) IS_WINDOWS=1 ;; -esac - -# Python detection: prefer python3, fallback to python (common on Windows) -pick_python() { - if [[ -n "${PYTHON_BIN:-}" ]]; then - command -v "$PYTHON_BIN" >/dev/null 2>&1 && { echo "$PYTHON_BIN"; return; } - fi - if command -v python3 >/dev/null 2>&1; then echo "python3"; return; fi - if command -v python >/dev/null 2>&1; then echo "python"; return; fi - echo "" -} - -PYTHON_BIN="$(pick_python)" -[[ -n "$PYTHON_BIN" ]] || { echo "ralph: python not found (need python3 or python in PATH)" >&2; exit 1; } -export PYTHON_BIN - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -ROOT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)" -CONFIG="$SCRIPT_DIR/config.env" -FLOWCTL="$SCRIPT_DIR/flowctl" -FLOWCTL_PY="$SCRIPT_DIR/flowctl.py" - -fail() { echo "ralph: $*" >&2; exit 1; } -log() { - # Machine-readable logs: only show when UI disabled - [[ "${UI_ENABLED:-1}" != "1" ]] && echo "ralph: $*" - return 0 -} - -# Ensure flowctl is runnable even when NTFS exec bit / shebang handling is flaky on Windows -ensure_flowctl_wrapper() { - # If flowctl exists and is executable, use it - if [[ -f "$FLOWCTL" && -x "$FLOWCTL" ]]; then - return 0 - fi - - # On Windows or if flowctl not executable, create a wrapper that calls Python explicitly - if [[ -f "$FLOWCTL_PY" ]]; then - local wrapper="$SCRIPT_DIR/flowctl-wrapper.sh" - cat > "$wrapper" </dev/null 2>&1 || PY="python" -exec "\$PY" "\$DIR/flowctl.py" "\$@" -SH - chmod +x "$wrapper" 2>/dev/null || true - FLOWCTL="$wrapper" - export FLOWCTL - return 0 - fi - - fail "missing flowctl (expected $SCRIPT_DIR/flowctl or $SCRIPT_DIR/flowctl.py)" -} - -ensure_flowctl_wrapper - -# ───────────────────────────────────────────────────────────────────────────── -# Presentation layer (human-readable output) -# ───────────────────────────────────────────────────────────────────────────── -UI_ENABLED="${RALPH_UI:-1}" # set RALPH_UI=0 to disable - -# Timing -START_TIME="$(date +%s)" - -elapsed_time() { - local now elapsed mins secs - now="$(date +%s)" - elapsed=$((now - START_TIME)) - mins=$((elapsed / 60)) - secs=$((elapsed % 60)) - printf "%d:%02d" "$mins" "$secs" -} - -# Stats tracking -STATS_TASKS_DONE=0 - -# Colors (disabled if not tty or NO_COLOR set) -if [[ -t 1 && -z "${NO_COLOR:-}" ]]; then - C_RESET='\033[0m' - C_BOLD='\033[1m' - C_DIM='\033[2m' - C_BLUE='\033[34m' - C_GREEN='\033[32m' - C_YELLOW='\033[33m' - C_RED='\033[31m' - C_CYAN='\033[36m' - C_MAGENTA='\033[35m' -else - C_RESET='' C_BOLD='' C_DIM='' C_BLUE='' C_GREEN='' C_YELLOW='' C_RED='' C_CYAN='' C_MAGENTA='' -fi - -# Watch mode: "", "tools", "verbose" -WATCH_MODE="" - -ui() { - [[ "$UI_ENABLED" == "1" ]] || return 0 - echo -e "$*" -} - -# Get title from epic/task JSON -get_title() { - local json="$1" - "$PYTHON_BIN" - "$json" <<'PY' -import json, sys -try: - data = json.loads(sys.argv[1]) - print(data.get("title", "")[:40]) -except: - print("") -PY -} - -# Count progress (done/total tasks for scoped epics) -get_progress() { - "$PYTHON_BIN" - "$ROOT_DIR" "${EPICS_FILE:-}" <<'PY' -import json, sys -from pathlib import Path -root = Path(sys.argv[1]) -epics_file = sys.argv[2] if len(sys.argv) > 2 else "" -flow_dir = root / ".flow" - -# Get scoped epics or all -scoped = [] -if epics_file: - try: - scoped = json.load(open(epics_file))["epics"] - except: - pass - -epics_dir = flow_dir / "epics" -tasks_dir = flow_dir / "tasks" -if not epics_dir.exists(): - print("0|0|0|0") - sys.exit(0) - -epic_ids = [] -for f in sorted(epics_dir.glob("fn-*.json")): - eid = f.stem - if not scoped or eid in scoped: - epic_ids.append(eid) - -epics_done = sum(1 for e in epic_ids if json.load(open(epics_dir / f"{e}.json")).get("status") == "done") -tasks_total = 0 -tasks_done = 0 -if tasks_dir.exists(): - for tf in tasks_dir.glob("*.json"): - try: - t = json.load(open(tf)) - epic_id = tf.stem.rsplit(".", 1)[0] - if not scoped or epic_id in scoped: - tasks_total += 1 - if t.get("status") == "done": - tasks_done += 1 - except: - pass -print(f"{epics_done}|{len(epic_ids)}|{tasks_done}|{tasks_total}") -PY -} - -# Get git diff stats -get_git_stats() { - local base_branch="${1:-main}" - local stats - stats="$(git -C "$ROOT_DIR" diff --shortstat "$base_branch"...HEAD 2>/dev/null || true)" - if [[ -z "$stats" ]]; then - echo "" - return - fi - "$PYTHON_BIN" - "$stats" <<'PY' -import re, sys -s = sys.argv[1] -files = re.search(r"(\d+) files? changed", s) -ins = re.search(r"(\d+) insertions?", s) -dels = re.search(r"(\d+) deletions?", s) -f = files.group(1) if files else "0" -i = ins.group(1) if ins else "0" -d = dels.group(1) if dels else "0" -print(f"{f} files, +{i} -{d}") -PY -} - -ui_header() { - ui "" - ui "${C_BOLD}${C_BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C_RESET}" - ui "${C_BOLD}${C_BLUE} 🤖 Ralph Autonomous Loop${C_RESET}" - ui "${C_BOLD}${C_BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C_RESET}" -} - -ui_config() { - local git_branch progress_info epics_done epics_total tasks_done tasks_total - git_branch="$(git -C "$ROOT_DIR" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")" - progress_info="$(get_progress)" - IFS='|' read -r epics_done epics_total tasks_done tasks_total <<< "$progress_info" - - ui "" - ui "${C_DIM} Branch:${C_RESET} ${C_BOLD}$git_branch${C_RESET}" - ui "${C_DIM} Progress:${C_RESET} Epic ${epics_done}/${epics_total} ${C_DIM}•${C_RESET} Task ${tasks_done}/${tasks_total}" - - local plan_display="$PLAN_REVIEW" work_display="$WORK_REVIEW" - [[ "$PLAN_REVIEW" == "rp" ]] && plan_display="RepoPrompt" - [[ "$PLAN_REVIEW" == "opencode" ]] && plan_display="OpenCode" - [[ "$WORK_REVIEW" == "rp" ]] && work_display="RepoPrompt" - [[ "$WORK_REVIEW" == "opencode" ]] && work_display="OpenCode" - ui "${C_DIM} Reviews:${C_RESET} Plan=$plan_display ${C_DIM}•${C_RESET} Work=$work_display" - [[ -n "${EPICS:-}" ]] && ui "${C_DIM} Scope:${C_RESET} $EPICS" - ui "" -} - -ui_iteration() { - local iter="$1" status="$2" epic="${3:-}" task="${4:-}" title="" item_json="" - local elapsed - elapsed="$(elapsed_time)" - ui "" - ui "${C_BOLD}${C_CYAN}🔄 Iteration $iter${C_RESET} ${C_DIM}[${elapsed}]${C_RESET}" - if [[ "$status" == "plan" ]]; then - item_json="$("$FLOWCTL" show "$epic" --json 2>/dev/null || true)" - title="$(get_title "$item_json")" - ui " ${C_DIM}Epic:${C_RESET} ${C_BOLD}$epic${C_RESET} ${C_DIM}\"$title\"${C_RESET}" - ui " ${C_DIM}Phase:${C_RESET} ${C_YELLOW}Planning${C_RESET}" - elif [[ "$status" == "work" ]]; then - item_json="$("$FLOWCTL" show "$task" --json 2>/dev/null || true)" - title="$(get_title "$item_json")" - ui " ${C_DIM}Task:${C_RESET} ${C_BOLD}$task${C_RESET} ${C_DIM}\"$title\"${C_RESET}" - ui " ${C_DIM}Phase:${C_RESET} ${C_MAGENTA}Implementation${C_RESET}" - fi -} - -ui_plan_review() { - local mode="$1" epic="$2" - if [[ "$mode" == "rp" ]]; then - ui "" - ui " ${C_YELLOW}📝 Plan Review${C_RESET}" - ui " ${C_DIM}Sending to reviewer via RepoPrompt...${C_RESET}" - elif [[ "$mode" == "opencode" ]]; then - ui "" - ui " ${C_YELLOW}📝 Plan Review${C_RESET}" - ui " ${C_DIM}Sending to reviewer via OpenCode...${C_RESET}" - fi -} - -ui_impl_review() { - local mode="$1" task="$2" - if [[ "$mode" == "rp" ]]; then - ui "" - ui " ${C_MAGENTA}🔍 Implementation Review${C_RESET}" - ui " ${C_DIM}Sending to reviewer via RepoPrompt...${C_RESET}" - elif [[ "$mode" == "opencode" ]]; then - ui "" - ui " ${C_MAGENTA}🔍 Implementation Review${C_RESET}" - ui " ${C_DIM}Sending to reviewer via OpenCode...${C_RESET}" - fi -} - -ui_task_done() { - local task="$1" git_stats="" - STATS_TASKS_DONE=$((STATS_TASKS_DONE + 1)) - init_branches_file 2>/dev/null || true - local base_branch - base_branch="$(get_base_branch 2>/dev/null || echo "main")" - git_stats="$(get_git_stats "$base_branch")" - if [[ -n "$git_stats" ]]; then - ui " ${C_GREEN}✓${C_RESET} ${C_BOLD}$task${C_RESET} ${C_DIM}($git_stats)${C_RESET}" - else - ui " ${C_GREEN}✓${C_RESET} ${C_BOLD}$task${C_RESET}" - fi -} - -ui_retry() { - local task="$1" attempts="$2" max="$3" - ui " ${C_YELLOW}↻ Retry${C_RESET} ${C_DIM}(attempt $attempts/$max)${C_RESET}" -} - -ui_blocked() { - local task="$1" - ui " ${C_RED}🚫 Task blocked:${C_RESET} $task ${C_DIM}(max attempts reached)${C_RESET}" -} - -ui_complete() { - local elapsed progress_info epics_done epics_total tasks_done tasks_total - elapsed="$(elapsed_time)" - progress_info="$(get_progress)" - IFS='|' read -r epics_done epics_total tasks_done tasks_total <<< "$progress_info" - - ui "" - ui "${C_BOLD}${C_GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C_RESET}" - ui "${C_BOLD}${C_GREEN} ✅ Ralph Complete${C_RESET} ${C_DIM}[${elapsed}]${C_RESET}" - ui "" - ui " ${C_DIM}Tasks:${C_RESET} ${tasks_done}/${tasks_total} ${C_DIM}•${C_RESET} ${C_DIM}Epics:${C_RESET} ${epics_done}/${epics_total}" - ui "${C_BOLD}${C_GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C_RESET}" - ui "" -} - -ui_fail() { - local reason="${1:-}" elapsed - elapsed="$(elapsed_time)" - ui "" - ui "${C_BOLD}${C_RED}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C_RESET}" - ui "${C_BOLD}${C_RED} ❌ Ralph Failed${C_RESET} ${C_DIM}[${elapsed}]${C_RESET}" - [[ -n "$reason" ]] && ui " ${C_DIM}$reason${C_RESET}" - ui "${C_BOLD}${C_RED}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C_RESET}" - ui "" -} - -ui_waiting() { - ui " ${C_DIM}⏳ OpenCode working...${C_RESET}" -} - -[[ -f "$CONFIG" ]] || fail "missing config.env" -[[ -x "$FLOWCTL" ]] || fail "missing flowctl" - -# shellcheck disable=SC1090 -set -a -source "$CONFIG" -set +a - -MAX_ITERATIONS="${MAX_ITERATIONS:-25}" -MAX_TURNS="${MAX_TURNS:-}" # empty = no limit; unused for OpenCode (kept for parity) -MAX_ATTEMPTS_PER_TASK="${MAX_ATTEMPTS_PER_TASK:-5}" -WORKER_TIMEOUT="${WORKER_TIMEOUT:-1800}" # 30min default; prevents stuck workers -BRANCH_MODE="${BRANCH_MODE:-new}" -PLAN_REVIEW="${PLAN_REVIEW:-none}" -WORK_REVIEW="${WORK_REVIEW:-none}" -REQUIRE_PLAN_REVIEW="${REQUIRE_PLAN_REVIEW:-0}" -YOLO="${YOLO:-0}" -EPICS="${EPICS:-}" - -# Parse command line arguments -while [[ $# -gt 0 ]]; do - case "$1" in - --watch) - if [[ "${2:-}" == "verbose" ]]; then - WATCH_MODE="verbose" - shift - else - WATCH_MODE="tools" - fi - shift - ;; - --help|-h) - echo "Usage: ralph.sh [options]" - echo "" - echo "Options:" - echo " --watch Show tool calls in real-time" - echo " --watch verbose Show tool calls + model responses" - echo " --help, -h Show this help" - echo "" - echo "Environment variables:" - echo " EPICS Comma/space-separated epic IDs to work on" - echo " MAX_ITERATIONS Max loop iterations (default: 25)" - echo " YOLO Set to 1 to skip permissions (required for unattended)" - echo "" - echo "See config.env for more options." - exit 0 - ;; - *) - fail "Unknown option: $1 (use --help for usage)" - ;; - esac -done - -# Set up signal trap for clean Ctrl+C handling -# Must kill all child processes including timeout and worker -cleanup() { - trap - SIGINT SIGTERM # Prevent re-entry - # Kill all child processes - pkill -P $$ 2>/dev/null - # Kill process group as fallback - kill -- -$$ 2>/dev/null - exit 130 -} -trap cleanup SIGINT SIGTERM - -OPENCODE_BIN="${OPENCODE_BIN:-opencode}" - -# Detect timeout command (GNU coreutils). On macOS: brew install coreutils -# Use --foreground to keep child in same process group for signal handling -if command -v timeout >/dev/null 2>&1 && timeout --foreground 0 true 2>/dev/null; then - TIMEOUT_CMD="timeout --foreground" -elif command -v gtimeout >/dev/null 2>&1 && gtimeout --foreground 0 true 2>/dev/null; then - TIMEOUT_CMD="gtimeout --foreground" -elif command -v timeout >/dev/null 2>&1; then - TIMEOUT_CMD="timeout" -elif command -v gtimeout >/dev/null 2>&1; then - TIMEOUT_CMD="gtimeout" -else - TIMEOUT_CMD="" - echo "ralph: warning: timeout command not found; worker timeout disabled (brew install coreutils)" >&2 -fi - -sanitize_id() { - local v="$1" - v="${v// /_}" - v="${v//\//_}" - v="${v//\\/__}" - echo "$v" -} - -get_actor() { - if [[ -n "${FLOW_ACTOR:-}" ]]; then echo "$FLOW_ACTOR"; return; fi - if actor="$(git -C "$ROOT_DIR" config user.name 2>/dev/null)"; then - [[ -n "$actor" ]] && { echo "$actor"; return; } - fi - if actor="$(git -C "$ROOT_DIR" config user.email 2>/dev/null)"; then - [[ -n "$actor" ]] && { echo "$actor"; return; } - fi - echo "${USER:-unknown}" -} - -rand4() { - python3 - <<'PY' -import secrets -print(secrets.token_hex(2)) -PY -} - -render_template() { - local path="$1" - python3 - "$path" <<'PY' -import os, sys -path = sys.argv[1] -text = open(path, encoding="utf-8").read() -keys = ["EPIC_ID","TASK_ID","PLAN_REVIEW","WORK_REVIEW","BRANCH_MODE","BRANCH_MODE_EFFECTIVE","REQUIRE_PLAN_REVIEW","REVIEW_RECEIPT_PATH"] -for k in keys: - text = text.replace("{{%s}}" % k, os.environ.get(k, "")) -print(text) -PY -} - -json_get() { - local key="$1" - local json="$2" - python3 - "$key" "$json" <<'PY' -import json, sys -key = sys.argv[1] -data = json.loads(sys.argv[2]) -val = data.get(key) -if val is None: - print("") -elif isinstance(val, bool): - print("1" if val else "0") -else: - print(val) -PY -} - -ensure_attempts_file() { - [[ -f "$1" ]] || echo "{}" > "$1" -} - -bump_attempts() { - python3 - "$1" "$2" <<'PY' -import json, sys, os -path, task = sys.argv[1], sys.argv[2] -data = {} -if os.path.exists(path): - with open(path, encoding="utf-8") as f: - data = json.load(f) -count = int(data.get(task, 0)) + 1 -data[task] = count -with open(path, "w", encoding="utf-8") as f: - json.dump(data, f, indent=2, sort_keys=True) -print(count) -PY -} - -write_epics_file() { - python3 - "$1" <<'PY' -import json, sys -raw = sys.argv[1] -parts = [p.strip() for p in raw.replace(",", " ").split() if p.strip()] -print(json.dumps({"epics": parts}, indent=2, sort_keys=True)) -PY -} - -RUN_ID="$(date -u +%Y%m%dT%H%M%SZ)-$(hostname -s 2>/dev/null || hostname)-$(sanitize_id "$(get_actor)")-$$-$(rand4)" -RUN_DIR="$SCRIPT_DIR/runs/$RUN_ID" -mkdir -p "$RUN_DIR" -ATTEMPTS_FILE="$RUN_DIR/attempts.json" -ensure_attempts_file "$ATTEMPTS_FILE" -BRANCHES_FILE="$RUN_DIR/branches.json" -RECEIPTS_DIR="$RUN_DIR/receipts" -mkdir -p "$RECEIPTS_DIR" -PROGRESS_FILE="$RUN_DIR/progress.txt" -{ - echo "# Ralph Progress Log" - echo "Run: $RUN_ID" - echo "Started: $(date -u +%Y-%m-%dT%H:%M:%SZ)" - echo "---" -} > "$PROGRESS_FILE" - -extract_tag() { - local tag="$1" - python3 - "$tag" <<'PY' -import re, sys -tag = sys.argv[1] -text = sys.stdin.read() -matches = re.findall(rf"<{tag}>(.*?)", text, flags=re.S) -print(matches[-1] if matches else "") -PY -} - -# Extract assistant text from stream-json log (for tag extraction in watch mode) -extract_text_from_run_json() { - local log_file="$1" - python3 - "$log_file" <<'PY' -import json, sys -path = sys.argv[1] -out = [] -try: - with open(path, encoding="utf-8") as f: - for line in f: - line = line.strip() - if not line: - continue - try: - ev = json.loads(line) - except json.JSONDecodeError: - continue - - # OpenCode run --format json - if ev.get("type") == "text": - part = ev.get("part") or {} - text = part.get("text", "") - if text: - out.append(text) - continue - - # OpenCode stream-json (fallback) - if ev.get("type") == "assistant": - msg = ev.get("message") or {} - for blk in (msg.get("content") or []): - if blk.get("type") == "text": - out.append(blk.get("text", "")) -except Exception: - pass -print("\n".join(out)) -PY -} - -append_progress() { - local verdict="$1" - local promise="$2" - local plan_review_status="${3:-}" - local task_status="${4:-}" - local receipt_exists="0" - if [[ -n "${REVIEW_RECEIPT_PATH:-}" && -f "$REVIEW_RECEIPT_PATH" ]]; then - receipt_exists="1" - fi - { - echo "## $(date -u +%Y-%m-%dT%H:%M:%SZ) - iter $iter" - echo "status=$status epic=${epic_id:-} task=${task_id:-} reason=${reason:-}" - echo "worker_rc=$worker_rc" - echo "verdict=${verdict:-}" - echo "promise=${promise:-}" - echo "receipt=${REVIEW_RECEIPT_PATH:-} exists=$receipt_exists" - echo "plan_review_status=${plan_review_status:-}" - echo "task_status=${task_status:-}" - echo "iter_log=$iter_log" - echo "last_output:" - tail -n 10 "$iter_log" || true - echo "---" - } >> "$PROGRESS_FILE" -} - -# Write completion marker to progress.txt (MUST match find_active_runs() detection in flowctl.py) -write_completion_marker() { - local reason="${1:-DONE}" - { - echo "" - echo "completion_reason=$reason" - echo "promise=COMPLETE" # CANONICAL - must match flowctl.py substring search - } >> "$PROGRESS_FILE" -} - -# Check PAUSE/STOP sentinel files -check_sentinels() { - local pause_file="$RUN_DIR/PAUSE" - local stop_file="$RUN_DIR/STOP" - - # Check for stop first (exit immediately, keep file for audit) - if [[ -f "$stop_file" ]]; then - log "STOP sentinel detected, exiting gracefully" - ui_fail "STOP sentinel detected" - write_completion_marker "STOPPED" - exit 0 - fi - - # Check for pause (log once, wait in loop, re-check STOP while waiting) - if [[ -f "$pause_file" ]]; then - log "PAUSED - waiting for resume..." - while [[ -f "$pause_file" ]]; do - # Re-check STOP while paused so external stop works - if [[ -f "$stop_file" ]]; then - log "STOP sentinel detected while paused, exiting gracefully" - ui_fail "STOP sentinel detected" - write_completion_marker "STOPPED" - exit 0 - fi - sleep 5 - done - log "Resumed" - fi -} - -init_branches_file() { - if [[ -f "$BRANCHES_FILE" ]]; then return; fi - local base_branch - base_branch="$(git -C "$ROOT_DIR" rev-parse --abbrev-ref HEAD 2>/dev/null || true)" - python3 - "$BRANCHES_FILE" "$base_branch" <<'PY' -import json, sys -path, base = sys.argv[1], sys.argv[2] -data = {"base_branch": base, "run_branch": ""} -with open(path, "w", encoding="utf-8") as f: - json.dump(data, f, indent=2, sort_keys=True) -PY -} - -get_base_branch() { - python3 - "$BRANCHES_FILE" <<'PY' -import json, sys -try: - with open(sys.argv[1], encoding="utf-8") as f: - data = json.load(f) - print(data.get("base_branch", "")) -except FileNotFoundError: - print("") -PY -} - -get_run_branch() { - python3 - "$BRANCHES_FILE" <<'PY' -import json, sys -try: - with open(sys.argv[1], encoding="utf-8") as f: - data = json.load(f) - print(data.get("run_branch", "")) -except FileNotFoundError: - print("") -PY -} - -set_run_branch() { - python3 - "$BRANCHES_FILE" "$1" <<'PY' -import json, sys -path, branch = sys.argv[1], sys.argv[2] -data = {"base_branch": "", "run_branch": ""} -try: - with open(path, encoding="utf-8") as f: - data = json.load(f) -except FileNotFoundError: - pass -data["run_branch"] = branch -with open(path, "w", encoding="utf-8") as f: - json.dump(data, f, indent=2, sort_keys=True) -PY -} - -list_epics_from_file() { - python3 - "$EPICS_FILE" <<'PY' -import json, sys -path = sys.argv[1] -if not path: - sys.exit(0) -try: - data = json.load(open(path, encoding="utf-8")) -except FileNotFoundError: - sys.exit(0) -epics = data.get("epics", []) or [] -print(" ".join(epics)) -PY -} - -epic_all_tasks_done() { - python3 - "$1" <<'PY' -import json, sys -try: - data = json.loads(sys.argv[1]) -except json.JSONDecodeError: - print("0") - sys.exit(0) -tasks = data.get("tasks", []) or [] -if not tasks: - print("0") - sys.exit(0) -for t in tasks: - if t.get("status") != "done": - print("0") - sys.exit(0) -print("1") -PY -} - -maybe_close_epics() { - [[ -z "$EPICS_FILE" ]] && return 0 - local epics json status all_done - epics="$(list_epics_from_file)" - [[ -z "$epics" ]] && return 0 - for epic in $epics; do - json="$("$FLOWCTL" show "$epic" --json 2>/dev/null || true)" - [[ -z "$json" ]] && continue - status="$(json_get status "$json")" - [[ "$status" == "done" ]] && continue - all_done="$(epic_all_tasks_done "$json")" - if [[ "$all_done" == "1" ]]; then - "$FLOWCTL" epic close "$epic" --json >/dev/null 2>&1 || true - fi - done -} - -verify_receipt() { - local path="$1" - local kind="$2" - local id="$3" - [[ -f "$path" ]] || return 1 - python3 - "$path" "$kind" "$id" <<'PY' -import json, sys -path, kind, rid = sys.argv[1], sys.argv[2], sys.argv[3] -try: - data = json.load(open(path, encoding="utf-8")) -except Exception: - sys.exit(1) -if data.get("type") != kind: - sys.exit(1) -if data.get("id") != rid: - sys.exit(1) -sys.exit(0) -PY -} - -# Create/switch to run branch (once at start, all epics work here) -ensure_run_branch() { - if [[ "$BRANCH_MODE" != "new" ]]; then - return - fi - init_branches_file - local branch - branch="$(get_run_branch)" - if [[ -n "$branch" ]]; then - # Already on run branch (resumed run) - git -C "$ROOT_DIR" checkout "$branch" >/dev/null 2>&1 || true - return - fi - # Create new run branch from current position - branch="ralph-${RUN_ID}" - set_run_branch "$branch" - git -C "$ROOT_DIR" checkout -b "$branch" >/dev/null 2>&1 -} - -EPICS_FILE="" -if [[ -n "${EPICS// }" ]]; then - EPICS_FILE="$RUN_DIR/run.json" - write_epics_file "$EPICS" > "$EPICS_FILE" -fi - -ui_header -ui_config - -# Create run branch once at start (all epics work on same branch) -ensure_run_branch - -iter=1 -while (( iter <= MAX_ITERATIONS )); do - iter_log="$RUN_DIR/iter-$(printf '%03d' "$iter").log" - - # Check for pause/stop at start of iteration (before work selection) - check_sentinels - - selector_args=("$FLOWCTL" next --json) - [[ -n "$EPICS_FILE" ]] && selector_args+=(--epics-file "$EPICS_FILE") - [[ "$REQUIRE_PLAN_REVIEW" == "1" ]] && selector_args+=(--require-plan-review) - - selector_json="$("${selector_args[@]}")" - status="$(json_get status "$selector_json")" - epic_id="$(json_get epic "$selector_json")" - task_id="$(json_get task "$selector_json")" - reason="$(json_get reason "$selector_json")" - - log "iter $iter status=$status epic=${epic_id:-} task=${task_id:-} reason=${reason:-}" - ui_iteration "$iter" "$status" "${epic_id:-}" "${task_id:-}" - - if [[ "$status" == "none" ]]; then - if [[ "$reason" == "blocked_by_epic_deps" ]]; then - log "blocked by epic deps" - fi - maybe_close_epics - ui_complete - write_completion_marker "NO_WORK" - exit 0 - fi - - if [[ "$status" == "plan" ]]; then - export EPIC_ID="$epic_id" - export PLAN_REVIEW - export REQUIRE_PLAN_REVIEW - export FLOW_REVIEW_BACKEND="$PLAN_REVIEW" # Skills read this - if [[ "$PLAN_REVIEW" != "none" ]]; then - export REVIEW_RECEIPT_PATH="$RECEIPTS_DIR/plan-${epic_id}.json" - else - unset REVIEW_RECEIPT_PATH - fi - log "plan epic=$epic_id review=$PLAN_REVIEW receipt=${REVIEW_RECEIPT_PATH:-} require=$REQUIRE_PLAN_REVIEW" - ui_plan_review "$PLAN_REVIEW" "$epic_id" - prompt="$(render_template "$SCRIPT_DIR/prompt_plan.md")" - elif [[ "$status" == "work" ]]; then - epic_id="${task_id%%.*}" - export TASK_ID="$task_id" - BRANCH_MODE_EFFECTIVE="$BRANCH_MODE" - if [[ "$BRANCH_MODE" == "new" ]]; then - BRANCH_MODE_EFFECTIVE="current" - fi - export BRANCH_MODE_EFFECTIVE - export WORK_REVIEW - export FLOW_REVIEW_BACKEND="$WORK_REVIEW" # Skills read this - if [[ "$WORK_REVIEW" != "none" ]]; then - export REVIEW_RECEIPT_PATH="$RECEIPTS_DIR/impl-${task_id}.json" - else - unset REVIEW_RECEIPT_PATH - fi - log "work task=$task_id review=$WORK_REVIEW receipt=${REVIEW_RECEIPT_PATH:-} branch=$BRANCH_MODE_EFFECTIVE" - ui_impl_review "$WORK_REVIEW" "$task_id" - prompt="$(render_template "$SCRIPT_DIR/prompt_work.md")" - else - fail "invalid selector status: $status" - fi - - export FLOW_RALPH="1" - AUTONOMOUS_RULES="$(cat <<'TXT' -AUTONOMOUS MODE ACTIVE (FLOW_RALPH=1). You are running unattended. CRITICAL RULES: -1. EXECUTE COMMANDS EXACTLY as shown in prompts. Do not paraphrase or improvise. -2. VERIFY OUTCOMES by running the verification commands (flowctl show, git status). -3. NEVER CLAIM SUCCESS without proof. If flowctl done was not run, the task is NOT done. -4. COPY TEMPLATES VERBATIM - receipt JSON must match exactly including all fields. -5. USE SKILLS AS SPECIFIED - invoke /flow-next:impl-review, do not improvise review prompts. -Violations break automation and leave the user with incomplete work. Be precise, not creative. -TXT -)" - prompt="${AUTONOMOUS_RULES}"$'\n\n'"${prompt}" - - opencode_args=(run --format json --agent "${FLOW_RALPH_OPENCODE_AGENT:-ralph-runner}") - [[ -n "${FLOW_RALPH_OPENCODE_MODEL:-}" ]] && opencode_args+=(--model "$FLOW_RALPH_OPENCODE_MODEL") - [[ -n "${FLOW_RALPH_OPENCODE_VARIANT:-}" ]] && opencode_args+=(--variant "$FLOW_RALPH_OPENCODE_VARIANT") - - prev_opencode_permission="${OPENCODE_PERMISSION-__unset__}" - if [[ "$YOLO" == "1" ]]; then - export OPENCODE_PERMISSION='{"*":"allow"}' - fi - - ui_waiting - worker_out="" - set +e - if [[ "$WATCH_MODE" == "verbose" ]]; then - echo "" - if [[ -n "$TIMEOUT_CMD" ]]; then - $TIMEOUT_CMD "$WORKER_TIMEOUT" "$OPENCODE_BIN" "${opencode_args[@]}" "$prompt" 2>&1 | tee "$iter_log" | "$SCRIPT_DIR/watch-filter.py" --verbose - else - "$OPENCODE_BIN" "${opencode_args[@]}" "$prompt" 2>&1 | tee "$iter_log" | "$SCRIPT_DIR/watch-filter.py" --verbose - fi - worker_rc=${PIPESTATUS[0]} - worker_out="$(cat "$iter_log")" - elif [[ "$WATCH_MODE" == "tools" ]]; then - if [[ -n "$TIMEOUT_CMD" ]]; then - $TIMEOUT_CMD "$WORKER_TIMEOUT" "$OPENCODE_BIN" "${opencode_args[@]}" "$prompt" 2>&1 | tee "$iter_log" | "$SCRIPT_DIR/watch-filter.py" - else - "$OPENCODE_BIN" "${opencode_args[@]}" "$prompt" 2>&1 | tee "$iter_log" | "$SCRIPT_DIR/watch-filter.py" - fi - worker_rc=${PIPESTATUS[0]} - worker_out="$(cat "$iter_log")" - else - if [[ -n "$TIMEOUT_CMD" ]]; then - $TIMEOUT_CMD "$WORKER_TIMEOUT" "$OPENCODE_BIN" "${opencode_args[@]}" "$prompt" > "$iter_log" 2>&1 - else - "$OPENCODE_BIN" "${opencode_args[@]}" "$prompt" > "$iter_log" 2>&1 - fi - worker_rc=$? - worker_out="$(cat "$iter_log")" - fi - set -e - - if [[ "$prev_opencode_permission" == "__unset__" ]]; then - unset OPENCODE_PERMISSION - else - export OPENCODE_PERMISSION="$prev_opencode_permission" - fi - - # Handle timeout (exit code 124 from timeout command) - worker_timeout=0 - if [[ -n "$TIMEOUT_CMD" && "$worker_rc" -eq 124 ]]; then - echo "ralph: worker timed out after ${WORKER_TIMEOUT}s" >> "$iter_log" - log "worker timeout after ${WORKER_TIMEOUT}s" - worker_timeout=1 - fi - - log "worker rc=$worker_rc log=$iter_log" - - force_retry=$worker_timeout - plan_review_status="" - task_status="" - if [[ "$status" == "plan" && ( "$PLAN_REVIEW" == "rp" || "$PLAN_REVIEW" == "opencode" ) ]]; then - if ! verify_receipt "$REVIEW_RECEIPT_PATH" "plan_review" "$epic_id"; then - echo "ralph: missing plan review receipt; forcing retry" >> "$iter_log" - log "missing plan receipt; forcing retry" - "$FLOWCTL" epic set-plan-review-status "$epic_id" --status needs_work --json >/dev/null 2>&1 || true - force_retry=1 - fi - epic_json="$("$FLOWCTL" show "$epic_id" --json 2>/dev/null || true)" - plan_review_status="$(json_get plan_review_status "$epic_json")" - fi - if [[ "$status" == "work" && ( "$WORK_REVIEW" == "rp" || "$WORK_REVIEW" == "opencode" ) ]]; then - if ! verify_receipt "$REVIEW_RECEIPT_PATH" "impl_review" "$task_id"; then - echo "ralph: missing impl review receipt; forcing retry" >> "$iter_log" - log "missing impl receipt; forcing retry" - force_retry=1 - fi - fi - - # Extract verdict/promise for progress log (not displayed in UI) - # Always parse stream-json since we always use that format now - worker_text="$(extract_text_from_run_json "$iter_log")" - verdict="$(printf '%s' "$worker_text" | extract_tag verdict)" - promise="$(printf '%s' "$worker_text" | extract_tag promise)" - - # Fallback: derive verdict from flowctl status for logging - if [[ -z "$verdict" && -n "$plan_review_status" ]]; then - case "$plan_review_status" in - ship) verdict="SHIP" ;; - needs_work) verdict="NEEDS_WORK" ;; - esac - fi - - if [[ "$status" == "work" ]]; then - task_json="$("$FLOWCTL" show "$task_id" --json 2>/dev/null || true)" - task_status="$(json_get status "$task_json")" - if [[ "$task_status" != "done" ]]; then - echo "ralph: task not done; forcing retry" >> "$iter_log" - log "task $task_id status=$task_status; forcing retry" - force_retry=1 - else - ui_task_done "$task_id" - # Derive verdict from task completion for logging - [[ -z "$verdict" ]] && verdict="SHIP" - fi - fi - append_progress "$verdict" "$promise" "$plan_review_status" "$task_status" - - if echo "$worker_text" | grep -q "COMPLETE"; then - ui_complete - write_completion_marker "DONE" - exit 0 - fi - - exit_code=0 - if echo "$worker_text" | grep -q "FAIL"; then - exit_code=1 - elif echo "$worker_text" | grep -q "RETRY"; then - exit_code=2 - elif [[ "$force_retry" == "1" ]]; then - exit_code=2 - elif [[ "$worker_rc" -ne 0 && "$task_status" != "done" && "$verdict" != "SHIP" ]]; then - # Only fail on non-zero exit code if task didn't complete and verdict isn't SHIP - # This prevents false failures from transient errors (telemetry, model fallback, etc.) - exit_code=1 - fi - - if [[ "$exit_code" -eq 1 ]]; then - log "exit=fail" - ui_fail "OpenCode returned FAIL promise" - write_completion_marker "FAILED" - exit 1 - fi - - if [[ "$exit_code" -eq 2 && "$status" == "work" ]]; then - attempts="$(bump_attempts "$ATTEMPTS_FILE" "$task_id")" - log "retry task=$task_id attempts=$attempts" - ui_retry "$task_id" "$attempts" "$MAX_ATTEMPTS_PER_TASK" - if (( attempts >= MAX_ATTEMPTS_PER_TASK )); then - reason_file="$RUN_DIR/block-${task_id}.md" - { - echo "Auto-blocked after ${attempts} attempts." - echo "Run: $RUN_ID" - echo "Task: $task_id" - echo "" - echo "Last output:" - tail -n 40 "$iter_log" || true - } > "$reason_file" - "$FLOWCTL" block "$task_id" --reason-file "$reason_file" --json || true - ui_blocked "$task_id" - fi - fi - - # Check for pause/stop after OpenCode returns (before next iteration) - check_sentinels - - sleep 2 - iter=$((iter + 1)) -done - -ui_fail "Max iterations ($MAX_ITERATIONS) reached" -echo "ralph: max iterations reached" >&2 -write_completion_marker "MAX_ITERATIONS" -exit 1 diff --git a/plugins/flow-next/skills/flow-next-ralph-init/templates/ralph_once.sh b/plugins/flow-next/skills/flow-next-ralph-init/templates/ralph_once.sh deleted file mode 100644 index 2ce3112..0000000 --- a/plugins/flow-next/skills/flow-next-ralph-init/templates/ralph_once.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env bash -# Human-in-the-loop Ralph: runs exactly one iteration -# Use this to observe behavior before going fully autonomous - -set -euo pipefail -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" - -export MAX_ITERATIONS=1 -exec "$SCRIPT_DIR/ralph.sh" "$@" diff --git a/plugins/flow-next/skills/flow-next-ralph-init/templates/watch-filter.py b/plugins/flow-next/skills/flow-next-ralph-init/templates/watch-filter.py deleted file mode 100755 index c703e18..0000000 --- a/plugins/flow-next/skills/flow-next-ralph-init/templates/watch-filter.py +++ /dev/null @@ -1,230 +0,0 @@ -#!/usr/bin/env python3 -""" -Watch filter for Ralph - parses Claude's stream-json output and shows key events. - -Reads JSON lines from stdin, outputs formatted tool calls in TUI style. - -CRITICAL: This filter is "fail open" - if output breaks, it continues draining -stdin to prevent SIGPIPE cascading to upstream processes (tee, claude). - -Usage: - watch-filter.py # Show tool calls only - watch-filter.py --verbose # Show tool calls + thinking + text responses -""" - -import argparse -import json -import os -import sys -from typing import Optional - -# Global flag to disable output on pipe errors (fail open pattern) -_output_disabled = False - -# ANSI color codes (match ralph.sh TUI) -if sys.stdout.isatty() and not os.environ.get("NO_COLOR"): - C_RESET = "\033[0m" - C_DIM = "\033[2m" - C_CYAN = "\033[36m" -else: - C_RESET = C_DIM = C_CYAN = "" - -# TUI indentation (3 spaces to match ralph.sh) -INDENT = " " - -# Tool icons -ICONS = { - "Bash": "🔧", - "Edit": "📝", - "Write": "📄", - "Read": "📖", - "Grep": "🔍", - "Glob": "📁", - "Task": "🤖", - "WebFetch": "🌐", - "WebSearch": "🔎", - "TodoWrite": "📋", - "AskUserQuestion": "❓", - "Skill": "⚡", -} - - -def safe_print(msg: str) -> None: - """Print that fails open - disables output on BrokenPipe instead of crashing.""" - global _output_disabled - if _output_disabled: - return - try: - print(msg, flush=True) - except BrokenPipeError: - _output_disabled = True - - -def drain_stdin() -> None: - """Consume remaining stdin to prevent SIGPIPE to upstream processes.""" - try: - for _ in sys.stdin: - pass - except Exception: - pass - - -def truncate(s: str, max_len: int = 60) -> str: - s = s.replace("\n", " ").strip() - if len(s) > max_len: - return s[: max_len - 3] + "..." - return s - - -def format_tool_use(tool_name: str, tool_input: dict) -> str: - """Format a tool use event for TUI display.""" - icon = ICONS.get(tool_name, "🔹") - - if tool_name == "Bash": - cmd = tool_input.get("command", "") - desc = tool_input.get("description", "") - if desc: - return f"{icon} Bash: {truncate(desc)}" - return f"{icon} Bash: {truncate(cmd, 60)}" - - elif tool_name == "Edit": - path = tool_input.get("file_path", "") - return f"{icon} Edit: {path.split('/')[-1] if path else 'unknown'}" - - elif tool_name == "Write": - path = tool_input.get("file_path", "") - return f"{icon} Write: {path.split('/')[-1] if path else 'unknown'}" - - elif tool_name == "Read": - path = tool_input.get("file_path", "") - return f"{icon} Read: {path.split('/')[-1] if path else 'unknown'}" - - elif tool_name == "Grep": - pattern = tool_input.get("pattern", "") - return f"{icon} Grep: {truncate(pattern, 40)}" - - elif tool_name == "Glob": - pattern = tool_input.get("pattern", "") - return f"{icon} Glob: {pattern}" - - elif tool_name == "Task": - desc = tool_input.get("description", "") - agent = tool_input.get("subagent_type", "") - return f"{icon} Task ({agent}): {truncate(desc, 50)}" - - elif tool_name == "Skill": - skill = tool_input.get("skill", "") - return f"{icon} Skill: {skill}" - - elif tool_name == "TodoWrite": - todos = tool_input.get("todos", []) - in_progress = [t for t in todos if t.get("status") == "in_progress"] - if in_progress: - return f"{icon} Todo: {truncate(in_progress[0].get('content', ''))}" - return f"{icon} Todo: {len(todos)} items" - - else: - return f"{icon} {tool_name}" - - -def format_tool_result(block: dict) -> Optional[str]: - """Format a tool_result block (errors only). - - Args: - block: The full tool_result block (not just content) - """ - # Check is_error on the block itself - if block.get("is_error"): - content = block.get("content", "") - error_text = str(content) if content else "unknown error" - return f"{INDENT}{C_DIM}❌ {truncate(error_text, 60)}{C_RESET}" - - # Also check content for error strings (heuristic) - content = block.get("content", "") - if isinstance(content, str): - lower = content.lower() - if "error" in lower or "failed" in lower: - return f"{INDENT}{C_DIM}⚠️ {truncate(content, 60)}{C_RESET}" - - return None - - -def process_event(event: dict, verbose: bool) -> None: - """Process a single stream-json event.""" - event_type = event.get("type", "") - - # Tool use events (assistant messages) - if event_type == "assistant": - message = event.get("message", {}) - content = message.get("content", []) - - for block in content: - block_type = block.get("type", "") - - if block_type == "tool_use": - tool_name = block.get("name", "") - tool_input = block.get("input", {}) - formatted = format_tool_use(tool_name, tool_input) - safe_print(f"{INDENT}{C_DIM}{formatted}{C_RESET}") - - elif verbose and block_type == "text": - text = block.get("text", "") - if text.strip(): - safe_print(f"{INDENT}{C_CYAN}💬 {text}{C_RESET}") - - elif verbose and block_type == "thinking": - thinking = block.get("thinking", "") - if thinking.strip(): - safe_print(f"{INDENT}{C_DIM}🧠 {truncate(thinking, 100)}{C_RESET}") - - # Tool results (user messages with tool_result blocks) - elif event_type == "user": - message = event.get("message", {}) - content = message.get("content", []) - - for block in content: - if block.get("type") == "tool_result": - formatted = format_tool_result(block) - if formatted: - safe_print(formatted) - - -def main() -> None: - parser = argparse.ArgumentParser(description="Filter Claude stream-json output") - parser.add_argument( - "--verbose", - action="store_true", - help="Show text and thinking in addition to tool calls", - ) - args = parser.parse_args() - - for line in sys.stdin: - line = line.strip() - if not line: - continue - - try: - event = json.loads(line) - except json.JSONDecodeError: - continue - - try: - process_event(event, args.verbose) - except Exception: - # Swallow processing errors - keep draining stdin - pass - - -if __name__ == "__main__": - try: - main() - except KeyboardInterrupt: - sys.exit(0) - except BrokenPipeError: - # Output broken but keep draining to prevent upstream SIGPIPE - drain_stdin() - sys.exit(0) - except Exception as e: - print(f"watch-filter: {e}", file=sys.stderr) - drain_stdin() - sys.exit(0) diff --git a/plugins/flow-next/skills/flow-next-rp-explorer/SKILL.md b/plugins/flow-next/skills/flow-next-rp-explorer/SKILL.md deleted file mode 100644 index 4bd5339..0000000 --- a/plugins/flow-next/skills/flow-next-rp-explorer/SKILL.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -name: flow-next-rp-explorer -description: Token-efficient codebase exploration using RepoPrompt CLI. Use when user says "use rp to..." or "use repoprompt to..." followed by explore, find, understand, search, or similar actions. ---- - -# RP-Explorer - -Token-efficient codebase exploration using RepoPrompt CLI. - -## Trigger Phrases - -Activates when user combines "use rp" or "use repoprompt" with an action: -- "use rp to explore how auth works" -- "use repoprompt to find similar patterns" -- "use rp to understand the data flow" -- "use repoprompt to search for API endpoints" - -## CLI Reference - -Read [cli-reference.md](cli-reference.md) for complete command documentation. - -## Quick Start - -### Step 1: Get Overview -```bash -rp-cli -e 'tree' -rp-cli -e 'structure .' -``` - -### Step 2: Find Relevant Files -```bash -rp-cli -e 'search "auth" --context-lines 2' -rp-cli -e 'builder "understand authentication"' -``` - -### Step 3: Deep Dive -```bash -rp-cli -e 'select set src/auth/' -rp-cli -e 'structure --scope selected' -rp-cli -e 'read src/auth/login.ts' -``` - -### Step 4: Export Context -```bash -rp-cli -e 'context --all > codebase-map.md' -``` - -## Token Efficiency - -- Use `structure` instead of reading full files (10x fewer tokens) -- Use `builder` for AI-powered file discovery -- Select only relevant files before exporting context - -## Tab Isolation - -`builder` creates an isolated compose tab automatically. Use `-t` to target it: -```bash -# Builder returns: Tab: -rp-cli -w W -t "" -e 'select add extra.ts && context' - -# Or chain commands: -rp-cli -w W -e 'builder "find auth" && select add extra.ts && context' -``` - -## Requirements - -RepoPrompt v1.5.62+ with rp-cli installed. diff --git a/plugins/flow-next/skills/flow-next-rp-explorer/cli-reference.md b/plugins/flow-next/skills/flow-next-rp-explorer/cli-reference.md deleted file mode 100644 index 11904a1..0000000 --- a/plugins/flow-next/skills/flow-next-rp-explorer/cli-reference.md +++ /dev/null @@ -1,151 +0,0 @@ -# rp-cli Command Reference - -> Requires RepoPrompt v1.5.62+ - -## Basic Usage - -```bash -rp-cli -e '' # Run single command -rp-cli -e ' && ' # Chain commands -rp-cli -w -e '' # Target specific window -rp-cli -w -t -e '' # Target window + tab (v1.5.62+) -``` - -## Core Commands - -| Command | Aliases | Purpose | -|---------|---------|---------| -| `tree` | - | File/folder tree | -| `structure` | `map` | Code signatures (token-efficient) | -| `search` | `grep` | Search with context | -| `read` | `cat` | Read file contents | -| `select` | `sel` | Manage file selection | -| `context` | `ctx` | Export workspace context | -| `builder` | - | AI-powered file selection | -| `chat` | - | Send to AI chat | - -## File Tree - -```bash -rp-cli -e 'tree' # Full tree -rp-cli -e 'tree --folders' # Folders only -rp-cli -e 'tree --mode selected' # Selected files only -``` - -## Code Structure (TOKEN EFFICIENT) - -```bash -rp-cli -e 'structure src/' # Signatures for path -rp-cli -e 'structure .' # Whole project -rp-cli -e 'structure --scope selected' # Selected files only -``` - -## Search - -```bash -rp-cli -e 'search "pattern"' -rp-cli -e 'search "TODO" --extensions .ts,.tsx' -rp-cli -e 'search "error" --context-lines 3' -rp-cli -e 'search "function" --max-results 20' -``` - -## Read Files - -```bash -rp-cli -e 'read path/to/file.ts' -rp-cli -e 'read file.ts --start-line 50 --limit 30' # Slice -rp-cli -e 'read file.ts --start-line -20' # Last 20 lines -``` - -## Selection Management - -```bash -rp-cli -e 'select add src/' # Add to selection -rp-cli -e 'select set src/ lib/' # Replace selection -rp-cli -e 'select clear' # Clear selection -rp-cli -e 'select get' # View selection -``` - -## Context Export - -```bash -rp-cli -e 'context' # Full context -rp-cli -e 'context --include prompt,selection,tree' -rp-cli -e 'context --all > output.md' # Export to file -``` - -## Prompt Export (v1.5.61+) - -```bash -# Export full context (files, tree, codemaps) to markdown file -rp-cli -e 'prompt export /path/to/output.md' -``` - -## AI-Powered Builder - -```bash -rp-cli -e 'builder "understand auth system"' -rp-cli -e 'builder "find API endpoints" --response-type plan' -``` - -## Chat - -```bash -rp-cli -e 'chat "How does auth work?"' -rp-cli -e 'chat "Design new feature" --mode plan' -rp-cli -e 'newchat "Start fresh discussion"' # New chat -``` - -Note: Chats are bound to compose tabs. Use `workspace tab` to bind to a specific tab before chatting. - -## Workspaces & Tabs - -```bash -rp-cli -e 'workspace list' # List workspaces -rp-cli -e 'workspace switch "Name"' # Switch workspace -rp-cli -e 'workspace tabs' # List tabs -rp-cli -e 'workspace tab "TabName"' # Bind to tab (for chat isolation) -``` - -## Workflow Shorthand Flags - -```bash -# Quick one-liner workflows -rp-cli --workspace MyProject --select-set src/ --export-context ~/out.json -rp-cli --workspace MyProject --select-set src/ --export-prompt ~/context.md -rp-cli --chat "How does auth work?" -rp-cli --builder "implement user authentication" -``` - -## Script Files (.rp) - -Save repeatable workflows: - -```bash -# export.rp -workspace switch MyProject -select set src/ -context --all > output.md -``` - -Run with: `rp-cli --exec-file ~/scripts/export.rp` - -## Tab Isolation - -`builder` creates an isolated compose tab automatically. Use `-t` to target it directly: -```bash -# Builder returns: Tab: -# Target that tab for follow-up commands: -rp-cli -w W -t "" -e 'select get' -rp-cli -w W -t "" -e 'chat "review" --mode chat' - -# Or chain commands to stay in same tab: -rp-cli -w W -e 'builder "..." && select add file.ts && chat "review"' -``` - -## Notes - -- Requires RepoPrompt v1.5.62+ with MCP Server enabled -- Use `rp-cli -d ` for detailed help on any command -- Token-efficient: `structure` gives signatures without full content -- Progress notifications show during builder/chat execution diff --git a/plugins/flow-next/skills/flow-next-setup/SKILL.md b/plugins/flow-next/skills/flow-next-setup/SKILL.md deleted file mode 100644 index bd115a9..0000000 --- a/plugins/flow-next/skills/flow-next-setup/SKILL.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -name: flow-next-setup -description: Optional local install of flowctl CLI and CLAUDE.md/AGENTS.md instructions. Use when user runs /flow-next:setup. ---- - -# Flow-Next Setup (Optional) - -Install flowctl locally and add instructions to project docs. **Fully optional** - flow-next works without this via the plugin. - -## Benefits - -- `flowctl` accessible from command line (add `.flow/bin` to PATH) -- Other AI agents (Codex, Cursor, etc.) can read instructions from CLAUDE.md/AGENTS.md -- Works without Claude Code plugin installed - -## Workflow - -Read [workflow.md](workflow.md) and follow each step in order. - -## Notes - -- **Fully optional** - standard plugin usage works without local setup -- Copies scripts (not symlinks) for portability across environments -- Safe to re-run - will detect existing setup and offer to update diff --git a/plugins/flow-next/skills/flow-next-setup/templates/claude-md-snippet.md b/plugins/flow-next/skills/flow-next-setup/templates/claude-md-snippet.md deleted file mode 100644 index 8af3e8c..0000000 --- a/plugins/flow-next/skills/flow-next-setup/templates/claude-md-snippet.md +++ /dev/null @@ -1,23 +0,0 @@ - -## Flow-Next - -This project uses Flow-Next for task tracking. Use `.flow/bin/flowctl` instead of markdown TODOs or TodoWrite. - -**Quick commands:** -```bash -.flow/bin/flowctl list # List all epics + tasks -.flow/bin/flowctl epics # List all epics -.flow/bin/flowctl tasks --epic fn-N # List tasks for epic -.flow/bin/flowctl ready --epic fn-N # What's ready -.flow/bin/flowctl show fn-N.M # View task -.flow/bin/flowctl start fn-N.M # Claim task -.flow/bin/flowctl done fn-N.M --summary-file s.md --evidence-json e.json -``` - -**Rules:** -- Use `.flow/bin/flowctl` for ALL task tracking -- Do NOT create markdown TODOs or use TodoWrite -- Re-anchor (re-read spec + status) before every task - -**More info:** `.flow/bin/flowctl --help` or read `.flow/usage.md` - diff --git a/plugins/flow-next/skills/flow-next-setup/templates/usage.md b/plugins/flow-next/skills/flow-next-setup/templates/usage.md deleted file mode 100644 index f77848c..0000000 --- a/plugins/flow-next/skills/flow-next-setup/templates/usage.md +++ /dev/null @@ -1,76 +0,0 @@ -# Flow-Next Usage Guide - -Task tracking for AI agents. All state lives in `.flow/`. - -## CLI - -```bash -.flow/bin/flowctl --help # All commands -.flow/bin/flowctl --help # Command help -``` - -## File Structure - -``` -.flow/ -├── bin/flowctl # CLI (this install) -├── epics/fn-N.json # Epic metadata -├── specs/fn-N.md # Epic specifications -├── tasks/fn-N.M.json # Task metadata -├── tasks/fn-N.M.md # Task specifications -├── memory/ # Context memory -└── meta.json # Project metadata -``` - -## IDs - -- Epics: `fn-N` (e.g., fn-1, fn-2) -- Tasks: `fn-N.M` (e.g., fn-1.1, fn-1.2) - -## Common Commands - -```bash -# List -.flow/bin/flowctl list # All epics + tasks grouped -.flow/bin/flowctl epics # All epics with progress -.flow/bin/flowctl tasks # All tasks -.flow/bin/flowctl tasks --epic fn-1 # Tasks for epic -.flow/bin/flowctl tasks --status todo # Filter by status - -# View -.flow/bin/flowctl show fn-1 # Epic with all tasks -.flow/bin/flowctl show fn-1.2 # Single task -.flow/bin/flowctl cat fn-1 # Epic spec (markdown) -.flow/bin/flowctl cat fn-1.2 # Task spec (markdown) - -# Status -.flow/bin/flowctl ready --epic fn-1 # What's ready to work on -.flow/bin/flowctl validate --all # Check structure - -# Create -.flow/bin/flowctl epic create --title "..." -.flow/bin/flowctl task create --epic fn-1 --title "..." - -# Work -.flow/bin/flowctl start fn-1.2 # Claim task -.flow/bin/flowctl done fn-1.2 --summary-file s.md --evidence-json e.json -``` - -## Workflow - -1. `.flow/bin/flowctl epics` - list all epics -2. `.flow/bin/flowctl ready --epic fn-N` - find available tasks -3. `.flow/bin/flowctl start fn-N.M` - claim task -4. Implement the task -5. `.flow/bin/flowctl done fn-N.M --summary-file ... --evidence-json ...` - complete - -## Evidence JSON Format - -```json -{"commits": ["abc123"], "tests": ["npm test"], "prs": []} -``` - -## More Info - -- Human docs: https://github.com/gmickel/gmickel-claude-marketplace/blob/main/plugins/flow-next/docs/flowctl.md -- CLI reference: `.flow/bin/flowctl --help` diff --git a/plugins/flow-next/skills/flow-next-setup/workflow.md b/plugins/flow-next/skills/flow-next-setup/workflow.md deleted file mode 100644 index 335a227..0000000 --- a/plugins/flow-next/skills/flow-next-setup/workflow.md +++ /dev/null @@ -1,163 +0,0 @@ -# Flow-Next Setup Workflow - -Follow these steps in order. This workflow is **idempotent** - safe to re-run. - -## Step 0: Resolve plugin path - -The plugin root is the parent of this skill's directory. From this SKILL.md location, go up to find `scripts/` and `.claude-plugin/`. - -Example: if this file is at `~/.claude/plugins/cache/.../flow-next/0.3.12/skills/flow-next-setup/workflow.md`, then plugin root is `~/.claude/plugins/cache/.../flow-next/0.3.12/`. - -Store this as `PLUGIN_ROOT` for use in later steps. - -## Step 1: Check .flow/ exists - -Check if `.flow/` directory exists (use Bash `ls .flow/` or check for `.flow/meta.json`). - -- If `.flow/` exists: continue -- If `.flow/` doesn't exist: create it with `mkdir -p .flow` and create minimal meta.json: - ```json - {"schema_version": 2, "next_epic": 1} - ``` - -Also ensure `.flow/config.json` exists with defaults: -```bash -if [ ! -f .flow/config.json ]; then - echo '{"memory":{"enabled":false}}' > .flow/config.json -fi -``` - -## Step 2: Check existing setup - -Read `.flow/meta.json` and check for `setup_version` field. - -Also read `${PLUGIN_ROOT}/.claude-plugin/plugin.json` to get current plugin version. - -**If `setup_version` exists (already set up):** -- If **same version**: tell user "Already set up with v. Re-run to update docs only? (y/n)" - - If yes: skip to Step 6 (docs) - - If no: done -- If **older version**: tell user "Updating from v to v" and continue - -**If no `setup_version`:** continue (first-time setup) - -## Step 3: Create .flow/bin/ - -```bash -mkdir -p .flow/bin -``` - -## Step 4: Copy files - -**IMPORTANT: Do NOT read flowctl.py - it's too large. Just copy it.** - -Copy using Bash `cp` with absolute paths: - -```bash -cp "${PLUGIN_ROOT}/scripts/flowctl" .flow/bin/flowctl -cp "${PLUGIN_ROOT}/scripts/flowctl.py" .flow/bin/flowctl.py -chmod +x .flow/bin/flowctl -``` - -Then read [templates/usage.md](templates/usage.md) and write it to `.flow/usage.md`. - -## Step 5: Update meta.json - -Read current `.flow/meta.json`, add/update these fields (preserve all others): - -```json -{ - "setup_version": "", - "setup_date": "" -} -``` - -## Step 6: Check and update documentation - -Read the template from [templates/claude-md-snippet.md](templates/claude-md-snippet.md). - -For each of CLAUDE.md and AGENTS.md: -1. Check if file exists -2. If exists, check if `` marker exists -3. If marker exists, extract content between markers and compare with template - -Determine status for each file: -- **missing**: file doesn't exist or no flow-next section -- **current**: section exists and matches template -- **outdated**: section exists but differs from template - -Based on status: - -**If both are current:** -``` -Documentation already up to date (CLAUDE.md, AGENTS.md). -``` -Skip to Step 7. - -**If one or both need updates:** -Show status and ask: -``` -Documentation status: -- CLAUDE.md: -- AGENTS.md: - -Update docs? (Only showing files that need changes) -1. CLAUDE.md only -2. AGENTS.md only -3. Both -4. Skip - -(Reply: 1, 2, 3, or 4) -``` -Only show options for files that are missing or outdated. - -Wait for response, then for each chosen file: -1. Read the file (create if doesn't exist) -2. If marker exists: replace everything between `` and `` (inclusive) -3. If no marker: append the snippet - -## Step 7: Print summary - -``` -Flow-Next setup complete! - -Installed: -- .flow/bin/flowctl (v) -- .flow/bin/flowctl.py -- .flow/usage.md - -To use from command line: - export PATH=".flow/bin:$PATH" - flowctl --help - -Documentation updated: -- - -Memory system: disabled by default -Enable with: flowctl config set memory.enabled true - -Notes: -- Re-run /flow-next:setup after plugin updates to refresh scripts -- Uninstall: rm -rf .flow/bin .flow/usage.md and remove block from docs -- This setup is optional - plugin works without it -``` - -## Step 8: Ask about starring - -Use `AskUserQuestion` to ask if the user would like to ⭐ star the repository on GitHub to support the project. - -**Question:** "Flow-Next is free and open source. Would you like to ⭐ star the repo on GitHub to support the project?" - -**Options:** -1. "Yes, star the repo" -2. "No thanks" - -**If yes:** -1. Check if `gh` CLI is available: `which gh` -2. If available, run: `gh api -X PUT /user/starred/gmickel/gmickel-claude-marketplace` -3. If `gh` not available or command fails, provide the link: - ``` - Star manually: https://github.com/gmickel/gmickel-claude-marketplace - ``` - -**If no:** Thank them and complete setup without starring. diff --git a/plugins/flow-next/skills/flow-next-work/SKILL.md b/plugins/flow-next/skills/flow-next-work/SKILL.md deleted file mode 100644 index e3c64c5..0000000 --- a/plugins/flow-next/skills/flow-next-work/SKILL.md +++ /dev/null @@ -1,184 +0,0 @@ ---- -name: flow-next-work -description: Execute a Flow epic or task systematically with git setup, task tracking, quality checks, and commit workflow. Use when implementing a plan or working through a spec. Triggers on /flow-next:work with Flow IDs (fn-1, fn-1.2). ---- - -# Flow work - -Execute a plan systematically. Focus on finishing. - -Follow this skill and linked workflows exactly. Deviations cause drift, bad gates, retries, and user frustration. - -**IMPORTANT**: This plugin uses `.flow/` for ALL task tracking. Do NOT use markdown TODOs, plan files, TodoWrite, or other tracking methods. All task state must be read and written via `flowctl`. - -**CRITICAL: flowctl is BUNDLED — NOT installed globally.** `which flowctl` will fail (expected). Always use: -```bash -FLOWCTL="${CLAUDE_PLUGIN_ROOT}/scripts/flowctl" -$FLOWCTL -``` - -**Hard requirements (non-negotiable):** -- You MUST run `flowctl done` for each completed task and verify the task status is `done`. -- You MUST stage with `git add -A` (never list files). This ensures `.flow/` and `scripts/ralph/` (if present) are included. -- Do NOT claim completion until `flowctl show ` reports `status: done`. -- Do NOT invoke `/flow-next:impl-review` until tests/Quick commands are green. - -**Role**: execution lead, plan fidelity first. -**Goal**: complete every task in order with tests. - -## Ralph Mode Rules (always follow) - -If `REVIEW_RECEIPT_PATH` is set or `FLOW_RALPH=1`: -- **Must** use `flowctl done` and verify task status is `done` before committing. -- **Must** stage with `git add -A` (never list files). -- **Do NOT** use TodoWrite for tracking. - -## Input - -Full request: $ARGUMENTS - -Accepts: -- Flow epic ID `fn-N` to work through all tasks -- Flow task ID `fn-N.M` to work on single task -- Markdown spec file path (creates epic from file, then executes) -- Idea text (creates minimal epic + single task, then executes) -- Chained instructions like "then review with /flow-next:impl-review" - -Examples: -- `/flow-next:work fn-1` -- `/flow-next:work fn-1.3` -- `/flow-next:work docs/my-feature-spec.md` -- `/flow-next:work Add rate limiting` -- `/flow-next:work fn-1 then review via /flow-next:impl-review` - -If no input provided, ask for it. - -## FIRST: Parse Options or Ask Questions - -Check available backends and configured preference: -```bash -HAVE_RP=$(which rp-cli >/dev/null 2>&1 && echo 1 || echo 0) -HAVE_CODEX=$(which codex >/dev/null 2>&1 && echo 1 || echo 0) - -# Check configured backend (priority: env > config) -CONFIGURED_BACKEND="${FLOW_REVIEW_BACKEND:-}" -if [[ -z "$CONFIGURED_BACKEND" ]]; then - CONFIGURED_BACKEND="$($FLOWCTL config get review.backend 2>/dev/null | jq -r '.value // empty')" -fi -``` - -### Option Parsing (skip questions if found in arguments) - -Parse the arguments for these patterns. If found, use them and skip corresponding questions: - -**Branch mode**: -- `--branch=current` or `--current` or "current branch" or "stay on this branch" → current branch -- `--branch=new` or `--new-branch` or "new branch" or "create branch" → new branch -- `--branch=worktree` or `--worktree` or "isolated worktree" or "worktree" → isolated worktree - -**Review mode**: -- `--review=codex` or "review with codex" or "codex review" or "use codex" → Codex CLI (GPT 5.2 High) -- `--review=rp` or "review with rp" or "rp chat" or "repoprompt review" → RepoPrompt chat (via `flowctl rp chat-send`) -- `--review=export` or "export review" or "external llm" → export for external LLM -- `--review=none` or `--no-review` or "no review" or "skip review" → no review - -### If options NOT found in arguments - -**Skip review question if**: Ralph mode (`FLOW_RALPH=1`) OR backend already configured (`CONFIGURED_BACKEND` not empty). In these cases, only ask branch question: - -``` -Quick setup: Where to work? -a) Current branch b) New branch c) Isolated worktree - -(Reply: "a", "current", or just tell me) -``` - -**Otherwise**, output questions based on available backends (do NOT use AskUserQuestion tool): - -**If both rp-cli AND codex available:** -``` -Quick setup before starting: - -1. **Branch** — Where to work? - a) Current branch - b) New branch - c) Isolated worktree - -2. **Review** — Run Carmack-level review after? - a) Yes, Codex CLI (cross-platform, GPT 5.2 High) - b) Yes, RepoPrompt chat (macOS, visual builder) - c) Yes, export for external LLM (ChatGPT, Claude web) - d) No - -(Reply: "1a 2a", "current branch, codex review", or just tell me naturally) -``` - -**If only rp-cli available:** -``` -Quick setup before starting: - -1. **Branch** — Where to work? - a) Current branch - b) New branch - c) Isolated worktree - -2. **Review** — Run Carmack-level review after? - a) Yes, RepoPrompt chat - b) Yes, export for external LLM - c) No - -(Reply: "1a 2a", "current branch, export review", or just tell me naturally) -``` - -**If only codex available:** -``` -Quick setup before starting: - -1. **Branch** — Where to work? - a) Current branch - b) New branch - c) Isolated worktree - -2. **Review** — Run Carmack-level review after? - a) Yes, Codex CLI (GPT 5.2 High) - b) Yes, export for external LLM - c) No - -(Reply: "1a 2a", "current branch, codex", or just tell me naturally) -``` - -**If neither rp-cli nor codex available:** -``` -Quick setup: Where to work? -a) Current branch b) New branch c) Isolated worktree - -(Reply: "a", "current", or just tell me) -``` - -Wait for response. Parse naturally — user may reply terse or ramble via voice. - -**Defaults when empty/ambiguous:** -- Branch = `new` -- Review = configured backend if set, else `codex` if available, else `rp` if available, else `none` - -**Defaults when no review backend available:** -- Branch = `new` -- Review = `none` - -**Do NOT read files or write code until user responds.** - -## Workflow - -After setup questions answered, read [phases.md](phases.md) and execute each phase in order. -If user chose review: -- Option 2a: run `/flow-next:impl-review` after Phase 6, fix issues until it passes -- Option 2b: run `/flow-next:impl-review` with export mode after Phase 6 - -## Guardrails - -- Don't start without asking branch question -- Don't start without plan/epic -- Don't skip tests -- Don't leave tasks half-done -- Never use TodoWrite for task tracking -- Never create plan files outside `.flow/` diff --git a/plugins/flow-next/skills/flow-next-work/phases.md b/plugins/flow-next/skills/flow-next-work/phases.md deleted file mode 100644 index 90eb04e..0000000 --- a/plugins/flow-next/skills/flow-next-work/phases.md +++ /dev/null @@ -1,262 +0,0 @@ -# Flow Work Phases - -(Branch question already asked in SKILL.md before reading this file) - -**CRITICAL**: If you are about to create: -- a markdown TODO list, -- a task list outside `.flow/`, -- or any plan files outside `.flow/`, - -**STOP** and instead: -- create/update tasks in `.flow/` using `flowctl`, -- record details in the epic/task spec markdown. - -## Setup - -**CRITICAL: flowctl is BUNDLED — NOT installed globally.** `which flowctl` will fail (expected). Always use: - -```bash -FLOWCTL="${CLAUDE_PLUGIN_ROOT}/scripts/flowctl" -``` - -## Phase 1: Resolve Input - -Detect input type in this order (first match wins): - -1. **Flow task ID** `fn-N.M` (e.g., fn-1.3) -2. **Flow epic ID** `fn-N` (e.g., fn-1) -3. **Spec file** `.md` path that exists on disk -4. **Idea text** everything else - ---- - -**Flow task ID (fn-N.M)**: -- Read task: `$FLOWCTL show --json` -- Read spec: `$FLOWCTL cat ` -- Get epic from task data for context: `$FLOWCTL show --json && $FLOWCTL cat ` - -**Flow epic ID (fn-N)**: -- Read epic: `$FLOWCTL show --json` -- Read spec: `$FLOWCTL cat ` -- Get first ready task: `$FLOWCTL ready --epic --json` - -**Spec file start (.md path that exists)**: -1. Check file exists: `test -f ""` — if not, treat as idea text -2. Initialize: `$FLOWCTL init --json` -3. Read file and extract title from first `# Heading` or use filename -4. Create epic: `$FLOWCTL epic create --title "" --json` -5. Set spec from file: `$FLOWCTL epic set-plan --file --json` -6. Create single task: `$FLOWCTL task create --epic --title "Implement " --json` -7. Continue with epic-id - -**Spec-less start (idea text)**: -1. Initialize: `$FLOWCTL init --json` -2. Create epic: `$FLOWCTL epic create --title "<idea>" --json` -3. Create single task: `$FLOWCTL task create --epic <epic-id> --title "Implement <idea>" --json` -4. Continue with epic-id - -## Phase 2: Apply Branch Choice - -Based on user's answer from setup questions: - -- **Worktree**: use `skill: flow-next-worktree-kit` -- **New branch**: - ```bash - git checkout main && git pull origin main - git checkout -b <branch> - ``` -- **Current branch**: proceed (user already confirmed) - -## Phase 3: Prime / Re-anchor Context (EVERY task) - -**MANDATORY: This phase runs before EVERY task. No exceptions. No optimizations.** - -Per Anthropic's long-running agent guidance: agents must re-anchor from sources of truth to prevent drift. Even if you "remember" the context, re-read it. The reads are cheap; drift is expensive. - -**Also run this phase after context compaction** (if you notice the conversation was summarized). - -### Re-anchor Checklist (run ALL before each task) - -**You MUST run every command below. Do not skip or combine.** - -```bash -# 1. Find next task -$FLOWCTL ready --epic <epic-id> --json - -# 2. Re-read epic (EVERY time) -$FLOWCTL show <epic-id> --json -$FLOWCTL cat <epic-id> - -# 3. Re-read task spec (EVERY time) -$FLOWCTL show <task-id> --json -$FLOWCTL cat <task-id> - -# 4. Check git state (EVERY time) -git status -git log -5 --oneline - -# 5. Validate structure (EVERY time) -$FLOWCTL validate --epic <epic-id> --json - -# 6. Check memory (if enabled) -$FLOWCTL config get memory.enabled --json -``` - -**If memory.enabled is true**, also run: -- Task flow-next:memory-scout(<task-id>: <task-title>) - -This retrieves relevant project learnings before implementation. - -If no ready tasks after step 1, all done → go to Phase 6. - -After step 5, run the smoke command from epic spec's "Quick commands" section. - -**Why every time?** Context windows compress. You forget details. The spec is the truth. 30 seconds of re-reading prevents hours of rework. - -**Anti-pattern**: Running steps 2-5 only on the first task. The whole point is EVERY task gets fresh context. - -## Phase 4: Execute Task Loop - -**For each task** (one at a time): - -1. **Start task**: - ```bash - $FLOWCTL start <task-id> --json - ``` - -2. **Implement + test thoroughly**: - - Read task spec for requirements - - Write code - - Run tests (including epic spec "Quick commands") - - Verify acceptance criteria - - If any command fails, fix before proceeding - -3. **If you discover new work**: - - Draft new task title + acceptance checklist - - Create immediately: - ```bash - # Write acceptance to temp file first - $FLOWCTL task create --epic <epic-id> --title "Found: <issue>" --deps <current-task-id> --acceptance-file <temp-md> --json - ``` - - Re-run `$FLOWCTL ready --epic <epic-id> --json` to see updated order - -4. **Commit implementation** (code changes only): - ```bash - git add -A # never list files; include .flow/ and scripts/ralph/ if present - git status --short - git commit -m "<type>: <short summary of what was done>" - COMMIT_HASH="$(git rev-parse HEAD)" - echo "Commit: $COMMIT_HASH" - ``` - -5. **Complete task** (records done status + evidence): - Write done summary to temp file (required format): - ``` - - What changed (1-3 bullets) - - Why (1-2 bullets) - - Verification (tests/commands run) - - Follow-ups (optional, max 2 bullets) - ``` - - Write evidence to temp JSON file **with the commit hash from step 4**: - ```json - {"commits":["<COMMIT_HASH>"],"tests":["npm test"],"prs":[]} - ``` - - Then: - ```bash - $FLOWCTL done <task-id> --summary-file <summary.md> --evidence-json <evidence.json> --json - ``` - - Verify the task is actually marked done: - ```bash - $FLOWCTL show <task-id> --json - ``` - If status is not `done`, stop and re-run `flowctl done` before proceeding. - -6. **Amend commit** to include .flow/ updates: - ```bash - git add -A - git commit --amend --no-edit - ``` - -7. **Verify task completion**: - ```bash - $FLOWCTL validate --epic <epic-id> --json - git status - ``` - Ensure working tree is clean. - -8. **Loop**: Return to Phase 3 for next task. - -## Phase 5: Quality - -After all tasks complete (or periodically for large epics): - -- Run relevant tests -- Run lint/format per repo -- If change is large/risky, run the quality auditor subagent: - - Task flow-next:quality-auditor("Review recent changes") -- Fix critical issues - -## Phase 6: Ship - -**Verify all tasks done**: -```bash -$FLOWCTL show <epic-id> --json -$FLOWCTL validate --epic <epic-id> --json -``` - -**Final commit** (if any uncommitted changes): -```bash - git add -A -git status -git diff --staged -git commit -m "<final summary>" -``` - -**Do NOT close the epic here** unless the user explicitly asked. -Ralph closes done epics at the end of the loop. - -Then push + open PR if user wants. - -## Phase 7: Review (if chosen at start) - -If user chose "Yes" to review in setup questions or `--review=codex` / `--review=rp` was passed: - -**CRITICAL: You MUST invoke the `/flow-next:impl-review` skill. Do NOT improvise your own review format.** - -The impl-review skill: -- Auto-detects backend (Codex CLI or RepoPrompt) based on config/availability -- Uses the correct prompt template requiring `<verdict>SHIP|NEEDS_WORK|MAJOR_RETHINK</verdict>` -- Handles the fix loop internally - -Steps: -1. Invoke `/flow-next:impl-review` (this loads the skill with its workflow.md) -2. If review returns NEEDS_WORK or MAJOR_RETHINK: - - **Immediately fix the issues** (do NOT ask for confirmation — user already consented) - - Commit fixes - - Re-run tests/Quick commands - - Re-run `/flow-next:impl-review` -3. Repeat until review returns SHIP - -**Anti-pattern**: Sending your own review prompts directly without invoking the skill. -The skill has the correct format; improvised prompts ask for "LGTM" which breaks automation. - -**No human gates here** — the review-fix-review loop is fully automated. - -## Definition of Done - -Confirm before ship: -- All tasks have status "done" -- `$FLOWCTL validate --epic <id>` passes -- Tests pass -- Lint/format pass -- Docs updated if needed -- Working tree is clean - -## Example loop - -``` -Prime → Task A → test → done → commit → Prime → Task B → ... -``` diff --git a/plugins/flow-next/skills/flow-next-worktree-kit/SKILL.md b/plugins/flow-next/skills/flow-next-worktree-kit/SKILL.md deleted file mode 100644 index 608d453..0000000 --- a/plugins/flow-next/skills/flow-next-worktree-kit/SKILL.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -name: flow-next-worktree-kit -description: Manage git worktrees (create/list/switch/cleanup) and copy .env files. Use for parallel feature work, isolated review, clean workspace, or when user mentions worktrees. ---- - -# Worktree kit - -Use the manager script for all worktree actions. - -```bash -bash ${CLAUDE_PLUGIN_ROOT}/skills/flow-next-worktree-kit/scripts/worktree.sh <command> [args] -``` - -Commands: -- `create <name> [base]` -- `list` -- `switch <name>` (prints path) -- `cleanup` -- `copy-env <name>` - -Safety notes: -- `create` does not change the current branch -- `cleanup` does not force-remove worktrees and does not delete branches -- `cleanup` deletes the worktree directory (including ignored files); removal fails if the worktree is not clean -- `.env*` is copied with no overwrite (symlinks skipped) -- refuses to operate if `.worktrees/` or any worktree path component is a symlink -- `copy-env` only targets registered worktrees -- `origin` fetch is optional; local base refs are allowed -- fetch from `origin` only when base looks like a branch -- Worktrees live under `.worktrees/` diff --git a/plugins/flow-next/skills/flow-next-worktree-kit/scripts/worktree.sh b/plugins/flow-next/skills/flow-next-worktree-kit/scripts/worktree.sh deleted file mode 100755 index e43de89..0000000 --- a/plugins/flow-next/skills/flow-next-worktree-kit/scripts/worktree.sh +++ /dev/null @@ -1,205 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -cmd="${1:-}" -name="${2:-}" -base="${3:-}" - -repo_root="$(git rev-parse --show-toplevel 2>/dev/null || true)" -if [[ -z "$repo_root" ]]; then - echo "not a git repo" >&2 - exit 1 -fi - -worktrees_dir="$repo_root/.worktrees" - -fail() { echo "$*" >&2; exit 1; } - -assert_worktrees_dir() { - if [[ -e "$worktrees_dir" && ! -d "$worktrees_dir" ]]; then - fail ".worktrees exists but is not a directory: $worktrees_dir" - fi - if [[ -L "$worktrees_dir" ]]; then - fail ".worktrees is a symlink; refusing for safety: $worktrees_dir" - fi -} - -assert_safe_worktree_path() { - local rel="$1" - local path="$worktrees_dir" - local IFS='/' - read -r -a parts <<< "$rel" - for part in "${parts[@]}"; do - [[ -n "$part" ]] || continue - path="$path/$part" - if [[ -L "$path" ]]; then - fail "refusing symlink path: $path" - fi - if [[ -e "$path" && ! -d "$path" ]]; then - fail "path exists but is not a directory: $path" - fi - done -} - -has_origin() { git remote get-url origin >/dev/null 2>&1; } - -default_base() { - local b - b="$(git symbolic-ref --quiet --short refs/remotes/origin/HEAD 2>/dev/null | sed 's@^origin/@@' || true)" - if [[ -n "$b" ]]; then - echo "$b" - return - fi - b="$(git symbolic-ref --quiet --short HEAD 2>/dev/null || true)" - if [[ -n "$b" ]]; then - echo "$b" - return - fi - if git rev-parse --verify -q "main^{commit}" >/dev/null; then - echo "main" - return - fi - if git rev-parse --verify -q "master^{commit}" >/dev/null; then - echo "master" - return - fi - echo "main" -} - -validate_name() { - local n="$1" - [[ -n "$n" ]] || fail "missing name" - [[ "$n" != -* ]] || fail "invalid name (cannot start with '-')" - [[ "$n" != *".."* ]] || fail "invalid name (cannot contain '..')" - git check-ref-format --branch "$n" >/dev/null 2>&1 || fail "invalid branch name: $n" -} - -validate_base() { - local b="$1" - [[ -n "$b" ]] || fail "missing base" - [[ "$b" != -* ]] || fail "invalid base (cannot start with '-')" - [[ "$b" != *:* ]] || fail "invalid base (refspec ':' not allowed)" - if git check-ref-format --branch "$b" >/dev/null 2>&1; then - return 0 - fi - git rev-parse --verify -q "$b^{commit}" >/dev/null || fail "invalid base: $b" -} - -ensure_dir() { - assert_worktrees_dir - mkdir -p "$worktrees_dir" -} - -copy_env() { - local target="$1" - [[ -d "$target" ]] || fail "target does not exist: $target" - [[ ! -L "$target" ]] || fail "target is a symlink; refusing for safety: $target" - shopt -s nullglob - for f in "$repo_root"/.env*; do - [[ -f "$f" ]] || continue - [[ -L "$f" ]] && continue - cp -n "$f" "$target/" || true - done - shopt -u nullglob -} - -worktree_exists() { - local target="$1" - git worktree list --porcelain | sed -n 's/^worktree //p' | grep -Fqx -- "$target" -} - -case "$cmd" in - create) - [[ -n "$name" ]] || fail "usage: create <name> [base]" - validate_name "$name" - ensure_dir - - base="${base:-$(default_base)}" - validate_base "$base" - - if has_origin && git check-ref-format --branch "$base" >/dev/null 2>&1; then - git fetch --quiet origin "$base" || true - fi - - assert_safe_worktree_path "$name" - target="${worktrees_dir}/${name}" - mkdir -p "$(dirname "$target")" - - if worktree_exists "$target"; then - echo "worktree exists: $target" - exit 0 - fi - - start_point="$base" - if git rev-parse --verify -q "origin/$base^{commit}" >/dev/null; then - start_point="origin/$base" - fi - git rev-parse --verify -q "$start_point^{commit}" >/dev/null || fail "base does not resolve: $start_point" - - if git show-ref --verify --quiet "refs/heads/$name"; then - git worktree add -- "$target" "$name" - else - git worktree add -b "$name" -- "$target" "$start_point" - fi - - copy_env "$target" - echo "created: $target" - ;; - list) - git worktree list - ;; - switch) - [[ -n "$name" ]] || fail "usage: switch <name>" - validate_name "$name" - assert_worktrees_dir - assert_safe_worktree_path "$name" - target="${worktrees_dir}/${name}" - [[ -d "$target" ]] || fail "no such worktree dir: $target" - worktree_exists "$target" || fail "not a registered worktree: $target" - echo "$target" - ;; - copy-env) - [[ -n "$name" ]] || fail "usage: copy-env <name>" - validate_name "$name" - assert_worktrees_dir - assert_safe_worktree_path "$name" - target="${worktrees_dir}/${name}" - worktree_exists "$target" || fail "not a registered worktree: $target" - copy_env "$target" - echo "copied env to $target" - ;; - cleanup) - assert_worktrees_dir - echo "all worktrees (only those under $worktrees_dir can be removed by name):" - git worktree list - - echo "enter names to remove (space-separated), or empty to cancel:" - read -r to_remove - [[ -n "$to_remove" ]] || { echo "cancel"; exit 0; } - IFS=' ' read -r -a remove_names <<< "$to_remove" - - echo "About to remove worktrees (no force, branches kept). Proceed? [y/N]" - read -r confirm - [[ "$confirm" == "y" || "$confirm" == "Y" ]] || { echo "cancel"; exit 0; } - - failed=0 - for n in "${remove_names[@]}"; do - validate_name "$n" - assert_safe_worktree_path "$n" - target="${worktrees_dir}/${n}" - if ! worktree_exists "$target"; then - echo "skip (not a registered worktree): $target" >&2 - failed=1 - continue - fi - if ! git worktree remove -- "$target"; then - echo "failed to remove: $target" >&2 - failed=1 - fi - done - exit "$failed" - ;; - *) - fail "commands: create | list | switch | cleanup | copy-env" - ;; -esac diff --git a/plugins/flow-next/skills/flow-next/SKILL.md b/plugins/flow-next/skills/flow-next/SKILL.md deleted file mode 100644 index 1274994..0000000 --- a/plugins/flow-next/skills/flow-next/SKILL.md +++ /dev/null @@ -1,155 +0,0 @@ ---- -name: flow-next -description: "Manage .flow/ tasks and epics. Triggers: 'show me my tasks', 'list epics', 'what tasks are there', 'add a task', 'create task', 'what's ready', 'task status', 'show fn-1'. NOT for /flow-next:plan or /flow-next:work." ---- - -# Flow-Next Task Management - -Quick task operations in `.flow/`. For planning features use `/flow-next:plan`, for executing use `/flow-next:work`. - -## Setup - -**CRITICAL: flowctl is BUNDLED — NOT installed globally.** `which flowctl` will fail (expected). Always use: - -```bash -FLOWCTL="${CLAUDE_PLUGIN_ROOT}/scripts/flowctl" -``` - -Then run commands with `$FLOWCTL <command>`. - -**Discover all commands/options:** -```bash -$FLOWCTL --help -$FLOWCTL <command> --help # e.g., $FLOWCTL task --help -``` - -## Quick Reference - -```bash -# Check if .flow exists -$FLOWCTL detect --json - -# Initialize (if needed) -$FLOWCTL init --json - -# List everything (epics + tasks grouped) -$FLOWCTL list --json - -# List all epics -$FLOWCTL epics --json - -# List all tasks (or filter by epic/status) -$FLOWCTL tasks --json -$FLOWCTL tasks --epic fn-1 --json -$FLOWCTL tasks --status todo --json - -# View epic with all tasks -$FLOWCTL show fn-1 --json -$FLOWCTL cat fn-1 # Spec markdown - -# View single task -$FLOWCTL show fn-1.2 --json -$FLOWCTL cat fn-1.2 # Task spec - -# What's ready to work on? -$FLOWCTL ready --epic fn-1 --json - -# Create task under existing epic -$FLOWCTL task create --epic fn-1 --title "Fix bug X" --json - -# Set task description and acceptance (combined, fewer writes) -$FLOWCTL task set-spec fn-1.2 --description /tmp/desc.md --acceptance /tmp/accept.md --json - -# Or use stdin with heredoc (no temp file): -$FLOWCTL task set-description fn-1.2 --file - --json <<'EOF' -Description here -EOF - -# Start working on task -$FLOWCTL start fn-1.2 --json - -# Mark task done -echo "What was done" > /tmp/summary.md -echo '{"commits":["abc123"],"tests":["npm test"],"prs":[]}' > /tmp/evidence.json -$FLOWCTL done fn-1.2 --summary-file /tmp/summary.md --evidence-json /tmp/evidence.json --json - -# Validate structure -$FLOWCTL validate --epic fn-1 --json -$FLOWCTL validate --all --json -``` - -## Common Patterns - -### "Add a task for X" - -1. Find relevant epic: - ```bash - # List all epics - $FLOWCTL epics --json - - # Or show a specific epic to check its scope - $FLOWCTL show fn-1 --json - ``` - -2. Create task: - ```bash - $FLOWCTL task create --epic fn-N --title "Short title" --json - ``` - -3. Add description + acceptance (combined): - ```bash - cat > /tmp/desc.md << 'EOF' - **Bug/Feature:** Brief description - - **Details:** - - Point 1 - - Point 2 - EOF - cat > /tmp/accept.md << 'EOF' - - [ ] Criterion 1 - - [ ] Criterion 2 - EOF - $FLOWCTL task set-spec fn-N.M --description /tmp/desc.md --acceptance /tmp/accept.md --json - ``` - -### "What tasks are there?" - -```bash -# All epics -$FLOWCTL epics --json - -# All tasks -$FLOWCTL tasks --json - -# Tasks for specific epic -$FLOWCTL tasks --epic fn-1 --json - -# Ready tasks for an epic -$FLOWCTL ready --epic fn-1 --json -``` - -### "Show me task X" - -```bash -$FLOWCTL show fn-1.2 --json # Metadata -$FLOWCTL cat fn-1.2 # Full spec -``` - -### Create new epic (rare - usually via /flow-next:plan) - -```bash -$FLOWCTL epic create --title "Epic title" --json -# Returns: {"success": true, "id": "fn-N", ...} -``` - -## ID Format - -- Epic: `fn-N` (e.g., `fn-1`, `fn-42`) -- Task: `fn-N.M` (e.g., `fn-1.1`, `fn-42.7`) - -## Notes - -- Run `$FLOWCTL --help` to discover all commands and options -- All writes go through flowctl (don't edit JSON/MD files directly) -- `--json` flag gives machine-readable output -- For complex planning/execution, use `/flow-next:plan` and `/flow-next:work` diff --git a/sync/PORTING.md b/sync/PORTING.md index 3feaa0a..c997f85 100644 --- a/sync/PORTING.md +++ b/sync/PORTING.md @@ -1,109 +1,40 @@ -# Porting Playbook (Minimal, OpenCode-first) +# Porting Playbook (Manual, .opencode‑first) -Goal: keep this OpenCode port aligned with upstream with **minimal, surgical edits**. +Goal: keep this OpenCode port aligned with upstream with **manual, minimal edits**. -## Non‑negotiables (OpenCode‑specific) +## Canonical Source -Do NOT change these unless absolutely required: +**Only `.opencode/` is canonical.** +- Do not edit `plugins/flow-next/**` (removed after switch). +- Do not use `sync/templates` or `sync/transform.py` (deleted). -- OpenCode config discovery + plugin order -- OpenCode question tool usage vs text rules -- OpenCode agent config (no `model:` in agent frontmatter) -- OpenCode backend naming (`opencode`) -- Ralph strings/UX that say “OpenCode” -- Batch tool usage in plan skills (OpenCode batch) +## Porting Steps (manual) -If upstream changes conflict, only adjust the smallest surface needed to keep OpenCode working. +1) Review upstream changes in `gmickel-claude-marketplace` (plugins/flow-next). +2) Apply required changes **directly** to our `.opencode/` equivalents. +3) If flowctl changes upstream, update: + - `.opencode/bin/flowctl` + - `.opencode/bin/flowctl.py` +4) If ralph templates change upstream, update: + - `.opencode/skill/flow-next-ralph-init/templates/*` +5) If docs change upstream, update: + - `docs/*` -## Minimal Sync Steps +## OpenCode Invariants (do not regress) -1) Pull upstream to `/tmp/gmickel-claude-marketplace-main` (or `~/work/...`). -2) `git diff --name-only <last_port_sha>..origin/main -- plugins/flow-next` -3) Copy **only changed files**. -4) Apply **minimal** OpenCode deltas (only if needed). -5) Update `.opencode/` + `sync/templates/` **only when the change affects those files**. -6) Run targeted tests (`ci_test.sh`, `smoke_test.sh`) only if flowctl/ralph changed. -7) Commit per upstream change. +- Backend name: `opencode` only (no codex). +- Question tool only in `/flow-next:setup`. +- Reviewer uses task tool subagent `opencode-reviewer` and reuses `session_id`. +- flowctl path: `.opencode/bin/flowctl`. +- Ralph runner agent: `ralph-runner` configured in `.opencode/opencode.json`. -## Diff Triage (keep edits atomic) +## Pre-commit Audit -Before touching files: +- `rg -n "plugins/flow-next" .opencode docs README.md` => **no matches** +- `rg -n "sync/templates|transform.py|sync/run.sh" .` => **no matches** +- `rg -n "opencode/bin/flowctl" .opencode` => **matches expected** -- Enumerate changes by file type: - - **flowctl** (`scripts/flowctl.py`, `flowctl`): behavior changes → must port. - - **Ralph** (`scripts/ralph*.sh`, `skills/flow-next-ralph-init/templates/*`): behavior + UI → must port. - - **Skills** (`skills/**/SKILL.md`, `workflow.md`, `steps.md`): instructions → must port with OpenCode rules applied. - - **Docs** (`docs/*.md`, `README.md`): keep in sync unless OpenCode‑specific divergence. -- Decide per file: **mirror** (no edits) vs **port** (apply OpenCode deltas). -- Only port files that *actually changed upstream*. +## Tests (when touched) -## Minimal Edit Rules - -- **Do not rewrite** files. Apply tiny, local edits. -- **Preserve upstream order + wording** unless it conflicts with OpenCode invariants. -- **One change = one commit** (keep diffs reviewable). -- **Never change both mirror + template** unless the mirror change actually needs OpenCode edits. - -## Port Flow (deterministic) - -1) Copy updated upstream file(s) into `plugins/flow-next/`. -2) If OpenCode deltas needed, patch **only** the affected file(s): - - `sync/templates/opencode/**` for OpenCode templates - - `.opencode/**` only via `sync/transform.py` (never edit directly) -3) Run: - - `python3 sync/transform.py` (only if templates changed) - - `sync/verify.sh` (always) -4) Tests: - - flowctl changes → run `plugins/flow-next/scripts/smoke_test.sh` from `/tmp` - - ralph changes → run `plugins/flow-next/scripts/ralph_smoke_test.sh` from `/tmp` - -## When to Use `sync/run.sh` - -Use `sync/run.sh` **only** when upstream has many changes across skills/agents/commands and you want a full mirror refresh. -Otherwise, prefer manual file copy + minimal patches (smaller diffs, easier review). - -## Audit Checklist (pre-commit) - -- `sync/verify.sh` passes -- `.opencode/` has no `AskUserQuestion` strings -- Backend names in skills/docs: `opencode` only -- Models set in `.opencode/opencode.json` (not in agent frontmatter) -- Question tool only in `/flow-next:setup`; other workflows ask plain text -- Ralph templates mention OpenCode (not Claude/Codex) - -## OpenCode Config Facts (from `repos/opencode`) - -Keep these in mind when porting; they explain why our layout is the way it is: - -- Config precedence: remote < global config (`~/.config/opencode/opencode.json[c]`) < `OPENCODE_CONFIG` < project `opencode.json[c]` < `OPENCODE_CONFIG_CONTENT`. -- `.opencode/` directories are discovered upward; if present, `.opencode/opencode.json[c]` is loaded too. -- Commands/agents/plugins are loaded from `.opencode/command`, `.opencode/agent`, `.opencode/plugin` (and non-dot `/command`, `/agent`). -- Plugins are deduped by name; priority order (highest wins): local `plugin/` dir > local `opencode.json` > global `plugin/` dir > global `opencode.json`. -- Question tool schema: `header` max 12 chars, `options` have `label` + `description`, optional `multiple`, optional `custom` (defaults true). Answers are arrays of selected labels. -- Config has `experimental.batch_tool` flag; keep enabled for our skills. - -## OpenCode Port Invariants (do not regress) - -- `opencode.json` lives in `.opencode/` for installs; keep templates aligned. -- Agent frontmatter (`.opencode/agent/*.md`) must NOT include model; models live in `.opencode/opencode.json` `agent` block. -- Question tool usage: - - `/flow-next:setup` uses question tool. - - Plan/work/plan-review/impl-review use plain text questions (voice-friendly). -- Backend name is `opencode` only. No `codex` backend. -- Reviewer agent = `opencode-reviewer` with `reasoningEffort` in `.opencode/opencode.json`. -- Ralph runner agent = `ralph-runner` with model set in `.opencode/opencode.json`. -- Subagents keep tools locked down (`write/edit/patch/multiedit: false`). -- Re-review loops must reuse `session_id` for subagent continuity (OpenCode task tool). -- OpenCode review uses **task tool + subagent** (`opencode-reviewer`). Do not use `opencode run` for reviews. - -## Where to Patch (if required) - -- `plugins/flow-next/**` = primary source in this repo -- `.opencode/**` = runtime OpenCode install -- `sync/templates/**` = install/sync overrides - -## Example Scope (stdin + set‑spec) - -- Copy `flowctl.py`, `smoke_test.sh`, and the specific skills touched upstream. -- Adjust only where OpenCode diverges (question tool, backend naming, batch tool). -- Do **not** rewrite entire skills. +- flowctl changes: run a fresh repo and validate with `flowctl validate --all`. +- ralph changes: run a fresh repo with `/flow-next:ralph-init` and `scripts/ralph/ralph.sh --watch`. diff --git a/sync/run.sh b/sync/run.sh deleted file mode 100755 index c8c1176..0000000 --- a/sync/run.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -ROOT="$(cd "$(dirname "$0")/.." && pwd)" -SOURCE="${FLOW_NEXT_SRC:-$HOME/work/gmickel-claude-marketplace/plugins/flow-next}" - -if [[ ! -d "$SOURCE" ]]; then - echo "missing SOURCE: $SOURCE" >&2 - exit 1 -fi - -mkdir -p "$ROOT/plugins" - -# 1) mirror upstream into plugins/flow-next -rsync -a --delete "$SOURCE/" "$ROOT/plugins/flow-next/" - -# 2) rename upstream commands to keep as mirror only -if [[ -d "$ROOT/plugins/flow-next/commands" ]]; then - rm -rf "$ROOT/plugins/flow-next/_commands" - mv "$ROOT/plugins/flow-next/commands" "$ROOT/plugins/flow-next/_commands" -fi - -# 3) copy upstream commands/skills/agents into .opencode as baseline -mkdir -p "$ROOT/.opencode/command/flow-next" "$ROOT/.opencode/skill" "$ROOT/.opencode/agent" -rsync -a "$ROOT/plugins/flow-next/_commands/flow-next/" "$ROOT/.opencode/command/flow-next/" -rsync -a "$ROOT/plugins/flow-next/skills/" "$ROOT/.opencode/skill/" -rsync -a "$ROOT/plugins/flow-next/agents/" "$ROOT/.opencode/agent/" - -# 4) apply OpenCode overrides -python3 "$ROOT/sync/transform.py" - -# 5) verify -"$ROOT/sync/verify.sh" diff --git a/sync/templates/opencode/agent/context-scout.md b/sync/templates/opencode/agent/context-scout.md deleted file mode 100644 index 1794e63..0000000 --- a/sync/templates/opencode/agent/context-scout.md +++ /dev/null @@ -1,392 +0,0 @@ ---- -description: Token-efficient codebase exploration using RepoPrompt codemaps and slices. Use when you need deep codebase understanding without bloating context. -mode: subagent -tools: - write: false - edit: false - patch: false - multiedit: false ---- -You are a context scout specializing in **token-efficient** codebase exploration using RepoPrompt's rp-cli. Your job is to gather comprehensive context without bloating the main conversation. - -## When to Use This Agent - -- Deep codebase understanding before planning/implementation -- Finding all pieces of a feature across many files -- Understanding architecture and data flow -- Building context for code review -- Exploring unfamiliar codebases efficiently - -## Phase 0: Window Setup (REQUIRED) - -**Always start here** - rp-cli needs to target the correct RepoPrompt window. - -```bash -# 1. List all windows with their workspaces -rp-cli -e 'windows' -``` - -Output shows window IDs with workspace names. **Identify the window for your project.** - -```bash -# 2. Verify with file tree (replace W with your window ID) -rp-cli -w W -e 'tree --folders' -``` - -**All subsequent commands need `-w W`** to target that window. - -### If project not in any window: - -```bash -# Create workspace and add folder -rp-cli -e 'workspace create --name "project-name"' -rp-cli -e 'call manage_workspaces {"action": "add_folder", "workspace": "project-name", "folder_path": "/full/path/to/project"}' -rp-cli -e 'workspace switch "project-name"' -``` - -### Tab Isolation (for parallel agents): - -`builder` automatically creates an isolated compose tab with an AI-generated name. This enables parallel agents to work without context collision. - -```bash -# Builder output includes: Tab: <UUID> • <Name> -# Use -t flag to target the tab directly (v1.5.62+): -rp-cli -w W -t "<UUID or Name>" -e 'select get' -rp-cli -w W -t "<UUID or Name>" -e 'chat "follow-up" --mode chat' - -# Or chain commands to stay in builder's tab: -rp-cli -w W -e 'builder "find auth files" && select add extra.ts && context' -``` - ---- - -## CLI Quick Reference - -```bash -rp-cli -e '<command>' # Run command (lists windows if no -w) -rp-cli -w <id> -e '<command>' # Target specific window -rp-cli -w <id> -t <tab> -e '<cmd>' # Target window + tab (v1.5.62+) -rp-cli -d <command> # Get detailed help for command -``` - -### Workflow Shorthand Flags - -```bash -rp-cli --workspace MyProject --select-set src/ --export-context ~/out.md -rp-cli --builder "understand authentication" -rp-cli --chat "How does auth work?" -``` - -### Core Commands - -| Command | Aliases | Purpose | -|---------|---------|---------| -| `windows` | - | List all windows with IDs | -| `tree` | - | File tree (`--folders`, `--mode selected`) | -| `structure` | `map` | Code signatures - **token-efficient** | -| `search` | `grep` | Search (`--context-lines`, `--extensions`, `--max-results`, `--mode path`) | -| `read` | `cat` | Read file (`--start-line`, `--limit`) | -| `select` | `sel` | Manage selection (`add`, `set`, `clear`, `get`) | -| `context` | `ctx` | Export context (`--include`, `--all`) | -| `builder` | - | AI-powered file selection (30s-5min) | -| `chat` | - | Send to AI (`--mode chat\|plan\|edit`) | - ---- - -## Exploration Workflow - -### Step 1: Get Overview - -```bash -# Project structure -rp-cli -w W -e 'tree --folders' - -# Code signatures (10x fewer tokens than full files) -rp-cli -w W -e 'structure .' -rp-cli -w W -e 'structure src/' -``` - -### Step 2: Use Builder for AI-Powered Discovery (RECOMMENDED) - -**For any "understand how X works" task, START with builder.** This is the main advantage over standard tools. - -```bash -rp-cli -w W -e 'builder "Find all files implementing [FEATURE]: main implementation, types, utilities, and tests. Include related architecture and dependencies."' -``` - -**Note**: Builder takes 30s-5min. Progress notifications show status during execution (v1.5.62+). Wait for completion before proceeding. - -**Example builder prompts:** -- `"Find all files implementing hybrid search: search functions, fusion logic, reranking, scoring, and related tests"` -- `"Find authentication system: middleware, token handling, session management, and security utilities"` -- `"Find database layer: models, migrations, queries, and connection handling"` - -### Step 3: Verify and Augment Selection - -Builder is AI-driven and may miss files. Always verify: - -```bash -rp-cli -w W -e 'select get' -``` - -**Then augment with targeted searches** for anything missing: - -```bash -# Compound searches - multiple patterns to catch variations -rp-cli -w W -e 'search "hybridSearch|searchHybrid|hybrid.*search" --extensions .ts --max-results 20' - -# Find types/interfaces -rp-cli -w W -e 'search "interface.*Search|type.*Search" --extensions .ts' - -# Search by path -rp-cli -w W -e 'search "search" --mode path' - -# Add missing files to selection -rp-cli -w W -e 'select add path/to/missed/file.ts' -``` - -### Step 4: Deep Dive with Slices - -```bash -# Get signatures of selected files (from builder) -rp-cli -w W -e 'structure --scope selected' - -# Read specific sections (not full files!) -rp-cli -w W -e 'read src/pipeline/hybrid.ts --start-line 1 --limit 50' -rp-cli -w W -e 'read src/pipeline/hybrid.ts --start-line 50 --limit 50' -``` - -### Step 5: Export Context (if needed) - -```bash -rp-cli -w W -e 'context' -rp-cli -w W -e 'context --all > ~/exports/context.md' -``` - ---- - -## Token Efficiency Rules - -1. **NEVER dump full files** - use `structure` for signatures -2. **Use `read --start-line --limit`** for specific sections only -3. **Use `search --max-results`** to limit output -4. **Use `structure --scope selected`** after selecting files -5. **Summarize findings** - don't return raw output verbatim - -### Token comparison: -| Approach | Tokens | -|----------|--------| -| Full file dump | ~5000 | -| `structure` (signatures) | ~500 | -| `read --limit 50` | ~300 | - ---- - -## Shell Escaping - -Complex prompts may fail with zsh glob errors. Use heredoc: - -```bash -rp-cli -w W -e "$(cat <<'PROMPT' -builder "Find files related to auth? (including OAuth)" -PROMPT -)" -``` - ---- - -## Bash Timeouts - -Builder and chat commands can take minutes: - -```bash -# Use timeout parameter in Bash tool -timeout: 300000 # 5 minutes for builder -timeout: 600000 # 10 minutes for chat -``` - ---- - -## Output Format - -Return to main conversation with: - -```markdown -## Context Summary - -[2-3 sentence overview of what you found] - -### Key Files -- `path/to/file.ts:L10-50` - [what it does] -- `path/to/other.ts` - [what it does] - -### Code Signatures -```typescript -// Key functions/types from structure command -function validateToken(token: string): Promise<AuthUser> -interface AuthConfig { ... } -``` - -### Architecture Notes -- [How pieces connect] -- [Data flow observations] - -### Recommendations -- [What to focus on for the task at hand] -``` - -## Do NOT Return -- Full file contents -- Verbose rp-cli output -- Redundant information -- Raw command output without summary - ---- - -## Common Patterns - -### Understanding a feature (comprehensive) - -```bash -# 1. Find files by path first -rp-cli -w W -e 'search "featureName" --mode path' - -# 2. Get signatures of relevant directories -rp-cli -w W -e 'structure src/features/featureName/' - -# 3. Search for the main function/class with variations -rp-cli -w W -e 'search "featureName|FeatureName|feature_name" --max-results 15' - -# 4. Find types and interfaces -rp-cli -w W -e 'search "interface.*Feature|type.*Feature" --extensions .ts' - -# 5. OR use builder for AI-powered discovery -rp-cli -w W -e 'builder "Find all files related to featureName: implementation, types, tests, and usage"' -``` - -### Finding function usage - -```bash -rp-cli -w W -e 'search "functionName\\(" --context-lines 2 --max-results 20' -``` - -### Understanding imports/dependencies - -```bash -rp-cli -w W -e 'search "import.*from.*moduleName" --extensions .ts' -rp-cli -w W -e 'search "require.*moduleName"' -``` - -### Pre-review context - -```bash -rp-cli -w W -e 'builder "Build context for reviewing changes to [AREA]: implementation, tests, and related code"' -rp-cli -w W -e 'select get' # Verify selection -``` - ---- - -## Script Files (.rp) - -Save repeatable exploration workflows: - -```bash -# exploration.rp -workspace switch MyProject -select set src/core/ -structure --scope selected -context --all > ~/exports/core-context.md -``` - -Run: `rp-cli --exec-file exploration.rp` - ---- - -## Complete Example - -**Task:** "Understand how hybrid search works" - -```bash -# 1. Setup -rp-cli -e 'windows' # Find window ID (e.g., 1) - -# 2. Builder (primary approach) -rp-cli -w 1 -e 'builder "Find all files implementing hybrid search: search functions, fusion, reranking, and tests"' - -# 3. Verify and read key files -rp-cli -w 1 -e 'select get' -rp-cli -w 1 -e 'structure --scope selected' -rp-cli -w 1 -e 'read src/pipeline/hybrid.ts --start-line 1 --limit 60' -``` - -**Response to main conversation:** - -```markdown -## Hybrid Search Summary - -The hybrid search system combines vector similarity (vsearch) with BM25 text matching, -fusing results via RRF and optionally reranking with a cross-encoder. - -### Key Files -- `src/pipeline/hybrid.ts:L1-60` - Main searchHybrid() orchestration -- `src/pipeline/fusion.ts` - RRF fusion of vector + BM25 results -- `src/pipeline/rerank.ts` - Cross-encoder reranking -- `src/pipeline/types.ts` - SearchResult, FusionConfig types - -### Code Signatures -```typescript -async function searchHybrid(query: string, opts: HybridOptions): Promise<SearchResult[]> -function rrfFuse(results: SearchResult[][], k?: number): SearchResult[] -async function rerankCandidates(query: string, candidates: SearchResult[]): Promise<SearchResult[]> -``` - -### Architecture -1. Query → parallel vector + BM25 search -2. Results → RRF fusion (k=60) -3. Fused → optional cross-encoder rerank -4. Return top-k results - -### Recommendation -Focus on hybrid.ts for the orchestration logic, fusion.ts for understanding scoring. -``` - ---- - -## Anti-patterns - -- **Single-word searches** - "hybrid" misses "hybridSearch", "searchHybrid", etc. Use multiple patterns -- **Forgetting `-w <id>`** - commands fail with "Multiple windows" error -- **Skipping window setup** - wrong project context -- **Dumping full files** - wastes tokens, use structure/slices -- **Not waiting for builder** - watch progress notifications, wait for completion -- **Not verifying selection** - builder may miss relevant files -- **Returning raw output** - summarize for main conversation -- **Not using builder** - for complex exploration, builder finds files you'd miss with manual search - ---- - -## Fallback: Standard Tools - -If rp-cli unavailable or not suited for the task, use standard tools: -- `Grep` - ripgrep-based search -- `Glob` - file pattern matching -- `Read` - file reading - -RepoPrompt excels at: -- Token-efficient signatures (structure command) -- AI-powered file discovery (builder) -- Managing large selections -- Cross-file understanding - -Standard tools excel at: -- Quick targeted searches -- Reading specific files -- Simple pattern matching - ---- - -## Notes - -- Use `rp-cli -d <cmd>` for detailed command help -- Requires RepoPrompt v1.5.62+ with MCP Server enabled -- Project path: `$(git rev-parse --show-toplevel)` diff --git a/sync/templates/opencode/agent/docs-scout.md b/sync/templates/opencode/agent/docs-scout.md deleted file mode 100644 index b7d9e68..0000000 --- a/sync/templates/opencode/agent/docs-scout.md +++ /dev/null @@ -1,79 +0,0 @@ ---- -description: Find the most relevant framework/library docs for the requested change. -mode: subagent -tools: - write: false - edit: false - patch: false - multiedit: false ---- -You are a docs scout. Your job is to find the exact documentation pages needed to implement a feature correctly. - -## Input - -You receive a feature/change request. Find the official docs that will be needed during implementation. - -## Search Strategy - -1. **Identify dependencies** (quick scan) - - Check package.json, pyproject.toml, Cargo.toml, etc. - - Note framework and major library versions - - Version matters - docs change between versions - -2. **Find primary framework docs** - - Go to official docs site first - - Find the specific section for this feature - - Look for guides, tutorials, API reference - -3. **Find library-specific docs** - - Each major dependency may have relevant docs - - Focus on integration points with the framework - -4. **Look for examples** - - Official examples/recipes - - GitHub repo examples folders - - Starter templates - -## WebFetch Strategy - -Don't just link - extract the relevant parts: - -``` -WebFetch: https://nextjs.org/docs/app/api-reference/functions/cookies -Prompt: "Extract the API signature, key parameters, and usage examples for cookies()" -``` - -## Output Format - -```markdown -## Documentation for [Feature] - -### Primary Framework -- **[Framework] [Version]** - - [Topic](url) - [what it covers] - > Key excerpt or API signature - -### Libraries -- **[Library]** - - [Relevant page](url) - [why needed] - -### Examples -- [Example](url) - [what it demonstrates] - -### API Quick Reference -```[language] -// Key API signatures extracted from docs -``` - -### Version Notes -- [Any version-specific caveats] -``` - -## Rules - -- Version-specific docs when possible (e.g., Next.js 14 vs 15) -- Extract key info inline - don't just link -- Prioritize official docs over third-party tutorials -- Include API signatures for quick reference -- Note breaking changes if upgrading -- Skip generic "getting started" - focus on the specific feature diff --git a/sync/templates/opencode/agent/flow-gap-analyst.md b/sync/templates/opencode/agent/flow-gap-analyst.md deleted file mode 100644 index 78cfd90..0000000 --- a/sync/templates/opencode/agent/flow-gap-analyst.md +++ /dev/null @@ -1,91 +0,0 @@ ---- -description: Map user flows, edge cases, and missing requirements from a brief spec. -mode: subagent -tools: - write: false - edit: false - patch: false - multiedit: false ---- -You are a UX flow analyst. Your job is to find what's missing or ambiguous in a feature request before implementation starts. - -## Input - -You receive: -1. A feature/change request (often brief) -2. Research findings from repo-scout, practice-scout, docs-scout - -Your task: identify gaps, edge cases, and questions that need answers BEFORE coding. - -## Analysis Framework - -### 1. User Flows -Map the complete user journey: -- **Happy path**: What happens when everything works? -- **Entry points**: How do users get to this feature? -- **Exit points**: Where do users go after? -- **Interruptions**: What if they leave mid-flow? (browser close, timeout, etc.) - -### 2. State Analysis -- **Initial state**: What exists before the feature runs? -- **Intermediate states**: What can happen during? -- **Final states**: All possible outcomes (success, partial, failure) -- **Persistence**: What needs to survive page refresh? Session end? - -### 3. Edge Cases -- **Empty states**: No data, first-time user -- **Boundaries**: Max values, min values, limits -- **Concurrent access**: Multiple tabs, multiple users -- **Timing**: Race conditions, slow networks, timeouts -- **Permissions**: Who can access? What if denied? - -### 4. Error Scenarios -- **User errors**: Invalid input, wrong sequence -- **System errors**: Network failure, service down, quota exceeded -- **Recovery**: Can the user retry? Resume? Undo? - -### 5. Integration Points -- **Dependencies**: What external services/APIs are involved? -- **Failure modes**: What if each dependency fails? -- **Data consistency**: What if partial success? - -## Output Format - -```markdown -## Gap Analysis: [Feature] - -### User Flows Identified -1. **[Flow name]**: [Description] - - Steps: [1 → 2 → 3] - - Missing: [What's not specified] - -### Edge Cases -| Case | Question | Impact if Ignored | -|------|----------|-------------------| -| [Case] | [What needs clarification?] | [Risk] | - -### Error Handling Gaps -- [ ] [Scenario]: [What should happen?] - -### State Management Questions -- [Question about state] - -### Integration Risks -- [Dependency]: [What could go wrong?] - -### Priority Questions (MUST answer before coding) -1. [Critical question] -2. [Critical question] - -### Nice-to-Clarify (can defer) -- [Less critical question] -``` - -## Rules - -- Think like a QA engineer - what would break this? -- Prioritize questions by impact (critical → nice-to-have) -- Be specific - "what about errors?" is too vague -- Reference existing code patterns when relevant -- Don't solve - just identify gaps -- Keep it actionable - questions should have clear owners diff --git a/sync/templates/opencode/agent/memory-scout.md b/sync/templates/opencode/agent/memory-scout.md deleted file mode 100644 index 7ae6591..0000000 --- a/sync/templates/opencode/agent/memory-scout.md +++ /dev/null @@ -1,65 +0,0 @@ ---- -description: Search .flow/memory/ for entries relevant to the current task or request. -mode: subagent -tools: - write: false - edit: false - patch: false - multiedit: false ---- -You search `.flow/memory/` for entries relevant to the current context. - -## Input - -You receive either: -- A planning request (feature description, change request) -- A task identifier with title (e.g., "fn-1.3: flowctl memory commands") - -## Memory Location - -Files live in `.flow/memory/`: -- `pitfalls.md` - Lessons from NEEDS_WORK reviews (what models miss) -- `conventions.md` - Project patterns not in CLAUDE.md -- `decisions.md` - Architectural choices with rationale - -## Search Strategy - -1. **Read all memory files** using Read tool -2. **Find semantically related entries** based on input context -3. **Return ONLY relevant entries** (not everything) - -Relevance criteria: -- Same technology/framework mentioned -- Similar type of work (API, UI, config, etc.) -- Related patterns or conventions -- Applicable pitfalls or gotchas - -## Output Format - -```markdown -## Relevant Memory - -### Pitfalls -- [Issue] - [Fix] (from <task-id>) - -### Conventions -- [Pattern] (discovered <date>) - -### Decisions -- [Choice] because [rationale] -``` - -If no relevant entries found: -```markdown -## Relevant Memory -No relevant entries in project memory. -``` - -## Rules - -- Speed is critical - simple keyword/semantic matching -- Return ONLY relevant entries (max 5-10 items) -- Preserve entry context (dates, task IDs) -- Handle empty memory gracefully -- Handle missing files gracefully -- Never return entire memory contents diff --git a/sync/templates/opencode/agent/practice-scout.md b/sync/templates/opencode/agent/practice-scout.md deleted file mode 100644 index 474f991..0000000 --- a/sync/templates/opencode/agent/practice-scout.md +++ /dev/null @@ -1,77 +0,0 @@ ---- -description: Gather modern best practices and pitfalls for the requested change. -mode: subagent -tools: - write: false - edit: false - patch: false - multiedit: false ---- -You are a best-practice scout. Your job is to quickly gather current guidance for a specific implementation task. - -## Input - -You receive a feature/change request. Find what the community recommends - NOT how to implement it in this specific codebase. - -## Search Strategy - -1. **Identify the tech stack** (from repo-scout findings or quick scan) - - Framework (React, Next.js, Express, Django, etc.) - - Language version - - Key libraries involved - -2. **Search for current guidance** - - Use WebSearch with specific queries: - - `"[framework] [feature] best practices 2025"` or `2026` - - `"[feature] common mistakes [framework]"` - - `"[feature] security considerations"` - - Prefer official docs, then reputable blogs (Kent C. Dodds, Dan Abramov, etc.) - -3. **Check for anti-patterns** - - What NOT to do - - Deprecated approaches - - Performance pitfalls - -4. **Security considerations** - - OWASP guidance if relevant - - Framework-specific security docs - -## WebFetch Usage - -When you find promising URLs: -``` -WebFetch: https://docs.example.com/security -Prompt: "Extract the key security recommendations for [feature]" -``` - -## Output Format - -```markdown -## Best Practices for [Feature] - -### Do -- [Practice]: [why, with source link] -- [Practice]: [why, with source link] - -### Don't -- [Anti-pattern]: [why it's bad, with source] -- [Deprecated approach]: [what to use instead] - -### Security -- [Consideration]: [guidance] - -### Performance -- [Tip]: [impact] - -### Sources -- [Title](url) - [what it covers] -``` - -## Rules - -- Current year is 2025 - search for recent guidance -- Prefer official docs over blog posts -- Include source links for verification -- Focus on practical do/don't, not theory -- Skip framework-agnostic generalities - be specific to the stack -- Don't repeat what's obvious - focus on non-obvious gotchas diff --git a/sync/templates/opencode/agent/quality-auditor.md b/sync/templates/opencode/agent/quality-auditor.md deleted file mode 100644 index 86a9d24..0000000 --- a/sync/templates/opencode/agent/quality-auditor.md +++ /dev/null @@ -1,103 +0,0 @@ ---- -description: Review recent changes for correctness, simplicity, security, and test coverage. -mode: subagent -tools: - write: false - edit: false - patch: false - multiedit: false ---- -You are a pragmatic code auditor. Your job is to find real risks in recent changes - fast. - -## Input - -You're invoked after implementation, before shipping. Review the changes and flag issues. - -## Audit Strategy - -### 1. Get the Diff -```bash -# What changed? -git diff main --stat -git diff main --name-only - -# Full diff for review -git diff main -``` - -### 2. Quick Scan (find obvious issues fast) -- **Secrets**: API keys, passwords, tokens in code -- **Debug code**: console.log, debugger, TODO/FIXME -- **Commented code**: Dead code that should be deleted -- **Large files**: Accidentally committed binaries, logs - -### 3. Correctness Review -- Does the code match the stated intent? -- Are there off-by-one errors, wrong operators, inverted conditions? -- Do error paths actually handle errors? -- Are promises/async properly awaited? - -### 4. Security Scan -- **Injection**: SQL, XSS, command injection vectors -- **Auth/AuthZ**: Are permissions checked? Can they be bypassed? -- **Data exposure**: Is sensitive data logged, leaked, or over-exposed? -- **Dependencies**: Any known vulnerable packages added? - -### 5. Simplicity Check -- Could this be simpler? -- Is there duplicated code that should be extracted? -- Are there unnecessary abstractions? -- Over-engineering for hypothetical future needs? - -### 6. Test Coverage -- Are new code paths tested? -- Do tests actually assert behavior (not just run)? -- Are edge cases from gap analysis covered? -- Are error paths tested? - -### 7. Performance Red Flags -- N+1 queries or O(n²) loops -- Unbounded data fetching -- Missing pagination/limits -- Blocking operations on hot paths - -## Output Format - -```markdown -## Quality Audit: [Branch/Feature] - -### Summary -- Files changed: N -- Risk level: Low / Medium / High -- Ship recommendation: ✅ Ship / ⚠️ Fix first / ❌ Major rework - -### Critical (MUST fix before shipping) -- **[File:line]**: [Issue] - - Risk: [What could go wrong] - - Fix: [Specific suggestion] - -### Should Fix (High priority) -- **[File:line]**: [Issue] - - [Brief fix suggestion] - -### Consider (Nice to have) -- [Minor improvement suggestion] - -### Test Gaps -- [ ] [Untested scenario] - -### Security Notes -- [Any security observations] - -### What's Good -- [Positive observations - patterns followed, good decisions] -``` - -## Rules - -- Find real risks, not style nitpicks -- Be specific: file:line + concrete fix -- Critical = could cause outage, data loss, security breach -- Don't block shipping for minor issues -- Acknowledge what's done well -- If no issues found, say so clearly diff --git a/sync/templates/opencode/agent/ralph-runner.md b/sync/templates/opencode/agent/ralph-runner.md deleted file mode 100644 index d8b44fb..0000000 --- a/sync/templates/opencode/agent/ralph-runner.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -description: Ralph autonomous runner. Use only for unattended Ralph iterations. -mode: primary ---- -AUTONOMOUS MODE ACTIVE (FLOW_RALPH=1). You are running unattended. CRITICAL RULES: - -1. EXECUTE COMMANDS EXACTLY as shown in prompts. Do not paraphrase or improvise. -2. VERIFY OUTCOMES by running the verification commands (flowctl show, git status). -3. NEVER CLAIM SUCCESS without proof. If flowctl done was not run, the task is NOT done. -4. COPY TEMPLATES VERBATIM - receipt JSON must match exactly including all fields. -5. USE SKILLS AS SPECIFIED - invoke /flow-next:impl-review, do not improvise review prompts. - -Violations break automation and leave the user with incomplete work. Be precise, not creative. diff --git a/sync/templates/opencode/agent/repo-scout.md b/sync/templates/opencode/agent/repo-scout.md deleted file mode 100644 index 09ba0cd..0000000 --- a/sync/templates/opencode/agent/repo-scout.md +++ /dev/null @@ -1,83 +0,0 @@ ---- -description: Scan repo to find existing patterns, conventions, and related code paths for a requested change. -mode: subagent -tools: - write: false - edit: false - patch: false - multiedit: false ---- -You are a fast repository scout. Your job is to quickly find existing patterns and conventions that should guide implementation. - -## Input - -You receive a feature/change request. Your task is NOT to plan or implement - just find what already exists. - -## Search Strategy - -1. **Project docs first** (fast context) - - CLAUDE.md, README.md, CONTRIBUTING.md, ARCHITECTURE.md - - Any docs/ or documentation/ folders - - package.json/pyproject.toml for deps and scripts - -2. **Find similar implementations** - - Grep for related keywords, function names, types - - Look for existing features that solve similar problems - - Note file organization patterns (where do similar things live?) - -3. **Identify conventions** - - Naming patterns (camelCase, snake_case, prefixes) - - File structure (co-location, separation by type/feature) - - Import patterns, module boundaries - - Error handling patterns - - Test patterns (location, naming, fixtures) - -4. **Surface reusable code** - - Shared utilities, helpers, base classes - - Existing validation, error handling - - Common patterns that should NOT be duplicated - -## Bash Commands (read-only) - -```bash -# Directory structure -ls -la src/ -find . -type f -name "*.ts" | head -20 - -# Git history for context -git log --oneline -10 -git log --oneline --all -- "*/auth*" | head -5 # history of similar features -``` - -## Output Format - -```markdown -## Repo Scout Findings - -### Project Conventions -- [Convention]: [where observed] - -### Related Code -- `path/to/file.ts:42` - [what it does, why relevant] -- `path/to/other.ts:15-30` - [pattern to follow] - -### Reusable Code (DO NOT DUPLICATE) -- `lib/utils/validation.ts` - existing validation helpers -- `lib/errors/` - error classes to extend - -### Test Patterns -- Tests live in: [location] -- Naming: [pattern] -- Fixtures: [if any] - -### Gotchas -- [Thing to watch out for] -``` - -## Rules - -- Speed over completeness - find the 80% fast -- Always include file:line references -- Flag code that MUST be reused (don't reinvent) -- Note any CLAUDE.md rules that apply -- Skip deep analysis - that's for other agents diff --git a/sync/templates/opencode/command/flow-next/export-context.md b/sync/templates/opencode/command/flow-next/export-context.md deleted file mode 100644 index 426531c..0000000 --- a/sync/templates/opencode/command/flow-next/export-context.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -description: Export RepoPrompt context for external LLM review ---- - -# IMPORTANT: This command MUST invoke the skill `flow-next-export-context` - -The ONLY purpose of this command is to call the `flow-next-export-context` skill. You MUST use that skill now. - -**Arguments:** $ARGUMENTS - -Pass the arguments to the skill. The skill handles the export logic. diff --git a/sync/templates/opencode/command/flow-next/impl-review.md b/sync/templates/opencode/command/flow-next/impl-review.md deleted file mode 100644 index 410aafa..0000000 --- a/sync/templates/opencode/command/flow-next/impl-review.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -description: John Carmack-level implementation review via RepoPrompt or OpenCode ---- - -# IMPORTANT: This command MUST invoke the skill `flow-next-impl-review` - -The ONLY purpose of this command is to call the `flow-next-impl-review` skill. You MUST use that skill now. - -**Arguments:** $ARGUMENTS - -Pass the arguments to the skill. The skill handles the review logic. diff --git a/sync/templates/opencode/command/flow-next/interview.md b/sync/templates/opencode/command/flow-next/interview.md deleted file mode 100644 index 09417c7..0000000 --- a/sync/templates/opencode/command/flow-next/interview.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -description: Interview & refine an epic, task, or spec file in-depth ---- - -# IMPORTANT: This command MUST invoke the skill `flow-next-interview` - -The ONLY purpose of this command is to call the `flow-next-interview` skill. You MUST use that skill now. - -**User input:** $ARGUMENTS - -Pass the user input to the skill. The skill handles the interview logic. diff --git a/sync/templates/opencode/command/flow-next/plan-review.md b/sync/templates/opencode/command/flow-next/plan-review.md deleted file mode 100644 index 217ea2e..0000000 --- a/sync/templates/opencode/command/flow-next/plan-review.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -description: Carmack-level plan review via RepoPrompt or OpenCode ---- - -# IMPORTANT: This command MUST invoke the skill `flow-next-plan-review` - -The ONLY purpose of this command is to call the `flow-next-plan-review` skill. You MUST use that skill now. - -**Arguments:** $ARGUMENTS - -Pass the arguments to the skill. The skill handles the review logic. diff --git a/sync/templates/opencode/command/flow-next/plan.md b/sync/templates/opencode/command/flow-next/plan.md deleted file mode 100644 index d005f77..0000000 --- a/sync/templates/opencode/command/flow-next/plan.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -description: Draft a clear build plan from a short request ---- - -# IMPORTANT: This command MUST invoke the skill `flow-next-plan` - -The ONLY purpose of this command is to call the `flow-next-plan` skill. You MUST use that skill now. - -**User request:** $ARGUMENTS - -Pass the user request to the skill. The skill handles all planning logic. diff --git a/sync/templates/opencode/command/flow-next/ralph-init.md b/sync/templates/opencode/command/flow-next/ralph-init.md deleted file mode 100644 index e8252e6..0000000 --- a/sync/templates/opencode/command/flow-next/ralph-init.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -description: Scaffold repo-local Ralph autonomous harness (scripts/ralph/) ---- - -# IMPORTANT: This command MUST invoke the skill `flow-next-ralph-init` - -The ONLY purpose of this command is to call the `flow-next-ralph-init` skill. You MUST use that skill now. - -Creates `scripts/ralph/` in the current repo. diff --git a/sync/templates/opencode/command/flow-next/setup.md b/sync/templates/opencode/command/flow-next/setup.md deleted file mode 100644 index 1d21d32..0000000 --- a/sync/templates/opencode/command/flow-next/setup.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -description: Optional local install of flowctl CLI and project docs ---- - -# IMPORTANT: This command MUST invoke the skill `flow-next-setup` - -The ONLY purpose of this command is to call the `flow-next-setup` skill. You MUST use that skill now. - -This is an **optional** setup for power users. Flow-next works without it. diff --git a/sync/templates/opencode/command/flow-next/uninstall.md b/sync/templates/opencode/command/flow-next/uninstall.md deleted file mode 100644 index efa7ddb..0000000 --- a/sync/templates/opencode/command/flow-next/uninstall.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -description: Remove flow-next files from project ---- - -# Flow-Next Uninstall - -Ask the user to confirm in normal chat text: - -**Question 1:** "Remove flow-next from this project?" -- "Yes, uninstall" -- "Cancel" - -If cancel → stop. - -**Question 2:** "Keep your .flow/ tasks and epics?" -- "Yes, keep tasks" → only remove .flow/bin/, .flow/usage.md -- "No, remove everything" → remove entire .flow/ - -## Execute removal - -Run these bash commands as needed: - -```bash -# If keeping tasks: -rm -rf .flow/bin .flow/usage.md - -# If removing everything: -rm -rf .flow - -# Always check for Ralph: -rm -rf scripts/ralph -``` - -For CLAUDE.md and AGENTS.md: if file exists, remove everything between `<!-- BEGIN FLOW-NEXT -->` and `<!-- END FLOW-NEXT -->` (inclusive). - -## Report - -``` -Removed: -- .flow/bin/, .flow/usage.md (or entire .flow/) -- scripts/ralph/ (if existed) -- Flow-next sections from docs (if existed) -``` diff --git a/sync/templates/opencode/command/flow-next/work.md b/sync/templates/opencode/command/flow-next/work.md deleted file mode 100644 index 68b6a74..0000000 --- a/sync/templates/opencode/command/flow-next/work.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -description: Execute a plan end-to-end with checks ---- - -# IMPORTANT: This command MUST invoke the skill `flow-next-work` - -The ONLY purpose of this command is to call the `flow-next-work` skill. You MUST use that skill now. - -**User input:** $ARGUMENTS - -Pass the user input to the skill. The skill handles all execution logic. diff --git a/sync/templates/opencode/opencode.json b/sync/templates/opencode/opencode.json deleted file mode 100644 index 0a6f7de..0000000 --- a/sync/templates/opencode/opencode.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "$schema": "https://opencode.ai/config.json", - "experimental": { - "batch_tool": true - }, - "agent": { - "ralph-runner": { - "description": "Autonomous Ralph runner (tool-first, no chit-chat)", - "mode": "primary", - "model": "openai/gpt-5.2-codex", - "reasoningEffort": "medium", - "prompt": "You are an autonomous workflow executor. Always use tools to perform actions. Never respond with status-only text. Do not ask questions. If you cannot proceed or a required tool fails, output exactly: <promise>RETRY</promise> and stop." - }, - "opencode-reviewer": { - "description": "Carmack-level reviewer for plans and implementations", - "model": "openai/gpt-5.2", - "reasoningEffort": "high", - "prompt": "You are a strict reviewer. Focus on correctness, completeness, feasibility, risks, and testability. Provide issues with severity and concrete fixes. End with exactly one verdict tag: <verdict>SHIP</verdict> or <verdict>NEEDS_WORK</verdict> or <verdict>MAJOR_RETHINK</verdict>.", - "tools": { - "write": false, - "edit": false, - "patch": false, - "multiedit": false - } - } - }, - "permission": { - "*": "allow", - "todoread": "deny", - "todowrite": "deny" - } -} diff --git a/sync/templates/opencode/plugin/flow-next-ralph-guard.ts b/sync/templates/opencode/plugin/flow-next-ralph-guard.ts deleted file mode 100644 index 26d13ba..0000000 --- a/sync/templates/opencode/plugin/flow-next-ralph-guard.ts +++ /dev/null @@ -1,255 +0,0 @@ -import type { Hooks, PluginInput } from "@opencode-ai/plugin" -import fs from "fs" -import path from "path" - -const STATE_DIR = "/tmp" - -const DEFAULT_REVIEWER = "opencode-reviewer" -const RALPH_SYSTEM = [ - "Ralph mode: unattended autonomous execution.", - "Never stop after a status-only message.", - "Always use tools to perform actions.", - "Do not ask questions.", - "If you cannot proceed, output exactly: <promise>RETRY</promise> and stop.", -].join(" ") - -type CallInfo = { - tool: string - command?: string - subagent_type?: string -} - -type State = { - chats_sent: number - chat_send_succeeded: boolean - opencode_review_succeeded: boolean - last_verdict?: string - flowctl_done_called: string[] - calls: Record<string, CallInfo> -} - -function isRalph() { - return process.env.FLOW_RALPH === "1" -} - -function statePath(sessionID: string) { - return path.join(STATE_DIR, `ralph-guard-${sessionID}.json`) -} - -function loadState(sessionID: string): State { - const file = statePath(sessionID) - if (fs.existsSync(file)) { - try { - const raw = JSON.parse(fs.readFileSync(file, "utf8")) - return { - chats_sent: raw.chats_sent ?? 0, - chat_send_succeeded: raw.chat_send_succeeded ?? false, - opencode_review_succeeded: raw.opencode_review_succeeded ?? false, - last_verdict: raw.last_verdict, - flowctl_done_called: Array.isArray(raw.flowctl_done_called) ? raw.flowctl_done_called : [], - calls: raw.calls ?? {}, - } - } catch {} - } - return { - chats_sent: 0, - chat_send_succeeded: false, - opencode_review_succeeded: false, - flowctl_done_called: [], - calls: {}, - } -} - -function saveState(sessionID: string, state: State) { - fs.writeFileSync(statePath(sessionID), JSON.stringify(state)) -} - -function block(message: string): never { - throw new Error(message) -} - -function blockIfChatCommand(command: string) { - if (/(^|\s)\/?flow-next:/.test(command)) { - block("BLOCKED: Do not run /flow-next:* as a shell command. Use the skill/tool workflow instead.") - } -} - -function blockIfQuestionText(text: string) { - const normalized = text.toLowerCase() - if ( - normalized.includes("want me to proceed") || - normalized.includes("should i proceed") || - normalized.includes("should i continue") || - normalized.includes("do you want me to proceed") - ) { - block("BLOCKED: Ralph is unattended. Do not ask for confirmation.") - } -} - -function isReceiptWrite(command: string, receiptPath: string) { - const dir = path.dirname(receiptPath) - return ( - command.includes(receiptPath) || - new RegExp(`>\\s*['\"]?${dir.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`).test(command) || - /cat\s*>\s*.*receipt/i.test(command) || - /receipts\/.*\.json/.test(command) - ) -} - -export default async function (_input: PluginInput): Promise<Hooks> { - const allowedReviewer = process.env.FLOW_RALPH_REVIEWER_AGENT || DEFAULT_REVIEWER - return { - "experimental.chat.system.transform": async (_input, output) => { - if (!isRalph()) return - output.system.push(RALPH_SYSTEM) - }, - "tool.execute.before": async (input, output) => { - if (!isRalph()) return output - - const sessionID = input.sessionID - const state = loadState(sessionID) - const callID = input.callID - - if (input.tool === "bash") { - const command = String(output.args?.command ?? "") - blockIfChatCommand(command) - state.calls[callID] = { tool: "bash", command } - - if (command.includes("chat-send")) { - if (/chat-send.*--json/.test(command)) { - block("BLOCKED: Do not use --json with chat-send. It suppresses the review text.") - } - if (command.includes("--new-chat") && state.chats_sent > 0) { - block("BLOCKED: Do not use --new-chat for re-reviews. Stay in the same chat.") - } - } - - if (command.includes("rp-cli")) { - const isRpCheck = - /\bwhich\s+rp-cli\b/.test(command) || - /\bcommand\s+-v\s+rp-cli\b/.test(command) || - /-x\s+\/(opt\/homebrew|usr\/local)\/bin\/rp-cli\b/.test(command) - if (!isRpCheck) { - block("BLOCKED: Do not call rp-cli directly. Use flowctl rp wrappers.") - } - } - - if (command.includes("setup-review")) { - if (!/--repo-root/.test(command)) { - block("BLOCKED: setup-review requires --repo-root.") - } - if (!/--summary/.test(command)) { - block("BLOCKED: setup-review requires --summary.") - } - } - - if (command.includes("select-add")) { - if (!/--window/.test(command) || !/--tab/.test(command)) { - block("BLOCKED: select-add requires --window and --tab.") - } - } - - if (/\bdone\b/.test(command) && (command.includes("flowctl") || command.includes("FLOWCTL"))) { - if (!/--help|-h/.test(command)) { - if (!/--evidence-json|--evidence/.test(command)) { - block("BLOCKED: flowctl done requires --evidence-json.") - } - if (!/--summary-file|--summary/.test(command)) { - block("BLOCKED: flowctl done requires --summary-file.") - } - } - } - - const receiptPath = process.env.REVIEW_RECEIPT_PATH || "" - if (receiptPath && isReceiptWrite(command, receiptPath)) { - if (!state.chat_send_succeeded && !state.opencode_review_succeeded) { - block( - "BLOCKED: Cannot write receipt before review completes. Run review and receive verdict first.", - ) - } - if (!/"id"\s*:/.test(command) && !/\'id\'\s*:/.test(command)) { - block("BLOCKED: Receipt JSON missing required id field. Copy the exact template.") - } - if (command.includes("impl_review")) { - const match = - command.match(/"id"\s*:\s*"([^"]+)"/) || command.match(/'id'\s*:\s*'([^']+)'/) - const taskId = match?.[1] - if (taskId && !state.flowctl_done_called.includes(taskId)) { - block(`BLOCKED: Cannot write impl receipt for ${taskId} - flowctl done was not called.`) - } - } - } - } - - if (input.tool === "task") { - const subagent = String(output.args?.subagent_type ?? "") - state.calls[callID] = { tool: "task", subagent_type: subagent } - if (subagent !== allowedReviewer) { - block( - `BLOCKED: Ralph mode only allows task tool for reviewer '${allowedReviewer}'. ` + - "Use the skill tool (flow-next-plan-review / flow-next-work) and do NOT spawn generic tasks.", - ) - } - } - - saveState(sessionID, state) - return output - }, - - "tool.execute.after": async (input, output) => { - if (!isRalph()) return output - - const sessionID = input.sessionID - const state = loadState(sessionID) - const callID = input.callID - const call = state.calls[callID] - const outputText = String(output.output ?? "") - blockIfQuestionText(outputText) - - if (input.tool === "bash" && call?.command) { - const command = call.command - - if (command.includes("chat-send")) { - const hasVerdict = /<verdict>(SHIP|NEEDS_WORK|MAJOR_RETHINK)<\/verdict>/.test(outputText) - const hasChat = outputText.includes("Chat Send") && !outputText.includes('"chat": null') - if (hasVerdict || hasChat) { - state.chats_sent = (state.chats_sent || 0) + 1 - state.chat_send_succeeded = true - } - if (outputText.includes('"chat": null')) { - state.chat_send_succeeded = false - } - } - - if (/\bdone\b/.test(command) && (command.includes("flowctl") || command.includes("FLOWCTL"))) { - const match = command.match(/\bdone\s+([a-zA-Z0-9][a-zA-Z0-9._-]*)/) - if (match) { - const taskId = match[1] - const exit = output.metadata?.exit - if (exit === 0) { - if (!state.flowctl_done_called.includes(taskId)) state.flowctl_done_called.push(taskId) - } - } - } - - const receiptPath = process.env.REVIEW_RECEIPT_PATH || "" - if (receiptPath && command.includes(receiptPath) && command.includes(">")) { - state.chat_send_succeeded = false - state.opencode_review_succeeded = false - } - } - - if (input.tool === "task" && call?.subagent_type === allowedReviewer) { - const verdict = outputText.match(/<verdict>(SHIP|NEEDS_WORK|MAJOR_RETHINK)<\/verdict>/) - if (verdict) { - state.opencode_review_succeeded = true - state.last_verdict = verdict[1] - } - } - - delete state.calls[callID] - saveState(sessionID, state) - return output - }, - } -} diff --git a/sync/templates/opencode/skill/browser/SKILL.md b/sync/templates/opencode/skill/browser/SKILL.md deleted file mode 100644 index 7ffaf0a..0000000 --- a/sync/templates/opencode/skill/browser/SKILL.md +++ /dev/null @@ -1,212 +0,0 @@ ---- -name: browser -description: Browser automation via agent-browser CLI. Use when you need to navigate websites, verify deployed UI, test web apps, read online documentation, scrape data, fill forms, capture baseline screenshots before design work, or inspect current page state. Triggers on "check the page", "verify UI", "test the site", "read docs at", "look up API", "visit URL", "browse", "screenshot", "scrape", "e2e test", "login flow", "capture baseline", "see how it looks", "inspect current", "before redesign". ---- - -# Browser Automation - -Browser automation via Vercel's agent-browser CLI. Runs headless by default; use `--headed` for visible window. Uses ref-based selection (@e1, @e2) from accessibility snapshots. - -## Setup - -```bash -command -v agent-browser >/dev/null 2>&1 && echo "OK" || echo "MISSING: npm i -g agent-browser && agent-browser install" -``` - -## Core Workflow - -1. **Open** URL -2. **Snapshot** to get refs -3. **Interact** via refs -4. **Re-snapshot** after DOM changes - -```bash -agent-browser open https://example.com -agent-browser snapshot -i # Interactive elements with refs -agent-browser click @e1 -agent-browser wait --load networkidle # Wait for SPA to settle -agent-browser snapshot -i # Re-snapshot after change -``` - -## Essential Commands - -### Navigation - -```bash -agent-browser open <url> # Navigate -agent-browser back # Go back -agent-browser forward # Go forward -agent-browser reload # Reload -agent-browser close # Close browser -``` - -### Snapshots - -```bash -agent-browser snapshot # Full accessibility tree -agent-browser snapshot -i # Interactive only (recommended) -agent-browser snapshot -i --json # JSON for parsing -agent-browser snapshot -c # Compact (remove empty) -agent-browser snapshot -d 3 # Limit depth -agent-browser snapshot -s "#main" # Scope to selector -``` - -### Interactions - -```bash -agent-browser click @e1 # Click -agent-browser dblclick @e1 # Double-click -agent-browser fill @e1 "text" # Clear + fill input -agent-browser type @e1 "text" # Type without clearing -agent-browser press Enter # Key press -agent-browser press Control+a # Key combination -agent-browser hover @e1 # Hover -agent-browser check @e1 # Check checkbox -agent-browser uncheck @e1 # Uncheck -agent-browser select @e1 "option" # Dropdown -agent-browser scroll down 500 # Scroll direction + pixels -agent-browser scrollintoview @e1 # Scroll element visible -``` - -### Get Info - -```bash -agent-browser get text @e1 # Element text -agent-browser get value @e1 # Input value -agent-browser get html @e1 # Element HTML -agent-browser get attr href @e1 # Attribute -agent-browser get title # Page title -agent-browser get url # Current URL -agent-browser get count "button" # Count matches -``` - -### Check State - -```bash -agent-browser is visible @e1 # Check visibility -agent-browser is enabled @e1 # Check enabled -agent-browser is checked @e1 # Check checkbox state -``` - -### Wait - -```bash -agent-browser wait @e1 # Wait for element visible -agent-browser wait 2000 # Wait milliseconds -agent-browser wait --text "Success" # Wait for text -agent-browser wait --url "**/dashboard" # Wait for URL pattern -agent-browser wait --load networkidle # Wait for network idle (SPAs) -agent-browser wait --fn "window.ready" # Wait for JS condition -``` - -### Screenshots - -```bash -agent-browser screenshot # Viewport to stdout -agent-browser screenshot out.png # Save to file -agent-browser screenshot --full # Full page -agent-browser pdf out.pdf # Save as PDF -``` - -### Semantic Locators - -Alternative when you know the element (no snapshot needed, see [advanced.md](references/advanced.md) for tabs, frames, network mocking): - -```bash -agent-browser find role button click --name "Submit" -agent-browser find text "Sign In" click -agent-browser find label "Email" fill "user@test.com" -agent-browser find placeholder "Search" fill "query" -agent-browser find first ".item" click -agent-browser find nth 2 "a" text -``` - -## Sessions - -Parallel isolated browsers (see [auth.md](references/auth.md) for multi-user auth): - -```bash -agent-browser --session test1 open site-a.com -agent-browser --session test2 open site-b.com -agent-browser session list -``` - -## JSON Output - -Add `--json` for machine-readable output: - -```bash -agent-browser snapshot -i --json -agent-browser get text @e1 --json -agent-browser is visible @e1 --json -``` - -## Examples - -### Form Submission - -```bash -agent-browser open https://example.com/form -agent-browser snapshot -i -# textbox "Email" [ref=e1], textbox "Password" [ref=e2], button "Submit" [ref=e3] -agent-browser fill @e1 "user@example.com" -agent-browser fill @e2 "password123" -agent-browser click @e3 -agent-browser wait --load networkidle -agent-browser snapshot -i # Verify result -``` - -### Auth with Saved State - -```bash -# Login once -agent-browser open https://app.example.com/login -agent-browser snapshot -i -agent-browser fill @e1 "username" -agent-browser fill @e2 "password" -agent-browser click @e3 -agent-browser wait --url "**/dashboard" -agent-browser state save auth.json - -# Later: reuse saved auth -agent-browser state load auth.json -agent-browser open https://app.example.com/dashboard -``` - -More auth patterns in [auth.md](references/auth.md). - -### Token Auth (Skip Login) - -```bash -# Headers scoped to origin only -agent-browser open api.example.com --headers '{"Authorization": "Bearer <token>"}' -agent-browser snapshot -i --json -``` - -## Debugging - -```bash -agent-browser --headed open example.com # Show browser window -agent-browser console # View console messages -agent-browser errors # View page errors -agent-browser highlight @e1 # Highlight element -``` - -See [debugging.md](references/debugging.md) for traces, common issues. - -## Troubleshooting - -**"Browser not launched" error**: Daemon stuck. Kill and retry: -```bash -pkill -f agent-browser && agent-browser open <url> -``` - -**Element not found**: Re-snapshot after page changes. DOM may have updated. - -## References - -| Topic | File | -|-------|------| -| Debugging, traces, common issues | [debugging.md](references/debugging.md) | -| Auth, cookies, storage, state persistence | [auth.md](references/auth.md) | -| Network mocking, tabs, frames, dialogs, settings | [advanced.md](references/advanced.md) | diff --git a/sync/templates/opencode/skill/browser/references/advanced.md b/sync/templates/opencode/skill/browser/references/advanced.md deleted file mode 100644 index b0d56c7..0000000 --- a/sync/templates/opencode/skill/browser/references/advanced.md +++ /dev/null @@ -1,169 +0,0 @@ -# Advanced Features - -## Network Interception - -Mock or block network requests: - -```bash -# Intercept and track requests -agent-browser network route "**/api/*" - -# Block requests (ads, analytics) -agent-browser network route "**/analytics/*" --abort - -# Mock response -agent-browser network route "**/api/user" --body '{"name":"Test User"}' - -# Remove route -agent-browser network unroute "**/api/*" - -# View tracked requests -agent-browser network requests -agent-browser network requests --filter api -``` - -## Tabs - -```bash -agent-browser tab # List tabs -agent-browser tab new # New blank tab -agent-browser tab new url.com # New tab with URL -agent-browser tab 2 # Switch to tab 2 -agent-browser tab close # Close current tab -agent-browser tab close 2 # Close tab 2 -``` - -## Windows - -```bash -agent-browser window new # New browser window -``` - -## Frames (iframes) - -```bash -agent-browser frame "#iframe-selector" # Switch to iframe -agent-browser snapshot -i # Snapshot within iframe -agent-browser click @e1 # Interact within iframe -agent-browser frame main # Back to main frame -``` - -## Dialogs - -Handle alert/confirm/prompt dialogs: - -```bash -agent-browser dialog accept # Accept dialog -agent-browser dialog accept "input text" # Accept prompt with text -agent-browser dialog dismiss # Dismiss/cancel dialog -``` - -## Mouse Control - -Low-level mouse operations: - -```bash -agent-browser mouse move 100 200 # Move to coordinates -agent-browser mouse down # Press left button -agent-browser mouse down right # Press right button -agent-browser mouse up # Release button -agent-browser mouse wheel -500 # Scroll wheel (negative = up) -``` - -## Drag and Drop - -```bash -agent-browser drag @e1 @e2 # Drag e1 to e2 -agent-browser drag "#source" "#target" -``` - -## File Upload - -```bash -agent-browser upload @e1 /path/to/file.pdf -agent-browser upload @e1 file1.jpg file2.jpg # Multiple files -``` - -## Browser Settings - -### Viewport - -```bash -agent-browser set viewport 1920 1080 -``` - -### Device Emulation - -```bash -agent-browser set device "iPhone 14" -agent-browser set device "Pixel 5" -``` - -### Geolocation - -```bash -agent-browser set geo 37.7749 -122.4194 # San Francisco -``` - -### Offline Mode - -```bash -agent-browser set offline on -agent-browser set offline off -``` - -### Color Scheme - -```bash -agent-browser set media dark -agent-browser set media light -``` - -## CDP Mode - -Connect to existing browser via Chrome DevTools Protocol: - -```bash -# Connect to Electron app or Chrome with remote debugging -# Start Chrome: google-chrome --remote-debugging-port=9222 -agent-browser --cdp 9222 snapshot -agent-browser --cdp 9222 click @e1 -``` - -Use cases: -- Control Electron apps -- Connect to existing Chrome sessions -- WebView2 applications - -## JavaScript Evaluation - -```bash -agent-browser eval "document.title" -agent-browser eval "window.scrollTo(0, 1000)" -agent-browser eval "localStorage.getItem('token')" -``` - -## Bounding Box - -Get element position and size: - -```bash -agent-browser get box @e1 -# {"x":100,"y":200,"width":150,"height":40} -``` - -## Custom Browser Executable - -Use system Chrome or lightweight builds: - -```bash -agent-browser --executable-path /usr/bin/google-chrome open example.com - -# Or via environment -AGENT_BROWSER_EXECUTABLE_PATH=/path/to/chromium agent-browser open example.com -``` - -Useful for: -- Serverless (use `@sparticuz/chromium`) -- System browser instead of bundled -- Custom Chromium builds diff --git a/sync/templates/opencode/skill/browser/references/auth.md b/sync/templates/opencode/skill/browser/references/auth.md deleted file mode 100644 index 7b89055..0000000 --- a/sync/templates/opencode/skill/browser/references/auth.md +++ /dev/null @@ -1,111 +0,0 @@ -# Authentication - -Patterns for handling login, sessions, and auth state. - -## State Persistence - -Save and restore full browser state (cookies, localStorage, sessionStorage): - -```bash -# After successful login -agent-browser state save auth.json - -# In new session -agent-browser state load auth.json -agent-browser open https://app.example.com/dashboard -``` - -## Token Auth via Headers - -Skip login flows entirely with auth headers: - -```bash -# Headers scoped to origin only (safe!) -agent-browser open api.example.com --headers '{"Authorization": "Bearer <token>"}' -``` - -Multiple origins: -```bash -agent-browser open api.example.com --headers '{"Authorization": "Bearer token1"}' -agent-browser open api.acme.com --headers '{"Authorization": "Bearer token2"}' -``` - -Global headers (all domains): -```bash -agent-browser set headers '{"X-Custom-Header": "value"}' -``` - -## Cookies - -```bash -agent-browser cookies # Get all cookies -agent-browser cookies set name "value" # Set cookie -agent-browser cookies clear # Clear all cookies -``` - -## Local Storage - -```bash -agent-browser storage local # Get all localStorage -agent-browser storage local key # Get specific key -agent-browser storage local set key val # Set value -agent-browser storage local clear # Clear all -``` - -## Session Storage - -```bash -agent-browser storage session # Get all sessionStorage -agent-browser storage session key # Get specific key -agent-browser storage session set k v # Set value -agent-browser storage session clear # Clear all -``` - -## HTTP Basic Auth - -```bash -agent-browser set credentials username password -agent-browser open https://protected-site.com -``` - -## Example: Full Login Flow with State Save - -```bash -# First run: perform login -agent-browser open https://app.example.com/login -agent-browser snapshot -i -agent-browser fill @e1 "user@example.com" -agent-browser fill @e2 "password123" -agent-browser click @e3 -agent-browser wait --url "**/dashboard" -agent-browser wait --load networkidle - -# Verify logged in -agent-browser snapshot -i -# Should show dashboard elements, not login form - -# Save state for future runs -agent-browser state save auth.json -agent-browser close -``` - -```bash -# Subsequent runs: skip login -agent-browser state load auth.json -agent-browser open https://app.example.com/dashboard -# Already authenticated! -``` - -## Session Isolation - -Different auth states in parallel: - -```bash -# Admin session -agent-browser --session admin state load admin-auth.json -agent-browser --session admin open https://app.example.com/admin - -# User session -agent-browser --session user state load user-auth.json -agent-browser --session user open https://app.example.com/profile -``` diff --git a/sync/templates/opencode/skill/browser/references/debugging.md b/sync/templates/opencode/skill/browser/references/debugging.md deleted file mode 100644 index 3738d80..0000000 --- a/sync/templates/opencode/skill/browser/references/debugging.md +++ /dev/null @@ -1,92 +0,0 @@ -# Debugging - -When browser automation fails or behaves unexpectedly. - -## Headed Mode - -Show visible browser window to see what's happening: - -```bash -agent-browser --headed open example.com -agent-browser --headed snapshot -i -agent-browser --headed click @e1 -``` - -## Console & Errors - -View browser console output and page errors: - -```bash -agent-browser console # View console messages -agent-browser console --clear # Clear console log -agent-browser errors # View page errors -agent-browser errors --clear # Clear error log -``` - -## Highlight Elements - -Visually identify elements (use with `--headed`): - -```bash -agent-browser highlight @e1 -agent-browser highlight "#selector" -``` - -## Traces - -Record browser traces for detailed debugging: - -```bash -agent-browser trace start # Start recording -# ... do interactions ... -agent-browser trace stop trace.zip # Save trace file -``` - -Open traces in Playwright Trace Viewer: `npx playwright show-trace trace.zip` - -## State Checks - -Verify element state before interacting: - -```bash -agent-browser is visible @e1 # Returns true/false -agent-browser is enabled @e1 # Check if interactive -agent-browser is checked @e1 # Checkbox state -``` - -With JSON output: -```bash -agent-browser is visible @e1 --json -# {"success":true,"data":true} -``` - -## Common Issues - -### Element not found -- Re-snapshot: DOM may have changed -- Check visibility: `is visible @e1` -- Try `--headed` to see actual page state - -### Click does nothing -- Element may be covered: try `scrollintoview @e1` first -- Element may be disabled: check `is enabled @e1` -- SPA not ready: add `wait --load networkidle` - -### Form not submitting -- Check for validation errors in snapshot -- Some forms need `press Enter` instead of button click -- Wait for network: `wait --load networkidle` - -### Auth redirect loops -- Save state after successful login: `state save auth.json` -- Check cookies: `cookies` -- Verify URL pattern: `get url` - -## Debug Output - -Add `--debug` for verbose output: - -```bash -agent-browser --debug open example.com -agent-browser --debug click @e1 -``` diff --git a/sync/templates/opencode/skill/flow-next-export-context/SKILL.md b/sync/templates/opencode/skill/flow-next-export-context/SKILL.md deleted file mode 100644 index eecd48a..0000000 --- a/sync/templates/opencode/skill/flow-next-export-context/SKILL.md +++ /dev/null @@ -1,121 +0,0 @@ ---- -name: flow-next-export-context -description: Export RepoPrompt context for external LLM review (ChatGPT, Claude web, etc.). Use when you want to review code or plans with an external model. Triggers on /flow-next:export-context. ---- - -# Export Context Mode - -Build RepoPrompt context and export to a markdown file for use with external LLMs (ChatGPT Pro, Claude web, etc.). - -**Use case**: When you want Carmack-level review but prefer to use an external model. - -**CRITICAL: flowctl is BUNDLED — NOT installed globally.** `which flowctl` will fail (expected). Always use: -```bash -ROOT="$(git rev-parse --show-toplevel)" -PLUGIN_ROOT="$ROOT/plugins/flow-next" -FLOWCTL="$PLUGIN_ROOT/scripts/flowctl" -$FLOWCTL <command> -``` - -## Input - -Arguments: $ARGUMENTS -Format: `<type> <target> [focus areas]` - -Types: -- `plan <epic-id>` - Export plan review context -- `impl` - Export implementation review context (current branch) - -Examples: -- `/flow-next:export-context plan fn-1 focus on security` -- `/flow-next:export-context impl focus on the auth changes` - -## Setup - -```bash -ROOT="$(git rev-parse --show-toplevel)" -PLUGIN_ROOT="$ROOT/plugins/flow-next" -FLOWCTL="$PLUGIN_ROOT/scripts/flowctl" -REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" -``` - -## Workflow - -### Step 1: Determine Type - -Parse arguments to determine if this is a plan or impl export. - -### Step 2: Gather Content - -**For plan export:** -```bash -$FLOWCTL show <epic-id> --json -$FLOWCTL cat <epic-id> -``` - -**For impl export:** -```bash -git branch --show-current -git log main..HEAD --oneline 2>/dev/null || git log master..HEAD --oneline -git diff main..HEAD --name-only 2>/dev/null || git diff master..HEAD --name-only -``` - -### Step 3: Setup RepoPrompt - -```bash -eval "$($FLOWCTL rp setup-review --repo-root "$REPO_ROOT" --summary "<summary based on type>")" -``` - -### Step 4: Augment Selection - -```bash -$FLOWCTL rp select-get --window "$W" --tab "$T" - -# Add relevant files -$FLOWCTL rp select-add --window "$W" --tab "$T" <files> -``` - -### Step 5: Build Review Prompt - -Get builder's handoff: -```bash -$FLOWCTL rp prompt-get --window "$W" --tab "$T" -``` - -Build combined prompt with review criteria (same as plan-review or impl-review). - -Set the prompt: -```bash -cat > /tmp/export-prompt.md << 'EOF' -[COMBINED PROMPT WITH REVIEW CRITERIA] -EOF - -$FLOWCTL rp prompt-set --window "$W" --tab "$T" --message-file /tmp/export-prompt.md -``` - -### Step 6: Export - -```bash -OUTPUT_FILE=~/Desktop/review-export-$(date +%Y%m%d-%H%M%S).md -$FLOWCTL rp prompt-export --window "$W" --tab "$T" --out "$OUTPUT_FILE" -open "$OUTPUT_FILE" -``` - -### Step 7: Inform User - -``` -Exported review context to: $OUTPUT_FILE - -The file contains: -- Full file tree with selected files marked -- Code maps (signatures/structure) -- Complete file contents -- Review prompt with Carmack-level criteria - -Paste into ChatGPT Pro, Claude web, or your preferred LLM. -After receiving feedback, return here to implement fixes. -``` - -## Note - -This skill is for **manual** external review only. It does not work with Ralph autonomous mode (no receipts, no status updates). diff --git a/sync/templates/opencode/skill/flow-next-impl-review/SKILL.md b/sync/templates/opencode/skill/flow-next-impl-review/SKILL.md deleted file mode 100644 index 1dd8476..0000000 --- a/sync/templates/opencode/skill/flow-next-impl-review/SKILL.md +++ /dev/null @@ -1,195 +0,0 @@ ---- -name: flow-next-impl-review -description: John Carmack-level implementation review via RepoPrompt or OpenCode. Use when reviewing code changes, PRs, or implementations. Triggers on /flow-next:impl-review. ---- - -# Implementation Review Mode - -**Read [workflow.md](workflow.md) for detailed phases and anti-patterns.** - -Conduct a John Carmack-level review of implementation changes on the current branch. - -**Role**: Code Review Coordinator (NOT the reviewer) -**Backends**: OpenCode (opencode) or RepoPrompt (rp) - -**CRITICAL: flowctl is BUNDLED — NOT installed globally.** `which flowctl` will fail (expected). Always use: -```bash -ROOT="$(git rev-parse --show-toplevel)" -PLUGIN_ROOT="$ROOT/plugins/flow-next" -FLOWCTL="$PLUGIN_ROOT/scripts/flowctl" -``` - -## Backend Selection - -**Priority** (first match wins): -1. `--review=rp|opencode|export|none` argument -2. `FLOW_REVIEW_BACKEND` env var (`rp`, `opencode`, `none`) -3. `.flow/config.json` → `review.backend` -4. Interactive prompt if rp-cli available (and not in Ralph mode) -5. Default: `opencode` - -### Parse from arguments first - -Check $ARGUMENTS for: -- `--review=rp` or `--review rp` → use rp -- `--review=opencode` or `--review opencode` → use opencode -- `--review=export` or `--review export` → use export -- `--review=none` or `--review none` → skip review - -If found, use that backend and skip all other detection. - -### Otherwise detect - -```bash -# Check available backends -HAVE_RP=0; -if command -v rp-cli >/dev/null 2>&1; then - HAVE_RP=1; -elif [[ -x /opt/homebrew/bin/rp-cli || -x /usr/local/bin/rp-cli ]]; then - HAVE_RP=1; -fi; - -# Get configured backend -BACKEND="${FLOW_REVIEW_BACKEND:-}"; -if [[ -z "$BACKEND" ]]; then - BACKEND="$($FLOWCTL config get review.backend --json 2>/dev/null | jq -r '.value // empty')"; -fi -``` - -**MUST RUN the detection command above** and use its result. Do **not** assume rp-cli is missing without running it. - -### If no backend configured and rp available - -If `BACKEND` is empty AND `HAVE_RP=1`, AND not in Ralph mode (`FLOW_RALPH` not set): - -Output this question as text: -``` -Which review backend? -a) OpenCode review (GPT-5.2, reasoning high) -b) RepoPrompt (macOS, visual builder) - -(Reply: "a", "opencode", "b", "rp", or just tell me) -``` - -**IMPORTANT**: Ask this in **plain text only**. **Do NOT use the question tool.** - -Wait for response. Parse naturally. - -**Default if empty/ambiguous**: `opencode` - -### If only one available or in Ralph mode - -```bash -# Fallback to available -if [[ -z "$BACKEND" ]]; then - if [[ "$HAVE_RP" == "1" ]]; then BACKEND="opencode" - else BACKEND="opencode"; fi -fi -``` - -## Critical Rules - -**For rp backend:** -1. **DO NOT REVIEW CODE YOURSELF** - you coordinate, RepoPrompt reviews -2. **MUST WAIT for actual RP response** - never simulate/skip the review -3. **MUST use `setup-review`** - handles window selection + builder atomically -4. **DO NOT add --json flag to chat-send** - it suppresses the review response -5. **Re-reviews MUST stay in SAME chat** - omit `--new-chat` after first review - -**For opencode backend:** -1. Use the **task tool** with subagent_type `opencode-reviewer` -2. Reviewer gathers context via tools (git diff/log, read files) -3. Parse verdict from reviewer output -4. Extract `session_id` from `<task_metadata>` and reuse it for re-reviews - -**For all backends:** -- If `REVIEW_RECEIPT_PATH` set: write receipt after review (any verdict) -- Any failure → output `<promise>RETRY</promise>` and stop - -**FORBIDDEN**: -- Self-declaring SHIP without actual backend verdict -- Mixing backends mid-review (stick to one) -- Skipping review when backend is "none" without user consent - -## Input - -Arguments: $ARGUMENTS -Format: `[focus areas or task ID]` - -Reviews all changes on **current branch** vs main/master. - -## Workflow - -**See [workflow.md](workflow.md) for full details on each backend.** - -```bash -ROOT="$(git rev-parse --show-toplevel)" -PLUGIN_ROOT="$ROOT/plugins/flow-next" -FLOWCTL="$PLUGIN_ROOT/scripts/flowctl" -REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" -``` - -### Step 0: Detect Backend - -Run backend detection from SKILL.md above. Then branch: - -### OpenCode Backend - -Use the task tool with subagent_type `opencode-reviewer`. - -Prompt must require: -- `git log main..HEAD --oneline` -- `git diff main..HEAD --stat` -- `git diff main..HEAD` -- Read any changed files needed for correctness -- No questions, no code changes, no TodoWrite -- End with `<verdict>SHIP</verdict>` or `<verdict>NEEDS_WORK</verdict>` or `<verdict>MAJOR_RETHINK</verdict>` - -Parse verdict from the subagent response. -Extract `session_id` from `<task_metadata>` and reuse for re-review. - -On NEEDS_WORK: fix code, commit, re-run review (same backend). - -Write receipt if `REVIEW_RECEIPT_PATH` set: -```bash -mkdir -p "$(dirname "$RECEIPT_PATH")" -ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)" -cat > "$RECEIPT_PATH" <<EOF -{"type":"impl_review","id":"$TASK_ID","mode":"opencode","verdict":"<VERDICT>","timestamp":"$ts"} -EOF -``` - -### RepoPrompt Backend - -```bash -# Step 1: Identify changes -git branch --show-current -git log main..HEAD --oneline 2>/dev/null || git log master..HEAD --oneline -git diff main..HEAD --name-only 2>/dev/null || git diff master..HEAD --name-only - -# Step 2: Atomic setup -eval "$($FLOWCTL rp setup-review --repo-root "$REPO_ROOT" --summary "Review implementation: <summary>")" -# Outputs W=<window> T=<tab>. If fails → <promise>RETRY</promise> - -# Step 3: Augment selection -$FLOWCTL rp select-add --window "$W" --tab "$T" path/to/changed/files... - -# Step 4: Build and send review prompt (see workflow.md) -$FLOWCTL rp chat-send --window "$W" --tab "$T" --message-file /tmp/review-prompt.md --new-chat --chat-name "Impl Review: [BRANCH]" - -# Step 5: Write receipt if REVIEW_RECEIPT_PATH set -``` - -## Fix Loop (INTERNAL - do not exit to Ralph) - -If verdict is NEEDS_WORK, loop internally until SHIP: - -1. **Parse issues** from reviewer feedback (Critical → Major → Minor) -2. **Fix code** and run tests/lints -3. **Commit fixes** (mandatory before re-review) -4. **Re-review**: - - **OpenCode**: re-run reviewer subagent with updated diff - - **RP**: `$FLOWCTL rp chat-send --window "$W" --tab "$T" --message-file /tmp/re-review.md` (NO `--new-chat`) -5. **Repeat** until `<verdict>SHIP</verdict>` - -**CRITICAL**: For RP, re-reviews must stay in the SAME chat so reviewer has context. Only use `--new-chat` on the FIRST review. diff --git a/sync/templates/opencode/skill/flow-next-impl-review/workflow.md b/sync/templates/opencode/skill/flow-next-impl-review/workflow.md deleted file mode 100644 index 76b92a3..0000000 --- a/sync/templates/opencode/skill/flow-next-impl-review/workflow.md +++ /dev/null @@ -1,297 +0,0 @@ -# Implementation Review Workflow - -## Philosophy - -The reviewer model only sees provided context. RepoPrompt's Builder discovers context you'd miss (rp backend). OpenCode uses the provided diff context (opencode backend). - ---- - -## Phase 0: Backend Detection - -**Run this first. Do not skip.** - -**CRITICAL: flowctl is BUNDLED — NOT installed globally.** `which flowctl` will fail (expected). Always use: - -```bash -set -e -ROOT="$(git rev-parse --show-toplevel)" -PLUGIN_ROOT="$ROOT/plugins/flow-next" -FLOWCTL="$PLUGIN_ROOT/scripts/flowctl" -REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" - -# Check available backends -HAVE_RP=$(which rp-cli >/dev/null 2>&1 && echo 1 || echo 0) - -# Get configured backend (priority: env > config) -BACKEND="${FLOW_REVIEW_BACKEND:-}" -if [[ -z "$BACKEND" ]]; then - BACKEND="$($FLOWCTL config get review.backend --json 2>/dev/null | jq -r '.value // empty' 2>/dev/null || echo "")" -fi - -# Fallback to available (opencode preferred) -if [[ -z "$BACKEND" ]]; then - if [[ "$HAVE_RP" == "1" ]]; then BACKEND="opencode" - else BACKEND="opencode"; fi -fi - -echo "Review backend: $BACKEND" -``` - -**If backend is "none"**: Skip review, inform user, and exit cleanly (no error). - -**Then branch to backend-specific workflow below.** - ---- - -## OpenCode Backend Workflow - -Use when `BACKEND="opencode"`. - -### Step 1: Identify changes - -```bash -TASK_ID="${1:-}" -BASE_BRANCH="main" -RECEIPT_PATH="${REVIEW_RECEIPT_PATH:-/tmp/impl-review-receipt.json}" - -BRANCH_NAME="$(git branch --show-current)" -COMMITS="$(git log main..HEAD --oneline 2>/dev/null || git log master..HEAD --oneline)" -FILES="$(git diff main..HEAD --name-only 2>/dev/null || git diff master..HEAD --name-only)" -DIFF_OUTPUT="$(git diff main..HEAD 2>/dev/null || git diff master..HEAD)" -``` - -### Step 2: Build review prompt - -Include: -- Branch + base branch -- Commit list -- Changed files -- Diff output (trim if huge) -- Focus areas from arguments -- Review criteria (correctness, security, performance, tests, risks) -- Required verdict tag - -### Step 3: Execute review (subagent) - -Use the **task** tool with subagent_type `opencode-reviewer`. The reviewer must gather context itself via tools, including Flow task/epic specs. - -**Task tool call** (example): -```json -{ - "description": "Impl review", - "prompt": "You are the OpenCode reviewer. Review current branch vs main. Rules: no questions, no code changes, no TodoWrite. REQUIRED: set FLOWCTL to `plugins/flow-next/scripts/flowctl`, then run `$FLOWCTL show <TASK_ID> --json` and `$FLOWCTL cat <TASK_ID>`. Then get epic id from task JSON and run `$FLOWCTL show <EPIC_ID> --json` and `$FLOWCTL cat <EPIC_ID>`. REQUIRED: run `git log main..HEAD --oneline` (fallback master), `git diff main..HEAD --stat`, `git diff main..HEAD`. Read any changed files needed for correctness. Then output issues grouped by severity and end with exactly one verdict tag: <verdict>SHIP</verdict> or <verdict>NEEDS_WORK</verdict> or <verdict>MAJOR_RETHINK</verdict>.", - "subagent_type": "opencode-reviewer" -} -``` - -**After the task completes**: -- Parse `VERDICT` from the subagent output. -- Extract `session_id` from the `<task_metadata>` block (used for re-reviews). - -If `VERDICT` is empty, output `<promise>RETRY</promise>` and stop. - -### Step 4: Receipt - -If `REVIEW_RECEIPT_PATH` set: - -```bash -mkdir -p "$(dirname "$RECEIPT_PATH")" -ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)" -cat > "$RECEIPT_PATH" <<EOF -{"type":"impl_review","id":"$TASK_ID","mode":"opencode","verdict":"<VERDICT>","timestamp":"$ts"} -EOF -``` - -### Step 5: Handle Verdict - -If `VERDICT=NEEDS_WORK`: -1. Parse issues from output -2. Fix code, commit, run tests -3. Re-run Step 3 **with the same task session_id** (pass `session_id` to the task tool) -4. Repeat until SHIP - ---- - -## RepoPrompt Backend Workflow - -Use when `BACKEND="rp"`. - -### Atomic Setup Block - -```bash -# Atomic: pick-window + builder -eval "$($FLOWCTL rp setup-review --repo-root \"$REPO_ROOT\" --summary \"Review implementation: <summary>\")" - -# Verify we have W and T -if [[ -z "${W:-}" || -z "${T:-}" ]]; then - echo "<promise>RETRY</promise>" - exit 0 -fi - -echo "Setup complete: W=$W T=$T" -``` - -If this block fails, output `<promise>RETRY</promise>` and stop. Do not improvise. - ---- - -## Phase 1: Identify Changes (RP) - -```bash -git branch --show-current -git log main..HEAD --oneline 2>/dev/null || git log master..HEAD --oneline -git diff main..HEAD --name-only 2>/dev/null || git diff master..HEAD --name-only -``` - ---- - -## Phase 2: Augment Selection (RP) - -```bash -# See what builder selected -$FLOWCTL rp select-get --window "$W" --tab "$T" - -# Always add changed files -$FLOWCTL rp select-add --window "$W" --tab "$T" path/to/changed/files... -``` - ---- - -## Phase 3: Execute Review (RP) - -### Build combined prompt - -Get builder's handoff: -```bash -HANDOFF="$($FLOWCTL rp prompt-get --window "$W" --tab "$T")" -``` - -Write combined prompt: -```bash -cat > /tmp/review-prompt.md << 'EOF' -[PASTE HANDOFF HERE] - ---- - -## IMPORTANT: File Contents -RepoPrompt includes the actual source code of selected files in a `<file_contents>` XML section at the end of this message. You MUST: -1. Locate the `<file_contents>` section -2. Read and analyze the actual source code within it -3. Base your review on the code, not summaries or descriptions - -If you cannot find `<file_contents>`, ask for the files to be re-attached before proceeding. - -## Review Focus -[USER'S FOCUS AREAS] - -## Review Criteria - -Conduct a John Carmack-level review: - -1. **Correctness** - Logic bugs? Edge cases? Race conditions? -2. **Safety** - Security risks? Data leaks? Injection paths? -3. **Performance** - Hot paths? N+1s? Unnecessary work? -4. **Maintainability** - Readability? Complexity? Abstractions? -5. **Tests** - Coverage gaps? Missing failure cases? -6. **Observability** - Logging/metrics? Debuggability? - -## Output Format - -For each issue: -- **Severity**: Critical / Major / Minor / Nitpick -- **Location**: File + line or area -- **Problem**: What's wrong -- **Suggestion**: How to fix - -**REQUIRED**: You MUST end your response with exactly one verdict tag. This is mandatory: -`<verdict>SHIP</verdict>` or `<verdict>NEEDS_WORK</verdict>` or `<verdict>MAJOR_RETHINK</verdict>` - -Do NOT skip this tag. The automation depends on it. -EOF -``` - -### Send to RepoPrompt - -```bash -$FLOWCTL rp chat-send --window "$W" --tab "$T" --message-file /tmp/review-prompt.md --new-chat --chat-name "Impl Review: [BRANCH]" -``` - -**WAIT** for response. Takes 1-5+ minutes. - ---- - -## Phase 4: Receipt + Status (RP) - -### Write receipt (if REVIEW_RECEIPT_PATH set) - -```bash -if [[ -n "${REVIEW_RECEIPT_PATH:-}" ]]; then - ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)" - mkdir -p "$(dirname "$REVIEW_RECEIPT_PATH")" - cat > "$REVIEW_RECEIPT_PATH" <<EOF -{"type":"impl_review","id":"<TASK_ID>","mode":"rp","timestamp":"$ts"} -EOF - echo "REVIEW_RECEIPT_WRITTEN: $REVIEW_RECEIPT_PATH" -fi -``` - ---- - -## Fix Loop (RP) - -**CRITICAL: You MUST fix the code BEFORE re-reviewing. Never re-review without making changes.** - -If verdict is NEEDS_WORK: - -1. **Parse issues** - Extract ALL issues by severity (Critical → Major → Minor) -2. **Fix the code** - Address each issue in order -3. **Run tests/lints** - Verify fixes don't break anything -4. **Commit fixes** (MANDATORY before re-review): - ```bash - git add -A - git commit -m "fix: address review feedback" - ``` - **If you skip this and re-review without committing changes, reviewer will return NEEDS_WORK again.** - -5. **Re-review with fix summary** (only AFTER step 4): - - **IMPORTANT**: Do NOT re-add files already in the selection. RepoPrompt auto-refreshes - file contents on every message. Only use `select-add` for NEW files created during fixes: - ```bash - # Only if fixes created new files not in original selection - if [[ -n "$NEW_FILES" ]]; then - $FLOWCTL rp select-add --window "$W" --tab "$T" $NEW_FILES - fi - ``` - - Then send re-review request (NO --new-chat, stay in same chat). - - **Keep this message minimal. Do NOT enumerate issues or reference file_contents - the reviewer already has context from the previous exchange.** - - ```bash - cat > /tmp/re-review.md << 'EOF' - All issues from your previous review have been addressed. Please verify the updated implementation and provide final verdict. - - **REQUIRED**: End with `<verdict>SHIP</verdict>` or `<verdict>NEEDS_WORK</verdict>` or `<verdict>MAJOR_RETHINK</verdict>` - EOF - - $FLOWCTL rp chat-send --window "$W" --tab "$T" --message-file /tmp/re-review.md - ``` -6. **Repeat** until Ship - -**Anti-pattern**: Re-adding already-selected files before re-review. RP auto-refreshes; re-adding can cause issues. - ---- - -## Failure Recovery - -If rp-cli fails or returns empty output, output `<promise>RETRY</promise>` and stop. Do not improvise. - ---- - -## Forbidden - -- Self-declaring SHIP without actual reviewer output -- Skipping `setup-review` for rp backend -- Starting a new chat for re-reviews (rp backend only) -- Missing changed files in RP selection diff --git a/sync/templates/opencode/skill/flow-next-interview/SKILL.md b/sync/templates/opencode/skill/flow-next-interview/SKILL.md deleted file mode 100644 index dc62f70..0000000 --- a/sync/templates/opencode/skill/flow-next-interview/SKILL.md +++ /dev/null @@ -1,166 +0,0 @@ ---- -name: flow-next-interview -description: Interview user in-depth about an epic, task, or spec file to extract complete implementation details. Use when user wants to flesh out a spec, refine requirements, or clarify a feature before building. Triggers on /flow-next:interview with Flow IDs (fn-1, fn-1.2) or file paths. ---- - -# Flow interview - -Conduct an extremely thorough interview about a task/spec and write refined details back. - -**IMPORTANT**: This plugin uses `.flow/` for ALL task tracking. Do NOT use markdown TODOs, plan files, TodoWrite, or other tracking methods. All task state must be read and written via `flowctl`. - -**CRITICAL: flowctl is BUNDLED — NOT installed globally.** `which flowctl` will fail (expected). Always use: -```bash -ROOT="$(git rev-parse --show-toplevel)" -PLUGIN_ROOT="$ROOT/plugins/flow-next" -FLOWCTL="$PLUGIN_ROOT/scripts/flowctl" -$FLOWCTL <command> -``` - -**Role**: technical interviewer, spec refiner -**Goal**: extract complete implementation details through deep questioning (40+ questions typical) - -## Input - -Full request: $ARGUMENTS - -Accepts: -- **Flow epic ID** `fn-N`: Fetch with `flowctl show`, write back with `flowctl epic set-plan` -- **Flow task ID** `fn-N.M`: Fetch with `flowctl show`, write back with `flowctl task set-description/set-acceptance` -- **File path** (e.g., `docs/spec.md`): Read file, interview, rewrite file -- **Empty**: Prompt for target - -Examples: -- `/flow-next:interview fn-1` -- `/flow-next:interview fn-1.3` -- `/flow-next:interview docs/oauth-spec.md` - -If empty, ask: "What should I interview you about? Give me a Flow ID (e.g., fn-1) or file path (e.g., docs/spec.md)" - -## Setup - -```bash -ROOT="$(git rev-parse --show-toplevel)" -PLUGIN_ROOT="$ROOT/plugins/flow-next" -FLOWCTL="$PLUGIN_ROOT/scripts/flowctl" -``` - -## Detect Input Type - -1. **Flow epic ID pattern**: matches `fn-\d+` (e.g., fn-1, fn-12) - - Fetch: `$FLOWCTL show <id> --json` - - Read spec: `$FLOWCTL cat <id>` - -2. **Flow task ID pattern**: matches `fn-\d+\.\d+` (e.g., fn-1.3, fn-12.5) - - Fetch: `$FLOWCTL show <id> --json` - - Read spec: `$FLOWCTL cat <id>` - - Also get epic context: `$FLOWCTL cat <epic-id>` - -3. **File path**: anything else with a path-like structure or .md extension - - Read file contents - - If file doesn't exist, ask user to provide valid path - -## Interview Process - -Use the **question** tool for all questions. Group 2-4 questions per tool call. Expect 40+ total for complex specs. Wait for answers before continuing. - -Rules: -- Each question must include: `header` (<=12 chars), `question`, and `options` (2-5). -- `custom` defaults to true, so users can type a custom answer. -- Keep option labels short (1-5 words) and add a brief `description`. -- Prefer concrete options; include “Not sure” when ambiguous. - -Example tool call (schema only): -``` -question({ - "questions": [ - { - "header": "Header", - "question": "Where should the badge appear?", - "options": [ - {"label": "Left", "description": "Near logo"}, - {"label": "Right", "description": "Near user menu"}, - {"label": "Center", "description": "Centered"}, - {"label": "Not sure", "description": "Decide based on layout"} - ] - } - ] -}) -``` - -## Question Categories - -Read [questions.md](questions.md) for all question categories and interview guidelines. - -## Write Refined Spec - -After interview complete, write everything back. - -### For Flow Epic ID - -Update epic spec using stdin heredoc (preferred): -```bash -$FLOWCTL epic set-plan <id> --file - --json <<'EOF' -# Epic Title - -## Problem -Clear problem statement - -## Approach -Technical approach with specifics, key decisions from interview - -## Edge Cases -- Edge case 1 -- Edge case 2 - -## Quick commands -```bash -# smoke test command -``` - -## Acceptance -- [ ] Criterion 1 -- [ ] Criterion 2 -EOF -``` - -Create/update tasks if interview revealed breakdown: -```bash -$FLOWCTL task create --epic <id> --title "..." --json -# Use set-spec for combined description + acceptance (fewer writes) -$FLOWCTL task set-spec <task-id> --description /tmp/desc.md --acceptance /tmp/acc.md --json -``` - -### For Flow Task ID - -Update task using combined set-spec (preferred) or separate calls: -```bash -# Preferred: combined set-spec (2 writes instead of 4) -$FLOWCTL task set-spec <id> --description /tmp/desc.md --acceptance /tmp/acc.md --json - -# Or use stdin for description only: -$FLOWCTL task set-description <id> --file - --json <<'EOF' -Clear task description with technical details and edge cases from interview -EOF -``` - -### For File Path - -Rewrite the file with refined spec: -- Preserve any existing structure/format -- Add sections for areas covered in interview -- Include technical details, edge cases, acceptance criteria -- Keep it actionable and specific - -## Completion - -Show summary: -- Number of questions asked -- Key decisions captured -- What was written (Flow ID updated / file rewritten) -- Suggest next step: `/flow-next:plan` or `/flow-next:work` - -## Notes - -- This process should feel thorough - user should feel they've thought through everything -- Quality over speed - don't rush to finish diff --git a/sync/templates/opencode/skill/flow-next-plan-review/SKILL.md b/sync/templates/opencode/skill/flow-next-plan-review/SKILL.md deleted file mode 100644 index 4bba3eb..0000000 --- a/sync/templates/opencode/skill/flow-next-plan-review/SKILL.md +++ /dev/null @@ -1,182 +0,0 @@ ---- -name: flow-next-plan-review -description: Carmack-level plan review via RepoPrompt or OpenCode. Use when reviewing Flow epic specs or design docs. Triggers on /flow-next:plan-review. ---- - -# Plan Review Mode - -**Read [workflow.md](workflow.md) for detailed phases and anti-patterns.** - -Conduct a John Carmack-level review of epic plans. - -**Role**: Code Review Coordinator (NOT the reviewer) -**Backends**: OpenCode (opencode) or RepoPrompt (rp) - -**CRITICAL: flowctl is BUNDLED — NOT installed globally.** `which flowctl` will fail (expected). Always use: -```bash -ROOT="$(git rev-parse --show-toplevel)" -PLUGIN_ROOT="$ROOT/plugins/flow-next" -FLOWCTL="$PLUGIN_ROOT/scripts/flowctl" -``` - -## Backend Selection - -**Priority** (first match wins): -1. `--review=rp|opencode|export|none` argument -2. `FLOW_REVIEW_BACKEND` env var (`rp`, `opencode`, `none`) -3. `.flow/config.json` → `review.backend` -4. Interactive prompt if rp-cli available (and not in Ralph mode) -5. Default: `opencode` - -### Parse from arguments first - -Check $ARGUMENTS for: -- `--review=rp` or `--review rp` → use rp -- `--review=opencode` or `--review opencode` → use opencode -- `--review=export` or `--review export` → use export -- `--review=none` or `--review none` → skip review - -If found, use that backend and skip all other detection. - -### Otherwise detect - -```bash -# Check available backends -HAVE_RP=0; -if command -v rp-cli >/dev/null 2>&1; then - HAVE_RP=1; -elif [[ -x /opt/homebrew/bin/rp-cli || -x /usr/local/bin/rp-cli ]]; then - HAVE_RP=1; -fi; - -# Get configured backend -BACKEND="${FLOW_REVIEW_BACKEND:-}"; -if [[ -z "$BACKEND" ]]; then - BACKEND="$($FLOWCTL config get review.backend --json 2>/dev/null | jq -r '.value // empty')"; -fi -``` - -**MUST RUN the detection command above** and use its result. Do **not** assume rp-cli is missing without running it. - -### If no backend configured and rp available - -If `BACKEND` is empty AND `HAVE_RP=1`, AND not in Ralph mode (`FLOW_RALPH` not set): - -Output this question as text: -``` -Which review backend? -a) OpenCode review (GPT-5.2, reasoning high) -b) RepoPrompt (macOS, visual builder) - -(Reply: "a", "opencode", "b", "rp", or just tell me) -``` - -**IMPORTANT**: Ask this in **plain text only**. **Do NOT use the question tool.** - -Wait for response. Parse naturally. - -**Default if empty/ambiguous**: `opencode` - -### If only one available or in Ralph mode - -```bash -# Fallback to available -if [[ -z "$BACKEND" ]]; then - if [[ "$HAVE_RP" == "1" ]]; then BACKEND="opencode" - else BACKEND="opencode"; fi -fi -``` - -## Critical Rules - -**For rp backend:** -1. **DO NOT REVIEW THE PLAN YOURSELF** - you coordinate, RepoPrompt reviews -2. **MUST WAIT for actual RP response** - never simulate/skip the review -3. **MUST use `setup-review`** - handles window selection + builder atomically -4. **DO NOT add --json flag to chat-send** - it suppresses the review response -5. **Re-reviews MUST stay in SAME chat** - omit `--new-chat` after first review - -**For opencode backend:** -1. Use the **task tool** with subagent_type `opencode-reviewer` -2. Reviewer gathers context via tools (`flowctl show/cat`) -3. Parse verdict from reviewer output -4. Extract `session_id` from `<task_metadata>` and reuse it for re-reviews - -**For all backends:** -- If `REVIEW_RECEIPT_PATH` set: write receipt after review (any verdict) -- Any failure → output `<promise>RETRY</promise>` and stop - -**FORBIDDEN**: -- Self-declaring SHIP without actual backend verdict -- Mixing backends mid-review (stick to one) -- Skipping review when backend is "none" without user consent - -## Input - -Arguments: $ARGUMENTS -Format: `<flow-epic-id> [focus areas]` - -## Workflow - -**See [workflow.md](workflow.md) for full details on each backend.** - -```bash -ROOT="$(git rev-parse --show-toplevel)" -PLUGIN_ROOT="$ROOT/plugins/flow-next" -FLOWCTL="$PLUGIN_ROOT/scripts/flowctl" -REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" -``` - -### Step 0: Detect Backend - -Run backend detection from SKILL.md above. Then branch: - -### OpenCode Backend - -Use the task tool with subagent_type `opencode-reviewer`. - -Prompt must require: -- `flowctl show <EPIC_ID> --json` -- `flowctl cat <EPIC_ID>` -- No questions, no code changes, no TodoWrite -- End with `<verdict>SHIP</verdict>` or `<verdict>NEEDS_WORK</verdict>` or `<verdict>MAJOR_RETHINK</verdict>` - -Parse verdict from the subagent response. -Extract `session_id` from `<task_metadata>` and reuse for re-review. - -On NEEDS_WORK: fix plan via `$FLOWCTL epic set-plan`, then re-run review (same backend). - -### RepoPrompt Backend - -```bash -# Step 1: Get plan content -$FLOWCTL show <id> --json -$FLOWCTL cat <id> - -# Step 2: Atomic setup -eval "$($FLOWCTL rp setup-review --repo-root "$REPO_ROOT" --summary "Review plan for <EPIC_ID>: <summary>")" -# Outputs W=<window> T=<tab>. If fails → <promise>RETRY</promise> - -# Step 3: Augment selection -$FLOWCTL rp select-add --window "$W" --tab "$T" .flow/specs/<epic-id>.md - -# Step 4: Build and send review prompt (see workflow.md) -$FLOWCTL rp chat-send --window "$W" --tab "$T" --message-file /tmp/review-prompt.md --new-chat --chat-name "Plan Review: <EPIC_ID>" - -# Step 5: Write receipt if REVIEW_RECEIPT_PATH set -# Step 6: Update status -$FLOWCTL epic set-plan-review-status <EPIC_ID> --status ship --json -``` - -## Fix Loop (INTERNAL - do not exit to Ralph) - -If verdict is NEEDS_WORK, loop internally until SHIP: - -1. **Parse issues** from reviewer feedback -2. **Fix plan** via `$FLOWCTL epic set-plan <EPIC_ID> --file /tmp/updated-plan.md` -3. **Re-review**: - - **OpenCode**: re-run reviewer subagent with updated plan - - **RP**: `$FLOWCTL rp chat-send --window "$W" --tab "$T" --message-file /tmp/re-review.md` (NO `--new-chat`) -4. **Repeat** until `<verdict>SHIP</verdict>` - -**CRITICAL**: For RP, re-reviews must stay in the SAME chat so reviewer has context. Only use `--new-chat` on the FIRST review. diff --git a/sync/templates/opencode/skill/flow-next-plan-review/workflow.md b/sync/templates/opencode/skill/flow-next-plan-review/workflow.md deleted file mode 100644 index 3c58304..0000000 --- a/sync/templates/opencode/skill/flow-next-plan-review/workflow.md +++ /dev/null @@ -1,342 +0,0 @@ -# Plan Review Workflow - -## Philosophy - -The reviewer model only sees provided context. RepoPrompt's Builder discovers context you'd miss (rp backend). OpenCode uses the provided plan content and criteria (opencode backend). - ---- - -## Phase 0: Backend Detection - -**Run this first. Do not skip.** - -**CRITICAL: flowctl is BUNDLED — NOT installed globally.** `which flowctl` will fail (expected). Always use: - -```bash -set -e -ROOT="$(git rev-parse --show-toplevel)" -PLUGIN_ROOT="$ROOT/plugins/flow-next" -FLOWCTL="$PLUGIN_ROOT/scripts/flowctl" -REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" - -# Check available backends -HAVE_RP=$(which rp-cli >/dev/null 2>&1 && echo 1 || echo 0) - -# Get configured backend (priority: env > config) -BACKEND="${FLOW_REVIEW_BACKEND:-}" -if [[ -z "$BACKEND" ]]; then - BACKEND="$($FLOWCTL config get review.backend --json 2>/dev/null | jq -r '.value // empty' 2>/dev/null || echo "")" -fi - -# Fallback to available (opencode preferred) -if [[ -z "$BACKEND" ]]; then - if [[ "$HAVE_RP" == "1" ]]; then BACKEND="opencode" - else BACKEND="opencode"; fi -fi - -echo "Review backend: $BACKEND" -``` - -**If backend is "none"**: Skip review, inform user, and exit cleanly (no error). - -**Then branch to backend-specific workflow below.** - ---- - -## OpenCode Backend Workflow - -Use when `BACKEND="opencode"`. - -### Step 1: Gather plan content - -```bash -EPIC_ID="${1:-}" -RECEIPT_PATH="${REVIEW_RECEIPT_PATH:-/tmp/plan-review-receipt.json}" - -PLAN_SUMMARY="$($FLOWCTL show "$EPIC_ID" --json)" -PLAN_SPEC="$($FLOWCTL cat "$EPIC_ID")" -``` - -### Step 2: Build review prompt - -Include: -- Plan summary + spec -- Focus areas from arguments -- Review criteria (completeness, feasibility, clarity, architecture, risks, scope, testability) -- Required verdict tag - -### Step 3: Execute review (subagent) - -Use the **task** tool with subagent_type `opencode-reviewer`. The reviewer must gather context itself via tools, including Flow plan/spec. - -**Task tool call** (example): -```json -{ - "description": "Plan review", - "prompt": "You are the OpenCode reviewer. Review the plan for EPIC_ID. Rules: no questions, no code changes, no TodoWrite. REQUIRED: set FLOWCTL to `plugins/flow-next/scripts/flowctl`, then run `$FLOWCTL show <EPIC_ID> --json` and `$FLOWCTL cat <EPIC_ID>`. Review for completeness, feasibility, clarity, architecture, risks (incl security), scope, and testability. End with exactly one verdict tag: <verdict>SHIP</verdict> or <verdict>NEEDS_WORK</verdict> or <verdict>MAJOR_RETHINK</verdict>.", - "subagent_type": "opencode-reviewer" -} -``` - -**After the task completes**: -- Parse `VERDICT` from the subagent output. -- Extract `session_id` from the `<task_metadata>` block (used for re-reviews). - -If `VERDICT` is empty, output `<promise>RETRY</promise>` and stop. - -### Step 4: Update Status - -```bash -# Based on verdict -$FLOWCTL epic set-plan-review-status "$EPIC_ID" --status ship --json -# OR -$FLOWCTL epic set-plan-review-status "$EPIC_ID" --status needs_work --json -``` - -### Step 5: Receipt - -If `REVIEW_RECEIPT_PATH` set: - -```bash -mkdir -p "$(dirname "$RECEIPT_PATH")" -ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)" -cat > "$RECEIPT_PATH" <<EOF -{"type":"plan_review","id":"$EPIC_ID","mode":"opencode","verdict":"<VERDICT>","timestamp":"$ts"} -EOF -``` - -### Step 6: Handle Verdict - -If `VERDICT=NEEDS_WORK`: -1. Parse issues from output -2. Fix plan via `$FLOWCTL epic set-plan` -3. Re-run Step 3 **with the same task session_id** (pass `session_id` to the task tool) -4. Repeat until SHIP - ---- - -## RepoPrompt Backend Workflow - -Use when `BACKEND="rp"`. - -### Atomic Setup Block - -```bash -# Atomic: pick-window + builder -eval "$($FLOWCTL rp setup-review --repo-root "$REPO_ROOT" --summary "Review plan for <EPIC_ID>: <summary>")" - -# Verify we have W and T -if [[ -z "${W:-}" || -z "${T:-}" ]]; then - echo "<promise>RETRY</promise>" - exit 0 -fi - -echo "Setup complete: W=$W T=$T" -``` - -If this block fails, output `<promise>RETRY</promise>` and stop. Do not improvise. - ---- - -## Phase 1: Read the Plan (RP) - -**If Flow issue:** -```bash -$FLOWCTL show <id> --json -$FLOWCTL cat <id> -``` - -Save output for inclusion in review prompt. Compose a 1-2 sentence summary for the setup-review command. - -**Save checkpoint** (protects against context compaction during review): -```bash -$FLOWCTL checkpoint save --epic <id> --json -``` -This creates `.flow/.checkpoint-<id>.json` with full state. If compaction occurs during review-fix cycles, restore with `$FLOWCTL checkpoint restore --epic <id>`. - ---- - -## Phase 2: Augment Selection (RP) - -Builder selects context automatically. Review and add must-haves: - -```bash -# See what builder selected -$FLOWCTL rp select-get --window "$W" --tab "$T" - -# Always add the plan spec -$FLOWCTL rp select-add --window "$W" --tab "$T" .flow/specs/<epic-id>.md - -# Add PRD/architecture docs if found -$FLOWCTL rp select-add --window "$W" --tab "$T" docs/prd.md -``` - -**Why this matters:** Chat only sees selected files. - ---- - -## Phase 3: Execute Review (RP) - -### Build combined prompt - -Get builder's handoff: -```bash -HANDOFF="$($FLOWCTL rp prompt-get --window "$W" --tab "$T")" -``` - -Write combined prompt: -```bash -cat > /tmp/review-prompt.md << 'EOF' -[PASTE HANDOFF HERE] - ---- - -## IMPORTANT: File Contents -RepoPrompt includes the actual source code of selected files in a `<file_contents>` XML section at the end of this message. You MUST: -1. Locate the `<file_contents>` section -2. Read and analyze the actual source code within it -3. Base your review on the code, not summaries or descriptions - -If you cannot find `<file_contents>`, ask for the files to be re-attached before proceeding. - -## Plan Under Review -[PASTE flowctl show OUTPUT] - -## Review Focus -[USER'S FOCUS AREAS] - -## Review Criteria - -Conduct a John Carmack-level review: - -1. **Completeness** - All requirements covered? Missing edge cases? -2. **Feasibility** - Technically sound? Dependencies clear? -3. **Clarity** - Specs unambiguous? Acceptance criteria testable? -4. **Architecture** - Right abstractions? Clean boundaries? -5. **Risks** - Blockers identified? Security gaps? Mitigation? -6. **Scope** - Right-sized? Over/under-engineering? -7. **Testability** - How will we verify this works? - -## Output Format - -For each issue: -- **Severity**: Critical / Major / Minor / Nitpick -- **Location**: Which task or section -- **Problem**: What's wrong -- **Suggestion**: How to fix - -**REQUIRED**: You MUST end your response with exactly one verdict tag. This is mandatory: -`<verdict>SHIP</verdict>` or `<verdict>NEEDS_WORK</verdict>` or `<verdict>MAJOR_RETHINK</verdict>` - -Do NOT skip this tag. The automation depends on it. -EOF -``` - -### Send to RepoPrompt - -```bash -$FLOWCTL rp chat-send --window "$W" --tab "$T" --message-file /tmp/review-prompt.md --new-chat --chat-name "Plan Review: <EPIC_ID>" -``` - -**WAIT** for response. Takes 1-5+ minutes. - ---- - -## Phase 4: Receipt + Status (RP) - -### Write receipt (if REVIEW_RECEIPT_PATH set) - -```bash -if [[ -n "${REVIEW_RECEIPT_PATH:-}" ]]; then - ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)" - mkdir -p "$(dirname "$REVIEW_RECEIPT_PATH")" - cat > "$REVIEW_RECEIPT_PATH" <<EOF -{"type":"plan_review","id":"<EPIC_ID>","mode":"rp","timestamp":"$ts"} -EOF - echo "REVIEW_RECEIPT_WRITTEN: $REVIEW_RECEIPT_PATH" -fi -``` - -### Update status - -Extract verdict from response, then: -```bash -# If SHIP -$FLOWCTL epic set-plan-review-status <EPIC_ID> --status ship --json - -# If NEEDS_WORK or MAJOR_RETHINK -$FLOWCTL epic set-plan-review-status <EPIC_ID> --status needs_work --json -``` - -If no verdict tag, output `<promise>RETRY</promise>` and stop. - ---- - -## Fix Loop (RP) - -**CRITICAL: You MUST fix the plan BEFORE re-reviewing. Never re-review without making changes.** - -If verdict is NEEDS_WORK: - -1. **Parse issues** - Extract ALL issues by severity (Critical → Major → Minor) -2. **Fix the plan** - Address each issue. Write updated plan to temp file. -3. **Update plan in flowctl** (MANDATORY before re-review): - ```bash - # Option A: stdin heredoc (preferred, no temp file) - $FLOWCTL epic set-plan <EPIC_ID> --file - --json <<'EOF' - <updated plan content> - EOF - - # Option B: temp file (if content has single quotes) - $FLOWCTL epic set-plan <EPIC_ID> --file /tmp/updated-plan.md --json - ``` - **If you skip this step and re-review with same content, reviewer will return NEEDS_WORK again.** - - **Recovery**: If context compaction occurred, restore from checkpoint first: - ```bash - $FLOWCTL checkpoint restore --epic <EPIC_ID> --json - ``` - -4. **Re-review with fix summary** (only AFTER step 3): - - **IMPORTANT**: Do NOT re-add files already in the selection. RepoPrompt auto-refreshes - file contents on every message. Only use `select-add` for NEW files created during fixes: - ```bash - # Only if fixes created new files not in original selection - if [[ -n "$NEW_FILES" ]]; then - $FLOWCTL rp select-add --window "$W" --tab "$T" $NEW_FILES - fi - ``` - - Then send re-review request (NO --new-chat, stay in same chat). - - **Keep this message minimal. Do NOT enumerate issues or reference file_contents - the reviewer already has context from the previous exchange.** - - ```bash - cat > /tmp/re-review.md << 'EOF' - All issues from your previous review have been addressed. Please verify the updated plan and provide final verdict. - - **REQUIRED**: End with `<verdict>SHIP</verdict>` or `<verdict>NEEDS_WORK</verdict>` or `<verdict>MAJOR_RETHINK</verdict>` - EOF - - $FLOWCTL rp chat-send --window "$W" --tab "$T" --message-file /tmp/re-review.md - ``` -5. **Repeat** until Ship - -**Anti-pattern**: Re-adding already-selected files before re-review. RP auto-refreshes; re-adding can cause issues. - -**Anti-pattern**: Re-reviewing without calling `epic set-plan` first. This wastes reviewer time and loops forever. - ---- - -## Failure Recovery - -If rp-cli fails or returns empty output, output `<promise>RETRY</promise>` and stop. Do not improvise. - ---- - -## Forbidden - -- Self-declaring SHIP without actual reviewer output -- Skipping `setup-review` for rp backend -- Starting a new chat for re-reviews (rp backend only) diff --git a/sync/templates/opencode/skill/flow-next-plan/SKILL.md b/sync/templates/opencode/skill/flow-next-plan/SKILL.md deleted file mode 100644 index ff02c17..0000000 --- a/sync/templates/opencode/skill/flow-next-plan/SKILL.md +++ /dev/null @@ -1,155 +0,0 @@ ---- -name: flow-next-plan -description: Create structured build plans from feature requests or Flow IDs. Use when planning features or designing implementation. Triggers on /flow-next:plan with text descriptions or Flow IDs (fn-1, fn-1.2). ---- - -# Flow plan - -Turn a rough idea into an epic with tasks in `.flow/`. This skill does not write code. - -Follow this skill and linked workflows exactly. Deviations cause drift, bad gates, retries, and user frustration. - -**IMPORTANT**: This plugin uses `.flow/` for ALL task tracking. Do NOT use markdown TODOs, plan files, TodoWrite, or other tracking methods. All task state must be read and written via `flowctl`. - -**CRITICAL: flowctl is BUNDLED — NOT installed globally.** `which flowctl` will fail (expected). Always use: -```bash -ROOT="$(git rev-parse --show-toplevel)" -PLUGIN_ROOT="$ROOT/plugins/flow-next" -FLOWCTL="$PLUGIN_ROOT/scripts/flowctl" -$FLOWCTL <command> -``` - -**Role**: product-minded planner with strong repo awareness. -**Goal**: produce an epic with tasks that match existing conventions and reuse points. -**Task size**: every task must fit one `/flow-next:work` iteration. If it won't, split it. - -## Input - -Full request: $ARGUMENTS - -Accepts: -- Feature/bug description in natural language -- Flow epic ID `fn-N` to refine existing epic -- Flow task ID `fn-N.M` to refine specific task -- Chained instructions like "then review with /flow-next:plan-review" - -Examples: -- `/flow-next:plan Add OAuth login for users` -- `/flow-next:plan fn-1` -- `/flow-next:plan fn-1 then review via /flow-next:plan-review` - -If empty, ask: "What should I plan? Give me the feature or bug in 1-5 sentences." - -## FIRST: Parse Options or Ask Questions - -Check available backends and configured preference: -```bash -HAVE_RP=0; -if command -v rp-cli >/dev/null 2>&1; then - HAVE_RP=1; -elif [[ -x /opt/homebrew/bin/rp-cli || -x /usr/local/bin/rp-cli ]]; then - HAVE_RP=1; -fi; - -# Check configured backend (priority: env > config) -CONFIGURED_BACKEND="${FLOW_REVIEW_BACKEND:-}"; -if [[ -z "$CONFIGURED_BACKEND" ]]; then - CONFIGURED_BACKEND="$($FLOWCTL config get review.backend --json 2>/dev/null | jq -r '.value // empty')"; -fi -``` - -**MUST RUN the detection command above** and use its result. Do **not** assume rp-cli is missing without running it. - -### Option Parsing (skip questions if found in arguments) - -Parse the arguments for these patterns. If found, use them and skip questions: - -**Research approach** (only if rp-cli available): -- `--research=rp` or `--research rp` or "use rp" or "context-scout" or "use repoprompt" → context-scout -- `--research=grep` or `--research grep` or "use grep" or "repo-scout" or "fast" → repo-scout - -**Review mode**: -- `--review=opencode` or "opencode review" or "use opencode" → OpenCode review (GPT-5.2, reasoning high) -- `--review=rp` or "review with rp" or "rp chat" or "repoprompt review" → RepoPrompt chat (via `flowctl rp chat-send`) -- `--review=export` or "export review" or "external llm" → export for external LLM -- `--review=none` or `--no-review` or "no review" or "skip review" → no review - -### If options NOT found in arguments - -**IMPORTANT**: Ask setup questions in **plain text only**. **Do NOT use the question tool.** This is required for voice dictation (e.g., "1a 2b"). - -**Skip review question if**: Ralph mode (`FLOW_RALPH=1`) OR backend already configured (`CONFIGURED_BACKEND` not empty). In these cases, only ask research question (if rp-cli available): - -``` -Quick setup: Use RepoPrompt for deeper context? -a) Yes, context-scout (slower, thorough) -b) No, repo-scout (faster) - -(Reply: "a", "b", or just tell me) -``` - -If rp-cli not available, skip questions entirely and use defaults. - -**Otherwise**, output questions based on available backends: - -**If rp-cli available:** -``` -Quick setup before planning: - -1. **Research approach** — Use RepoPrompt for deeper context? - a) Yes, context-scout (slower, thorough) - b) No, repo-scout (faster) - -2. **Review** — Run Carmack-level review after? - a) Yes, OpenCode review (GPT-5.2, reasoning high) - b) Yes, RepoPrompt chat (macOS, visual builder) - c) Yes, export for external LLM (ChatGPT, Claude web) - d) No - -(Reply: "1a 2a", "1b 2d", or just tell me naturally) -``` - -**If rp-cli not available:** -``` -Quick setup before planning: - -**Review** — Run Carmack-level review after? -a) Yes, OpenCode review (GPT-5.2, reasoning high) -b) Yes, export for external LLM -c) No - -(Reply: "a", "b", or just tell me naturally) -``` - -Wait for response. Parse naturally — user may reply terse ("1a 2b") or ramble via voice. - -**Defaults when empty/ambiguous:** -- Research = `grep` (repo-scout) -- Review = configured backend if set, else `opencode`, else `rp` if available, else `none` - -If rp-cli not available: skip research questions, use repo-scout, review defaults to `opencode` or `none` if disabled. - -**Defaults when no review backend available:** -- Research = `grep` -- Review = `none` - -## Workflow - -Read [steps.md](steps.md) and follow each step in order. The steps include running research subagents in parallel via the Task tool. -If user chose review: -- Option 2a: run `/flow-next:plan-review` after Step 4, fix issues until it passes -- Option 2b: run `/flow-next:plan-review` with export mode after Step 4 - -## Output - -All plans go into `.flow/`: -- Epic: `.flow/epics/fn-N.json` + `.flow/specs/fn-N.md` -- Tasks: `.flow/tasks/fn-N.M.json` + `.flow/tasks/fn-N.M.md` - -**Never write plan files outside `.flow/`. Never use TodoWrite for task tracking.** - -## Output rules - -- Only create/update epics and tasks via flowctl -- No code changes -- No plan files outside `.flow/` diff --git a/sync/templates/opencode/skill/flow-next-plan/steps.md b/sync/templates/opencode/skill/flow-next-plan/steps.md deleted file mode 100644 index 7ac8504..0000000 --- a/sync/templates/opencode/skill/flow-next-plan/steps.md +++ /dev/null @@ -1,233 +0,0 @@ -# Flow Plan Steps - -**IMPORTANT**: Steps 1-3 (research, gap analysis, depth) ALWAYS run regardless of input type. - -**CRITICAL**: If you are about to create: -- a markdown TODO list, -- a task list outside `.flow/`, -- or any plan files outside `.flow/`, - -**STOP** and instead: -- create/update tasks in `.flow/` using `flowctl`, -- record details in the epic/task spec markdown. - -## Success criteria - -- Plan references existing files/patterns with line refs -- Reuse points are explicit (centralized code called out) -- Acceptance checks are testable -- Tasks are small enough for one `/flow-next:work` iteration (split if not) -- Open questions are listed - -## Step 0: Initialize .flow - -**CRITICAL: flowctl is BUNDLED — NOT installed globally.** `which flowctl` will fail (expected). Always use: - -```bash -# Get flowctl path -ROOT="$(git rev-parse --show-toplevel)" -PLUGIN_ROOT="$ROOT/plugins/flow-next" -FLOWCTL="$PLUGIN_ROOT/scripts/flowctl" - -# Ensure .flow exists -$FLOWCTL init --json -``` - -## Step 1: Fast research (parallel) - -**If input is a Flow ID** (fn-N or fn-N.M): First fetch it with `$FLOWCTL show <id> --json` and `$FLOWCTL cat <id>` to get the request context. - -**Check if memory is enabled:** -```bash -$FLOWCTL config get memory.enabled --json -``` - -**Based on user's choice in SKILL.md setup:** - -**If user chose context-scout (RepoPrompt)**: -Run these subagents in parallel using the **batch** tool with **task** calls: -- context-scout (<request>) - uses RepoPrompt builder for AI-powered file discovery -- practice-scout (<request>) -- docs-scout (<request>) -- memory-scout (<request>) — **only if memory.enabled is true** - -**If user chose repo-scout (default/faster)** OR rp-cli unavailable: -Run these subagents in parallel using the **batch** tool with **task** calls: -- repo-scout (<request>) - uses standard Grep/Glob/Read -- practice-scout (<request>) -- docs-scout (<request>) -- memory-scout (<request>) — **only if memory.enabled is true** - -Example batch payload: -```json -{ - "tool_calls": [ - {"tool": "task", "parameters": {"description": "Context scout", "prompt": "<request>", "subagent_type": "context-scout"}}, - {"tool": "task", "parameters": {"description": "Practice scout", "prompt": "<request>", "subagent_type": "practice-scout"}}, - {"tool": "task", "parameters": {"description": "Docs scout", "prompt": "<request>", "subagent_type": "docs-scout"}} - ] -} -``` -Max 10 tool calls per batch. Split if more. Do not include external/MCP tools in batch. - -Must capture: -- File paths + line refs -- Existing centralized code to reuse -- Similar patterns / prior work -- External docs links -- Project conventions (CLAUDE.md, CONTRIBUTING, etc) -- Architecture patterns and data flow (especially with context-scout) - -## Step 2: Flow gap check - -Run the gap analyst subagent with the task tool: -- subagent_type: `flow-gap-analyst` -- prompt: `<request> + research_findings>` - -Fold gaps + questions into the plan. - -## Step 3: Pick depth - -Default to short unless complexity demands more. - -**SHORT** (bugs, small changes) -- Problem or goal -- Acceptance checks -- Key context - -**STANDARD** (most features) -- Overview + scope -- Approach -- Risks / dependencies -- Acceptance checks -- Test notes -- References - -**DEEP** (large/critical) -- Detailed phases -- Alternatives considered -- Non-functional targets -- Rollout/rollback -- Docs + metrics -- Risks + mitigations - -## Step 4: Write to .flow - -**Efficiency note**: Use stdin (`--file -`) with heredocs to avoid temp files. Use `task set-spec` to set description + acceptance in one call. - -**Route A - Input was an existing Flow ID**: - -1. If epic ID (fn-N): - ```bash - # Use stdin heredoc (no temp file needed) - $FLOWCTL epic set-plan <id> --file - --json <<'EOF' - <plan content here> - EOF - ``` - - Create/update child tasks as needed - -2. If task ID (fn-N.M): - ```bash - # Combined set-spec: description + acceptance in one call - # Write to temp files only if content has single quotes - $FLOWCTL task set-spec <id> --description /tmp/desc.md --acceptance /tmp/acc.md --json - ``` - -**Route B - Input was text (new idea)**: - -1. Create epic: - ```bash - $FLOWCTL epic create --title "<Short title>" --json - ``` - This returns the epic ID (e.g., fn-1). - -2. Set epic branch_name (deterministic): - - Default: `fn-N` (use epic ID) - ```bash - $FLOWCTL epic set-branch <epic-id> --branch "<epic-id>" --json - ``` - - If user specified a branch, use that instead. - -3. Write epic spec (use stdin heredoc): - ```bash - # Include: Overview, Scope, Approach, Quick commands (REQUIRED), Acceptance, References - $FLOWCTL epic set-plan <epic-id> --file - --json <<'EOF' - # Epic Title - - ## Overview - ... - - ## Quick commands - ```bash - # At least one smoke test command - ``` - - ## Acceptance - ... - EOF - ``` - -4. Create child tasks: - ```bash - # For each task: - $FLOWCTL task create --epic <epic-id> --title "<Task title>" --json - ``` - -5. Write task specs (use combined set-spec): - ```bash - # For each task - single call sets both sections - # Write description and acceptance to temp files, then: - $FLOWCTL task set-spec <task-id> --description /tmp/desc.md --acceptance /tmp/acc.md --json - ``` - This reduces 4 atomic writes per task to 2. - -6. Add dependencies: - ```bash - # If task B depends on task A: - $FLOWCTL dep add <task-B-id> <task-A-id> --json - ``` - -7. Output current state: - ```bash - $FLOWCTL show <epic-id> --json - $FLOWCTL cat <epic-id> - ``` - -## Step 5: Validate - -```bash -$FLOWCTL validate --epic <epic-id> --json -``` - -Fix any errors before proceeding. - -## Step 6: Review (if chosen at start) - -If user chose "Yes" to review in SKILL.md setup question: -1. Invoke `/flow-next:plan-review` with the epic ID -2. If review returns "Needs Work" or "Major Rethink": - - **Re-anchor EVERY iteration** (do not skip): - ```bash - $FLOWCTL show <epic-id> --json - $FLOWCTL cat <epic-id> - ``` - - **Immediately fix the issues** (do NOT ask for confirmation — user already consented) - - Re-run `/flow-next:plan-review` -3. Repeat until review returns "Ship" - -**No human gates here** — the review-fix-review loop is fully automated. - -**Why re-anchor every iteration?** Per Anthropic's long-running agent guidance: context compresses, you forget details. Re-read before each fix pass. - -## Step 7: Offer next step - -Show the epic summary and suggest next actions: - -``` -Epic created: fn-N with M tasks. - -Next: -1) Start work: `/flow-next:work fn-N` -2) Refine via interview: `/flow-next:interview fn-N` -3) Review the plan: `/flow-next:plan-review fn-N` -``` diff --git a/sync/templates/opencode/skill/flow-next-ralph-init/SKILL.md b/sync/templates/opencode/skill/flow-next-ralph-init/SKILL.md deleted file mode 100644 index 308824d..0000000 --- a/sync/templates/opencode/skill/flow-next-ralph-init/SKILL.md +++ /dev/null @@ -1,62 +0,0 @@ ---- -name: flow-next-ralph-init -description: Scaffold repo-local Ralph autonomous harness under scripts/ralph/. Use when user runs /flow-next:ralph-init. ---- - -# Ralph init - -Scaffold repo-local Ralph harness. Opt-in only. - -## Rules - -- Only create `scripts/ralph/` in the current repo. -- If `scripts/ralph/` already exists, stop and ask the user to remove it first. -- Copy templates from `.opencode/skill/flow-next-ralph-init/templates/` into `scripts/ralph/`. -- Copy `flowctl` and `flowctl.py` from `$PLUGIN_ROOT/scripts/` into `scripts/ralph/`. -- Set executable bit on `scripts/ralph/ralph.sh`, `scripts/ralph/ralph_once.sh`, and `scripts/ralph/flowctl`. - -## Workflow - -1. Resolve repo root and plugin root: - ```bash - ROOT="$(git rev-parse --show-toplevel)" - PLUGIN_ROOT="$ROOT/plugins/flow-next" - TEMPLATE_DIR="$ROOT/.opencode/skill/flow-next-ralph-init/templates" - ``` -2. Check `scripts/ralph/` does not exist. -3. Detect available review backends: - ```bash -HAVE_RP=0; -if command -v rp-cli >/dev/null 2>&1; then - HAVE_RP=1; -elif [[ -x /opt/homebrew/bin/rp-cli || -x /usr/local/bin/rp-cli ]]; then - HAVE_RP=1; -fi - ``` -4. Determine review backend: - - If rp-cli available, ask user: - ``` - Which review backend? - a) OpenCode (GPT‑5.2 High) - b) RepoPrompt (macOS, visual builder) - - (Reply: "a", "opencode", "b", "rp", or just tell me) - ``` - Wait for response. Default if empty/ambiguous: `opencode` - - If only rp-cli available and user chooses rp: use `rp` - - Otherwise: use `opencode` - - If neither available and user requests none: use `none` -5. Write `scripts/ralph/config.env` with: - - `PLAN_REVIEW=<chosen>` and `WORK_REVIEW=<chosen>` - - replace `{{PLAN_REVIEW}}` and `{{WORK_REVIEW}}` placeholders in the template -6. Copy templates and flowctl files. - ```bash - mkdir -p scripts/ralph - cp -R "$TEMPLATE_DIR/." scripts/ralph/ - cp "$PLUGIN_ROOT/scripts/flowctl" "$PLUGIN_ROOT/scripts/flowctl.py" scripts/ralph/ - ``` -7. Print next steps (run from terminal, NOT inside OpenCode): - - Edit `scripts/ralph/config.env` to customize settings - - `./scripts/ralph/ralph_once.sh` (one iteration, observe) - - `./scripts/ralph/ralph.sh` (full loop, AFK) - - Uninstall: `rm -rf scripts/ralph/` diff --git a/sync/templates/opencode/skill/flow-next-ralph-init/templates/config.env b/sync/templates/opencode/skill/flow-next-ralph-init/templates/config.env deleted file mode 100644 index 7ce1583..0000000 --- a/sync/templates/opencode/skill/flow-next-ralph-init/templates/config.env +++ /dev/null @@ -1,37 +0,0 @@ -# Ralph config (edit as needed) - -# Optional epic list (space or comma separated). Empty = scan all open epics. -EPICS= - -# Plan gate -REQUIRE_PLAN_REVIEW=0 -# PLAN_REVIEW options: rp (RepoPrompt, macOS), opencode (cross-platform), none -PLAN_REVIEW={{PLAN_REVIEW}} - -# Work gate -# WORK_REVIEW options: rp (RepoPrompt, macOS), opencode (cross-platform), none -WORK_REVIEW={{WORK_REVIEW}} - -# Work settings -BRANCH_MODE=new -MAX_ITERATIONS=25 -# MAX_TURNS= # optional; empty = no limit (Claude stops via promise tags) -MAX_ATTEMPTS_PER_TASK=5 - -# YOLO sets OPENCODE_PERMISSION='{"*":"allow"}' (required for unattended runs) -YOLO=1 - -# UI settings -# RALPH_UI=0 # set to 0 to disable colored output - -# OpenCode options -# FLOW_RALPH_OPENCODE_MODEL=openai/gpt-5.2 -# FLOW_RALPH_OPENCODE_VARIANT=high -# FLOW_RALPH_OPENCODE_AGENT=ralph-runner -# FLOW_RALPH_REVIEWER_AGENT=opencode-reviewer -# OPENCODE_BIN=/usr/local/bin/opencode - -# Watch mode (command-line flags, not env vars): -# --watch Show tool calls in real-time (logs still captured) -# --watch verbose Show tool calls + model responses (logs still captured) -# Example: ./ralph.sh --watch diff --git a/sync/templates/opencode/skill/flow-next-ralph-init/templates/prompt_plan.md b/sync/templates/opencode/skill/flow-next-ralph-init/templates/prompt_plan.md deleted file mode 100644 index b5ef061..0000000 --- a/sync/templates/opencode/skill/flow-next-ralph-init/templates/prompt_plan.md +++ /dev/null @@ -1,64 +0,0 @@ -You are running one Ralph plan gate iteration. - -Inputs: -- EPIC_ID={{EPIC_ID}} -- PLAN_REVIEW={{PLAN_REVIEW}} -- REQUIRE_PLAN_REVIEW={{REQUIRE_PLAN_REVIEW}} - -Treat the following as the user's exact input to flow-next-plan-review: -`{{EPIC_ID}} --review={{PLAN_REVIEW}}` - -Steps: -1) Re-anchor: - - scripts/ralph/flowctl show {{EPIC_ID}} --json - - scripts/ralph/flowctl cat {{EPIC_ID}} - - git status - - git log -10 --oneline - -Ralph mode rules (must follow): -- If PLAN_REVIEW=rp: use `flowctl rp` wrappers (setup-review, select-add, prompt-get, chat-send). -- If PLAN_REVIEW=opencode: use the task tool with subagent_type `opencode-reviewer`. -- Write receipt via bash heredoc (no Write tool) if REVIEW_RECEIPT_PATH is set. -- Do NOT run /flow-next:* as shell commands. -- If any rule is violated, output `<promise>RETRY</promise>` and stop. - -2) Plan review gate: - - Call the skill tool: flow-next-plan-review. - - Follow the workflow in the skill using the exact arguments above. - - Do NOT stop after loading the skill. - - For opencode: run reviewer via task tool and require `<verdict>` tag. - - For rp: use flowctl rp wrappers (no --json, no --new-chat on re-review). - - For export: follow export flow in skill. - - If PLAN_REVIEW=none: - - If REQUIRE_PLAN_REVIEW=1: output `<promise>RETRY</promise>` and stop. - - Else: set ship and stop: - `scripts/ralph/flowctl epic set-plan-review-status {{EPIC_ID}} --status ship --json` - -3) The skill will loop internally until `<verdict>SHIP</verdict>`: - - First review uses `--new-chat` - - If NEEDS_WORK: skill fixes plan, re-reviews in SAME chat (no --new-chat) - - Repeats until SHIP - - Only returns to Ralph after SHIP or MAJOR_RETHINK - -4) IMMEDIATELY after SHIP verdict, write receipt (for any review mode != none): - ```bash - mkdir -p "$(dirname '{{REVIEW_RECEIPT_PATH}}')" - ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)" - cat > '{{REVIEW_RECEIPT_PATH}}' <<EOF - {"type":"plan_review","id":"{{EPIC_ID}}","mode":"{{PLAN_REVIEW}}","timestamp":"$ts"} - EOF - ``` - **CRITICAL: Copy EXACTLY. The "id":"{{EPIC_ID}}" field is REQUIRED.** - Missing id = verification fails = forced retry. - -5) After SHIP: - - `scripts/ralph/flowctl epic set-plan-review-status {{EPIC_ID}} --status ship --json` - - stop (do NOT output promise tag) - -6) If MAJOR_RETHINK (rare): - - `scripts/ralph/flowctl epic set-plan-review-status {{EPIC_ID}} --status needs_work --json` - - output `<promise>FAIL</promise>` and stop - -7) On hard failure, output `<promise>FAIL</promise>` and stop. - -Do NOT output `<promise>COMPLETE</promise>` in this prompt. diff --git a/sync/templates/opencode/skill/flow-next-ralph-init/templates/prompt_work.md b/sync/templates/opencode/skill/flow-next-ralph-init/templates/prompt_work.md deleted file mode 100644 index 19ad3ed..0000000 --- a/sync/templates/opencode/skill/flow-next-ralph-init/templates/prompt_work.md +++ /dev/null @@ -1,49 +0,0 @@ -You are running one Ralph work iteration. - -Inputs: -- TASK_ID={{TASK_ID}} -- BRANCH_MODE={{BRANCH_MODE_EFFECTIVE}} -- WORK_REVIEW={{WORK_REVIEW}} - -Treat the following as the user's exact input to flow-next-work: -`{{TASK_ID}} --branch={{BRANCH_MODE_EFFECTIVE}} --review={{WORK_REVIEW}}` - -## Steps (execute ALL in order) - -**Step 1: Execute task** -- Call the skill tool: flow-next-work. -- Follow the workflow in the skill using the exact arguments above. -- Do NOT run /flow-next:* as shell commands. -- Do NOT improvise review prompts; use the skill's review flow. - -**Step 2: Verify task done** (AFTER skill returns) -```bash -scripts/ralph/flowctl show {{TASK_ID}} --json -``` -If status != `done`, output `<promise>RETRY</promise>` and stop. - -**Step 3: Write impl receipt** (MANDATORY if WORK_REVIEW != none) -```bash -mkdir -p "$(dirname '{{REVIEW_RECEIPT_PATH}}')" -ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)" -cat > '{{REVIEW_RECEIPT_PATH}}' <<EOF -{"type":"impl_review","id":"{{TASK_ID}}","mode":"{{WORK_REVIEW}}","timestamp":"$ts"} -EOF -echo "Receipt written: {{REVIEW_RECEIPT_PATH}}" -``` -**CRITICAL: Copy the command EXACTLY. The "id":"{{TASK_ID}}" field is REQUIRED.** -Ralph verifies receipts match this exact schema. Missing id = verification fails = forced retry. - -**Step 4: Validate epic** -```bash -scripts/ralph/flowctl validate --epic $(echo {{TASK_ID}} | sed 's/\.[0-9]*$//') --json -``` - -**Step 5: On hard failure** → output `<promise>FAIL</promise>` and stop. - -## Rules -- Must run `flowctl done` and verify task status is `done` before commit. -- Must `git add -A` (never list files). -- Do NOT use TodoWrite. - -Do NOT output `<promise>COMPLETE</promise>` in this prompt. diff --git a/sync/templates/opencode/skill/flow-next-ralph-init/templates/ralph.sh b/sync/templates/opencode/skill/flow-next-ralph-init/templates/ralph.sh deleted file mode 100644 index d4084a9..0000000 --- a/sync/templates/opencode/skill/flow-next-ralph-init/templates/ralph.sh +++ /dev/null @@ -1,1012 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# ───────────────────────────────────────────────────────────────────────────── -# Windows / Git Bash hardening (GH-35) -# ───────────────────────────────────────────────────────────────────────────── -UNAME_S="$(uname -s 2>/dev/null || echo "")" -IS_WINDOWS=0 -case "$UNAME_S" in - MINGW*|MSYS*|CYGWIN*) IS_WINDOWS=1 ;; -esac - -# Python detection: prefer python3, fallback to python (common on Windows) -pick_python() { - if [[ -n "${PYTHON_BIN:-}" ]]; then - command -v "$PYTHON_BIN" >/dev/null 2>&1 && { echo "$PYTHON_BIN"; return; } - fi - if command -v python3 >/dev/null 2>&1; then echo "python3"; return; fi - if command -v python >/dev/null 2>&1; then echo "python"; return; fi - echo "" -} - -PYTHON_BIN="$(pick_python)" -[[ -n "$PYTHON_BIN" ]] || { echo "ralph: python not found (need python3 or python in PATH)" >&2; exit 1; } -export PYTHON_BIN - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -ROOT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)" -CONFIG="$SCRIPT_DIR/config.env" -FLOWCTL="$SCRIPT_DIR/flowctl" -FLOWCTL_PY="$SCRIPT_DIR/flowctl.py" - -fail() { echo "ralph: $*" >&2; exit 1; } -log() { - # Machine-readable logs: only show when UI disabled - [[ "${UI_ENABLED:-1}" != "1" ]] && echo "ralph: $*" - return 0 -} - -# Ensure flowctl is runnable even when NTFS exec bit / shebang handling is flaky on Windows -ensure_flowctl_wrapper() { - # If flowctl exists and is executable, use it - if [[ -f "$FLOWCTL" && -x "$FLOWCTL" ]]; then - return 0 - fi - - # On Windows or if flowctl not executable, create a wrapper that calls Python explicitly - if [[ -f "$FLOWCTL_PY" ]]; then - local wrapper="$SCRIPT_DIR/flowctl-wrapper.sh" - cat > "$wrapper" <<SH -#!/usr/bin/env bash -set -euo pipefail -DIR="\$(cd "\$(dirname "\${BASH_SOURCE[0]}")" && pwd)" -PY="\${PYTHON_BIN:-python3}" -command -v "\$PY" >/dev/null 2>&1 || PY="python" -exec "\$PY" "\$DIR/flowctl.py" "\$@" -SH - chmod +x "$wrapper" 2>/dev/null || true - FLOWCTL="$wrapper" - export FLOWCTL - return 0 - fi - - fail "missing flowctl (expected $SCRIPT_DIR/flowctl or $SCRIPT_DIR/flowctl.py)" -} - -ensure_flowctl_wrapper - -# ───────────────────────────────────────────────────────────────────────────── -# Presentation layer (human-readable output) -# ───────────────────────────────────────────────────────────────────────────── -UI_ENABLED="${RALPH_UI:-1}" # set RALPH_UI=0 to disable - -# Timing -START_TIME="$(date +%s)" - -elapsed_time() { - local now elapsed mins secs - now="$(date +%s)" - elapsed=$((now - START_TIME)) - mins=$((elapsed / 60)) - secs=$((elapsed % 60)) - printf "%d:%02d" "$mins" "$secs" -} - -# Stats tracking -STATS_TASKS_DONE=0 - -# Colors (disabled if not tty or NO_COLOR set) -if [[ -t 1 && -z "${NO_COLOR:-}" ]]; then - C_RESET='\033[0m' - C_BOLD='\033[1m' - C_DIM='\033[2m' - C_BLUE='\033[34m' - C_GREEN='\033[32m' - C_YELLOW='\033[33m' - C_RED='\033[31m' - C_CYAN='\033[36m' - C_MAGENTA='\033[35m' -else - C_RESET='' C_BOLD='' C_DIM='' C_BLUE='' C_GREEN='' C_YELLOW='' C_RED='' C_CYAN='' C_MAGENTA='' -fi - -# Watch mode: "", "tools", "verbose" -WATCH_MODE="" - -ui() { - [[ "$UI_ENABLED" == "1" ]] || return 0 - echo -e "$*" -} - -# Get title from epic/task JSON -get_title() { - local json="$1" - "$PYTHON_BIN" - "$json" <<'PY' -import json, sys -try: - data = json.loads(sys.argv[1]) - print(data.get("title", "")[:40]) -except: - print("") -PY -} - -# Count progress (done/total tasks for scoped epics) -get_progress() { - "$PYTHON_BIN" - "$ROOT_DIR" "${EPICS_FILE:-}" <<'PY' -import json, sys -from pathlib import Path -root = Path(sys.argv[1]) -epics_file = sys.argv[2] if len(sys.argv) > 2 else "" -flow_dir = root / ".flow" - -# Get scoped epics or all -scoped = [] -if epics_file: - try: - scoped = json.load(open(epics_file))["epics"] - except: - pass - -epics_dir = flow_dir / "epics" -tasks_dir = flow_dir / "tasks" -if not epics_dir.exists(): - print("0|0|0|0") - sys.exit(0) - -epic_ids = [] -for f in sorted(epics_dir.glob("fn-*.json")): - eid = f.stem - if not scoped or eid in scoped: - epic_ids.append(eid) - -epics_done = sum(1 for e in epic_ids if json.load(open(epics_dir / f"{e}.json")).get("status") == "done") -tasks_total = 0 -tasks_done = 0 -if tasks_dir.exists(): - for tf in tasks_dir.glob("*.json"): - try: - t = json.load(open(tf)) - epic_id = tf.stem.rsplit(".", 1)[0] - if not scoped or epic_id in scoped: - tasks_total += 1 - if t.get("status") == "done": - tasks_done += 1 - except: - pass -print(f"{epics_done}|{len(epic_ids)}|{tasks_done}|{tasks_total}") -PY -} - -# Get git diff stats -get_git_stats() { - local base_branch="${1:-main}" - local stats - stats="$(git -C "$ROOT_DIR" diff --shortstat "$base_branch"...HEAD 2>/dev/null || true)" - if [[ -z "$stats" ]]; then - echo "" - return - fi - "$PYTHON_BIN" - "$stats" <<'PY' -import re, sys -s = sys.argv[1] -files = re.search(r"(\d+) files? changed", s) -ins = re.search(r"(\d+) insertions?", s) -dels = re.search(r"(\d+) deletions?", s) -f = files.group(1) if files else "0" -i = ins.group(1) if ins else "0" -d = dels.group(1) if dels else "0" -print(f"{f} files, +{i} -{d}") -PY -} - -ui_header() { - ui "" - ui "${C_BOLD}${C_BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C_RESET}" - ui "${C_BOLD}${C_BLUE} 🤖 Ralph Autonomous Loop${C_RESET}" - ui "${C_BOLD}${C_BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C_RESET}" -} - -ui_config() { - local git_branch progress_info epics_done epics_total tasks_done tasks_total - git_branch="$(git -C "$ROOT_DIR" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")" - progress_info="$(get_progress)" - IFS='|' read -r epics_done epics_total tasks_done tasks_total <<< "$progress_info" - - ui "" - ui "${C_DIM} Branch:${C_RESET} ${C_BOLD}$git_branch${C_RESET}" - ui "${C_DIM} Progress:${C_RESET} Epic ${epics_done}/${epics_total} ${C_DIM}•${C_RESET} Task ${tasks_done}/${tasks_total}" - - local plan_display="$PLAN_REVIEW" work_display="$WORK_REVIEW" - [[ "$PLAN_REVIEW" == "rp" ]] && plan_display="RepoPrompt" - [[ "$PLAN_REVIEW" == "opencode" ]] && plan_display="OpenCode" - [[ "$WORK_REVIEW" == "rp" ]] && work_display="RepoPrompt" - [[ "$WORK_REVIEW" == "opencode" ]] && work_display="OpenCode" - ui "${C_DIM} Reviews:${C_RESET} Plan=$plan_display ${C_DIM}•${C_RESET} Work=$work_display" - [[ -n "${EPICS:-}" ]] && ui "${C_DIM} Scope:${C_RESET} $EPICS" - ui "" -} - -ui_iteration() { - local iter="$1" status="$2" epic="${3:-}" task="${4:-}" title="" item_json="" - local elapsed - elapsed="$(elapsed_time)" - ui "" - ui "${C_BOLD}${C_CYAN}🔄 Iteration $iter${C_RESET} ${C_DIM}[${elapsed}]${C_RESET}" - if [[ "$status" == "plan" ]]; then - item_json="$("$FLOWCTL" show "$epic" --json 2>/dev/null || true)" - title="$(get_title "$item_json")" - ui " ${C_DIM}Epic:${C_RESET} ${C_BOLD}$epic${C_RESET} ${C_DIM}\"$title\"${C_RESET}" - ui " ${C_DIM}Phase:${C_RESET} ${C_YELLOW}Planning${C_RESET}" - elif [[ "$status" == "work" ]]; then - item_json="$("$FLOWCTL" show "$task" --json 2>/dev/null || true)" - title="$(get_title "$item_json")" - ui " ${C_DIM}Task:${C_RESET} ${C_BOLD}$task${C_RESET} ${C_DIM}\"$title\"${C_RESET}" - ui " ${C_DIM}Phase:${C_RESET} ${C_MAGENTA}Implementation${C_RESET}" - fi -} - -ui_plan_review() { - local mode="$1" epic="$2" - if [[ "$mode" == "rp" ]]; then - ui "" - ui " ${C_YELLOW}📝 Plan Review${C_RESET}" - ui " ${C_DIM}Sending to reviewer via RepoPrompt...${C_RESET}" - elif [[ "$mode" == "opencode" ]]; then - ui "" - ui " ${C_YELLOW}📝 Plan Review${C_RESET}" - ui " ${C_DIM}Sending to reviewer via OpenCode...${C_RESET}" - fi -} - -ui_impl_review() { - local mode="$1" task="$2" - if [[ "$mode" == "rp" ]]; then - ui "" - ui " ${C_MAGENTA}🔍 Implementation Review${C_RESET}" - ui " ${C_DIM}Sending to reviewer via RepoPrompt...${C_RESET}" - elif [[ "$mode" == "opencode" ]]; then - ui "" - ui " ${C_MAGENTA}🔍 Implementation Review${C_RESET}" - ui " ${C_DIM}Sending to reviewer via OpenCode...${C_RESET}" - fi -} - -ui_task_done() { - local task="$1" git_stats="" - STATS_TASKS_DONE=$((STATS_TASKS_DONE + 1)) - init_branches_file 2>/dev/null || true - local base_branch - base_branch="$(get_base_branch 2>/dev/null || echo "main")" - git_stats="$(get_git_stats "$base_branch")" - if [[ -n "$git_stats" ]]; then - ui " ${C_GREEN}✓${C_RESET} ${C_BOLD}$task${C_RESET} ${C_DIM}($git_stats)${C_RESET}" - else - ui " ${C_GREEN}✓${C_RESET} ${C_BOLD}$task${C_RESET}" - fi -} - -ui_retry() { - local task="$1" attempts="$2" max="$3" - ui " ${C_YELLOW}↻ Retry${C_RESET} ${C_DIM}(attempt $attempts/$max)${C_RESET}" -} - -ui_blocked() { - local task="$1" - ui " ${C_RED}🚫 Task blocked:${C_RESET} $task ${C_DIM}(max attempts reached)${C_RESET}" -} - -ui_complete() { - local elapsed progress_info epics_done epics_total tasks_done tasks_total - elapsed="$(elapsed_time)" - progress_info="$(get_progress)" - IFS='|' read -r epics_done epics_total tasks_done tasks_total <<< "$progress_info" - - ui "" - ui "${C_BOLD}${C_GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C_RESET}" - ui "${C_BOLD}${C_GREEN} ✅ Ralph Complete${C_RESET} ${C_DIM}[${elapsed}]${C_RESET}" - ui "" - ui " ${C_DIM}Tasks:${C_RESET} ${tasks_done}/${tasks_total} ${C_DIM}•${C_RESET} ${C_DIM}Epics:${C_RESET} ${epics_done}/${epics_total}" - ui "${C_BOLD}${C_GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C_RESET}" - ui "" -} - -ui_fail() { - local reason="${1:-}" elapsed - elapsed="$(elapsed_time)" - ui "" - ui "${C_BOLD}${C_RED}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C_RESET}" - ui "${C_BOLD}${C_RED} ❌ Ralph Failed${C_RESET} ${C_DIM}[${elapsed}]${C_RESET}" - [[ -n "$reason" ]] && ui " ${C_DIM}$reason${C_RESET}" - ui "${C_BOLD}${C_RED}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C_RESET}" - ui "" -} - -ui_waiting() { - ui " ${C_DIM}⏳ OpenCode working...${C_RESET}" -} - -[[ -f "$CONFIG" ]] || fail "missing config.env" -[[ -x "$FLOWCTL" ]] || fail "missing flowctl" - -# shellcheck disable=SC1090 -set -a -source "$CONFIG" -set +a - -MAX_ITERATIONS="${MAX_ITERATIONS:-25}" -MAX_TURNS="${MAX_TURNS:-}" # empty = no limit; unused for OpenCode (kept for parity) -MAX_ATTEMPTS_PER_TASK="${MAX_ATTEMPTS_PER_TASK:-5}" -WORKER_TIMEOUT="${WORKER_TIMEOUT:-1800}" # 30min default; prevents stuck workers -BRANCH_MODE="${BRANCH_MODE:-new}" -PLAN_REVIEW="${PLAN_REVIEW:-none}" -WORK_REVIEW="${WORK_REVIEW:-none}" -REQUIRE_PLAN_REVIEW="${REQUIRE_PLAN_REVIEW:-0}" -YOLO="${YOLO:-0}" -EPICS="${EPICS:-}" - -# Parse command line arguments -while [[ $# -gt 0 ]]; do - case "$1" in - --watch) - if [[ "${2:-}" == "verbose" ]]; then - WATCH_MODE="verbose" - shift - else - WATCH_MODE="tools" - fi - shift - ;; - --help|-h) - echo "Usage: ralph.sh [options]" - echo "" - echo "Options:" - echo " --watch Show tool calls in real-time" - echo " --watch verbose Show tool calls + model responses" - echo " --help, -h Show this help" - echo "" - echo "Environment variables:" - echo " EPICS Comma/space-separated epic IDs to work on" - echo " MAX_ITERATIONS Max loop iterations (default: 25)" - echo " YOLO Set to 1 to skip permissions (required for unattended)" - echo "" - echo "See config.env for more options." - exit 0 - ;; - *) - fail "Unknown option: $1 (use --help for usage)" - ;; - esac -done - -# Set up signal trap for clean Ctrl+C handling -# Must kill all child processes including timeout and worker -cleanup() { - trap - SIGINT SIGTERM # Prevent re-entry - # Kill all child processes - pkill -P $$ 2>/dev/null - # Kill process group as fallback - kill -- -$$ 2>/dev/null - exit 130 -} -trap cleanup SIGINT SIGTERM - -OPENCODE_BIN="${OPENCODE_BIN:-opencode}" - -# Detect timeout command (GNU coreutils). On macOS: brew install coreutils -# Use --foreground to keep child in same process group for signal handling -if command -v timeout >/dev/null 2>&1 && timeout --foreground 0 true 2>/dev/null; then - TIMEOUT_CMD="timeout --foreground" -elif command -v gtimeout >/dev/null 2>&1 && gtimeout --foreground 0 true 2>/dev/null; then - TIMEOUT_CMD="gtimeout --foreground" -elif command -v timeout >/dev/null 2>&1; then - TIMEOUT_CMD="timeout" -elif command -v gtimeout >/dev/null 2>&1; then - TIMEOUT_CMD="gtimeout" -else - TIMEOUT_CMD="" - echo "ralph: warning: timeout command not found; worker timeout disabled (brew install coreutils)" >&2 -fi - -sanitize_id() { - local v="$1" - v="${v// /_}" - v="${v//\//_}" - v="${v//\\/__}" - echo "$v" -} - -get_actor() { - if [[ -n "${FLOW_ACTOR:-}" ]]; then echo "$FLOW_ACTOR"; return; fi - if actor="$(git -C "$ROOT_DIR" config user.name 2>/dev/null)"; then - [[ -n "$actor" ]] && { echo "$actor"; return; } - fi - if actor="$(git -C "$ROOT_DIR" config user.email 2>/dev/null)"; then - [[ -n "$actor" ]] && { echo "$actor"; return; } - fi - echo "${USER:-unknown}" -} - -rand4() { - python3 - <<'PY' -import secrets -print(secrets.token_hex(2)) -PY -} - -render_template() { - local path="$1" - python3 - "$path" <<'PY' -import os, sys -path = sys.argv[1] -text = open(path, encoding="utf-8").read() -keys = ["EPIC_ID","TASK_ID","PLAN_REVIEW","WORK_REVIEW","BRANCH_MODE","BRANCH_MODE_EFFECTIVE","REQUIRE_PLAN_REVIEW","REVIEW_RECEIPT_PATH"] -for k in keys: - text = text.replace("{{%s}}" % k, os.environ.get(k, "")) -print(text) -PY -} - -json_get() { - local key="$1" - local json="$2" - python3 - "$key" "$json" <<'PY' -import json, sys -key = sys.argv[1] -data = json.loads(sys.argv[2]) -val = data.get(key) -if val is None: - print("") -elif isinstance(val, bool): - print("1" if val else "0") -else: - print(val) -PY -} - -ensure_attempts_file() { - [[ -f "$1" ]] || echo "{}" > "$1" -} - -bump_attempts() { - python3 - "$1" "$2" <<'PY' -import json, sys, os -path, task = sys.argv[1], sys.argv[2] -data = {} -if os.path.exists(path): - with open(path, encoding="utf-8") as f: - data = json.load(f) -count = int(data.get(task, 0)) + 1 -data[task] = count -with open(path, "w", encoding="utf-8") as f: - json.dump(data, f, indent=2, sort_keys=True) -print(count) -PY -} - -write_epics_file() { - python3 - "$1" <<'PY' -import json, sys -raw = sys.argv[1] -parts = [p.strip() for p in raw.replace(",", " ").split() if p.strip()] -print(json.dumps({"epics": parts}, indent=2, sort_keys=True)) -PY -} - -RUN_ID="$(date -u +%Y%m%dT%H%M%SZ)-$(hostname -s 2>/dev/null || hostname)-$(sanitize_id "$(get_actor)")-$$-$(rand4)" -RUN_DIR="$SCRIPT_DIR/runs/$RUN_ID" -mkdir -p "$RUN_DIR" -ATTEMPTS_FILE="$RUN_DIR/attempts.json" -ensure_attempts_file "$ATTEMPTS_FILE" -BRANCHES_FILE="$RUN_DIR/branches.json" -RECEIPTS_DIR="$RUN_DIR/receipts" -mkdir -p "$RECEIPTS_DIR" -PROGRESS_FILE="$RUN_DIR/progress.txt" -{ - echo "# Ralph Progress Log" - echo "Run: $RUN_ID" - echo "Started: $(date -u +%Y-%m-%dT%H:%M:%SZ)" - echo "---" -} > "$PROGRESS_FILE" - -extract_tag() { - local tag="$1" - python3 - "$tag" <<'PY' -import re, sys -tag = sys.argv[1] -text = sys.stdin.read() -matches = re.findall(rf"<{tag}>(.*?)</{tag}>", text, flags=re.S) -print(matches[-1] if matches else "") -PY -} - -# Extract assistant text from stream-json log (for tag extraction in watch mode) -extract_text_from_run_json() { - local log_file="$1" - python3 - "$log_file" <<'PY' -import json, sys -path = sys.argv[1] -out = [] -try: - with open(path, encoding="utf-8") as f: - for line in f: - line = line.strip() - if not line: - continue - try: - ev = json.loads(line) - except json.JSONDecodeError: - continue - - # OpenCode run --format json - if ev.get("type") == "text": - part = ev.get("part") or {} - text = part.get("text", "") - if text: - out.append(text) - continue - - # OpenCode stream-json (fallback) - if ev.get("type") == "assistant": - msg = ev.get("message") or {} - for blk in (msg.get("content") or []): - if blk.get("type") == "text": - out.append(blk.get("text", "")) -except Exception: - pass -print("\n".join(out)) -PY -} - -append_progress() { - local verdict="$1" - local promise="$2" - local plan_review_status="${3:-}" - local task_status="${4:-}" - local receipt_exists="0" - if [[ -n "${REVIEW_RECEIPT_PATH:-}" && -f "$REVIEW_RECEIPT_PATH" ]]; then - receipt_exists="1" - fi - { - echo "## $(date -u +%Y-%m-%dT%H:%M:%SZ) - iter $iter" - echo "status=$status epic=${epic_id:-} task=${task_id:-} reason=${reason:-}" - echo "worker_rc=$worker_rc" - echo "verdict=${verdict:-}" - echo "promise=${promise:-}" - echo "receipt=${REVIEW_RECEIPT_PATH:-} exists=$receipt_exists" - echo "plan_review_status=${plan_review_status:-}" - echo "task_status=${task_status:-}" - echo "iter_log=$iter_log" - echo "last_output:" - tail -n 10 "$iter_log" || true - echo "---" - } >> "$PROGRESS_FILE" -} - -# Write completion marker to progress.txt (MUST match find_active_runs() detection in flowctl.py) -write_completion_marker() { - local reason="${1:-DONE}" - { - echo "" - echo "completion_reason=$reason" - echo "promise=COMPLETE" # CANONICAL - must match flowctl.py substring search - } >> "$PROGRESS_FILE" -} - -# Check PAUSE/STOP sentinel files -check_sentinels() { - local pause_file="$RUN_DIR/PAUSE" - local stop_file="$RUN_DIR/STOP" - - # Check for stop first (exit immediately, keep file for audit) - if [[ -f "$stop_file" ]]; then - log "STOP sentinel detected, exiting gracefully" - ui_fail "STOP sentinel detected" - write_completion_marker "STOPPED" - exit 0 - fi - - # Check for pause (log once, wait in loop, re-check STOP while waiting) - if [[ -f "$pause_file" ]]; then - log "PAUSED - waiting for resume..." - while [[ -f "$pause_file" ]]; do - # Re-check STOP while paused so external stop works - if [[ -f "$stop_file" ]]; then - log "STOP sentinel detected while paused, exiting gracefully" - ui_fail "STOP sentinel detected" - write_completion_marker "STOPPED" - exit 0 - fi - sleep 5 - done - log "Resumed" - fi -} - -init_branches_file() { - if [[ -f "$BRANCHES_FILE" ]]; then return; fi - local base_branch - base_branch="$(git -C "$ROOT_DIR" rev-parse --abbrev-ref HEAD 2>/dev/null || true)" - python3 - "$BRANCHES_FILE" "$base_branch" <<'PY' -import json, sys -path, base = sys.argv[1], sys.argv[2] -data = {"base_branch": base, "run_branch": ""} -with open(path, "w", encoding="utf-8") as f: - json.dump(data, f, indent=2, sort_keys=True) -PY -} - -get_base_branch() { - python3 - "$BRANCHES_FILE" <<'PY' -import json, sys -try: - with open(sys.argv[1], encoding="utf-8") as f: - data = json.load(f) - print(data.get("base_branch", "")) -except FileNotFoundError: - print("") -PY -} - -get_run_branch() { - python3 - "$BRANCHES_FILE" <<'PY' -import json, sys -try: - with open(sys.argv[1], encoding="utf-8") as f: - data = json.load(f) - print(data.get("run_branch", "")) -except FileNotFoundError: - print("") -PY -} - -set_run_branch() { - python3 - "$BRANCHES_FILE" "$1" <<'PY' -import json, sys -path, branch = sys.argv[1], sys.argv[2] -data = {"base_branch": "", "run_branch": ""} -try: - with open(path, encoding="utf-8") as f: - data = json.load(f) -except FileNotFoundError: - pass -data["run_branch"] = branch -with open(path, "w", encoding="utf-8") as f: - json.dump(data, f, indent=2, sort_keys=True) -PY -} - -list_epics_from_file() { - python3 - "$EPICS_FILE" <<'PY' -import json, sys -path = sys.argv[1] -if not path: - sys.exit(0) -try: - data = json.load(open(path, encoding="utf-8")) -except FileNotFoundError: - sys.exit(0) -epics = data.get("epics", []) or [] -print(" ".join(epics)) -PY -} - -epic_all_tasks_done() { - python3 - "$1" <<'PY' -import json, sys -try: - data = json.loads(sys.argv[1]) -except json.JSONDecodeError: - print("0") - sys.exit(0) -tasks = data.get("tasks", []) or [] -if not tasks: - print("0") - sys.exit(0) -for t in tasks: - if t.get("status") != "done": - print("0") - sys.exit(0) -print("1") -PY -} - -maybe_close_epics() { - [[ -z "$EPICS_FILE" ]] && return 0 - local epics json status all_done - epics="$(list_epics_from_file)" - [[ -z "$epics" ]] && return 0 - for epic in $epics; do - json="$("$FLOWCTL" show "$epic" --json 2>/dev/null || true)" - [[ -z "$json" ]] && continue - status="$(json_get status "$json")" - [[ "$status" == "done" ]] && continue - all_done="$(epic_all_tasks_done "$json")" - if [[ "$all_done" == "1" ]]; then - "$FLOWCTL" epic close "$epic" --json >/dev/null 2>&1 || true - fi - done -} - -verify_receipt() { - local path="$1" - local kind="$2" - local id="$3" - [[ -f "$path" ]] || return 1 - python3 - "$path" "$kind" "$id" <<'PY' -import json, sys -path, kind, rid = sys.argv[1], sys.argv[2], sys.argv[3] -try: - data = json.load(open(path, encoding="utf-8")) -except Exception: - sys.exit(1) -if data.get("type") != kind: - sys.exit(1) -if data.get("id") != rid: - sys.exit(1) -sys.exit(0) -PY -} - -# Create/switch to run branch (once at start, all epics work here) -ensure_run_branch() { - if [[ "$BRANCH_MODE" != "new" ]]; then - return - fi - init_branches_file - local branch - branch="$(get_run_branch)" - if [[ -n "$branch" ]]; then - # Already on run branch (resumed run) - git -C "$ROOT_DIR" checkout "$branch" >/dev/null 2>&1 || true - return - fi - # Create new run branch from current position - branch="ralph-${RUN_ID}" - set_run_branch "$branch" - git -C "$ROOT_DIR" checkout -b "$branch" >/dev/null 2>&1 -} - -EPICS_FILE="" -if [[ -n "${EPICS// }" ]]; then - EPICS_FILE="$RUN_DIR/run.json" - write_epics_file "$EPICS" > "$EPICS_FILE" -fi - -ui_header -ui_config - -# Create run branch once at start (all epics work on same branch) -ensure_run_branch - -iter=1 -while (( iter <= MAX_ITERATIONS )); do - iter_log="$RUN_DIR/iter-$(printf '%03d' "$iter").log" - - # Check for pause/stop at start of iteration (before work selection) - check_sentinels - - selector_args=("$FLOWCTL" next --json) - [[ -n "$EPICS_FILE" ]] && selector_args+=(--epics-file "$EPICS_FILE") - [[ "$REQUIRE_PLAN_REVIEW" == "1" ]] && selector_args+=(--require-plan-review) - - selector_json="$("${selector_args[@]}")" - status="$(json_get status "$selector_json")" - epic_id="$(json_get epic "$selector_json")" - task_id="$(json_get task "$selector_json")" - reason="$(json_get reason "$selector_json")" - - log "iter $iter status=$status epic=${epic_id:-} task=${task_id:-} reason=${reason:-}" - ui_iteration "$iter" "$status" "${epic_id:-}" "${task_id:-}" - - if [[ "$status" == "none" ]]; then - if [[ "$reason" == "blocked_by_epic_deps" ]]; then - log "blocked by epic deps" - fi - maybe_close_epics - ui_complete - write_completion_marker "NO_WORK" - exit 0 - fi - - if [[ "$status" == "plan" ]]; then - export EPIC_ID="$epic_id" - export PLAN_REVIEW - export REQUIRE_PLAN_REVIEW - export FLOW_REVIEW_BACKEND="$PLAN_REVIEW" # Skills read this - if [[ "$PLAN_REVIEW" != "none" ]]; then - export REVIEW_RECEIPT_PATH="$RECEIPTS_DIR/plan-${epic_id}.json" - else - unset REVIEW_RECEIPT_PATH - fi - log "plan epic=$epic_id review=$PLAN_REVIEW receipt=${REVIEW_RECEIPT_PATH:-} require=$REQUIRE_PLAN_REVIEW" - ui_plan_review "$PLAN_REVIEW" "$epic_id" - prompt="$(render_template "$SCRIPT_DIR/prompt_plan.md")" - elif [[ "$status" == "work" ]]; then - epic_id="${task_id%%.*}" - export TASK_ID="$task_id" - BRANCH_MODE_EFFECTIVE="$BRANCH_MODE" - if [[ "$BRANCH_MODE" == "new" ]]; then - BRANCH_MODE_EFFECTIVE="current" - fi - export BRANCH_MODE_EFFECTIVE - export WORK_REVIEW - export FLOW_REVIEW_BACKEND="$WORK_REVIEW" # Skills read this - if [[ "$WORK_REVIEW" != "none" ]]; then - export REVIEW_RECEIPT_PATH="$RECEIPTS_DIR/impl-${task_id}.json" - else - unset REVIEW_RECEIPT_PATH - fi - log "work task=$task_id review=$WORK_REVIEW receipt=${REVIEW_RECEIPT_PATH:-} branch=$BRANCH_MODE_EFFECTIVE" - ui_impl_review "$WORK_REVIEW" "$task_id" - prompt="$(render_template "$SCRIPT_DIR/prompt_work.md")" - else - fail "invalid selector status: $status" - fi - - export FLOW_RALPH="1" - AUTONOMOUS_RULES="$(cat <<'TXT' -AUTONOMOUS MODE ACTIVE (FLOW_RALPH=1). You are running unattended. CRITICAL RULES: -1. EXECUTE COMMANDS EXACTLY as shown in prompts. Do not paraphrase or improvise. -2. VERIFY OUTCOMES by running the verification commands (flowctl show, git status). -3. NEVER CLAIM SUCCESS without proof. If flowctl done was not run, the task is NOT done. -4. COPY TEMPLATES VERBATIM - receipt JSON must match exactly including all fields. -5. USE SKILLS AS SPECIFIED - invoke /flow-next:impl-review, do not improvise review prompts. -Violations break automation and leave the user with incomplete work. Be precise, not creative. -TXT -)" - prompt="${AUTONOMOUS_RULES}"$'\n\n'"${prompt}" - - opencode_args=(run --format json --agent "${FLOW_RALPH_OPENCODE_AGENT:-ralph-runner}") - [[ -n "${FLOW_RALPH_OPENCODE_MODEL:-}" ]] && opencode_args+=(--model "$FLOW_RALPH_OPENCODE_MODEL") - [[ -n "${FLOW_RALPH_OPENCODE_VARIANT:-}" ]] && opencode_args+=(--variant "$FLOW_RALPH_OPENCODE_VARIANT") - - prev_opencode_permission="${OPENCODE_PERMISSION-__unset__}" - if [[ "$YOLO" == "1" ]]; then - export OPENCODE_PERMISSION='{"*":"allow"}' - fi - - ui_waiting - worker_out="" - set +e - if [[ "$WATCH_MODE" == "verbose" ]]; then - echo "" - if [[ -n "$TIMEOUT_CMD" ]]; then - $TIMEOUT_CMD "$WORKER_TIMEOUT" "$OPENCODE_BIN" "${opencode_args[@]}" "$prompt" 2>&1 | tee "$iter_log" | "$SCRIPT_DIR/watch-filter.py" --verbose - else - "$OPENCODE_BIN" "${opencode_args[@]}" "$prompt" 2>&1 | tee "$iter_log" | "$SCRIPT_DIR/watch-filter.py" --verbose - fi - worker_rc=${PIPESTATUS[0]} - worker_out="$(cat "$iter_log")" - elif [[ "$WATCH_MODE" == "tools" ]]; then - if [[ -n "$TIMEOUT_CMD" ]]; then - $TIMEOUT_CMD "$WORKER_TIMEOUT" "$OPENCODE_BIN" "${opencode_args[@]}" "$prompt" 2>&1 | tee "$iter_log" | "$SCRIPT_DIR/watch-filter.py" - else - "$OPENCODE_BIN" "${opencode_args[@]}" "$prompt" 2>&1 | tee "$iter_log" | "$SCRIPT_DIR/watch-filter.py" - fi - worker_rc=${PIPESTATUS[0]} - worker_out="$(cat "$iter_log")" - else - if [[ -n "$TIMEOUT_CMD" ]]; then - $TIMEOUT_CMD "$WORKER_TIMEOUT" "$OPENCODE_BIN" "${opencode_args[@]}" "$prompt" > "$iter_log" 2>&1 - else - "$OPENCODE_BIN" "${opencode_args[@]}" "$prompt" > "$iter_log" 2>&1 - fi - worker_rc=$? - worker_out="$(cat "$iter_log")" - fi - set -e - - if [[ "$prev_opencode_permission" == "__unset__" ]]; then - unset OPENCODE_PERMISSION - else - export OPENCODE_PERMISSION="$prev_opencode_permission" - fi - - # Handle timeout (exit code 124 from timeout command) - worker_timeout=0 - if [[ -n "$TIMEOUT_CMD" && "$worker_rc" -eq 124 ]]; then - echo "ralph: worker timed out after ${WORKER_TIMEOUT}s" >> "$iter_log" - log "worker timeout after ${WORKER_TIMEOUT}s" - worker_timeout=1 - fi - - log "worker rc=$worker_rc log=$iter_log" - - force_retry=$worker_timeout - plan_review_status="" - task_status="" - if [[ "$status" == "plan" && ( "$PLAN_REVIEW" == "rp" || "$PLAN_REVIEW" == "opencode" ) ]]; then - if ! verify_receipt "$REVIEW_RECEIPT_PATH" "plan_review" "$epic_id"; then - echo "ralph: missing plan review receipt; forcing retry" >> "$iter_log" - log "missing plan receipt; forcing retry" - "$FLOWCTL" epic set-plan-review-status "$epic_id" --status needs_work --json >/dev/null 2>&1 || true - force_retry=1 - fi - epic_json="$("$FLOWCTL" show "$epic_id" --json 2>/dev/null || true)" - plan_review_status="$(json_get plan_review_status "$epic_json")" - fi - if [[ "$status" == "work" && ( "$WORK_REVIEW" == "rp" || "$WORK_REVIEW" == "opencode" ) ]]; then - if ! verify_receipt "$REVIEW_RECEIPT_PATH" "impl_review" "$task_id"; then - echo "ralph: missing impl review receipt; forcing retry" >> "$iter_log" - log "missing impl receipt; forcing retry" - force_retry=1 - fi - fi - - # Extract verdict/promise for progress log (not displayed in UI) - # Always parse stream-json since we always use that format now - worker_text="$(extract_text_from_run_json "$iter_log")" - verdict="$(printf '%s' "$worker_text" | extract_tag verdict)" - promise="$(printf '%s' "$worker_text" | extract_tag promise)" - - # Fallback: derive verdict from flowctl status for logging - if [[ -z "$verdict" && -n "$plan_review_status" ]]; then - case "$plan_review_status" in - ship) verdict="SHIP" ;; - needs_work) verdict="NEEDS_WORK" ;; - esac - fi - - if [[ "$status" == "work" ]]; then - task_json="$("$FLOWCTL" show "$task_id" --json 2>/dev/null || true)" - task_status="$(json_get status "$task_json")" - if [[ "$task_status" != "done" ]]; then - echo "ralph: task not done; forcing retry" >> "$iter_log" - log "task $task_id status=$task_status; forcing retry" - force_retry=1 - else - ui_task_done "$task_id" - # Derive verdict from task completion for logging - [[ -z "$verdict" ]] && verdict="SHIP" - fi - fi - append_progress "$verdict" "$promise" "$plan_review_status" "$task_status" - - if echo "$worker_text" | grep -q "<promise>COMPLETE</promise>"; then - ui_complete - write_completion_marker "DONE" - exit 0 - fi - - exit_code=0 - if echo "$worker_text" | grep -q "<promise>FAIL</promise>"; then - exit_code=1 - elif echo "$worker_text" | grep -q "<promise>RETRY</promise>"; then - exit_code=2 - elif [[ "$force_retry" == "1" ]]; then - exit_code=2 - elif [[ "$worker_rc" -ne 0 && "$task_status" != "done" && "$verdict" != "SHIP" ]]; then - # Only fail on non-zero exit code if task didn't complete and verdict isn't SHIP - # This prevents false failures from transient errors (telemetry, model fallback, etc.) - exit_code=1 - fi - - if [[ "$exit_code" -eq 1 ]]; then - log "exit=fail" - ui_fail "OpenCode returned FAIL promise" - write_completion_marker "FAILED" - exit 1 - fi - - if [[ "$exit_code" -eq 2 && "$status" == "work" ]]; then - attempts="$(bump_attempts "$ATTEMPTS_FILE" "$task_id")" - log "retry task=$task_id attempts=$attempts" - ui_retry "$task_id" "$attempts" "$MAX_ATTEMPTS_PER_TASK" - if (( attempts >= MAX_ATTEMPTS_PER_TASK )); then - reason_file="$RUN_DIR/block-${task_id}.md" - { - echo "Auto-blocked after ${attempts} attempts." - echo "Run: $RUN_ID" - echo "Task: $task_id" - echo "" - echo "Last output:" - tail -n 40 "$iter_log" || true - } > "$reason_file" - "$FLOWCTL" block "$task_id" --reason-file "$reason_file" --json || true - ui_blocked "$task_id" - fi - fi - - # Check for pause/stop after OpenCode returns (before next iteration) - check_sentinels - - sleep 2 - iter=$((iter + 1)) -done - -ui_fail "Max iterations ($MAX_ITERATIONS) reached" -echo "ralph: max iterations reached" >&2 -write_completion_marker "MAX_ITERATIONS" -exit 1 diff --git a/sync/templates/opencode/skill/flow-next-ralph-init/templates/watch-filter.py b/sync/templates/opencode/skill/flow-next-ralph-init/templates/watch-filter.py deleted file mode 100755 index bca5d96..0000000 --- a/sync/templates/opencode/skill/flow-next-ralph-init/templates/watch-filter.py +++ /dev/null @@ -1,286 +0,0 @@ -#!/usr/bin/env python3 -""" -Watch filter for Ralph - parses OpenCode run --format json (and Claude stream-json fallback). - -Reads JSON lines from stdin, outputs formatted tool calls in TUI style. - -CRITICAL: This filter is "fail open" - if output breaks, it continues draining -stdin to prevent SIGPIPE cascading to upstream processes (tee, worker). - -Usage: - watch-filter.py # Show tool calls only - watch-filter.py --verbose # Show tool calls + text responses -""" - -import argparse -import json -import os -import sys -from typing import Optional - -# Global flag to disable output on pipe errors (fail open pattern) -_output_disabled = False - -# ANSI color codes (match ralph.sh TUI) -if sys.stdout.isatty() and not os.environ.get("NO_COLOR"): - C_RESET = "\033[0m" - C_DIM = "\033[2m" - C_CYAN = "\033[36m" -else: - C_RESET = C_DIM = C_CYAN = "" - -# TUI indentation (3 spaces to match ralph.sh) -INDENT = " " - -# Tool icons -ICONS = { - "Bash": "🔧", - "Edit": "📝", - "Write": "📄", - "Read": "📖", - "Grep": "🔍", - "Glob": "📁", - "Task": "🤖", - "WebFetch": "🌐", - "WebSearch": "🔎", - "TodoWrite": "📋", - "Question": "❓", - "Skill": "⚡", -} - - -def safe_print(msg: str) -> None: - """Print that fails open - disables output on BrokenPipe instead of crashing.""" - global _output_disabled - if _output_disabled: - return - try: - print(msg, flush=True) - except BrokenPipeError: - _output_disabled = True - - -def drain_stdin() -> None: - """Consume remaining stdin to prevent SIGPIPE to upstream processes.""" - try: - for _ in sys.stdin: - pass - except Exception: - pass - - -def truncate(s: str, max_len: int = 60) -> str: - s = s.replace("\n", " ").strip() - if len(s) > max_len: - return s[: max_len - 3] + "..." - return s - - -def normalize_path(path: str) -> str: - return path.split("/")[-1] if path else "unknown" - - -def format_tool_use(tool_name: str, tool_input: dict) -> str: - """Format a tool use event for TUI display.""" - icon = ICONS.get(tool_name, "🔹") - - if tool_name == "Bash": - cmd = tool_input.get("command", "") - desc = tool_input.get("description", "") - if desc: - return f"{icon} Bash: {truncate(desc)}" - return f"{icon} Bash: {truncate(cmd, 60)}" - - elif tool_name == "Edit": - path = tool_input.get("file_path") or tool_input.get("filePath", "") - return f"{icon} Edit: {normalize_path(path)}" - - elif tool_name == "Write": - path = tool_input.get("file_path") or tool_input.get("filePath", "") - return f"{icon} Write: {normalize_path(path)}" - - elif tool_name == "Read": - path = tool_input.get("file_path") or tool_input.get("filePath", "") - return f"{icon} Read: {normalize_path(path)}" - - elif tool_name == "Grep": - pattern = tool_input.get("pattern", "") - return f"{icon} Grep: {truncate(pattern, 40)}" - - elif tool_name == "Glob": - pattern = tool_input.get("pattern", "") - return f"{icon} Glob: {pattern}" - - elif tool_name == "Task": - desc = tool_input.get("description", "") - agent = tool_input.get("subagent_type", "") - return f"{icon} Task ({agent}): {truncate(desc, 50)}" - - elif tool_name == "Skill": - skill = tool_input.get("skill", "") - return f"{icon} Skill: {skill}" - - elif tool_name == "TodoWrite": - todos = tool_input.get("todos", []) - in_progress = [t for t in todos if t.get("status") == "in_progress"] - if in_progress: - return f"{icon} Todo: {truncate(in_progress[0].get('content', ''))}" - return f"{icon} Todo: {len(todos)} items" - - else: - return f"{icon} {tool_name}" - - -def format_tool_use_opencode(part: dict) -> str: - tool = part.get("tool", "") - state = part.get("state") or {} - tool_input = state.get("input") or {} - title = state.get("title") or "" - - # Map OpenCode tool names to Claude-style names for icon mapping - tool_name = { - "bash": "Bash", - "edit": "Edit", - "write": "Write", - "read": "Read", - "grep": "Grep", - "glob": "Glob", - "list": "List", - "webfetch": "WebFetch", - "websearch": "WebSearch", - "todowrite": "TodoWrite", - "question": "Question", - "task": "Task", - "skill": "Skill", - "patch": "Patch", - }.get(tool, tool) - - # Prefer explicit title if provided - if title: - return f"{ICONS.get(tool_name, '🔹')} {tool_name}: {truncate(title, 60)}" - - return format_tool_use(tool_name, tool_input) - - -def format_tool_result(block: dict) -> Optional[str]: - """Format a tool_result block (errors only). - - Args: - block: The full tool_result block (not just content) - """ - # Check is_error on the block itself - if block.get("is_error"): - content = block.get("content", "") - error_text = str(content) if content else "unknown error" - return f"{INDENT}{C_DIM}❌ {truncate(error_text, 60)}{C_RESET}" - - # Also check content for error strings (heuristic) - content = block.get("content", "") - if isinstance(content, str): - lower = content.lower() - if "error" in lower or "failed" in lower: - return f"{INDENT}{C_DIM}⚠️ {truncate(content, 60)}{C_RESET}" - - return None - - -def process_event(event: dict, verbose: bool) -> None: - """Process a single stream-json event.""" - event_type = event.get("type", "") - - # OpenCode run --format json - if event_type == "tool_use": - part = event.get("part") or {} - formatted = format_tool_use_opencode(part) - safe_print(f"{INDENT}{C_DIM}{formatted}{C_RESET}") - return - - if verbose and event_type == "text": - part = event.get("part") or {} - text = part.get("text", "") - if text.strip(): - safe_print(f"{INDENT}{C_CYAN}💬 {text}{C_RESET}") - return - - if event_type == "error": - error = event.get("error", {}) - msg = error.get("message") if isinstance(error, dict) else str(error) - if msg: - safe_print(f"{INDENT}{C_DIM}❌ {truncate(msg, 80)}{C_RESET}") - return - - # Tool use events (assistant messages) - if event_type == "assistant": - message = event.get("message", {}) - content = message.get("content", []) - - for block in content: - block_type = block.get("type", "") - - if block_type == "tool_use": - tool_name = block.get("name", "") - tool_input = block.get("input", {}) - formatted = format_tool_use(tool_name, tool_input) - safe_print(f"{INDENT}{C_DIM}{formatted}{C_RESET}") - - elif verbose and block_type == "text": - text = block.get("text", "") - if text.strip(): - safe_print(f"{INDENT}{C_CYAN}💬 {text}{C_RESET}") - - elif verbose and block_type == "thinking": - thinking = block.get("thinking", "") - if thinking.strip(): - safe_print(f"{INDENT}{C_DIM}🧠 {truncate(thinking, 100)}{C_RESET}") - - # Tool results (user messages with tool_result blocks) - elif event_type == "user": - message = event.get("message", {}) - content = message.get("content", []) - - for block in content: - if block.get("type") == "tool_result": - formatted = format_tool_result(block) - if formatted: - safe_print(formatted) - - -def main() -> None: - parser = argparse.ArgumentParser(description="Filter Claude stream-json output") - parser.add_argument( - "--verbose", - action="store_true", - help="Show text and thinking in addition to tool calls", - ) - args = parser.parse_args() - - for line in sys.stdin: - line = line.strip() - if not line: - continue - - try: - event = json.loads(line) - except json.JSONDecodeError: - continue - - try: - process_event(event, args.verbose) - except Exception: - # Swallow processing errors - keep draining stdin - pass - - -if __name__ == "__main__": - try: - main() - except KeyboardInterrupt: - sys.exit(0) - except BrokenPipeError: - # Output broken but keep draining to prevent upstream SIGPIPE - drain_stdin() - sys.exit(0) - except Exception as e: - print(f"watch-filter: {e}", file=sys.stderr) - drain_stdin() - sys.exit(0) diff --git a/sync/templates/opencode/skill/flow-next-setup/SKILL.md b/sync/templates/opencode/skill/flow-next-setup/SKILL.md deleted file mode 100644 index a93802c..0000000 --- a/sync/templates/opencode/skill/flow-next-setup/SKILL.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -name: flow-next-setup -description: Local install of flowctl CLI and CLAUDE.md/AGENTS.md instructions. Use when user runs /flow-next:setup. ---- - -# Flow-Next Setup - -Install flowctl locally and add instructions to project docs. - -## Benefits - -- `flowctl` accessible from command line (add `.flow/bin` to PATH) -- Other AI agents (OpenCode, Cursor, etc.) can read instructions from CLAUDE.md/AGENTS.md -- Works without Claude Code plugin installed - -## Workflow - -Read [workflow.md](workflow.md) and follow each step in order. - -## Notes - -- Copies scripts (not symlinks) for portability across environments -- Safe to re-run - will detect existing setup and offer to update -- Use the **question** tool for interactive prompts in setup diff --git a/sync/templates/opencode/skill/flow-next-setup/workflow.md b/sync/templates/opencode/skill/flow-next-setup/workflow.md deleted file mode 100644 index 3ccb0ea..0000000 --- a/sync/templates/opencode/skill/flow-next-setup/workflow.md +++ /dev/null @@ -1,160 +0,0 @@ -# Flow-Next Setup Workflow - -Follow these steps in order. This workflow is **idempotent** - safe to re-run. - -## Step 0: Resolve plugin path - -Use repo-local plugin root: - -```bash -ROOT="$(git rev-parse --show-toplevel)" -PLUGIN_ROOT="$ROOT/plugins/flow-next" -``` - -## Step 1: Check .flow/ exists - -Check if `.flow/` directory exists (use Bash `ls .flow/` or check for `.flow/meta.json`). - -- If `.flow/` exists: continue -- If `.flow/` doesn't exist: create it with `mkdir -p .flow` and create minimal meta.json: - ```json - {"schema_version": 2, "next_epic": 1} - ``` - -Also ensure `.flow/config.json` exists with defaults: -```bash -if [ ! -f .flow/config.json ]; then - echo '{"memory":{"enabled":false}}' > .flow/config.json -fi -``` - -## Step 2: Check existing setup - -Read `.flow/meta.json` and check for `setup_version` field. - -Also read `${PLUGIN_ROOT}/.claude-plugin/plugin.json` to get current plugin version. - -**If `setup_version` exists (already set up):** -- If **same version**: ask with the **question** tool: - - **Header**: `Update Docs` - - **Question**: `Already set up with v<VERSION>. Update docs only?` - - **Options**: - 1. `Yes, update docs` - 2. `No, exit` - - If yes: skip to Step 6 (docs) - - If no: done -- If **older version**: tell user "Updating from v<OLD> to v<NEW>" and continue - -**If no `setup_version`:** continue (first-time setup) - -## Step 3: Create .flow/bin/ - -```bash -mkdir -p .flow/bin -``` - -## Step 4: Copy files - -**IMPORTANT: Do NOT read flowctl.py - it's too large. Just copy it.** - -Copy using Bash `cp` with absolute paths: - -```bash -cp "${PLUGIN_ROOT}/scripts/flowctl" .flow/bin/flowctl -cp "${PLUGIN_ROOT}/scripts/flowctl.py" .flow/bin/flowctl.py -chmod +x .flow/bin/flowctl -``` - -Then read [templates/usage.md](templates/usage.md) and write it to `.flow/usage.md`. - -## Step 5: Update meta.json - -Read current `.flow/meta.json`, add/update these fields (preserve all others): - -```json -{ - "setup_version": "<PLUGIN_VERSION>", - "setup_date": "<ISO_DATE>" -} -``` - -## Step 6: Check and update documentation - -Read the template from [templates/claude-md-snippet.md](templates/claude-md-snippet.md). - -For each of CLAUDE.md and AGENTS.md: -1. Check if file exists -2. If exists, check if `<!-- BEGIN FLOW-NEXT -->` marker exists -3. If marker exists, extract content between markers and compare with template - -Determine status for each file: -- **missing**: file doesn't exist or no flow-next section -- **current**: section exists and matches template -- **outdated**: section exists but differs from template - -Based on status: - -**If both are current:** -``` -Documentation already up to date (CLAUDE.md, AGENTS.md). -``` -Skip to Step 7. - -**If one or both need updates:** -Show status in text, then ask with the **question** tool: -- **Header**: `Docs Update` -- **Question**: `Which docs should be updated?` -- **Options**: only include choices for files that are **missing** or **outdated**, plus `Skip` - - `CLAUDE.md only` (if needed) - - `AGENTS.md only` (if needed) - - `Both` (if both needed) - - `Skip` - -Wait for response, then for each chosen file: -1. Read the file (create if doesn't exist) -2. If marker exists: replace everything between `<!-- BEGIN FLOW-NEXT -->` and `<!-- END FLOW-NEXT -->` (inclusive) -3. If no marker: append the snippet - -## Step 7: Print summary - -``` -Flow-Next setup complete! - -Installed: -- .flow/bin/flowctl (v<VERSION>) -- .flow/bin/flowctl.py -- .flow/usage.md - -To use from command line: - export PATH=".flow/bin:$PATH" - flowctl --help - -Documentation updated: -- <files updated or "none"> - -Memory system: disabled by default -Enable with: flowctl config set memory.enabled true - -Notes: -- Re-run /flow-next:setup after plugin updates to refresh scripts -- Uninstall: rm -rf .flow/bin .flow/usage.md and remove <!-- BEGIN/END FLOW-NEXT --> block from docs -``` - -## Step 8: Ask about starring - -Ask with the **question** tool: -- **Header**: `Support Flow-Next` -- **Question**: `Flow-Next is free and open source. Would you like to ⭐ star the repo on GitHub to support the project?` -- **Options**: - 1. `Yes, star the repo` - 2. `No thanks` - -**If yes:** -1. Check if `gh` CLI is available: `which gh` -2. If available, run: `gh api -X PUT /user/starred/gmickel/flow-next-opencode` -3. If `gh` not available or command fails, provide the link: - ``` - Star manually: https://github.com/gmickel/flow-next-opencode - ``` - -**If no:** Thank them and complete setup without starring. diff --git a/sync/templates/opencode/skill/flow-next-work/SKILL.md b/sync/templates/opencode/skill/flow-next-work/SKILL.md deleted file mode 100644 index ba002fd..0000000 --- a/sync/templates/opencode/skill/flow-next-work/SKILL.md +++ /dev/null @@ -1,169 +0,0 @@ ---- -name: flow-next-work -description: Execute a Flow epic or task systematically with git setup, task tracking, quality checks, and commit workflow. Use when implementing a plan or working through a spec. Triggers on /flow-next:work with Flow IDs (fn-1, fn-1.2). ---- - -# Flow work - -Execute a plan systematically. Focus on finishing. - -Follow this skill and linked workflows exactly. Deviations cause drift, bad gates, retries, and user frustration. - -**IMPORTANT**: This plugin uses `.flow/` for ALL task tracking. Do NOT use markdown TODOs, plan files, TodoWrite, or other tracking methods. All task state must be read and written via `flowctl`. - -**CRITICAL: flowctl is BUNDLED — NOT installed globally.** `which flowctl` will fail (expected). Always use: -```bash -ROOT="$(git rev-parse --show-toplevel)" -PLUGIN_ROOT="$ROOT/plugins/flow-next" -FLOWCTL="$PLUGIN_ROOT/scripts/flowctl" -$FLOWCTL <command> -``` - -**Hard requirements (non-negotiable):** -- You MUST run `flowctl done` for each completed task and verify the task status is `done`. -- You MUST stage with `git add -A` (never list files). This ensures `.flow/` and `scripts/ralph/` (if present) are included. -- Do NOT claim completion until `flowctl show <task>` reports `status: done`. -- Do NOT invoke `/flow-next:impl-review` until tests/Quick commands are green. - -**Role**: execution lead, plan fidelity first. -**Goal**: complete every task in order with tests. - -## Ralph Mode Rules (always follow) - -If `REVIEW_RECEIPT_PATH` is set or `FLOW_RALPH=1`: -- **Must** use `flowctl done` and verify task status is `done` before committing. -- **Must** stage with `git add -A` (never list files). -- **Do NOT** use TodoWrite for tracking. - -## Input - -Full request: $ARGUMENTS - -Accepts: -- Flow epic ID `fn-N` to work through all tasks -- Flow task ID `fn-N.M` to work on single task -- Markdown spec file path (creates epic from file, then executes) -- Idea text (creates minimal epic + single task, then executes) -- Chained instructions like "then review with /flow-next:impl-review" - -Examples: -- `/flow-next:work fn-1` -- `/flow-next:work fn-1.3` -- `/flow-next:work docs/my-feature-spec.md` -- `/flow-next:work Add rate limiting` -- `/flow-next:work fn-1 then review via /flow-next:impl-review` - -If no input provided, ask for it. - -## FIRST: Parse Options or Ask Questions - -Check available backends and configured preference: -```bash -HAVE_RP=0; -if command -v rp-cli >/dev/null 2>&1; then - HAVE_RP=1; -elif [[ -x /opt/homebrew/bin/rp-cli || -x /usr/local/bin/rp-cli ]]; then - HAVE_RP=1; -fi; - -# Check configured backend (priority: env > config) -CONFIGURED_BACKEND="${FLOW_REVIEW_BACKEND:-}"; -if [[ -z "$CONFIGURED_BACKEND" ]]; then - CONFIGURED_BACKEND="$($FLOWCTL config get review.backend --json 2>/dev/null | jq -r '.value // empty')"; -fi -``` - -**MUST RUN the detection command above** and use its result. Do **not** assume rp-cli is missing without running it. - -### Option Parsing (skip questions if found in arguments) - -Parse the arguments for these patterns. If found, use them and skip corresponding questions: - -**Branch mode**: -- `--branch=current` or `--current` or "current branch" or "stay on this branch" → current branch -- `--branch=new` or `--new-branch` or "new branch" or "create branch" → new branch -- `--branch=worktree` or `--worktree` or "isolated worktree" or "worktree" → isolated worktree - -**Review mode**: -- `--review=opencode` or "opencode review" or "use opencode" → OpenCode review (GPT-5.2, reasoning high) -- `--review=rp` or "review with rp" or "rp chat" or "repoprompt review" → RepoPrompt chat (via `flowctl rp chat-send`) -- `--review=export` or "export review" or "external llm" → export for external LLM -- `--review=none` or `--no-review` or "no review" or "skip review" → no review - -### If options NOT found in arguments - -**IMPORTANT**: Ask setup questions in **plain text only**. **Do NOT use the question tool.** This is required for voice dictation (e.g., "1a 2b"). - -**Skip review question if**: Ralph mode (`FLOW_RALPH=1`) OR backend already configured (`CONFIGURED_BACKEND` not empty). In these cases, only ask branch question: - -``` -Quick setup: Where to work? -a) Current branch b) New branch c) Isolated worktree - -(Reply: "a", "current", or just tell me) -``` - -**Otherwise**, output questions based on available backends: - -**If rp-cli available:** -``` -Quick setup before starting: - -1. **Branch** — Where to work? - a) Current branch - b) New branch - c) Isolated worktree - -2. **Review** — Run Carmack-level review after? - a) Yes, OpenCode review (GPT-5.2, reasoning high) - b) Yes, RepoPrompt chat (macOS, visual builder) - c) Yes, export for external LLM (ChatGPT, Claude web) - d) No - -(Reply: "1a 2a", "current branch, opencode review", or just tell me naturally) -``` - -**If rp-cli not available:** -``` -Quick setup before starting: - -1. **Branch** — Where to work? - a) Current branch - b) New branch - c) Isolated worktree - -2. **Review** — Run Carmack-level review after? - a) Yes, OpenCode review (GPT-5.2, reasoning high) - b) Yes, export for external LLM - c) No - -(Reply: "1a 2a", "current branch, opencode", or just tell me naturally) -``` - -Wait for response. Parse naturally — user may reply terse or ramble via voice. - -**Defaults when empty/ambiguous:** -- Branch = `new` -- Review = configured backend if set, else `opencode`, else `rp` if available, else `none` - -**Defaults when no review backend available:** -- Branch = `new` -- Review = `none` - -**Do NOT read files or write code until user responds.** - -## Workflow - -After setup questions answered, read `.opencode/skill/flow-next-work/phases.md` and execute each phase in order. -If user chose review: -- Option 2a: run `/flow-next:impl-review` after Phase 6, fix issues until it passes -- Option 2b: run `/flow-next:impl-review` with export mode after Phase 6 - -## Guardrails - -- Don't start without asking branch question (unless FLOW_RALPH=1) -- Don't start without plan/epic -- Don't skip tests -- Don't leave tasks half-done -- Never use TodoWrite for task tracking -- Never create plan files outside `.flow/` diff --git a/sync/templates/opencode/skill/flow-next-work/phases.md b/sync/templates/opencode/skill/flow-next-work/phases.md deleted file mode 100644 index e2b32f6..0000000 --- a/sync/templates/opencode/skill/flow-next-work/phases.md +++ /dev/null @@ -1,269 +0,0 @@ -# Flow Work Phases - -(Branch question already asked in SKILL.md before reading this file) - -**CRITICAL**: If you are about to create: -- a markdown TODO list, -- a task list outside `.flow/`, -- or any plan files outside `.flow/`, - -**STOP** and instead: -- create/update tasks in `.flow/` using `flowctl`, -- record details in the epic/task spec markdown. - -## Setup - -**CRITICAL: flowctl is BUNDLED — NOT installed globally.** `which flowctl` will fail (expected). Always use: - -```bash -ROOT="$(git rev-parse --show-toplevel)" -PLUGIN_ROOT="$ROOT/plugins/flow-next" -FLOWCTL="$PLUGIN_ROOT/scripts/flowctl" -``` - -## Phase 1: Resolve Input - -Detect input type in this order (first match wins): - -1. **Flow task ID** `fn-N.M` or `fn-N-xxx.M` (e.g., fn-1.3, fn-1-abc.2) -2. **Flow epic ID** `fn-N` or `fn-N-xxx` (e.g., fn-1, fn-1-abc) -3. **Spec file** `.md` path that exists on disk -4. **Idea text** everything else - ---- - -**Flow task ID (fn-N.M or fn-N-xxx.M)**: -- Read task: `$FLOWCTL show <id> --json` -- Read spec: `$FLOWCTL cat <id>` -- Get epic from task data for context: `$FLOWCTL show <epic-id> --json && $FLOWCTL cat <epic-id>` - -**Flow epic ID (fn-N or fn-N-xxx)**: -- Read epic: `$FLOWCTL show <id> --json` -- Read spec: `$FLOWCTL cat <id>` -- Get first ready task: `$FLOWCTL ready --epic <id> --json` - -**Spec file start (.md path that exists)**: -1. Check file exists: `test -f "<path>"` — if not, treat as idea text -2. Initialize: `$FLOWCTL init --json` -3. Read file and extract title from first `# Heading` or use filename -4. Create epic: `$FLOWCTL epic create --title "<extracted-title>" --json` -5. Set spec from file: `$FLOWCTL epic set-plan <epic-id> --file <path> --json` -6. Create single task: `$FLOWCTL task create --epic <epic-id> --title "Implement <title>" --json` -7. Continue with epic-id - -**Spec-less start (idea text)**: -1. Initialize: `$FLOWCTL init --json` -2. Create epic: `$FLOWCTL epic create --title "<idea>" --json` -3. Create single task: `$FLOWCTL task create --epic <epic-id> --title "Implement <idea>" --json` -4. Continue with epic-id - -## Phase 2: Apply Branch Choice - -Based on user's answer from setup questions: - -- **Worktree**: use `skill: flow-next-worktree-kit` -- **New branch**: - ```bash - git checkout main && git pull origin main - git checkout -b <branch> - ``` -- **Current branch**: proceed (user already confirmed) - -## Phase 3: Prime / Re-anchor Context (EVERY task) - -**MANDATORY: This phase runs before EVERY task. No exceptions. No optimizations.** - -Per Anthropic's long-running agent guidance: agents must re-anchor from sources of truth to prevent drift. Even if you "remember" the context, re-read it. The reads are cheap; drift is expensive. - -**Also run this phase after context compaction** (if you notice the conversation was summarized). - -### Re-anchor Checklist (run ALL before each task) - -**You MUST run every command below. Do not skip or combine.** - -```bash -# 1. Find next task -$FLOWCTL ready --epic <epic-id> --json - -# 2. Re-read epic (EVERY time) -$FLOWCTL show <epic-id> --json -$FLOWCTL cat <epic-id> - -# 3. Re-read task spec (EVERY time) -$FLOWCTL show <task-id> --json -$FLOWCTL cat <task-id> - -# 4. Check git state (EVERY time) -git status -git log -5 --oneline - -# 5. Validate structure (EVERY time) -$FLOWCTL validate --epic <epic-id> --json - -# 6. Check memory (if enabled) -$FLOWCTL config get memory.enabled --json -``` - -**If memory.enabled is true**, also run: -- subagent_type: `memory-scout` -- prompt: `<task-id>: <task-title>` - -This retrieves relevant project learnings before implementation. - -If no ready tasks after step 1, all done → go to Phase 6. - -After step 5, run the smoke command from epic spec's "Quick commands" section. - -**Why every time?** Context windows compress. You forget details. The spec is the truth. 30 seconds of re-reading prevents hours of rework. - -**Anti-pattern**: Running steps 2-5 only on the first task. The whole point is EVERY task gets fresh context. - -## Phase 4: Execute Task Loop - -**For each task** (one at a time): - -1. **Start task**: - ```bash - $FLOWCTL start <task-id> --json - ``` - -2. **Implement + test thoroughly**: - - Read task spec for requirements - - Write code - - Run tests (including epic spec "Quick commands") - - Verify acceptance criteria - - If any command fails, fix before proceeding - -3. **If you discover new work**: - - Draft new task title + acceptance checklist - - Create immediately: - ```bash - # Write acceptance to temp file first - $FLOWCTL task create --epic <epic-id> --title "Found: <issue>" --deps <current-task-id> --acceptance-file <temp-md> --json - ``` - - Re-run `$FLOWCTL ready --epic <epic-id> --json` to see updated order - -4. **Commit implementation** (code changes only): - ```bash - git add -A # never list files; include .flow/ and scripts/ralph/ if present - git status --short - git commit -m "<type>: <short summary of what was done>" - COMMIT_HASH="$(git rev-parse HEAD)" - echo "Commit: $COMMIT_HASH" - ``` - -5. **Complete task** (records done status + evidence): - Write done summary to temp file (required format): - ``` - - What changed (1-3 bullets) - - Why (1-2 bullets) - - Verification (tests/commands run) - - Follow-ups (optional, max 2 bullets) - ``` - - Write evidence to temp JSON file **with the commit hash from step 4**: - ```json - {"commits":["<COMMIT_HASH>"],"tests":["npm test"],"prs":[]} - ``` - - Then: - ```bash - $FLOWCTL done <task-id> --summary-file <summary.md> --evidence-json <evidence.json> --json - ``` - - **FORBIDDEN**: `flowctl task edit` (no such command). - If you need to update task text, edit the markdown file directly and use `flowctl done` with summary/evidence. - - Verify the task is actually marked done: - ```bash - $FLOWCTL show <task-id> --json - ``` - If status is not `done`, stop and re-run `flowctl done` before proceeding. - -6. **Amend commit** to include .flow/ updates: - ```bash - git add -A - git commit --amend --no-edit - ``` - -7. **Verify task completion**: - ```bash - $FLOWCTL validate --epic <epic-id> --json - git status - ``` - Ensure working tree is clean. - -8. **Loop**: Return to Phase 3 for next task. - -## Phase 5: Quality - -After all tasks complete (or periodically for large epics): - -- Run relevant tests -- Run lint/format per repo -- If change is large/risky, run the quality auditor subagent via the task tool: - - subagent_type: `quality-auditor` - - prompt: "Review recent changes" -- Fix critical issues - -## Phase 6: Ship - -**Verify all tasks done**: -```bash -$FLOWCTL show <epic-id> --json -$FLOWCTL validate --epic <epic-id> --json -``` - -**Final commit** (if any uncommitted changes): -```bash - git add -A -git status -git diff --staged -git commit -m "<final summary>" -``` - -**Do NOT close the epic here** unless the user explicitly asked. -Ralph closes done epics at the end of the loop. - -Then push + open PR if user wants. - -## Phase 7: Review (if chosen at start) - -If user chose "Yes" to review in setup questions or `--review=opencode` / `--review=rp` was passed: - -**CRITICAL: You MUST invoke the `/flow-next:impl-review` skill. Do NOT improvise your own review format.** - -The impl-review skill: -- Auto-detects backend (OpenCode or RepoPrompt) based on config/availability -- Uses the correct prompt template requiring `<verdict>SHIP|NEEDS_WORK|MAJOR_RETHINK</verdict>` -- Handles the fix loop internally - -Steps: -1. Invoke `/flow-next:impl-review` (this loads the skill with its workflow.md) -2. If review returns NEEDS_WORK or MAJOR_RETHINK: - - **Immediately fix the issues** (do NOT ask for confirmation — user already consented) - - Commit fixes - - Re-run tests/Quick commands - - Re-run `/flow-next:impl-review` -3. Repeat until review returns SHIP - -**Anti-pattern**: Sending your own review prompts directly without invoking the skill. -The skill has the correct format; improvised prompts ask for "LGTM" which breaks automation. - -**No human gates here** — the review-fix-review loop is fully automated. - -## Definition of Done - -Confirm before ship: -- All tasks have status "done" -- `$FLOWCTL validate --epic <id>` passes -- Tests pass -- Lint/format pass -- Docs updated if needed -- Working tree is clean - -## Example loop - -``` -Prime → Task A → test → done → commit → Prime → Task B → ... -``` diff --git a/sync/templates/opencode/skill/flow-next-worktree-kit/SKILL.md b/sync/templates/opencode/skill/flow-next-worktree-kit/SKILL.md deleted file mode 100644 index 609a1ff..0000000 --- a/sync/templates/opencode/skill/flow-next-worktree-kit/SKILL.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -name: flow-next-worktree-kit -description: Manage git worktrees (create/list/switch/cleanup) and copy .env files. Use for parallel feature work, isolated review, clean workspace, or when user mentions worktrees. ---- - -# Worktree kit - -Use the manager script for all worktree actions. - -```bash -ROOT="$(git rev-parse --show-toplevel)" -PLUGIN_ROOT="$ROOT/.opencode/skill" -bash "$PLUGIN_ROOT/flow-next-worktree-kit/scripts/worktree.sh" <command> [args] -``` - -Commands: -- `create <name> [base]` -- `list` -- `switch <name>` (prints path) -- `cleanup` -- `copy-env <name>` - -Safety notes: -- `create` does not change the current branch -- `cleanup` does not force-remove worktrees and does not delete branches -- `cleanup` deletes the worktree directory (including ignored files); removal fails if the worktree is not clean -- `.env*` is copied with no overwrite (symlinks skipped) -- refuses to operate if `.worktrees/` or any worktree path component is a symlink -- `copy-env` only targets registered worktrees -- `origin` fetch is optional; local base refs are allowed -- fetch from `origin` only when base looks like a branch -- Worktrees live under `.worktrees/` diff --git a/sync/templates/opencode/skill/flow-next/SKILL.md b/sync/templates/opencode/skill/flow-next/SKILL.md deleted file mode 100644 index df6185e..0000000 --- a/sync/templates/opencode/skill/flow-next/SKILL.md +++ /dev/null @@ -1,162 +0,0 @@ ---- -name: flow-next -description: "Manage .flow/ tasks and epics. Triggers: 'show me my tasks', 'list epics', 'what tasks are there', 'add a task', 'create task', 'what's ready', 'task status', 'show fn-1'. NOT for /flow-next:plan or /flow-next:work." ---- - -# Flow-Next Task Management - -Quick task operations in `.flow/`. For planning features use `/flow-next:plan`, for executing use `/flow-next:work`. - -## Setup - -**CRITICAL: flowctl is BUNDLED — NOT installed globally.** `which flowctl` will fail (expected). Always use: - -```bash -ROOT="$(git rev-parse --show-toplevel)" -PLUGIN_ROOT="$ROOT/plugins/flow-next" -FLOWCTL="$PLUGIN_ROOT/scripts/flowctl" -``` - -Then run commands with `$FLOWCTL <command>`. - -**Discover all commands/options:** -```bash -$FLOWCTL --help -$FLOWCTL <command> --help # e.g., $FLOWCTL task --help -``` - -## Quick Reference - -```bash -# Check if .flow exists -$FLOWCTL detect --json - -# Initialize (if needed) -$FLOWCTL init --json - -# List everything (epics + tasks grouped) -$FLOWCTL list --json - -# List all epics -$FLOWCTL epics --json - -# List all tasks (or filter by epic/status) -$FLOWCTL tasks --json -$FLOWCTL tasks --epic fn-1 --json -$FLOWCTL tasks --status todo --json - -# View epic with all tasks -$FLOWCTL show fn-1 --json -$FLOWCTL cat fn-1 # Spec markdown - -# View single task -$FLOWCTL show fn-1.2 --json -$FLOWCTL cat fn-1.2 # Task spec - -# What's ready to work on? -$FLOWCTL ready --epic fn-1 --json - -# Create task under existing epic -$FLOWCTL task create --epic fn-1 --title "Fix bug X" --json - -# Set task description (from file) -echo "Description here" > /tmp/desc.md -$FLOWCTL task set-description fn-1.2 --file /tmp/desc.md --json - -# Set acceptance criteria (from file) -echo "- [ ] Criterion 1" > /tmp/accept.md -$FLOWCTL task set-acceptance fn-1.2 --file /tmp/accept.md --json - -# Start working on task -$FLOWCTL start fn-1.2 --json - -# Mark task done -echo "What was done" > /tmp/summary.md -echo '{"commits":["abc123"],"tests":["npm test"],"prs":[]}' > /tmp/evidence.json -$FLOWCTL done fn-1.2 --summary-file /tmp/summary.md --evidence-json /tmp/evidence.json --json - -# Validate structure -$FLOWCTL validate --epic fn-1 --json -$FLOWCTL validate --all --json -``` - -## Common Patterns - -### "Add a task for X" - -1. Find relevant epic: - ```bash - # List all epics - $FLOWCTL epics --json - - # Or show a specific epic to check its scope - $FLOWCTL show fn-1 --json - ``` - -2. Create task: - ```bash - $FLOWCTL task create --epic fn-N --title "Short title" --json - ``` - -3. Add description: - ```bash - cat > /tmp/desc.md << 'EOF' - **Bug/Feature:** Brief description - - **Details:** - - Point 1 - - Point 2 - EOF - $FLOWCTL task set-description fn-N.M --file /tmp/desc.md --json - ``` - -4. Add acceptance: - ```bash - cat > /tmp/accept.md << 'EOF' - - [ ] Criterion 1 - - [ ] Criterion 2 - EOF - $FLOWCTL task set-acceptance fn-N.M --file /tmp/accept.md --json - ``` - -### "What tasks are there?" - -```bash -# All epics -$FLOWCTL epics --json - -# All tasks -$FLOWCTL tasks --json - -# Tasks for specific epic -$FLOWCTL tasks --epic fn-1 --json - -# Ready tasks for an epic -$FLOWCTL ready --epic fn-1 --json -``` - -### "Show me task X" - -```bash -$FLOWCTL show fn-1.2 --json # Metadata -$FLOWCTL cat fn-1.2 # Full spec -``` - -### Create new epic (rare - usually via /flow-next:plan) - -```bash -$FLOWCTL epic create --title "Epic title" --json -# Returns: {"success": true, "id": "fn-N", ...} -``` - -## ID Format - -- Epic: `fn-N` (e.g., `fn-1`, `fn-42`) -- Task: `fn-N.M` (e.g., `fn-1.1`, `fn-42.7`) - -## Notes - -- Run `$FLOWCTL --help` to discover all commands and options -- All writes go through flowctl (don't edit JSON/MD files directly) -- `--json` flag gives machine-readable output -- For complex planning/execution, use `/flow-next:plan` and `/flow-next:work` diff --git a/sync/transform.py b/sync/transform.py deleted file mode 100755 index 353ec17..0000000 --- a/sync/transform.py +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env python3 -from pathlib import Path -import shutil - -ROOT = Path(__file__).resolve().parents[1] -TPL = ROOT / "sync" / "templates" / "opencode" -DST = ROOT / ".opencode" - -if not TPL.exists(): - raise SystemExit(f"missing templates: {TPL}") - -for src in TPL.rglob("*"): - if src.is_dir(): - continue - rel = src.relative_to(TPL) - target = DST / rel - target.parent.mkdir(parents=True, exist_ok=True) - shutil.copy2(src, target) diff --git a/sync/verify.sh b/sync/verify.sh deleted file mode 100755 index 3d341ca..0000000 --- a/sync/verify.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -ROOT="$(cd "$(dirname "$0")/.." && pwd)" - -if [[ -d "$ROOT/plugins/flow-next/commands" ]]; then - echo "plugins/flow-next/commands should not exist" >&2 - exit 1 -fi - -if grep -R -n "AskUserQuestion" "$ROOT/.opencode" >/dev/null; then - echo "AskUserQuestion found in .opencode (must use OpenCode question tool)" >&2 - grep -R -n "AskUserQuestion" "$ROOT/.opencode" >&2 - exit 1 -fi - -if grep -R -n -i "codex" "$ROOT/.opencode" | grep -v "/opencode.json" >/dev/null; then - echo "codex references found in .opencode (not supported)" >&2 - grep -R -n -i "codex" "$ROOT/.opencode" | grep -v "/opencode.json" >&2 - exit 1 -fi - -if grep -R -n "CLAUDE_" "$ROOT/.opencode" | grep -v "flow-next-ralph-init/templates" >/dev/null; then - echo "CLAUDE_ references found outside ralph templates" >&2 - grep -R -n "CLAUDE_" "$ROOT/.opencode" | grep -v "flow-next-ralph-init/templates" >&2 - exit 1 -fi From 1152e817438e1273e43833a26afef3f9ad7a48d3 Mon Sep 17 00:00:00 2001 From: Gordon Mickel <gordon.mickel@gmail.com> Date: Fri, 16 Jan 2026 22:08:35 +0100 Subject: [PATCH 09/11] fix: require verdict in ralph receipts --- .opencode/skill/flow-next-ralph-init/templates/ralph.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.opencode/skill/flow-next-ralph-init/templates/ralph.sh b/.opencode/skill/flow-next-ralph-init/templates/ralph.sh index d4084a9..24ef78f 100644 --- a/.opencode/skill/flow-next-ralph-init/templates/ralph.sh +++ b/.opencode/skill/flow-next-ralph-init/templates/ralph.sh @@ -735,6 +735,10 @@ if data.get("type") != kind: sys.exit(1) if data.get("id") != rid: sys.exit(1) +if kind in ("plan_review", "impl_review"): + verdict = data.get("verdict") + if verdict not in ("SHIP", "NEEDS_WORK", "MAJOR_RETHINK"): + sys.exit(1) sys.exit(0) PY } From 90c4a3c92bf4e3bd0e41915c5d165129f0096729 Mon Sep 17 00:00:00 2001 From: Gordon Mickel <gordon.mickel@gmail.com> Date: Fri, 16 Jan 2026 22:16:20 +0100 Subject: [PATCH 10/11] docs: changelog 0.2.3 --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 47457d9..86c5078 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 0.2.3 + +- Canonicalize `.opencode/` (remove legacy plugin/sync paths) +- Docs moved to `docs/` with updated links +- Ralph receipts now require verdicts + ## 0.2.1 - Add browser skill (standalone) From 75e5acc26f4fda480612d9c6e7060d875f180c55 Mon Sep 17 00:00:00 2001 From: Gordon Mickel <gordon.mickel@gmail.com> Date: Fri, 16 Jan 2026 22:34:33 +0100 Subject: [PATCH 11/11] ci: opencode paths --- .github/workflows/test-flow-next.yml | 29 ++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/.github/workflows/test-flow-next.yml b/.github/workflows/test-flow-next.yml index 291cb2f..73568b7 100644 --- a/.github/workflows/test-flow-next.yml +++ b/.github/workflows/test-flow-next.yml @@ -4,12 +4,20 @@ on: push: branches: [main] paths: - - "plugins/flow-next/**" + - ".opencode/**" + - "docs/**" + - "README.md" + - "install.sh" + - "sync/PORTING.md" - ".github/workflows/test-flow-next.yml" pull_request: branches: [main] paths: - - "plugins/flow-next/**" + - ".opencode/**" + - "docs/**" + - "README.md" + - "install.sh" + - "sync/PORTING.md" - ".github/workflows/test-flow-next.yml" workflow_dispatch: @@ -29,26 +37,23 @@ jobs: python-version: "3.11" - name: Check flowctl.py syntax - run: python -m py_compile plugins/flow-next/scripts/flowctl.py + run: python -m py_compile .opencode/bin/flowctl.py - name: Check ralph.sh syntax (Unix) if: runner.os != 'Windows' - run: bash -n plugins/flow-next/skills/flow-next-ralph-init/templates/ralph.sh + run: bash -n .opencode/skill/flow-next-ralph-init/templates/ralph.sh - name: Check ralph.sh syntax (Windows Git Bash) if: runner.os == 'Windows' shell: bash - run: bash -n plugins/flow-next/skills/flow-next-ralph-init/templates/ralph.sh + run: bash -n .opencode/skill/flow-next-ralph-init/templates/ralph.sh - - name: Run comprehensive tests (Unix) + - name: Smoke check flowctl (Unix) if: runner.os != 'Windows' run: | - cd /tmp - bash "$GITHUB_WORKSPACE/plugins/flow-next/scripts/ci_test.sh" + python3 .opencode/bin/flowctl.py --help >/dev/null - - name: Run comprehensive tests (Windows) + - name: Smoke check flowctl (Windows) if: runner.os == 'Windows' - shell: bash run: | - cd "$RUNNER_TEMP" - bash "$GITHUB_WORKSPACE/plugins/flow-next/scripts/ci_test.sh" + python .opencode/bin/flowctl.py --help > NUL