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.
- package/CHANGELOG.md +10 -0
- package/README.md +6 -6
- package/lib/api-server.js +3 -2
- package/lib/dashboard-server.js +131 -50
- package/lib/flag-detection.js +4 -2
- package/lib/git-operations.js +4 -2
- package/lib/process-executor.js +24 -9
- package/lib/skill-loader.js +11 -3
- package/package.json +1 -1
- package/scripts/agileflow-welcome.js +65 -25
- package/scripts/archive-completed-stories.sh +3 -0
- package/scripts/ci-summary.js +294 -0
- package/scripts/claude-smart.sh +18 -0
- package/scripts/claude-tmux.sh +50 -20
- package/scripts/damage-control-multi-agent.js +14 -10
- package/scripts/lib/bus-utils.js +3 -1
- package/scripts/lib/configure-detect.js +89 -8
- package/scripts/lib/configure-features.js +77 -10
- package/scripts/lib/configure-repair.js +6 -5
- package/scripts/lib/context-formatter.js +13 -3
- package/scripts/lib/damage-control-utils.js +5 -1
- package/scripts/lib/lifecycle-detector.js +5 -3
- package/scripts/lib/process-cleanup.js +8 -4
- package/scripts/lib/scale-detector.js +47 -8
- package/scripts/lib/signal-detectors.js +117 -59
- package/scripts/lib/task-registry.js +5 -1
- package/scripts/lib/team-events.js +4 -4
- package/scripts/messaging-bridge.js +7 -1
- package/scripts/precompact-context.sh +3 -0
- package/scripts/ralph-loop.js +10 -8
- package/scripts/smart-detect.js +32 -11
- package/scripts/team-manager.js +1 -1
- package/scripts/tmux-task-name.sh +75 -0
- package/scripts/tmux-task-watcher.sh +177 -0
- package/src/core/commands/babysit.md +75 -42
- package/src/core/commands/blockers.md +7 -7
- package/src/core/commands/configure.md +49 -63
- package/src/core/commands/discovery/brief.md +363 -0
- package/src/core/commands/discovery/new.md +395 -0
- package/src/core/commands/ideate/new.md +5 -5
- package/src/core/commands/logic/audit.md +5 -5
- package/src/core/commands/review.md +7 -1
- package/src/core/commands/rpi.md +61 -26
- package/src/core/commands/sprint.md +7 -6
- package/src/core/templates/product-brief.md +136 -0
- package/tools/cli/installers/ide/claude-code.js +67 -2
- package/src/core/agents/configuration/archival.md +0 -350
- package/src/core/agents/configuration/attribution.md +0 -343
- package/src/core/agents/configuration/ci.md +0 -1103
- package/src/core/agents/configuration/damage-control.md +0 -375
- package/src/core/agents/configuration/git-config.md +0 -537
- package/src/core/agents/configuration/hooks.md +0 -623
- package/src/core/agents/configuration/precompact.md +0 -302
- package/src/core/agents/configuration/status-line.md +0 -557
- package/src/core/agents/configuration/verify.md +0 -618
- package/src/core/agents/configuration-damage-control.md +0 -259
- 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':
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
256
|
+
adr: signals => {
|
|
257
257
|
const { story } = signals;
|
|
258
258
|
if (!story || !story.id) return null;
|
|
259
|
-
if (
|
|
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
|
-
|
|
280
|
+
research: signals => {
|
|
272
281
|
const { story } = signals;
|
|
273
282
|
if (!story || !story.id) return null;
|
|
274
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
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':
|
|
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':
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
471
|
+
packages: signals => {
|
|
452
472
|
const { story } = signals;
|
|
453
473
|
if (!story || !story.id) return null;
|
|
454
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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':
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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':
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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',
|
|
667
|
-
'
|
|
697
|
+
'story-validate',
|
|
698
|
+
'blockers',
|
|
699
|
+
'choose',
|
|
700
|
+
'assign',
|
|
701
|
+
'board',
|
|
702
|
+
'sprint',
|
|
703
|
+
'batch',
|
|
704
|
+
'workflow',
|
|
705
|
+
'template',
|
|
706
|
+
'configure',
|
|
668
707
|
],
|
|
669
|
-
|
|
670
|
-
'impact',
|
|
671
|
-
'
|
|
708
|
+
planning: [
|
|
709
|
+
'impact',
|
|
710
|
+
'adr',
|
|
711
|
+
'research',
|
|
712
|
+
'baseline',
|
|
713
|
+
'council',
|
|
714
|
+
'multi-expert',
|
|
715
|
+
'validate-expertise',
|
|
672
716
|
],
|
|
673
|
-
|
|
674
|
-
'verify',
|
|
675
|
-
'
|
|
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',
|
|
679
|
-
'
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
'
|
|
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 {
|
|
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,
|
|
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 =
|
|
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 +=
|
|
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,
|
|
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 = {
|
|
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 {
|
package/scripts/ralph-loop.js
CHANGED
|
@@ -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
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
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
|
-
|
|
440
|
+
}, 'metadata read') || './screenshots'
|
|
441
|
+
);
|
|
440
442
|
}
|
|
441
443
|
|
|
442
444
|
// Run screenshot verification (Visual Mode)
|
package/scripts/smart-detect.js
CHANGED
|
@@ -54,7 +54,13 @@ try {
|
|
|
54
54
|
return { ok: false, error: e.message };
|
|
55
55
|
}
|
|
56
56
|
};
|
|
57
|
-
tryOptional = (fn, _label) => {
|
|
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:
|
|
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:
|
|
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(
|
|
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 && !!
|
|
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
|
-
|
|
347
|
-
|
|
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 =
|
|
361
|
-
|
|
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: {
|
package/scripts/team-manager.js
CHANGED
|
@@ -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 =
|
|
207
|
+
const updateState = state => {
|
|
208
208
|
state.active_team = {
|
|
209
209
|
template: templateName,
|
|
210
210
|
mode,
|