spec-and-loop 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spec-and-loop",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "OpenSpec + Ralph Loop integration for iterative development with opencode",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -0,0 +1,96 @@
1
+ #!/bin/bash
2
+
3
+ set -e
4
+
5
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6
+ CHANGE_NAME="${1:-auto-detect}"
7
+
8
+ if [[ "$CHANGE_NAME" == "auto-detect" ]]; then
9
+ # Auto-detect most recent change
10
+ CHANGE_NAME=$(ls -t openspec/changes/ 2>/dev/null | head -1)
11
+ if [[ -z "$CHANGE_NAME" ]]; then
12
+ echo "No changes found in openspec/changes/"
13
+ exit 1
14
+ fi
15
+ fi
16
+
17
+ TASKS_FILE="$SCRIPT_DIR/../openspec/changes/$CHANGE_NAME/tasks.md"
18
+ RALPH_STATE="$SCRIPT_DIR/../.ralph/ralph-loop.state.json"
19
+
20
+ if [[ ! -f "$TASKS_FILE" ]]; then
21
+ echo "Tasks file not found: $TASKS_FILE"
22
+ exit 1
23
+ fi
24
+
25
+ clear
26
+ while true; do
27
+ clear
28
+
29
+ echo "======================================================================="
30
+ echo " Ralph Progress Monitor - Change: $CHANGE_NAME"
31
+ echo "======================================================================="
32
+ echo ""
33
+
34
+ # Progress bar
35
+ total=$(grep -c "^- \[" "$TASKS_FILE" 2>/dev/null) || total=0
36
+ completed=$(grep -c "^- \[x\]" "$TASKS_FILE" 2>/dev/null) || completed=0
37
+ in_progress=$(grep -c "^- \[\/\]" "$TASKS_FILE" 2>/dev/null) || in_progress=0
38
+
39
+ remaining=$((total - completed - in_progress))
40
+
41
+ if [[ $total -gt 0 ]]; then
42
+ progress=$((completed * 100 / total))
43
+ echo " Progress: $completed/$total tasks completed ($progress%)"
44
+ echo " Status: $in_progress in progress, $remaining remaining"
45
+
46
+ # Simple progress bar
47
+ filled=$((completed * 50 / total))
48
+ bar=$(printf '#%.0s' $(seq 1 $filled))
49
+ empty=$(printf '.%.0s' $(seq 1 $((50 - filled))))
50
+ echo " |$bar$empty|"
51
+ fi
52
+ echo ""
53
+
54
+ # Ralph state
55
+ if [[ -f "$RALPH_STATE" ]]; then
56
+ echo " Ralph State:"
57
+ jq -r '
58
+ " Iteration: \(.iteration)/\(.maxIterations)",
59
+ " Active: \(.active)",
60
+ " Mode: \(.tasksMode // "N/A")",
61
+ " Prompt: \(.completionPromise // "N/A")"
62
+ ' "$RALPH_STATE" 2>/dev/null || echo " Unable to read state"
63
+ echo ""
64
+ fi
65
+
66
+ # Current task
67
+ echo " Current Task:"
68
+ current_task=$(grep -n "^- \[\/\]" "$TASKS_FILE" 2>/dev/null | head -1)
69
+ if [[ -n "$current_task" ]]; then
70
+ echo " $current_task"
71
+ else
72
+ # Show next incomplete task
73
+ next_task=$(grep -n "^- \[ \]" "$TASKS_FILE" 2>/dev/null | head -1)
74
+ if [[ -n "$next_task" ]]; then
75
+ echo " Next: $next_task"
76
+ else
77
+ echo " No current task (idle)"
78
+ fi
79
+ fi
80
+ echo ""
81
+
82
+ # Recent completed tasks (last 3)
83
+ echo " Recently Completed:"
84
+ grep "^- \[x\]" "$TASKS_FILE" 2>/dev/null | tail -3 | sed 's/^- \[x\] / /'
85
+ echo ""
86
+
87
+ # File status
88
+ echo " Last Updated: $(stat -c "%y" "$TASKS_FILE" 2>/dev/null | cut -d'.' -f1)"
89
+ echo ""
90
+
91
+ echo "======================================================================="
92
+ echo " Press Ctrl+C to exit | Refreshing in 2 seconds..."
93
+ echo "======================================================================="
94
+
95
+ sleep 2
96
+ done
@@ -74,7 +74,7 @@ PREREQUISITES:
74
74
  - Git repository (git init)
