Initial checkin

This commit is contained in:
Den Delimarsky 🌺
2025-08-22 10:06:25 -07:00
parent fa2736371e
commit 28fdfaa869
26 changed files with 3356 additions and 184 deletions

View File

@@ -0,0 +1,62 @@
#!/bin/bash
# Check that implementation plan exists and find optional design documents
# Usage: ./check-task-prerequisites.sh [--json]
set -e
JSON_MODE=false
for arg in "$@"; do
case "$arg" in
--json) JSON_MODE=true ;;
--help|-h) echo "Usage: $0 [--json]"; exit 0 ;;
esac
done
# Source common functions
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/common.sh"
# Get all paths
eval $(get_feature_paths)
# Check if on feature branch
check_feature_branch "$CURRENT_BRANCH" || exit 1
# Check if feature directory exists
if [[ ! -d "$FEATURE_DIR" ]]; then
echo "ERROR: Feature directory not found: $FEATURE_DIR"
echo "Run /specify first to create the feature structure."
exit 1
fi
# Check for implementation plan (required)
if [[ ! -f "$IMPL_PLAN" ]]; then
echo "ERROR: plan.md not found in $FEATURE_DIR"
echo "Run /plan first to create the plan."
exit 1
fi
if $JSON_MODE; then
# Build JSON array of available docs that actually exist
docs=()
[[ -f "$RESEARCH" ]] && docs+=("research.md")
[[ -f "$DATA_MODEL" ]] && docs+=("data-model.md")
([[ -d "$CONTRACTS_DIR" ]] && [[ -n "$(ls -A "$CONTRACTS_DIR" 2>/dev/null)" ]]) && docs+=("contracts/")
[[ -f "$QUICKSTART" ]] && docs+=("quickstart.md")
# join array into JSON
json_docs=$(printf '"%s",' "${docs[@]}")
json_docs="[${json_docs%,}]"
printf '{"FEATURE_DIR":"%s","AVAILABLE_DOCS":%s}\n' "$FEATURE_DIR" "$json_docs"
else
# List available design documents (optional)
echo "FEATURE_DIR:$FEATURE_DIR"
echo "AVAILABLE_DOCS:"
# Use common check functions
check_file "$RESEARCH" "research.md"
check_file "$DATA_MODEL" "data-model.md"
check_dir "$CONTRACTS_DIR" "contracts/"
check_file "$QUICKSTART" "quickstart.md"
fi
# Always succeed - task generation should work with whatever docs are available

77
scripts/common.sh Normal file
View File

@@ -0,0 +1,77 @@
#!/bin/bash
# Common functions and variables for all scripts
# Get repository root
get_repo_root() {
git rev-parse --show-toplevel
}
# Get current branch
get_current_branch() {
git rev-parse --abbrev-ref HEAD
}
# Check if current branch is a feature branch
# Returns 0 if valid, 1 if not
check_feature_branch() {
local branch="$1"
if [[ ! "$branch" =~ ^[0-9]{3}- ]]; then
echo "ERROR: Not on a feature branch. Current branch: $branch"
echo "Feature branches should be named like: 001-feature-name"
return 1
fi
return 0
}
# Get feature directory path
get_feature_dir() {
local repo_root="$1"
local branch="$2"
echo "$repo_root/specs/$branch"
}
# Get all standard paths for a feature
# Usage: eval $(get_feature_paths)
# Sets: REPO_ROOT, CURRENT_BRANCH, FEATURE_DIR, FEATURE_SPEC, IMPL_PLAN, TASKS
get_feature_paths() {
local repo_root=$(get_repo_root)
local current_branch=$(get_current_branch)
local feature_dir=$(get_feature_dir "$repo_root" "$current_branch")
echo "REPO_ROOT='$repo_root'"
echo "CURRENT_BRANCH='$current_branch'"
echo "FEATURE_DIR='$feature_dir'"
echo "FEATURE_SPEC='$feature_dir/spec.md'"
echo "IMPL_PLAN='$feature_dir/plan.md'"
echo "TASKS='$feature_dir/tasks.md'"
echo "RESEARCH='$feature_dir/research.md'"
echo "DATA_MODEL='$feature_dir/data-model.md'"
echo "QUICKSTART='$feature_dir/quickstart.md'"
echo "CONTRACTS_DIR='$feature_dir/contracts'"
}
# Check if a file exists and report
check_file() {
local file="$1"
local description="$2"
if [[ -f "$file" ]]; then
echo "$description"
return 0
else
echo "$description"
return 1
fi
}
# Check if a directory exists and has files
check_dir() {
local dir="$1"
local description="$2"
if [[ -d "$dir" ]] && [[ -n "$(ls -A "$dir" 2>/dev/null)" ]]; then
echo "$description"
return 0
else
echo "$description"
return 1
fi
}

