spec-and-loop 3.1.0 → 3.3.2
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/OPENSPEC-RALPH-BP.md +42 -6
- package/lib/mini-ralph/history.js +4 -0
- package/lib/mini-ralph/index.js +1 -0
- package/lib/mini-ralph/runner.js +1082 -19
- package/lib/mini-ralph/status.js +35 -0
- package/package.json +1 -1
- package/scripts/mini-ralph-cli.js +12 -0
- package/scripts/ralph-run.sh +122 -26
package/lib/mini-ralph/status.js
CHANGED
|
@@ -60,6 +60,31 @@ function render(ralphDir, tasksFile) {
|
|
|
60
60
|
lines.push(`Exit reason: ${loopState.exitReason}`);
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
const pendingDirtyPaths = _pendingDirtyPaths(loopState);
|
|
64
|
+
if (pendingDirtyPaths) {
|
|
65
|
+
lines.push('');
|
|
66
|
+
lines.push('--- Pending Dirty Paths ---');
|
|
67
|
+
lines.push(` Reason: ${pendingDirtyPaths.reason || 'blocked_handoff'}`);
|
|
68
|
+
if (pendingDirtyPaths.iteration) {
|
|
69
|
+
lines.push(` From iteration: ${pendingDirtyPaths.iteration}`);
|
|
70
|
+
}
|
|
71
|
+
const task = pendingDirtyPaths.taskNumber
|
|
72
|
+
? `${pendingDirtyPaths.taskNumber} ${pendingDirtyPaths.taskDescription || ''}`.trim()
|
|
73
|
+
: (pendingDirtyPaths.task || '');
|
|
74
|
+
if (task) {
|
|
75
|
+
lines.push(` Prior task: ${task}`);
|
|
76
|
+
}
|
|
77
|
+
const files = pendingDirtyPaths.files.slice(0, 10);
|
|
78
|
+
for (const file of files) {
|
|
79
|
+
lines.push(` - ${file}`);
|
|
80
|
+
}
|
|
81
|
+
if (pendingDirtyPaths.files.length > files.length) {
|
|
82
|
+
lines.push(` - (+${pendingDirtyPaths.files.length - files.length} more)`);
|
|
83
|
+
}
|
|
84
|
+
lines.push(' Resolve before continuing: commit with the same task, revert, or move to a separate change.');
|
|
85
|
+
lines.push('-'.repeat(50));
|
|
86
|
+
}
|
|
87
|
+
|
|
63
88
|
const latestCommitAnomaly = _latestCommitAnomaly(history.recent(ralphDir, 20));
|
|
64
89
|
if (latestCommitAnomaly) {
|
|
65
90
|
lines.push(`Commit issue: ${latestCommitAnomaly.commitAnomaly}`);
|
|
@@ -186,6 +211,16 @@ function _promptSummary(loopState) {
|
|
|
186
211
|
return '';
|
|
187
212
|
}
|
|
188
213
|
|
|
214
|
+
function _pendingDirtyPaths(loopState) {
|
|
215
|
+
const pending = loopState && loopState.pendingDirtyPaths;
|
|
216
|
+
if (!pending || typeof pending !== 'object') return null;
|
|
217
|
+
const files = Array.isArray(pending.files)
|
|
218
|
+
? pending.files.filter((file) => typeof file === 'string' && file.trim())
|
|
219
|
+
: [];
|
|
220
|
+
if (files.length === 0) return null;
|
|
221
|
+
return Object.assign({}, pending, { files });
|
|
222
|
+
}
|
|
223
|
+
|
|
189
224
|
/**
|
|
190
225
|
* Try to find a tasks file path from loop state.
|
|
191
226
|
*
|
package/package.json
CHANGED
|
@@ -22,6 +22,11 @@
|
|
|
22
22
|
* --stall-threshold <n> Halt after N consecutive no-op iterations (default: 3; 0 disables)
|
|
23
23
|
* --completion-promise <s> Completion promise string (default: COMPLETE)
|
|
24
24
|
* --task-promise <s> Task promise string (default: READY_FOR_NEXT_TASK)
|
|
25
|
+
* --blocked-handoff-promise <s>
|
|
26
|
+
* Blocked-handoff promise string (default: BLOCKED_HANDOFF).
|
|
27
|
+
* Loop exits cleanly with `blocked_handoff` when the
|
|
28
|
+
* agent emits this tag and writes the agent's note
|
|
29
|
+
* to <ralph-dir>/HANDOFF.md.
|
|
25
30
|
* --no-commit Suppress auto-commit
|
|
26
31
|
* --model <name> Optional model override
|
|
27
32
|
* --verbose Verbose output
|
|
@@ -53,6 +58,7 @@ function parseArgs(argv) {
|
|
|
53
58
|
stallThreshold: 3,
|
|
54
59
|
completionPromise: 'COMPLETE',
|
|
55
60
|
taskPromise: 'READY_FOR_NEXT_TASK',
|
|
61
|
+
blockedHandoffPromise: 'BLOCKED_HANDOFF',
|
|
56
62
|
noCommit: false,
|
|
57
63
|
model: '',
|
|
58
64
|
verbose: false,
|
|
@@ -101,6 +107,9 @@ function parseArgs(argv) {
|
|
|
101
107
|
case '--task-promise':
|
|
102
108
|
opts.taskPromise = args[++i];
|
|
103
109
|
break;
|
|
110
|
+
case '--blocked-handoff-promise':
|
|
111
|
+
opts.blockedHandoffPromise = args[++i];
|
|
112
|
+
break;
|
|
104
113
|
case '--no-commit':
|
|
105
114
|
opts.noCommit = true;
|
|
106
115
|
break;
|
|
@@ -154,6 +163,8 @@ Options:
|
|
|
154
163
|
--stall-threshold <n> Halt after N consecutive no-op iterations (default: 3; 0 disables)
|
|
155
164
|
--completion-promise <s> Completion promise string
|
|
156
165
|
--task-promise <s> Task promise string
|
|
166
|
+
--blocked-handoff-promise <s>
|
|
167
|
+
Blocked-handoff promise string (default: BLOCKED_HANDOFF)
|
|
157
168
|
--no-commit Suppress auto-commit
|
|
158
169
|
--model <name> Model override
|
|
159
170
|
--verbose Verbose output
|
|
@@ -212,6 +223,7 @@ async function main() {
|
|
|
212
223
|
stallThreshold: opts.stallThreshold,
|
|
213
224
|
completionPromise: opts.completionPromise,
|
|
214
225
|
taskPromise: opts.taskPromise,
|
|
226
|
+
blockedHandoffPromise: opts.blockedHandoffPromise,
|
|
215
227
|
noCommit: opts.noCommit,
|
|
216
228
|
model: opts.model,
|
|
217
229
|
verbose: opts.verbose,
|
package/scripts/ralph-run.sh
CHANGED
|
@@ -333,6 +333,19 @@ validate_dependencies() {
|
|
|
333
333
|
log_verbose "All dependencies validated"
|
|
334
334
|
}
|
|
335
335
|
|
|
336
|
+
should_auto_fix_artifacts() {
|
|
337
|
+
case "${RALPH_RUN_AUTO_FIX_ARTIFACTS:-}" in
|
|
338
|
+
1|true|TRUE|yes|YES)
|
|
339
|
+
return 0
|
|
340
|
+
;;
|
|
341
|
+
0|false|FALSE|no|NO)
|
|
342
|
+
return 1
|
|
343
|
+
;;
|
|
344
|
+
esac
|
|
345
|
+
|
|
346
|
+
[[ -t 0 ]]
|
|
347
|
+
}
|
|
348
|
+
|
|
336
349
|
ensure_artifacts_present() {
|
|
337
350
|
local change_dir="$1"
|
|
338
351
|
local change_name="$2"
|
|
@@ -351,6 +364,13 @@ ensure_artifacts_present() {
|
|
|
351
364
|
fi
|
|
352
365
|
|
|
353
366
|
log_info "Blocked artifacts detected: $blocked"
|
|
367
|
+
if ! should_auto_fix_artifacts; then
|
|
368
|
+
log_error "OpenSpec artifacts are blocked and this is a non-interactive run."
|
|
369
|
+
log_error 'Run `ralph-run init` or complete the artifacts manually, then rerun.'
|
|
370
|
+
log_error "Set RALPH_RUN_AUTO_FIX_ARTIFACTS=true to opt into opencode artifact repair in automation."
|
|
371
|
+
exit 1
|
|
372
|
+
fi
|
|
373
|
+
|
|
354
374
|
log_info "Invoking opencode to complete missing artifacts..."
|
|
355
375
|
|
|
356
376
|
opencode run "/opsx-ff $change_name" || true
|
|
@@ -651,15 +671,21 @@ validate_script_state() {
|
|
|
651
671
|
|
|
652
672
|
log_verbose "Validating script state..."
|
|
653
673
|
|
|
654
|
-
|
|
655
|
-
".ralph"
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
674
|
+
if [[ ! -d "$change_dir/.ralph" ]]; then
|
|
675
|
+
log_verbose "Required directory not found: .ralph (will be created)"
|
|
676
|
+
fi
|
|
677
|
+
|
|
678
|
+
if [[ ! -d "$change_dir/specs" ]]; then
|
|
679
|
+
log_error "Required directory not found: specs"
|
|
680
|
+
return 1
|
|
681
|
+
fi
|
|
682
|
+
|
|
683
|
+
local first_spec=""
|
|
684
|
+
first_spec=$(find "$change_dir/specs" -name "spec.md" -type f -print -quit 2>/dev/null || true)
|
|
685
|
+
if [[ -z "$first_spec" ]]; then
|
|
686
|
+
log_error "No spec.md files found under specs"
|
|
687
|
+
return 1
|
|
688
|
+
fi
|
|
663
689
|
|
|
664
690
|
local required_files=(
|
|
665
691
|
"tasks.md"
|
|
@@ -949,7 +975,20 @@ You are operating inside an automated loop. Follow these constraints EXACTLY:
|
|
|
949
975
|
1. Implement exactly ONE pending task from the task list /opsx-apply shows you.
|
|
950
976
|
2. After marking the task checkbox [x] on disk, output <promise>READY_FOR_NEXT_TASK</promise> on its own line.
|
|
951
977
|
3. If and only if EVERY task checkbox is [x], output <promise>COMPLETE</promise> instead.
|
|
952
|
-
4. Do not ask questions or wait for input. If
|
|
978
|
+
4. Do not ask questions or wait for input. If you cannot make progress on the current task because an external decision is required (revert protected drift outside the change scope, file an out-of-scope refactor, escalate to a human reviewer, etc.), STOP and emit a structured handoff in this exact form:
|
|
979
|
+
|
|
980
|
+
## Blocker Note
|
|
981
|
+
<one paragraph describing what is blocked>
|
|
982
|
+
|
|
983
|
+
## Why
|
|
984
|
+
<one paragraph: which task spec clause / invariant fired, and what evidence (file paths, hashes, test names) supports the diagnosis>
|
|
985
|
+
|
|
986
|
+
## Suggested Next Step
|
|
987
|
+
<one or two bullets the human can execute to unblock>
|
|
988
|
+
|
|
989
|
+
<promise>BLOCKED_HANDOFF</promise>
|
|
990
|
+
|
|
991
|
+
The runner will save this note to .ralph/HANDOFF.md and exit cleanly with reason=blocked_handoff. Do NOT keep retrying the same task; emit the handoff and stop. Do NOT emit BLOCKED_HANDOFF for transient errors that a retry could fix (network blips, tool-not-found that is fixable by an absolute path, etc.) — those are normal failures the loop will retry on its own.
|
|
953
992
|
5. If the task is already satisfied by prior work, still flip the checkbox to [x] before emitting the promise.
|
|
954
993
|
|
|
955
994
|
Do not create git commits yourself. The Ralph runner manages automatic task commits when auto-commit is enabled."
|
|
@@ -975,12 +1014,53 @@ Do not create git commits yourself. The Ralph runner manages automatic task comm
|
|
|
975
1014
|
mini_ralph_args+=("--quiet")
|
|
976
1015
|
fi
|
|
977
1016
|
|
|
978
|
-
# Run the internal mini Ralph CLI and capture output
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
wait
|
|
1017
|
+
# Run the internal mini Ralph CLI and capture output.
|
|
1018
|
+
#
|
|
1019
|
+
# Avoid Bash process substitution here. macOS ships Bash 3.2, and under
|
|
1020
|
+
# Bats' captured `run` wrapper a bare `wait` after `> >(tee ...)` can hang
|
|
1021
|
+
# after the node child has already exited. Explicit FIFOs give us concrete
|
|
1022
|
+
# tee PIDs to wait on and work consistently on macOS and Linux.
|
|
1023
|
+
local stdout_pipe="$output_dir/ralph-stdout.pipe"
|
|
1024
|
+
local stderr_pipe="$output_dir/ralph-stderr.pipe"
|
|
1025
|
+
local node_exit_code=0
|
|
1026
|
+
local tee_stdout_pid=""
|
|
1027
|
+
local tee_stderr_pid=""
|
|
1028
|
+
local had_errexit=false
|
|
1029
|
+
case $- in
|
|
1030
|
+
*e*)
|
|
1031
|
+
had_errexit=true
|
|
1032
|
+
set +e
|
|
1033
|
+
;;
|
|
1034
|
+
esac
|
|
1035
|
+
|
|
1036
|
+
if mkfifo "$stdout_pipe" "$stderr_pipe" 2>/dev/null; then
|
|
1037
|
+
tee "$stdout_log" < "$stdout_pipe" &
|
|
1038
|
+
tee_stdout_pid=$!
|
|
1039
|
+
tee "$stderr_log" < "$stderr_pipe" >&2 &
|
|
1040
|
+
tee_stderr_pid=$!
|
|
1041
|
+
|
|
1042
|
+
node "$MINI_RALPH_CLI" "${mini_ralph_args[@]}" > "$stdout_pipe" 2> "$stderr_pipe"
|
|
1043
|
+
node_exit_code=$?
|
|
1044
|
+
|
|
1045
|
+
wait "$tee_stdout_pid" 2>/dev/null || true
|
|
1046
|
+
wait "$tee_stderr_pid" 2>/dev/null || true
|
|
1047
|
+
rm -f "$stdout_pipe" "$stderr_pipe"
|
|
1048
|
+
else
|
|
1049
|
+
log_verbose "mkfifo unavailable; capturing output without live tee"
|
|
1050
|
+
node "$MINI_RALPH_CLI" "${mini_ralph_args[@]}" > "$stdout_log" 2> "$stderr_log"
|
|
1051
|
+
node_exit_code=$?
|
|
1052
|
+
if [[ -s "$stdout_log" ]]; then
|
|
1053
|
+
cat "$stdout_log"
|
|
1054
|
+
fi
|
|
1055
|
+
if [[ -s "$stderr_log" ]]; then
|
|
1056
|
+
cat "$stderr_log" >&2
|
|
1057
|
+
fi
|
|
1058
|
+
fi
|
|
1059
|
+
|
|
1060
|
+
if [[ "$had_errexit" == true ]]; then
|
|
1061
|
+
set -e
|
|
1062
|
+
fi
|
|
1063
|
+
|
|
984
1064
|
return $node_exit_code
|
|
985
1065
|
}
|
|
986
1066
|
|
|
@@ -1102,6 +1182,7 @@ rules:
|
|
|
1102
1182
|
tasks:
|
|
1103
1183
|
- Use the task template from OPENSPEC-RALPH-BP.md
|
|
1104
1184
|
- Each task has one dominant outcome and one verification cluster
|
|
1185
|
+
- Use surgical, scope-targeted validation commands; reserve broad gates for pre-flight baselines or final integration tasks
|
|
1105
1186
|
- Include explicit stop-and-hand-off conditions
|
|
1106
1187
|
design:
|
|
1107
1188
|
- Do not leave core policy choices unresolved
|
|
@@ -1123,6 +1204,7 @@ Before generating any OpenSpec artifacts, you MUST:
|
|
|
1123
1204
|
- Read `openspec/OPENSPEC-RALPH-BP.md` (Ralph Wiggum authoring guide)
|
|
1124
1205
|
- Verify proposals against the Ralph authoring checklist
|
|
1125
1206
|
- Ensure tasks use the task template with objective done-when conditions
|
|
1207
|
+
- Ensure each task uses the narrowest verifier that proves its scope; use broad gates only with baseline classification or final integration tasks
|
|
1126
1208
|
- Include explicit stop-and-hand-off conditions in every task
|
|
1127
1209
|
RALPH_AGENTS
|
|
1128
1210
|
log_verbose "Updated $agents_file with Ralph Wiggum compliance section"
|
|
@@ -1159,6 +1241,7 @@ check_ralphified() {
|
|
|
1159
1241
|
|
|
1160
1242
|
show_ralphify_warning() {
|
|
1161
1243
|
local change_name="$1"
|
|
1244
|
+
local preset_choice="${RALPH_RUN_RALPHIFY_CHOICE:-}"
|
|
1162
1245
|
|
|
1163
1246
|
cat >&2 << 'WARNING_BOX'
|
|
1164
1247
|
┌─────────────────────────────────────────────────────────────────────┐
|
|
@@ -1173,16 +1256,29 @@ show_ralphify_warning() {
|
|
|
1173
1256
|
└─────────────────────────────────────────────────────────────────────┘
|
|
1174
1257
|
WARNING_BOX
|
|
1175
1258
|
|
|
1259
|
+
if [[ -z "$preset_choice" && ! -t 0 ]]; then
|
|
1260
|
+
log_info "Non-interactive environment detected. Continuing without Ralph Wiggum configuration."
|
|
1261
|
+
log_info 'Run `ralph-run init` to configure Ralph Wiggum best practices before the next interactive run.'
|
|
1262
|
+
return 0
|
|
1263
|
+
fi
|
|
1264
|
+
|
|
1176
1265
|
while true; do
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1266
|
+
local choice=""
|
|
1267
|
+
if [[ -n "$preset_choice" ]]; then
|
|
1268
|
+
choice="$preset_choice"
|
|
1269
|
+
preset_choice=""
|
|
1270
|
+
log_info "Using RALPH_RUN_RALPHIFY_CHOICE=$choice"
|
|
1271
|
+
else
|
|
1272
|
+
echo "" >&2
|
|
1273
|
+
echo "Choose an option:" >&2
|
|
1274
|
+
echo " [A] Run ralphify init and redo the proposal, then continue" >&2
|
|
1275
|
+
echo " [C] Continue without init" >&2
|
|
1276
|
+
echo " [Q] Quit" >&2
|
|
1277
|
+
printf "Enter choice: " >&2
|
|
1278
|
+
if ! read -r choice; then
|
|
1279
|
+
log_info "Non-interactive environment detected. Continuing without Ralph Wiggum configuration."
|
|
1280
|
+
return 0
|
|
1281
|
+
fi
|
|
1186
1282
|
fi
|
|
1187
1283
|
|
|
1188
1284
|
case "$choice" in
|
|
@@ -1217,7 +1313,7 @@ WARNING_BOX
|
|
|
1217
1313
|
fi
|
|
1218
1314
|
local ralph_guidance=""
|
|
1219
1315
|
if [[ -f "$bp_file" ]]; then
|
|
1220
|
-
ralph_guidance=" When creating artifacts, read ${bp_file} and follow the Ralph Wiggum task template and authoring checklist. Ensure the proposal includes explicit scope, non-goals, first-rollout boundaries, and capabilities that map to Ralph-friendly tasks. Ensure tasks use the task template with objective done-when conditions and explicit stop-and-hand-off conditions. Do NOT restore or copy from any .bak backup files - write fresh artifacts from scratch."
|
|
1316
|
+
ralph_guidance=" When creating artifacts, read ${bp_file} and follow the Ralph Wiggum task template and authoring checklist. Ensure the proposal includes explicit scope, non-goals, first-rollout boundaries, and capabilities that map to Ralph-friendly tasks. Ensure tasks use the task template with objective done-when conditions, surgical scope-targeted verifier commands, and explicit stop-and-hand-off conditions. Prefer direct test-file or validator commands over full-suite commands; reserve broad gates for pre-flight baselines or final integration tasks. Do NOT restore or copy from any .bak backup files - write fresh artifacts from scratch."
|
|
1221
1317
|
fi
|
|
1222
1318
|
|
|
1223
1319
|
log_info "Invoking opencode to regenerate proposal and tasks with Ralph Wiggum best practices..."
|