@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
package/dist/commands/doctor.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { execa } from 'execa';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import { resolveConfigPath, loadConfig } from '../config/load.js';
|
|
5
|
+
import { getRunrPaths } from '../store/runs-root.js';
|
|
4
6
|
async function checkWorker(name, worker, repoPath) {
|
|
5
7
|
const result = {
|
|
6
8
|
name,
|
|
@@ -61,64 +63,447 @@ async function checkWorker(name, worker, repoPath) {
|
|
|
61
63
|
}
|
|
62
64
|
return result;
|
|
63
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* Run worker health checks (used by run command)
|
|
68
|
+
* Only checks workers that are actually used by the phases config.
|
|
69
|
+
*/
|
|
64
70
|
export async function runDoctorChecks(config, repoPath) {
|
|
71
|
+
// Get unique workers that are actually used by phases
|
|
72
|
+
const usedWorkers = new Set([
|
|
73
|
+
config.phases.plan,
|
|
74
|
+
config.phases.implement,
|
|
75
|
+
config.phases.review
|
|
76
|
+
]);
|
|
65
77
|
const checks = [];
|
|
66
78
|
for (const [name, workerConfig] of Object.entries(config.workers)) {
|
|
79
|
+
// Skip workers not used by any phase
|
|
80
|
+
if (!usedWorkers.has(name)) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
67
83
|
const check = await checkWorker(name, workerConfig, repoPath);
|
|
68
84
|
checks.push(check);
|
|
69
85
|
}
|
|
70
86
|
return checks;
|
|
71
87
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
88
|
+
// ==========================================
|
|
89
|
+
// Repository diagnostics (user-facing command)
|
|
90
|
+
// ==========================================
|
|
91
|
+
/**
|
|
92
|
+
* Check if the given path is inside a git repository
|
|
93
|
+
*/
|
|
94
|
+
async function checkGitRepository(repoPath) {
|
|
78
95
|
try {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
96
|
+
const result = await execa('git', ['rev-parse', '--git-dir'], {
|
|
97
|
+
cwd: repoPath,
|
|
98
|
+
reject: false
|
|
99
|
+
});
|
|
100
|
+
if (result.exitCode === 0) {
|
|
101
|
+
return { ok: true };
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
return { ok: false, error: 'not a git repository' };
|
|
105
|
+
}
|
|
82
106
|
}
|
|
83
107
|
catch (err) {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
108
|
+
return { ok: false, error: `Git check failed: ${err.message}` };
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Check working tree status
|
|
113
|
+
*/
|
|
114
|
+
async function checkWorkingTree(repoPath) {
|
|
115
|
+
try {
|
|
116
|
+
// Check for uncommitted changes
|
|
117
|
+
const statusResult = await execa('git', ['status', '--porcelain'], {
|
|
118
|
+
cwd: repoPath,
|
|
119
|
+
reject: false
|
|
120
|
+
});
|
|
121
|
+
if (statusResult.exitCode !== 0) {
|
|
122
|
+
return { ok: false, clean: false, error: 'git status failed' };
|
|
123
|
+
}
|
|
124
|
+
const uncommittedLines = statusResult.stdout.trim().split('\n').filter(line => line.length > 0);
|
|
125
|
+
const uncommittedCount = uncommittedLines.length;
|
|
126
|
+
const isClean = uncommittedCount === 0;
|
|
127
|
+
// Check for ignored noise
|
|
128
|
+
const ignoredResult = await execa('git', ['status', '--porcelain', '--ignored', '--', '.runr/', '.agent/', '.runr-worktrees/', '.agent-worktrees/'], { cwd: repoPath, reject: false });
|
|
129
|
+
const ignoredLines = ignoredResult.exitCode === 0
|
|
130
|
+
? ignoredResult.stdout.trim().split('\n').filter(line => line.startsWith('!!')).length
|
|
131
|
+
: 0;
|
|
132
|
+
return {
|
|
133
|
+
ok: true,
|
|
134
|
+
clean: isClean,
|
|
135
|
+
uncommittedCount,
|
|
136
|
+
ignoredCount: ignoredLines
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
return { ok: false, clean: false, error: `Working tree check failed: ${err.message}` };
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Check if .runr/ is properly gitignored
|
|
145
|
+
* Uses git check-ignore if available, falls back to .gitignore parsing
|
|
146
|
+
*/
|
|
147
|
+
async function checkGitignore(repoPath) {
|
|
148
|
+
// Preferred method: use git check-ignore
|
|
149
|
+
try {
|
|
150
|
+
const result = await execa('git', ['check-ignore', '-q', '.runr/'], {
|
|
151
|
+
cwd: repoPath,
|
|
152
|
+
reject: false
|
|
153
|
+
});
|
|
154
|
+
// Exit code 0 means the path is ignored
|
|
155
|
+
if (result.exitCode === 0) {
|
|
156
|
+
return { ok: true, message: '.runr/ is gitignored ✓' };
|
|
157
|
+
}
|
|
158
|
+
// Exit code 1 means the path is not ignored
|
|
159
|
+
if (result.exitCode === 1) {
|
|
160
|
+
return {
|
|
161
|
+
ok: false,
|
|
162
|
+
message: '.runr/ is NOT gitignored - will cause dirty tree warnings\n' +
|
|
163
|
+
' Add ".runr/" to .gitignore or run "runr init" to update'
|
|
164
|
+
};
|
|
118
165
|
}
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
// Git not available or other error, fall back to .gitignore parsing
|
|
169
|
+
}
|
|
170
|
+
// Fallback method: parse .gitignore file
|
|
171
|
+
const gitignorePath = path.join(repoPath, '.gitignore');
|
|
172
|
+
try {
|
|
173
|
+
const content = fs.readFileSync(gitignorePath, 'utf-8');
|
|
174
|
+
const lines = content.split('\n').map(l => l.trim());
|
|
175
|
+
const hasEntry = lines.some(line => line === '.runr/' ||
|
|
176
|
+
line === '.runr' ||
|
|
177
|
+
line.startsWith('.runr/'));
|
|
178
|
+
if (hasEntry) {
|
|
179
|
+
return { ok: true, message: '.runr/ is gitignored ✓' };
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
return {
|
|
183
|
+
ok: false,
|
|
184
|
+
message: '.runr/ is NOT gitignored - will cause dirty tree warnings\n' +
|
|
185
|
+
' Add ".runr/" to .gitignore or run "runr init" to update'
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
return {
|
|
191
|
+
ok: false,
|
|
192
|
+
message: 'No .gitignore found - create one with ".runr/" entry'
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Get version from package.json
|
|
198
|
+
*/
|
|
199
|
+
function getRunrVersion() {
|
|
200
|
+
try {
|
|
201
|
+
// Navigate up from dist/commands/doctor.js to find package.json
|
|
202
|
+
const packagePath = path.resolve(new URL(import.meta.url).pathname, '../../../package.json');
|
|
203
|
+
const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf-8'));
|
|
204
|
+
return packageJson.version || 'unknown';
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
return 'unknown';
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Check for config file and validate if present
|
|
212
|
+
*/
|
|
213
|
+
function checkConfig(repoPath, configPath) {
|
|
214
|
+
try {
|
|
215
|
+
const resolvedPath = resolveConfigPath(repoPath, configPath);
|
|
216
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
217
|
+
return { ok: true, found: false };
|
|
218
|
+
}
|
|
219
|
+
// Try to load and validate config
|
|
220
|
+
try {
|
|
221
|
+
loadConfig(resolvedPath);
|
|
222
|
+
return { ok: true, found: true, path: resolvedPath, valid: true };
|
|
223
|
+
}
|
|
224
|
+
catch (err) {
|
|
225
|
+
return {
|
|
226
|
+
ok: false,
|
|
227
|
+
found: true,
|
|
228
|
+
path: resolvedPath,
|
|
229
|
+
valid: false,
|
|
230
|
+
error: `Invalid config: ${err.message}`
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
catch (err) {
|
|
235
|
+
return { ok: false, found: false, error: `Config check failed: ${err.message}` };
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Check .runr/ directory write access
|
|
240
|
+
*/
|
|
241
|
+
async function checkRunrDirectory(repoPath) {
|
|
242
|
+
try {
|
|
243
|
+
const paths = getRunrPaths(repoPath);
|
|
244
|
+
const runrRoot = paths.runr_root;
|
|
245
|
+
if (!fs.existsSync(runrRoot)) {
|
|
246
|
+
return { ok: true, exists: false };
|
|
247
|
+
}
|
|
248
|
+
// Test write access
|
|
249
|
+
const testFile = path.join(runrRoot, '.doctor-test-write');
|
|
250
|
+
try {
|
|
251
|
+
fs.writeFileSync(testFile, 'test', 'utf-8');
|
|
252
|
+
fs.unlinkSync(testFile);
|
|
253
|
+
}
|
|
254
|
+
catch (err) {
|
|
255
|
+
return {
|
|
256
|
+
ok: false,
|
|
257
|
+
exists: true,
|
|
258
|
+
writable: false,
|
|
259
|
+
error: `Not writable: ${err.message}`
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
// Count runs if runs directory exists
|
|
263
|
+
let runCount = 0;
|
|
264
|
+
if (fs.existsSync(paths.runs_dir)) {
|
|
265
|
+
try {
|
|
266
|
+
const entries = fs.readdirSync(paths.runs_dir);
|
|
267
|
+
runCount = entries.filter(entry => {
|
|
268
|
+
const fullPath = path.join(paths.runs_dir, entry);
|
|
269
|
+
return fs.statSync(fullPath).isDirectory();
|
|
270
|
+
}).length;
|
|
271
|
+
}
|
|
272
|
+
catch {
|
|
273
|
+
// Ignore error counting runs
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return { ok: true, exists: true, writable: true, runCount };
|
|
277
|
+
}
|
|
278
|
+
catch (err) {
|
|
279
|
+
return { ok: false, exists: false, error: `Directory check failed: ${err.message}` };
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Check if AGENTS.md exists (for meta-agent workflows)
|
|
284
|
+
*/
|
|
285
|
+
function checkAgentsMd(repoPath) {
|
|
286
|
+
const agentsMdPath = path.join(repoPath, 'AGENTS.md');
|
|
287
|
+
return {
|
|
288
|
+
exists: fs.existsSync(agentsMdPath),
|
|
289
|
+
path: agentsMdPath
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Check Claude Code integration status
|
|
294
|
+
*/
|
|
295
|
+
function checkClaudeIntegration(repoPath) {
|
|
296
|
+
const claudeDir = path.join(repoPath, '.claude');
|
|
297
|
+
const skillPath = path.join(repoPath, '.claude/skills/runr-workflow/SKILL.md');
|
|
298
|
+
const commandPaths = [
|
|
299
|
+
path.join(repoPath, '.claude/commands/runr-bundle.md'),
|
|
300
|
+
path.join(repoPath, '.claude/commands/runr-submit.md'),
|
|
301
|
+
path.join(repoPath, '.claude/commands/runr-resume.md')
|
|
302
|
+
];
|
|
303
|
+
const claudeDetected = fs.existsSync(claudeDir);
|
|
304
|
+
const skillsPresent = fs.existsSync(skillPath);
|
|
305
|
+
const commandsPresent = commandPaths.every(p => fs.existsSync(p));
|
|
306
|
+
const missingFiles = [];
|
|
307
|
+
if (claudeDetected) {
|
|
308
|
+
if (!skillsPresent)
|
|
309
|
+
missingFiles.push('.claude/skills/runr-workflow/SKILL.md');
|
|
310
|
+
commandPaths.forEach(p => {
|
|
311
|
+
if (!fs.existsSync(p)) {
|
|
312
|
+
missingFiles.push(path.relative(repoPath, p));
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
return {
|
|
317
|
+
claudeDetected,
|
|
318
|
+
skillsPresent,
|
|
319
|
+
commandsPresent,
|
|
320
|
+
missingFiles: missingFiles.length > 0 ? missingFiles : undefined
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Check worktree sanity
|
|
325
|
+
*/
|
|
326
|
+
async function checkWorktrees(repoPath) {
|
|
327
|
+
try {
|
|
328
|
+
const paths = getRunrPaths(repoPath);
|
|
329
|
+
const worktreesDir = paths.worktrees_dir;
|
|
330
|
+
if (!fs.existsSync(worktreesDir)) {
|
|
331
|
+
return { ok: true, worktreesUsed: false };
|
|
332
|
+
}
|
|
333
|
+
// List all worktree directories
|
|
334
|
+
const entries = fs.readdirSync(worktreesDir);
|
|
335
|
+
const worktreeDirs = entries.filter(entry => {
|
|
336
|
+
const fullPath = path.join(worktreesDir, entry);
|
|
337
|
+
return fs.statSync(fullPath).isDirectory();
|
|
338
|
+
});
|
|
339
|
+
if (worktreeDirs.length === 0) {
|
|
340
|
+
return { ok: true, worktreesUsed: false };
|
|
341
|
+
}
|
|
342
|
+
// Check which worktrees are still valid git worktrees
|
|
343
|
+
let orphanedCount = 0;
|
|
344
|
+
for (const dir of worktreeDirs) {
|
|
345
|
+
const worktreePath = path.join(worktreesDir, dir);
|
|
346
|
+
// Check if it's a valid git worktree
|
|
347
|
+
// A worktree must have .git file (not directory) pointing to parent repo
|
|
348
|
+
const gitPath = path.join(worktreePath, '.git');
|
|
349
|
+
const isValidWorktree = fs.existsSync(gitPath) && fs.statSync(gitPath).isFile();
|
|
350
|
+
if (!isValidWorktree) {
|
|
351
|
+
orphanedCount++;
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
// Double-check with git command
|
|
355
|
+
try {
|
|
356
|
+
const result = await execa('git', ['rev-parse', '--git-dir'], {
|
|
357
|
+
cwd: worktreePath,
|
|
358
|
+
reject: false
|
|
359
|
+
});
|
|
360
|
+
if (result.exitCode !== 0) {
|
|
361
|
+
orphanedCount++;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
catch {
|
|
365
|
+
orphanedCount++;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
return {
|
|
369
|
+
ok: orphanedCount === 0,
|
|
370
|
+
worktreesUsed: true,
|
|
371
|
+
totalWorktrees: worktreeDirs.length,
|
|
372
|
+
orphanedWorktrees: orphanedCount
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
catch (err) {
|
|
376
|
+
return { ok: false, worktreesUsed: false, error: `Worktree check failed: ${err.message}` };
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Run diagnostic checks on the repository
|
|
381
|
+
*/
|
|
382
|
+
export async function doctorCommand(options) {
|
|
383
|
+
const repoPath = path.resolve(options.repo || '.');
|
|
384
|
+
console.log('Runr Doctor');
|
|
385
|
+
console.log('===========\n');
|
|
386
|
+
let hasErrors = false;
|
|
387
|
+
// Check 1: Runr version
|
|
388
|
+
const version = getRunrVersion();
|
|
389
|
+
console.log(`Runr version: ${version}`);
|
|
390
|
+
// Check 2: Git repository
|
|
391
|
+
const gitCheck = await checkGitRepository(repoPath);
|
|
392
|
+
if (gitCheck.ok) {
|
|
393
|
+
console.log('Git repository: OK');
|
|
394
|
+
}
|
|
395
|
+
else {
|
|
396
|
+
console.log(`Git repository: FAIL - ${gitCheck.error}`);
|
|
397
|
+
hasErrors = true;
|
|
398
|
+
}
|
|
399
|
+
// Check 3: Working tree status
|
|
400
|
+
const treeCheck = await checkWorkingTree(repoPath);
|
|
401
|
+
if (treeCheck.ok) {
|
|
402
|
+
if (treeCheck.clean) {
|
|
403
|
+
console.log('Working tree: clean');
|
|
404
|
+
}
|
|
405
|
+
else {
|
|
406
|
+
console.log(`Working tree: dirty (${treeCheck.uncommittedCount} uncommitted files)`);
|
|
407
|
+
if (treeCheck.ignoredCount && treeCheck.ignoredCount > 0) {
|
|
408
|
+
console.log(` Ignored noise: ${treeCheck.ignoredCount} files in .runr/`);
|
|
409
|
+
}
|
|
410
|
+
console.log('\n⚠️ Meta-Agent Safety Warning:');
|
|
411
|
+
console.log(' Never run agents on uncommitted work - you risk data loss.');
|
|
412
|
+
console.log(' Commit or stash before using `runr meta` or `runr run`.');
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
else {
|
|
416
|
+
console.log(`Working tree: FAIL - ${treeCheck.error}`);
|
|
417
|
+
hasErrors = true;
|
|
418
|
+
}
|
|
419
|
+
// Check 4: .gitignore
|
|
420
|
+
const gitignoreCheck = await checkGitignore(repoPath);
|
|
421
|
+
if (gitignoreCheck.ok) {
|
|
422
|
+
console.log(gitignoreCheck.message);
|
|
423
|
+
}
|
|
424
|
+
else {
|
|
425
|
+
console.log(`⚠ ${gitignoreCheck.message}`);
|
|
426
|
+
}
|
|
427
|
+
// Check 5: Config file
|
|
428
|
+
const configCheck = checkConfig(repoPath, options.config);
|
|
429
|
+
if (configCheck.found) {
|
|
430
|
+
if (configCheck.valid) {
|
|
431
|
+
console.log(`Config: ${configCheck.path}`);
|
|
432
|
+
}
|
|
433
|
+
else {
|
|
434
|
+
console.log(`Config: FAIL - ${configCheck.error}`);
|
|
435
|
+
hasErrors = true;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
else {
|
|
439
|
+
console.log('Config: no config file (using defaults)');
|
|
440
|
+
}
|
|
441
|
+
// Check 6: .runr/ directory
|
|
442
|
+
const dirCheck = await checkRunrDirectory(repoPath);
|
|
443
|
+
if (dirCheck.exists) {
|
|
444
|
+
if (dirCheck.writable) {
|
|
445
|
+
const runInfo = dirCheck.runCount !== undefined ? ` (${dirCheck.runCount} runs)` : '';
|
|
446
|
+
console.log(`.runr/ directory: OK${runInfo}`);
|
|
447
|
+
}
|
|
448
|
+
else {
|
|
449
|
+
console.log(`.runr/ directory: FAIL - ${dirCheck.error}`);
|
|
450
|
+
hasErrors = true;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
else {
|
|
454
|
+
console.log('.runr/ directory: not yet created');
|
|
455
|
+
}
|
|
456
|
+
// Check 7: Worktrees
|
|
457
|
+
const worktreeCheck = await checkWorktrees(repoPath);
|
|
458
|
+
if (worktreeCheck.worktreesUsed) {
|
|
459
|
+
if (worktreeCheck.ok) {
|
|
460
|
+
console.log(`Worktrees: OK (${worktreeCheck.totalWorktrees} worktrees)`);
|
|
461
|
+
}
|
|
462
|
+
else {
|
|
463
|
+
console.log(`Worktrees: WARNING - ${worktreeCheck.orphanedWorktrees} orphaned worktrees`);
|
|
464
|
+
console.log(' Run "runr tools gc" to clean up orphaned worktrees');
|
|
465
|
+
// Note: This is a warning, not an error - don't set hasErrors
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
else {
|
|
469
|
+
console.log('Worktrees: not used');
|
|
470
|
+
}
|
|
471
|
+
// Check 8: AGENTS.md (for meta-agent workflows)
|
|
472
|
+
const agentsCheck = checkAgentsMd(repoPath);
|
|
473
|
+
if (agentsCheck.exists) {
|
|
474
|
+
console.log('AGENTS.md: present ✓');
|
|
475
|
+
}
|
|
476
|
+
else {
|
|
477
|
+
console.log('⚠️ AGENTS.md: missing');
|
|
478
|
+
console.log(' Run "runr init --pack solo" to create workflow documentation');
|
|
479
|
+
}
|
|
480
|
+
// Check 9: Claude Code integration (if .claude/ exists)
|
|
481
|
+
const claudeCheck = checkClaudeIntegration(repoPath);
|
|
482
|
+
if (claudeCheck.claudeDetected) {
|
|
483
|
+
if (claudeCheck.skillsPresent && claudeCheck.commandsPresent) {
|
|
484
|
+
console.log('Claude Code integration: complete ✓');
|
|
485
|
+
}
|
|
486
|
+
else {
|
|
487
|
+
console.log('Claude Code integration: incomplete');
|
|
488
|
+
if (claudeCheck.missingFiles) {
|
|
489
|
+
console.log(' Missing:');
|
|
490
|
+
claudeCheck.missingFiles.forEach(file => console.log(` - ${file}`));
|
|
491
|
+
console.log(' Run "runr init --pack solo --with-claude" to complete setup');
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
else {
|
|
496
|
+
console.log('Claude Code integration: not configured');
|
|
497
|
+
console.log(' (optional) Run "runr init --pack solo --with-claude" to add Claude Code support');
|
|
498
|
+
}
|
|
499
|
+
// Exit with appropriate code
|
|
500
|
+
console.log();
|
|
501
|
+
if (hasErrors) {
|
|
502
|
+
console.log('Result: Some checks failed');
|
|
119
503
|
process.exitCode = 1;
|
|
120
504
|
}
|
|
121
505
|
else {
|
|
122
|
-
console.log('
|
|
506
|
+
console.log('Result: All checks passed');
|
|
507
|
+
process.exitCode = 0;
|
|
123
508
|
}
|
|
124
509
|
}
|