From 0eed98697ef1b0be06ecb7761f0c4279e2cb7bda Mon Sep 17 00:00:00 2001 From: marcusquinn <6428977+marcusquinn@users.noreply.github.com> Date: Tue, 13 Jan 2026 01:02:53 +0000 Subject: [PATCH 1/4] feat: add upgrade-planning command to upgrade TODO.md/PLANS.md to latest templates - Add cmd_upgrade_planning function with --dry-run, --force, --no-backup options - Detects minimal templates (missing TOON markers) and offers upgrade - Preserves existing tasks by extracting and merging into new structure - Creates backups before modification - Updates .aidevops.json with templates_version tracking - Add command to help, main entry point, and documentation --- .agent/AGENTS.md | 1 + README.md | 12 +++ aidevops.sh | 273 +++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 278 insertions(+), 8 deletions(-) diff --git a/.agent/AGENTS.md b/.agent/AGENTS.md index fbdad5ae..b4cad696 100644 --- a/.agent/AGENTS.md +++ b/.agent/AGENTS.md @@ -431,6 +431,7 @@ aidevops features # List available features | Command | Purpose | |---------|---------| | `aidevops init [features]` | Initialize aidevops in current project | +| `aidevops upgrade-planning` | Upgrade TODO.md/PLANS.md to latest templates | | `aidevops features` | List available features | | `aidevops status` | Check installation status | | `aidevops update` | Update to latest version | diff --git a/README.md b/README.md index eb7690dd..2badc7ad 100644 --- a/README.md +++ b/README.md @@ -143,6 +143,18 @@ This creates: **Available features:** `planning`, `git-workflow`, `code-quality`, `time-tracking`, `beads` +### Upgrade Planning Files + +When aidevops templates evolve, upgrade existing projects to the latest format: + +```bash +aidevops upgrade-planning # Interactive upgrade with backup +aidevops upgrade-planning --dry-run # Preview changes without modifying +aidevops upgrade-planning --force # Skip confirmation prompt +``` + +This preserves your existing tasks while adding TOON-enhanced parsing, dependency tracking, and better structure. + ### Task Graph Visualization with Beads [Beads](https://github.com/steveyegge/beads) provides task dependency tracking and graph visualization: diff --git a/aidevops.sh b/aidevops.sh index 664ebf19..d2f53a2f 100755 --- a/aidevops.sh +++ b/aidevops.sh @@ -761,6 +761,257 @@ EOF fi } +# Upgrade planning command - upgrade TODO.md and PLANS.md to latest templates +cmd_upgrade_planning() { + local force=false + local backup=true + local dry_run=false + + # Parse arguments + while [[ $# -gt 0 ]]; do + case "$1" in + --force|-f) force=true; shift ;; + --no-backup) backup=false; shift ;; + --dry-run|-n) dry_run=true; shift ;; + *) shift ;; + esac + done + + print_header "Upgrade Planning Files" + echo "" + + # Check if in a git repo + if ! git rev-parse --is-inside-work-tree &>/dev/null; then + print_error "Not in a git repository" + return 1 + fi + + local project_root + project_root=$(git rev-parse --show-toplevel) + + # Check if aidevops is initialized + if [[ ! -f "$project_root/.aidevops.json" ]]; then + print_error "aidevops not initialized in this project" + print_info "Run 'aidevops init' first" + return 1 + fi + + # Check if planning is enabled + local planning_enabled + planning_enabled=$(grep -o '"planning": *true' "$project_root/.aidevops.json" 2>/dev/null || echo "") + if [[ -z "$planning_enabled" ]]; then + print_error "Planning feature not enabled" + print_info "Run 'aidevops init planning' to enable" + return 1 + fi + + local todo_file="$project_root/TODO.md" + local plans_file="$project_root/todo/PLANS.md" + local todo_template="$AGENTS_DIR/templates/todo-template.md" + local plans_template="$AGENTS_DIR/templates/plans-template.md" + + # Check templates exist + if [[ ! -f "$todo_template" ]]; then + print_error "TODO template not found: $todo_template" + return 1 + fi + if [[ ! -f "$plans_template" ]]; then + print_error "PLANS template not found: $plans_template" + return 1 + fi + + local needs_upgrade=false + local todo_needs_upgrade=false + local plans_needs_upgrade=false + + # Check TODO.md + if [[ -f "$todo_file" ]]; then + # Check if it's using the minimal template (no TOON markers) + if ! grep -q "TOON:meta" "$todo_file" 2>/dev/null; then + todo_needs_upgrade=true + needs_upgrade=true + print_warning "TODO.md uses minimal template (missing TOON markers)" + else + print_success "TODO.md already has TOON markers" + fi + else + print_info "TODO.md not found - will create from template" + todo_needs_upgrade=true + needs_upgrade=true + fi + + # Check PLANS.md + if [[ -f "$plans_file" ]]; then + # Check if it's using the minimal template (no TOON markers) + if ! grep -q "TOON:meta" "$plans_file" 2>/dev/null; then + plans_needs_upgrade=true + needs_upgrade=true + print_warning "todo/PLANS.md uses minimal template (missing TOON markers)" + else + print_success "todo/PLANS.md already has TOON markers" + fi + else + print_info "todo/PLANS.md not found - will create from template" + plans_needs_upgrade=true + needs_upgrade=true + fi + + if [[ "$needs_upgrade" == "false" ]]; then + echo "" + print_success "Planning files are up to date!" + return 0 + fi + + echo "" + + if [[ "$dry_run" == "true" ]]; then + print_info "Dry run - no changes will be made" + echo "" + [[ "$todo_needs_upgrade" == "true" ]] && echo " Would upgrade: TODO.md" + [[ "$plans_needs_upgrade" == "true" ]] && echo " Would upgrade: todo/PLANS.md" + return 0 + fi + + # Confirm upgrade unless forced + if [[ "$force" == "false" ]]; then + echo "Files to upgrade:" + [[ "$todo_needs_upgrade" == "true" ]] && echo " - TODO.md" + [[ "$plans_needs_upgrade" == "true" ]] && echo " - todo/PLANS.md" + echo "" + echo "This will:" + echo " 1. Extract existing tasks from current files" + echo " 2. Create backups (.bak files)" + echo " 3. Apply new TOON-enhanced templates" + echo " 4. Merge existing tasks into new structure" + echo "" + read -r -p "Continue? [y/N] " response + if [[ ! "$response" =~ ^[Yy]$ ]]; then + print_info "Upgrade cancelled" + return 0 + fi + fi + + echo "" + + # Upgrade TODO.md + if [[ "$todo_needs_upgrade" == "true" ]]; then + print_info "Upgrading TODO.md..." + + # Extract existing tasks if file exists + local existing_tasks="" + if [[ -f "$todo_file" ]]; then + # Extract task lines (lines starting with - [ ] or - [x] or - [-]) + existing_tasks=$(grep -E "^[[:space:]]*- \[([ x-])\]" "$todo_file" 2>/dev/null || echo "") + + # Create backup + if [[ "$backup" == "true" ]]; then + cp "$todo_file" "${todo_file}.bak" + print_success "Backup created: TODO.md.bak" + fi + fi + + # Copy template (strip frontmatter) + sed '1,/^---$/d' "$todo_template" | sed '1,/^---$/d' > "$todo_file" 2>/dev/null || \ + cp "$todo_template" "$todo_file" + + # Update date placeholder + sed -i.tmp "s/{{DATE}}/$(date +%Y-%m-%d)/" "$todo_file" 2>/dev/null || true + rm -f "${todo_file}.tmp" + + # Merge existing tasks into Backlog section + if [[ -n "$existing_tasks" ]]; then + # Find the Backlog section and insert tasks after the TOON marker + local backlog_marker=" + if grep -q "$/ { print; print ""; print tasks; in_backlog=0; next } { print } ' "$todo_file" > "$temp_file" mv "$temp_file" "$todo_file" @@ -957,22 +964,21 @@ cmd_upgrade_planning() { fi fi - # Copy template (strip frontmatter) - sed '1,/^---$/d' "$plans_template" | sed '1,/^---$/d' > "$plans_file" 2>/dev/null || \ + # Copy template (strip YAML frontmatter - lines between first two ---) + awk 'BEGIN{skip=0} /^---$/{skip++; if(skip==2){skip=0; next}} skip<2{next} {print}' "$plans_template" > "$plans_file" 2>/dev/null || \ cp "$plans_template" "$plans_file" # Update date placeholder sed -i.tmp "s/{{DATE}}/$(date +%Y-%m-%d)/" "$plans_file" 2>/dev/null || true rm -f "${plans_file}.tmp" - # Merge existing plans into Active Plans section + # Merge existing plans into Active Plans section (after the TOON block closing tag) if [[ -n "$existing_plans" ]]; then - # Find the Active Plans section and insert after the TOON marker - local active_marker="$/ { print; print ""; print plans; in_active=0; next } { print } ' "$plans_file" > "$temp_file" mv "$temp_file" "$plans_file" @@ -988,15 +994,22 @@ cmd_upgrade_planning() { local aidevops_version aidevops_version=$(get_version) - # Add templates_version to config if not present - if ! grep -q '"templates_version"' "$config_file" 2>/dev/null; then - # Insert templates_version after version line - sed -i.tmp "s/\"version\": \"[^\"]*\"/\"version\": \"$aidevops_version\",\n \"templates_version\": \"$aidevops_version\"/" "$config_file" 2>/dev/null || true - rm -f "${config_file}.tmp" + # Add/update templates_version in config (use jq if available) + if command -v jq &>/dev/null; then + local temp_json="${config_file}.tmp" + jq --arg version "$aidevops_version" '.templates_version = $version' "$config_file" > "$temp_json" && \ + mv "$temp_json" "$config_file" else - # Update existing templates_version - sed -i.tmp "s/\"templates_version\": \"[^\"]*\"/\"templates_version\": \"$aidevops_version\"/" "$config_file" 2>/dev/null || true - rm -f "${config_file}.tmp" + # Fallback to sed if jq not available + if ! grep -q '"templates_version"' "$config_file" 2>/dev/null; then + # Insert templates_version after version line + sed -i.tmp "s/\"version\": \"[^\"]*\"/\"version\": \"$aidevops_version\",\n \"templates_version\": \"$aidevops_version\"/" "$config_file" 2>/dev/null || true + rm -f "${config_file}.tmp" + else + # Update existing templates_version + sed -i.tmp "s/\"templates_version\": \"[^\"]*\"/\"templates_version\": \"$aidevops_version\"/" "$config_file" 2>/dev/null || true + rm -f "${config_file}.tmp" + fi fi echo "" From f6ae5fcb7d02825411f5f54b931ebec2d8766d53 Mon Sep 17 00:00:00 2001 From: marcusquinn <6428977+marcusquinn@users.noreply.github.com> Date: Tue, 13 Jan 2026 01:29:45 +0000 Subject: [PATCH 3/4] feat: add protected branch check to init and upgrade-planning - Add check_protected_branch() helper function - Offers worktree creation when on main/master - Options: create worktree, continue on main, or cancel - Skips check for upgrade-planning --dry-run - Uses worktree-helper.sh if available, falls back to git worktree --- aidevops.sh | 98 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/aidevops.sh b/aidevops.sh index d7759bc1..6ed548de 100755 --- a/aidevops.sh +++ b/aidevops.sh @@ -67,6 +67,92 @@ check_file() { [[ -f "$1" ]] } +# Check if on protected branch and offer worktree creation +# Returns 0 if safe to proceed, 1 if user cancelled +# Sets WORKTREE_PATH if worktree was created +check_protected_branch() { + local branch_type="${1:-chore}" + local branch_suffix="${2:-aidevops-setup}" + + # Not in a git repo - skip check + if ! git rev-parse --is-inside-work-tree &>/dev/null; then + return 0 + fi + + local current_branch + current_branch=$(git branch --show-current 2>/dev/null || echo "") + + # Not on a protected branch - safe to proceed + if [[ ! "$current_branch" =~ ^(main|master)$ ]]; then + return 0 + fi + + local project_root + project_root=$(git rev-parse --show-toplevel) + local repo_name + repo_name=$(basename "$project_root") + local suggested_branch="$branch_type/$branch_suffix" + + echo "" + print_warning "On protected branch '$current_branch'" + echo "" + echo "Options:" + echo " 1. Create worktree: $suggested_branch (recommended)" + echo " 2. Continue on $current_branch (commits directly to main)" + echo " 3. Cancel" + echo "" + read -r -p "Choice [1]: " choice + choice="${choice:-1}" + + case "$choice" in + 1) + # Create worktree + local worktree_dir + worktree_dir="$(dirname "$project_root")/${repo_name}-${branch_type}-${branch_suffix}" + + print_info "Creating worktree at $worktree_dir..." + + if [[ -f "$AGENTS_DIR/scripts/worktree-helper.sh" ]]; then + if bash "$AGENTS_DIR/scripts/worktree-helper.sh" add "$suggested_branch" 2>/dev/null; then + export WORKTREE_PATH="$worktree_dir" + echo "" + print_success "Worktree created!" + print_info "Switching to: $worktree_dir" + echo "" + # Change to worktree directory + cd "$worktree_dir" || return 1 + return 0 + else + print_error "Failed to create worktree" + return 1 + fi + else + # Fallback without helper script + if git worktree add -b "$suggested_branch" "$worktree_dir" 2>/dev/null; then + export WORKTREE_PATH="$worktree_dir" + echo "" + print_success "Worktree created!" + print_info "Switching to: $worktree_dir" + echo "" + cd "$worktree_dir" || return 1 + return 0 + else + print_error "Failed to create worktree" + return 1 + fi + fi + ;; + 2) + print_warning "Continuing on $current_branch - changes will commit directly" + return 0 + ;; + 3|*) + print_info "Cancelled" + return 1 + ;; + esac +} + # Status command - check all installations cmd_status() { print_header "AI DevOps Framework Status" @@ -450,6 +536,11 @@ cmd_init() { return 1 fi + # Check for protected branch and offer worktree + if ! check_protected_branch "chore" "aidevops-init"; then + return 1 + fi + local project_root project_root=$(git rev-parse --show-toplevel) print_info "Project root: $project_root" @@ -786,6 +877,13 @@ cmd_upgrade_planning() { return 1 fi + # Check for protected branch and offer worktree (skip for dry-run) + if [[ "$dry_run" != "true" ]]; then + if ! check_protected_branch "chore" "upgrade-planning"; then + return 1 + fi + fi + local project_root project_root=$(git rev-parse --show-toplevel) From 3601fc7618f105f13fb29adb7ad73867d9b3f9e2 Mon Sep 17 00:00:00 2001 From: marcusquinn <6428977+marcusquinn@users.noreply.github.com> Date: Tue, 13 Jan 2026 02:32:55 +0000 Subject: [PATCH 4/4] fix: address CodeRabbit review feedback - Fix awk frontmatter stripping logic (was skipping all content) - Use temp file for awk output to avoid race condition on failure - Replace sed \n with awk for portable newline handling (BSD/macOS) - Only show backup restore hints when --no-backup not used - Add local declaration for choice variable --- aidevops.sh | 40 ++++++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/aidevops.sh b/aidevops.sh index 6ed548de..34cf08d6 100755 --- a/aidevops.sh +++ b/aidevops.sh @@ -101,6 +101,7 @@ check_protected_branch() { echo " 2. Continue on $current_branch (commits directly to main)" echo " 3. Cancel" echo "" + local choice read -r -p "Choice [1]: " choice choice="${choice:-1}" @@ -1017,8 +1018,14 @@ cmd_upgrade_planning() { fi # Copy template (strip YAML frontmatter - lines between first two ---) - awk 'BEGIN{skip=0} /^---$/{skip++; if(skip==2){skip=0; next}} skip<2{next} {print}' "$todo_template" > "$todo_file" 2>/dev/null || \ + # Use temp file to avoid race condition on failure + local temp_todo="${todo_file}.new" + if awk '/^---$/ && !p {c++; if(c==2) p=1; next} p' "$todo_template" > "$temp_todo" 2>/dev/null && [[ -s "$temp_todo" ]]; then + mv "$temp_todo" "$todo_file" + else + rm -f "$temp_todo" cp "$todo_template" "$todo_file" + fi # Update date placeholder sed -i.tmp "s/{{DATE}}/$(date +%Y-%m-%d)/" "$todo_file" 2>/dev/null || true @@ -1063,8 +1070,14 @@ cmd_upgrade_planning() { fi # Copy template (strip YAML frontmatter - lines between first two ---) - awk 'BEGIN{skip=0} /^---$/{skip++; if(skip==2){skip=0; next}} skip<2{next} {print}' "$plans_template" > "$plans_file" 2>/dev/null || \ + # Use temp file to avoid race condition on failure + local temp_plans="${plans_file}.new" + if awk '/^---$/ && !p {c++; if(c==2) p=1; next} p' "$plans_template" > "$temp_plans" 2>/dev/null && [[ -s "$temp_plans" ]]; then + mv "$temp_plans" "$plans_file" + else + rm -f "$temp_plans" cp "$plans_template" "$plans_file" + fi # Update date placeholder sed -i.tmp "s/{{DATE}}/$(date +%Y-%m-%d)/" "$plans_file" 2>/dev/null || true @@ -1098,11 +1111,16 @@ cmd_upgrade_planning() { jq --arg version "$aidevops_version" '.templates_version = $version' "$config_file" > "$temp_json" && \ mv "$temp_json" "$config_file" else - # Fallback to sed if jq not available + # Fallback using awk for portable newline handling (BSD sed doesn't support \n) if ! grep -q '"templates_version"' "$config_file" 2>/dev/null; then # Insert templates_version after version line - sed -i.tmp "s/\"version\": \"[^\"]*\"/\"version\": \"$aidevops_version\",\n \"templates_version\": \"$aidevops_version\"/" "$config_file" 2>/dev/null || true - rm -f "${config_file}.tmp" + local temp_json="${config_file}.tmp" + awk -v ver="$aidevops_version" ' + /"version":/ { + sub(/"version": "[^"]*"/, "\"version\": \"" ver "\",\n \"templates_version\": \"" ver "\"") + } + { print } + ' "$config_file" > "$temp_json" && mv "$temp_json" "$config_file" else # Update existing templates_version sed -i.tmp "s/\"templates_version\": \"[^\"]*\"/\"templates_version\": \"$aidevops_version\"/" "$config_file" 2>/dev/null || true @@ -1116,11 +1134,13 @@ cmd_upgrade_planning() { echo "Next steps:" echo " 1. Review the upgraded files" echo " 2. Verify your tasks were preserved" - echo " 3. Remove .bak files when satisfied" - echo "" - echo "If issues occurred, restore from backups:" - [[ "$todo_needs_upgrade" == "true" ]] && echo " mv TODO.md.bak TODO.md" - [[ "$plans_needs_upgrade" == "true" ]] && echo " mv todo/PLANS.md.bak todo/PLANS.md" + if [[ "$backup" == "true" ]]; then + echo " 3. Remove .bak files when satisfied" + echo "" + echo "If issues occurred, restore from backups:" + [[ "$todo_needs_upgrade" == "true" ]] && echo " mv TODO.md.bak TODO.md" + [[ "$plans_needs_upgrade" == "true" ]] && echo " mv todo/PLANS.md.bak todo/PLANS.md" + fi } # Features command - list available features