spec-and-loop 3.3.2 → 3.3.4

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": "3.3.2",
3
+ "version": "3.3.4",
4
4
  "description": "OpenSpec + Ralph Loop integration for iterative development with opencode",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -27,6 +27,17 @@
27
27
  * Loop exits cleanly with `blocked_handoff` when the
28
28
  * agent emits this tag and writes the agent's note
29
29
  * to <ralph-dir>/HANDOFF.md.
30
+ * --auto-resolve-handoffs Enable bounded continuation attempts for
31
+ * explicit, safe BLOCKED_HANDOFF classes
32
+ * --no-auto-resolve-handoffs
33
+ * Disable auto-resolution even when enabled by env
34
+ * --no-self-heal Disable supervisor self-heal (env: RALPH_SELF_HEAL=0)
35
+ * --self-heal-max-tries <n> Supervisor tries per blocker (env: RALPH_SELF_HEAL_MAX_TRIES)
36
+ * --no-self-heal-downstream Disable downstream supervisor patches (env: RALPH_SELF_HEAL_DOWNSTREAM=0)
37
+ * --no-self-heal-hints Disable supervisor investigation hints (env: RALPH_SELF_HEAL_HINTS=0)
38
+ * --no-self-heal-log-access Disable supervisor log-path injection (env: RALPH_SELF_HEAL_LOG_ACCESS=0)
39
+ * --self-heal-verbose Enable supervisor debug logging (env: RALPH_SELF_HEAL_VERBOSE=1)
40
+ * --no-self-heal-verbose Disable supervisor debug logging, even with --verbose
30
41
  * --no-commit Suppress auto-commit
31
42
  * --model <name> Optional model override
32
43
  * --verbose Verbose output
@@ -44,6 +55,11 @@ const miniRalph = require('../lib/mini-ralph/index');
44
55
  // Argument parsing
45
56
  // ---------------------------------------------------------------------------
46
57
 
