821 lines
23 KiB
Bash
821 lines
23 KiB
Bash
#!/usr/bin/env bash
|
|
|
|
# enable_core.sh - Shared logic for ralph enable commands
|
|
# Provides idempotency checks, safe file creation, and project detection
|
|
#
|
|
# Used by:
|
|
# - ralph_enable.sh (interactive wizard)
|
|
# - ralph_enable_ci.sh (non-interactive CI version)
|
|
|
|
# Exit codes - specific codes for different failure types
|
|
export ENABLE_SUCCESS=0 # Successful completion
|
|
export ENABLE_ERROR=1 # General error
|
|
export ENABLE_ALREADY_ENABLED=2 # Ralph already enabled (use --force)
|
|
export ENABLE_INVALID_ARGS=3 # Invalid command line arguments
|
|
export ENABLE_FILE_NOT_FOUND=4 # Required file not found (e.g., PRD file)
|
|
export ENABLE_DEPENDENCY_MISSING=5 # Required dependency missing (e.g., jq for --json)
|
|
export ENABLE_PERMISSION_DENIED=6 # Cannot create files/directories
|
|
|
|
# Colors (can be disabled for non-interactive mode)
|
|
export ENABLE_USE_COLORS="${ENABLE_USE_COLORS:-true}"
|
|
|
|
_color() {
|
|
if [[ "$ENABLE_USE_COLORS" == "true" ]]; then
|
|
echo -e "$1"
|
|
else
|
|
echo -e "$2"
|
|
fi
|
|
}
|
|
|
|
# Color codes
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
CYAN='\033[0;36m'
|
|
NC='\033[0m'
|
|
|
|
# Logging function
|
|
enable_log() {
|
|
local level=$1
|
|
local message=$2
|
|
local color=""
|
|
|
|
case $level in
|
|
"INFO") color=$BLUE ;;
|
|
"WARN") color=$YELLOW ;;
|
|
"ERROR") color=$RED ;;
|
|
"SUCCESS") color=$GREEN ;;
|
|
"SKIP") color=$CYAN ;;
|
|
esac
|
|
|
|
if [[ "$ENABLE_USE_COLORS" == "true" ]]; then
|
|
echo -e "${color}[$level]${NC} $message"
|
|
else
|
|
echo "[$level] $message"
|
|
fi
|
|
}
|
|
|
|
# =============================================================================
|
|
# IDEMPOTENCY CHECKS
|
|
# =============================================================================
|
|
|
|
# check_existing_ralph - Check if .ralph directory exists and its state
|
|
#
|
|
# Returns:
|
|
# 0 - No .ralph directory, safe to proceed
|
|
# 1 - .ralph exists but incomplete (partial setup)
|
|
# 2 - .ralph exists and fully initialized
|
|
#
|
|
# Outputs:
|
|
# Sets global RALPH_STATE: "none" | "partial" | "complete"
|
|
# Sets global RALPH_MISSING_FILES: array of missing files if partial
|
|
#
|
|
check_existing_ralph() {
|
|
RALPH_STATE="none"
|
|
RALPH_MISSING_FILES=()
|
|
|
|
if [[ ! -d ".ralph" ]]; then
|
|
RALPH_STATE="none"
|
|
return 0
|
|
fi
|
|
|
|
# Check for required files
|
|
local required_files=(
|
|
".ralph/PROMPT.md"
|
|
".ralph/@fix_plan.md"
|
|
".ralph/@AGENT.md"
|
|
)
|
|
|
|
local missing=()
|
|
local found=0
|
|
|
|
for file in "${required_files[@]}"; do
|
|
if [[ -f "$file" ]]; then
|
|
found=$((found + 1))
|
|
else
|
|
missing+=("$file")
|
|
fi
|
|
done
|
|
|
|
RALPH_MISSING_FILES=("${missing[@]}")
|
|
|
|
if [[ $found -eq 0 ]]; then
|
|
RALPH_STATE="none"
|
|
return 0
|
|
elif [[ ${#missing[@]} -gt 0 ]]; then
|
|
RALPH_STATE="partial"
|
|
return 1
|
|
else
|
|
RALPH_STATE="complete"
|
|
return 2
|
|
fi
|
|
}
|
|
|
|
# is_ralph_enabled - Simple check if Ralph is fully enabled
|
|
#
|
|
# Returns:
|
|
# 0 - Ralph is fully enabled
|
|
# 1 - Ralph is not enabled or only partially
|
|
#
|
|
is_ralph_enabled() {
|
|
check_existing_ralph || true
|
|
[[ "$RALPH_STATE" == "complete" ]]
|
|
}
|
|
|
|
# =============================================================================
|
|
# SAFE FILE OPERATIONS
|
|
# =============================================================================
|
|
|
|
# safe_create_file - Create a file only if it doesn't exist (or force overwrite)
|
|
#
|
|
# Parameters:
|
|
# $1 (target) - Target file path
|
|
# $2 (content) - Content to write (can be empty string)
|
|
#
|
|
# Environment:
|
|
# ENABLE_FORCE - If "true", overwrites existing files instead of skipping
|
|
#
|
|
# Returns:
|
|
# 0 - File created/overwritten successfully
|
|
# 1 - File already exists (skipped, only when ENABLE_FORCE is not true)
|
|
# 2 - Error creating file
|
|
#
|
|
# Side effects:
|
|
# Logs [CREATE], [OVERWRITE], or [SKIP] message
|
|
#
|
|
safe_create_file() {
|
|
local target=$1
|
|
local content=$2
|
|
local force="${ENABLE_FORCE:-false}"
|
|
|
|
if [[ -f "$target" ]]; then
|
|
if [[ "$force" == "true" ]]; then
|
|
# Force mode: overwrite existing file
|
|
enable_log "INFO" "Overwriting $target (--force)"
|
|
else
|
|
# Normal mode: skip existing file
|
|
enable_log "SKIP" "$target already exists"
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
# Create parent directory if needed
|
|
local parent_dir
|
|
parent_dir=$(dirname "$target")
|
|
if [[ ! -d "$parent_dir" ]]; then
|
|
if ! mkdir -p "$parent_dir" 2>/dev/null; then
|
|
enable_log "ERROR" "Failed to create directory: $parent_dir"
|
|
return 2
|
|
fi
|
|
fi
|
|
|
|
# Write content to file using printf to avoid shell injection
|
|
# printf '%s\n' is safer than echo for arbitrary content (handles backslashes, -n, etc.)
|
|
if printf '%s\n' "$content" > "$target" 2>/dev/null; then
|
|
if [[ -f "$target" ]] && [[ "$force" == "true" ]]; then
|
|
enable_log "SUCCESS" "Overwrote $target"
|
|
else
|
|
enable_log "SUCCESS" "Created $target"
|
|
fi
|
|
return 0
|
|
else
|
|
enable_log "ERROR" "Failed to create: $target"
|
|
return 2
|
|
fi
|
|
}
|
|
|
|
# safe_create_dir - Create a directory only if it doesn't exist
|
|
#
|
|
# Parameters:
|
|
# $1 (target) - Target directory path
|
|
#
|
|
# Returns:
|
|
# 0 - Directory created or already exists
|
|
# 1 - Error creating directory
|
|
#
|
|
safe_create_dir() {
|
|
local target=$1
|
|
|
|
if [[ -d "$target" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
if mkdir -p "$target" 2>/dev/null; then
|
|
enable_log "SUCCESS" "Created directory: $target"
|
|
return 0
|
|
else
|
|
enable_log "ERROR" "Failed to create directory: $target"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# =============================================================================
|
|
# DIRECTORY STRUCTURE
|
|
# =============================================================================
|
|
|
|
# create_ralph_structure - Create the .ralph/ directory structure
|
|
#
|
|
# Creates:
|
|
# .ralph/
|
|
# .ralph/specs/
|
|
# .ralph/examples/
|
|
# .ralph/logs/
|
|
# .ralph/docs/generated/
|
|
#
|
|
# Returns:
|
|
# 0 - Structure created successfully
|
|
# 1 - Error creating structure
|
|
#
|
|
create_ralph_structure() {
|
|
local dirs=(
|
|
".ralph"
|
|
".ralph/specs"
|
|
".ralph/examples"
|
|
".ralph/logs"
|
|
".ralph/docs/generated"
|
|
)
|
|
|
|
for dir in "${dirs[@]}"; do
|
|
if ! safe_create_dir "$dir"; then
|
|
return 1
|
|
fi
|
|
done
|
|
|
|
return 0
|
|
}
|
|
|
|
# =============================================================================
|
|
# PROJECT DETECTION
|
|
# =============================================================================
|
|
|
|
# Exported detection results
|
|
export DETECTED_PROJECT_NAME=""
|
|
export DETECTED_PROJECT_TYPE=""
|
|
export DETECTED_FRAMEWORK=""
|
|
export DETECTED_BUILD_CMD=""
|
|
export DETECTED_TEST_CMD=""
|
|
export DETECTED_RUN_CMD=""
|
|
|
|
# detect_project_context - Detect project type, name, and build commands
|
|
#
|
|
# Detects:
|
|
# - Project type: javascript, typescript, python, rust, go, unknown
|
|
# - Framework: nextjs, fastapi, express, etc.
|
|
# - Build/test/run commands based on detected tooling
|
|
#
|
|
# Sets globals:
|
|
# DETECTED_PROJECT_NAME - Project name (from package.json, folder, etc.)
|
|
# DETECTED_PROJECT_TYPE - Language/type
|
|
# DETECTED_FRAMEWORK - Framework if detected
|
|
# DETECTED_BUILD_CMD - Build command
|
|
# DETECTED_TEST_CMD - Test command
|
|
# DETECTED_RUN_CMD - Run/start command
|
|
#
|
|
detect_project_context() {
|
|
# Reset detection results
|
|
DETECTED_PROJECT_NAME=""
|
|
DETECTED_PROJECT_TYPE="unknown"
|
|
DETECTED_FRAMEWORK=""
|
|
DETECTED_BUILD_CMD=""
|
|
DETECTED_TEST_CMD=""
|
|
DETECTED_RUN_CMD=""
|
|
|
|
# Detect from package.json (JavaScript/TypeScript)
|
|
if [[ -f "package.json" ]]; then
|
|
DETECTED_PROJECT_TYPE="javascript"
|
|
|
|
# Check for TypeScript
|
|
if grep -q '"typescript"' package.json 2>/dev/null || \
|
|
[[ -f "tsconfig.json" ]]; then
|
|
DETECTED_PROJECT_TYPE="typescript"
|
|
fi
|
|
|
|
# Extract project name
|
|
if command -v jq &>/dev/null; then
|
|
DETECTED_PROJECT_NAME=$(jq -r '.name // empty' package.json 2>/dev/null)
|
|
else
|
|
# Fallback: grep for name field
|
|
DETECTED_PROJECT_NAME=$(grep -m1 '"name"' package.json | sed 's/.*: *"\([^"]*\)".*/\1/' 2>/dev/null)
|
|
fi
|
|
|
|
# Detect framework
|
|
if grep -q '"next"' package.json 2>/dev/null; then
|
|
DETECTED_FRAMEWORK="nextjs"
|
|
elif grep -q '"express"' package.json 2>/dev/null; then
|
|
DETECTED_FRAMEWORK="express"
|
|
elif grep -q '"react"' package.json 2>/dev/null; then
|
|
DETECTED_FRAMEWORK="react"
|
|
elif grep -q '"vue"' package.json 2>/dev/null; then
|
|
DETECTED_FRAMEWORK="vue"
|
|
fi
|
|
|
|
# Set build commands
|
|
DETECTED_BUILD_CMD="npm run build"
|
|
DETECTED_TEST_CMD="npm test"
|
|
DETECTED_RUN_CMD="npm start"
|
|
|
|
# Check for yarn
|
|
if [[ -f "yarn.lock" ]]; then
|
|
DETECTED_BUILD_CMD="yarn build"
|
|
DETECTED_TEST_CMD="yarn test"
|
|
DETECTED_RUN_CMD="yarn start"
|
|
fi
|
|
|
|
# Check for pnpm
|
|
if [[ -f "pnpm-lock.yaml" ]]; then
|
|
DETECTED_BUILD_CMD="pnpm build"
|
|
DETECTED_TEST_CMD="pnpm test"
|
|
DETECTED_RUN_CMD="pnpm start"
|
|
fi
|
|
|
|
# Detect from pyproject.toml or setup.py (Python)
|
|
elif [[ -f "pyproject.toml" ]] || [[ -f "setup.py" ]]; then
|
|
DETECTED_PROJECT_TYPE="python"
|
|
|
|
# Extract project name from pyproject.toml
|
|
if [[ -f "pyproject.toml" ]]; then
|
|
DETECTED_PROJECT_NAME=$(grep -m1 '^name' pyproject.toml | sed 's/.*= *"\([^"]*\)".*/\1/' 2>/dev/null)
|
|
|
|
# Detect framework
|
|
if grep -q 'fastapi' pyproject.toml 2>/dev/null; then
|
|
DETECTED_FRAMEWORK="fastapi"
|
|
elif grep -q 'django' pyproject.toml 2>/dev/null; then
|
|
DETECTED_FRAMEWORK="django"
|
|
elif grep -q 'flask' pyproject.toml 2>/dev/null; then
|
|
DETECTED_FRAMEWORK="flask"
|
|
fi
|
|
fi
|
|
|
|
# Set build commands (prefer uv if detected)
|
|
if [[ -f "uv.lock" ]] || command -v uv &>/dev/null; then
|
|
DETECTED_BUILD_CMD="uv sync"
|
|
DETECTED_TEST_CMD="uv run pytest"
|
|
DETECTED_RUN_CMD="uv run python -m ${DETECTED_PROJECT_NAME:-main}"
|
|
else
|
|
DETECTED_BUILD_CMD="pip install -e ."
|
|
DETECTED_TEST_CMD="pytest"
|
|
DETECTED_RUN_CMD="python -m ${DETECTED_PROJECT_NAME:-main}"
|
|
fi
|
|
|
|
# Detect from Cargo.toml (Rust)
|
|
elif [[ -f "Cargo.toml" ]]; then
|
|
DETECTED_PROJECT_TYPE="rust"
|
|
DETECTED_PROJECT_NAME=$(grep -m1 '^name' Cargo.toml | sed 's/.*= *"\([^"]*\)".*/\1/' 2>/dev/null)
|
|
DETECTED_BUILD_CMD="cargo build"
|
|
DETECTED_TEST_CMD="cargo test"
|
|
DETECTED_RUN_CMD="cargo run"
|
|
|
|
# Detect from go.mod (Go)
|
|
elif [[ -f "go.mod" ]]; then
|
|
DETECTED_PROJECT_TYPE="go"
|
|
DETECTED_PROJECT_NAME=$(head -1 go.mod | sed 's/module //' 2>/dev/null)
|
|
DETECTED_BUILD_CMD="go build"
|
|
DETECTED_TEST_CMD="go test ./..."
|
|
DETECTED_RUN_CMD="go run ."
|
|
fi
|
|
|
|
# Fallback project name to folder name
|
|
if [[ -z "$DETECTED_PROJECT_NAME" ]]; then
|
|
DETECTED_PROJECT_NAME=$(basename "$(pwd)")
|
|
fi
|
|
}
|
|
|
|
# detect_git_info - Detect git repository information
|
|
#
|
|
# Sets globals:
|
|
# DETECTED_GIT_REPO - true if in git repo
|
|
# DETECTED_GIT_REMOTE - Remote URL (origin)
|
|
# DETECTED_GIT_GITHUB - true if GitHub remote
|
|
#
|
|
export DETECTED_GIT_REPO="false"
|
|
export DETECTED_GIT_REMOTE=""
|
|
export DETECTED_GIT_GITHUB="false"
|
|
|
|
detect_git_info() {
|
|
DETECTED_GIT_REPO="false"
|
|
DETECTED_GIT_REMOTE=""
|
|
DETECTED_GIT_GITHUB="false"
|
|
|
|
# Check if in git repo
|
|
if git rev-parse --git-dir &>/dev/null; then
|
|
DETECTED_GIT_REPO="true"
|
|
|
|
# Get remote URL
|
|
DETECTED_GIT_REMOTE=$(git remote get-url origin 2>/dev/null || echo "")
|
|
|
|
# Check if GitHub
|
|
if [[ "$DETECTED_GIT_REMOTE" == *"github.com"* ]]; then
|
|
DETECTED_GIT_GITHUB="true"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# detect_task_sources - Detect available task sources
|
|
#
|
|
# Sets globals:
|
|
# DETECTED_BEADS_AVAILABLE - true if .beads directory exists
|
|
# DETECTED_GITHUB_AVAILABLE - true if GitHub remote detected
|
|
# DETECTED_PRD_FILES - Array of potential PRD files found
|
|
#
|
|
export DETECTED_BEADS_AVAILABLE="false"
|
|
export DETECTED_GITHUB_AVAILABLE="false"
|
|
declare -a DETECTED_PRD_FILES=()
|
|
|
|
detect_task_sources() {
|
|
DETECTED_BEADS_AVAILABLE="false"
|
|
DETECTED_GITHUB_AVAILABLE="false"
|
|
DETECTED_PRD_FILES=()
|
|
|
|
# Check for beads
|
|
if [[ -d ".beads" ]]; then
|
|
DETECTED_BEADS_AVAILABLE="true"
|
|
fi
|
|
|
|
# Check for GitHub (reuse git detection)
|
|
detect_git_info
|
|
DETECTED_GITHUB_AVAILABLE="$DETECTED_GIT_GITHUB"
|
|
|
|
# Search for PRD/spec files
|
|
local search_dirs=("docs" "specs" "." "requirements")
|
|
local prd_patterns=("*prd*.md" "*PRD*.md" "*requirements*.md" "*spec*.md" "*specification*.md")
|
|
|
|
for dir in "${search_dirs[@]}"; do
|
|
if [[ -d "$dir" ]]; then
|
|
for pattern in "${prd_patterns[@]}"; do
|
|
while IFS= read -r -d '' file; do
|
|
DETECTED_PRD_FILES+=("$file")
|
|
done < <(find "$dir" -maxdepth 2 -name "$pattern" -print0 2>/dev/null)
|
|
done
|
|
fi
|
|
done
|
|
}
|
|
|
|
# =============================================================================
|
|
# TEMPLATE GENERATION
|
|
# =============================================================================
|
|
|
|
# get_templates_dir - Get the templates directory path
|
|
#
|
|
# Returns:
|
|
# Echoes the path to templates directory
|
|
# Returns 1 if not found
|
|
#
|
|
get_templates_dir() {
|
|
# Check global installation first
|
|
if [[ -d "$HOME/.ralph/templates" ]]; then
|
|
echo "$HOME/.ralph/templates"
|
|
return 0
|
|
fi
|
|
|
|
# Check local installation (development)
|
|
local script_dir
|
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
if [[ -d "$script_dir/../templates" ]]; then
|
|
echo "$script_dir/../templates"
|
|
return 0
|
|
fi
|
|
|
|
return 1
|
|
}
|
|
|
|
# generate_prompt_md - Generate PROMPT.md with project context
|
|
#
|
|
# Parameters:
|
|
# $1 (project_name) - Project name
|
|
# $2 (project_type) - Project type (typescript, python, etc.)
|
|
# $3 (framework) - Framework if any (optional)
|
|
# $4 (objectives) - Custom objectives (optional, newline-separated)
|
|
#
|
|
# Outputs to stdout
|
|
#
|
|
generate_prompt_md() {
|
|
local project_name="${1:-$(basename "$(pwd)")}"
|
|
local project_type="${2:-unknown}"
|
|
local framework="${3:-}"
|
|
local objectives="${4:-}"
|
|
|
|
local framework_line=""
|
|
if [[ -n "$framework" ]]; then
|
|
framework_line="**Framework:** $framework"
|
|
fi
|
|
|
|
local objectives_section=""
|
|
if [[ -n "$objectives" ]]; then
|
|
objectives_section="$objectives"
|
|
else
|
|
objectives_section="- Review the codebase and understand the current state
|
|
- Follow tasks in @fix_plan.md
|
|
- Implement one task per loop
|
|
- Write tests for new functionality
|
|
- Update documentation as needed"
|
|
fi
|
|
|
|
cat << PROMPTEOF
|
|
# Ralph Development Instructions
|
|
|
|
## Context
|
|
You are Ralph, an autonomous AI development agent working on the **${project_name}** project.
|
|
|
|
**Project Type:** ${project_type}
|
|
${framework_line}
|
|
|
|
## Current Objectives
|
|
${objectives_section}
|
|
|
|
## Key Principles
|
|
- ONE task per loop - focus on the most important thing
|
|
- Search the codebase before assuming something isn't implemented
|
|
- Write comprehensive tests with clear documentation
|
|
- Toggle completed story checkboxes in @fix_plan.md without rewriting story lines
|
|
- Commit working changes with descriptive messages
|
|
|
|
## Progress Tracking (CRITICAL)
|
|
- Ralph tracks progress by counting story checkboxes in @fix_plan.md
|
|
- When you complete a story, change \`- [ ]\` to \`- [x]\` on that exact story line
|
|
- Do NOT remove, rewrite, or reorder story lines in @fix_plan.md
|
|
- Update the checkbox before committing so the monitor updates immediately
|
|
- Set \`TASKS_COMPLETED_THIS_LOOP\` to the exact number of story checkboxes toggled this loop
|
|
- Only valid values: 0 or 1
|
|
|
|
## Testing Guidelines
|
|
- LIMIT testing to ~20% of your total effort per loop
|
|
- PRIORITIZE: Implementation > Documentation > Tests
|
|
- Only write tests for NEW functionality you implement
|
|
|
|
## Build & Run
|
|
See @AGENT.md for build and run instructions.
|
|
|
|
## Status Reporting (CRITICAL)
|
|
|
|
At the end of your response, ALWAYS include this status block:
|
|
|
|
\`\`\`
|
|
---RALPH_STATUS---
|
|
STATUS: IN_PROGRESS | COMPLETE | BLOCKED
|
|
TASKS_COMPLETED_THIS_LOOP: 0 | 1
|
|
FILES_MODIFIED: <number>
|
|
TESTS_STATUS: PASSING | FAILING | NOT_RUN
|
|
WORK_TYPE: IMPLEMENTATION | TESTING | DOCUMENTATION | REFACTORING
|
|
EXIT_SIGNAL: false | true
|
|
RECOMMENDATION: <one line summary of what to do next>
|
|
---END_RALPH_STATUS---
|
|
\`\`\`
|
|
|
|
## Current Task
|
|
Follow @fix_plan.md and choose the most important item to implement next.
|
|
PROMPTEOF
|
|
}
|
|
|
|
# generate_agent_md - Generate @AGENT.md with detected build commands
|
|
#
|
|
# Parameters:
|
|
# $1 (build_cmd) - Build command
|
|
# $2 (test_cmd) - Test command
|
|
# $3 (run_cmd) - Run command
|
|
#
|
|
# Outputs to stdout
|
|
#
|
|
generate_agent_md() {
|
|
local build_cmd="${1:-echo 'No build command configured'}"
|
|
local test_cmd="${2:-echo 'No test command configured'}"
|
|
local run_cmd="${3:-echo 'No run command configured'}"
|
|
|
|
cat << AGENTEOF
|
|
# Ralph Agent Configuration
|
|
|
|
## Build Instructions
|
|
|
|
\`\`\`bash
|
|
# Build the project
|
|
${build_cmd}
|
|
\`\`\`
|
|
|
|
## Test Instructions
|
|
|
|
\`\`\`bash
|
|
# Run tests
|
|
${test_cmd}
|
|
\`\`\`
|
|
|
|
## Run Instructions
|
|
|
|
\`\`\`bash
|
|
# Start/run the project
|
|
${run_cmd}
|
|
\`\`\`
|
|
|
|
## Notes
|
|
- Update this file when build process changes
|
|
- Add environment setup instructions as needed
|
|
- Include any pre-requisites or dependencies
|
|
AGENTEOF
|
|
}
|
|
|
|
# generate_fix_plan_md - Generate @fix_plan.md with imported tasks
|
|
#
|
|
# Parameters:
|
|
# $1 (tasks) - Tasks to include (newline-separated, markdown checkbox format)
|
|
#
|
|
# Outputs to stdout
|
|
#
|
|
generate_fix_plan_md() {
|
|
local tasks="${1:-}"
|
|
|
|
local high_priority=""
|
|
local medium_priority=""
|
|
local low_priority=""
|
|
|
|
if [[ -n "$tasks" ]]; then
|
|
high_priority="$tasks"
|
|
else
|
|
high_priority="- [ ] Review codebase and understand architecture
|
|
- [ ] Identify and document key components
|
|
- [ ] Set up development environment"
|
|
medium_priority="- [ ] Implement core features
|
|
- [ ] Add test coverage
|
|
- [ ] Update documentation"
|
|
low_priority="- [ ] Performance optimization
|
|
- [ ] Code cleanup and refactoring"
|
|
fi
|
|
|
|
cat << FIXPLANEOF
|
|
# Ralph Fix Plan
|
|
|
|
## High Priority
|
|
${high_priority}
|
|
|
|
## Medium Priority
|
|
${medium_priority}
|
|
|
|
## Low Priority
|
|
${low_priority}
|
|
|
|
## Completed
|
|
- [x] Project enabled for Ralph
|
|
|
|
## Notes
|
|
- Focus on MVP functionality first
|
|
- Ensure each feature is properly tested
|
|
- Update this file after each major milestone
|
|
FIXPLANEOF
|
|
}
|
|
|
|
# generate_ralphrc - Generate .ralphrc configuration file
|
|
#
|
|
# Parameters:
|
|
# $1 (project_name) - Project name
|
|
# $2 (project_type) - Project type
|
|
# $3 (task_sources) - Task sources (local, beads, github)
|
|
#
|
|
# Outputs to stdout
|
|
#
|
|
generate_ralphrc() {
|
|
local project_name="${1:-$(basename "$(pwd)")}"
|
|
local project_type="${2:-unknown}"
|
|
local task_sources="${3:-local}"
|
|
|
|
cat << RALPHRCEOF
|
|
# .ralphrc - Ralph project configuration
|
|
# Generated by: ralph enable
|
|
# Documentation: https://github.com/frankbria/ralph-claude-code
|
|
|
|
# Project identification
|
|
PROJECT_NAME="${project_name}"
|
|
PROJECT_TYPE="${project_type}"
|
|
|
|
# Loop settings
|
|
MAX_CALLS_PER_HOUR=100
|
|
CLAUDE_TIMEOUT_MINUTES=15
|
|
CLAUDE_OUTPUT_FORMAT="json"
|
|
|
|
# Tool permissions
|
|
# Comma-separated list of allowed tools
|
|
ALLOWED_TOOLS="Write,Read,Edit,Bash(git *),Bash(npm *),Bash(pytest)"
|
|
|
|
# Session management
|
|
SESSION_CONTINUITY=true
|
|
SESSION_EXPIRY_HOURS=24
|
|
|
|
# Task sources (for ralph enable --sync)
|
|
# Options: local, beads, github (comma-separated for multiple)
|
|
TASK_SOURCES="${task_sources}"
|
|
GITHUB_TASK_LABEL="ralph-task"
|
|
BEADS_FILTER="status:open"
|
|
|
|
# Circuit breaker thresholds
|
|
CB_NO_PROGRESS_THRESHOLD=3
|
|
CB_SAME_ERROR_THRESHOLD=5
|
|
CB_OUTPUT_DECLINE_THRESHOLD=70
|
|
RALPHRCEOF
|
|
}
|
|
|
|
# =============================================================================
|
|
# MAIN ENABLE LOGIC
|
|
# =============================================================================
|
|
|
|
# enable_ralph_in_directory - Main function to enable Ralph in current directory
|
|
#
|
|
# Parameters:
|
|
# $1 (options) - JSON-like options string or empty
|
|
# force: true/false - Force overwrite existing
|
|
# skip_tasks: true/false - Skip task import
|
|
# project_name: string - Override project name
|
|
# task_content: string - Pre-imported task content
|
|
#
|
|
# Returns:
|
|
# 0 - Success
|
|
# 1 - Error
|
|
# 2 - Already enabled (and no force flag)
|
|
#
|
|
enable_ralph_in_directory() {
|
|
local force="${ENABLE_FORCE:-false}"
|
|
local skip_tasks="${ENABLE_SKIP_TASKS:-false}"
|
|
local project_name="${ENABLE_PROJECT_NAME:-}"
|
|
local project_type="${ENABLE_PROJECT_TYPE:-}"
|
|
local task_content="${ENABLE_TASK_CONTENT:-}"
|
|
|
|
# Check existing state (use || true to prevent set -e from exiting)
|
|
check_existing_ralph || true
|
|
|
|
if [[ "$RALPH_STATE" == "complete" && "$force" != "true" ]]; then
|
|
enable_log "INFO" "Ralph is already enabled in this project"
|
|
enable_log "INFO" "Use --force to overwrite existing configuration"
|
|
return $ENABLE_ALREADY_ENABLED
|
|
fi
|
|
|
|
# Detect project context
|
|
detect_project_context
|
|
|
|
# Use detected or provided project name
|
|
if [[ -z "$project_name" ]]; then
|
|
project_name="$DETECTED_PROJECT_NAME"
|
|
fi
|
|
|
|
# Use detected or provided project type
|
|
if [[ -n "$project_type" ]]; then
|
|
DETECTED_PROJECT_TYPE="$project_type"
|
|
fi
|
|
|
|
enable_log "INFO" "Enabling Ralph for: $project_name"
|
|
enable_log "INFO" "Project type: $DETECTED_PROJECT_TYPE"
|
|
if [[ -n "$DETECTED_FRAMEWORK" ]]; then
|
|
enable_log "INFO" "Framework: $DETECTED_FRAMEWORK"
|
|
fi
|
|
|
|
# Create directory structure
|
|
if ! create_ralph_structure; then
|
|
enable_log "ERROR" "Failed to create .ralph/ structure"
|
|
return $ENABLE_ERROR
|
|
fi
|
|
|
|
# Generate and create files
|
|
local prompt_content
|
|
prompt_content=$(generate_prompt_md "$project_name" "$DETECTED_PROJECT_TYPE" "$DETECTED_FRAMEWORK")
|
|
safe_create_file ".ralph/PROMPT.md" "$prompt_content"
|
|
|
|
local agent_content
|
|
agent_content=$(generate_agent_md "$DETECTED_BUILD_CMD" "$DETECTED_TEST_CMD" "$DETECTED_RUN_CMD")
|
|
safe_create_file ".ralph/@AGENT.md" "$agent_content"
|
|
|
|
local fix_plan_content
|
|
fix_plan_content=$(generate_fix_plan_md "$task_content")
|
|
safe_create_file ".ralph/@fix_plan.md" "$fix_plan_content"
|
|
|
|
# Detect task sources for .ralphrc
|
|
detect_task_sources
|
|
local task_sources="local"
|
|
if [[ "$DETECTED_BEADS_AVAILABLE" == "true" ]]; then
|
|
task_sources="beads,$task_sources"
|
|
fi
|
|
if [[ "$DETECTED_GITHUB_AVAILABLE" == "true" ]]; then
|
|
task_sources="github,$task_sources"
|
|
fi
|
|
|
|
# Generate .ralphrc
|
|
local ralphrc_content
|
|
ralphrc_content=$(generate_ralphrc "$project_name" "$DETECTED_PROJECT_TYPE" "$task_sources")
|
|
safe_create_file ".ralphrc" "$ralphrc_content"
|
|
|
|
enable_log "SUCCESS" "Ralph enabled successfully!"
|
|
|
|
return $ENABLE_SUCCESS
|
|
}
|
|
|
|
# Export functions for use in other scripts
|
|
export -f enable_log
|
|
export -f check_existing_ralph
|
|
export -f is_ralph_enabled
|
|
export -f safe_create_file
|
|
export -f safe_create_dir
|
|
export -f create_ralph_structure
|
|
export -f detect_project_context
|
|
export -f detect_git_info
|
|
export -f detect_task_sources
|
|
export -f get_templates_dir
|
|
export -f generate_prompt_md
|
|
export -f generate_agent_md
|
|
export -f generate_fix_plan_md
|
|
export -f generate_ralphrc
|
|
export -f enable_ralph_in_directory
|