Initial plan-tools: plan-refine and plan-status

Semi-automated plan refinement pipeline using Oracle for ChatGPT
browser automation. Includes frontmatter-based tracking and a
dashboard scanner for plan status across projects.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Taylor Eernisse
2026-02-07 13:03:28 -05:00
commit defb1fb08e
6 changed files with 561 additions and 0 deletions

200
bin/plan-refine Executable file
View File

@@ -0,0 +1,200 @@
#!/usr/bin/env bash
# plan-refine — Run one ChatGPT evaluation iteration on a plan file via Oracle
# Usage: plan-refine <plan-file> [options]
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
PROMPTS_DIR="$SCRIPT_DIR/prompts"
source "$SCRIPT_DIR/lib/frontmatter.sh"
# Defaults
MODEL_STRATEGY="current"
ORACLE_MODEL=""
DRY_RUN=false
SKIP_CLIPBOARD=false
BROWSER_TIMEOUT="1200s"
PLAN_FILE=""
usage() {
cat <<'EOF'
plan-refine — Send a plan to ChatGPT for adversarial review via Oracle
Usage: plan-refine <plan-file> [options]
After Oracle captures ChatGPT's feedback, the Claude integration
prompt is copied to your clipboard. Paste it into Claude Code.
Options:
--dry-run Preview prompt without sending
--model <model> Force Oracle model (e.g. gpt-5.2-pro)
--model-strategy <s> Browser model strategy: current|select|ignore (default: current)
--timeout <duration> Browser timeout (default: 1200s)
--no-clipboard Skip copying integration prompt to clipboard
--init Add plan frontmatter to file without running Oracle
-h, --help Show this help
EOF
exit 0
}
INIT_ONLY=false
while [[ $# -gt 0 ]]; do
case "$1" in
--dry-run) DRY_RUN=true; shift ;;
--model) ORACLE_MODEL="$2"; shift 2 ;;
--model-strategy) MODEL_STRATEGY="$2"; shift 2 ;;
--timeout) BROWSER_TIMEOUT="$2"; shift 2 ;;
--no-clipboard) SKIP_CLIPBOARD=true; shift ;;
--init) INIT_ONLY=true; shift ;;
-h|--help) usage ;;
-*) echo "Unknown option: $1" >&2; exit 2 ;;
*) PLAN_FILE="$1"; shift ;;
esac
done
if [[ -z "$PLAN_FILE" ]]; then
echo "Error: No plan file specified" >&2
echo "Usage: plan-refine <plan-file>" >&2
exit 2
fi
# Resolve to absolute path
PLAN_FILE="$(cd "$(dirname "$PLAN_FILE")" && pwd)/$(basename "$PLAN_FILE")"
if [[ ! -f "$PLAN_FILE" ]]; then
echo "Error: Plan file not found: $PLAN_FILE" >&2
exit 1
fi
# Ensure frontmatter exists
if ! head -1 "$PLAN_FILE" | grep -q '^---$'; then
echo "No frontmatter found. Initializing..."
init_frontmatter "$PLAN_FILE"
fi
if ! is_plan_file "$PLAN_FILE"; then
echo "Adding plan: true to frontmatter..."
set_frontmatter "$PLAN_FILE" "plan" "true"
fi
if [[ "$INIT_ONLY" == "true" ]]; then
init_frontmatter "$PLAN_FILE"
echo "Frontmatter initialized for: $PLAN_FILE"
exit 0
fi
# Read current state
ITERATION=$(get_frontmatter "$PLAN_FILE" "iteration" "0")
TARGET=$(get_frontmatter "$PLAN_FILE" "target_iterations" "8")
STATUS=$(get_frontmatter "$PLAN_FILE" "status" "drafting")
NEXT_ITERATION=$((ITERATION + 1))
# Status check
if [[ "$STATUS" == "ready" || "$STATUS" == "implementing" || "$STATUS" == "completed" ]]; then
echo "Warning: Plan status is '$STATUS' — already past iteration phase."
echo "Continue anyway? (y/N)"
read -r confirm
[[ "$confirm" != "y" && "$confirm" != "Y" ]] && exit 0
fi
# Build feedback output path
FEEDBACK_FILE="${PLAN_FILE%.md}.feedback-${NEXT_ITERATION}.md"
# Read evaluation prompt
EVAL_PROMPT=$(cat "$PROMPTS_DIR/chatgpt-eval.md")
# Build Oracle model args
ORACLE_MODEL_ARGS=()
if [[ -n "$ORACLE_MODEL" ]]; then
ORACLE_MODEL_ARGS+=(--model "$ORACLE_MODEL")
fi
echo "=== plan-refine ==="
echo " Plan: $(basename "$PLAN_FILE")"
echo " Status: $STATUS"
echo " Iteration: $ITERATION -> $NEXT_ITERATION (target: $TARGET)"
echo " Feedback: $(basename "$FEEDBACK_FILE")"
echo ""
if [[ "$DRY_RUN" == "true" ]]; then
echo "=== DRY RUN — would send this to Oracle ==="
echo ""
echo "oracle --engine browser \\"
echo " --browser-manual-login \\"
echo " --browser-model-strategy $MODEL_STRATEGY \\"
echo " --browser-timeout $BROWSER_TIMEOUT \\"
[[ -n "$ORACLE_MODEL" ]] && echo " --model $ORACLE_MODEL \\"
echo " -p \"$(echo "$EVAL_PROMPT" | head -1)...\" \\"
echo " --file \"$PLAN_FILE\" \\"
echo " --write-output \"$FEEDBACK_FILE\""
echo ""
echo "=== FULL PROMPT ==="
echo "$EVAL_PROMPT"
exit 0
fi
echo "Sending to ChatGPT via Oracle..."
echo "(This may take a few minutes. Oracle will automate the browser.)"
echo ""
# Call Oracle
oracle --engine browser \
--browser-manual-login \
--browser-model-strategy "$MODEL_STRATEGY" \
--browser-timeout "$BROWSER_TIMEOUT" \
"${ORACLE_MODEL_ARGS[@]}" \
-p "$EVAL_PROMPT" \
--file "$PLAN_FILE" \
--write-output "$FEEDBACK_FILE"
ORACLE_EXIT=$?
if [[ $ORACLE_EXIT -ne 0 ]]; then
echo "Error: Oracle exited with code $ORACLE_EXIT" >&2
exit $ORACLE_EXIT
fi
if [[ ! -f "$FEEDBACK_FILE" ]]; then
echo "Error: Expected feedback file not found: $FEEDBACK_FILE" >&2
exit 1
fi
# Update frontmatter
set_frontmatter "$PLAN_FILE" "iteration" "$NEXT_ITERATION"
set_frontmatter "$PLAN_FILE" "updated" "$(date +%Y-%m-%d)"
if [[ "$STATUS" == "drafting" ]]; then
set_frontmatter "$PLAN_FILE" "status" "iterating"
fi
echo ""
echo "Feedback saved to: $FEEDBACK_FILE"
echo ""
# Build and copy Claude integration prompt
if [[ "$SKIP_CLIPBOARD" != "true" ]] && command -v pbcopy &>/dev/null; then
INTEGRATE_PROMPT=$(cat "$PROMPTS_DIR/claude-integrate.md")
CLIPBOARD_CONTENT="Read the plan at: $PLAN_FILE
Read ChatGPT's feedback at: $FEEDBACK_FILE
${INTEGRATE_PROMPT}
Write the updated plan back to: $PLAN_FILE"
echo "$CLIPBOARD_CONTENT" | pbcopy
echo "Claude integration prompt copied to clipboard."
echo "Paste it into Claude Code to integrate the feedback."
else
echo "Next: Tell Claude to integrate the feedback."
echo " Plan: $PLAN_FILE"
echo " Feedback: $FEEDBACK_FILE"
fi
# Progress indicator
if [[ $NEXT_ITERATION -ge $TARGET ]]; then
echo ""
echo "Target iterations reached ($NEXT_ITERATION/$TARGET)."
echo "Consider: plan-refine $PLAN_FILE --init (to update status to 'splitting')"
fi

