agileflow 2.99.8 → 3.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.
Files changed (65) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/lib/cache-provider.js +155 -0
  3. package/lib/codebase-indexer.js +1 -1
  4. package/lib/content-sanitizer.js +1 -0
  5. package/lib/dashboard-protocol.js +25 -0
  6. package/lib/dashboard-server.js +184 -133
  7. package/lib/errors.js +18 -0
  8. package/lib/file-cache.js +1 -1
  9. package/lib/flag-detection.js +11 -20
  10. package/lib/git-operations.js +15 -33
  11. package/lib/merge-operations.js +40 -34
  12. package/lib/process-executor.js +199 -0
  13. package/lib/registry-cache.js +13 -47
  14. package/lib/skill-loader.js +206 -0
  15. package/lib/smart-json-file.js +2 -4
  16. package/package.json +1 -1
  17. package/scripts/agileflow-configure.js +13 -12
  18. package/scripts/agileflow-statusline.sh +30 -0
  19. package/scripts/agileflow-welcome.js +181 -212
  20. package/scripts/auto-self-improve.js +3 -3
  21. package/scripts/claude-smart.sh +67 -0
  22. package/scripts/claude-tmux.sh +248 -161
  23. package/scripts/damage-control-multi-agent.js +227 -0
  24. package/scripts/lib/bus-utils.js +471 -0
  25. package/scripts/lib/configure-detect.js +5 -6
  26. package/scripts/lib/configure-features.js +44 -0
  27. package/scripts/lib/configure-repair.js +5 -6
  28. package/scripts/lib/configure-utils.js +2 -3
  29. package/scripts/lib/context-formatter.js +87 -8
  30. package/scripts/lib/damage-control-utils.js +37 -3
  31. package/scripts/lib/file-lock.js +392 -0
  32. package/scripts/lib/ideation-index.js +2 -5
  33. package/scripts/lib/lifecycle-detector.js +123 -0
  34. package/scripts/lib/process-cleanup.js +55 -81
  35. package/scripts/lib/scale-detector.js +357 -0
  36. package/scripts/lib/signal-detectors.js +779 -0
  37. package/scripts/lib/story-state-machine.js +1 -1
  38. package/scripts/lib/sync-ideation-status.js +2 -3
  39. package/scripts/lib/task-registry.js +7 -1
  40. package/scripts/lib/team-events.js +357 -0
  41. package/scripts/messaging-bridge.js +79 -36
  42. package/scripts/migrate-ideation-index.js +37 -14
  43. package/scripts/obtain-context.js +37 -19
  44. package/scripts/ralph-loop.js +3 -4
  45. package/scripts/smart-detect.js +390 -0
  46. package/scripts/team-manager.js +174 -30
  47. package/src/core/commands/audit.md +13 -11
  48. package/src/core/commands/babysit.md +162 -115
  49. package/src/core/commands/changelog.md +21 -4
  50. package/src/core/commands/configure.md +105 -2
  51. package/src/core/commands/debt.md +12 -2
  52. package/src/core/commands/feedback.md +7 -6
  53. package/src/core/commands/ideate/history.md +1 -1
  54. package/src/core/commands/ideate/new.md +5 -5
  55. package/src/core/commands/logic/audit.md +2 -2
  56. package/src/core/commands/pr.md +7 -6
  57. package/src/core/commands/research/analyze.md +28 -20
  58. package/src/core/commands/research/ask.md +43 -0
  59. package/src/core/commands/research/import.md +29 -21
  60. package/src/core/commands/research/list.md +8 -7
  61. package/src/core/commands/research/synthesize.md +356 -20
  62. package/src/core/commands/research/view.md +8 -5
  63. package/src/core/commands/review.md +24 -6
  64. package/src/core/commands/skill/create.md +34 -0
  65. package/tools/cli/lib/docs-setup.js +4 -0
@@ -13,7 +13,7 @@
13
13
 
14
14
  const fs = require('fs');
15
15
  const path = require('path');
16
- const { execFileSync, spawnSync } = require('child_process');
16
+ const { executeCommandSync, git, spawnBackground } = require('../lib/process-executor');
17
17
 
18
18
  // Shared utilities
