karajan-code 1.28.0 → 1.29.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.
- package/package.json +1 -1
- package/src/mcp/direct-role-runner.js +100 -0
- package/src/mcp/server-handlers.js +55 -205
- package/src/orchestrator/pipeline-context.js +46 -0
- package/src/orchestrator/pre-loop-stages.js +2 -0
- package/src/orchestrator.js +85 -156
- package/src/planning-game/pipeline-adapter.js +147 -0
- package/src/sonar/scanner.js +4 -0
package/package.json
CHANGED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared runner for direct role execution (discover, triage, researcher, architect, audit).
|
|
3
|
+
* Extracts the repeated boilerplate from server-handlers.js handleXxxDirect functions.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { createStallDetector } from "../utils/stall-detector.js";
|
|
7
|
+
import { resolveRole } from "../config.js";
|
|
8
|
+
import { createLogger } from "../utils/logger.js";
|
|
9
|
+
import { assertAgentsAvailable } from "../agents/availability.js";
|
|
10
|
+
import { createRunLog } from "../utils/run-log.js";
|
|
11
|
+
import { sendTrackerLog } from "./progress.js";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Run a role-based tool directly (not through the orchestrator pipeline).
|
|
15
|
+
*
|
|
16
|
+
* @param {object} opts
|
|
17
|
+
* @param {string} opts.roleName - Config role key (e.g. "discover", "triage")
|
|
18
|
+
* @param {string} [opts.stage] - Stage label for events/logs (defaults to roleName)
|
|
19
|
+
* @param {Function} opts.importRole - Async fn returning { RoleClass } (dynamic import wrapper)
|
|
20
|
+
* @param {object} opts.initContext - Object passed to role.init()
|
|
21
|
+
* @param {object} opts.runInput - Object passed to role.run() (onOutput is injected automatically)
|
|
22
|
+
* @param {string} [opts.logStartMsg] - Custom "[kj_xxx] started" suffix (optional)
|
|
23
|
+
* @param {object} opts.args - Raw tool arguments (for buildConfig overrides)
|
|
24
|
+
* @param {string} [opts.commandName] - Config command name (defaults to roleName)
|
|
25
|
+
* @param {object} opts.server - MCP server instance
|
|
26
|
+
* @param {object} opts.extra - MCP extra context (for progress notifier)
|
|
27
|
+
* @param {Function} opts.resolveProjectDir - Async fn(server, explicitDir) => projectDir
|
|
28
|
+
* @param {Function} opts.buildConfig - Async fn(args, commandName) => config
|
|
29
|
+
* @param {Function} opts.buildDirectEmitter - fn(server, runLog, extra) => emitter
|
|
30
|
+
*/
|
|
31
|
+
export async function runDirectRole({
|
|
32
|
+
roleName,
|
|
33
|
+
stage,
|
|
34
|
+
importRole,
|
|
35
|
+
initContext,
|
|
36
|
+
runInput,
|
|
37
|
+
logStartMsg,
|
|
38
|
+
args,
|
|
39
|
+
commandName,
|
|
40
|
+
server,
|
|
41
|
+
extra,
|
|
42
|
+
resolveProjectDir,
|
|
43
|
+
buildConfig,
|
|
44
|
+
buildDirectEmitter
|
|
45
|
+
}) {
|
|
46
|
+
const effectiveStage = stage || roleName;
|
|
47
|
+
const effectiveCommand = commandName || roleName;
|
|
48
|
+
|
|
49
|
+
const config = await buildConfig(args, effectiveCommand);
|
|
50
|
+
const logger = createLogger(config.output.log_level, "mcp");
|
|
51
|
+
|
|
52
|
+
const role = resolveRole(config, roleName);
|
|
53
|
+
await assertAgentsAvailable([role.provider]);
|
|
54
|
+
|
|
55
|
+
const projectDir = await resolveProjectDir(server, args.projectDir);
|
|
56
|
+
const runLog = createRunLog(projectDir);
|
|
57
|
+
const startMsg = logStartMsg || `[kj_${effectiveStage}] started`;
|
|
58
|
+
runLog.logText(startMsg);
|
|
59
|
+
|
|
60
|
+
const emitter = buildDirectEmitter(server, runLog, extra);
|
|
61
|
+
const eventBase = { sessionId: null, iteration: 0, startedAt: Date.now() };
|
|
62
|
+
const onOutput = ({ stream, line }) => {
|
|
63
|
+
emitter.emit("progress", {
|
|
64
|
+
type: "agent:output",
|
|
65
|
+
stage: effectiveStage,
|
|
66
|
+
message: line,
|
|
67
|
+
detail: { stream, agent: role.provider }
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
const stallDetector = createStallDetector({
|
|
71
|
+
onOutput, emitter, eventBase, stage: effectiveStage, provider: role.provider
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const { RoleClass } = await importRole();
|
|
75
|
+
const roleInstance = new RoleClass({ config, logger, emitter });
|
|
76
|
+
await roleInstance.init(initContext);
|
|
77
|
+
|
|
78
|
+
sendTrackerLog(server, effectiveStage, "running", role.provider);
|
|
79
|
+
runLog.logText(`[${effectiveStage}] agent launched, waiting for response...`);
|
|
80
|
+
|
|
81
|
+
let result;
|
|
82
|
+
try {
|
|
83
|
+
result = await roleInstance.run({ ...runInput, onOutput: stallDetector.onOutput });
|
|
84
|
+
} finally {
|
|
85
|
+
stallDetector.stop();
|
|
86
|
+
const stats = stallDetector.stats();
|
|
87
|
+
runLog.logText(
|
|
88
|
+
`[${effectiveStage}] finished — lines=${stats.lineCount}, bytes=${stats.bytesReceived}, elapsed=${Math.round(stats.elapsedMs / 1000)}s`
|
|
89
|
+
);
|
|
90
|
+
runLog.close();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (!result.ok) {
|
|
94
|
+
sendTrackerLog(server, effectiveStage, "failed");
|
|
95
|
+
throw new Error(result.result?.error || result.summary || `${effectiveStage} failed`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
sendTrackerLog(server, effectiveStage, "done");
|
|
99
|
+
return { ok: true, ...result.result, summary: result.summary };
|
|
100
|
+
}
|
|
@@ -9,6 +9,7 @@ import { runKjCommand } from "./run-kj.js";
|
|
|
9
9
|
import { normalizePlanArgs } from "./tool-arg-normalizers.js";
|
|
10
10
|
import { buildProgressHandler, buildProgressNotifier, buildPipelineTracker, sendTrackerLog } from "./progress.js";
|
|
11
11
|
import { createStallDetector } from "../utils/stall-detector.js";
|
|
12
|
+
import { runDirectRole } from "./direct-role-runner.js";
|
|
12
13
|
import { runFlow, resumeFlow } from "../orchestrator.js";
|
|
13
14
|
import { loadConfig, applyRunOverrides, validateConfig, resolveRole } from "../config.js";
|
|
14
15
|
import { createLogger } from "../utils/logger.js";
|
|
@@ -523,28 +524,6 @@ export async function handleReviewDirect(a, server, extra) {
|
|
|
523
524
|
}
|
|
524
525
|
|
|
525
526
|
export async function handleDiscoverDirect(a, server, extra) {
|
|
526
|
-
const config = await buildConfig(a, "discover");
|
|
527
|
-
const logger = createLogger(config.output.log_level, "mcp");
|
|
528
|
-
|
|
529
|
-
const discoverRole = resolveRole(config, "discover");
|
|
530
|
-
await assertAgentsAvailable([discoverRole.provider]);
|
|
531
|
-
|
|
532
|
-
const projectDir = await resolveProjectDir(server, a.projectDir);
|
|
533
|
-
const runLog = createRunLog(projectDir);
|
|
534
|
-
runLog.logText(`[kj_discover] started — mode=${a.mode || "gaps"}`);
|
|
535
|
-
const emitter = buildDirectEmitter(server, runLog, extra);
|
|
536
|
-
const eventBase = { sessionId: null, iteration: 0, startedAt: Date.now() };
|
|
537
|
-
const onOutput = ({ stream, line }) => {
|
|
538
|
-
emitter.emit("progress", { type: "agent:output", stage: "discover", message: line, detail: { stream, agent: discoverRole.provider } });
|
|
539
|
-
};
|
|
540
|
-
const stallDetector = createStallDetector({
|
|
541
|
-
onOutput, emitter, eventBase, stage: "discover", provider: discoverRole.provider
|
|
542
|
-
});
|
|
543
|
-
|
|
544
|
-
const { DiscoverRole } = await import("../roles/discover-role.js");
|
|
545
|
-
const discover = new DiscoverRole({ config, logger, emitter });
|
|
546
|
-
await discover.init({ task: a.task });
|
|
547
|
-
|
|
548
527
|
// Build context from pgTask if provided
|
|
549
528
|
let context = a.context || null;
|
|
550
529
|
if (a.pgTask && a.pgProject) {
|
|
@@ -554,205 +533,76 @@ export async function handleDiscoverDirect(a, server, extra) {
|
|
|
554
533
|
} catch { /* PG not available — proceed without */ }
|
|
555
534
|
}
|
|
556
535
|
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
if (!result.ok) {
|
|
570
|
-
sendTrackerLog(server, "discover", "failed");
|
|
571
|
-
throw new Error(result.result?.error || result.summary || "Discovery failed");
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
sendTrackerLog(server, "discover", "done");
|
|
575
|
-
return { ok: true, ...result.result, summary: result.summary };
|
|
536
|
+
return runDirectRole({
|
|
537
|
+
roleName: "discover",
|
|
538
|
+
importRole: async () => {
|
|
539
|
+
const { DiscoverRole } = await import("../roles/discover-role.js");
|
|
540
|
+
return { RoleClass: DiscoverRole };
|
|
541
|
+
},
|
|
542
|
+
initContext: { task: a.task },
|
|
543
|
+
runInput: { task: a.task, mode: a.mode || "gaps", context },
|
|
544
|
+
logStartMsg: `[kj_discover] started — mode=${a.mode || "gaps"}`,
|
|
545
|
+
args: a, server, extra,
|
|
546
|
+
resolveProjectDir, buildConfig, buildDirectEmitter
|
|
547
|
+
});
|
|
576
548
|
}
|
|
577
549
|
|
|
578
550
|
export async function handleTriageDirect(a, server, extra) {
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
const eventBase = { sessionId: null, iteration: 0, startedAt: Date.now() };
|
|
590
|
-
const onOutput = ({ stream, line }) => {
|
|
591
|
-
emitter.emit("progress", { type: "agent:output", stage: "triage", message: line, detail: { stream, agent: triageRole.provider } });
|
|
592
|
-
};
|
|
593
|
-
const stallDetector = createStallDetector({
|
|
594
|
-
onOutput, emitter, eventBase, stage: "triage", provider: triageRole.provider
|
|
551
|
+
return runDirectRole({
|
|
552
|
+
roleName: "triage",
|
|
553
|
+
importRole: async () => {
|
|
554
|
+
const { TriageRole } = await import("../roles/triage-role.js");
|
|
555
|
+
return { RoleClass: TriageRole };
|
|
556
|
+
},
|
|
557
|
+
initContext: { task: a.task },
|
|
558
|
+
runInput: { task: a.task },
|
|
559
|
+
args: a, server, extra,
|
|
560
|
+
resolveProjectDir, buildConfig, buildDirectEmitter
|
|
595
561
|
});
|
|
596
|
-
|
|
597
|
-
const { TriageRole } = await import("../roles/triage-role.js");
|
|
598
|
-
const triage = new TriageRole({ config, logger, emitter });
|
|
599
|
-
await triage.init({ task: a.task });
|
|
600
|
-
|
|
601
|
-
sendTrackerLog(server, "triage", "running", triageRole.provider);
|
|
602
|
-
runLog.logText(`[triage] agent launched, waiting for response...`);
|
|
603
|
-
let result;
|
|
604
|
-
try {
|
|
605
|
-
result = await triage.run({ task: a.task, onOutput: stallDetector.onOutput });
|
|
606
|
-
} finally {
|
|
607
|
-
stallDetector.stop();
|
|
608
|
-
const stats = stallDetector.stats();
|
|
609
|
-
runLog.logText(`[triage] finished — lines=${stats.lineCount}, bytes=${stats.bytesReceived}, elapsed=${Math.round(stats.elapsedMs / 1000)}s`);
|
|
610
|
-
runLog.close();
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
if (!result.ok) {
|
|
614
|
-
sendTrackerLog(server, "triage", "failed");
|
|
615
|
-
throw new Error(result.result?.error || result.summary || "Triage failed");
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
sendTrackerLog(server, "triage", "done");
|
|
619
|
-
return { ok: true, ...result.result, summary: result.summary };
|
|
620
562
|
}
|
|
621
563
|
|
|
622
564
|
export async function handleResearcherDirect(a, server, extra) {
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
const eventBase = { sessionId: null, iteration: 0, startedAt: Date.now() };
|
|
634
|
-
const onOutput = ({ stream, line }) => {
|
|
635
|
-
emitter.emit("progress", { type: "agent:output", stage: "researcher", message: line, detail: { stream, agent: researcherRole.provider } });
|
|
636
|
-
};
|
|
637
|
-
const stallDetector = createStallDetector({
|
|
638
|
-
onOutput, emitter, eventBase, stage: "researcher", provider: researcherRole.provider
|
|
565
|
+
return runDirectRole({
|
|
566
|
+
roleName: "researcher",
|
|
567
|
+
importRole: async () => {
|
|
568
|
+
const { ResearcherRole } = await import("../roles/researcher-role.js");
|
|
569
|
+
return { RoleClass: ResearcherRole };
|
|
570
|
+
},
|
|
571
|
+
initContext: { task: a.task },
|
|
572
|
+
runInput: { task: a.task },
|
|
573
|
+
args: a, server, extra,
|
|
574
|
+
resolveProjectDir, buildConfig, buildDirectEmitter
|
|
639
575
|
});
|
|
640
|
-
|
|
641
|
-
const { ResearcherRole } = await import("../roles/researcher-role.js");
|
|
642
|
-
const researcher = new ResearcherRole({ config, logger, emitter });
|
|
643
|
-
await researcher.init({ task: a.task });
|
|
644
|
-
|
|
645
|
-
sendTrackerLog(server, "researcher", "running", researcherRole.provider);
|
|
646
|
-
runLog.logText(`[researcher] agent launched, waiting for response...`);
|
|
647
|
-
let result;
|
|
648
|
-
try {
|
|
649
|
-
result = await researcher.run({ task: a.task, onOutput: stallDetector.onOutput });
|
|
650
|
-
} finally {
|
|
651
|
-
stallDetector.stop();
|
|
652
|
-
const stats = stallDetector.stats();
|
|
653
|
-
runLog.logText(`[researcher] finished — lines=${stats.lineCount}, bytes=${stats.bytesReceived}, elapsed=${Math.round(stats.elapsedMs / 1000)}s`);
|
|
654
|
-
runLog.close();
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
if (!result.ok) {
|
|
658
|
-
sendTrackerLog(server, "researcher", "failed");
|
|
659
|
-
throw new Error(result.result?.error || result.summary || "Researcher failed");
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
sendTrackerLog(server, "researcher", "done");
|
|
663
|
-
return { ok: true, ...result.result, summary: result.summary };
|
|
664
576
|
}
|
|
665
577
|
|
|
666
578
|
export async function handleAuditDirect(a, server, extra) {
|
|
667
|
-
const
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
emitter.emit("progress", { type: "agent:output", stage: "audit", message: line, detail: { stream, agent: auditRole.provider } });
|
|
680
|
-
};
|
|
681
|
-
const stallDetector = createStallDetector({
|
|
682
|
-
onOutput, emitter, eventBase, stage: "audit", provider: auditRole.provider
|
|
579
|
+
const task = a.task || "Analyze the full codebase";
|
|
580
|
+
return runDirectRole({
|
|
581
|
+
roleName: "audit",
|
|
582
|
+
importRole: async () => {
|
|
583
|
+
const { AuditRole } = await import("../roles/audit-role.js");
|
|
584
|
+
return { RoleClass: AuditRole };
|
|
585
|
+
},
|
|
586
|
+
initContext: { task },
|
|
587
|
+
runInput: { task, dimensions: a.dimensions || null },
|
|
588
|
+
logStartMsg: `[kj_audit] started — dimensions=${a.dimensions || "all"}`,
|
|
589
|
+
args: a, server, extra,
|
|
590
|
+
resolveProjectDir, buildConfig, buildDirectEmitter
|
|
683
591
|
});
|
|
684
|
-
|
|
685
|
-
const { AuditRole } = await import("../roles/audit-role.js");
|
|
686
|
-
const audit = new AuditRole({ config, logger, emitter });
|
|
687
|
-
await audit.init({ task: a.task || "Analyze the full codebase" });
|
|
688
|
-
|
|
689
|
-
sendTrackerLog(server, "audit", "running", auditRole.provider);
|
|
690
|
-
runLog.logText(`[audit] agent launched, waiting for response...`);
|
|
691
|
-
let result;
|
|
692
|
-
try {
|
|
693
|
-
result = await audit.run({
|
|
694
|
-
task: a.task || "Analyze the full codebase",
|
|
695
|
-
dimensions: a.dimensions || null,
|
|
696
|
-
onOutput: stallDetector.onOutput
|
|
697
|
-
});
|
|
698
|
-
} finally {
|
|
699
|
-
stallDetector.stop();
|
|
700
|
-
const stats = stallDetector.stats();
|
|
701
|
-
runLog.logText(`[audit] finished — lines=${stats.lineCount}, bytes=${stats.bytesReceived}, elapsed=${Math.round(stats.elapsedMs / 1000)}s`);
|
|
702
|
-
runLog.close();
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
if (!result.ok) {
|
|
706
|
-
sendTrackerLog(server, "audit", "failed");
|
|
707
|
-
throw new Error(result.result?.error || result.summary || "Audit failed");
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
sendTrackerLog(server, "audit", "done");
|
|
711
|
-
return { ok: true, ...result.result, summary: result.summary };
|
|
712
592
|
}
|
|
713
593
|
|
|
714
594
|
export async function handleArchitectDirect(a, server, extra) {
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
const eventBase = { sessionId: null, iteration: 0, startedAt: Date.now() };
|
|
726
|
-
const onOutput = ({ stream, line }) => {
|
|
727
|
-
emitter.emit("progress", { type: "agent:output", stage: "architect", message: line, detail: { stream, agent: architectRole.provider } });
|
|
728
|
-
};
|
|
729
|
-
const stallDetector = createStallDetector({
|
|
730
|
-
onOutput, emitter, eventBase, stage: "architect", provider: architectRole.provider
|
|
595
|
+
return runDirectRole({
|
|
596
|
+
roleName: "architect",
|
|
597
|
+
importRole: async () => {
|
|
598
|
+
const { ArchitectRole } = await import("../roles/architect-role.js");
|
|
599
|
+
return { RoleClass: ArchitectRole };
|
|
600
|
+
},
|
|
601
|
+
initContext: { task: a.task },
|
|
602
|
+
runInput: { task: a.task, researchContext: a.context || null },
|
|
603
|
+
args: a, server, extra,
|
|
604
|
+
resolveProjectDir, buildConfig, buildDirectEmitter
|
|
731
605
|
});
|
|
732
|
-
|
|
733
|
-
const { ArchitectRole } = await import("../roles/architect-role.js");
|
|
734
|
-
const architect = new ArchitectRole({ config, logger, emitter });
|
|
735
|
-
await architect.init({ task: a.task });
|
|
736
|
-
|
|
737
|
-
sendTrackerLog(server, "architect", "running", architectRole.provider);
|
|
738
|
-
runLog.logText(`[architect] agent launched, waiting for response...`);
|
|
739
|
-
let result;
|
|
740
|
-
try {
|
|
741
|
-
result = await architect.run({ task: a.task, researchContext: a.context || null, onOutput: stallDetector.onOutput });
|
|
742
|
-
} finally {
|
|
743
|
-
stallDetector.stop();
|
|
744
|
-
const stats = stallDetector.stats();
|
|
745
|
-
runLog.logText(`[architect] finished — lines=${stats.lineCount}, bytes=${stats.bytesReceived}, elapsed=${Math.round(stats.elapsedMs / 1000)}s`);
|
|
746
|
-
runLog.close();
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
if (!result.ok) {
|
|
750
|
-
sendTrackerLog(server, "architect", "failed");
|
|
751
|
-
throw new Error(result.result?.error || result.summary || "Architect failed");
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
sendTrackerLog(server, "architect", "done");
|
|
755
|
-
return { ok: true, ...result.result, summary: result.summary };
|
|
756
606
|
}
|
|
757
607
|
|
|
758
608
|
/* ── Preflight helpers ─────────────────────────────────────────────── */
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared context object for pipeline execution.
|
|
3
|
+
* Replaces destructured parameter objects across orchestrator functions.
|
|
4
|
+
*/
|
|
5
|
+
export class PipelineContext {
|
|
6
|
+
constructor({ config, session, logger, emitter, task, flags = {} }) {
|
|
7
|
+
this.config = config;
|
|
8
|
+
this.session = session;
|
|
9
|
+
this.logger = logger;
|
|
10
|
+
this.emitter = emitter;
|
|
11
|
+
this.task = task;
|
|
12
|
+
this.flags = flags;
|
|
13
|
+
|
|
14
|
+
// Pipeline state (set during execution)
|
|
15
|
+
this.eventBase = {};
|
|
16
|
+
this.pipelineFlags = {};
|
|
17
|
+
this.trackBudget = null;
|
|
18
|
+
this.budgetTracker = null;
|
|
19
|
+
this.budgetLimit = null;
|
|
20
|
+
this.budgetSummary = null;
|
|
21
|
+
this.askQuestion = null;
|
|
22
|
+
this.reviewRules = null;
|
|
23
|
+
this.repeatDetector = null;
|
|
24
|
+
this.sonarState = { issuesInitial: null, issuesFinal: null };
|
|
25
|
+
this.stageResults = {};
|
|
26
|
+
this.gitCtx = {};
|
|
27
|
+
this.iteration = 0;
|
|
28
|
+
|
|
29
|
+
// Roles (initialized during setup)
|
|
30
|
+
this.coderRole = null;
|
|
31
|
+
this.reviewerRole = null;
|
|
32
|
+
this.refactorerRole = null;
|
|
33
|
+
this.coderRoleInstance = null;
|
|
34
|
+
|
|
35
|
+
// Planning Game context
|
|
36
|
+
this.pgTaskId = null;
|
|
37
|
+
this.pgProject = null;
|
|
38
|
+
this.pgCard = null;
|
|
39
|
+
|
|
40
|
+
// Planned task (may differ from original task after planner)
|
|
41
|
+
this.plannedTask = null;
|
|
42
|
+
|
|
43
|
+
// Timing
|
|
44
|
+
this.startedAt = null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -333,6 +333,8 @@ export async function runArchitectStage({ config, logger, emitter, eventBase, se
|
|
|
333
333
|
|
|
334
334
|
const architectContext = architectOutput.ok ? architectOutput.result : null;
|
|
335
335
|
|
|
336
|
+
// TODO: Move ADR creation to planning-game/pipeline-adapter.js (PG coupling still here because
|
|
337
|
+
// stageResult.adrs is consumed synchronously within runArchitectStage's return value).
|
|
336
338
|
// Generate ADRs from architect tradeoffs when PG is linked
|
|
337
339
|
const tradeoffs = architectOutput.result?.architecture?.tradeoffs;
|
|
338
340
|
if (architectOutput.ok
|
package/src/orchestrator.js
CHANGED
|
@@ -29,6 +29,7 @@ import { classifyIntent } from "./guards/intent-guard.js";
|
|
|
29
29
|
import { resolveReviewProfile } from "./review/profiles.js";
|
|
30
30
|
import { CoderRole } from "./roles/coder-role.js";
|
|
31
31
|
import { invokeSolomon } from "./orchestrator/solomon-escalation.js";
|
|
32
|
+
import { PipelineContext } from "./orchestrator/pipeline-context.js";
|
|
32
33
|
import { runTriageStage, runResearcherStage, runArchitectStage, runPlannerStage, runDiscoverStage } from "./orchestrator/pre-loop-stages.js";
|
|
33
34
|
import { runCoderStage, runRefactorerStage, runTddCheckStage, runSonarStage, runSonarCloudStage, runReviewerStage } from "./orchestrator/iteration-stages.js";
|
|
34
35
|
import { runTesterStage, runSecurityStage, runImpeccableStage } from "./orchestrator/post-loop-stages.js";
|
|
@@ -178,33 +179,7 @@ async function initializeSession({ task, config, flags, pgTaskId, pgProject }) {
|
|
|
178
179
|
return createSession(sessionInit);
|
|
179
180
|
}
|
|
180
181
|
|
|
181
|
-
|
|
182
|
-
if (!pgTaskId || !pgProject || config.planning_game?.enabled === false) {
|
|
183
|
-
return null;
|
|
184
|
-
}
|
|
185
|
-
try {
|
|
186
|
-
const { fetchCard, updateCard } = await import("./planning-game/client.js");
|
|
187
|
-
const pgCard = await fetchCard({ projectId: pgProject, cardId: pgTaskId });
|
|
188
|
-
if (pgCard && pgCard.status !== "In Progress") {
|
|
189
|
-
await updateCard({
|
|
190
|
-
projectId: pgProject,
|
|
191
|
-
cardId: pgTaskId,
|
|
192
|
-
firebaseId: pgCard.firebaseId,
|
|
193
|
-
updates: {
|
|
194
|
-
status: "In Progress",
|
|
195
|
-
startDate: new Date().toISOString(),
|
|
196
|
-
developer: "dev_016",
|
|
197
|
-
codeveloper: config.planning_game?.codeveloper || null
|
|
198
|
-
}
|
|
199
|
-
});
|
|
200
|
-
logger.info(`Planning Game: ${pgTaskId} → In Progress`);
|
|
201
|
-
}
|
|
202
|
-
return pgCard;
|
|
203
|
-
} catch (err) {
|
|
204
|
-
logger.warn(`Planning Game: could not update ${pgTaskId}: ${err.message}`);
|
|
205
|
-
return null;
|
|
206
|
-
}
|
|
207
|
-
}
|
|
182
|
+
// PG card "In Progress" logic moved to src/planning-game/pipeline-adapter.js → initPgAdapter()
|
|
208
183
|
|
|
209
184
|
function applyTriageOverrides(pipelineFlags, roleOverrides) {
|
|
210
185
|
const keys = ["plannerEnabled", "researcherEnabled", "architectEnabled", "refactorerEnabled", "reviewerEnabled", "testerEnabled", "securityEnabled", "impeccableEnabled"];
|
|
@@ -238,57 +213,7 @@ function applyAutoSimplify({ pipelineFlags, triageLevel, config, flags, logger,
|
|
|
238
213
|
return true;
|
|
239
214
|
}
|
|
240
215
|
|
|
241
|
-
|
|
242
|
-
const shouldDecompose = triageResult.stageResult?.shouldDecompose
|
|
243
|
-
&& triageResult.stageResult.subtasks?.length > 1
|
|
244
|
-
&& pgTaskId
|
|
245
|
-
&& pgProject
|
|
246
|
-
&& config.planning_game?.enabled !== false
|
|
247
|
-
&& askQuestion;
|
|
248
|
-
|
|
249
|
-
if (!shouldDecompose) return;
|
|
250
|
-
|
|
251
|
-
try {
|
|
252
|
-
const { buildDecompositionQuestion, createDecompositionSubtasks } = await import("./planning-game/decomposition.js");
|
|
253
|
-
const { createCard, relateCards, fetchCard } = await import("./planning-game/client.js");
|
|
254
|
-
|
|
255
|
-
const question = buildDecompositionQuestion(triageResult.stageResult.subtasks, pgTaskId);
|
|
256
|
-
const answer = await askQuestion(question);
|
|
257
|
-
|
|
258
|
-
if (answer && (answer.trim().toLowerCase() === "yes" || answer.trim().toLowerCase() === "sí" || answer.trim().toLowerCase() === "si")) {
|
|
259
|
-
const parentCard = await fetchCard({ projectId: pgProject, cardId: pgTaskId }).catch(() => null);
|
|
260
|
-
const createdSubtasks = await createDecompositionSubtasks({
|
|
261
|
-
client: { createCard, relateCards },
|
|
262
|
-
projectId: pgProject,
|
|
263
|
-
parentCardId: pgTaskId,
|
|
264
|
-
parentFirebaseId: parentCard?.firebaseId || null,
|
|
265
|
-
subtasks: triageResult.stageResult.subtasks,
|
|
266
|
-
epic: parentCard?.epic || null,
|
|
267
|
-
sprint: parentCard?.sprint || null,
|
|
268
|
-
codeveloper: config.planning_game?.codeveloper || null
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
stageResults.triage.pgSubtasks = createdSubtasks;
|
|
272
|
-
logger.info(`Planning Game: created ${createdSubtasks.length} subtasks from decomposition`);
|
|
273
|
-
|
|
274
|
-
emitProgress(
|
|
275
|
-
emitter,
|
|
276
|
-
makeEvent("pg:decompose", { ...eventBase, stage: "triage" }, {
|
|
277
|
-
message: `Created ${createdSubtasks.length} subtasks in Planning Game`,
|
|
278
|
-
detail: { subtasks: createdSubtasks.map((s) => ({ cardId: s.cardId, title: s.title })) }
|
|
279
|
-
})
|
|
280
|
-
);
|
|
281
|
-
|
|
282
|
-
await addCheckpoint(session, {
|
|
283
|
-
stage: "pg-decompose",
|
|
284
|
-
subtasksCreated: createdSubtasks.length,
|
|
285
|
-
cardIds: createdSubtasks.map((s) => s.cardId)
|
|
286
|
-
});
|
|
287
|
-
}
|
|
288
|
-
} catch (err) {
|
|
289
|
-
logger.warn(`Planning Game decomposition failed: ${err.message}`);
|
|
290
|
-
}
|
|
291
|
-
}
|
|
216
|
+
// PG decomposition logic moved to src/planning-game/pipeline-adapter.js → handlePgDecomposition()
|
|
292
217
|
|
|
293
218
|
function applyFlagOverrides(pipelineFlags, flags) {
|
|
294
219
|
if (flags.enablePlanner !== undefined) pipelineFlags.plannerEnabled = Boolean(flags.enablePlanner);
|
|
@@ -713,6 +638,7 @@ async function finalizeApprovedSession({ config, gitCtx, task, logger, session,
|
|
|
713
638
|
session.budget = budgetSummary();
|
|
714
639
|
await markSessionStatus(session, "approved");
|
|
715
640
|
|
|
641
|
+
const { markPgCardToValidate } = await import("./planning-game/pipeline-adapter.js");
|
|
716
642
|
await markPgCardToValidate({ pgCard, pgProject, config, session, gitResult, logger });
|
|
717
643
|
|
|
718
644
|
const deferredIssues = session.deferred_issues || [];
|
|
@@ -728,29 +654,7 @@ async function finalizeApprovedSession({ config, gitCtx, task, logger, session,
|
|
|
728
654
|
return { approved: true, sessionId: session.id, review, git: gitResult, deferredIssues };
|
|
729
655
|
}
|
|
730
656
|
|
|
731
|
-
|
|
732
|
-
if (!pgCard || !pgProject) return;
|
|
733
|
-
|
|
734
|
-
try {
|
|
735
|
-
const { updateCard } = await import("./planning-game/client.js");
|
|
736
|
-
const { buildCompletionUpdates } = await import("./planning-game/adapter.js");
|
|
737
|
-
const pgUpdates = buildCompletionUpdates({
|
|
738
|
-
approved: true,
|
|
739
|
-
commits: gitResult?.commits || [],
|
|
740
|
-
startDate: session.pg_card?.startDate || session.created_at,
|
|
741
|
-
codeveloper: config.planning_game?.codeveloper || null
|
|
742
|
-
});
|
|
743
|
-
await updateCard({
|
|
744
|
-
projectId: pgProject,
|
|
745
|
-
cardId: session.pg_task_id,
|
|
746
|
-
firebaseId: pgCard.firebaseId,
|
|
747
|
-
updates: pgUpdates
|
|
748
|
-
});
|
|
749
|
-
logger.info(`Planning Game: ${session.pg_task_id} → To Validate`);
|
|
750
|
-
} catch (err) {
|
|
751
|
-
logger.warn(`Planning Game: could not update ${session.pg_task_id} on completion: ${err.message}`);
|
|
752
|
-
}
|
|
753
|
-
}
|
|
657
|
+
// PG card "To Validate" logic moved to src/planning-game/pipeline-adapter.js → markPgCardToValidate()
|
|
754
658
|
|
|
755
659
|
async function handleReviewerRetryAndSolomon({ config, session, emitter, eventBase, logger, review, task, i, askQuestion }) {
|
|
756
660
|
session.last_reviewer_feedback = review.blocking_issues
|
|
@@ -835,6 +739,7 @@ async function runPreLoopStages({ config, logger, emitter, eventBase, session, f
|
|
|
835
739
|
});
|
|
836
740
|
if (simplified) stageResults.triage.autoSimplified = true;
|
|
837
741
|
|
|
742
|
+
const { handlePgDecomposition } = await import("./planning-game/pipeline-adapter.js");
|
|
838
743
|
await handlePgDecomposition({ triageResult, pgTaskId, pgProject, config, askQuestion, emitter, eventBase, session, stageResults, logger });
|
|
839
744
|
|
|
840
745
|
applyFlagOverrides(pipelineFlags, flags);
|
|
@@ -1097,59 +1002,61 @@ async function handleMaxIterationsReached({ session, budgetSummary, emitter, eve
|
|
|
1097
1002
|
}
|
|
1098
1003
|
|
|
1099
1004
|
async function initFlowContext({ task, config, logger, emitter, askQuestion, pgTaskId, pgProject, flags }) {
|
|
1100
|
-
const
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
const
|
|
1114
|
-
|
|
1005
|
+
const ctx = new PipelineContext({ config, session: null, logger, emitter, task, flags });
|
|
1006
|
+
ctx.askQuestion = askQuestion;
|
|
1007
|
+
ctx.pgTaskId = pgTaskId;
|
|
1008
|
+
ctx.pgProject = pgProject;
|
|
1009
|
+
|
|
1010
|
+
ctx.coderRole = resolveRole(config, "coder");
|
|
1011
|
+
ctx.reviewerRole = resolveRole(config, "reviewer");
|
|
1012
|
+
ctx.refactorerRole = resolveRole(config, "refactorer");
|
|
1013
|
+
ctx.pipelineFlags = resolvePipelineFlags(config);
|
|
1014
|
+
ctx.repeatDetector = new RepeatDetector({ threshold: getRepeatThreshold(config) });
|
|
1015
|
+
ctx.coderRoleInstance = new CoderRole({ config, logger, emitter, createAgentFn: createAgent, askHost: askQuestion });
|
|
1016
|
+
ctx.startedAt = Date.now();
|
|
1017
|
+
ctx.eventBase = { sessionId: null, iteration: 0, stage: null, startedAt: ctx.startedAt };
|
|
1018
|
+
const { budgetTracker, budgetLimit, budgetSummary, trackBudget } = createBudgetManager({ config, emitter, eventBase: ctx.eventBase });
|
|
1019
|
+
ctx.budgetTracker = budgetTracker;
|
|
1020
|
+
ctx.budgetLimit = budgetLimit;
|
|
1021
|
+
ctx.budgetSummary = budgetSummary;
|
|
1022
|
+
ctx.trackBudget = trackBudget;
|
|
1023
|
+
|
|
1024
|
+
ctx.session = await initializeSession({ task, config, flags, pgTaskId, pgProject });
|
|
1025
|
+
ctx.eventBase.sessionId = ctx.session.id;
|
|
1026
|
+
|
|
1027
|
+
const { initPgAdapter } = await import("./planning-game/pipeline-adapter.js");
|
|
1028
|
+
const pgAdapterResult = await initPgAdapter({ session: ctx.session, config, logger, pgTaskId, pgProject });
|
|
1029
|
+
ctx.pgCard = pgAdapterResult.pgCard;
|
|
1030
|
+
ctx.session.pg_card = ctx.pgCard || null;
|
|
1115
1031
|
|
|
1116
1032
|
emitProgress(
|
|
1117
1033
|
emitter,
|
|
1118
|
-
makeEvent("session:start", eventBase, {
|
|
1034
|
+
makeEvent("session:start", ctx.eventBase, {
|
|
1119
1035
|
message: "Session started",
|
|
1120
|
-
detail: { task, coder: coderRole.provider, reviewer: reviewerRole.provider, maxIterations: config.max_iterations }
|
|
1036
|
+
detail: { task, coder: ctx.coderRole.provider, reviewer: ctx.reviewerRole.provider, maxIterations: config.max_iterations }
|
|
1121
1037
|
})
|
|
1122
1038
|
);
|
|
1123
1039
|
|
|
1124
|
-
|
|
1125
|
-
|
|
1040
|
+
ctx.stageResults = {};
|
|
1041
|
+
ctx.sonarState = { issuesInitial: null, issuesFinal: null };
|
|
1126
1042
|
|
|
1127
|
-
const preLoopResult = await runPreLoopStages({ config, logger, emitter, eventBase, session, flags, pipelineFlags, coderRole, trackBudget, task, askQuestion, pgTaskId, pgProject, stageResults });
|
|
1128
|
-
|
|
1129
|
-
|
|
1043
|
+
const preLoopResult = await runPreLoopStages({ config, logger, emitter, eventBase: ctx.eventBase, session: ctx.session, flags, pipelineFlags: ctx.pipelineFlags, coderRole: ctx.coderRole, trackBudget: ctx.trackBudget, task, askQuestion, pgTaskId, pgProject, stageResults: ctx.stageResults });
|
|
1044
|
+
ctx.plannedTask = preLoopResult.plannedTask;
|
|
1045
|
+
ctx.config = preLoopResult.updatedConfig;
|
|
1130
1046
|
|
|
1131
|
-
|
|
1132
|
-
const projectDir =
|
|
1133
|
-
|
|
1134
|
-
await coderRoleInstance.init();
|
|
1047
|
+
ctx.gitCtx = await prepareGitAutomation({ config: ctx.config, task, logger, session: ctx.session });
|
|
1048
|
+
const projectDir = ctx.config.projectDir || process.cwd();
|
|
1049
|
+
ctx.reviewRules = (await resolveReviewProfile({ mode: ctx.config.review_mode, projectDir })).rules;
|
|
1050
|
+
await ctx.coderRoleInstance.init();
|
|
1135
1051
|
|
|
1136
|
-
return
|
|
1137
|
-
coderRole, reviewerRole, refactorerRole, pipelineFlags, repeatDetector, coderRoleInstance,
|
|
1138
|
-
startedAt, eventBase, budgetTracker, budgetLimit, budgetSummary, trackBudget,
|
|
1139
|
-
session, pgCard, stageResults, sonarState, plannedTask, config: updatedConfig,
|
|
1140
|
-
gitCtx, reviewRules
|
|
1141
|
-
};
|
|
1052
|
+
return ctx;
|
|
1142
1053
|
}
|
|
1143
1054
|
|
|
1144
1055
|
async function runSingleIteration(ctx) {
|
|
1145
|
-
const {
|
|
1146
|
-
coderRoleInstance, coderRole, refactorerRole, pipelineFlags, config, logger, emitter, eventBase,
|
|
1147
|
-
session, plannedTask, trackBudget, i, reviewerRole, reviewRules, task, repeatDetector,
|
|
1148
|
-
budgetSummary, askQuestion, sonarState, stageResults, gitCtx, pgCard, pgProject
|
|
1149
|
-
} = ctx;
|
|
1056
|
+
const { config, logger, emitter, eventBase, session, task, iteration: i } = ctx;
|
|
1150
1057
|
|
|
1151
1058
|
const iterStart = Date.now();
|
|
1152
|
-
const becariaEnabled = Boolean(config.becaria?.enabled) && gitCtx?.enabled;
|
|
1059
|
+
const becariaEnabled = Boolean(config.becaria?.enabled) && ctx.gitCtx?.enabled;
|
|
1153
1060
|
logger.setContext({ iteration: i, stage: "iteration" });
|
|
1154
1061
|
|
|
1155
1062
|
emitProgress(emitter, makeEvent("iteration:start", { ...eventBase, stage: "iteration" }, {
|
|
@@ -1158,18 +1065,34 @@ async function runSingleIteration(ctx) {
|
|
|
1158
1065
|
}));
|
|
1159
1066
|
logger.info(`Iteration ${i}/${config.max_iterations}`);
|
|
1160
1067
|
|
|
1161
|
-
const crResult = await runCoderAndRefactorerStages({
|
|
1068
|
+
const crResult = await runCoderAndRefactorerStages({
|
|
1069
|
+
coderRoleInstance: ctx.coderRoleInstance, coderRole: ctx.coderRole, refactorerRole: ctx.refactorerRole,
|
|
1070
|
+
pipelineFlags: ctx.pipelineFlags, config, logger, emitter, eventBase, session,
|
|
1071
|
+
plannedTask: ctx.plannedTask, trackBudget: ctx.trackBudget, i
|
|
1072
|
+
});
|
|
1162
1073
|
if (crResult.action === "return" || crResult.action === "retry") return crResult;
|
|
1163
1074
|
|
|
1164
1075
|
const guardResult = await runGuardStages({ config, logger, emitter, eventBase, session, iteration: i });
|
|
1165
1076
|
if (guardResult.action === "return") return guardResult;
|
|
1166
1077
|
|
|
1167
|
-
const qgResult = await runQualityGateStages({
|
|
1078
|
+
const qgResult = await runQualityGateStages({
|
|
1079
|
+
config, logger, emitter, eventBase, session, trackBudget: ctx.trackBudget, i,
|
|
1080
|
+
askQuestion: ctx.askQuestion, repeatDetector: ctx.repeatDetector, budgetSummary: ctx.budgetSummary,
|
|
1081
|
+
sonarState: ctx.sonarState, task, stageResults: ctx.stageResults, coderRole: ctx.coderRole,
|
|
1082
|
+
pipelineFlags: ctx.pipelineFlags
|
|
1083
|
+
});
|
|
1168
1084
|
if (qgResult.action === "return" || qgResult.action === "continue") return qgResult;
|
|
1169
1085
|
|
|
1170
|
-
await handleBecariaEarlyPrOrPush({
|
|
1086
|
+
await handleBecariaEarlyPrOrPush({
|
|
1087
|
+
becariaEnabled, config, session, emitter, eventBase, gitCtx: ctx.gitCtx, task, logger,
|
|
1088
|
+
stageResults: ctx.stageResults, i
|
|
1089
|
+
});
|
|
1171
1090
|
|
|
1172
|
-
const revResult = await runReviewerGateStage({
|
|
1091
|
+
const revResult = await runReviewerGateStage({
|
|
1092
|
+
pipelineFlags: ctx.pipelineFlags, reviewerRole: ctx.reviewerRole, config, logger, emitter, eventBase,
|
|
1093
|
+
session, trackBudget: ctx.trackBudget, i, reviewRules: ctx.reviewRules, task,
|
|
1094
|
+
repeatDetector: ctx.repeatDetector, budgetSummary: ctx.budgetSummary, askQuestion: ctx.askQuestion
|
|
1095
|
+
});
|
|
1173
1096
|
if (revResult.action === "return" || revResult.action === "retry") return revResult;
|
|
1174
1097
|
const review = revResult.review;
|
|
1175
1098
|
|
|
@@ -1179,20 +1102,27 @@ async function runSingleIteration(ctx) {
|
|
|
1179
1102
|
}));
|
|
1180
1103
|
session.standby_retry_count = 0;
|
|
1181
1104
|
|
|
1182
|
-
const solomonResult = await handleSolomonCheck({
|
|
1105
|
+
const solomonResult = await handleSolomonCheck({
|
|
1106
|
+
config, session, emitter, eventBase, logger, task, i, askQuestion: ctx.askQuestion,
|
|
1107
|
+
becariaEnabled, blockingIssues: review?.blocking_issues
|
|
1108
|
+
});
|
|
1183
1109
|
if (solomonResult.action === "pause") return { action: "return", result: solomonResult.result };
|
|
1184
1110
|
|
|
1185
1111
|
await handleBecariaReviewDispatch({ becariaEnabled, config, session, review, i, logger });
|
|
1186
1112
|
|
|
1187
1113
|
if (review.approved) {
|
|
1188
|
-
const approvedResult = await handleApprovedReview({
|
|
1114
|
+
const approvedResult = await handleApprovedReview({
|
|
1115
|
+
config, session, emitter, eventBase, coderRole: ctx.coderRole, trackBudget: ctx.trackBudget, i, task,
|
|
1116
|
+
stageResults: ctx.stageResults, pipelineFlags: ctx.pipelineFlags, askQuestion: ctx.askQuestion, logger,
|
|
1117
|
+
gitCtx: ctx.gitCtx, budgetSummary: ctx.budgetSummary, pgCard: ctx.pgCard, pgProject: ctx.pgProject, review
|
|
1118
|
+
});
|
|
1189
1119
|
if (approvedResult.action === "return" || approvedResult.action === "continue") return approvedResult;
|
|
1190
1120
|
}
|
|
1191
1121
|
|
|
1192
|
-
// Solomon already evaluated the rejection in runReviewerStage
|
|
1122
|
+
// Solomon already evaluated the rejection in runReviewerStage -> handleReviewerRejection
|
|
1193
1123
|
// Only use retry counter as fallback if Solomon is disabled
|
|
1194
1124
|
if (!config.pipeline?.solomon?.enabled) {
|
|
1195
|
-
const retryResult = await handleReviewerRetryAndSolomon({ config, session, emitter, eventBase, logger, review, task, i, askQuestion });
|
|
1125
|
+
const retryResult = await handleReviewerRetryAndSolomon({ config, session, emitter, eventBase, logger, review, task, i, askQuestion: ctx.askQuestion });
|
|
1196
1126
|
if (retryResult.action === "return") return retryResult;
|
|
1197
1127
|
} else {
|
|
1198
1128
|
// Solomon is enabled — feed back the blocking issues for the next coder iteration
|
|
@@ -1213,37 +1143,36 @@ export async function runFlow({ task, config, logger, flags = {}, emitter = null
|
|
|
1213
1143
|
}
|
|
1214
1144
|
|
|
1215
1145
|
const ctx = await initFlowContext({ task, config, logger, emitter, askQuestion, pgTaskId, pgProject, flags });
|
|
1216
|
-
config = ctx.config;
|
|
1217
1146
|
|
|
1218
|
-
const checkpointIntervalMs = (config.session.checkpoint_interval_minutes ?? 5) * 60 * 1000;
|
|
1147
|
+
const checkpointIntervalMs = (ctx.config.session.checkpoint_interval_minutes ?? 5) * 60 * 1000;
|
|
1219
1148
|
let lastCheckpointAt = Date.now();
|
|
1220
1149
|
let checkpointDisabled = false;
|
|
1221
1150
|
|
|
1222
1151
|
let i = 0;
|
|
1223
|
-
while (i < config.max_iterations) {
|
|
1152
|
+
while (i < ctx.config.max_iterations) {
|
|
1224
1153
|
i += 1;
|
|
1225
1154
|
const elapsedMinutes = (Date.now() - ctx.startedAt) / 60000;
|
|
1226
1155
|
|
|
1227
1156
|
const cpResult = await handleCheckpoint({
|
|
1228
1157
|
checkpointDisabled, askQuestion, lastCheckpointAt, checkpointIntervalMs, elapsedMinutes,
|
|
1229
|
-
i, config, budgetTracker: ctx.budgetTracker, stageResults: ctx.stageResults, emitter, eventBase: ctx.eventBase, session: ctx.session, budgetSummary: ctx.budgetSummary
|
|
1158
|
+
i, config: ctx.config, budgetTracker: ctx.budgetTracker, stageResults: ctx.stageResults, emitter, eventBase: ctx.eventBase, session: ctx.session, budgetSummary: ctx.budgetSummary
|
|
1230
1159
|
});
|
|
1231
1160
|
if (cpResult.action === "stop") return cpResult.result;
|
|
1232
1161
|
checkpointDisabled = cpResult.checkpointDisabled;
|
|
1233
1162
|
lastCheckpointAt = cpResult.lastCheckpointAt;
|
|
1234
1163
|
|
|
1235
|
-
await checkSessionTimeout({ askQuestion, elapsedMinutes, config, session: ctx.session, emitter, eventBase: ctx.eventBase, i, budgetSummary: ctx.budgetSummary });
|
|
1236
|
-
await checkBudgetExceeded({ budgetTracker: ctx.budgetTracker, config, session: ctx.session, emitter, eventBase: ctx.eventBase, i, budgetLimit: ctx.budgetLimit, budgetSummary: ctx.budgetSummary });
|
|
1164
|
+
await checkSessionTimeout({ askQuestion, elapsedMinutes, config: ctx.config, session: ctx.session, emitter, eventBase: ctx.eventBase, i, budgetSummary: ctx.budgetSummary });
|
|
1165
|
+
await checkBudgetExceeded({ budgetTracker: ctx.budgetTracker, config: ctx.config, session: ctx.session, emitter, eventBase: ctx.eventBase, i, budgetLimit: ctx.budgetLimit, budgetSummary: ctx.budgetSummary });
|
|
1237
1166
|
|
|
1238
1167
|
ctx.eventBase.iteration = i;
|
|
1239
|
-
ctx.
|
|
1168
|
+
ctx.iteration = i;
|
|
1240
1169
|
|
|
1241
|
-
const iterResult = await runSingleIteration(
|
|
1170
|
+
const iterResult = await runSingleIteration(ctx);
|
|
1242
1171
|
if (iterResult.action === "return") return iterResult.result;
|
|
1243
1172
|
if (iterResult.action === "retry") { i -= 1; }
|
|
1244
1173
|
}
|
|
1245
1174
|
|
|
1246
|
-
return handleMaxIterationsReached({ session: ctx.session, budgetSummary: ctx.budgetSummary, emitter, eventBase: ctx.eventBase, config, stageResults: ctx.stageResults, logger, askQuestion, task });
|
|
1175
|
+
return handleMaxIterationsReached({ session: ctx.session, budgetSummary: ctx.budgetSummary, emitter, eventBase: ctx.eventBase, config: ctx.config, stageResults: ctx.stageResults, logger, askQuestion, task });
|
|
1247
1176
|
}
|
|
1248
1177
|
|
|
1249
1178
|
export async function resumeFlow({ sessionId, answer, config, logger, flags = {}, emitter = null, askQuestion = null }) {
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Planning Game pipeline adapter.
|
|
3
|
+
* Subscribes to pipeline events and handles PG card lifecycle updates.
|
|
4
|
+
* Decouples PG logic from the orchestrator — works as an event-driven plugin.
|
|
5
|
+
*
|
|
6
|
+
* Handles:
|
|
7
|
+
* - Marking card as "In Progress" at session start
|
|
8
|
+
* - Decomposing tasks into subtasks after triage
|
|
9
|
+
* - Marking card as "To Validate" on approved completion
|
|
10
|
+
*
|
|
11
|
+
* If PG is disabled or no pgTaskId is present, the adapter does nothing.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { emitProgress, makeEvent } from "../utils/events.js";
|
|
15
|
+
import { addCheckpoint, saveSession } from "../session-store.js";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Attach the PG adapter to a pipeline emitter.
|
|
19
|
+
* Call this once during runFlow() initialization.
|
|
20
|
+
*
|
|
21
|
+
* @param {object} opts
|
|
22
|
+
* @param {EventEmitter} opts.emitter - Pipeline event emitter
|
|
23
|
+
* @param {object} opts.session - Session object (mutated during pipeline)
|
|
24
|
+
* @param {object} opts.config - Resolved config
|
|
25
|
+
* @param {object} opts.logger - Logger instance
|
|
26
|
+
* @param {string|null} opts.pgTaskId - Planning Game card ID (e.g. "KJC-TSK-0099")
|
|
27
|
+
* @param {string|null} opts.pgProject - Planning Game project ID
|
|
28
|
+
* @returns {{ pgCard: object|null }} The fetched PG card (or null)
|
|
29
|
+
*/
|
|
30
|
+
export async function initPgAdapter({ session, config, logger, pgTaskId, pgProject }) {
|
|
31
|
+
if (!pgTaskId || !pgProject || config.planning_game?.enabled === false) {
|
|
32
|
+
return { pgCard: null };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const pgCard = await markPgCardInProgress({ pgTaskId, pgProject, config, logger });
|
|
36
|
+
return { pgCard };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Handle PG task decomposition after triage.
|
|
41
|
+
* Creates subtask cards in PG when triage recommends decomposition.
|
|
42
|
+
*/
|
|
43
|
+
export async function handlePgDecomposition({ triageResult, pgTaskId, pgProject, config, askQuestion, emitter, eventBase, session, stageResults, logger }) {
|
|
44
|
+
const shouldDecompose = triageResult.stageResult?.shouldDecompose
|
|
45
|
+
&& triageResult.stageResult.subtasks?.length > 1
|
|
46
|
+
&& pgTaskId
|
|
47
|
+
&& pgProject
|
|
48
|
+
&& config.planning_game?.enabled !== false
|
|
49
|
+
&& askQuestion;
|
|
50
|
+
|
|
51
|
+
if (!shouldDecompose) return;
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const { buildDecompositionQuestion, createDecompositionSubtasks } = await import("./decomposition.js");
|
|
55
|
+
const { createCard, relateCards, fetchCard } = await import("./client.js");
|
|
56
|
+
|
|
57
|
+
const question = buildDecompositionQuestion(triageResult.stageResult.subtasks, pgTaskId);
|
|
58
|
+
const answer = await askQuestion(question);
|
|
59
|
+
|
|
60
|
+
if (answer && (answer.trim().toLowerCase() === "yes" || answer.trim().toLowerCase() === "sí" || answer.trim().toLowerCase() === "si")) {
|
|
61
|
+
const parentCard = await fetchCard({ projectId: pgProject, cardId: pgTaskId }).catch(() => null);
|
|
62
|
+
const createdSubtasks = await createDecompositionSubtasks({
|
|
63
|
+
client: { createCard, relateCards },
|
|
64
|
+
projectId: pgProject,
|
|
65
|
+
parentCardId: pgTaskId,
|
|
66
|
+
parentFirebaseId: parentCard?.firebaseId || null,
|
|
67
|
+
subtasks: triageResult.stageResult.subtasks,
|
|
68
|
+
epic: parentCard?.epic || null,
|
|
69
|
+
sprint: parentCard?.sprint || null,
|
|
70
|
+
codeveloper: config.planning_game?.codeveloper || null
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
stageResults.triage.pgSubtasks = createdSubtasks;
|
|
74
|
+
logger.info(`Planning Game: created ${createdSubtasks.length} subtasks from decomposition`);
|
|
75
|
+
|
|
76
|
+
emitProgress(
|
|
77
|
+
emitter,
|
|
78
|
+
makeEvent("pg:decompose", { ...eventBase, stage: "triage" }, {
|
|
79
|
+
message: `Created ${createdSubtasks.length} subtasks in Planning Game`,
|
|
80
|
+
detail: { subtasks: createdSubtasks.map((s) => ({ cardId: s.cardId, title: s.title })) }
|
|
81
|
+
})
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
await addCheckpoint(session, {
|
|
85
|
+
stage: "pg-decompose",
|
|
86
|
+
subtasksCreated: createdSubtasks.length,
|
|
87
|
+
cardIds: createdSubtasks.map((s) => s.cardId)
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
} catch (err) {
|
|
91
|
+
logger.warn(`Planning Game decomposition failed: ${err.message}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Mark PG card as "To Validate" on approved session completion.
|
|
97
|
+
*/
|
|
98
|
+
export async function markPgCardToValidate({ pgCard, pgProject, config, session, gitResult, logger }) {
|
|
99
|
+
if (!pgCard || !pgProject) return;
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
const { updateCard } = await import("./client.js");
|
|
103
|
+
const { buildCompletionUpdates } = await import("./adapter.js");
|
|
104
|
+
const pgUpdates = buildCompletionUpdates({
|
|
105
|
+
approved: true,
|
|
106
|
+
commits: gitResult?.commits || [],
|
|
107
|
+
startDate: session.pg_card?.startDate || session.created_at,
|
|
108
|
+
codeveloper: config.planning_game?.codeveloper || null
|
|
109
|
+
});
|
|
110
|
+
await updateCard({
|
|
111
|
+
projectId: pgProject,
|
|
112
|
+
cardId: session.pg_task_id,
|
|
113
|
+
firebaseId: pgCard.firebaseId,
|
|
114
|
+
updates: pgUpdates
|
|
115
|
+
});
|
|
116
|
+
logger.info(`Planning Game: ${session.pg_task_id} → To Validate`);
|
|
117
|
+
} catch (err) {
|
|
118
|
+
logger.warn(`Planning Game: could not update ${session.pg_task_id} on completion: ${err.message}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// --- Internal helpers ---
|
|
123
|
+
|
|
124
|
+
async function markPgCardInProgress({ pgTaskId, pgProject, config, logger }) {
|
|
125
|
+
try {
|
|
126
|
+
const { fetchCard, updateCard } = await import("./client.js");
|
|
127
|
+
const pgCard = await fetchCard({ projectId: pgProject, cardId: pgTaskId });
|
|
128
|
+
if (pgCard && pgCard.status !== "In Progress") {
|
|
129
|
+
await updateCard({
|
|
130
|
+
projectId: pgProject,
|
|
131
|
+
cardId: pgTaskId,
|
|
132
|
+
firebaseId: pgCard.firebaseId,
|
|
133
|
+
updates: {
|
|
134
|
+
status: "In Progress",
|
|
135
|
+
startDate: new Date().toISOString(),
|
|
136
|
+
developer: "dev_016",
|
|
137
|
+
codeveloper: config.planning_game?.codeveloper || null
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
logger.info(`Planning Game: ${pgTaskId} → In Progress`);
|
|
141
|
+
}
|
|
142
|
+
return pgCard;
|
|
143
|
+
} catch (err) {
|
|
144
|
+
logger.warn(`Planning Game: could not update ${pgTaskId}: ${err.message}`);
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
}
|
package/src/sonar/scanner.js
CHANGED
|
@@ -144,6 +144,10 @@ async function resolveSonarToken(config, apiHost) {
|
|
|
144
144
|
].filter(Boolean);
|
|
145
145
|
|
|
146
146
|
for (const password of new Set(candidates)) {
|
|
147
|
+
if (password === "admin") {
|
|
148
|
+
// eslint-disable-next-line no-console
|
|
149
|
+
console.warn("[karajan] WARNING: Using default admin/admin credentials for SonarQube. Set KJ_SONAR_TOKEN for production use.");
|
|
150
|
+
}
|
|
147
151
|
const valid = await validateAdminCredentials(apiHost, adminUser, password);
|
|
148
152
|
if (!valid) continue;
|
|
149
153
|
const token = await generateUserToken(apiHost, adminUser, password);
|