58
+ function _envFlagDefaultEnabled(value) {
59
+ if (value === undefined) return true;
60
+ return !/^(0|false|no|off)$/i.test(String(value || '').trim());
61
+ }
62
+
47
63
  function parseArgs(argv) {
48
64
  const args = argv.slice(2);
49
65
  const opts = {
@@ -59,6 +75,13 @@ function parseArgs(argv) {
59
75
  completionPromise: 'COMPLETE',
60
76
  taskPromise: 'READY_FOR_NEXT_TASK',
61
77
  blockedHandoffPromise: 'BLOCKED_HANDOFF',
78
+ autoResolveHandoffs: _envFlagDefaultEnabled(process.env.RALPH_AUTO_RESOLVE_HANDOFFS),
79
+ selfHeal: null,
80
+ selfHealMaxTries: null,
81
+ selfHealDownstream: null,
82
+ selfHealHints: null,
83
+ selfHealLogAccess: null,
84
+ selfHealVerbose: null,
62
85
  noCommit: false,
63
86
  model: '',
64
87
  verbose: false,
@@ -110,6 +133,33 @@ function parseArgs(argv) {
110
133
  case '--blocked-handoff-promise':
111
134
  opts.blockedHandoffPromise = args[++i];
112
135
  break;
136
+ case '--auto-resolve-handoffs':
137
+ opts.autoResolveHandoffs = true;
138
+ break;
139
+ case '--no-auto-resolve-handoffs':
140
+ opts.autoResolveHandoffs = false;
141
+ break;
142
+ case '--no-self-heal':
143
+ opts.selfHeal = false;
144
+ break;
145
+ case '--self-heal-max-tries':
146
+ opts.selfHealMaxTries = parseInt(args[++i], 10);
147
+ break;
148
+ case '--no-self-heal-downstream':
149
+ opts.selfHealDownstream = false;
150
+ break;
151
+ case '--no-self-heal-hints':
152
+ opts.selfHealHints = false;
153
+ break;
154
+ case '--no-self-heal-log-access':
155
+ opts.selfHealLogAccess = false;
156
+ break;
157
+ case '--self-heal-verbose':
158
+ opts.selfHealVerbose = true;
159
+ break;
160
+ case '--no-self-heal-verbose':
161
+ opts.selfHealVerbose = false;
162
+ break;
113
163
  case '--no-commit':
114
164
  opts.noCommit = true;
115
165
  break;
@@ -164,7 +214,16 @@ Options:
164
214
  --completion-promise <s> Completion promise string
165
215
  --task-promise <s> Task promise string
166
216
  --blocked-handoff-promise <s>
167
- Blocked-handoff promise string (default: BLOCKED_HANDOFF)
217
+ Blocked-handoff promise string (default: BLOCKED_HANDOFF)
218
+ --auto-resolve-handoffs Enable bounded continuation for explicit safe handoffs
219
+ --no-auto-resolve-handoffs Disable bounded continuation for explicit safe handoffs
220
+ --no-self-heal Disable supervisor self-heal (env: RALPH_SELF_HEAL=0)
221
+ --self-heal-max-tries <n> Supervisor tries per blocker (env: RALPH_SELF_HEAL_MAX_TRIES)
222
+ --no-self-heal-downstream Disable downstream supervisor patches (env: RALPH_SELF_HEAL_DOWNSTREAM=0)
223
+ --no-self-heal-hints Disable supervisor investigation hints (env: RALPH_SELF_HEAL_HINTS=0)
224
+ --no-self-heal-log-access Disable supervisor log-path injection (env: RALPH_SELF_HEAL_LOG_ACCESS=0)
225
+ --self-heal-verbose Enable supervisor debug logging (env: RALPH_SELF_HEAL_VERBOSE=1)
226
+ --no-self-heal-verbose Disable supervisor debug logging, even with --verbose
168
227
  --no-commit Suppress auto-commit
169
228
  --model <name> Model override
170
229
  --verbose Verbose output
@@ -224,6 +283,13 @@ async function main() {
224
283
  completionPromise: opts.completionPromise,
225
284
  taskPromise: opts.taskPromise,
226
285
  blockedHandoffPromise: opts.blockedHandoffPromise,
286
+ autoResolveHandoffs: opts.autoResolveHandoffs,
287
+ selfHeal: opts.selfHeal,
288
+ selfHealMaxTries: opts.selfHealMaxTries,
289
+ selfHealDownstream: opts.selfHealDownstream,
290
+ selfHealHints: opts.selfHealHints,
291
+ selfHealLogAccess: opts.selfHealLogAccess,
292
+ selfHealVerbose: opts.selfHealVerbose,
227
293
  noCommit: opts.noCommit,
228
294
  model: opts.model,
229
295
  verbose: opts.verbose,
@@ -251,4 +317,11 @@ async function main() {
251
317
  }
252
318
  }
253
319
 
254
- main();
320
+ if (require.main === module) {
321
+ main();
322
+ }
323
+
324
+ module.exports = {
325
+ _envFlagDefaultEnabled,
326
+ _parseArgs: parseArgs,
327
+ };
@@ -129,6 +129,13 @@ resolve_ralph_command() {
129
129
  CHANGE_NAME=""
130
130
  MAX_ITERATIONS=""
131
131
  NO_COMMIT=false
132
+ AUTO_RESOLVE_HANDOFFS=""
133
+ SELF_HEAL=""
134
+ SELF_HEAL_MAX_TRIES=""
135
+ SELF_HEAL_DOWNSTREAM=""
136
+ SELF_HEAL_HINTS=""
137
+ SELF_HEAL_LOG_ACCESS=""
138
+ SELF_HEAL_VERBOSE=""
132
139
  SHOW_STATUS=false
133
140
  SHOW_VERSION=false
134
141
  ADD_CONTEXT=""
@@ -137,6 +144,12 @@ SUBCOMMAND=""
137
144
  ERROR_OCCURRED=false
138
145
  CLEANUP_IN_PROGRESS=false
139
146
 
147
+ # Tracks the per-run temp directory created by setup_output_capture so the
148
+ # EXIT trap can remove it. Without this each invocation leaves a stale
149
+ # `$TMPDIR/ralph-run-XXXXXX` directory behind and they accumulate quickly
150
+ # (we observed ~1k stale dirs on a single workstation).
151
+ RALPH_RUN_TEMP_DIR=""
152
+
140
153
  # Trap signals for proper cleanup
141
154
  cleanup() {
142
155
  # Prevent multiple cleanup calls
@@ -152,7 +165,16 @@ cleanup() {
152
165
  # 1. The mini Ralph runtime runs synchronously in the foreground
153
166
  # 2. Ctrl+C (SIGINT) naturally propagates to child processes
154
167
  # 3. The shell's process group handling ensures clean termination.
155
-
168
+
169
+ # Remove the per-run temp directory created by setup_output_capture.
170
+ # Path-shape guard: only delete dirs whose name starts with `ralph-run-`
171
+ # so an accidental empty/aliased value cannot wipe an unrelated path.
172
+ if [[ -n "$RALPH_RUN_TEMP_DIR" \
173
+ && -d "$RALPH_RUN_TEMP_DIR" \
174
+ && "$(basename "$RALPH_RUN_TEMP_DIR")" == ralph-run-* ]]; then
175
+ rm -rf "$RALPH_RUN_TEMP_DIR" 2>/dev/null || true
176
+ fi
177
+
156
178
  if [[ $exit_code -ne 0 ]]; then
157
179
  log_error "Script terminated with exit code: $exit_code"
158
180
  fi
@@ -186,6 +208,19 @@ OPTIONS:
186
208
  --change <name> Specify the OpenSpec change to execute (default: auto-detect)
187
209
  --max-iterations <n> Maximum iterations for Ralph loop (default: 50)
188
210
  --no-commit Suppress automatic git commits during the loop
211
+ --auto-resolve-handoffs Enable bounded continuation for explicit safe handoffs
212
+ --no-auto-resolve-handoffs
213
+ Disable bounded continuation for explicit safe handoffs
214
+ --no-self-heal Disable supervisor self-heal (env: RALPH_SELF_HEAL=0)
215
+ --self-heal-max-tries <n>
216
+ Supervisor tries per blocker (env: RALPH_SELF_HEAL_MAX_TRIES)
217
+ --no-self-heal-downstream
218
+ Disable downstream supervisor patches (env: RALPH_SELF_HEAL_DOWNSTREAM=0)
219
+ --no-self-heal-hints Disable supervisor investigation hints (env: RALPH_SELF_HEAL_HINTS=0)
220
+ --no-self-heal-log-access
221
+ Disable supervisor log-path injection (env: RALPH_SELF_HEAL_LOG_ACCESS=0)
222
+ --self-heal-verbose Enable supervisor debug logging (env: RALPH_SELF_HEAL_VERBOSE=1)
223
+ --no-self-heal-verbose Disable supervisor debug logging, even with --verbose
189
224
  --verbose, -v Enable verbose mode for debugging
190
225
  --quiet Suppress the per-iteration progress stream
191
226
  --version Print the version and exit
@@ -232,6 +267,42 @@ parse_arguments() {
232
267
  NO_COMMIT=true
233
268
  shift
234
269
  ;;
270
+ --auto-resolve-handoffs)
271
+ AUTO_RESOLVE_HANDOFFS=true
272
+ shift
273
+ ;;
274
+ --no-auto-resolve-handoffs)
275
+ AUTO_RESOLVE_HANDOFFS=false
276
+ shift
277
+ ;;
278
+ --no-self-heal)
279
+ SELF_HEAL=false
280
+ shift
281
+ ;;
282
+ --self-heal-max-tries)
283
+ SELF_HEAL_MAX_TRIES="$2"
284
+ shift 2
285
+ ;;
286
+ --no-self-heal-downstream)
287
+ SELF_HEAL_DOWNSTREAM=false
288
+ shift
289
+ ;;
290
+ --no-self-heal-hints)
291
+ SELF_HEAL_HINTS=false
292
+ shift
293
+ ;;
294
+ --no-self-heal-log-access)
295
+ SELF_HEAL_LOG_ACCESS=false
296
+ shift
297
+ ;;
298
+ --self-heal-verbose)
299
+ SELF_HEAL_VERBOSE=true
300
+ shift
301
+ ;;
302
+ --no-self-heal-verbose)
303
+ SELF_HEAL_VERBOSE=false
304
+ shift
305
+ ;;
235
306
  --verbose|-v)