184
bin/plan-status Executable file
View File

@@ -0,0 +1,184 @@
#!/usr/bin/env bash
# plan-status — Scan projects for plan files and show pipeline status
# Usage: plan-status [search-root] [options]
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
source "$SCRIPT_DIR/lib/frontmatter.sh"
SEARCH_ROOT="${HOME}/projects"
JSON_OUTPUT=false
FILTER_STATUS=""
usage() {
cat <<'EOF'
plan-status — Dashboard for plan files across projects
Usage: plan-status [search-root] [options]
Scans for markdown files with plan: true frontmatter and shows
their position in the refinement pipeline.
Pipeline: drafting -> iterating -> splitting -> refining -> ready -> implementing -> completed
Options:
--json Machine-readable JSON output
--status <status> Filter to plans with this status
--root <path> Search root (default: ~/projects)
-h, --help Show this help
EOF
exit 0
}
while [[ $# -gt 0 ]]; do
case "$1" in
--json) JSON_OUTPUT=true; shift ;;
--status) FILTER_STATUS="$2"; shift 2 ;;
--root) SEARCH_ROOT="$2"; shift 2 ;;
-h|--help) usage ;;
-*) echo "Unknown option: $1" >&2; exit 2 ;;
*) SEARCH_ROOT="$1"; shift ;;
esac
done
if [[ ! -d "$SEARCH_ROOT" ]]; then
echo "Error: Search root not found: $SEARCH_ROOT" >&2
exit 1
fi
# Collect plan files
declare -a PLAN_FILES=()
while IFS= read -r -d '' file; do
if is_plan_file "$file"; then
PLAN_FILES+=("$file")
fi
done < <(find "$SEARCH_ROOT" \
-name "*.md" \
-not -path "*/node_modules/*" \
-not -path "*/.git/*" \
-not -path "*/.jj/*" \
-not -path "*/target/*" \
-not -name "*.feedback-*" \
-print0 2>/dev/null)
if [[ ${#PLAN_FILES[@]} -eq 0 ]]; then
if [[ "$JSON_OUTPUT" == "true" ]]; then
echo '{"plans":[],"summary":{"total":0}}'
else
echo "No plan files found under $SEARCH_ROOT"
echo "Initialize a plan: plan-refine <file.md> --init"
fi
exit 0
fi
if [[ "$JSON_OUTPUT" == "true" ]]; then
# JSON output
echo '{"plans":['
first=true
for file in "${PLAN_FILES[@]}"; do
status=$(get_frontmatter "$file" "status" "unknown")
[[ -n "$FILTER_STATUS" && "$status" != "$FILTER_STATUS" ]] && continue
title=$(get_frontmatter "$file" "title" "$(basename "$file" .md)")
iteration=$(get_frontmatter "$file" "iteration" "0")
target=$(get_frontmatter "$file" "target_iterations" "")
beads_rev=$(get_frontmatter "$file" "beads_revision" "0")
updated=$(get_frontmatter "$file" "updated" "")
project=$(echo "$file" | sed "s|${SEARCH_ROOT}/||" | cut -d/ -f1)
# Count feedback files
feedback_count=$(ls "${file%.md}".feedback-*.md 2>/dev/null | wc -l | tr -d ' ')
[[ "$first" != "true" ]] && echo ","
first=false
cat <<ENTRY
{"path":"$file","project":"$project","title":"$title","status":"$status","iteration":$iteration,"target_iterations":${target:-null},"beads_revision":$beads_rev,"feedback_files":$feedback_count,"updated":"$updated"}
ENTRY
done
echo ']}'
else
# Human-readable table
echo ""
echo " Plan Status Dashboard"
echo " $(date '+%Y-%m-%d %H:%M')"
echo " ════════════════════════════════════════════════════════════════════════"
printf " %-18s %-28s %-12s %-14s %s\n" "Project" "Plan" "Status" "Iterations" "Updated"
echo " ────────────────────────────────────────────────────────────────────────"
# Counters
total=0
by_status_drafting=0
by_status_iterating=0
by_status_splitting=0
by_status_refining=0
by_status_ready=0
by_status_implementing=0
by_status_completed=0
for file in "${PLAN_FILES[@]}"; do
status=$(get_frontmatter "$file" "status" "unknown")
[[ -n "$FILTER_STATUS" && "$status" != "$FILTER_STATUS" ]] && continue
title=$(get_frontmatter "$file" "title" "")
iteration=$(get_frontmatter "$file" "iteration" "0")
target=$(get_frontmatter "$file" "target_iterations" "")
beads_rev=$(get_frontmatter "$file" "beads_revision" "0")
updated=$(get_frontmatter "$file" "updated" "")
project=$(echo "$file" | sed "s|${SEARCH_ROOT}/||" | cut -d/ -f1)
plan_name=$(basename "$file" .md)
# Truncate long names
[[ ${#project} -gt 18 ]] && project="${project:0:15}..."
[[ ${#plan_name} -gt 28 ]] && plan_name="${plan_name:0:25}..."
# Format iteration progress
if [[ -n "$target" && "$target" != "0" ]]; then
iter_display="${iteration}/${target}"
else
iter_display="${iteration}"
fi
# Add beads revision if applicable
if [[ "$beads_rev" != "0" ]]; then
iter_display="${iter_display} br:${beads_rev}"
fi
printf " %-18s %-28s %-12s %-14s %s\n" \
"$project" "$plan_name" "$status" "$iter_display" "$updated"
total=$((total + 1))
case "$status" in
drafting) by_status_drafting=$((by_status_drafting + 1)) ;;
iterating) by_status_iterating=$((by_status_iterating + 1)) ;;
splitting) by_status_splitting=$((by_status_splitting + 1)) ;;
refining) by_status_refining=$((by_status_refining + 1)) ;;
ready) by_status_ready=$((by_status_ready + 1)) ;;
implementing) by_status_implementing=$((by_status_implementing + 1)) ;;
completed) by_status_completed=$((by_status_completed + 1)) ;;
esac
done
echo " ════════════════════════════════════════════════════════════════════════"
echo ""
# Summary line
summary_parts=()
[[ $by_status_drafting -gt 0 ]] && summary_parts+=("${by_status_drafting} drafting")
[[ $by_status_iterating -gt 0 ]] && summary_parts+=("${by_status_iterating} iterating")
[[ $by_status_splitting -gt 0 ]] && summary_parts+=("${by_status_splitting} splitting")
[[ $by_status_refining -gt 0 ]] && summary_parts+=("${by_status_refining} refining")
[[ $by_status_ready -gt 0 ]] && summary_parts+=("${by_status_ready} ready")
[[ $by_status_implementing -gt 0 ]] && summary_parts+=("${by_status_implementing} implementing")
[[ $by_status_completed -gt 0 ]] && summary_parts+=("${by_status_completed} completed")
IFS=", "
echo " ${total} plans: ${summary_parts[*]}"
echo ""
# Pipeline visualization
echo " Pipeline: drafting -> iterating -> splitting -> refining -> ready -> implementing -> completed"
echo ""
fi

72
install.sh Executable file
View File

@@ -0,0 +1,72 @@
#!/usr/bin/env bash
# install.sh — Set up plan-tools on this machine
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
BIN_DIR="$SCRIPT_DIR/bin"
echo "plan-tools setup"
echo "════════════════"
echo ""
# Make scripts executable
chmod +x "$BIN_DIR/plan-refine"
chmod +x "$BIN_DIR/plan-status"
echo "Made scripts executable."
# Check for Oracle
if command -v oracle &>/dev/null; then
echo "Oracle: $(oracle --version 2>/dev/null || echo 'installed')"
else
echo "Oracle not found. Installing via npm..."
npm install -g @steipete/oracle
fi
# Set up Oracle browser profile (first-time login)
ORACLE_PROFILE="$HOME/.oracle/browser-profile"
if [[ ! -d "$ORACLE_PROFILE" ]]; then
echo ""
echo "Oracle needs a one-time browser login to ChatGPT."
echo "This will open Chrome — log into ChatGPT, then close the browser."
read -rp "Run browser login now? (y/N) " confirm
if [[ "$confirm" == "y" || "$confirm" == "Y" ]]; then
oracle --engine browser \
--browser-manual-login \
--browser-keep-browser \
-p "Hello, this is a test. Reply with: Oracle setup complete." \
--write-output /dev/null || true
echo "Browser profile saved."
else
echo "Skipped. Run this later:"
echo " oracle --engine browser --browser-manual-login --browser-keep-browser -p 'test'"
fi
fi
# Symlink to PATH
echo ""
TARGET_BIN="$HOME/.local/bin"
mkdir -p "$TARGET_BIN"
for script in plan-refine plan-status; do
target="$TARGET_BIN/$script"
if [[ -L "$target" ]]; then
rm "$target"
fi
ln -s "$BIN_DIR/$script" "$target"
echo "Linked: $script -> $target"
done
# Check PATH
if ! echo "$PATH" | tr ':' '\n' | grep -q "^${TARGET_BIN}$"; then
echo ""
echo "NOTE: $TARGET_BIN is not in your PATH."
echo "Add this to your shell profile:"
echo " export PATH=\"\$HOME/.local/bin:\$PATH\""
fi
echo ""
echo "Setup complete. Available commands:"
echo " plan-refine <plan.md> Run one ChatGPT evaluation iteration"
echo " plan-refine <plan.md> --init Add plan frontmatter to a file"
echo " plan-status Show all plans and their pipeline stage"
echo ""

103
lib/frontmatter.sh Normal file
View File

@@ -0,0 +1,103 @@
#!/usr/bin/env bash
# frontmatter.sh — Read/write YAML frontmatter in plan files
# Read a value from YAML frontmatter
# Usage: get_frontmatter <file> <key> [default]
get_frontmatter() {
local file="$1"
local key="$2"
local default="${3:-}"
if ! head -1 "$file" | grep -q '^---$'; then
echo "$default"
return
fi
local value
value=$(awk '/^---$/{n++; next} n==1{print}' "$file" \
| grep "^${key}:" \
| head -1 \
| sed "s/^${key}: *//; s/^ *//; s/ *$//; s/^[\"']//; s/[\"']$//")
echo "${value:-$default}"
}
# Set a value in YAML frontmatter (adds frontmatter if missing)
# Usage: set_frontmatter <file> <key> <value>
set_frontmatter() {
local file="$1"
local key="$2"
local value="$3"
# If file has no frontmatter, add it
if ! head -1 "$file" | grep -q '^---$'; then
local tmp
tmp=$(mktemp)
{
echo "---"
echo "${key}: ${value}"
echo "---"
cat "$file"
} > "$tmp"
mv "$tmp" "$file"
return
fi
if grep -q "^${key}:" "$file"; then
# Replace existing key (using | as delimiter to avoid path issues)
sed -i '' "s|^${key}:.*|${key}: ${value}|" "$file"
else
# Insert before closing --- (second occurrence)
awk -v key="$key" -v val="$value" '
BEGIN { count=0; inserted=0 }
/^---$/ { count++ }
count == 2 && !inserted { print key ": " val; inserted=1 }
{ print }
' "$file" > "${file}.tmp" && mv "${file}.tmp" "$file"
fi
}
# Check if a file has plan frontmatter
# Usage: is_plan_file <file>
is_plan_file() {
local file="$1"
[[ -f "$file" ]] && head -20 "$file" | grep -q "^plan: true"
}
# Initialize frontmatter on a plan file that doesn't have it
# Usage: init_frontmatter <file> [title]
init_frontmatter() {
local file="$1"
local title="${2:-$(basename "$file" .md)}"
local today
today=$(date +%Y-%m-%d)
if head -1 "$file" | grep -q '^---$'; then
# Has frontmatter, just ensure plan fields exist
set_frontmatter "$file" "plan" "true"
[[ -z "$(get_frontmatter "$file" "status")" ]] && set_frontmatter "$file" "status" "drafting"
[[ -z "$(get_frontmatter "$file" "iteration")" ]] && set_frontmatter "$file" "iteration" "0"
[[ -z "$(get_frontmatter "$file" "target_iterations")" ]] && set_frontmatter "$file" "target_iterations" "8"
[[ -z "$(get_frontmatter "$file" "created")" ]] && set_frontmatter "$file" "created" "$today"
set_frontmatter "$file" "updated" "$today"
else
# No frontmatter, add full block
local tmp
tmp=$(mktemp)
{
echo "---"
echo "plan: true"
echo "title: \"${title}\""
echo "status: drafting"
echo "iteration: 0"
echo "target_iterations: 8"
echo "beads_revision: 0"
echo "related_plans: []"
echo "created: ${today}"
echo "updated: ${today}"
echo "---"
echo ""
cat "$file"
} > "$tmp"
mv "$tmp" "$file"
fi
}

1
prompts/chatgpt-eval.md Normal file
View File

@@ -0,0 +1 @@
Carefully review this entire plan for me and come up with your best revisions in terms of better architecture, new features, changed features, etc. to make it better, more robust/reliable, more performant, more compelling/useful, etc. For each proposed change, give me your detailed analysis and rationale/justification for why it would make the project better along with the git-diff style change versus the original plan shown below.

View File

@@ -0,0 +1 @@
I asked ChatGPT to review your plan. I want you to REALLY carefully analyze their plan with an open mind and be intellectually honest about what they did that's better than your plan. Then I want you to come up with the best possible revisions to your plan (you should simply update your existing document for your original plan with the revisions) that artfully and skillfully blends the "best of all worlds" to create a true, ultimate, superior hybrid version of the plan that best achieves our stated goals and will work the best in real-world practice to solve the problems we are facing and our overarching goals while ensuring the extreme success of the enterprise as best as possible; you should provide me with a complete series of git-diff style changes to your original plan to turn it into the new, enhanced, much longer and detailed plan that integrates the best of all the plans with every good idea included.