#!/usr/bin/env zsh # Standalone plan-build launcher. This file is intentionally sourceable so its # command seams can be replaced by the test suite. _plan_build_usage() { print -r -- "Usage: plan_build [--yolo] [--prompt] [--brainstorm | --writing-plan] plan_build --architect [--new] [--yolo]" } _plan_build_require_cli() { local binary="$1" local label="$2" local resolved resolved="$(command -v "$binary" 2>/dev/null)" || resolved="" if [[ -n "$resolved" && -x "$resolved" ]]; then return 0 fi print -u2 -r -- "Error: ${label} ('${binary}') is not installed or not in your PATH." return 1 } _plan_build_confirm_indexing() { local project_root="$1" local reply if ! (: /dev/null; then print -u2 -r -- "Notice: No interactive terminal available; enhancing without project indexing." return 1 fi printf "Allow Auggie to index and use project context from '%s'? (y/N) " "$project_root" >/dev/tty if ! IFS= read -r reply | "$output_file" } _plan_build_platform() { command uname -s } _plan_build_start_auggie_script() { local platform="$1" log_file="$2" workspace="$3" cache_dir="$4" local auth_file="$5" prompt_file="$6" use_project_context="$7" shift 7 if [[ "$platform" == Darwin ]]; then command script -q -t 0 "$log_file" auggie "$@" /dev/null 2>&1 & else AUGGIE_WORKSPACE="$workspace" AUGGIE_CACHE_DIR="$cache_dir" \ AUGGIE_AUTH_FILE="$auth_file" AUGGIE_PROMPT_FILE="$prompt_file" \ AUGGIE_INDEXING="$use_project_context" \ command script -q -f -O "$log_file" -c ' if [ "$AUGGIE_INDEXING" = 1 ]; then exec auggie --print --enhance-prompt --workspace-root "$AUGGIE_WORKSPACE" --augment-cache-dir "$AUGGIE_CACHE_DIR" --augment-session-json "$AUGGIE_AUTH_FILE" --allow-indexing --wait-for-indexing --dont-save-session --instruction-file "$AUGGIE_PROMPT_FILE" else exec auggie --print --enhance-prompt --no-discover-workspaces --workspace-root "$AUGGIE_WORKSPACE" --augment-cache-dir "$AUGGIE_CACHE_DIR" --augment-session-json "$AUGGIE_AUTH_FILE" --dont-save-session --instruction-file "$AUGGIE_PROMPT_FILE" fi ' /dev/null 2>&1 & fi REPLY=$! } _plan_build_enhance_prompt() { emulate -L zsh setopt localtraps local prompt="$1" local output_file="$2" local run_dir prompt_file workspace cache_dir auth_file log_file parsed_file local project_root use_project_context timeout_seconds script_pid= waited child_status=0 local -a auggie_args trap 'return 130' INT trap 'return 143' TERM trap 'return 129' HUP if [[ -z "$prompt" ]]; then print -u2 -r -- "Error: Prompt was empty." return 1 fi if [[ ! -s "$HOME/.augment/session.json" ]]; then print -u2 -r -- "Error: Auggie session file not found. Run 'auggie login' first." return 1 fi run_dir="$(mktemp -d "${TMPDIR:-/tmp}/plan-build-auggie.XXXXXX")" || return 1 trap "rm -rf -- ${(q)run_dir}" EXIT prompt_file="$run_dir/prompt.txt" workspace="$run_dir/workspace" cache_dir="$run_dir/cache" auth_file="$HOME/.augment/session.json" log_file="$run_dir/auggie.log" parsed_file="$run_dir/enhanced.txt" mkdir -p "$workspace" "$cache_dir" || { return 1 } print -r -- "$prompt" >| "$prompt_file" || return 1 project_root="$(command git rev-parse --show-toplevel 2>/dev/null)" || project_root="$PWD" use_project_context=0 timeout_seconds=90 if _plan_build_confirm_indexing "$project_root"; then use_project_context=1 timeout_seconds=300 workspace="$project_root" cache_dir="$HOME/.augment" print -u2 -r -- "Indexing approved; enhancing with project context from '${project_root}'." else print -u2 -r -- "Enhancing without project indexing." fi auggie_args=(--print --enhance-prompt --workspace-root "$workspace" --augment-cache-dir "$cache_dir" --augment-session-json "$auth_file" --dont-save-session --instruction-file "$prompt_file") if (( use_project_context )); then auggie_args+=(--allow-indexing --wait-for-indexing) else auggie_args+=(--no-discover-workspaces) fi _plan_build_start_auggie_script "$(_plan_build_platform)" "$log_file" \ "$workspace" "$cache_dir" "$auth_file" "$prompt_file" \ "$use_project_context" "${auggie_args[@]}" || return 1 script_pid="$REPLY" trap "kill ${(q)script_pid} >/dev/null 2>&1 wait ${(q)script_pid} >/dev/null 2>&1 rm -rf -- ${(q)run_dir}" EXIT waited=0 while kill -0 "$script_pid" >/dev/null 2>&1; do if command grep -aq "🤖" "$log_file" 2>/dev/null || command grep -aq "Tool call:" "$log_file" 2>/dev/null; then kill "$script_pid" >/dev/null 2>&1 || true wait "$script_pid" >/dev/null 2>&1 || true script_pid= trap "rm -rf -- ${(q)run_dir}" EXIT child_status=0 break fi if (( waited >= timeout_seconds )); then kill "$script_pid" >/dev/null 2>&1 || true wait "$script_pid" >/dev/null 2>&1 || true script_pid= trap "rm -rf -- ${(q)run_dir}" EXIT print -u2 -r -- "Error: Timed out waiting for Auggie to enhance the prompt." return 124 fi sleep 1 waited=$((waited + 1)) done if [[ -n "$script_pid" ]]; then wait "$script_pid" child_status=$? script_pid= trap "rm -rf -- ${(q)run_dir}" EXIT fi if (( child_status != 0 )); then print -u2 -r -- "Error: Auggie process failed with status ${child_status}." return "$child_status" fi _plan_build_parse_auggie_output "$log_file" "$parsed_file" if [[ ! -s "$parsed_file" ]]; then print -u2 -r -- "Error: Auggie did not return an enhanced prompt." return 1 fi command cp "$parsed_file" "$output_file" || { print -u2 -r -- "Error: Unable to save the enhanced prompt." return 1 } } _plan_build_confirm_enhanced_prompt() { local reply if ! (: /dev/null; then print -r -- "❌ Error: Cannot review the enhanced prompt without an interactive terminal; Claude was not launched." return 2 fi printf "Proceed with this enhanced prompt? (y/N) " >/dev/tty if ! IFS= read -r reply /dev/tty print -r -- "🛑 Review cancelled; Claude was not launched." return 2 fi [[ "$reply" == (y|Y|yes|YES|Yes) ]] } _plan_build_superpowers_state() { awk ' BEGIN { RS = "}"; state = "missing"; printed = 0 } /"id"[[:space:]]*:[[:space:]]*"superpowers@claude-plugins-official"/ { state = "installed" if ($0 ~ /"enabled"[[:space:]]*:[[:space:]]*true/) state = "enabled" else if ($0 ~ /"enabled"[[:space:]]*:[[:space:]]*false/) state = "disabled" print state printed = 1 exit } END { if (!printed) print state } ' } _plan_build_superpowers_preflight() { local plugin_json plugin_state case "${CLAUDE_CODE_SAFE_MODE:-}" in 1|true|TRUE|yes|YES|on|ON) print -r -- "❌ Error: Claude Code safe mode disables Superpowers." print -r -- "Unset CLAUDE_CODE_SAFE_MODE before using --brainstorm or --writing-plan." return 1 ;; esac if ! plugin_json="$(command claude plugin list --json 2>/dev/null)"; then print -r -- "❌ Error: Unable to inspect Claude Code plugins." print -r -- "Run 'claude plugin list' to diagnose the problem." return 1 fi plugin_state="$(print -r -- "$plugin_json" | _plan_build_superpowers_state)" case "$plugin_state" in enabled) return 0 ;; disabled) print -r -- "❌ Error: Claude Code Superpowers is installed but disabled." print -r -- "Enable it with: claude plugin enable superpowers@claude-plugins-official" ;; missing) print -r -- "❌ Error: Claude Code Superpowers is required for --brainstorm and --writing-plan." print -r -- "Install it in Claude Code with: /plugin install superpowers@claude-plugins-official" ;; *) print -r -- "❌ Error: Unable to determine Claude Code Superpowers status." print -r -- "Run 'claude plugin list' to diagnose the problem." ;; esac return 1 } _plan_build_is_worktree() { local inside_work_tree inside_work_tree="$(command git rev-parse --is-inside-work-tree 2>/dev/null)" || return 1 [[ "$inside_work_tree" == true ]] } _plan_build_has_tty() { (: /dev/null } _plan_build_architect_preflight() { local skill_path="$HOME/.claude/skills/plan-build-architect/SKILL.md" if ! _plan_build_has_tty; then print -r -- "❌ Error: Architect mode requires an interactive terminal." print -r -- "Run plan_build --architect from an interactive terminal." return 1 fi _plan_build_require_cli claude "Claude Code CLI" || return 1 _plan_build_require_cli codex "Codex CLI" || return 1 _plan_build_require_cli coderabbit "CodeRabbit CLI" || return 1 _plan_build_require_cli git "Git CLI" || return 1 if ! _plan_build_is_worktree; then print -r -- "❌ Error: Architect mode must run inside a Git worktree." print -r -- "Change to a Git worktree, then retry plan_build --architect." return 1 fi if [[ ! -r "$skill_path" || ! -s "$skill_path" ]]; then print -r -- "❌ Error: Architect skill is missing, unreadable, or empty: $skill_path" print -r -- "Install a readable, non-empty plan-build-architect skill at that path." return 1 fi } _plan_build_launch_claude() { local use_yolo="$1" shift if (( use_yolo )); then command claude --dangerously-skip-permissions "$@" else command claude "$@" fi } _plan_build_read_payload() { local line payload="" while IFS= read -r line; do [[ "$line" == EOF ]] && break if [[ -z "$payload" ]]; then payload="$line" else payload+=$'\n'"$line" fi done print -r -- "$payload" } plan_build() { emulate -L zsh setopt localtraps local use_yolo=0 use_architect=0 use_new=0 enhance_payload=0 local planning_mode="standard" local payload enhanced_file= confirm_status enhance_status planning_instruction architect_start_mode trap 'return 130' INT trap 'return 143' TERM trap 'return 129' HUP while (( $# )); do case "$1" in --yolo) (( use_yolo )) && { print -r -- "Error: Duplicate argument: --yolo" _plan_build_usage return 1 } use_yolo=1 ;; --architect) (( use_architect )) && { print -r -- "Error: Duplicate argument: --architect" _plan_build_usage return 1 } use_architect=1 ;; --new) (( use_new )) && { print -r -- "Error: Duplicate argument: --new" _plan_build_usage return 1 } use_new=1 ;; --prompt) (( enhance_payload )) && { print -r -- "Error: Duplicate argument: --prompt" _plan_build_usage return 1 } enhance_payload=1 ;; --brainstorm) [[ "$planning_mode" == brainstorm ]] && { print -r -- "Error: Duplicate argument: --brainstorm" _plan_build_usage return 1 } [[ "$planning_mode" != standard ]] && { print -r -- "Error: --brainstorm and --writing-plan are mutually exclusive." _plan_build_usage return 1 } planning_mode="brainstorm" ;; --writing-plan) [[ "$planning_mode" == writing-plan ]] && { print -r -- "Error: Duplicate argument: --writing-plan" _plan_build_usage return 1 } [[ "$planning_mode" != standard ]] && { print -r -- "Error: --brainstorm and --writing-plan are mutually exclusive." _plan_build_usage return 1 } planning_mode="writing-plan" ;; *) print -r -- "Error: Unknown argument: $1" _plan_build_usage return 1 ;; esac shift done if (( use_new && ! use_architect )); then print -r -- "Error: --new requires --architect." _plan_build_usage return 1 fi if (( use_architect && enhance_payload )) || { (( use_architect )) && [[ "$planning_mode" != standard ]]; }; then print -r -- "Error: --architect may combine only with --new and --yolo." _plan_build_usage return 1 fi if (( use_architect )); then _plan_build_architect_preflight || return 1 else _plan_build_require_cli claude "Claude Code CLI" || return 1 if [[ "$planning_mode" != standard ]]; then _plan_build_superpowers_preflight || return 1 fi _plan_build_require_cli codex "Codex CLI" || return 1 _plan_build_require_cli coderabbit "CodeRabbit CLI" || return 1 if (( enhance_payload )); then _plan_build_require_cli auggie "Auggie CLI" || return 1 _plan_build_require_cli script "script utility" || return 1 _plan_build_require_cli perl "Perl" || return 1 fi fi print -r -- "📥 Reading payload... Type 'EOF' on a new line and press Enter when finished." payload="$(_plan_build_read_payload)" if [[ -z "$payload" ]]; then print -r -- "❌ Error: Payload was empty." return 1 fi if (( enhance_payload )); then enhanced_file="$(mktemp "${TMPDIR:-/tmp}/plan-build-enhanced.XXXXXX")" || return 1 trap "rm -f -- ${(q)enhanced_file}" EXIT print -r -- "✨ Enhancing payload with Auggie..." _plan_build_enhance_prompt "$payload" "$enhanced_file" enhance_status=$? if (( enhance_status != 0 )); then print -r -- "❌ Error: Prompt enhancement failed; Claude was not launched." return "$enhance_status" fi payload="$(<"$enhanced_file")" rm -f -- "$enhanced_file" enhanced_file= trap - EXIT if [[ -z "$payload" ]]; then print -r -- "❌ Error: Enhanced payload was empty; Claude was not launched." return 1 fi printf '\n%s\n%s\n%s\n\n' \ "━━━━━━━━━━━━━━━━ Auggie enhanced prompt ━━━━━━━━━━━━━━━━" \ "$payload" \ "━━━━━━━━━━━━━━━━ End enhanced prompt ━━━━━━━━━━━━━━━━━" _plan_build_confirm_enhanced_prompt confirm_status=$? if (( confirm_status == 2 )); then return 1 elif (( confirm_status != 0 )); then print -r -- "🛑 Enhanced prompt not approved; Claude was not launched." return 0 fi print -r -- "✅ Enhanced prompt approved." fi if (( use_architect )); then if (( use_new )); then architect_start_mode="Start mode: archive-and-start-new. Explicitly archive the prior planning state as the skill directs, then begin a new architecture plan." else architect_start_mode="Start mode: safe-resume-detection. Safely detect whether an existing architecture planning session should be resumed; do not archive or replace it automatically." fi print -r -- "🚀 Launching Claude Code in architect mode..." _plan_build_launch_claude "$use_yolo" "Read \`~/.claude/skills/plan-build-architect/SKILL.md\` and follow it strictly, including every gate even when permissive Claude execution is active. Stay in the documentation-first architect/orchestrator role: produce and coordinate documentation and planning only, and do not implement the requirement. $architect_start_mode Use the following payload as the initial requirement: $payload" return $? fi case "$planning_mode" in brainstorm) planning_instruction="Planning mode: brainstorm. Use the superpowers:brainstorming skill, including its approval gates and transition to superpowers:writing-plans. After the implementation plan is saved, return to the plan-build workflow for Codex plan review before implementation." ;; writing-plan) planning_instruction="Planning mode: writing-plan. Use the superpowers:writing-plans skill with the payload as the requirements. After the implementation plan is saved, return to the plan-build workflow for Codex plan review before implementation." ;; *) planning_instruction="Planning mode: standard. Create the workflow's normal short implementation plan." ;; esac print -r -- "🚀 Launching Claude Code with your multi-agent workflow (${planning_mode} planning)..." _plan_build_launch_claude "$use_yolo" "Please read \`~/.claude/skills/plan-build/SKILL.md\` and strictly follow the 8-step multi-agent workflow to implement the following task. $planning_instruction $payload" } _plan_build_main() { plan_build "$@" } if [[ "${ZSH_EVAL_CONTEXT:-}" == toplevel ]]; then _plan_build_main "$@" fi