@ai-dev-methodologies/rlp-desk 0.9.0 → 0.9.1
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/docs/plans/validated-snacking-crayon.md +156 -359
- package/docs/superpowers/plans/2026-04-12-flywheel-redesign.md +704 -0
- package/docs/superpowers/specs/2026-04-12-flywheel-redesign.md +161 -0
- package/package.json +1 -1
- package/src/commands/rlp-desk.md +15 -1
- package/src/node/reporting/campaign-reporting.mjs +364 -0
- package/src/node/run.mjs +12 -0
- package/src/node/runner/campaign-main-loop.mjs +104 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
|
+
import os from 'node:os';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import { execFile } from 'node:child_process';
|
|
4
5
|
import { promisify } from 'node:util';
|
|
@@ -13,6 +14,7 @@ import {
|
|
|
13
14
|
import {
|
|
14
15
|
appendCampaignAnalytics,
|
|
15
16
|
generateCampaignReport,
|
|
17
|
+
generateSVReport,
|
|
16
18
|
prepareCampaignAnalytics,
|
|
17
19
|
} from '../reporting/campaign-reporting.mjs';
|
|
18
20
|
import {
|
|
@@ -56,9 +58,12 @@ function buildPaths(rootDir, slug) {
|
|
|
56
58
|
prdFile: path.join(deskRoot, 'plans', `prd-${slug}.md`),
|
|
57
59
|
testSpecFile: path.join(deskRoot, 'plans', `test-spec-${slug}.md`),
|
|
58
60
|
analyticsFile: path.join(campaignLogDir, 'campaign.jsonl'),
|
|
61
|
+
analyticsDir: path.join(os.homedir(), '.claude', 'ralph-desk', 'analytics', slug),
|
|
59
62
|
reportFile: path.join(campaignLogDir, 'campaign-report.md'),
|
|
60
63
|
statusFile: path.join(campaignLogDir, 'runtime', 'status.json'),
|
|
61
|
-
|
|
64
|
+
flywheelPromptFile: path.join(deskRoot, 'prompts', `${slug}.flywheel.prompt.md`),
|
|
65
|
+
flywheelSignalFile: path.join(deskRoot, 'memos', `${slug}-flywheel-signal.json`),
|
|
66
|
+
};
|
|
62
67
|
}
|
|
63
68
|
|
|
64
69
|
async function exists(targetPath) {
|
|
@@ -394,6 +399,25 @@ async function runFinalSequentialVerify({
|
|
|
394
399
|
};
|
|
395
400
|
}
|
|
396
401
|
|
|
402
|
+
function buildFlywheelTriggerCmd({ flywheelPromptFile, flywheelModel, rootDir }) {
|
|
403
|
+
return `cd ${JSON.stringify(rootDir)} && DISABLE_OMC=1 claude --model ${flywheelModel} --no-mcp -p "$(cat ${JSON.stringify(flywheelPromptFile)})"`;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
async function dispatchFlywheel({ paths, sendKeys, flywheelPaneId, flywheelModel, rootDir }) {
|
|
407
|
+
const triggerCmd = buildFlywheelTriggerCmd({
|
|
408
|
+
flywheelPromptFile: paths.flywheelPromptFile,
|
|
409
|
+
flywheelModel,
|
|
410
|
+
rootDir,
|
|
411
|
+
});
|
|
412
|
+
await sendKeys(flywheelPaneId, triggerCmd);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
export function shouldRunFlywheel(flywheelMode, state) {
|
|
416
|
+
if (flywheelMode === 'off') return false;
|
|
417
|
+
if (flywheelMode === 'on-fail' && (state.consecutive_failures ?? 0) > 0) return true;
|
|
418
|
+
return false;
|
|
419
|
+
}
|
|
420
|
+
|
|
397
421
|
export async function run(slug, options = {}) {
|
|
398
422
|
const rootDir = path.resolve(options.rootDir ?? process.cwd());
|
|
399
423
|
const paths = buildPaths(rootDir, slug);
|
|
@@ -433,6 +457,10 @@ export async function run(slug, options = {}) {
|
|
|
433
457
|
});
|
|
434
458
|
state.session_name = session.sessionName;
|
|
435
459
|
state.leader_pane_id = session.leaderPaneId;
|
|
460
|
+
state.flywheel_pane_id = await createPane({
|
|
461
|
+
targetPaneId: session.leaderPaneId,
|
|
462
|
+
layout: 'horizontal',
|
|
463
|
+
});
|
|
436
464
|
state.worker_pane_id = await createPane({
|
|
437
465
|
targetPaneId: session.leaderPaneId,
|
|
438
466
|
layout: 'horizontal',
|
|
@@ -462,6 +490,22 @@ export async function run(slug, options = {}) {
|
|
|
462
490
|
state.phase = 'complete';
|
|
463
491
|
await writeSentinel(paths.completeSentinel, 'complete', 'ALL');
|
|
464
492
|
await writeStatus(paths, state, options.onStatusChange, options.now);
|
|
493
|
+
let svSummary;
|
|
494
|
+
if (options.withSelfVerification) {
|
|
495
|
+
try {
|
|
496
|
+
const sv = await generateSVReport({
|
|
497
|
+
slug,
|
|
498
|
+
logsDir: path.dirname(paths.reportFile),
|
|
499
|
+
prdFile: paths.prdFile,
|
|
500
|
+
testSpecFile: paths.testSpecFile,
|
|
501
|
+
analyticsFile: paths.analyticsFile,
|
|
502
|
+
outputDir: paths.analyticsDir,
|
|
503
|
+
});
|
|
504
|
+
svSummary = sv.summary;
|
|
505
|
+
} catch (err) {
|
|
506
|
+
svSummary = `SV report generation failed: ${err.message}`;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
465
509
|
await generateCampaignReport({
|
|
466
510
|
slug,
|
|
467
511
|
reportFile: paths.reportFile,
|
|
@@ -469,6 +513,7 @@ export async function run(slug, options = {}) {
|
|
|
469
513
|
statusFile: paths.statusFile,
|
|
470
514
|
analyticsFile: paths.analyticsFile,
|
|
471
515
|
now: resolveNow(options.now),
|
|
516
|
+
svSummary,
|
|
472
517
|
});
|
|
473
518
|
return {
|
|
474
519
|
status: 'complete',
|
|
@@ -489,6 +534,30 @@ export async function run(slug, options = {}) {
|
|
|
489
534
|
};
|
|
490
535
|
}
|
|
491
536
|
|
|
537
|
+
// Flywheel direction review (runs BEFORE Worker)
|
|
538
|
+
if (shouldRunFlywheel(options.flywheel ?? 'off', state)) {
|
|
539
|
+
state.phase = 'flywheel';
|
|
540
|
+
await writeStatus(paths, state, options.onStatusChange, options.now);
|
|
541
|
+
|
|
542
|
+
await dispatchFlywheel({
|
|
543
|
+
paths,
|
|
544
|
+
sendKeys,
|
|
545
|
+
flywheelPaneId: state.flywheel_pane_id ?? state.verifier_pane_id,
|
|
546
|
+
flywheelModel: options.flywheelModel ?? 'opus',
|
|
547
|
+
rootDir,
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
const flywheelSignal = await pollForSignal(paths.flywheelSignalFile, {
|
|
551
|
+
mode: 'claude',
|
|
552
|
+
paneId: state.flywheel_pane_id ?? state.verifier_pane_id,
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
state.last_flywheel_decision = flywheelSignal.decision;
|
|
556
|
+
// Campaign memory already updated by flywheel agent
|
|
557
|
+
// Clean signal file for next iteration
|
|
558
|
+
await fs.unlink(paths.flywheelSignalFile).catch(() => {});
|
|
559
|
+
}
|
|
560
|
+
|
|
492
561
|
state.phase = 'worker';
|
|
493
562
|
await writeStatus(paths, state, options.onStatusChange, options.now);
|
|
494
563
|
await dispatchWorker({
|
|
@@ -565,6 +634,22 @@ export async function run(slug, options = {}) {
|
|
|
565
634
|
await writeSentinel(paths.blockedSentinel, 'blocked', usId);
|
|
566
635
|
await appendIterationAnalytics(paths, state, usId, 'blocked', options);
|
|
567
636
|
await writeStatus(paths, state, options.onStatusChange, options.now);
|
|
637
|
+
let svSummary;
|
|
638
|
+
if (options.withSelfVerification) {
|
|
639
|
+
try {
|
|
640
|
+
const sv = await generateSVReport({
|
|
641
|
+
slug,
|
|
642
|
+
logsDir: path.dirname(paths.reportFile),
|
|
643
|
+
prdFile: paths.prdFile,
|
|
644
|
+
testSpecFile: paths.testSpecFile,
|
|
645
|
+
analyticsFile: paths.analyticsFile,
|
|
646
|
+
outputDir: paths.analyticsDir,
|
|
647
|
+
});
|
|
648
|
+
svSummary = sv.summary;
|
|
649
|
+
} catch (err) {
|
|
650
|
+
svSummary = `SV report generation failed: ${err.message}`;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
568
653
|
await generateCampaignReport({
|
|
569
654
|
slug,
|
|
570
655
|
reportFile: paths.reportFile,
|
|
@@ -572,6 +657,7 @@ export async function run(slug, options = {}) {
|
|
|
572
657
|
statusFile: paths.statusFile,
|
|
573
658
|
analyticsFile: paths.analyticsFile,
|
|
574
659
|
now: resolveNow(options.now),
|
|
660
|
+
svSummary,
|
|
575
661
|
});
|
|
576
662
|
return {
|
|
577
663
|
status: 'blocked',
|
|
@@ -587,6 +673,22 @@ export async function run(slug, options = {}) {
|
|
|
587
673
|
state.phase = 'blocked';
|
|
588
674
|
await writeSentinel(paths.blockedSentinel, 'blocked', usId);
|
|
589
675
|
await writeStatus(paths, state, options.onStatusChange, options.now);
|
|
676
|
+
let svSummary;
|
|
677
|
+
if (options.withSelfVerification) {
|
|
678
|
+
try {
|
|
679
|
+
const sv = await generateSVReport({
|
|
680
|
+
slug,
|
|
681
|
+
logsDir: path.dirname(paths.reportFile),
|
|
682
|
+
prdFile: paths.prdFile,
|
|
683
|
+
testSpecFile: paths.testSpecFile,
|
|
684
|
+
analyticsFile: paths.analyticsFile,
|
|
685
|
+
outputDir: paths.analyticsDir,
|
|
686
|
+
});
|
|
687
|
+
svSummary = sv.summary;
|
|
688
|
+
} catch (err) {
|
|
689
|
+
svSummary = `SV report generation failed: ${err.message}`;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
590
692
|
await generateCampaignReport({
|
|
591
693
|
slug,
|
|
592
694
|
reportFile: paths.reportFile,
|
|
@@ -594,6 +696,7 @@ export async function run(slug, options = {}) {
|
|
|
594
696
|
statusFile: paths.statusFile,
|
|
595
697
|
analyticsFile: paths.analyticsFile,
|
|
596
698
|
now: resolveNow(options.now),
|
|
699
|
+
svSummary,
|
|
597
700
|
});
|
|
598
701
|
return {
|
|
599
702
|
status: 'blocked',
|