@weldr/runr 0.3.1 → 0.7.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/CHANGELOG.md +150 -1
- package/README.md +124 -111
- package/dist/audit/classifier.js +331 -0
- package/dist/cli.js +593 -282
- package/dist/commands/audit.js +259 -0
- package/dist/commands/bundle.js +180 -0
- package/dist/commands/continue.js +276 -0
- package/dist/commands/doctor.js +430 -45
- package/dist/commands/hooks.js +352 -0
- package/dist/commands/init.js +368 -8
- package/dist/commands/intervene.js +109 -0
- package/dist/commands/journal.js +167 -0
- package/dist/commands/meta.js +245 -0
- package/dist/commands/mode.js +157 -0
- package/dist/commands/orchestrate.js +29 -0
- package/dist/commands/packs.js +47 -0
- package/dist/commands/preflight.js +8 -5
- package/dist/commands/resume.js +421 -3
- package/dist/commands/run.js +63 -4
- package/dist/commands/status.js +47 -0
- package/dist/commands/submit.js +374 -0
- package/dist/config/schema.js +61 -1
- package/dist/diagnosis/analyzer.js +86 -1
- package/dist/diagnosis/formatter.js +3 -0
- package/dist/diagnosis/index.js +1 -0
- package/dist/diagnosis/stop-explainer.js +267 -0
- package/dist/diagnostics/stop-explainer.js +267 -0
- package/dist/guards/checkpoint.js +119 -0
- package/dist/journal/builder.js +497 -0
- package/dist/journal/redactor.js +68 -0
- package/dist/journal/renderer.js +220 -0
- package/dist/journal/types.js +7 -0
- package/dist/orchestrator/artifacts.js +17 -2
- package/dist/orchestrator/receipt.js +304 -0
- package/dist/output/stop-footer.js +185 -0
- package/dist/packs/actions.js +176 -0
- package/dist/packs/loader.js +200 -0
- package/dist/packs/renderer.js +46 -0
- package/dist/receipt/intervention.js +465 -0
- package/dist/receipt/writer.js +296 -0
- package/dist/redaction/redactor.js +95 -0
- package/dist/repo/context.js +147 -20
- package/dist/review/check-parser.js +211 -0
- package/dist/store/checkpoint-metadata.js +111 -0
- package/dist/store/run-store.js +21 -0
- package/dist/supervisor/runner.js +161 -10
- package/dist/tasks/task-metadata.js +74 -1
- package/dist/ux/brain.js +528 -0
- package/dist/ux/render.js +123 -0
- package/dist/ux/safe-commands.js +133 -0
- package/dist/ux/state.js +193 -0
- package/dist/ux/telemetry.js +110 -0
- package/package.json +5 -1
- package/packs/pr/pack.json +50 -0
- package/packs/pr/templates/AGENTS.md.tmpl +120 -0
- package/packs/pr/templates/CLAUDE.md.tmpl +101 -0
- package/packs/pr/templates/bundle.md.tmpl +27 -0
- package/packs/solo/pack.json +82 -0
- package/packs/solo/templates/AGENTS.md.tmpl +80 -0
- package/packs/solo/templates/CLAUDE.md.tmpl +126 -0
- package/packs/solo/templates/bundle.md.tmpl +27 -0
- package/packs/solo/templates/claude-cmd-bundle.md.tmpl +40 -0
- package/packs/solo/templates/claude-cmd-resume.md.tmpl +43 -0
- package/packs/solo/templates/claude-cmd-submit.md.tmpl +51 -0
- package/packs/solo/templates/claude-skill.md.tmpl +96 -0
- package/packs/trunk/pack.json +50 -0
- package/packs/trunk/templates/AGENTS.md.tmpl +87 -0
- package/packs/trunk/templates/CLAUDE.md.tmpl +126 -0
- package/packs/trunk/templates/bundle.md.tmpl +27 -0
- package/dist/commands/__tests__/report.test.js +0 -202
- package/dist/config/__tests__/presets.test.js +0 -104
- package/dist/context/__tests__/artifact.test.js +0 -130
- package/dist/context/__tests__/pack.test.js +0 -191
- package/dist/env/__tests__/fingerprint.test.js +0 -116
- package/dist/orchestrator/__tests__/policy.test.js +0 -185
- package/dist/orchestrator/__tests__/schema-version.test.js +0 -65
- package/dist/supervisor/__tests__/evidence-gate.test.js +0 -111
- package/dist/supervisor/__tests__/ownership.test.js +0 -103
- package/dist/supervisor/__tests__/state-machine.test.js +0 -290
- package/dist/workers/__tests__/claude.test.js +0 -88
- package/dist/workers/__tests__/codex.test.js +0 -81
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* runr continue - The "do the obvious next thing" command.
|
|
3
|
+
*
|
|
4
|
+
* This is the router that makes Runr feel like Rails:
|
|
5
|
+
* - If STOPPED with auto-fixable issue: run fix commands, then resume
|
|
6
|
+
* - If STOPPED with auto-resume reason: just resume
|
|
7
|
+
* - If orchestration cursor exists: continue orchestration
|
|
8
|
+
* - Otherwise: print the front door
|
|
9
|
+
*/
|
|
10
|
+
import fs from 'node:fs';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import { spawn } from 'node:child_process';
|
|
13
|
+
import { resolveRepoState } from '../ux/state.js';
|
|
14
|
+
import { computeBrain } from '../ux/brain.js';
|
|
15
|
+
import { formatFrontDoor } from '../ux/render.js';
|
|
16
|
+
import { getRunsRoot } from '../store/runs-root.js';
|
|
17
|
+
import { resumeCommand } from './resume.js';
|
|
18
|
+
import { recordContinueAttempt, recordAutoFixStep, recordContinueFailed, recordContinueSuccess, } from '../ux/telemetry.js';
|
|
19
|
+
/**
|
|
20
|
+
* Execute a canonical command and capture output.
|
|
21
|
+
*/
|
|
22
|
+
async function executeCommand(cmd, cwd) {
|
|
23
|
+
const startTime = Date.now();
|
|
24
|
+
return new Promise((resolve) => {
|
|
25
|
+
const proc = spawn(cmd.binary, cmd.args, {
|
|
26
|
+
cwd,
|
|
27
|
+
shell: false,
|
|
28
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
29
|
+
});
|
|
30
|
+
let stdout = '';
|
|
31
|
+
let stderr = '';
|
|
32
|
+
proc.stdout.on('data', (data) => {
|
|
33
|
+
stdout += data.toString();
|
|
34
|
+
});
|
|
35
|
+
proc.stderr.on('data', (data) => {
|
|
36
|
+
stderr += data.toString();
|
|
37
|
+
});
|
|
38
|
+
proc.on('close', (code) => {
|
|
39
|
+
resolve({
|
|
40
|
+
command: cmd.raw,
|
|
41
|
+
exitCode: code ?? 1,
|
|
42
|
+
stdout,
|
|
43
|
+
stderr,
|
|
44
|
+
durationMs: Date.now() - startTime,
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
proc.on('error', (err) => {
|
|
48
|
+
resolve({
|
|
49
|
+
command: cmd.raw,
|
|
50
|
+
exitCode: 1,
|
|
51
|
+
stdout,
|
|
52
|
+
stderr: stderr + '\n' + err.message,
|
|
53
|
+
durationMs: Date.now() - startTime,
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Slugify a command for use in filenames.
|
|
60
|
+
*/
|
|
61
|
+
function slugify(cmd) {
|
|
62
|
+
return cmd
|
|
63
|
+
.replace(/[^a-zA-Z0-9]+/g, '-')
|
|
64
|
+
.replace(/^-|-$/g, '')
|
|
65
|
+
.toLowerCase()
|
|
66
|
+
.slice(0, 50);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Execute auto-fix commands and write artifacts.
|
|
70
|
+
*/
|
|
71
|
+
async function executeAutoFix(runId, commands, repoPath) {
|
|
72
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
73
|
+
const runsRoot = getRunsRoot(repoPath);
|
|
74
|
+
const artifactDir = path.join(runsRoot, runId, 'artifacts', 'continue', timestamp);
|
|
75
|
+
// Create artifact directory
|
|
76
|
+
fs.mkdirSync(artifactDir, { recursive: true });
|
|
77
|
+
const artifact = {
|
|
78
|
+
timestamp: new Date().toISOString(),
|
|
79
|
+
runId,
|
|
80
|
+
strategy: 'auto_fix',
|
|
81
|
+
commands: [],
|
|
82
|
+
success: false,
|
|
83
|
+
};
|
|
84
|
+
let success = true;
|
|
85
|
+
for (let i = 0; i < commands.length; i++) {
|
|
86
|
+
const cmd = commands[i];
|
|
87
|
+
console.log(`Running: ${cmd.raw}`);
|
|
88
|
+
const result = await executeCommand(cmd, repoPath);
|
|
89
|
+
// Write log file
|
|
90
|
+
const logFileName = `${i + 1}-${slugify(cmd.raw)}.log`;
|
|
91
|
+
const logPath = path.join(artifactDir, logFileName);
|
|
92
|
+
const logContent = [
|
|
93
|
+
`Command: ${cmd.raw}`,
|
|
94
|
+
`Exit code: ${result.exitCode}`,
|
|
95
|
+
`Duration: ${result.durationMs}ms`,
|
|
96
|
+
'',
|
|
97
|
+
'=== STDOUT ===',
|
|
98
|
+
result.stdout,
|
|
99
|
+
'',
|
|
100
|
+
'=== STDERR ===',
|
|
101
|
+
result.stderr,
|
|
102
|
+
].join('\n');
|
|
103
|
+
fs.writeFileSync(logPath, logContent);
|
|
104
|
+
// Record in artifact
|
|
105
|
+
artifact.commands.push({
|
|
106
|
+
command: cmd.raw,
|
|
107
|
+
exitCode: result.exitCode,
|
|
108
|
+
durationMs: result.durationMs,
|
|
109
|
+
logPath: logFileName,
|
|
110
|
+
});
|
|
111
|
+
// Record telemetry for each step
|
|
112
|
+
recordAutoFixStep(repoPath, runId, i, cmd.raw, result.exitCode);
|
|
113
|
+
// Check for failure
|
|
114
|
+
if (result.exitCode !== 0) {
|
|
115
|
+
console.log(` Failed (exit ${result.exitCode})`);
|
|
116
|
+
artifact.success = false;
|
|
117
|
+
artifact.failedAt = i;
|
|
118
|
+
artifact.error = `Command "${cmd.raw}" failed with exit code ${result.exitCode}`;
|
|
119
|
+
success = false;
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
console.log(` OK (${result.durationMs}ms)`);
|
|
123
|
+
}
|
|
124
|
+
if (success) {
|
|
125
|
+
artifact.success = true;
|
|
126
|
+
}
|
|
127
|
+
// Write continue.json
|
|
128
|
+
const artifactPath = path.join(artifactDir, 'continue.json');
|
|
129
|
+
fs.writeFileSync(artifactPath, JSON.stringify(artifact, null, 2));
|
|
130
|
+
return { success, artifact };
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Load diagnosis data for a stopped run.
|
|
134
|
+
*/
|
|
135
|
+
function loadDiagnosisData(stopJsonPath, diagnosticsPath) {
|
|
136
|
+
let stopDiagnosis = null;
|
|
137
|
+
let stopExplainer = null;
|
|
138
|
+
if (stopJsonPath && fs.existsSync(stopJsonPath)) {
|
|
139
|
+
try {
|
|
140
|
+
stopDiagnosis = JSON.parse(fs.readFileSync(stopJsonPath, 'utf-8'));
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
// Ignore parse errors
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (diagnosticsPath && fs.existsSync(diagnosticsPath)) {
|
|
147
|
+
try {
|
|
148
|
+
stopExplainer = JSON.parse(fs.readFileSync(diagnosticsPath, 'utf-8'));
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
// Ignore parse errors
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return { stopDiagnosis, stopExplainer };
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Print front door and exit.
|
|
158
|
+
*/
|
|
159
|
+
function printFrontDoorAndExit(brainOutput) {
|
|
160
|
+
console.log(formatFrontDoor(brainOutput));
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Main continue command.
|
|
164
|
+
*/
|
|
165
|
+
export async function continueCommand(options) {
|
|
166
|
+
const repoPath = options.repo || process.cwd();
|
|
167
|
+
// Resolve repo state
|
|
168
|
+
const state = await resolveRepoState(repoPath);
|
|
169
|
+
// Load diagnosis data if we have a stopped run
|
|
170
|
+
let stopDiagnosis = null;
|
|
171
|
+
let stopExplainer = null;
|
|
172
|
+
if (state.latestStopped) {
|
|
173
|
+
const diagData = loadDiagnosisData(state.latestStopped.stopJsonPath, state.latestStopped.diagnosticsPath);
|
|
174
|
+
stopDiagnosis = diagData.stopDiagnosis;
|
|
175
|
+
stopExplainer = diagData.stopExplainer;
|
|
176
|
+
}
|
|
177
|
+
// Compute brain output
|
|
178
|
+
const brainOutput = computeBrain({
|
|
179
|
+
state,
|
|
180
|
+
stopDiagnosis,
|
|
181
|
+
stopExplainer,
|
|
182
|
+
});
|
|
183
|
+
const strategy = brainOutput.continueStrategy;
|
|
184
|
+
// Extract runId for telemetry (varies by strategy type)
|
|
185
|
+
const runIdForTelemetry = strategy.type === 'auto_resume' || strategy.type === 'auto_fix'
|
|
186
|
+
? strategy.runId
|
|
187
|
+
: strategy.type === 'manual'
|
|
188
|
+
? strategy.runId
|
|
189
|
+
: undefined;
|
|
190
|
+
// Record continue attempt with analysis for debugging
|
|
191
|
+
recordContinueAttempt(repoPath, runIdForTelemetry, strategy, brainOutput.stoppedAnalysis);
|
|
192
|
+
// Route based on strategy
|
|
193
|
+
switch (strategy.type) {
|
|
194
|
+
case 'auto_resume': {
|
|
195
|
+
console.log(`Auto-resuming run ${strategy.runId}...`);
|
|
196
|
+
await resumeCommand({
|
|
197
|
+
runId: strategy.runId,
|
|
198
|
+
time: 120,
|
|
199
|
+
maxTicks: 50,
|
|
200
|
+
allowDeps: false,
|
|
201
|
+
force: options.force ?? false,
|
|
202
|
+
repo: repoPath,
|
|
203
|
+
autoResume: true,
|
|
204
|
+
});
|
|
205
|
+
recordContinueSuccess(repoPath, strategy.runId, 'auto_resume');
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
case 'auto_fix': {
|
|
209
|
+
// Check for ledger mode with --force
|
|
210
|
+
if (state.mode === 'ledger' && !options.force) {
|
|
211
|
+
console.error('Error: Ledger mode requires --force for auto-fix.');
|
|
212
|
+
console.error('Suggested: runr continue --force');
|
|
213
|
+
recordContinueFailed(repoPath, strategy.runId, 'ledger_mode_requires_force');
|
|
214
|
+
process.exitCode = 1;
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
// Check for dirty tree in ledger mode
|
|
218
|
+
if (state.mode === 'ledger' && state.treeStatus === 'dirty' && !options.force) {
|
|
219
|
+
console.error('Error: Working tree is dirty and mode is ledger.');
|
|
220
|
+
console.error('Commit or stash changes first, or use --force.');
|
|
221
|
+
recordContinueFailed(repoPath, strategy.runId, 'dirty_tree_in_ledger_mode');
|
|
222
|
+
process.exitCode = 1;
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
console.log(`Auto-fixing run ${strategy.runId}...`);
|
|
226
|
+
console.log(`Running ${strategy.commands.length} command(s):\n`);
|
|
227
|
+
const { success, artifact } = await executeAutoFix(strategy.runId, strategy.commands, repoPath);
|
|
228
|
+
if (!success) {
|
|
229
|
+
console.log('\nAuto-fix failed.');
|
|
230
|
+
console.log(`Artifact written: .runr/runs/${strategy.runId}/artifacts/continue/${artifact.timestamp.replace(/[:.]/g, '-')}/continue.json`);
|
|
231
|
+
console.log('\nNext steps:');
|
|
232
|
+
console.log(` 1) runr report ${strategy.runId}`);
|
|
233
|
+
console.log(` 2) runr intervene ${strategy.runId} --reason auto_fix_failed --note "..."`);
|
|
234
|
+
console.log(` 3) runr resume ${strategy.runId}`);
|
|
235
|
+
recordContinueFailed(repoPath, strategy.runId, 'auto_fix_command_failed', artifact.failedAt);
|
|
236
|
+
process.exitCode = 1;
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
console.log('\nAuto-fix complete. Resuming...\n');
|
|
240
|
+
await resumeCommand({
|
|
241
|
+
runId: strategy.runId,
|
|
242
|
+
time: 120,
|
|
243
|
+
maxTicks: 50,
|
|
244
|
+
allowDeps: false,
|
|
245
|
+
force: options.force ?? false,
|
|
246
|
+
repo: repoPath,
|
|
247
|
+
autoResume: true,
|
|
248
|
+
});
|
|
249
|
+
recordContinueSuccess(repoPath, strategy.runId, 'auto_fix');
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
case 'continue_orch': {
|
|
253
|
+
console.log(`Continuing orchestration ${strategy.orchestratorId}...`);
|
|
254
|
+
// Import and call the orchestration resume command
|
|
255
|
+
const { resumeOrchestrationCommand } = await import('./orchestrate.js');
|
|
256
|
+
await resumeOrchestrationCommand({
|
|
257
|
+
orchestratorId: strategy.orchestratorId,
|
|
258
|
+
repo: repoPath,
|
|
259
|
+
});
|
|
260
|
+
recordContinueSuccess(repoPath, undefined, 'continue_orch');
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
case 'manual': {
|
|
264
|
+
console.log(`Cannot auto-continue: ${strategy.blockedReason}`);
|
|
265
|
+
console.log('');
|
|
266
|
+
printFrontDoorAndExit(brainOutput);
|
|
267
|
+
// Not recording as failure - manual is an expected outcome
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
case 'nothing': {
|
|
271
|
+
printFrontDoorAndExit(brainOutput);
|
|
272
|
+
// Not recording - nothing to do is not a failure
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|