236
307
  VERBOSE=true
237
308
  shift
@@ -916,7 +987,10 @@ setup_output_capture() {
916
987
  local output_dir
917
988
  output_dir=$(make_temp_dir "ralph-run")
918
989
  log_info "Output directory: $output_dir"
919
-
990
+
991
+ # Track for cleanup() so the EXIT trap can remove it.
992
+ RALPH_RUN_TEMP_DIR="$output_dir"
993
+
920
994
  # Store output directory path in Ralph directory for reference
921
995
  echo "$output_dir" > "$ralph_dir/.output_dir"
922
996
 
@@ -1006,6 +1080,38 @@ Do not create git commits yourself. The Ralph runner manages automatic task comm
1006
1080
  mini_ralph_args+=("--no-commit")
1007
1081
  fi
1008
1082
 
1083
+ if [[ "$AUTO_RESOLVE_HANDOFFS" == true ]]; then
1084
+ mini_ralph_args+=("--auto-resolve-handoffs")
1085
+ elif [[ "$AUTO_RESOLVE_HANDOFFS" == false ]]; then
1086
+ mini_ralph_args+=("--no-auto-resolve-handoffs")
1087
+ fi
1088
+
1089
+ if [[ "$SELF_HEAL" == false ]]; then
1090
+ mini_ralph_args+=("--no-self-heal")
1091
+ fi
1092
+
1093
+ if [[ -n "$SELF_HEAL_MAX_TRIES" ]]; then
1094
+ mini_ralph_args+=("--self-heal-max-tries" "$SELF_HEAL_MAX_TRIES")
1095
+ fi
1096
+
1097
+ if [[ "$SELF_HEAL_DOWNSTREAM" == false ]]; then
1098
+ mini_ralph_args+=("--no-self-heal-downstream")
1099
+ fi
1100
+
1101
+ if [[ "$SELF_HEAL_HINTS" == false ]]; then
1102
+ mini_ralph_args+=("--no-self-heal-hints")
1103
+ fi
1104
+
1105
+ if [[ "$SELF_HEAL_LOG_ACCESS" == false ]]; then
1106
+ mini_ralph_args+=("--no-self-heal-log-access")
1107
+ fi
1108
+
1109
+ if [[ "$SELF_HEAL_VERBOSE" == true ]]; then
1110
+ mini_ralph_args+=("--self-heal-verbose")
1111
+ elif [[ "$SELF_HEAL_VERBOSE" == false ]]; then
1112
+ mini_ralph_args+=("--no-self-heal-verbose")
1113
+ fi
1114
+
1009
1115
  if [[ "$VERBOSE" == true ]]; then
1010
1116
  mini_ralph_args+=("--verbose")
1011
1117
  fi
@@ -1184,6 +1290,7 @@ rules:
1184
1290
  - Each task has one dominant outcome and one verification cluster
1185
1291
  - Use surgical, scope-targeted validation commands; reserve broad gates for pre-flight baselines or final integration tasks
1186
1292
  - Include explicit stop-and-hand-off conditions
1293
+ - Run the OPENSPEC-RALPH-BP "Pre-loop scope-handoff pre-scan" against tasks.md before handing it to ralph-run; remediate every finding (dangling file paths, missing sections, scope/verifier mismatches, unclassified pre-existing failures, subjective stop conditions, unflagged manual-only tasks, cross-task scope conflicts)
1187
1294
  design:
1188
1295
  - Do not leave core policy choices unresolved
1189
1296
  - Specify algorithms, config shapes, and failure semantics
@@ -1206,6 +1313,18 @@ Before generating any OpenSpec artifacts, you MUST:
1206
1313
  - Ensure tasks use the task template with objective done-when conditions
1207
1314
  - Ensure each task uses the narrowest verifier that proves its scope; use broad gates only with baseline classification or final integration tasks
1208
1315
  - Include explicit stop-and-hand-off conditions in every task
1316
+
1317
+ Before handing `tasks.md` to `ralph-run` (whether you just authored it or just edited it), you MUST run the **Pre-loop scope-handoff pre-scan** from `openspec/OPENSPEC-RALPH-BP.md` against every pending `- [ ]` task. For each pending task, statically verify:
1318
+
1319
+ 1. Every file path in `Scope:` / `Done when:` / `Stop and hand off if:` resolves on disk (`ls`, `git ls-files`).
1320
+ 2. Every referenced section/heading exists in its named document with the exact heading text (`rg "^## <heading>$" <file>`).
1321
+ 3. The verifier's actual reach matches the `Scope:` statement; broaden scope or narrow the verifier when they disagree.
1322
+ 4. Multi-file gates that may hit pre-existing failures enumerate them in a "Pre-existing unrelated failures" sub-section with file:line references and a "do not stop on these" clause.
1323
+ 5. `Stop and hand off if:` conditions are objective (grep-able evidence, exact class/selector names) — never subjective ("looks wrong", "cannot be explained").
1324
+ 6. Any task requiring human-in-browser verification, deployed-URL checks, or visual judgment is tagged `[manual]` in its title with an explicit "manual verification required — emit BLOCKED_HANDOFF with verification template" stop condition.
1325
+ 7. No two pending tasks claim ownership of the same file/route/symbol; no task's `Stop and hand off if:` would trigger on the normal completion of a later task.
1326
+
1327
+ Remediate every finding by editing `tasks.md` directly, then re-run `openspec validate <change>` before starting the loop. If you cannot remediate a finding (it requires a product/policy decision), surface it to the user instead of starting the loop.
1209
1328
  RALPH_AGENTS
1210
1329
  log_verbose "Updated $agents_file with Ralph Wiggum compliance section"
1211
1330
  else
@@ -0,0 +1,134 @@
1
+ # Supervisor Prompt
2
+
3
+ You are the supervisor agent for the embedded Ralph loop.
4
+
5
+ Your job is to repair `tasks.md` structure, not to edit source code. You may propose a replacement body for the current pending task and optional downstream task patches when they share the same structural cause. You may also emit read-only investigation hints for the implementer.
6
+
7
+ ## Required Template Variables
8
+
9
+ The runner renders this template with every variable below:
10
+
11
+ - `{{blocker_note}}`
12
+ - `{{current_task_number}}`
13
+ - `{{current_task_body}}`
14
+ - `{{downstream_tasks}}`
15
+ - `{{handoff_history}}`
16
+ - `{{recent_iterations}}`
17
+ - `{{try_index}}`
18
+ - `{{previous_supervisor_attempts}}`
19
+ - `{{openspec_config_rules}}`
20
+ - `{{ralph_authoring_rules}}`
21
+ - `{{change_proposal}}`
22
+ - `{{change_design}}`
23
+ - `{{run_stdout_log_path}}`
24
+ - `{{run_stderr_log_path}}`
25
+
26
+ `{{tasks_md_path}}` and `{{blocker_hash}}` are intentionally not provided.
27
+
28
+ ## Structured Inputs
29
+
30
+ ### Blocker Note
31
+
32
+ {{blocker_note}}
33
+
34
+ ### Current Task Number
35
+
36
+ {{current_task_number}}
37
+
38
+ ### Current Task Body
39
+
40
+ {{current_task_body}}
41
+
42
+ ### Downstream Tasks
43
+
44
+ {{downstream_tasks}}
45
+
46
+ ### Handoff History
47
+
48
+ {{handoff_history}}
49
+
50
+ ### Recent Iterations
51
+
52
+ {{recent_iterations}}
53
+
54
+ ### Supervisor Try Index
55
+
56
+ {{try_index}}
57
+
58
+ ### Previous Supervisor Attempts
59
+
60
+ {{previous_supervisor_attempts}}
61
+
62
+ ### OpenSpec Config Rules
63
+
64
+ {{openspec_config_rules}}
65
+
66
+ ### Ralph Authoring Rules
67
+
68
+ {{ralph_authoring_rules}}
69
+
70
+ ### Change Proposal
71
+
72
+ {{change_proposal}}
73
+
74
+ ### Change Design
75
+
76
+ {{change_design}}
77
+
78
+ ### Optional Run Log Paths
79
+
80
+ stdout: `{{run_stdout_log_path}}`
81
+
82
+ stderr: `{{run_stderr_log_path}}`
83
+
84
+ If those paths are non-empty, you may read at most the tail 8 KiB needed to disambiguate a blocker. Treat them as read-only.
85
+
86
+ ## Response Contract
87
+
88
+ Emit exactly one JSON object inside a fenced `supervisor-response` block.
89
+
90
+ - `current_task_patch`: object or `null`
91
+ - `downstream_patches`: array
92
+ - `investigation_hints`: array
93
+ - `summary`: string
94
+ - `downstream_rationale`: string
95
+
96
+ When `current_task_patch` is an object, it must contain:
97
+
98
+ - `task_number`: numbered task identifier such as `2.3`
99
+ - `new_body`: full replacement markdown for the target task body
100
+ - `rationale`: one paragraph explaining the repair
101
+
102
+ Each `downstream_patches[]` entry may contain:
103
+
104
+ - `task_number`: existing numbered task to modify
105
+ - `operation`: `modify`, `insert_before`, or `insert_after`
106
+ - `anchor_task_number`: required for insert operations
107
+ - `new_body`: replacement or inserted task markdown
108
+ - `rationale`: similarity rationale for the downstream patch
109
+
110
+ Each `investigation_hints[]` entry must contain:
111
+
112
+ - `path`: repository-relative file path to read
113
+ - `rationale`: read-only explanation for why the implementer should inspect it
114
+
115
+ ## Example Response
116
+
117
+ ```supervisor-response
118
+ {
119
+ "current_task_patch": {
120
+ "task_number": "2.3",
121
+ "new_body": "- [ ] 2.3 **Define and document the supervisor I/O contract (request fields + response JSON shape)**\n - Scope: `scripts/supervisor-prompt.md`, `lib/mini-ralph/supervisor.js` (parser stub only)\n - Change: Define the template variables and parser contract.\n - Done when:\n - `scripts/supervisor-prompt.md` documents the required variables\n - `lib/mini-ralph/supervisor.js` exports the parser stub\n - focused parser coverage passes\n - Stop and hand off if:\n - the response shape conflicts with the OpenCode surface",
122
+ "rationale": "Freeze the supervisor contract before later orchestration and prompt-rendering work."
123
+ },
124
+ "downstream_patches": [],
125
+ "investigation_hints": [
126
+ {
127
+ "path": "lib/mini-ralph/tasks.js",
128
+ "rationale": "Read task parsing helpers before changing task-number validation."
129
+ }
130
+ ],
131
+ "summary": "Define the supervisor prompt and parser contract so later supervisor tasks can build against a stable schema.",
132
+ "downstream_rationale": ""
133
+ }
134
+ ```