spec-and-loop 1.0.7 → 2.0.0-rc.1

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.
@@ -13,6 +13,16 @@ detect_os() {
13
13
 
14
14
  detect_os
15
15
 
16
+ # Cross-platform file modification time
17
+ get_file_mtime() {
18
+ local file="$1"
19
+ if [[ "$OS" == "macOS" ]]; then
20
+ stat -f %m "$file" 2>/dev/null || echo 0
21
+ else
22
+ stat -c %Y "$file" 2>/dev/null || echo 0
23
+ fi
24
+ }
25
+
16
26
  # Cross-platform file modification time display
17
27
  get_file_mtime_display() {
18
28
  local file="$1"
@@ -23,20 +33,57 @@ get_file_mtime_display() {
23
33
  fi
24
34
  }
25
35
 
26
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
36
+ resolve_project_root() {
37
+ local git_root=""
38
+ git_root=$(git rev-parse --show-toplevel 2>/dev/null || echo "")
39
+
40
+ if [[ -n "$git_root" ]]; then
41
+ printf "%s" "$git_root"
42
+ else
43
+ pwd
44
+ fi
45
+ }
46
+
47
+ auto_detect_change() {
48
+ local project_root="$1"
49
+ local changes_dir="$project_root/openspec/changes"
50
+
51
+ if [[ ! -d "$changes_dir" ]]; then
52
+ echo ""
53
+ return 1
54
+ fi
55
+
56
+ local latest_change=""
57
+ local latest_time=0
58
+
59
+ for change_dir in "$changes_dir"/*; do
60
+ if [[ -d "$change_dir" && -f "$change_dir/tasks.md" ]]; then
61
+ local mod_time
62
+ mod_time=$(get_file_mtime "$change_dir/tasks.md")
63
+
64
+ if [[ "$mod_time" -gt "$latest_time" ]]; then
65
+ latest_time="$mod_time"
66
+ latest_change=$(basename "$change_dir")
67
+ fi
68
+ fi
69
+ done
70
+
71
+ printf "%s" "$latest_change"
72
+ }
73
+
74
+ PROJECT_ROOT="$(resolve_project_root)"
27
75
  CHANGE_NAME="${1:-auto-detect}"
28
76
 
29
77
  if [[ "$CHANGE_NAME" == "auto-detect" ]]; then
30
- # Auto-detect most recent change
31
- CHANGE_NAME=$(ls -t openspec/changes/ 2>/dev/null | head -1)
78
+ CHANGE_NAME=$(auto_detect_change "$PROJECT_ROOT")
32
79
  if [[ -z "$CHANGE_NAME" ]]; then
33
- echo "No changes found in openspec/changes/"
80
+ echo "No changes found in $PROJECT_ROOT/openspec/changes/"
34
81
  exit 1
35
82
  fi
36
83
  fi
37
84
 
38
- TASKS_FILE="$SCRIPT_DIR/../openspec/changes/$CHANGE_NAME/tasks.md"
39
- RALPH_STATE="$SCRIPT_DIR/../.ralph/ralph-loop.state.json"
85
+ TASKS_FILE="$PROJECT_ROOT/openspec/changes/$CHANGE_NAME/tasks.md"
86
+ RALPH_STATE="$PROJECT_ROOT/.ralph/ralph-loop.state.json"
40
87
 
41
88
  if [[ ! -f "$TASKS_FILE" ]]; then
42
89
  echo "Tasks file not found: $TASKS_FILE"
@@ -1,14 +1,7 @@
1
1
  #!/bin/bash
2
2
 
3
- set -e
4
-
5
3
  VERSION="1.0.0"
6
4
 
7
- # Add Bun to PATH if installed
8
- if [[ -d "$HOME/.bun/bin" ]]; then
9
- export PATH="$HOME/.bun/bin:$PATH"
10
- fi
11
-
12
5
  # Detect OS for cross-platform compatibility
13
6
  detect_os() {
14
7
  case "$(uname -s)" in
@@ -61,8 +54,62 @@ get_realpath() {
61
54
  fi
62
55
  }
63
56
 
57
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
58
+ LOCAL_NODE_BIN="$SCRIPT_DIR/../node_modules/.bin"
59
+ # Allow tests to inject a mock by setting MINI_RALPH_CLI_OVERRIDE in the environment
60
+ MINI_RALPH_CLI="${MINI_RALPH_CLI_OVERRIDE:-$SCRIPT_DIR/mini-ralph-cli.js}"
61
+
62
+ if [[ -d "$LOCAL_NODE_BIN" ]]; then
63
+ case ":$PATH:" in
64
+ *":$LOCAL_NODE_BIN:"*) ;;
65
+ *) export PATH="$LOCAL_NODE_BIN:$PATH" ;;
66
+ esac
67
+ fi
68
+
69
+ get_temp_root() {
70
+ local temp_root="${TMPDIR:-/tmp}"
71
+ temp_root="${temp_root%/}"
72
+
73
+ if [[ -z "$temp_root" ]]; then
74
+ temp_root="/tmp"
75
+ fi
76
+
77
+ printf "%s" "$temp_root"
78
+ }
79
+
80
+ make_temp_dir() {
81
+ local prefix="${1:-ralph-run}"
82
+ local temp_root
83
+ temp_root=$(get_temp_root)
84
+
85
+ local temp_dir=""
86
+ temp_dir=$(mktemp -d "${temp_root}/${prefix}-XXXXXX" 2>/dev/null) || \
87
+ temp_dir=$(mktemp -d -t "$prefix" 2>/dev/null) || \
88
+ temp_dir=""
89
+
90
+ if [[ -n "$temp_dir" ]]; then
91
+ printf "%s" "$temp_dir"
92
+ return 0
93
+ fi
94
+
95
+ local fallback_dir="${temp_root}/${prefix}-$(date +"%Y%m%d_%H%M%S")-$$"
96
+ mkdir -p "$fallback_dir"
97
+ printf "%s" "$fallback_dir"
98
+ }
99
+
100
+ resolve_ralph_command() {
101
+ if [[ -f "$MINI_RALPH_CLI" ]] && command -v node >/dev/null 2>&1; then
102
+ return 0
103
+ fi
104
+ return 1
105
+ }
106
+
64
107
  CHANGE_NAME=""
65
108
  MAX_ITERATIONS=""
109
+ NO_COMMIT=false
110
+ SHOW_STATUS=false
111
+ ADD_CONTEXT=""
112
+ CLEAR_CONTEXT=false
66
113
  ERROR_OCCURRED=false
67
114
  CLEANUP_IN_PROGRESS=false
68
115
 
@@ -77,12 +124,10 @@ cleanup() {
77
124
  local exit_code=$1
78
125
  log_info "Cleaning up..."
79
126
 
80
- # NOTE: We do NOT kill ralph/bun processes here because:
81
- # 1. Ralph runs synchronously in the foreground
127
+ # NOTE: We do NOT kill node processes here because:
128
+ # 1. The mini Ralph runtime runs synchronously in the foreground
82
129
  # 2. Ctrl+C (SIGINT) naturally propagates to child processes
83
- # 3. Using pkill -f "bun" is DANGEROUS - it matches gnome-session-binary!
84
- # 4. Using pkill -f "ralph" could kill other user processes
85
- # The shell's process group handling ensures clean termination.
130
+ # 3. The shell's process group handling ensures clean termination.
86
131
 
87
132
  if [[ $exit_code -ne 0 ]]; then
88
133
  log_error "Script terminated with exit code: $exit_code"
@@ -90,7 +135,9 @@ cleanup() {
90
135
  exit $exit_code
91
136
  }
92
137
 
93
- trap 'cleanup $?' EXIT INT TERM QUIT
138
+ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
139
+ trap 'cleanup $?' EXIT INT TERM QUIT
140
+ fi
94
141
 
95
142
  handle_error() {
96
143
  local exit_code=$1
@@ -113,20 +160,29 @@ USAGE:
113
160
  OPTIONS:
114
161
  --change <name> Specify the OpenSpec change to execute (default: auto-detect)
115
162
  --max-iterations <n> Maximum iterations for Ralph loop (default: 50)
163
+ --no-commit Suppress automatic git commits during the loop
116
164
  --verbose, -v Enable verbose mode for debugging
117
165
  --help, -h Show this help message
118
166
 
167
+ OBSERVABILITY AND CONTROL:
168
+ --status Print the current loop status dashboard and exit
169
+ --add-context <text> Add pending context to inject into the next iteration and exit
170
+ --clear-context Clear any pending context and exit
171
+
119
172
  EXAMPLES:
120
173
  ralph-run # Auto-detect most recent change
121
174
  ralph-run --change my-feature # Execute specific change
122
- ralph-run --max-iterations 100 # Limit loop to 100 iterations
175
+ ralph-run --change my-feature --max-iterations 100
176
+ ralph-run --change my-feature --no-commit # Run without auto-committing
123
177
  ralph-run --verbose # Run with debug output
178
+ ralph-run --status # Check status of the active loop
179
+ ralph-run --add-context "Focus on error handling in module X"
180
+ ralph-run --clear-context # Remove pending context
124
181
 
125
182
  PREREQUISITES:
126
183
  - Git repository (git init)
127
- - OpenSpec artifacts created (openspec init, opsx-new, opsx-ff)
128
- - ralph CLI installed (npm install -g @th0rgal/ralph-wiggum)
129
- - opencode CLI installed (npm install -g opencode)
184
+ - OpenSpec artifacts created (openspec init, openspec new, openspec ff)
185
+ - opencode CLI installed (npm install -g opencode-ai)
130
186
 
131
187
  EOF
132
188
  }
@@ -142,10 +198,26 @@ parse_arguments() {
142
198
  MAX_ITERATIONS="$2"
143
199
  shift 2
144
200
  ;;
201
+ --no-commit)
202
+ NO_COMMIT=true
203
+ shift
204
+ ;;
145
205
  --verbose|-v)
146
206
  VERBOSE=true
147
207
  shift
148
208
  ;;
209
+ --status)
210
+ SHOW_STATUS=true
211
+ shift
212
+ ;;
213
+ --add-context)
214
+ ADD_CONTEXT="$2"
215
+ shift 2
216
+ ;;
217
+ --clear-context)
218
+ CLEAR_CONTEXT=true
219
+ shift
220
+ ;;
149
221
  --help|-h)
150
222
  SHOW_HELP=true
151
223
  shift
@@ -193,21 +265,28 @@ validate_git_repository() {
193
265
  validate_dependencies() {
194
266
  log_verbose "Validating dependencies..."
195
267
 
196
- # Check for ralph
197
- if ! command -v ralph &> /dev/null; then
198
- log_error "ralph CLI not found."
199
- log_error "Please install open-ralph-wiggum: npm install -g @th0rgal/ralph-wiggum"
268
+ if ! resolve_ralph_command; then
269
+ log_error "Internal mini Ralph runtime not found: $MINI_RALPH_CLI"
270
+ log_error "Ensure node is installed and spec-and-loop dependencies are up to date (npm install)."
200
271
  exit 1
201
272
  fi
202
- log_verbose "Found: ralph"
273
+ log_verbose "Found internal mini Ralph runtime: $MINI_RALPH_CLI"
203
274
 
204
275
  # Check for opencode
205
276
  if ! command -v opencode &> /dev/null; then
206
277
  log_error "opencode CLI not found."
207
- log_error "Please install opencode: npm install -g opencode"
278
+ log_error "Please install opencode: npm install -g opencode-ai"
208
279
  exit 1
209
280
  fi
210
281
  log_verbose "Found: opencode"
282
+
283
+ # Check for jq
284
+ if ! command -v jq &> /dev/null; then
285
+ log_error "jq CLI not found."
286
+ log_error "Please install jq: brew install jq / apt-get install jq"
287
+ exit 1
288
+ fi
289
+ log_verbose "Found: jq"
211
290
 
212
291
  log_verbose "All dependencies validated"
213
292
  }
@@ -388,7 +467,7 @@ parse_tasks() {
388
467
  ((line_number++)) || true
389
468
 
390
469
  if [[ "$line" == "- [ ]"* ]]; then
391
- local task_desc="${line#- [ ] }"
470
+ local task_desc="${line#- \[ \] }"
392
471
  TASKS+=("$task_desc")
393
472
  TASK_IDS+=("$line_number")
394
473
  log_verbose "Found incomplete task (line $line_number): $task_desc"
@@ -626,7 +705,7 @@ sync_tasks_to_ralph() {
626
705
  rm "$old_ralph_tasks_file"
627
706
  fi
628
707
 
629
- # Use symlink so Ralph and openspec-apply-change work on the SAME file
708
+ # Use a symlink so the loop runtime always works against the OpenSpec tasks file
630
709
  # Ensure parent directory for ralph_tasks_file exists
631
710
  mkdir -p "$(dirname "$ralph_tasks_file")"
632
711
 
@@ -678,6 +757,10 @@ Include full context from openspec artifacts in {{change_dir}}:
678
757
 
679
758
  {{tasks}}
680
759
 
760
+ ## Fresh Task Context
761
+
762
+ {{task_context}}
763
+
681
764
  ## Instructions
682
765
 
683
766
  1. **Identify** current task:
@@ -685,15 +768,15 @@ Include full context from openspec artifacts in {{change_dir}}:
685
768
  - If no task is in progress, pick the first task marked as [ ] (incomplete)
686
769
  - Mark the task as [/] in the tasks file before starting work
687
770
 
688
- 2. **Implement** task using openspec-apply-change:
689
- - Use the /opsx-apply skill to implement the current task
690
- - Read the relevant openspec artifacts for context (proposal.md, design.md, specs)
691
- - Follow the openspec workflow to complete the task
692
- - The openspec-apply-change skill will implement changes and update task status automatically
771
+ 2. **Implement** the current task directly:
772
+ - Read the relevant OpenSpec artifacts for context (proposal.md, design.md, specs)
773
+ - Make the smallest maintainable change that fully satisfies the current task
774
+ - Run the most relevant validation or tests for the task before claiming completion
693
775
 
694
776
  3. **Complete** task:
695
777
  - Verify that the implementation meets the requirements
696
778
  - When the task is successfully completed, mark it as [x] in the tasks file
779
+ - Create a git commit using the required format below
697
780
  - Output: `<promise>{{task_promise}}</promise>`
698
781
 
699
782
  4. **Continue** to the next task:
@@ -703,7 +786,9 @@ Include full context from openspec artifacts in {{change_dir}}:
703
786
  ## Critical Rules
704
787
 
705
788
  - Work on ONE task at a time from the task list
706
- - Use openspec-apply-change (/opsx-apply) for implementation
789
+ - Read the full tasks file every iteration; do not rely on memory from prior iterations
790
+ - Do not rely on editor-specific slash commands or local-only skills; follow this prompt directly
791
+ - Treat tasks.md as the only source of truth for task state
707
792
  - ONLY output `<promise>{{task_promise}}</promise>` when the current task is complete and marked as [x]
708
793
  - ONLY output `<promise>{{completion_promise}}</promise>` when ALL tasks are [x]
709
794
  - Output promise tags DIRECTLY - do not quote them, explain them, or say you "will" output them
@@ -815,12 +900,12 @@ get_current_task_context() {
815
900
  while IFS= read -r line; do
816
901
  if [[ "$line" =~ ^-\ \[/\] ]]; then
817
902
  # Found in-progress task - extract description
818
- current_task_desc="${line#- [/] }"
903
+ current_task_desc="${line#- \[/\] }"
819
904
  found_task=true
820
905
  break
821
906
  elif [[ "$line" =~ ^-\ \[\ \] ]] && [[ "$found_task" == "false" ]]; then
822
907
  # Found incomplete task - extract description
823
- current_task_desc="${line#- [ ] }"
908
+ current_task_desc="${line#- \[ \] }"
824
909
  found_task=true
825
910
  break
826
911
  fi
@@ -854,11 +939,9 @@ setup_output_capture() {
854
939
 
855
940
  log_verbose "Setting up output capture..."
856
941
 
857
- # Create timestamped output directory in /tmp
858
- local timestamp=$(date +"%Y%m%d_%H%M%S")
859
- local output_dir="/tmp/ralph-run-$timestamp"
860
-
861
- mkdir -p "$output_dir"
942
+ # Use the system temp directory so macOS and Linux both work naturally.
943
+ local output_dir
944
+ output_dir=$(make_temp_dir "ralph-run")
862
945
  log_info "Output directory: $output_dir"
863
946
 
864
947
  # Store output directory path in Ralph directory for reference
@@ -870,8 +953,11 @@ setup_output_capture() {
870
953
  cleanup_old_output() {
871
954
  log_verbose "Cleaning up old Ralph output directories..."
872
955
 
956
+ local temp_root
957
+ temp_root=$(get_temp_root)
958
+
873
959
  # Keep last 3 Ralph output directories, delete older ones
874
- find /tmp -type d -name "ralph-run-*" -mtime +7d 2>/dev/null | while read old_dir; do
960
+ find "$temp_root" -type d -name "ralph-run*" -mtime +7 2>/dev/null | while IFS= read -r old_dir; do
875
961
  log_verbose "Removing old output directory: $old_dir"
876
962
  rm -rf "$old_dir"
877
963
  done
@@ -881,14 +967,18 @@ execute_ralph_loop() {
881
967
  local change_dir="$1"
882
968
  local ralph_dir="$2"
883
969
  local max_iterations="${3:-50}"
970
+ local no_commit="${4:-false}"
884
971
 
885
- log_info "Starting Ralph Wiggum loop with open-ralph-wiggum..."
972
+ log_info "Starting internal mini Ralph loop..."
886
973
  log_info "Max iterations: $max_iterations"
887
974
  log_info "Change directory: $change_dir"
975
+ if [[ "$no_commit" == true ]]; then
976
+ log_info "Auto-commit disabled (--no-commit)"
977
+ fi
888
978
 
889
- if ! command -v ralph &> /dev/null; then
890
- log_error "ralph CLI not found."
891
- log_error "Please install open-ralph-wiggum: npm install -g @th0rgal/ralph-wiggum"
979
+ if ! resolve_ralph_command; then
980
+ log_error "Internal mini Ralph runtime not found: $MINI_RALPH_CLI"
981
+ log_error "Ensure node is installed and spec-and-loop dependencies are up to date (npm install)."
892
982
  return 1
893
983
  fi
894
984
 
@@ -906,51 +996,132 @@ execute_ralph_loop() {
906
996
  prd_content=$(generate_prd "$change_dir")
907
997
  echo "$prd_content" > "$ralph_dir/PRD.md"
908
998
 
909
- # Get current task context for Ralph to use in commits
910
- local task_context
911
- task_context=$(get_current_task_context "$change_dir")
912
-
913
- # Create context injection file for Ralph
914
- local context_file="$ralph_dir/.context_injection"
915
- if [[ -n "$task_context" ]]; then
916
- log_verbose "Writing task context injection..."
917
- echo "$task_context" > "$context_file"
918
- fi
919
-
920
- # Restore Ralph state from tasks.md before running
921
- restore_ralph_state_from_tasks "$change_dir/tasks.md"
922
-
923
- # Initialize context injections file for Ralph to read context
924
- initialize_context_injections "$ralph_dir"
925
-
926
999
  # Output files
927
1000
  local stdout_log="$output_dir/ralph-stdout.log"
928
1001
  local stderr_log="$output_dir/ralph-stderr.log"
929
1002
 
930
- log_info "Delegating to ralph CLI..."
1003
+ log_info "Invoking internal mini Ralph runtime..."
931
1004
  log_info "Capturing output to: $output_dir"
932
1005
 
933
- # Run Ralph and capture output to both console and files
1006
+ # Build the mini-ralph-cli arguments
1007
+ local mini_ralph_args=(
1008
+ "--prompt-file" "$ralph_dir/PRD.md"
1009
+ "--prompt-template" "$template_file"
1010
+ "--ralph-dir" "$ralph_dir"
1011
+ "--tasks-file" "$change_dir/tasks.md"
1012
+ "--tasks"
1013
+ "--max-iterations" "$max_iterations"
1014
+ )
1015
+
1016
+ if [[ "$no_commit" == true ]]; then
1017
+ mini_ralph_args+=("--no-commit")
1018
+ fi
1019
+
1020
+ if [[ "$VERBOSE" == true ]]; then
1021
+ mini_ralph_args+=("--verbose")
1022
+ fi
1023
+
1024
+ # Run the internal mini Ralph CLI and capture output
934
1025
  {
935
- ralph --prompt-file "$ralph_dir/PRD.md" \
936
- --agent opencode \
937
- --tasks \
938
- --max-iterations "$max_iterations" \
939
- --prompt-template "$template_file" \
940
- --verbose-tools
1026
+ node "$MINI_RALPH_CLI" "${mini_ralph_args[@]}"
941
1027
  } > >(tee "$stdout_log") 2> >(tee "$stderr_log")
942
1028
 
943
1029
  return $?
944
1030
  }
945
1031
 
946
1032
 
1033
+ # ---------------------------------------------------------------------------
1034
+ # Observability and control commands
1035
+ #
1036
+ # These commands delegate to the internal mini-ralph-cli.js for status,
1037
+ # context management, and other loop controls without running the full loop.
1038
+ # ---------------------------------------------------------------------------
1039
+
1040
+ run_observability_command() {
1041
+ local change_name="$1"
1042
+ local command="$2"
1043
+ local arg="$3"
1044
+
1045
+ if [[ ! -f "$MINI_RALPH_CLI" ]]; then
1046
+ log_error "Internal mini-ralph-cli.js not found: $MINI_RALPH_CLI"
1047
+ exit 1
1048
+ fi
1049
+
1050
+ if [[ ! -x "$(command -v node)" ]]; then
1051
+ log_error "node is required but not found in PATH."
1052
+ exit 1
1053
+ fi
1054
+
1055
+ local change_dir="openspec/changes/$change_name"
1056
+ local ralph_dir="$change_dir/.ralph"
1057
+
1058
+ case "$command" in
1059
+ status)
1060
+ local tasks_file="$change_dir/tasks.md"
1061
+ local tasks_arg=""
1062
+ if [[ -f "$tasks_file" ]]; then
1063
+ tasks_arg="--tasks-file $tasks_file"
1064
+ fi
1065
+ # shellcheck disable=SC2086
1066
+ node "$MINI_RALPH_CLI" --ralph-dir "$ralph_dir" --status $tasks_arg
1067
+ ;;
1068
+ add-context)
1069
+ node "$MINI_RALPH_CLI" --ralph-dir "$ralph_dir" --add-context "$arg"
1070
+ ;;
1071
+ clear-context)
1072
+ node "$MINI_RALPH_CLI" --ralph-dir "$ralph_dir" --clear-context
1073
+ ;;
1074
+ *)
1075
+ log_error "Unknown observability command: $command"
1076
+ exit 1
1077
+ ;;
1078
+ esac
1079
+ }
947
1080
 
948
1081
  main() {
1082
+ set -e
949
1083
  parse_arguments "$@"
950
1084
 
951
1085
  log_verbose "Starting ralph-run v$VERSION"
952
1086
  log_verbose "Change name: ${CHANGE_NAME:-<auto-detect>}"
953
-
1087
+
1088
+ # Resolve change name first for observability commands that need it
1089
+ if [[ -z "$CHANGE_NAME" ]] && ( [[ "$SHOW_STATUS" == true ]] || [[ -n "$ADD_CONTEXT" ]] || [[ "$CLEAR_CONTEXT" == true ]] ); then
1090
+ validate_git_repository
1091
+ CHANGE_NAME=$(auto_detect_change)
1092
+ log_verbose "Auto-detected change for observability: $CHANGE_NAME"
1093
+ fi
1094
+
1095
+ # Handle observability commands (status, add-context, clear-context)
1096
+ # These exit early without running the full loop.
1097
+ if [[ "$SHOW_STATUS" == true ]]; then
1098
+ if [[ -z "$CHANGE_NAME" ]]; then
1099
+ validate_git_repository
1100
+ CHANGE_NAME=$(auto_detect_change)
1101
+ fi
1102
+ run_observability_command "$CHANGE_NAME" "status"
1103
+ exit $?
1104
+ fi
1105
+
1106
+ if [[ -n "$ADD_CONTEXT" ]]; then
1107
+ if [[ -z "$CHANGE_NAME" ]]; then
1108
+ validate_git_repository
1109
+ CHANGE_NAME=$(auto_detect_change)
1110
+ fi
1111
+ run_observability_command "$CHANGE_NAME" "add-context" "$ADD_CONTEXT"
1112
+ exit $?
1113
+ fi
1114
+
1115
+ if [[ "$CLEAR_CONTEXT" == true ]]; then
1116
+ if [[ -z "$CHANGE_NAME" ]]; then
1117
+ validate_git_repository
1118
+ CHANGE_NAME=$(auto_detect_change)
1119
+ fi
1120
+ run_observability_command "$CHANGE_NAME" "clear-context"
1121
+ exit $?
1122
+ fi
1123
+
1124
+ # Normal loop execution path
954
1125
  validate_git_repository
955
1126
  validate_dependencies
956
1127
 
@@ -978,9 +1149,11 @@ main() {
978
1149
 
979
1150
  local max_iterations="${MAX_ITERATIONS:-50}"
980
1151
 
981
- execute_ralph_loop "$change_dir" "$ralph_dir" "$max_iterations"
1152
+ execute_ralph_loop "$change_dir" "$ralph_dir" "$max_iterations" "$NO_COMMIT"
982
1153
 
983
1154
  log_info "ralph-run.sh initialized successfully"
984
1155
  }
985
1156
 
986
- main "$@"
1157
+ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
1158
+ main "$@"
1159
+ fi
package/scripts/setup.js CHANGED
@@ -4,40 +4,48 @@ const fs = require('fs');
4
4
  const path = require('path');
5
5
  const { execSync } = require('child_process');
6
6
 
7
- console.log('Setting up spec-and-loop...');
7
+ function runSetup() {
8
+ console.log('Setting up spec-and-loop...');
8
9
 
9
- // Get the installation directory
10
- const installDir = __dirname;
11
- const ralphRunScript = path.join(installDir, 'ralph-run.sh');
10
+ // Get the installation directory
11
+ const installDir = __dirname;
12
+ const ralphRunScript = path.join(installDir, 'ralph-run.sh');
12
13
 
13
- console.log(`Installation directory: ${installDir}`);
14
+ console.log(`Installation directory: ${installDir}`);
14
15
 
15
- // Make ralph-run.sh executable
16
- if (fs.existsSync(ralphRunScript)) {
17
- try {
18
- execSync(`chmod +x "${ralphRunScript}"`);
19
- console.log('✓ Made ralph-run.sh executable');
20
- } catch (err) {
21
- console.warn(`Could not make ralph-run.sh executable: ${err.message}`);
16
+ // Make ralph-run.sh executable
17
+ if (fs.existsSync(ralphRunScript)) {
18
+ try {
19
+ execSync(`chmod +x "${ralphRunScript}"`, { stdio: 'inherit' });
20
+ console.log('✓ Made ralph-run.sh executable');
21
+ } catch (err) {
22
+ console.warn(`Could not make ralph-run.sh executable: ${err.message}`);
23
+ }
24
+ } else {
25
+ console.error(`Error: ralph-run.sh not found at ${ralphRunScript}`);
26
+ process.exit(1);
22
27
  }
23
- } else {
24
- console.error(`Error: ralph-run.sh not found at ${ralphRunScript}`);
25
- process.exit(1);
28
+
29
+ console.log('');
30
+ console.log('spec-and-loop setup complete!');
31
+ console.log('');
32
+ console.log('Usage:');
33
+ console.log(' cd /path/to/your/project');
34
+ console.log(' openspec init # Initialize OpenSpec');
35
+ console.log(' openspec new <name> # Create a new change');
36
+ console.log(' openspec ff <name> # Fast-forward artifacts');
37
+ console.log(' ralph-run --change <name> # Run ralph loop');
38
+ console.log(' ralph-run # Auto-detect change');
39
+ console.log('');
40
+ console.log('Prerequisites:');
41
+ console.log(' - openspec CLI: npm install -g openspec');
42
+ console.log(' - opencode CLI: npm install -g opencode-ai');
43
+ console.log(' - jq CLI: apt install jq / brew install jq');
44
+ console.log(' - git: git init');
45
+ }
46
+
47
+ if (require.main === module) {
48
+ runSetup();
26
49
  }
27
50
 
28
- console.log('');
29
- console.log('spec-and-loop setup complete!');
30
- console.log('');
31
- console.log('Usage:');
32
- console.log(' cd /path/to/your/project');
33
- console.log(' openspec init # Initialize OpenSpec');
34
- console.log(' openspec new <name> # Create a new change');
35
- console.log(' openspec ff <name> # Fast-forward artifacts');
36
- console.log(' ralph-run --change <name> # Run ralph loop');
37
- console.log(' ralph-run # Auto-detect change');
38
- console.log('');
39
- console.log('Prerequisites:');
40
- console.log(' - openspec CLI: npm install -g openspec');
41
- console.log(' - opencode CLI: npm install -g opencode');
42
- console.log(' - jq CLI: apt install jq / brew install jq');
43
- console.log(' - git: git init');
51
+ module.exports = { runSetup };