19
19
  const { c, box } = require('../lib/colors');
@@ -30,23 +30,7 @@ const { readJSONCached, readFileCached } = require('../lib/file-cache');
30
30
  // Session manager path (relative to script location)
31
31
  const SESSION_MANAGER_PATH = path.join(__dirname, 'session-manager.js');
32
32
 
33
- // Story claiming module
34
- let storyClaiming;
35
- try {
36
- storyClaiming = require('./lib/story-claiming.js');
37
- } catch (e) {
38
- // Story claiming not available
39
- }
40
-
41
- // Story state machine (for epic completion check)
42
- let storyStateMachine;
43
- try {
44
- storyStateMachine = require('./lib/story-state-machine.js');
45
- } catch (e) {
46
- // Story state machine not available
47
- }
48
-
49
- // Hook metrics module
33
+ // Hook metrics module (kept at top level - needed early for timer)
50
34
  let hookMetrics;
51
35
  try {
52
36
  hookMetrics = require('./lib/hook-metrics.js');
@@ -54,58 +38,9 @@ try {
54
38
  // Hook metrics not available
55
39
  }
56
40
 
57
- // File tracking module
58
- let fileTracking;
59
- try {
60
- fileTracking = require('./lib/file-tracking.js');
61
- } catch (e) {
62
- // File tracking not available
63
- }
64
-
65
- // Ideation sync module
66
- let syncIdeationStatus;
67
- try {
68
- syncIdeationStatus = require('./lib/sync-ideation-status.js');
69
- } catch (e) {
70
- // Ideation sync not available
71
- }
72
-
73
- // Automation registry and runner
74
- let automationRegistry, automationRunner;
75
- try {
76
- automationRegistry = require('./lib/automation-registry.js');
77
- automationRunner = require('./lib/automation-runner.js');
78
- } catch (e) {
79
- // Automation system not available
80
- }
81
-
82
- // Update checker module
83
- let updateChecker;
84
- try {
85
- updateChecker = require('./check-update.js');
86
- } catch (e) {
87
- // Update checker not available
88
- }
89
-
90
- // Process cleanup module (duplicate Claude detection)
91
- let processCleanup;
92
- try {
93
- processCleanup = require('./lib/process-cleanup.js');
94
- } catch (e) {
95
- // Process cleanup not available
96
- }
97
-
98
- // Feature flags module (Agent Teams detection)
99
- let featureFlags;
100
- try {
101
- featureFlags = require('../lib/feature-flags');
102
- } catch (e) {
103
- // Feature flags not available
104
- }
105
-
106
41
  /**
107
42
  * PERFORMANCE OPTIMIZATION: Load all project files using LRU cache
108
- * Uses file-cache module for automatic caching with 30s TTL.
43
+ * Uses file-cache module for automatic caching with 15s TTL.
109
44
  * Files are cached across script invocations within TTL window.
110
45
  * Estimated savings: 60-120ms on cache hits
111
46
  */
@@ -177,17 +112,16 @@ function detectPlatform() {
177
112
  * Returns object with availability info and platform-specific install suggestion
178
113
  */
179
114
  function checkTmuxAvailability() {
180
- try {
181
- execFileSync('which', ['tmux'], { encoding: 'utf8', stdio: 'pipe' });
115
+ const result = executeCommandSync('which', ['tmux'], { fallback: null });
116
+ if (result.data !== null) {
182
117
  return { available: true };
183
- } catch (e) {
184
- const platform = detectPlatform();
185
- return {
186
- available: false,
187
- platform,
188
- noSudoCmd: 'conda install -c conda-forge tmux',
189
- };
190
118
  }
119
+ const platform = detectPlatform();
120
+ return {
121
+ available: false,
122
+ platform,
123
+ noSudoCmd: 'conda install -c conda-forge tmux',
124
+ };
191
125
  }
192
126
 
193
127
  /**
@@ -196,21 +130,11 @@ function checkTmuxAvailability() {
196
130
  * Estimated savings: 20-40ms
197
131
  */
198
132
  function getGitInfo(rootDir) {
199
- try {
200
- const output = execFileSync(
201
- 'bash',
202
- ['-c', 'git branch --show-current && git rev-parse --short HEAD && git log -1 --format="%s"'],
203
- { cwd: rootDir, encoding: 'utf8', timeout: 5000 }
204
- );
205
- const lines = output.trim().split('\n');
206
- return {
207
- branch: lines[0] || 'unknown',
208
- commit: lines[1] || 'unknown',
209
- lastCommit: lines[2] || '',
210
- };
211
- } catch (e) {
212
- return { branch: 'unknown', commit: 'unknown', lastCommit: '' };
213
- }
133
+ const opts = { cwd: rootDir, timeout: 5000, fallback: 'unknown' };
134
+ const branch = git(['branch', '--show-current'], opts).data;
135
+ const commit = git(['rev-parse', '--short', 'HEAD'], opts).data;
136
+ const lastCommit = git(['log', '-1', '--format=%s'], { ...opts, fallback: '' }).data;
137
+ return { branch, commit, lastCommit };
214
138
  }
215
139
 
216
140
  function getProjectInfo(rootDir, cache = null) {
@@ -376,15 +300,13 @@ function runArchival(rootDir, cache = null) {
376
300
 
377
301
  if (toArchiveCount > 0) {
378
302
  // Run archival
379
- try {
380
- execFileSync('bash', ['scripts/archive-completed-stories.sh'], {
381
- cwd: rootDir,
382
- encoding: 'utf8',
383
- stdio: 'pipe',
384
- });
303
+ const archiveResult = executeCommandSync('bash', ['scripts/archive-completed-stories.sh'], {
304
+ cwd: rootDir,
305
+ });
306
+ if (archiveResult.ok) {
385
307
  result.archived = toArchiveCount;
386
308
  result.remaining -= toArchiveCount;
387
- } catch (e) {}
309
+ }
388
310
  }
389
311
  } catch (e) {}
390
312
 
@@ -492,69 +414,68 @@ function checkParallelSessions(rootDir) {
492
414
  // Try to use combined full-status command (saves ~200ms vs 3 separate calls)
493
415
  const scriptPath = fs.existsSync(managerPath) ? managerPath : SESSION_MANAGER_PATH;
494
416
 
495
- try {
496
- // PERFORMANCE: Single subprocess call instead of 3 (register + count + status)
497
- const fullStatusOutput = execFileSync('node', [scriptPath, 'full-status'], {
498
- cwd: rootDir,
499
- encoding: 'utf8',
500
- stdio: ['pipe', 'pipe', 'pipe'],
501
- });
502
- const data = JSON.parse(fullStatusOutput);
503
-
504
- result.registered = data.registered;
505
- result.currentId = data.id;
506
- result.otherActive = data.otherActive || 0;
507
- result.cleaned = data.cleaned || 0;
508
- result.cleanedSessions = data.cleanedSessions || [];
509
-
510
- if (data.current) {
511
- result.isMain = data.current.is_main === true;
512
- result.nickname = data.current.nickname;
513
- result.branch = data.current.branch;
514
- result.sessionPath = data.current.path;
515
- }
516
- } catch (e) {
517
- // Fall back to individual calls if full-status not available (older version)
417
+ // PERFORMANCE: Single subprocess call instead of 3 (register + count + status)
418
+ const fullStatusResult = executeCommandSync('node', [scriptPath, 'full-status'], {
419
+ cwd: rootDir, fallback: null,
420
+ });
421
+
422
+ if (fullStatusResult.data) {
518
423
  try {
519
- const registerOutput = execFileSync('node', [scriptPath, 'register'], {
520
- cwd: rootDir,
521
- encoding: 'utf8',
522
- stdio: ['pipe', 'pipe', 'pipe'],
523
- });
524
- const registerData = JSON.parse(registerOutput);
525
- result.registered = true;
526
- result.currentId = registerData.id;
424
+ const data = JSON.parse(fullStatusResult.data);
425
+ result.registered = data.registered;
426
+ result.currentId = data.id;
427
+ result.otherActive = data.otherActive || 0;
428
+ result.cleaned = data.cleaned || 0;
429
+ result.cleanedSessions = data.cleanedSessions || [];
430
+
431
+ if (data.current) {
432
+ result.isMain = data.current.is_main === true;
433
+ result.nickname = data.current.nickname;
434
+ result.branch = data.current.branch;
435
+ result.sessionPath = data.current.path;
436
+ }
527
437
  } catch (e) {
528
- // Registration failed
438
+ // JSON parse failed, fall through to individual calls
439
+ fullStatusResult.data = null;
529
440
  }
441
+ }
530
442
 
531
- try {
532
- const countOutput = execFileSync('node', [scriptPath, 'count'], {
533
- cwd: rootDir,
534
- encoding: 'utf8',
535
- stdio: ['pipe', 'pipe', 'pipe'],
536
- });
537
- const countData = JSON.parse(countOutput);
538
- result.otherActive = countData.count || 0;
539
- } catch (e) {
540
- // Count failed
443
+ if (!fullStatusResult.data) {
444
+ // Fall back to individual calls if full-status not available (older version)
445
+ const registerResult = executeCommandSync('node', [scriptPath, 'register'], {
446
+ cwd: rootDir, fallback: null,
447
+ });
448
+ if (registerResult.data) {
449
+ try {
450
+ const registerData = JSON.parse(registerResult.data);
451
+ result.registered = true;
452
+ result.currentId = registerData.id;
453
+ } catch (e) {}
541
454
  }
542
455
 
543
- try {
544
- const statusOutput = execFileSync('node', [scriptPath, 'status'], {
545
- cwd: rootDir,
546
- encoding: 'utf8',
547
- stdio: ['pipe', 'pipe', 'pipe'],
548
- });
549
- const statusData = JSON.parse(statusOutput);
550
- if (statusData.current) {
551
- result.isMain = statusData.current.is_main === true;
552
- result.nickname = statusData.current.nickname;
553
- result.branch = statusData.current.branch;
554
- result.sessionPath = statusData.current.path;
555
- }
556
- } catch (e) {
557
- // Status failed
456
+ const countResult = executeCommandSync('node', [scriptPath, 'count'], {
457
+ cwd: rootDir, fallback: null,
458
+ });
459
+ if (countResult.data) {
460
+ try {
461
+ const countData = JSON.parse(countResult.data);
462
+ result.otherActive = countData.count || 0;
463
+ } catch (e) {}
464
+ }
465
+
466
+ const statusCmdResult = executeCommandSync('node', [scriptPath, 'status'], {
467
+ cwd: rootDir, fallback: null,
468
+ });
469
+ if (statusCmdResult.data) {
470
+ try {
471
+ const statusData = JSON.parse(statusCmdResult.data);
472
+ if (statusData.current) {
473
+ result.isMain = statusData.current.is_main === true;
474
+ result.nickname = statusData.current.nickname;
475
+ result.branch = statusData.current.branch;
476
+ result.sessionPath = statusData.current.path;
477
+ }
478
+ } catch (e) {}
558
479
  }
559
480
  }
560
481
  } catch (e) {
@@ -1008,6 +929,8 @@ async function checkUpdates() {
1008
929
  changelog: [],
1009
930
  };
1010
931
 
932
+ let updateChecker;
933
+ try { updateChecker = require('./check-update.js'); } catch (e) {}
1011
934
  if (!updateChecker) return result;
1012
935
 
1013
936
  try {
@@ -1076,42 +999,36 @@ function getChangelogEntries(version) {
1076
999
  // DEPRECATED: Use spawnAutoUpdateInBackground() instead for non-blocking updates
1077
1000
  async function runAutoUpdate(rootDir, fromVersion, toVersion) {
1078
1001
  const runUpdate = () => {
1079
- // Use stdio: 'pipe' to capture output instead of showing everything
1080
- return execFileSync('npx', ['agileflow@latest', 'update', '--force'], {
1081
- cwd: rootDir,
1082
- encoding: 'utf8',
1083
- stdio: 'pipe',
1084
- timeout: 120000, // 2 minute timeout
1002
+ return executeCommandSync('npx', ['agileflow@latest', 'update', '--force'], {
1003
+ cwd: rootDir, timeout: 120000,
1085
1004
  });
1086
1005
  };
1087
1006
 
1088
- try {
1089
- console.log(
1090
- `${c.skyBlue}Updating AgileFlow${c.reset} ${c.dim}v${fromVersion} → v${toVersion}${c.reset}`
1091
- );
1092
- // Use --force to skip prompts for non-interactive auto-update
1093
- runUpdate();
1007
+ console.log(
1008
+ `${c.skyBlue}Updating AgileFlow${c.reset} ${c.dim}v${fromVersion} → v${toVersion}${c.reset}`
1009
+ );
1010
+ const result = runUpdate();
1011
+ if (result.ok) {
1094
1012
  console.log(`${c.mintGreen}✓ Update complete${c.reset}`);
1095
1013
  return true;
1096
- } catch (e) {
1097
- // Check if this is a stale npm cache issue (ETARGET = version not found)
1098
- if (e.message && (e.message.includes('ETARGET') || e.message.includes('notarget'))) {
1099
- console.log(`${c.dim} Clearing npm cache and retrying...${c.reset}`);
1100
- try {
1101
- execFileSync('npm', ['cache', 'clean', '--force'], { stdio: 'pipe', timeout: 30000 });
1102
- runUpdate();
1103
- console.log(`${c.mintGreen}✓ Update complete${c.reset}`);
1104
- return true;
1105
- } catch (retryError) {
1106
- console.log(`${c.peach}Auto-update failed after cache clean${c.reset}`);
1107
- console.log(`${c.dim} Run manually: npx agileflow update${c.reset}`);
1108
- return false;
1109
- }
1014
+ }
1015
+
1016
+ // Check if this is a stale npm cache issue (ETARGET = version not found)
1017
+ if (result.error && (result.error.includes('ETARGET') || result.error.includes('notarget'))) {
1018
+ console.log(`${c.dim} Clearing npm cache and retrying...${c.reset}`);
1019
+ executeCommandSync('npm', ['cache', 'clean', '--force'], { timeout: 30000 });
1020
+ const retryResult = runUpdate();
1021
+ if (retryResult.ok) {
1022
+ console.log(`${c.mintGreen}✓ Update complete${c.reset}`);
1023
+ return true;
1110
1024
  }
1111
- console.log(`${c.peach}Auto-update failed${c.reset}`);
1025
+ console.log(`${c.peach}Auto-update failed after cache clean${c.reset}`);
1112
1026
  console.log(`${c.dim} Run manually: npx agileflow update${c.reset}`);
1113
1027
  return false;
1114
1028
  }
1029
+ console.log(`${c.peach}Auto-update failed${c.reset}`);
1030
+ console.log(`${c.dim} Run manually: npx agileflow update${c.reset}`);
1031
+ return false;
1115
1032
  }
1116
1033
 
1117
1034
  /**
@@ -1123,8 +1040,6 @@ async function runAutoUpdate(rootDir, fromVersion, toVersion) {
1123
1040
  * @param {string} toVersion - Target version
1124
1041
  */
1125
1042
  function spawnAutoUpdateInBackground(rootDir, fromVersion, toVersion) {
1126
- const { spawn } = require('child_process');
1127
-
1128
1043
  // Track pending update in session-state.json
1129
1044
  try {
1130
1045
  const sessionStatePath = getSessionStatePath(rootDir);
@@ -1143,16 +1058,7 @@ function spawnAutoUpdateInBackground(rootDir, fromVersion, toVersion) {
1143
1058
  }
1144
1059
 
1145
1060
  // Create detached subprocess that survives parent exit
1146
- // Use shell: true to handle npx properly across platforms
1147
- const child = spawn('npx', ['agileflow@latest', 'update', '--force'], {
1148
- cwd: rootDir,
1149
- detached: true,
1150
- stdio: 'ignore', // Don't inherit stdout/stderr - prevents output after hook returns
1151
- shell: true,
1152
- });
1153
-
1154
- // Allow parent to exit independently
1155
- child.unref();
1061
+ spawnBackground('npx', ['agileflow@latest', 'update', '--force'], { cwd: rootDir });
1156
1062
 
1157
1063
  console.log(`${c.dim} Auto-update starting in background...${c.reset}`);
1158
1064
  }
@@ -1387,7 +1293,8 @@ function formatTable(
1387
1293
  updateInfo = {},
1388
1294
  expertise = {},
1389
1295
  damageControl = {},
1390
- agentTeamsInfo = {}
1296
+ agentTeamsInfo = {},
1297
+ scaleDetection = {}
1391
1298
  ) {
1392
1299
  const W = 58; // inner width (total table = W + 2 = 60)
1393
1300
  const R = W - 25; // right column width (33 chars) to match total of 60
@@ -1528,6 +1435,8 @@ function formatTable(
1528
1435
  // System section (colorful labels like obtain-context)
1529
1436
  if (archival.disabled) {
1530
1437
  lines.push(row('Auto-archival', 'disabled', c.lavender, c.slate));
1438
+ } else if (archival.skippedByScale) {
1439
+ lines.push(row('Auto-archival', 'skipped (small project)', c.lavender, c.dim));
1531
1440
  } else {
1532
1441
  const archivalStatus =
1533
1442
  archival.archived > 0 ? `archived ${archival.archived} stories` : `nothing to archive`;
@@ -1616,6 +1525,22 @@ function formatTable(
1616
1525
  lines.push(row('Agent Teams', `${agentTeamsInfo.value}`, c.lavender, c.dim));
1617
1526
  }
1618
1527
 
1528
+ // Scale detection (EP-0033)
1529
+ if (scaleDetection && scaleDetection.scale) {
1530
+ const scaleColors = {
1531
+ micro: c.cyan, small: c.teal, medium: c.mintGreen,
1532
+ large: c.peach, enterprise: c.coral,
1533
+ };
1534
+ const scaleIcons = {
1535
+ micro: '◦', small: '○', medium: '◎', large: '●', enterprise: '◉',
1536
+ };
1537
+ const scale = scaleDetection.scale;
1538
+ const icon = scaleIcons[scale] || '◎';
1539
+ const label = scale.charAt(0).toUpperCase() + scale.slice(1);
1540
+ const cacheNote = scaleDetection.fromCache ? '' : ` (${scaleDetection.detection_ms}ms)`;
1541
+ lines.push(row('Scale', `${icon} ${label}${cacheNote}`, c.lavender, scaleColors[scale] || c.dim));
1542
+ }
1543
+
1619
1544
  lines.push(divider());
1620
1545
 
1621
1546
  // Current story (colorful like obtain-context)
@@ -1693,7 +1618,29 @@ async function main() {
1693
1618
  // All fast operations - no network, no auto-update
1694
1619
  // ============================================
1695
1620
  const info = getProjectInfo(rootDir, cache);
1696
- const archival = runArchival(rootDir, cache);
1621
+
1622
+ // Smart hook scheduling: skip archival for micro/small projects (EP-0033)
1623
+ // Scale detection is done early to inform hook scheduling
1624
+ let earlyScale = null;
1625
+ try {
1626
+ const scaleDetector = require('./lib/scale-detector');
1627
+ // Check cache only (fast path, no full detection yet)
1628
+ earlyScale = scaleDetector.detectScale({
1629
+ rootDir,
1630
+ statusJson: cache?.status,
1631
+ sessionState: cache?.sessionState,
1632
+ });
1633
+ } catch (e) {
1634
+ // Scale detection not available
1635
+ }
1636
+
1637
+ const scaleRecommendations = earlyScale ? (() => {
1638
+ try { return require('./lib/scale-detector').getScaleRecommendations(earlyScale.scale); } catch { return null; }
1639
+ })() : null;
1640
+
1641
+ const archival = (scaleRecommendations && scaleRecommendations.skipArchival)
1642
+ ? { ran: false, threshold: 0, archived: 0, remaining: 0, skippedByScale: true }
1643
+ : runArchival(rootDir, cache);
1697
1644
  const session = clearActiveCommands(rootDir, cache);
1698
1645
  const precompact = checkPreCompact(rootDir, cache);
1699
1646
  const parallelSessions = checkParallelSessions(rootDir);
@@ -1702,7 +1649,12 @@ async function main() {
1702
1649
  const expertise = getExpertiseCountFast(rootDir);
1703
1650
  const damageControl = checkDamageControl(rootDir, cache);
1704
1651
 
1652
+ // Use early scale detection result (already computed for hook scheduling)
1653
+ const scaleDetection = earlyScale || { scale: 'medium' };
1654
+
1705
1655
  // Agent Teams feature flag detection
1656
+ let featureFlags;
1657
+ try { featureFlags = require('../lib/feature-flags'); } catch (e) {}
1706
1658
  let agentTeamsInfo = {};
1707
1659
  if (featureFlags) {
1708
1660
  try {
@@ -1794,7 +1746,8 @@ async function main() {
1794
1746
  updateInfo,
1795
1747
  expertise,
1796
1748
  damageControl,
1797
- agentTeamsInfo
1749
+ agentTeamsInfo,
1750
+ scaleDetection
1798
1751
  )
1799
1752
  );
1800
1753
 
@@ -1822,12 +1775,18 @@ async function main() {
1822
1775
  }
1823
1776
 
1824
1777
  // Mark current version as seen to track for next update
1778
+ let updateChecker;
1779
+ try { updateChecker = require('./check-update.js'); } catch (e) {}
1825
1780
  if (freshUpdateInfo.justUpdated && updateChecker) {
1826
1781
  updateChecker.markVersionSeen(info.version);
1827
1782
  }
1828
- } else if (updateChecker) {
1783
+ } else {
1829
1784
  // Mark current version as seen (for "just updated" case)
1830
- updateChecker.markVersionSeen(info.version);
1785
+ let updateChecker;
1786
+ try { updateChecker = require('./check-update.js'); } catch (e) {}
1787
+ if (updateChecker) {
1788
+ updateChecker.markVersionSeen(info.version);
1789
+ }
1831
1790
  }
1832
1791
  } catch (e) {
1833
1792
  // Update check failed - continue without it (non-critical)
@@ -1910,13 +1869,12 @@ async function main() {
1910
1869
  // === SESSION HEALTH WARNINGS ===
1911
1870
  // Check for forgotten sessions with uncommitted changes, stale sessions, orphaned entries
1912
1871
  try {
1913
- const healthResult = spawnSync('node', [SESSION_MANAGER_PATH, 'health'], {
1914
- encoding: 'utf8',
1915
- timeout: 10000,
1872
+ const healthResult = executeCommandSync('node', [SESSION_MANAGER_PATH, 'health'], {
1873
+ timeout: 10000, fallback: null,
1916
1874
  });
1917
1875
 
1918
- if (healthResult.stdout) {
1919
- const health = JSON.parse(healthResult.stdout);
1876
+ if (healthResult.data) {
1877
+ const health = JSON.parse(healthResult.data);
1920
1878
  const hasIssues =
1921
1879
  health.uncommitted.length > 0 ||
1922
1880
  health.stale.length > 0 ||
@@ -1963,6 +1921,8 @@ async function main() {
1963
1921
 
1964
1922
  // === DUPLICATE CLAUDE PROCESS DETECTION ===
1965
1923
  // Check for multiple Claude processes in the same working directory
1924
+ let processCleanup;
1925
+ try { processCleanup = require('./lib/process-cleanup.js'); } catch (e) {}
1966
1926
  if (processCleanup) {
1967
1927
  try {
1968
1928
  // Auto-kill is explicitly opt-in at runtime.
@@ -2015,6 +1975,8 @@ async function main() {
2015
1975
  }
2016
1976
 
2017
1977
  // Story claiming: cleanup stale claims and show warnings
1978
+ let storyClaiming;
1979
+ try { storyClaiming = require('./lib/story-claiming.js'); } catch (e) {}
2018
1980
  if (storyClaiming) {
2019
1981
  try {
2020
1982
  // Clean up stale claims (dead PIDs, expired TTL)
@@ -2040,6 +2002,8 @@ async function main() {
2040
2002
  }
2041
2003
 
2042
2004
  // File tracking: cleanup stale touches and show overlap warnings
2005
+ let fileTracking;
2006
+ try { fileTracking = require('./lib/file-tracking.js'); } catch (e) {}
2043
2007
  if (fileTracking) {
2044
2008
  try {
2045
2009
  // Clean up stale file touches (dead PIDs, expired TTL)
@@ -2063,6 +2027,8 @@ async function main() {
2063
2027
  }
2064
2028
 
2065
2029
  // Epic completion check: auto-complete epics where all stories are done
2030
+ let storyStateMachine;
2031
+ try { storyStateMachine = require('./lib/story-state-machine.js'); } catch (e) {}
2066
2032
  if (storyStateMachine && cache.status) {
2067
2033
  try {
2068
2034
  const statusPath = getStatusPath(rootDir);
@@ -2091,6 +2057,8 @@ async function main() {
2091
2057
  }
2092
2058
 
2093
2059
  // Ideation sync: mark ideas as implemented when linked epics complete
2060
+ let syncIdeationStatus;
2061
+ try { syncIdeationStatus = require('./lib/sync-ideation-status.js'); } catch (e) {}
2094
2062
  if (syncIdeationStatus) {
2095
2063
  try {
2096
2064
  const syncResult = syncIdeationStatus.syncImplementedIdeas(rootDir);
@@ -2105,6 +2073,13 @@ async function main() {
2105
2073
 
2106
2074
  // === SCHEDULED AUTOMATIONS ===
2107
2075
  // Check for and run due automations (non-blocking)
2076
+ let automationRegistry, automationRunner;
2077
+ try {
2078
+ automationRegistry = require('./lib/automation-registry.js');
2079
+ automationRunner = require('./lib/automation-runner.js');
2080
+ } catch (e) {
2081
+ // Automation system not available
2082
+ }
2108
2083
  if (automationRegistry && automationRunner) {
2109
2084
  try {
2110
2085
  const registry = automationRegistry.getAutomationRegistry({ rootDir });
@@ -2125,17 +2100,11 @@ async function main() {
2125
2100
 
2126
2101
  // Run due automations in background (spawn detached process)
2127
2102
  // This prevents blocking the welcome hook
2128
- const { spawn } = require('child_process');
2129
2103
  const runnerScriptPath = path.join(__dirname, 'automation-run-due.js');
2130
2104
 
2131
2105
  // Only spawn if the runner script exists
2132
2106
  if (fs.existsSync(runnerScriptPath)) {
2133
- const child = spawn('node', [runnerScriptPath], {
2134
- cwd: rootDir,
2135
- detached: true,
2136
- stdio: 'ignore',
2137
- });
2138
- child.unref();
2107
+ spawnBackground('node', [runnerScriptPath], { cwd: rootDir });
2139
2108
  console.log(`${c.dim} Running in background...${c.reset}`);
2140
2109
  } else {
2141
2110
  console.log(`${c.slate} Run: ${c.skyBlue}/agileflow:automate ACTION=run-due${c.reset}`);
@@ -24,7 +24,7 @@ const { execFileSync } = require('child_process');
24
24
  // Shared utilities
25
25
  const { c } = require('../lib/colors');
26
26
  const { getProjectRoot } = require('../lib/paths');
27
- const { safeReadJSON, safeReadFile, safeWriteFile } = require('../lib/errors');
27
+ const { safeReadJSON, safeReadFile, safeWriteFile, tryOptional } = require('../lib/errors');
28
28
 
29
29
  // Agents that have expertise files
30
30
  const AGENTS_WITH_EXPERTISE = [
@@ -114,7 +114,7 @@ function getGitDiff(rootDir) {
114
114
  // Get diff stats
115
115
  let additions = 0;
116
116
  let deletions = 0;
117
- try {
117
+ tryOptional(() => {
118
118
  const stats = execFileSync('git', ['diff', '--shortstat', 'HEAD'], {
119
119
  cwd: rootDir,
120
120
  encoding: 'utf8',
@@ -124,7 +124,7 @@ function getGitDiff(rootDir) {
124
124
  const delMatch = stats.match(/(\d+) deletion/);
125
125
  if (addMatch) additions = parseInt(addMatch[1]);
126
126
  if (delMatch) deletions = parseInt(delMatch[1]);
127
- } catch (e) {}
127
+ }, 'git diff stats');
128
128
 
129
129
  return {
130
130
  files: allFiles,