spec-and-loop 2.0.0-rc.1 → 2.0.0
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/lib/mini-ralph/invoker.js +62 -42
- package/package.json +1 -1
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
const { spawn, execFileSync } = require('child_process');
|
|
15
|
-
const path = require('path');
|
|
16
15
|
|
|
17
16
|
/**
|
|
18
17
|
* Invoke OpenCode with the given prompt and return a result object.
|
|
@@ -22,7 +21,7 @@ const path = require('path');
|
|
|
22
21
|
* @param {string} [opts.model] - Optional model override
|
|
23
22
|
* @param {boolean} [opts.noCommit] - Skip git commit if true
|
|
24
23
|
* @param {boolean} [opts.verbose] - Enable verbose output
|
|
25
|
-
* @param {string} opts.ralphDir
|
|
24
|
+
* @param {string} [opts.ralphDir] - Reserved for caller compatibility
|
|
26
25
|
* @returns {Promise<{stdout: string, exitCode: number, toolUsage: Array, filesChanged: Array}>}
|
|
27
26
|
*/
|
|
28
27
|
async function invoke(opts) {
|
|
@@ -31,52 +30,47 @@ async function invoke(opts) {
|
|
|
31
30
|
model,
|
|
32
31
|
noCommit = false,
|
|
33
32
|
verbose = false,
|
|
34
|
-
ralphDir,
|
|
35
33
|
} = opts;
|
|
36
34
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const tmpPromptFile = path.join(
|
|
41
|
-
os.tmpdir(),
|
|
42
|
-
`ralph-prompt-${Date.now()}-${process.pid}.md`
|
|
43
|
-
);
|
|
35
|
+
if (!prompt || !prompt.trim()) {
|
|
36
|
+
throw new Error('mini-ralph invoker: prompt is empty');
|
|
37
|
+
}
|
|
44
38
|
|
|
45
|
-
|
|
46
|
-
|
|
39
|
+
const args = ['run'];
|
|
40
|
+
if (model) {
|
|
41
|
+
args.push('--model', model);
|
|
42
|
+
}
|
|
43
|
+
args.push(prompt);
|
|
47
44
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
args.
|
|
51
|
-
|
|
45
|
+
if (verbose) {
|
|
46
|
+
process.stderr.write(
|
|
47
|
+
`[mini-ralph] invoking: opencode ${args.slice(0, -1).join(' ')} <prompt>\n`
|
|
48
|
+
);
|
|
49
|
+
}
|
|
52
50
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
51
|
+
// Snapshot git-tracked files before invocation for file-change detection
|
|
52
|
+
const preSnapshot = _gitSnapshot();
|
|
56
53
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
return {
|
|
67
|
-
stdout: result.stdout,
|
|
68
|
-
exitCode: result.exitCode,
|
|
69
|
-
toolUsage: _extractToolUsage(result.stdout),
|
|
70
|
-
filesChanged,
|
|
71
|
-
};
|
|
72
|
-
} finally {
|
|
73
|
-
// Clean up temp prompt file
|
|
74
|
-
try {
|
|
75
|
-
fs.unlinkSync(tmpPromptFile);
|
|
76
|
-
} catch {
|
|
77
|
-
// ignore cleanup errors
|
|
78
|
-
}
|
|
54
|
+
const result = await _spawnOpenCode(args, verbose);
|
|
55
|
+
const combinedOutput = [result.stdout, result.stderr].filter(Boolean).join('\n');
|
|
56
|
+
|
|
57
|
+
if (_looksLikeCliHelp(combinedOutput)) {
|
|
58
|
+
throw new Error(
|
|
59
|
+
'mini-ralph invoker: opencode printed CLI help instead of running the prompt. ' +
|
|
60
|
+
'The installed opencode CLI is likely incompatible with this version of spec-and-loop.'
|
|
61
|
+
);
|
|
79
62
|
}
|
|
63
|
+
|
|
64
|
+
// Detect which files changed during this iteration
|
|
65
|
+
const postSnapshot = _gitSnapshot();
|
|
66
|
+
const filesChanged = _diffSnapshots(preSnapshot, postSnapshot);
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
stdout: result.stdout,
|
|
70
|
+
exitCode: result.exitCode,
|
|
71
|
+
toolUsage: _extractToolUsage(result.stdout),
|
|
72
|
+
filesChanged,
|
|
73
|
+
};
|
|
80
74
|
}
|
|
81
75
|
|
|
82
76
|
/**
|
|
@@ -122,6 +116,25 @@ function _spawnOpenCode(args, verbose) {
|
|
|
122
116
|
});
|
|
123
117
|
}
|
|
124
118
|
|
|
119
|
+
/**
|
|
120
|
+
* Detect whether OpenCode printed its CLI help banner instead of executing the
|
|
121
|
+
* requested prompt. This usually means the invocation contract drifted.
|
|
122
|
+
*
|
|
123
|
+
* @param {string} text
|
|
124
|
+
* @returns {boolean}
|
|
125
|
+
*/
|
|
126
|
+
function _looksLikeCliHelp(text) {
|
|
127
|
+
if (!text) return false;
|
|
128
|
+
|
|
129
|
+
const hasHelpSections = text.includes('Commands:') && text.includes('Options:');
|
|
130
|
+
const hasOpenCodeUsage =
|
|
131
|
+
text.includes('opencode [project]') ||
|
|
132
|
+
text.includes('opencode run [message..]') ||
|
|
133
|
+
text.includes('run opencode with a message');
|
|
134
|
+
|
|
135
|
+
return hasHelpSections && hasOpenCodeUsage;
|
|
136
|
+
}
|
|
137
|
+
|
|
125
138
|
/**
|
|
126
139
|
* Extract a compact tool usage summary from OpenCode output.
|
|
127
140
|
* Returns an array of { tool, count } objects.
|
|
@@ -202,4 +215,11 @@ function _diffSnapshots(preSnapshot, postSnapshot) {
|
|
|
202
215
|
return changed;
|
|
203
216
|
}
|
|
204
217
|
|
|
205
|
-
module.exports = {
|
|
218
|
+
module.exports = {
|
|
219
|
+
invoke,
|
|
220
|
+
_spawnOpenCode,
|
|
221
|
+
_looksLikeCliHelp,
|
|
222
|
+
_extractToolUsage,
|
|
223
|
+
_gitSnapshot,
|
|
224
|
+
_diffSnapshots,
|
|
225
|
+
};
|