agileflow 3.0.0 → 3.0.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 (57) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +6 -6
  3. package/lib/api-server.js +3 -2
  4. package/lib/dashboard-server.js +131 -50
  5. package/lib/flag-detection.js +4 -2
  6. package/lib/git-operations.js +4 -2
  7. package/lib/process-executor.js +24 -9
  8. package/lib/skill-loader.js +11 -3
  9. package/package.json +1 -1
  10. package/scripts/agileflow-welcome.js +65 -25
  11. package/scripts/archive-completed-stories.sh +3 -0
  12. package/scripts/ci-summary.js +294 -0
  13. package/scripts/claude-smart.sh +18 -0
  14. package/scripts/claude-tmux.sh +50 -20
  15. package/scripts/damage-control-multi-agent.js +14 -10
  16. package/scripts/lib/bus-utils.js +3 -1
  17. package/scripts/lib/configure-detect.js +89 -8
  18. package/scripts/lib/configure-features.js +77 -10
  19. package/scripts/lib/configure-repair.js +6 -5
  20. package/scripts/lib/context-formatter.js +13 -3
  21. package/scripts/lib/damage-control-utils.js +5 -1
  22. package/scripts/lib/lifecycle-detector.js +5 -3
  23. package/scripts/lib/process-cleanup.js +8 -4
  24. package/scripts/lib/scale-detector.js +47 -8
  25. package/scripts/lib/signal-detectors.js +117 -59
  26. package/scripts/lib/task-registry.js +5 -1
  27. package/scripts/lib/team-events.js +4 -4
  28. package/scripts/messaging-bridge.js +7 -1
  29. package/scripts/precompact-context.sh +3 -0
  30. package/scripts/ralph-loop.js +10 -8
  31. package/scripts/smart-detect.js +32 -11
  32. package/scripts/team-manager.js +1 -1
  33. package/scripts/tmux-task-name.sh +75 -0
  34. package/scripts/tmux-task-watcher.sh +177 -0
  35. package/src/core/commands/babysit.md +75 -42
  36. package/src/core/commands/blockers.md +7 -7
  37. package/src/core/commands/configure.md +49 -63
  38. package/src/core/commands/discovery/brief.md +363 -0
  39. package/src/core/commands/discovery/new.md +395 -0
  40. package/src/core/commands/ideate/new.md +5 -5
  41. package/src/core/commands/logic/audit.md +5 -5
  42. package/src/core/commands/review.md +7 -1
  43. package/src/core/commands/rpi.md +61 -26
  44. package/src/core/commands/sprint.md +7 -6
  45. package/src/core/templates/product-brief.md +136 -0
  46. package/tools/cli/installers/ide/claude-code.js +67 -2
  47. package/src/core/agents/configuration/archival.md +0 -350
  48. package/src/core/agents/configuration/attribution.md +0 -343
  49. package/src/core/agents/configuration/ci.md +0 -1103
  50. package/src/core/agents/configuration/damage-control.md +0 -375
  51. package/src/core/agents/configuration/git-config.md +0 -537
  52. package/src/core/agents/configuration/hooks.md +0 -623
  53. package/src/core/agents/configuration/precompact.md +0 -302
  54. package/src/core/agents/configuration/status-line.md +0 -557
  55. package/src/core/agents/configuration/verify.md +0 -618
  56. package/src/core/agents/configuration-damage-control.md +0 -259
  57. package/src/core/agents/configuration-visual-e2e.md +0 -339
