@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.
Files changed (81) hide show
  1. package/CHANGELOG.md +150 -1
  2. package/README.md +124 -111
  3. package/dist/audit/classifier.js +331 -0
  4. package/dist/cli.js +593 -282
  5. package/dist/commands/audit.js +259 -0
  6. package/dist/commands/bundle.js +180 -0
  7. package/dist/commands/continue.js +276 -0
  8. package/dist/commands/doctor.js +430 -45
  9. package/dist/commands/hooks.js +352 -0
  10. package/dist/commands/init.js +368 -8
  11. package/dist/commands/intervene.js +109 -0
  12. package/dist/commands/journal.js +167 -0
  13. package/dist/commands/meta.js +245 -0
  14. package/dist/commands/mode.js +157 -0
  15. package/dist/commands/orchestrate.js +29 -0
  16. package/dist/commands/packs.js +47 -0
  17. package/dist/commands/preflight.js +8 -5
  18. package/dist/commands/resume.js +421 -3
  19. package/dist/commands/run.js +63 -4
  20. package/dist/commands/status.js +47 -0
  21. package/dist/commands/submit.js +374 -0
  22. package/dist/config/schema.js +61 -1
  23. package/dist/diagnosis/analyzer.js +86 -1
  24. package/dist/diagnosis/formatter.js +3 -0
  25. package/dist/diagnosis/index.js +1 -0
  26. package/dist/diagnosis/stop-explainer.js +267 -0
  27. package/dist/diagnostics/stop-explainer.js +267 -0
  28. package/dist/guards/checkpoint.js +119 -0
  29. package/dist/journal/builder.js +497 -0
  30. package/dist/journal/redactor.js +68 -0
  31. package/dist/journal/renderer.js +220 -0
  32. package/dist/journal/types.js +7 -0
  33. package/dist/orchestrator/artifacts.js +17 -2
  34. package/dist/orchestrator/receipt.js +304 -0
  35. package/dist/output/stop-footer.js +185 -0
  36. package/dist/packs/actions.js +176 -0
  37. package/dist/packs/loader.js +200 -0
  38. package/dist/packs/renderer.js +46 -0
  39. package/dist/receipt/intervention.js +465 -0
  40. package/dist/receipt/writer.js +296 -0
  41. package/dist/redaction/redactor.js +95 -0
  42. package/dist/repo/context.js +147 -20
  43. package/dist/review/check-parser.js +211 -0
  44. package/dist/store/checkpoint-metadata.js +111 -0
  45. package/dist/store/run-store.js +21 -0
  46. package/dist/supervisor/runner.js +161 -10
  47. package/dist/tasks/task-metadata.js +74 -1
  48. package/dist/ux/brain.js +528 -0
  49. package/dist/ux/render.js +123 -0
  50. package/dist/ux/safe-commands.js +133 -0
  51. package/dist/ux/state.js +193 -0
  52. package/dist/ux/telemetry.js +110 -0
  53. package/package.json +5 -1
  54. package/packs/pr/pack.json +50 -0
  55. package/packs/pr/templates/AGENTS.md.tmpl +120 -0
  56. package/packs/pr/templates/CLAUDE.md.tmpl +101 -0
  57. package/packs/pr/templates/bundle.md.tmpl +27 -0
  58. package/packs/solo/pack.json +82 -0
  59. package/packs/solo/templates/AGENTS.md.tmpl +80 -0
  60. package/packs/solo/templates/CLAUDE.md.tmpl +126 -0
  61. package/packs/solo/templates/bundle.md.tmpl +27 -0
  62. package/packs/solo/templates/claude-cmd-bundle.md.tmpl +40 -0
  63. package/packs/solo/templates/claude-cmd-resume.md.tmpl +43 -0
  64. package/packs/solo/templates/claude-cmd-submit.md.tmpl +51 -0
  65. package/packs/solo/templates/claude-skill.md.tmpl +96 -0
  66. package/packs/trunk/pack.json +50 -0
  67. package/packs/trunk/templates/AGENTS.md.tmpl +87 -0
  68. package/packs/trunk/templates/CLAUDE.md.tmpl +126 -0
  69. package/packs/trunk/templates/bundle.md.tmpl +27 -0
  70. package/dist/commands/__tests__/report.test.js +0 -202
  71. package/dist/config/__tests__/presets.test.js +0 -104
  72. package/dist/context/__tests__/artifact.test.js +0 -130
  73. package/dist/context/__tests__/pack.test.js +0 -191
  74. package/dist/env/__tests__/fingerprint.test.js +0 -116
  75. package/dist/orchestrator/__tests__/policy.test.js +0 -185
  76. package/dist/orchestrator/__tests__/schema-version.test.js +0 -65
  77. package/dist/supervisor/__tests__/evidence-gate.test.js +0 -111
  78. package/dist/supervisor/__tests__/ownership.test.js +0 -103
  79. package/dist/supervisor/__tests__/state-machine.test.js +0 -290
  80. package/dist/workers/__tests__/claude.test.js +0 -88
  81. 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
+ }