75
75
  - OpenSpec artifacts created (openspec init, opsx-new, opsx-ff)
76
76
  - ralph CLI installed (npm install -g @th0rgal/ralph-wiggum)
77
- - opencode CLI installed (npm install -g opencode-ai)
77
+ - opencode CLI installed (npm install -g opencode)
78
78
 
79
79
  EOF
80
80
  }
@@ -152,7 +152,7 @@ validate_dependencies() {
152
152
  # Check for opencode
153
153
  if ! command -v opencode &> /dev/null; then
154
154
  log_error "opencode CLI not found."
155
- log_error "Please install opencode: npm install -g opencode-ai"
155
+ log_error "Please install opencode: npm install -g opencode"
156
156
  exit 1
157
157
  fi
158
158
  log_verbose "Found: opencode"
@@ -265,9 +265,9 @@ read_openspec_artifacts() {
265
265
  log_verbose "Read design.md"
266
266
  fi
267
267
 
268
- declare -g OPENSPEC_PROPOSAL="$proposal_content"
269
- declare -g OPENSPEC_SPECS="$specs_content"
270
- declare -g OPENSPEC_DESIGN="$design_content"
268
+ OPENSPEC_PROPOSAL="$proposal_content"
269
+ OPENSPEC_SPECS="$specs_content"
270
+ OPENSPEC_DESIGN="$design_content"
271
271
  }
272
272
 
273
273
  generate_prd() {
@@ -287,7 +287,16 @@ generate_prd() {
287
287
  prd_content+="$OPENSPEC_SPECS"$'\n'$'\n'
288
288
 
289
289
  prd_content+="## Design"$'\n'$'\n'
290
- prd_content+="$OPENSPEC_DESIGN"$'\n'
290
+ prd_content+="$OPENSPEC_DESIGN"$'\n'$'\n'
291
+
292
+ # Add current task context for Ralph to use in commits
293
+ local task_context
294
+ task_context=$(get_current_task_context "$change_dir")
295
+
296
+ if [[ -n "$task_context" ]]; then
297
+ prd_content+="## Current Task Context"$'\n'$'\n'
298
+ prd_content+="$task_context"$'\n'$'\n'
299
+ fi
291
300
 
292
301
  echo "$prd_content"
293
302
  }
@@ -309,9 +318,9 @@ parse_tasks() {
309
318
 
310
319
  log_verbose "Parsing tasks from tasks.md..."
311
320
 
312
- declare -g TASKS=()
313
- declare -g TASK_IDS=()
314
- declare -g TASKS_MD5=""
321
+ TASKS=()
322
+ TASK_IDS=()
323
+ TASKS_MD5=""
315
324
 
316
325
  if [[ -f "$tasks_file" ]]; then
317
326
  TASKS_MD5=$(md5sum "$tasks_file" | cut -d' ' -f1)
@@ -319,8 +328,8 @@ parse_tasks() {
319
328
 
320
329
  log_verbose "Parsing tasks from tasks.md..."
321
330
 
322
- declare -g TASKS=()
323
- declare -g TASK_IDS=()
331
+ TASKS=()
332
+ TASK_IDS=()
324
333
 
325
334
  local line_number=0
326
335
  while IFS= read -r line; do
@@ -638,6 +647,42 @@ Include full context from openspec artifacts in {{change_dir}}:
638
647
  - If stuck, try a different approach
639
648
  - Check your work before claiming completion
640
649
 
650
+ ## CRITICAL: Git Commit Format (MANDATORY)
651
+
652
+ When making git commits, you MUST use this EXACT format:
653
+
654
+ ```
655
+ Ralph iteration <N>: <brief description of work completed>
656
+
657
+ Tasks completed:
658
+ - [x] <task.number> <task description text>
659
+ - [x] <task.number> <task description text>
660
+ ...
661
+ ```
662
+
663
+ **Requirements:**
664
+ 1. Use iteration number from Ralph's state (e.g., "Ralph iteration 7")
665
+ 2. Include a BRIEF description summarizing what was done
666
+ 3. List ALL completed tasks with their numbers and full descriptions
667
+ 4. Use the EXACT format: "- [x] <task.number> <task description>"
668
+ 5. Read the "## Completed Tasks for Git Commit" section from the PRD for the task list
669
+
670
+ **FORBIDDEN:**
671
+ - DO NOT use generic messages like "work in progress" or "iteration N"
672
+ - DO NOT skip task numbers
673
+ - DO NOT truncate task descriptions
674
+ - DO NOT create commits without task information
675
+
676
+ **Example:**
677
+ ```
678
+ Ralph iteration 7: Implement unit tests for response processing
679
+
680
+ Tasks completed:
681
+ - [x] 11.6 Write unit test for personality state management
682
+ - [x] 11.7 Write unit test for personality validation
683
+ - [x] 11.8 Write unit test for system prompt validation
684
+ ```
685
+
641
686
  {{context}}
642
687
  EOF
643
688
 
@@ -655,175 +700,190 @@ restore_ralph_state_from_tasks() {
655
700
  return 0
656
701
  fi
657
702
 
658
- # Count completed tasks in tasks.md
659
- local completed_count=$(grep -c "^- \[x\]" "$tasks_file" 2>/dev/null || echo "0")
660
- local next_iteration=$((completed_count + 1))
661
- log_verbose "Found $completed_count completed tasks in tasks.md, setting iteration to $next_iteration"
662
-
663
- # Update Ralph state to resume from completed task
664
- local updated_state
665
- updated_state=$(jq --argjson state "$(cat "$ralph_loop_file")" --arg iter "$next_iteration" '
666
- .iteration = $iter |
667
- .active = true
668
- ' 2>/dev/null)
703
+ # Read current iteration from state file - don't use completed task count
704
+ local current_iteration
705
+ current_iteration=$(jq -r '.iteration // 0' "$ralph_loop_file" 2>/dev/null || echo "0")
669
706
 
670
- if [[ -n "$updated_state" ]]; then
671
- log_verbose "Updated Ralph state: iteration set to $next_iteration"
672
- echo "$updated_state" > "$ralph_loop_file"
707
+ # Count completed tasks for informational purposes only
708
+ local completed_count=$(grep -c "^- \[x\]" "$tasks_file" 2>/dev/null || echo "0")
709
+ log_verbose "Found $completed_count completed tasks in tasks.md (iteration $current_iteration)"
710
+
711
+ # Read maxIterations from state file
712
+ local max_iterations
713
+ max_iterations=$(jq -r '.maxIterations // 50' "$ralph_loop_file" 2>/dev/null || echo "50")
714
+
715
+ # Only update iteration if it's 0 or missing (fresh start)
716
+ if [[ $current_iteration -eq 0 ]]; then
717
+ log_verbose "Setting initial iteration to 1 (max: $max_iterations)"
718
+ local updated_state
719
+ updated_state=$(jq --argjson state "$(cat "$ralph_loop_file")" '
720
+ .iteration = 1 |
721
+ .active = true
722
+ ' 2>/dev/null)
723
+
724
+ if [[ -n "$updated_state" ]]; then
725
+ echo "$updated_state" > "$ralph_loop_file"
726
+ fi
727
+ else
728
+ log_verbose "Ralph state preserved at iteration $current_iteration"
673
729
  fi
674
730
  }
675
731
 
676
- execute_ralph_loop() {
732
+ get_current_task_context() {
677
733
  local change_dir="$1"
678
- local ralph_dir="$2"
679
734
  local tasks_file="$change_dir/tasks.md"
680
- local max_iterations="${3:-50}"
681
735
 
682
- log_info "Starting Ralph Wiggum loop with open-ralph-wiggum..."
683
- log_info "Max iterations: $max_iterations"
684
- log_info "Change directory: $change_dir"
685
-
686
- if ! command -v ralph &> /dev/null; then
687
- log_error "ralph CLI not found."
688
- log_error "Please install open-ralph-wiggum: npm install -g @th0rgal/ralph-wiggum"
689
- return 1
736
+ if [[ ! -f "$tasks_file" ]]; then
737
+ echo ""
738
+ return
690
739
  fi
691
740
 
692
- local template_file="$ralph_dir/prompt-template.md"
741
+ log_verbose "Reading current task context from tasks.md..."
693
742
 
694
- sync_tasks_to_ralph "$change_dir" "$ralph_dir"
695
- create_prompt_template "$change_dir" "$template_file"
743
+ local context=""
744
+ local found_task=false
745
+ local all_completed_tasks=""
746
+ local current_task_desc=""
696
747
 
697
- local prd_content
698
- prd_content=$(generate_prd "$change_dir")
748
+ while IFS= read -r line; do
749
+ if [[ "$line" =~ ^-\ \[/\] ]]; then
750
+ # Found in-progress task - extract description
751
+ current_task_desc="${line#- [/] }"
752
+ found_task=true
753
+ break
754
+ elif [[ "$line" =~ ^-\ \[\ \] ]] && [[ "$found_task" == "false" ]]; then
755
+ # Found incomplete task - extract description
756
+ current_task_desc="${line#- [ ] }"
757
+ found_task=true
758
+ break
759
+ fi
760
+ done < "$tasks_file"
699
761
 
700
- # Restore Ralph state from tasks.md before running
701
- restore_ralph_state_from_tasks "$tasks_file"
762
+ # Also collect all completed tasks for commit message
763
+ local line_number=0
764
+ while IFS= read -r line; do
765
+ ((line_number++)) || true
766
+ if [[ "$line" =~ ^-\ \[x\] ]]; then
767
+ # Use the full line as-is to preserve task number and description
768
+ all_completed_tasks+="$line"$'\n'
769
+ fi
770
+ done < "$tasks_file"
702
771
 
703
- log_info "Delegating to ralph CLI..."
704
- ralph "$prd_content" \
705
- --agent opencode \
706
- --tasks \
707
- --max-iterations "$max_iterations" \
708
- --prompt-template "$template_file" \
709
- --verbose-tools
772
+ if [[ "$found_task" == "true" ]]; then
773
+ context="## Current Task"$'\n'
774
+ context+="- $current_task_desc"$'\n'
775
+ fi
710
776
 
711
- return $?
777
+ if [[ -n "$all_completed_tasks" ]]; then
778
+ context+="## Completed Tasks for Git Commit"$'\n'
779
+ context+="$all_completed_tasks"
780
+ fi
781
+
782
+ echo "$context"
712
783
  }
713
784
 
714
- initialize_tracking() {
785
+ setup_output_capture() {
715
786
  local ralph_dir="$1"
716
- local tracking_file="$ralph_dir/tracking.json"
717
787
 
718
- log_verbose "Initializing tracking..."
788
+ log_verbose "Setting up output capture..."
719
789
 
720
- if [[ ! -f "$tracking_file" ]]; then
721
- log_verbose "Creating tracking.json..."
722
- echo '{"tasks":{}}' > "$tracking_file"
723
- fi
790
+ # Create timestamped output directory in /tmp
791
+ local timestamp=$(date +"%Y%m%d_%H%M%S")
792
+ local output_dir="/tmp/ralph-run-$timestamp"
724
793
 
725
- echo "$tracking_file"
726
- }
727
-
728
- read_tracking() {
729
- local tracking_file="$1"
794
+ mkdir -p "$output_dir"
795
+ log_info "Output directory: $output_dir"
730
796
 
731
- log_verbose "Reading tracking.json..."
797
+ # Store output directory path in Ralph directory for reference
798
+ echo "$output_dir" > "$ralph_dir/.output_dir"
732
799
 
733
- if [[ -f "$tracking_file" ]]; then
734
- cat "$tracking_file"
735
- else
736
- echo '{"tasks":{}}'
737
- fi
800
+ echo "$output_dir"
738
801
  }
739
802
 
740
- update_task_checkbox() {
741
- local change_dir="$1"
742
- local task_id="$2"
743
- local complete="$3"
744
-
745
- log_verbose "Updating task checkbox (line $task_id, complete=$complete)..."
746
-
747
- local tasks_file="$change_dir/tasks.md"
748
- local temp_file=$(mktemp)
803
+ cleanup_old_output() {
804
+ log_verbose "Cleaning up old Ralph output directories..."
749
805
 
750
- local line_number=0
751
- while IFS= read -r line; do
752
- ((line_number++)) || true
753
-
754
- if [[ $line_number -eq $task_id ]]; then
755
- if [[ "$complete" == "true" ]]; then
756
- echo "${line/- \[ \]/- [x]}" >> "$temp_file"
757
- else
758
- echo "${line/- \[x\]/- [ ]}" >> "$temp_file"
759
- fi
760
- else
761
- echo "$line" >> "$temp_file"
762
- fi
763
- done < "$tasks_file"
764
-
765
- mv "$temp_file" "$tasks_file"
806
+ # Keep last 3 Ralph output directories, delete older ones
807
+ find /tmp -type d -name "ralph-run-*" -mtime +7d 2>/dev/null | while read old_dir; do
808
+ log_verbose "Removing old output directory: $old_dir"
809
+ rm -rf "$old_dir"
810
+ done
766
811
  }
767
812
 
768
- update_tracking() {
769
- local tracking_file="$1"
770
- local task_id="$2"
771
- local complete="$3"
813
+ execute_ralph_loop() {
814
+ local change_dir="$1"
815
+ local ralph_dir="$2"
816
+ local max_iterations="${3:-50}"
772
817
 
773
- log_verbose "Updating tracking.json (task $task_id, complete=$complete)..."
818
+ log_info "Starting Ralph Wiggum loop with open-ralph-wiggum..."
819
+ log_info "Max iterations: $max_iterations"
820
+ log_info "Change directory: $change_dir"
774
821
 
775
- local status="pending"
776
- if [[ "$complete" == "true" ]]; then
777
- status="complete"
822
+ if ! command -v ralph &> /dev/null; then
823
+ log_error "ralph CLI not found."
824
+ log_error "Please install open-ralph-wiggum: npm install -g @th0rgal/ralph-wiggum"
825
+ return 1
778
826
  fi
779
827
 
780
- local tracking_json
781
- tracking_json=$(cat "$tracking_file")
828
+ local template_file="$ralph_dir/prompt-template.md"
782
829
 
783
- local updated_json
784
- updated_json=$(echo "$tracking_json" | jq --arg id "$task_id" --arg status "$status" '.tasks[$id] = {status: $status}' 2>/dev/null || echo '{"tasks":{}}')
830
+ # Clean up old output directories and setup new one
831
+ cleanup_old_output
832
+ local output_dir=$(setup_output_capture "$ralph_dir")
785
833
 
786
- echo "$updated_json" > "$tracking_file"
787
- }
788
-
789
- update_task_status_atomic() {
790
- local change_dir="$1"
791
- local task_id="$2"
792
- local complete="$3"
793
- local tracking_file="$4"
834
+ sync_tasks_to_ralph "$change_dir" "$ralph_dir"
835
+ create_prompt_template "$change_dir" "$template_file"
836
+
837
+ # Generate PRD and write to file
838
+ local prd_content
839
+ prd_content=$(generate_prd "$change_dir")
840
+ echo "$prd_content" > "$ralph_dir/PRD.md"
794
841
 
795
- log_verbose "Updating task status atomically..."
842
+ # Get current task context for Ralph to use in commits
843
+ local task_context
844
+ task_context=$(get_current_task_context "$change_dir")
796
845
 
797
- local tasks_file="$change_dir/tasks.md"
798
- local tasks_backup="${tasks_file}.bak"
799
- local tracking_backup="${tracking_file}.bak"
846
+ # Create context injection file for Ralph
847
+ local context_file="$ralph_dir/.context_injection"
848
+ if [[ -n "$task_context" ]]; then
849
+ log_verbose "Writing task context injection..."
850
+ echo "$task_context" > "$context_file"
851
+ fi
800
852
 
801
- cp "$tasks_file" "$tasks_backup"
802
- cp "$tracking_file" "$tracking_backup"
853
+ # Restore Ralph state from tasks.md before running
854
+ restore_ralph_state_from_tasks "$change_dir/tasks.md"
803
855
 
804
- update_task_checkbox "$change_dir" "$task_id" "$complete"
805
- update_tracking "$tracking_file" "$task_id" "$complete"
856
+ # Initialize context injections file for Ralph to read context
857
+ initialize_context_injections "$ralph_dir"
806
858
 
807
- if [[ ! -f "$tasks_file" ]] || [[ ! -f "$tracking_file" ]]; then
808
- log_error "Update failed: files not found"
809
- mv "$tasks_backup" "$tasks_file"
810
- mv "$tracking_backup" "$tracking_file"
811
- return 1
812
- fi
859
+ # Output files
860
+ local stdout_log="$output_dir/ralph-stdout.log"
861
+ local stderr_log="$output_dir/ralph-stderr.log"
813
862
 
814
- rm "$tasks_backup"
815
- rm "$tracking_backup"
863
+ log_info "Delegating to ralph CLI..."
864
+ log_info "Capturing output to: $output_dir"
865
+
866
+ # Run Ralph and capture output to both console and files
867
+ {
868
+ ralph --prompt-file "$ralph_dir/PRD.md" \
869
+ --agent opencode \
870
+ --tasks \
871
+ --max-iterations "$max_iterations" \
872
+ --prompt-template "$template_file" \
873
+ --verbose-tools
874
+ } > >(tee "$stdout_log") 2> >(tee "$stderr_log")
816
875
 
817
- log_verbose "Atomic update successful"
818
- return 0
876
+ return $?
819
877
  }
820
878
 
879
+
880
+
821
881
  main() {
822
882
  parse_arguments "$@"
823
-
883
+
824
884
  log_verbose "Starting ralph-run v$VERSION"
825
885
  log_verbose "Change name: ${CHANGE_NAME:-<auto-detect>}"
826
-
886
+
827
887
  validate_git_repository
828
888
  validate_dependencies
829
889
 
@@ -836,7 +896,6 @@ main() {
836
896
  validate_openspec_artifacts "$change_dir"
837
897
  validate_script_state "$change_dir"
838
898
  local ralph_dir=$(setup_ralph_directory "$change_dir")
839
- local tracking_file=$(initialize_tracking "$ralph_dir")
840
899
 
841
900
  log_info "Change directory: $change_dir"
842
901
  log_info "Ralph directory: $ralph_dir"
package/scripts/setup.js CHANGED
@@ -37,7 +37,7 @@ console.log(' ralph-run --change <name> # Run ralph loop');
37
37
  console.log(' ralph-run # Auto-detect change');
38
38
  console.log('');
39
39
  console.log('Prerequisites:');
40
- console.log(' - openspec CLI: npm install -g @fission-ai/openspec@latest');
41
- console.log(' - opencode CLI: npm install -g opencode-ai (or see https://opencode.ai/install)');
40
+ console.log(' - openspec CLI: npm install -g openspec');
41
+ console.log(' - opencode CLI: npm install -g opencode');
42
42
  console.log(' - jq CLI: apt install jq / brew install jq');
43
43
  console.log(' - git: git init');