@@ -100,7 +100,7 @@ const FEATURE_DETECTORS = {
100
100
  // PRE-STORY PHASE
101
101
  // =========================================================================
102
102
 
103
- 'story-validate': (signals) => {
103
+ 'story-validate': signals => {
104
104
  const { story } = signals;
105
105
  if (!story || !story.id) return null;
106
106
  if (story.status !== 'ready' && story.status !== 'in-progress') return null;
@@ -115,7 +115,7 @@ const FEATURE_DETECTORS = {
115
115
  return null;
116
116
  },
117
117
 
118
- 'blockers': (signals) => {
118
+ blockers: signals => {
119
119
  const blocked = getStoriesByStatus(signals.statusJson, 'blocked');
120
120
  if (blocked.length === 0) return null;
121
121
  return recommend('blockers', {
@@ -126,7 +126,7 @@ const FEATURE_DETECTORS = {
126
126
  });
127
127
  },
128
128
 
129
- 'choose': (signals) => {
129
+ choose: signals => {
130
130
  const { story, counts } = signals;
131
131
  if (story && story.id) return null; // Already have a story
132
132
  if ((counts.ready || 0) < 2) return null;
@@ -138,7 +138,7 @@ const FEATURE_DETECTORS = {
138
138
  });
139
139
  },
140
140
 
141
- 'assign': (signals) => {
141
+ assign: signals => {
142
142
  const ready = getStoriesByStatus(signals.statusJson, 'ready');
143
143
  const unassigned = ready.filter(s => !s.owner);
144
144
  if (unassigned.length === 0) return null;
@@ -150,7 +150,7 @@ const FEATURE_DETECTORS = {
150
150
  });
151
151
  },
152
152
 
153
- 'board': (signals) => {
153
+ board: signals => {
154
154
  const { storyCount } = signals;
155
155
  if (!storyCount || storyCount < 5) return null;
156
156
  return recommend('board', {
@@ -161,7 +161,7 @@ const FEATURE_DETECTORS = {
161
161
  });
162
162
  },
163
163
 
164
- 'sprint': (signals) => {
164
+ sprint: signals => {
165
165
  const { counts } = signals;
166
166
  if ((counts.ready || 0) < 3) return null;
167
167
  return recommend('sprint', {
@@ -172,7 +172,7 @@ const FEATURE_DETECTORS = {
172
172
  });
173
173
  },
174
174
 
175
- 'batch': (signals) => {
175
+ batch: signals => {
176
176
  const ready = getStoriesByStatus(signals.statusJson, 'ready');
177
177
  if (ready.length < 5) return null;
178
178
  // Check if stories share same epic (good batch candidate)
@@ -193,7 +193,7 @@ const FEATURE_DETECTORS = {
193
193
  });
194
194
  },
195
195
 
196
- 'workflow': (signals) => {
196
+ workflow: signals => {
197
197
  const { metadata } = signals;
198
198
  const workflows = metadata?.workflows;
199
199
  if (!workflows || Object.keys(workflows).length === 0) return null;
@@ -205,7 +205,7 @@ const FEATURE_DETECTORS = {
205
205
  });
206
206
  },
207
207
 
208
- 'template': (signals) => {
208
+ template: signals => {
209
209
  const { story } = signals;
210
210
  if (!story || story.status !== 'ready') return null;
211
211
  if (!story.title) return null;
@@ -221,7 +221,7 @@ const FEATURE_DETECTORS = {
221
221
  return null;
222
222
  },
223
223
 
224
- 'configure': (signals) => {
224
+ configure: signals => {
225
225
  const { metadata } = signals;
226
226
  // Only suggest if metadata is minimal/missing
227
227
  if (metadata && Object.keys(metadata).length > 3) return null;
@@ -237,7 +237,7 @@ const FEATURE_DETECTORS = {
237
237
  // PLANNING PHASE
238
238
  // =========================================================================
239
239
 
240
- 'impact': (signals) => {
240
+ impact: signals => {
241
241
  const { git, story } = signals;
242
242
  if (!story || story.status !== 'in-progress') return null;
243
243
  // Suggest impact analysis if touching core/shared files
@@ -253,10 +253,19 @@ const FEATURE_DETECTORS = {
253
253
  });
254
254
  },
255
255
 
256
- 'adr': (signals) => {
256
+ adr: signals => {
257
257
  const { story } = signals;
258
258
  if (!story || !story.id) return null;
259
- if (storyMentions(story, ['architecture', 'redesign', 'migrate', 'replace', 'new system', 'framework'])) {
259
+ if (
260
+ storyMentions(story, [
261
+ 'architecture',
262
+ 'redesign',
263
+ 'migrate',
264
+ 'replace',
265
+ 'new system',
266
+ 'framework',
267
+ ])
268
+ ) {
260
269
  return recommend('adr', {
261
270
  priority: 'medium',
262
271
  trigger: 'Story involves architectural decisions',
@@ -268,10 +277,20 @@ const FEATURE_DETECTORS = {
268
277
  return null;
269
278
  },
270
279
 
271
- 'research': (signals) => {
280
+ research: signals => {
272
281
  const { story } = signals;
273
282
  if (!story || !story.id) return null;
274
- if (storyMentions(story, ['research', 'investigate', 'evaluate', 'compare', 'POC', 'proof of concept', 'spike'])) {
283
+ if (
284
+ storyMentions(story, [
285
+ 'research',
286
+ 'investigate',
287
+ 'evaluate',
288
+ 'compare',
289
+ 'POC',
290
+ 'proof of concept',
291
+ 'spike',
292
+ ])
293
+ ) {
275
294
  return recommend('research', {
276
295
  priority: 'medium',
277
296
  trigger: 'Story involves research/investigation',
@@ -283,7 +302,7 @@ const FEATURE_DETECTORS = {
283
302
  return null;
284
303
  },
285
304
 
286
- 'baseline': (signals) => {
305
+ baseline: signals => {
287
306
  const { story, files } = signals;
288
307
  if (!story || story.status !== 'in-progress') return null;
289
308
  if (!files.coverage) return null;
@@ -296,7 +315,7 @@ const FEATURE_DETECTORS = {
296
315
  });
297
316
  },
298
317
 
299
- 'council': (signals) => {
318
+ council: signals => {
300
319
  const { story } = signals;
301
320
  if (!story || !story.id) return null;
302
321
  if (storyMentions(story, ['strategic', 'trade-off', 'decision', 'approach', 'architecture'])) {
@@ -310,7 +329,7 @@ const FEATURE_DETECTORS = {
310
329
  return null;
311
330
  },
312
331
 
313
- 'multi-expert': (signals) => {
332
+ 'multi-expert': signals => {
314
333
  const { story } = signals;
315
334
  if (!story || !story.id) return null;
316
335
  if (storyMentions(story, ['complex', 'cross-cutting', 'full-stack', 'multi-domain'])) {
@@ -324,7 +343,7 @@ const FEATURE_DETECTORS = {
324
343
  return null;
325
344
  },
326
345
 
327
- 'validate-expertise': (signals) => {
346
+ 'validate-expertise': signals => {
328
347
  const { files } = signals;
329
348
  if (!files.expertiseDir) return null;
330
349
  return recommend('validate-expertise', {
@@ -339,7 +358,7 @@ const FEATURE_DETECTORS = {
339
358
  // IMPLEMENTATION PHASE
340
359
  // =========================================================================
341
360
 
342
- 'verify': (signals) => {
361
+ verify: signals => {
343
362
  const { story, tests, git } = signals;
344
363
  if (!story || story.status !== 'in-progress') return null;
345
364
  if ((git.filesChanged || 0) === 0) return null;
@@ -354,7 +373,7 @@ const FEATURE_DETECTORS = {
354
373
  return null;
355
374
  },
356
375
 
357
- 'tests': (signals) => {
376
+ tests: signals => {
358
377
  const { story, files, packageJson } = signals;
359
378
  if (!story || story.status !== 'in-progress') return null;
360
379
  if (!hasPackageScript(packageJson, 'test')) {
@@ -368,7 +387,7 @@ const FEATURE_DETECTORS = {
368
387
  return null;
369
388
  },
370
389
 
371
- 'audit': (signals) => {
390
+ audit: signals => {
372
391
  const { story, git } = signals;
373
392
  if (!story || story.status !== 'in-progress') return null;
374
393
  if ((git.filesChanged || 0) < 5) return null;
@@ -381,7 +400,7 @@ const FEATURE_DETECTORS = {
381
400
  });
382
401
  },
383
402
 
384
- 'ci': (signals) => {
403
+ ci: signals => {
385
404
  const { files } = signals;
386
405
  if (files.ciConfig) return null; // Already has CI
387
406
  return recommend('ci', {
@@ -392,11 +411,12 @@ const FEATURE_DETECTORS = {
392
411
  });
393
412
  },
394
413
 
395
- 'deps': (signals) => {
414
+ deps: signals => {
396
415
  const { packageJson } = signals;
397
416
  if (!packageJson) return null;
398
417
  // Check for outdated or vulnerable deps signal
399
- const depCount = Object.keys(packageJson.dependencies || {}).length +
418
+ const depCount =
419
+ Object.keys(packageJson.dependencies || {}).length +
400
420
  Object.keys(packageJson.devDependencies || {}).length;
401
421
  if (depCount < 10) return null;
402
422
  return recommend('deps', {
@@ -407,7 +427,7 @@ const FEATURE_DETECTORS = {
407
427
  });
408
428
  },
409
429
 
410
- 'diagnose': (signals) => {
430
+ diagnose: signals => {
411
431
  const { sessionState } = signals;
412
432
  // Detect if there have been recent errors or stuck patterns
413
433
  const failCount = sessionState?.failure_count || 0;
@@ -420,7 +440,7 @@ const FEATURE_DETECTORS = {
420
440
  });
421
441
  },
422
442
 
423
- 'debt': (signals) => {
443
+ debt: signals => {
424
444
  const { story } = signals;
425
445
  if (!story || !story.id) return null;
426
446
  if (storyMentions(story, ['refactor', 'cleanup', 'tech debt', 'legacy', 'deprecat'])) {
@@ -434,7 +454,7 @@ const FEATURE_DETECTORS = {
434
454
  return null;
435
455
  },
436
456
 
437
- 'maintain': (signals) => {
457
+ maintain: signals => {
438
458
  const { story } = signals;
439
459
  if (!story || !story.id) return null;
440
460
  if (storyMentions(story, ['maintenance', 'update', 'upgrade', 'patch', 'housekeeping'])) {
@@ -448,10 +468,19 @@ const FEATURE_DETECTORS = {
448
468
  return null;
449
469
  },
450
470
 
451
- 'packages': (signals) => {
471
+ packages: signals => {
452
472
  const { story } = signals;
453
473
  if (!story || !story.id) return null;
454
- if (storyMentions(story, ['dependency', 'dependencies', 'package', 'upgrade', 'npm', 'vulnerability'])) {
474
+ if (
475
+ storyMentions(story, [
476
+ 'dependency',
477
+ 'dependencies',
478
+ 'package',
479
+ 'upgrade',
480
+ 'npm',
481
+ 'vulnerability',
482
+ ])
483
+ ) {
455
484
  return recommend('packages', {
456
485
  priority: 'medium',
457
486
  trigger: 'Story involves dependency management',
@@ -462,7 +491,7 @@ const FEATURE_DETECTORS = {
462
491
  return null;
463
492
  },
464
493
 
465
- 'deploy': (signals) => {
494
+ deploy: signals => {
466
495
  const { story } = signals;
467
496
  if (!story || !story.id) return null;
468
497
  if (storyMentions(story, ['deploy', 'deployment', 'CD', 'pipeline', 'staging', 'production'])) {
@@ -476,7 +505,7 @@ const FEATURE_DETECTORS = {
476
505
  return null;
477
506
  },
478
507
 
479
- 'serve': (signals) => {
508
+ serve: signals => {
480
509
  const { metadata } = signals;
481
510
  const dashboardEnabled = metadata?.features?.dashboard?.enabled;
482
511
  if (!dashboardEnabled) return null;
@@ -492,7 +521,7 @@ const FEATURE_DETECTORS = {
492
521
  // POST-IMPLEMENTATION PHASE
493
522
  // =========================================================================
494
523
 
495
- 'review': (signals) => {
524
+ review: signals => {
496
525
  const { git, story } = signals;
497
526
  if (!story || story.status !== 'in-progress') return null;
498
527
  const linesChanged = (git.diffStats?.insertions || 0) + (git.diffStats?.deletions || 0);
@@ -505,7 +534,7 @@ const FEATURE_DETECTORS = {
505
534
  });
506
535
  },
507
536
 
508
- 'logic-audit': (signals) => {
537
+ 'logic-audit': signals => {
509
538
  const { git, story } = signals;
510
539
  if (!story || story.status !== 'in-progress') return null;
511
540
  // Suggest logic audit for complex changes
@@ -522,7 +551,7 @@ const FEATURE_DETECTORS = {
522
551
  });
523
552
  },
524
553
 
525
- 'docs': (signals) => {
554
+ docs: signals => {
526
555
  const { git, story } = signals;
527
556
  if (!story || story.status !== 'in-progress') return null;
528
557
  // Detect API or public interface changes
@@ -538,7 +567,7 @@ const FEATURE_DETECTORS = {
538
567
  });
539
568
  },
540
569
 
541
- 'changelog': (signals) => {
570
+ changelog: signals => {
542
571
  const { git } = signals;
543
572
  // Suggest changelog if there are multiple commits on feature branch
544
573
  if (!git.onFeatureBranch) return null;
@@ -551,7 +580,7 @@ const FEATURE_DETECTORS = {
551
580
  });
552
581
  },
553
582
 
554
- 'metrics': (signals) => {
583
+ metrics: signals => {
555
584
  const { statusJson } = signals;
556
585
  if (!statusJson || !statusJson.stories) return null;
557
586
  const doneCount = getStoriesByStatus(statusJson, 'done').length;
@@ -564,7 +593,7 @@ const FEATURE_DETECTORS = {
564
593
  });
565
594
  },
566
595
 
567
- 'retro': (signals) => {
596
+ retro: signals => {
568
597
  const { statusJson } = signals;
569
598
  if (!statusJson || !statusJson.epics) return null;
570
599
  // Suggest retro when an epic is mostly complete
@@ -583,7 +612,7 @@ const FEATURE_DETECTORS = {
583
612
  return null;
584
613
  },
585
614
 
586
- 'velocity': (signals) => {
615
+ velocity: signals => {
587
616
  const { statusJson } = signals;
588
617
  if (!statusJson || !statusJson.stories) return null;
589
618
  const doneCount = getStoriesByStatus(statusJson, 'done').length;
@@ -596,11 +625,11 @@ const FEATURE_DETECTORS = {
596
625
  });
597
626
  },
598
627
 
599
- 'readme-sync': (signals) => {
628
+ 'readme-sync': signals => {
600
629
  const { git } = signals;
601
630
  // Check if any README files were potentially affected
602
- const readmeAffected = (git.changedFiles || []).some(f =>
603
- /readme/i.test(f) || /^(src|packages|apps)\/[^/]+\//.test(f)
631
+ const readmeAffected = (git.changedFiles || []).some(
632
+ f => /readme/i.test(f) || /^(src|packages|apps)\/[^/]+\//.test(f)
604
633
  );
605
634
  if (!readmeAffected) return null;
606
635
  return recommend('readme-sync', {
@@ -611,11 +640,13 @@ const FEATURE_DETECTORS = {
611
640
  });
612
641
  },
613
642
 
614
- 'feedback': (signals) => {
643
+ feedback: signals => {
615
644
  const { sessionState } = signals;
616
645
  // Suggest feedback collection after extended sessions
617
646
  const sessionDuration = sessionState?.current_session?.started_at
618
- ? Math.round((Date.now() - new Date(sessionState.current_session.started_at).getTime()) / 60000)
647
+ ? Math.round(
648
+ (Date.now() - new Date(sessionState.current_session.started_at).getTime()) / 60000
649
+ )
619
650
  : 0;
620
651
  if (isNaN(sessionDuration) || sessionDuration < 30) return null;
621
652
  return recommend('feedback', {
@@ -630,7 +661,7 @@ const FEATURE_DETECTORS = {
630
661
  // PRE-PR PHASE
631
662
  // =========================================================================
632
663
 
633
- 'pr': (signals) => {
664
+ pr: signals => {
634
665
  const { git, tests, story } = signals;
635
666
  if (!story || story.status !== 'in-progress') return null;
636
667
  if (!git.onFeatureBranch) return null;
@@ -643,7 +674,7 @@ const FEATURE_DETECTORS = {
643
674
  });
644
675
  },
645
676
 
646
- 'compress': (signals) => {
677
+ compress: signals => {
647
678
  const { statusJson } = signals;
648
679
  if (!statusJson || !statusJson.stories) return null;
649
680
  const totalStories = Object.keys(statusJson.stories).length;
@@ -663,24 +694,51 @@ const FEATURE_DETECTORS = {
663
694
 
664
695
  const PHASE_MAP = {
665
696
  'pre-story': [
666
- 'story-validate', 'blockers', 'choose', 'assign', 'board',
667
- 'sprint', 'batch', 'workflow', 'template', 'configure',
697
+ 'story-validate',
698
+ 'blockers',
699
+ 'choose',
700
+ 'assign',
701
+ 'board',
702
+ 'sprint',
703
+ 'batch',
704
+ 'workflow',
705
+ 'template',
706
+ 'configure',
668
707
  ],
669
- 'planning': [
670
- 'impact', 'adr', 'research', 'baseline', 'council',
671
- 'multi-expert', 'validate-expertise',
708
+ planning: [
709
+ 'impact',
710
+ 'adr',
711
+ 'research',
712
+ 'baseline',
713
+ 'council',
714
+ 'multi-expert',
715
+ 'validate-expertise',
672
716
  ],
673
- 'implementation': [
674
- 'verify', 'tests', 'audit', 'ci', 'deps',
675
- 'diagnose', 'debt', 'maintain', 'packages', 'deploy', 'serve',
717
+ implementation: [
718
+ 'verify',
719
+ 'tests',
720
+ 'audit',
721
+ 'ci',
722
+ 'deps',
723
+ 'diagnose',
724
+ 'debt',
725
+ 'maintain',
726
+ 'packages',
727
+ 'deploy',
728
+ 'serve',
676
729
  ],
677
730
  'post-impl': [
678
- 'review', 'logic-audit', 'docs', 'changelog', 'metrics',
679
- 'retro', 'velocity', 'readme-sync', 'feedback',
680
- ],
681
- 'pre-pr': [
682
- 'pr', 'compress',
731
+ 'review',
732
+ 'logic-audit',
733
+ 'docs',
734
+ 'changelog',
735
+ 'metrics',
736
+ 'retro',
737
+ 'velocity',
738
+ 'readme-sync',
739
+ 'feedback',
683
740
  ],
741
+ 'pre-pr': ['pr', 'compress'],
684
742
  };
685
743
 
686
744
  /**
@@ -354,7 +354,11 @@ class FileLock {
354
354
  } catch (e) {
355
355
  // Clean up fd if still open
356
356
  if (fd !== null) {
357
- try { fs.closeSync(fd); } catch (_) { /* ignore */ }
357
+ try {
358
+ fs.closeSync(fd);
359
+ } catch (_) {
360
+ /* ignore */
361
+ }
358
362
  }
359
363
  if (e.code === 'EEXIST') {
360
364
  // Another process got the lock
@@ -111,7 +111,7 @@ function trackEvent(rootDir, eventType, data = {}) {
111
111
  const fileLock = getFileLock();
112
112
 
113
113
  if (fileLock && fs.existsSync(sessionStatePath)) {
114
- fileLock.atomicReadModifyWrite(sessionStatePath, (state) => {
114
+ fileLock.atomicReadModifyWrite(sessionStatePath, state => {
115
115
  if (!state.hook_metrics) state.hook_metrics = {};
116
116
  if (!state.hook_metrics.teams) state.hook_metrics.teams = { events: [], summary: {} };
117
117
 
@@ -230,7 +230,7 @@ function aggregateTeamMetrics(rootDir, traceId) {
230
230
 
231
231
  // Per-agent metrics from task_completed, agent_error, agent_timeout
232
232
  const perAgent = {};
233
- const ensureAgent = (agent) => {
233
+ const ensureAgent = agent => {
234
234
  if (!perAgent[agent]) {
235
235
  perAgent[agent] = { total_duration_ms: 0, tasks_completed: 0, errors: 0, timeouts: 0 };
236
236
  }
@@ -239,7 +239,7 @@ function aggregateTeamMetrics(rootDir, traceId) {
239
239
  for (const e of events) {
240
240
  if (e.type === 'task_completed' && e.agent) {
241
241
  ensureAgent(e.agent);
242
- perAgent[e.agent].total_duration_ms += (e.duration_ms || 0);
242
+ perAgent[e.agent].total_duration_ms += e.duration_ms || 0;
243
243
  perAgent[e.agent].tasks_completed++;
244
244
  }
245
245
  if (e.type === 'agent_error' && e.agent) {
@@ -306,7 +306,7 @@ function saveAggregatedMetrics(rootDir, metrics) {
306
306
  const fileLock = getFileLock();
307
307
 
308
308
  if (fileLock && fs.existsSync(sessionStatePath)) {
309
- fileLock.atomicReadModifyWrite(sessionStatePath, (state) => {
309
+ fileLock.atomicReadModifyWrite(sessionStatePath, state => {
310
310
  if (!state.team_metrics) state.team_metrics = {};
311
311
  if (!state.team_metrics.traces) state.team_metrics.traces = {};
312
312
  state.team_metrics.traces[metrics.trace_id] = {
@@ -266,7 +266,13 @@ function sendPlanProposal(rootDir, from, to, taskId, plan, traceId) {
266
266
  * Send a plan approval/rejection message.
267
267
  */
268
268
  function sendPlanDecision(rootDir, from, to, taskId, approved, reason, traceId) {
269
- const msg = { from, to, type: approved ? 'plan_approved' : 'plan_rejected', task_id: taskId, reason };
269
+ const msg = {
270
+ from,
271
+ to,
272
+ type: approved ? 'plan_approved' : 'plan_rejected',
273
+ task_id: taskId,
274
+ reason,
275
+ };
270
276
  if (traceId) msg.trace_id = traceId;
271
277
  return sendMessage(rootDir, msg);
272
278
  }
@@ -10,6 +10,8 @@
10
10
 
11
11
  # Track start time for hook metrics
12
12
  HOOK_START_TIME=$(date +%s%3N 2>/dev/null || date +%s)
13
+ # macOS date doesn't support %N - outputs literal "3N" instead of millis
14
+ [[ ! "$HOOK_START_TIME" =~ ^[0-9]+$ ]] && HOOK_START_TIME="$(date +%s)000"
13
15
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
14
16
 
15
17
  # Get current version from package.json
@@ -286,6 +288,7 @@ fi
286
288
  # Record hook metrics
287
289
  if command -v node &> /dev/null && [[ -f "$SCRIPT_DIR/lib/hook-metrics.js" ]]; then
288
290
  HOOK_END_TIME=$(date +%s%3N 2>/dev/null || date +%s)
291
+ [[ ! "$HOOK_END_TIME" =~ ^[0-9]+$ ]] && HOOK_END_TIME="$(date +%s)000"
289
292
  HOOK_DURATION=$((HOOK_END_TIME - HOOK_START_TIME))
290
293
  HOOK_DURATION="$HOOK_DURATION" node -e '
291
294
  try {
@@ -428,15 +428,17 @@ function runCoverage(rootDir) {
428
428
 
429
429
  // Get screenshots directory from metadata or default
430
430
  function getScreenshotsDir(rootDir) {
431
- return tryOptional(() => {
432
- const metadataPath = path.join(rootDir, 'docs/00-meta/agileflow-metadata.json');
433
- if (fs.existsSync(metadataPath)) {
434
- const metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf8'));
435
- if (metadata.ralph_loop?.screenshots_dir) {
436
- return metadata.ralph_loop.screenshots_dir;
431
+ return (
432
+ tryOptional(() => {
433
+ const metadataPath = path.join(rootDir, 'docs/00-meta/agileflow-metadata.json');
434
+ if (fs.existsSync(metadataPath)) {
435
+ const metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf8'));
436
+ if (metadata.ralph_loop?.screenshots_dir) {
437
+ return metadata.ralph_loop.screenshots_dir;
438
+ }
437
439
  }
438
- }
439
- }, 'metadata read') || './screenshots';
440
+ }, 'metadata read') || './screenshots'
441
+ );
440
442
  }
441
443
 
442
444
  // Run screenshot verification (Visual Mode)
@@ -54,7 +54,13 @@ try {
54
54
  return { ok: false, error: e.message };
55
55
  }
56
56
  };
57
- tryOptional = (fn, _label) => { try { return fn(); } catch { return undefined; } };
57
+ tryOptional = (fn, _label) => {
58
+ try {
59
+ return fn();
60
+ } catch {
61
+ return undefined;
62
+ }
63
+ };
58
64
  }
59
65
 
60
66
  // =============================================================================
@@ -121,11 +127,17 @@ function extractSignals(prefetched, sessionState, metadata) {
121
127
  // File existence checks
122
128
  const files = {
123
129
  tsconfig: fs.existsSync('tsconfig.json'),
124
- eslintrc: fs.existsSync('.eslintrc.js') || fs.existsSync('.eslintrc.json') || fs.existsSync('.eslintrc.yml'),
130
+ eslintrc:
131
+ fs.existsSync('.eslintrc.js') ||
132
+ fs.existsSync('.eslintrc.json') ||
133
+ fs.existsSync('.eslintrc.yml'),
125
134
  coverage: fs.existsSync('coverage/coverage-summary.json'),
126
135
  playwright: fs.existsSync('playwright.config.ts') || fs.existsSync('playwright.config.js'),
127
136
  screenshots: fs.existsSync('screenshots'),
128
- ciConfig: fs.existsSync('.github/workflows') || fs.existsSync('.gitlab-ci.yml') || fs.existsSync('Jenkinsfile'),
137
+ ciConfig:
138
+ fs.existsSync('.github/workflows') ||
139
+ fs.existsSync('.gitlab-ci.yml') ||
140
+ fs.existsSync('Jenkinsfile'),
129
141
  expertiseDir: fs.existsSync('.agileflow/expertise'),
130
142
  };
131
143
 
@@ -255,7 +267,11 @@ function analyze(prefetched, sessionState, metadata) {
255
267
  const rawRecommendations = runDetectorsForPhases(relevantPhases, signals);
256
268
 
257
269
  // Filter and categorize
258
- const { immediate, available } = filterRecommendations(rawRecommendations, metadata, sessionState);
270
+ const { immediate, available } = filterRecommendations(
271
+ rawRecommendations,
272
+ metadata,
273
+ sessionState
274
+ );
259
275
 
260
276
  // Auto-enabled features (existing babysit modes)
261
277
  const autoEnabled = detectAutoModes(signals);
@@ -304,7 +320,7 @@ function detectAutoModes(signals) {
304
320
  const readyInEpic = Object.entries(statusJson.stories).filter(
305
321
  ([, s]) => s.epic === story.epic && s.status === 'ready'
306
322
  ).length;
307
- loopMode = readyInEpic >= 3 && !!(packageJson?.scripts?.test);
323
+ loopMode = readyInEpic >= 3 && !!packageJson?.scripts?.test;
308
324
  }
309
325
 
310
326
  // Visual mode: UI-related story or visual e2e setup
@@ -342,10 +358,9 @@ function writeRecommendations(results, outputPath) {
342
358
 
343
359
  if (require.main === module) {
344
360
  // Run standalone - gather our own data
345
- const statusJsonResult = safeReadJSON(
346
- path.join(process.cwd(), 'docs/09-agents/status.json'),
347
- { defaultValue: {} }
348
- );
361
+ const statusJsonResult = safeReadJSON(path.join(process.cwd(), 'docs/09-agents/status.json'), {
362
+ defaultValue: {},
363
+ });
349
364
  const sessionStateResult = safeReadJSON(
350
365
  path.join(process.cwd(), 'docs/09-agents/session-state.json'),
351
366
  { defaultValue: {} }
@@ -357,8 +372,14 @@ if (require.main === module) {
357
372
 
358
373
  // Build minimal prefetched structure
359
374
  const { execSync } = require('child_process');
360
- const gitBranch = tryOptional(() => execSync('git branch --show-current', { encoding: 'utf8' }).trim(), 'git branch') || '';
361
- const gitStatus = tryOptional(() => execSync('git status --short', { encoding: 'utf8' }).trim(), 'git status') || '';
375
+ const gitBranch =
376
+ tryOptional(
377
+ () => execSync('git branch --show-current', { encoding: 'utf8' }).trim(),
378
+ 'git branch'
379
+ ) || '';
380
+ const gitStatus =
381
+ tryOptional(() => execSync('git status --short', { encoding: 'utf8' }).trim(), 'git status') ||
382
+ '';
362
383
 
363
384
  const prefetched = {
364
385
  json: {
@@ -204,7 +204,7 @@ function startTeam(rootDir, templateName) {
204
204
  const sessionStatePath = paths.getSessionStatePath(rootDir);
205
205
  const fileLock = getFileLock();
206
206
 
207
- const updateState = (state) => {
207
+ const updateState = state => {
208
208
  state.active_team = {
209
209
  template: templateName,
210
210
  mode,