@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
@@ -1,6 +1,8 @@
1
1
  import { execa } from 'execa';
2
2
  import path from 'node:path';
3
- import { loadConfig, resolveConfigPath } from '../config/load.js';
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
- export async function doctorCommand(options) {
73
- const repoPath = path.resolve(options.repo || '.');
74
- const configPath = resolveConfigPath(repoPath, options.config);
75
- console.log('Doctor Check');
76
- console.log('============\n');
77
- let config;
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
- config = loadConfig(configPath);
80
- console.log(`Config: ${configPath}`);
81
- console.log(`Repo: ${repoPath}\n`);
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
- console.log(`Config: FAIL - ${err.message}`);
85
- process.exitCode = 1;
86
- return;
87
- }
88
- const checks = await runDoctorChecks(config, repoPath);
89
- console.log('Workers\n-------');
90
- for (const check of checks) {
91
- const status = check.error ? 'FAIL' : 'PASS';
92
- const version = check.version || 'unknown';
93
- const headless = check.headless ? 'headless OK' : 'headless FAIL';
94
- console.log(`${check.name}: ${status}`);
95
- console.log(` bin: ${check.bin}`);
96
- console.log(` version: ${version}`);
97
- console.log(` ${headless}`);
98
- if (check.error) {
99
- console.log(` error: ${check.error}`);
100
- }
101
- console.log('');
102
- }
103
- // Show phase configuration
104
- console.log('Phases\n------');
105
- console.log(` plan: ${config.phases.plan}`);
106
- console.log(` implement: ${config.phases.implement}`);
107
- console.log(` review: ${config.phases.review}`);
108
- console.log('');
109
- // Check that configured phase workers are available
110
- const phaseWorkers = new Set([config.phases.plan, config.phases.implement, config.phases.review]);
111
- const failedWorkers = checks.filter((c) => c.error).map((c) => c.name);
112
- const usedButFailed = [...phaseWorkers].filter((w) => failedWorkers.includes(w));
113
- const failed = checks.filter((c) => c.error);
114
- if (failed.length > 0) {
115
- console.log(`\nResult: ${failed.length} worker(s) failed`);
116
- if (usedButFailed.length > 0) {
117
- console.log(`Warning: Phase(s) configured to use failed worker(s): ${usedButFailed.join(', ')}`);
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('\nResult: All workers OK');
506
+ console.log('Result: All checks passed');
507
+ process.exitCode = 0;
123
508
  }
124
509
  }