View File

@@ -0,0 +1,96 @@
#!/bin/bash
# Create a new feature with branch, directory structure, and template
# Usage: ./create-new-feature.sh "feature description"
# ./create-new-feature.sh --json "feature description"
set -e
JSON_MODE=false
# Collect non-flag args
ARGS=()
for arg in "$@"; do
case "$arg" in
--json)
JSON_MODE=true
;;
--help|-h)
echo "Usage: $0 [--json] <feature_description>"; exit 0 ;;
*)
ARGS+=("$arg") ;;
esac
done
FEATURE_DESCRIPTION="${ARGS[*]}"
if [ -z "$FEATURE_DESCRIPTION" ]; then
echo "Usage: $0 [--json] <feature_description>" >&2
exit 1
fi
# Get repository root
REPO_ROOT=$(git rev-parse --show-toplevel)
SPECS_DIR="$REPO_ROOT/specs"
# Create specs directory if it doesn't exist
mkdir -p "$SPECS_DIR"
# Find the highest numbered feature directory
HIGHEST=0
if [ -d "$SPECS_DIR" ]; then
for dir in "$SPECS_DIR"/*; do
if [ -d "$dir" ]; then
dirname=$(basename "$dir")
number=$(echo "$dirname" | grep -o '^[0-9]\+' || echo "0")
number=$((10#$number))
if [ "$number" -gt "$HIGHEST" ]; then
HIGHEST=$number
fi
fi
done
fi
# Generate next feature number with zero padding
NEXT=$((HIGHEST + 1))
FEATURE_NUM=$(printf "%03d" "$NEXT")
# Create branch name from description
BRANCH_NAME=$(echo "$FEATURE_DESCRIPTION" | \
tr '[:upper:]' '[:lower:]' | \
sed 's/[^a-z0-9]/-/g' | \
sed 's/-\+/-/g' | \
sed 's/^-//' | \
sed 's/-$//')
# Extract 2-3 meaningful words
WORDS=$(echo "$BRANCH_NAME" | tr '-' '\n' | grep -v '^$' | head -3 | tr '\n' '-' | sed 's/-$//')
# Final branch name
BRANCH_NAME="${FEATURE_NUM}-${WORDS}"
# Create and switch to new branch
git checkout -b "$BRANCH_NAME"
# Create feature directory
FEATURE_DIR="$SPECS_DIR/$BRANCH_NAME"
mkdir -p "$FEATURE_DIR"
# Copy template if it exists
TEMPLATE="$REPO_ROOT/templates/spec-template.md"
SPEC_FILE="$FEATURE_DIR/spec.md"
if [ -f "$TEMPLATE" ]; then
cp "$TEMPLATE" "$SPEC_FILE"
else
echo "Warning: Template not found at $TEMPLATE" >&2
touch "$SPEC_FILE"
fi
if $JSON_MODE; then
printf '{"BRANCH_NAME":"%s","SPEC_FILE":"%s","FEATURE_NUM":"%s"}\n' \
"$BRANCH_NAME" "$SPEC_FILE" "$FEATURE_NUM"
else
# Output results for the LLM to use (legacy key: value format)
echo "BRANCH_NAME: $BRANCH_NAME"
echo "SPEC_FILE: $SPEC_FILE"
echo "FEATURE_NUM: $FEATURE_NUM"
fi

View File

@@ -0,0 +1,23 @@
#!/bin/bash
# Get paths for current feature branch without creating anything
# Used by commands that need to find existing feature files
set -e
# Source common functions
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/common.sh"
# Get all paths
eval $(get_feature_paths)
# Check if on feature branch
check_feature_branch "$CURRENT_BRANCH" || exit 1
# Output paths (don't create anything)
echo "REPO_ROOT: $REPO_ROOT"
echo "BRANCH: $CURRENT_BRANCH"
echo "FEATURE_DIR: $FEATURE_DIR"
echo "FEATURE_SPEC: $FEATURE_SPEC"
echo "IMPL_PLAN: $IMPL_PLAN"
echo "TASKS: $TASKS"

44
scripts/setup-plan.sh Normal file
View File

@@ -0,0 +1,44 @@
#!/bin/bash
# Setup implementation plan structure for current branch
# Returns paths needed for implementation plan generation
# Usage: ./setup-plan.sh [--json]
set -e
JSON_MODE=false
for arg in "$@"; do
case "$arg" in
--json) JSON_MODE=true ;;
--help|-h) echo "Usage: $0 [--json]"; exit 0 ;;
esac
done
# Source common functions
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/common.sh"
# Get all paths
eval $(get_feature_paths)
# Check if on feature branch
check_feature_branch "$CURRENT_BRANCH" || exit 1
# Create specs directory if it doesn't exist
mkdir -p "$FEATURE_DIR"
# Copy plan template if it exists
TEMPLATE="$REPO_ROOT/templates/plan-template.md"
if [ -f "$TEMPLATE" ]; then
cp "$TEMPLATE" "$IMPL_PLAN"
fi
if $JSON_MODE; then
printf '{"FEATURE_SPEC":"%s","IMPL_PLAN":"%s","SPECS_DIR":"%s","BRANCH":"%s"}\n' \
"$FEATURE_SPEC" "$IMPL_PLAN" "$FEATURE_DIR" "$CURRENT_BRANCH"
else
# Output all paths for LLM use
echo "FEATURE_SPEC: $FEATURE_SPEC"
echo "IMPL_PLAN: $IMPL_PLAN"
echo "SPECS_DIR: $FEATURE_DIR"
echo "BRANCH: $CURRENT_BRANCH"
fi

View File

@@ -0,0 +1,234 @@
#!/bin/bash
# Incrementally update agent context files based on new feature plan
# Supports: CLAUDE.md, GEMINI.md, and .github/copilot-instructions.md
# O(1) operation - only reads current context file and new plan.md
set -e
REPO_ROOT=$(git rev-parse --show-toplevel)
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
FEATURE_DIR="$REPO_ROOT/specs/$CURRENT_BRANCH"
NEW_PLAN="$FEATURE_DIR/plan.md"
# Determine which agent context files to update
CLAUDE_FILE="$REPO_ROOT/CLAUDE.md"
GEMINI_FILE="$REPO_ROOT/GEMINI.md"
COPILOT_FILE="$REPO_ROOT/.github/copilot-instructions.md"
# Allow override via argument
AGENT_TYPE="$1"
if [ ! -f "$NEW_PLAN" ]; then
echo "ERROR: No plan.md found at $NEW_PLAN"
exit 1
fi
echo "=== Updating agent context files for feature $CURRENT_BRANCH ==="
# Extract tech from new plan
NEW_LANG=$(grep "^**Language/Version**: " "$NEW_PLAN" 2>/dev/null | head -1 | sed 's/^**Language\/Version**: //' | grep -v "NEEDS CLARIFICATION" || echo "")
NEW_FRAMEWORK=$(grep "^**Primary Dependencies**: " "$NEW_PLAN" 2>/dev/null | head -1 | sed 's/^**Primary Dependencies**: //' | grep -v "NEEDS CLARIFICATION" || echo "")
NEW_TESTING=$(grep "^**Testing**: " "$NEW_PLAN" 2>/dev/null | head -1 | sed 's/^**Testing**: //' | grep -v "NEEDS CLARIFICATION" || echo "")
NEW_DB=$(grep "^**Storage**: " "$NEW_PLAN" 2>/dev/null | head -1 | sed 's/^**Storage**: //' | grep -v "N/A" | grep -v "NEEDS CLARIFICATION" || echo "")
NEW_PROJECT_TYPE=$(grep "^**Project Type**: " "$NEW_PLAN" 2>/dev/null | head -1 | sed 's/^**Project Type**: //' || echo "")
# Function to update a single agent context file
update_agent_file() {
local target_file="$1"
local agent_name="$2"
echo "Updating $agent_name context file: $target_file"
# Create temp file for new context
local temp_file=$(mktemp)
# If file doesn't exist, create from template
if [ ! -f "$target_file" ]; then
echo "Creating new $agent_name context file..."
# Check if this is the SDD repo itself
if [ -f "$REPO_ROOT/templates/agent-file-template.md" ]; then
cp "$REPO_ROOT/templates/agent-file-template.md" "$temp_file"
else
echo "ERROR: Template not found at $REPO_ROOT/templates/agent-file-template.md"
return 1
fi
# Replace placeholders
sed -i.bak "s/\[PROJECT NAME\]/$(basename $REPO_ROOT)/" "$temp_file"
sed -i.bak "s/\[DATE\]/$(date +%Y-%m-%d)/" "$temp_file"
sed -i.bak "s/\[EXTRACTED FROM ALL PLAN.MD FILES\]/- $NEW_LANG + $NEW_FRAMEWORK ($CURRENT_BRANCH)/" "$temp_file"
# Add project structure based on type
if [[ "$NEW_PROJECT_TYPE" == *"web"* ]]; then
sed -i.bak "s|\[ACTUAL STRUCTURE FROM PLANS\]|backend/\nfrontend/\ntests/|" "$temp_file"
else
sed -i.bak "s|\[ACTUAL STRUCTURE FROM PLANS\]|src/\ntests/|" "$temp_file"
fi
# Add minimal commands
if [[ "$NEW_LANG" == *"Python"* ]]; then
COMMANDS="cd src && pytest && ruff check ."
elif [[ "$NEW_LANG" == *"Rust"* ]]; then
COMMANDS="cargo test && cargo clippy"
elif [[ "$NEW_LANG" == *"JavaScript"* ]] || [[ "$NEW_LANG" == *"TypeScript"* ]]; then
COMMANDS="npm test && npm run lint"
else
COMMANDS="# Add commands for $NEW_LANG"
fi
sed -i.bak "s|\[ONLY COMMANDS FOR ACTIVE TECHNOLOGIES\]|$COMMANDS|" "$temp_file"
# Add code style
sed -i.bak "s|\[LANGUAGE-SPECIFIC, ONLY FOR LANGUAGES IN USE\]|$NEW_LANG: Follow standard conventions|" "$temp_file"
# Add recent changes
sed -i.bak "s|\[LAST 3 FEATURES AND WHAT THEY ADDED\]|- $CURRENT_BRANCH: Added $NEW_LANG + $NEW_FRAMEWORK|" "$temp_file"
rm "$temp_file.bak"
else
echo "Updating existing $agent_name context file..."
# Extract manual additions
local manual_start=$(grep -n "<!-- MANUAL ADDITIONS START -->" "$target_file" | cut -d: -f1)
local manual_end=$(grep -n "<!-- MANUAL ADDITIONS END -->" "$target_file" | cut -d: -f1)
if [ ! -z "$manual_start" ] && [ ! -z "$manual_end" ]; then
sed -n "${manual_start},${manual_end}p" "$target_file" > /tmp/manual_additions.txt
fi
# Parse existing file and create updated version
python3 - << EOF
import re
import sys
from datetime import datetime
# Read existing file
with open("$target_file", 'r') as f:
content = f.read()
# Check if new tech already exists
tech_section = re.search(r'## Active Technologies\n(.*?)\n\n', content, re.DOTALL)
if tech_section:
existing_tech = tech_section.group(1)
# Add new tech if not already present
new_additions = []
if "$NEW_LANG" and "$NEW_LANG" not in existing_tech:
new_additions.append(f"- $NEW_LANG + $NEW_FRAMEWORK ($CURRENT_BRANCH)")
if "$NEW_DB" and "$NEW_DB" not in existing_tech and "$NEW_DB" != "N/A":
new_additions.append(f"- $NEW_DB ($CURRENT_BRANCH)")
if new_additions:
updated_tech = existing_tech + "\n" + "\n".join(new_additions)
content = content.replace(tech_section.group(0), f"## Active Technologies\n{updated_tech}\n\n")
# Update project structure if needed
if "$NEW_PROJECT_TYPE" == "web" and "frontend/" not in content:
struct_section = re.search(r'## Project Structure\n\`\`\`\n(.*?)\n\`\`\`', content, re.DOTALL)
if struct_section:
updated_struct = struct_section.group(1) + "\nfrontend/src/ # Web UI"
content = re.sub(r'(## Project Structure\n\`\`\`\n).*?(\n\`\`\`)',
f'\\1{updated_struct}\\2', content, flags=re.DOTALL)
# Add new commands if language is new
if "$NEW_LANG" and f"# {NEW_LANG}" not in content:
commands_section = re.search(r'## Commands\n\`\`\`bash\n(.*?)\n\`\`\`', content, re.DOTALL)
if not commands_section:
commands_section = re.search(r'## Commands\n(.*?)\n\n', content, re.DOTALL)
if commands_section:
new_commands = commands_section.group(1)
if "Python" in "$NEW_LANG":
new_commands += "\ncd src && pytest && ruff check ."
elif "Rust" in "$NEW_LANG":
new_commands += "\ncargo test && cargo clippy"
elif "JavaScript" in "$NEW_LANG" or "TypeScript" in "$NEW_LANG":
new_commands += "\nnpm test && npm run lint"
if "```bash" in content:
content = re.sub(r'(## Commands\n\`\`\`bash\n).*?(\n\`\`\`)',
f'\\1{new_commands}\\2', content, flags=re.DOTALL)
else:
content = re.sub(r'(## Commands\n).*?(\n\n)',
f'\\1{new_commands}\\2', content, flags=re.DOTALL)
# Update recent changes (keep only last 3)
changes_section = re.search(r'## Recent Changes\n(.*?)(\n\n|$)', content, re.DOTALL)
if changes_section:
changes = changes_section.group(1).strip().split('\n')
changes.insert(0, f"- $CURRENT_BRANCH: Added $NEW_LANG + $NEW_FRAMEWORK")
# Keep only last 3
changes = changes[:3]
content = re.sub(r'(## Recent Changes\n).*?(\n\n|$)',
f'\\1{chr(10).join(changes)}\\2', content, flags=re.DOTALL)
# Update date
content = re.sub(r'Last updated: \d{4}-\d{2}-\d{2}',
f'Last updated: {datetime.now().strftime("%Y-%m-%d")}', content)
# Write to temp file
with open("$temp_file", 'w') as f:
f.write(content)
EOF
# Restore manual additions if they exist
if [ -f /tmp/manual_additions.txt ]; then
# Remove old manual section from temp file
sed -i.bak '/<!-- MANUAL ADDITIONS START -->/,/<!-- MANUAL ADDITIONS END -->/d' "$temp_file"
# Append manual additions
cat /tmp/manual_additions.txt >> "$temp_file"
rm /tmp/manual_additions.txt "$temp_file.bak"
fi
fi
# Move temp file to final location
mv "$temp_file" "$target_file"
echo "$agent_name context file updated successfully"
}
# Update files based on argument or detect existing files
case "$AGENT_TYPE" in
"claude")
update_agent_file "$CLAUDE_FILE" "Claude Code"
;;
"gemini")
update_agent_file "$GEMINI_FILE" "Gemini CLI"
;;
"copilot")
update_agent_file "$COPILOT_FILE" "GitHub Copilot"
;;
"")
# Update all existing files
[ -f "$CLAUDE_FILE" ] && update_agent_file "$CLAUDE_FILE" "Claude Code"
[ -f "$GEMINI_FILE" ] && update_agent_file "$GEMINI_FILE" "Gemini CLI"
[ -f "$COPILOT_FILE" ] && update_agent_file "$COPILOT_FILE" "GitHub Copilot"
# If no files exist, create based on current directory or ask user
if [ ! -f "$CLAUDE_FILE" ] && [ ! -f "$GEMINI_FILE" ] && [ ! -f "$COPILOT_FILE" ]; then
echo "No agent context files found. Creating Claude Code context file by default."
update_agent_file "$CLAUDE_FILE" "Claude Code"
fi
;;
*)
echo "ERROR: Unknown agent type '$AGENT_TYPE'. Use: claude, gemini, copilot, or leave empty for all."
exit 1
;;
esac
echo ""
echo "Summary of changes:"
if [ ! -z "$NEW_LANG" ]; then
echo "- Added language: $NEW_LANG"
fi
if [ ! -z "$NEW_FRAMEWORK" ]; then
echo "- Added framework: $NEW_FRAMEWORK"
fi
if [ ! -z "$NEW_DB" ] && [ "$NEW_DB" != "N/A" ]; then
echo "- Added database: $NEW_DB"
fi
echo ""
echo "Usage: $0 [claude|gemini|copilot]"
echo " - No argument: Update all existing agent context files"
echo " - claude: Update only CLAUDE.md"
echo " - gemini: Update only GEMINI.md"
echo " - copilot: Update only .github/copilot-instructions.md"