karajan-code 1.59.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/package.json +1 -1
- package/scripts/install.js +18 -15
- package/src/agents/aider-agent.js +0 -7
- package/src/agents/claude-agent.js +34 -6
- package/src/agents/codex-agent.js +2 -6
- package/src/agents/gemini-agent.js +0 -4
- package/src/agents/opencode-agent.js +0 -4
- package/src/{becaria → ci}/dispatch.js +13 -13
- package/src/{becaria → ci}/pr-diff.js +1 -1
- package/src/cli.js +3 -2
- package/src/commands/agents.js +0 -1
- package/src/commands/doctor.js +22 -40
- package/src/commands/init.js +8 -8
- package/src/commands/report.js +0 -13
- package/src/commands/review.js +17 -17
- package/src/commands/run.js +1 -2
- package/src/commands/status.js +1 -7
- package/src/config.js +10 -52
- package/src/domains/domain-loader.js +85 -4
- package/src/git/automation.js +9 -9
- package/src/guards/output-guard.js +0 -17
- package/src/mcp/handlers/direct-handlers.js +1 -1
- package/src/mcp/handlers/run-handler.js +1 -2
- package/src/mcp/sovereignty-guard.js +2 -2
- package/src/mcp/tools.js +3 -2
- package/src/orchestrator/brain-coordinator.js +165 -0
- package/src/orchestrator/ci-integration.js +139 -0
- package/src/orchestrator/config-init.js +435 -0
- package/src/orchestrator/direct-actions.js +186 -0
- package/src/orchestrator/feedback-enrichment.js +140 -0
- package/src/orchestrator/feedback-queue.js +152 -0
- package/src/orchestrator/flow-control.js +159 -0
- package/src/orchestrator/post-loop-stages.js +20 -15
- package/src/orchestrator/preflight-checks.js +7 -3
- package/src/orchestrator/role-output-compressor.js +190 -0
- package/src/orchestrator/session-journal.js +344 -0
- package/src/orchestrator/solomon-escalation.js +38 -5
- package/src/orchestrator/solomon-rules.js +32 -33
- package/src/orchestrator/stages/reviewer-stage.js +16 -5
- package/src/orchestrator/stages/sonar-stage.js +24 -1
- package/src/orchestrator/verification-gate.js +146 -0
- package/src/orchestrator.js +435 -661
- package/src/planning-game/client.js +2 -2
- package/src/planning-game/pipeline-adapter.js +1 -1
- package/src/prompts/coder.js +17 -3
- package/src/prompts/reviewer.js +2 -2
- package/src/prompts/rtk-snippet.js +3 -5
- package/src/review/diff-generator.js +66 -17
- package/src/review/snapshot-diff.js +122 -0
- package/src/review/tdd-policy.js +4 -2
- package/src/roles/agent-role.js +135 -0
- package/src/roles/architect-role.js +61 -108
- package/src/roles/audit-role.js +25 -71
- package/src/roles/coder-role.js +32 -57
- package/src/roles/discover-role.js +30 -71
- package/src/roles/hu-reviewer-role.js +51 -99
- package/src/roles/impeccable-role.js +34 -91
- package/src/roles/index.js +2 -0
- package/src/roles/karajan-brain-role.js +134 -0
- package/src/roles/planner-role.js +40 -75
- package/src/roles/refactorer-role.js +22 -64
- package/src/roles/researcher-role.js +52 -118
- package/src/roles/reviewer-role.js +69 -126
- package/src/roles/security-role.js +38 -102
- package/src/roles/solomon-role.js +71 -15
- package/src/roles/tester-role.js +121 -95
- package/src/roles/triage-role.js +50 -111
- package/src/session-store.js +1 -1
- package/src/skills/skill-detector.js +11 -0
- package/src/sonar/config-resolver.js +20 -1
- package/src/sonar/credentials.js +17 -0
- package/src/sonar/project-key.js +1 -1
- package/src/sonar/scanner.js +1 -1
- package/src/utils/banner.js +66 -0
- package/src/utils/display.js +35 -17
- package/src/utils/injection-guard.js +23 -8
- package/src/utils/logger.js +6 -1
- package/src/utils/process.js +3 -4
- package/src/utils/role-assigner.js +131 -0
- package/src/utils/update-check.js +5 -1
- package/src/utils/welcome.js +3 -8
- package/src/webperf/devtools-detect.js +70 -23
- package/templates/roles/coder.md +7 -0
- package/templates/roles/karajan-brain.md +95 -0
- package/templates/roles/solomon.md +67 -106
- package/templates/workflows/automerge.yml +4 -4
- package/templates/workflows/houston-override.yml +4 -4
- package/templates/workflows/{becaria-gateway.yml → kj-ci-gateway.yml} +12 -12
- package/src/proxy/adapters/anthropic.js +0 -131
- package/src/proxy/adapters/gemini.js +0 -106
- package/src/proxy/adapters/index.js +0 -51
- package/src/proxy/adapters/openai.js +0 -71
- package/src/proxy/compression/ai-compressor.js +0 -209
- package/src/proxy/compression/dedup-cache.js +0 -100
- package/src/proxy/compression/deterministic/bash-build.js +0 -60
- package/src/proxy/compression/deterministic/bash-git.js +0 -101
- package/src/proxy/compression/deterministic/bash-infra.js +0 -64
- package/src/proxy/compression/deterministic/bash-misc.js +0 -53
- package/src/proxy/compression/deterministic/bash-pkg.js +0 -49
- package/src/proxy/compression/deterministic/bash-test.js +0 -61
- package/src/proxy/compression/deterministic/glob.js +0 -26
- package/src/proxy/compression/deterministic/grep.js +0 -53
- package/src/proxy/compression/deterministic/index.js +0 -58
- package/src/proxy/compression/deterministic/read.js +0 -22
- package/src/proxy/compression/deterministic/utils.js +0 -33
- package/src/proxy/compression/pipeline.js +0 -270
- package/src/proxy/middleware/prompt-injector.js +0 -165
- package/src/proxy/middleware/response-interceptor.js +0 -223
- package/src/proxy/progress-monitor.js +0 -98
- package/src/proxy/proxy-lifecycle.js +0 -198
- package/src/proxy/proxy-server.js +0 -239
- package/src/proxy/security.js +0 -45
- /package/src/{becaria → ci}/index.js +0 -0
- /package/src/{becaria → ci}/repo.js +0 -0
package/src/orchestrator.js
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
1
|
import { createAgent } from "./agents/index.js";
|
|
4
2
|
import {
|
|
5
|
-
createSession,
|
|
6
3
|
loadSession,
|
|
7
4
|
markSessionStatus,
|
|
8
5
|
pauseSession,
|
|
@@ -10,31 +7,25 @@ import {
|
|
|
10
7
|
saveSession,
|
|
11
8
|
addCheckpoint
|
|
12
9
|
} from "./session-store.js";
|
|
13
|
-
import {
|
|
14
|
-
import { buildCoderPrompt } from "./prompts/coder.js";
|
|
15
|
-
import { buildReviewerPrompt } from "./prompts/reviewer.js";
|
|
10
|
+
import { generateDiff } from "./review/diff-generator.js";
|
|
16
11
|
import { resolveRole } from "./config.js";
|
|
12
|
+
import { resolveReviewProfile } from "./review/profiles.js";
|
|
13
|
+
import { msg, getLang } from "./utils/messages.js";
|
|
17
14
|
import { RepeatDetector, getRepeatThreshold } from "./repeat-detector.js";
|
|
18
15
|
import { emitProgress, makeEvent } from "./utils/events.js";
|
|
19
|
-
import { BudgetTracker, extractUsageMetrics } from "./utils/budget.js";
|
|
20
16
|
import {
|
|
21
17
|
prepareGitAutomation,
|
|
22
|
-
finalizeGitAutomation
|
|
23
|
-
earlyPrCreation,
|
|
24
|
-
incrementalPush
|
|
18
|
+
finalizeGitAutomation
|
|
25
19
|
} from "./git/automation.js";
|
|
26
|
-
import { resolveRoleMdPath, loadFirstExisting } from "./roles/base-role.js";
|
|
27
|
-
import { applyPolicies } from "./guards/policy-resolver.js";
|
|
28
20
|
import { scanDiff } from "./guards/output-guard.js";
|
|
29
21
|
import { scanPerfDiff } from "./guards/perf-guard.js";
|
|
30
22
|
import { classifyIntent } from "./guards/intent-guard.js";
|
|
31
|
-
import { resolveReviewProfile } from "./review/profiles.js";
|
|
32
23
|
import { CoderRole } from "./roles/coder-role.js";
|
|
33
24
|
import { invokeSolomon } from "./orchestrator/solomon-escalation.js";
|
|
34
|
-
import { msg, getLang } from "./utils/messages.js";
|
|
35
25
|
import { PipelineContext } from "./orchestrator/pipeline-context.js";
|
|
36
26
|
import { runTriageStage, runResearcherStage, runArchitectStage, runPlannerStage, runDiscoverStage, runHuReviewerStage } from "./orchestrator/pre-loop-stages.js";
|
|
37
27
|
import { runDomainCuratorStage } from "./orchestrator/stages/domain-curator-stage.js";
|
|
28
|
+
import { persistInlineDomain } from "./domains/domain-loader.js";
|
|
38
29
|
import { runCoderStage, runRefactorerStage, runTddCheckStage, runSonarStage, runSonarCloudStage, runReviewerStage } from "./orchestrator/iteration-stages.js";
|
|
39
30
|
import { runTesterStage, runSecurityStage, runImpeccableStage, runFinalAuditStage } from "./orchestrator/post-loop-stages.js";
|
|
40
31
|
import { needsSubPipeline, runHuSubPipeline } from "./orchestrator/hu-sub-pipeline.js";
|
|
@@ -43,267 +34,43 @@ import { detectTestFramework } from "./utils/project-detect.js";
|
|
|
43
34
|
import { runPreflightChecks } from "./orchestrator/preflight-checks.js";
|
|
44
35
|
import { detectRtk } from "./utils/rtk-detect.js";
|
|
45
36
|
import { createRtkRunner, RtkSavingsTracker } from "./utils/rtk-wrapper.js";
|
|
46
|
-
import { setRunner as setDiffRunner } from "./review/diff-generator.js";
|
|
37
|
+
import { setRunner as setDiffRunner, setProjectDir as setDiffProjectDir } from "./review/diff-generator.js";
|
|
47
38
|
import { setRunner as setGitRunner } from "./utils/git.js";
|
|
48
39
|
import { detectNeededSkills, autoInstallSkills, cleanupAutoInstalledSkills } from "./skills/skill-detector.js";
|
|
49
40
|
import { isOpenSkillsAvailable } from "./skills/openskills-client.js";
|
|
50
|
-
import { startProxy, stopProxy } from "./proxy/proxy-lifecycle.js";
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
// --- Product Context loader ---
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Load product context from well-known file locations.
|
|
57
|
-
* Returns the file content or null if no file is found.
|
|
58
|
-
* @param {string|null} projectDir
|
|
59
|
-
* @returns {Promise<{content: string|null, source: string|null}>}
|
|
60
|
-
*/
|
|
61
|
-
export async function loadProductContext(projectDir) {
|
|
62
|
-
const base = projectDir || process.cwd();
|
|
63
|
-
const candidates = [
|
|
64
|
-
path.join(base, ".karajan", "context.md"),
|
|
65
|
-
path.join(base, "product-vision.md")
|
|
66
|
-
];
|
|
67
|
-
for (const file of candidates) {
|
|
68
|
-
try {
|
|
69
|
-
const content = await fs.readFile(file, "utf8");
|
|
70
|
-
return { content, source: file };
|
|
71
|
-
} catch { /* not found, try next */ }
|
|
72
|
-
}
|
|
73
|
-
return { content: null, source: null };
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// --- Extracted helper functions (pure refactoring, zero behavior change) ---
|
|
77
|
-
|
|
78
|
-
function resolvePipelineFlags(config) {
|
|
79
|
-
return {
|
|
80
|
-
plannerEnabled: Boolean(config.pipeline?.planner?.enabled),
|
|
81
|
-
refactorerEnabled: Boolean(config.pipeline?.refactorer?.enabled),
|
|
82
|
-
researcherEnabled: Boolean(config.pipeline?.researcher?.enabled),
|
|
83
|
-
testerEnabled: Boolean(config.pipeline?.tester?.enabled),
|
|
84
|
-
securityEnabled: Boolean(config.pipeline?.security?.enabled),
|
|
85
|
-
impeccableEnabled: Boolean(config.pipeline?.impeccable?.enabled),
|
|
86
|
-
reviewerEnabled: config.pipeline?.reviewer?.enabled !== false,
|
|
87
|
-
discoverEnabled: Boolean(config.pipeline?.discover?.enabled),
|
|
88
|
-
architectEnabled: Boolean(config.pipeline?.architect?.enabled),
|
|
89
|
-
huReviewerEnabled: Boolean(config.pipeline?.hu_reviewer?.enabled),
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
async function handleDryRun({ task, config, flags, emitter, pipelineFlags }) {
|
|
94
|
-
const { plannerEnabled, refactorerEnabled, researcherEnabled, testerEnabled, securityEnabled, impeccableEnabled, reviewerEnabled, discoverEnabled, architectEnabled, huReviewerEnabled } = pipelineFlags;
|
|
95
|
-
const plannerRole = resolveRole(config, "planner");
|
|
96
|
-
const coderRole = resolveRole(config, "coder");
|
|
97
|
-
const reviewerRole = resolveRole(config, "reviewer");
|
|
98
|
-
const refactorerRole = resolveRole(config, "refactorer");
|
|
99
|
-
const triageEnabled = true;
|
|
100
|
-
|
|
101
|
-
const dryRunPolicies = applyPolicies({
|
|
102
|
-
taskType: flags.taskType || config.taskType || null,
|
|
103
|
-
policies: config.policies,
|
|
104
|
-
});
|
|
105
|
-
const projectDir = config.projectDir || process.cwd();
|
|
106
|
-
const { rules: reviewRules } = await resolveReviewProfile({ mode: config.review_mode, projectDir });
|
|
107
|
-
const coderRules = await loadFirstExisting(resolveRoleMdPath("coder", projectDir));
|
|
108
|
-
const coderPrompt = await buildCoderPrompt({ task, coderRules, methodology: config.development?.methodology, serenaEnabled: Boolean(config.serena?.enabled), rtkAvailable: Boolean(config.rtk?.available), proxyEnabled: Boolean(config.proxy?.enabled), productContext: config.productContext || null });
|
|
109
|
-
const reviewerPrompt = await buildReviewerPrompt({ task, diff: "(dry-run: no diff)", reviewRules, mode: config.review_mode, serenaEnabled: Boolean(config.serena?.enabled), rtkAvailable: Boolean(config.rtk?.available), proxyEnabled: Boolean(config.proxy?.enabled), productContext: config.productContext || null });
|
|
110
|
-
|
|
111
|
-
const summary = {
|
|
112
|
-
dry_run: true,
|
|
113
|
-
task,
|
|
114
|
-
policies: dryRunPolicies,
|
|
115
|
-
roles: { planner: plannerRole, coder: coderRole, reviewer: reviewerRole, refactorer: refactorerRole },
|
|
116
|
-
pipeline: {
|
|
117
|
-
discover_enabled: discoverEnabled,
|
|
118
|
-
architect_enabled: architectEnabled,
|
|
119
|
-
triage_enabled: triageEnabled,
|
|
120
|
-
planner_enabled: plannerEnabled,
|
|
121
|
-
refactorer_enabled: refactorerEnabled,
|
|
122
|
-
sonar_enabled: Boolean(config.sonarqube?.enabled),
|
|
123
|
-
reviewer_enabled: reviewerEnabled,
|
|
124
|
-
researcher_enabled: researcherEnabled,
|
|
125
|
-
tester_enabled: testerEnabled,
|
|
126
|
-
security_enabled: securityEnabled,
|
|
127
|
-
impeccable_enabled: impeccableEnabled,
|
|
128
|
-
solomon_enabled: Boolean(config.pipeline?.solomon?.enabled),
|
|
129
|
-
hu_reviewer_enabled: huReviewerEnabled
|
|
130
|
-
},
|
|
131
|
-
limits: {
|
|
132
|
-
max_iterations: config.max_iterations,
|
|
133
|
-
max_iteration_minutes: config.session?.max_iteration_minutes,
|
|
134
|
-
max_total_minutes: config.session?.max_total_minutes,
|
|
135
|
-
max_sonar_retries: config.session?.max_sonar_retries,
|
|
136
|
-
max_reviewer_retries: config.session?.max_reviewer_retries,
|
|
137
|
-
max_tester_retries: config.session?.max_tester_retries,
|
|
138
|
-
max_security_retries: config.session?.max_security_retries
|
|
139
|
-
},
|
|
140
|
-
prompts: { coder: coderPrompt, reviewer: reviewerPrompt },
|
|
141
|
-
git: config.git
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
emitProgress(
|
|
145
|
-
emitter,
|
|
146
|
-
makeEvent("dry-run:summary", { sessionId: null, iteration: 0, stage: "dry-run", startedAt: Date.now() }, {
|
|
147
|
-
message: "Dry-run complete — no changes made",
|
|
148
|
-
detail: summary
|
|
149
|
-
})
|
|
150
|
-
);
|
|
151
|
-
|
|
152
|
-
return summary;
|
|
153
|
-
}
|
|
154
41
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
const status = totalCost > budgetLimit ? "fail" : warnOrOk;
|
|
179
|
-
emitProgress(
|
|
180
|
-
emitter,
|
|
181
|
-
makeEvent("budget:update", { ...eventBase, stage: role }, {
|
|
182
|
-
status,
|
|
183
|
-
message: `Budget: $${totalCost.toFixed(2)} / $${budgetLimit.toFixed(2)}`,
|
|
184
|
-
detail: {
|
|
185
|
-
...budgetSummary(),
|
|
186
|
-
max_budget_usd: budgetLimit,
|
|
187
|
-
warn_threshold_pct: warnThresholdPct,
|
|
188
|
-
pct_used: Number(pctUsed.toFixed(2)),
|
|
189
|
-
remaining_usd: budgetTracker.remaining(budgetLimit),
|
|
190
|
-
executorType: "system"
|
|
191
|
-
}
|
|
192
|
-
})
|
|
193
|
-
);
|
|
194
|
-
}
|
|
42
|
+
// Extracted modules
|
|
43
|
+
import {
|
|
44
|
+
loadProductContext as _loadProductContext,
|
|
45
|
+
resolvePipelineFlags, handleDryRun, createBudgetManager,
|
|
46
|
+
initializeSession, applyTriageOverrides, applyAutoSimplify,
|
|
47
|
+
applyFlagOverrides, resolvePipelinePolicies, autoInit,
|
|
48
|
+
updateGitignoreForStack
|
|
49
|
+
} from "./orchestrator/config-init.js";
|
|
50
|
+
import {
|
|
51
|
+
tryCiComment, handleCiEarlyPrOrPush, handleCiReviewDispatch,
|
|
52
|
+
formatBlockingIssues
|
|
53
|
+
} from "./orchestrator/ci-integration.js";
|
|
54
|
+
import {
|
|
55
|
+
shouldAutoContinueCheckpoint as _shouldAutoContinueCheckpoint,
|
|
56
|
+
parseCheckpointAnswer as _parseCheckpointAnswer,
|
|
57
|
+
handleCheckpoint, checkSessionTimeout, checkBudgetExceeded,
|
|
58
|
+
takeCheckpointSnapshot
|
|
59
|
+
} from "./orchestrator/flow-control.js";
|
|
60
|
+
import {
|
|
61
|
+
createJournalDir, writePreLoopJournal, writeIterationsJournal,
|
|
62
|
+
writeDecisionsJournal, writeTreeJournal, writeSummaryJournal,
|
|
63
|
+
formatIteration, formatDecision, buildPlanSummary
|
|
64
|
+
} from "./orchestrator/session-journal.js";
|
|
195
65
|
|
|
196
|
-
|
|
197
|
-
|
|
66
|
+
// Re-export for external consumers
|
|
67
|
+
export const loadProductContext = _loadProductContext;
|
|
68
|
+
export const shouldAutoContinueCheckpoint = _shouldAutoContinueCheckpoint;
|
|
69
|
+
export const parseCheckpointAnswer = _parseCheckpointAnswer;
|
|
198
70
|
|
|
199
|
-
async function initializeSession({ task, config, flags, pgTaskId, pgProject }) {
|
|
200
|
-
const baseRef = await computeBaseRef({ baseBranch: config.base_branch, baseRef: flags.baseRef || null });
|
|
201
|
-
const sessionInit = {
|
|
202
|
-
task,
|
|
203
|
-
config_snapshot: config,
|
|
204
|
-
base_ref: baseRef,
|
|
205
|
-
session_start_sha: baseRef,
|
|
206
|
-
last_reviewer_feedback: null,
|
|
207
|
-
repeated_issue_count: 0,
|
|
208
|
-
sonar_retry_count: 0,
|
|
209
|
-
reviewer_retry_count: 0,
|
|
210
|
-
standby_retry_count: 0,
|
|
211
|
-
last_sonar_issue_signature: null,
|
|
212
|
-
sonar_repeat_count: 0,
|
|
213
|
-
last_reviewer_issue_signature: null,
|
|
214
|
-
reviewer_repeat_count: 0,
|
|
215
|
-
deferred_issues: []
|
|
216
|
-
};
|
|
217
|
-
if (pgTaskId) sessionInit.pg_task_id = pgTaskId;
|
|
218
|
-
if (pgProject) sessionInit.pg_project_id = pgProject;
|
|
219
|
-
return createSession(sessionInit);
|
|
220
|
-
}
|
|
221
71
|
|
|
222
72
|
// PG card "In Progress" logic moved to src/planning-game/pipeline-adapter.js → initPgAdapter()
|
|
223
73
|
|
|
224
|
-
function applyTriageOverrides(pipelineFlags, roleOverrides) {
|
|
225
|
-
const keys = ["plannerEnabled", "researcherEnabled", "architectEnabled", "refactorerEnabled", "reviewerEnabled", "testerEnabled", "securityEnabled", "impeccableEnabled"];
|
|
226
|
-
for (const key of keys) {
|
|
227
|
-
if (roleOverrides[key] !== undefined) {
|
|
228
|
-
pipelineFlags[key] = roleOverrides[key];
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
const SIMPLE_LEVELS = new Set(["trivial", "simple"]);
|
|
234
|
-
|
|
235
|
-
function applyAutoSimplify({ pipelineFlags, triageLevel, config, flags, logger, emitter, eventBase }) {
|
|
236
|
-
if (!config.pipeline?.auto_simplify) return false;
|
|
237
|
-
if (!triageLevel || !SIMPLE_LEVELS.has(triageLevel)) return false;
|
|
238
|
-
if (flags.mode) return false;
|
|
239
|
-
if (flags.enableReviewer !== undefined || flags.enableTester !== undefined) return false;
|
|
240
|
-
|
|
241
|
-
pipelineFlags.reviewerEnabled = false;
|
|
242
|
-
pipelineFlags.testerEnabled = false;
|
|
243
|
-
|
|
244
|
-
const disabledRoles = ["reviewer", "tester"];
|
|
245
|
-
logger.info(`Simple task (${triageLevel}) — lightweight pipeline (disabled: ${disabledRoles.join(", ")})`);
|
|
246
|
-
emitProgress(
|
|
247
|
-
emitter,
|
|
248
|
-
makeEvent("pipeline:simplify", { ...eventBase, stage: "triage" }, {
|
|
249
|
-
message: `Simple task (${triageLevel}) — lightweight pipeline`,
|
|
250
|
-
detail: { level: triageLevel, disabledRoles }
|
|
251
|
-
})
|
|
252
|
-
);
|
|
253
|
-
return true;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// PG decomposition logic moved to src/planning-game/pipeline-adapter.js → handlePgDecomposition()
|
|
257
|
-
|
|
258
|
-
function applyFlagOverrides(pipelineFlags, flags) {
|
|
259
|
-
if (flags.enablePlanner !== undefined) pipelineFlags.plannerEnabled = Boolean(flags.enablePlanner);
|
|
260
|
-
if (flags.enableResearcher !== undefined) pipelineFlags.researcherEnabled = Boolean(flags.enableResearcher);
|
|
261
|
-
if (flags.enableArchitect !== undefined) pipelineFlags.architectEnabled = Boolean(flags.enableArchitect);
|
|
262
|
-
if (flags.enableRefactorer !== undefined) pipelineFlags.refactorerEnabled = Boolean(flags.enableRefactorer);
|
|
263
|
-
if (flags.enableReviewer !== undefined) pipelineFlags.reviewerEnabled = Boolean(flags.enableReviewer);
|
|
264
|
-
if (flags.enableTester !== undefined) pipelineFlags.testerEnabled = Boolean(flags.enableTester);
|
|
265
|
-
if (flags.enableSecurity !== undefined) pipelineFlags.securityEnabled = Boolean(flags.enableSecurity);
|
|
266
|
-
if (flags.enableImpeccable !== undefined) pipelineFlags.impeccableEnabled = Boolean(flags.enableImpeccable);
|
|
267
|
-
|
|
268
|
-
// --design flag: force-enable impeccable in refactoring mode
|
|
269
|
-
if (flags.design) {
|
|
270
|
-
pipelineFlags.impeccableEnabled = true;
|
|
271
|
-
pipelineFlags.impeccableMode = "refactoring";
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
function resolvePipelinePolicies({ flags, config, stageResults, emitter, eventBase, session, pipelineFlags }) {
|
|
276
|
-
const resolvedPolicies = applyPolicies({
|
|
277
|
-
taskType: flags.taskType || config.taskType || stageResults.triage?.taskType || stageResults.intent?.taskType || null,
|
|
278
|
-
policies: config.policies,
|
|
279
|
-
});
|
|
280
|
-
session.resolved_policies = resolvedPolicies;
|
|
281
|
-
|
|
282
|
-
let updatedConfig = config;
|
|
283
|
-
if (!resolvedPolicies.tdd) {
|
|
284
|
-
updatedConfig = { ...updatedConfig, development: { ...updatedConfig.development, methodology: "standard", require_test_changes: false } };
|
|
285
|
-
}
|
|
286
|
-
if (!resolvedPolicies.sonar) {
|
|
287
|
-
updatedConfig = { ...updatedConfig, sonarqube: { ...updatedConfig.sonarqube, enabled: false } };
|
|
288
|
-
}
|
|
289
|
-
if (!resolvedPolicies.reviewer) {
|
|
290
|
-
pipelineFlags.reviewerEnabled = false;
|
|
291
|
-
}
|
|
292
|
-
if (resolvedPolicies.coderRequired === false) {
|
|
293
|
-
pipelineFlags.coderRequired = false;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
emitProgress(
|
|
297
|
-
emitter,
|
|
298
|
-
makeEvent("policies:resolved", eventBase, {
|
|
299
|
-
message: `Policies resolved for taskType="${resolvedPolicies.taskType}"`,
|
|
300
|
-
detail: resolvedPolicies
|
|
301
|
-
})
|
|
302
|
-
);
|
|
303
|
-
|
|
304
|
-
return updatedConfig;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
74
|
async function runPlanningPhases({ config, logger, emitter, eventBase, session, stageResults, pipelineFlags, coderRole, trackBudget, task, askQuestion }) {
|
|
308
75
|
let researchContext = null;
|
|
309
76
|
let plannedTask = task;
|
|
@@ -335,7 +102,7 @@ async function runPlanningPhases({ config, logger, emitter, eventBase, session,
|
|
|
335
102
|
plannedTask = plannerResult.plannedTask;
|
|
336
103
|
stageResults.planner = plannerResult.stageResult;
|
|
337
104
|
|
|
338
|
-
await
|
|
105
|
+
await tryCiComment({
|
|
339
106
|
config, session, logger,
|
|
340
107
|
agent: "Planner",
|
|
341
108
|
body: `Plan: ${plannerResult.stageResult?.summary || plannedTask}`
|
|
@@ -345,277 +112,92 @@ async function runPlanningPhases({ config, logger, emitter, eventBase, session,
|
|
|
345
112
|
return { plannedTask };
|
|
346
113
|
}
|
|
347
114
|
|
|
348
|
-
async function tryBecariaComment({ config, session, logger, agent, body }) {
|
|
349
|
-
if (!config.becaria?.enabled || !session.becaria_pr_number) return;
|
|
350
|
-
try {
|
|
351
|
-
const { dispatchComment } = await import("./becaria/dispatch.js");
|
|
352
|
-
const { detectRepo } = await import("./becaria/repo.js");
|
|
353
|
-
const repo = await detectRepo();
|
|
354
|
-
if (repo) {
|
|
355
|
-
await dispatchComment({
|
|
356
|
-
repo, prNumber: session.becaria_pr_number, agent,
|
|
357
|
-
body, becariaConfig: config.becaria
|
|
358
|
-
});
|
|
359
|
-
}
|
|
360
|
-
} catch { /* non-blocking */ }
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
function detectCheckpointProgress(session, lastCheckpointSnapshot) {
|
|
364
|
-
if (!lastCheckpointSnapshot) return true; // First checkpoint — assume progress
|
|
365
|
-
const currentIteration = session.reviewer_retry_count ?? 0;
|
|
366
|
-
const currentStages = Object.keys(session.resolved_policies || {}).length;
|
|
367
|
-
const currentCheckpoints = (session.checkpoints || []).length;
|
|
368
|
-
|
|
369
|
-
const iterationAdvanced = currentIteration !== lastCheckpointSnapshot.iteration;
|
|
370
|
-
const stagesChanged = currentStages !== lastCheckpointSnapshot.stagesCount;
|
|
371
|
-
const checkpointsChanged = currentCheckpoints !== lastCheckpointSnapshot.checkpointsCount;
|
|
372
|
-
|
|
373
|
-
return iterationAdvanced || stagesChanged || checkpointsChanged;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
function takeCheckpointSnapshot(session) {
|
|
377
|
-
return {
|
|
378
|
-
iteration: session.reviewer_retry_count ?? 0,
|
|
379
|
-
stagesCount: Object.keys(session.resolved_policies || {}).length,
|
|
380
|
-
checkpointsCount: (session.checkpoints || []).length
|
|
381
|
-
};
|
|
382
|
-
}
|
|
383
115
|
|
|
384
|
-
/**
|
|
385
|
-
* Determine if checkpoint should auto-continue without asking the user.
|
|
386
|
-
* Exported for testing.
|
|
387
|
-
*/
|
|
388
|
-
export function shouldAutoContinueCheckpoint(session, hasProgress) {
|
|
389
|
-
if (hasProgress) {
|
|
390
|
-
session._checkpoint_stall_count = 0;
|
|
391
|
-
return { autoContinue: true, reason: "progress_detected" };
|
|
392
|
-
}
|
|
393
|
-
const wasRateLimited = (session.standby_retry_count || 0) > 0;
|
|
394
|
-
const consecutiveStalls = (session._checkpoint_stall_count || 0) + 1;
|
|
395
|
-
session._checkpoint_stall_count = consecutiveStalls;
|
|
396
|
-
if (wasRateLimited && consecutiveStalls < 3) {
|
|
397
|
-
return { autoContinue: true, reason: "recoverable_stall" };
|
|
398
|
-
}
|
|
399
|
-
return { autoContinue: false, reason: consecutiveStalls >= 3 ? "max_stalls_reached" : "no_progress" };
|
|
400
|
-
}
|
|
401
116
|
|
|
402
|
-
async function
|
|
403
|
-
if (
|
|
404
|
-
return {
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
const elapsedStr = elapsedMinutes.toFixed(1);
|
|
408
|
-
const stagesCompleted = Object.keys(stageResults).join(", ") || "none";
|
|
409
|
-
|
|
410
|
-
// Decide whether to auto-continue or ask the user
|
|
411
|
-
const hasProgress = detectCheckpointProgress(session, lastCheckpointSnapshot);
|
|
412
|
-
const newSnapshot = takeCheckpointSnapshot(session);
|
|
413
|
-
const decision = shouldAutoContinueCheckpoint(session, hasProgress);
|
|
414
|
-
|
|
415
|
-
if (decision.autoContinue) {
|
|
416
|
-
emitProgress(
|
|
417
|
-
emitter,
|
|
418
|
-
makeEvent("session:checkpoint", { ...eventBase, iteration: i, stage: "checkpoint" }, {
|
|
419
|
-
message: `Checkpoint: ${decision.reason === "progress_detected" ? "progress detected" : "stall caused by rate limit/cooldown"}, auto-continuing (${elapsedStr} min elapsed)`,
|
|
420
|
-
detail: { elapsed_minutes: Number(elapsedStr), iterations_done: i - 1, stages: stagesCompleted, auto_continued: true, reason: decision.reason }
|
|
421
|
-
})
|
|
422
|
-
);
|
|
423
|
-
return { action: "continue_loop", checkpointDisabled, lastCheckpointAt: Date.now(), lastCheckpointSnapshot: newSnapshot };
|
|
117
|
+
async function handleStandbyResult({ stageResult, session, emitter, eventBase, i, stage, logger, config, askQuestion }) {
|
|
118
|
+
if (stageResult?.action !== "standby") {
|
|
119
|
+
return { handled: false };
|
|
424
120
|
}
|
|
425
121
|
|
|
426
|
-
|
|
427
|
-
const
|
|
428
|
-
const
|
|
429
|
-
const
|
|
430
|
-
|
|
431
|
-
emitProgress(
|
|
432
|
-
emitter,
|
|
433
|
-
makeEvent("session:checkpoint", { ...eventBase, iteration: i, stage: "checkpoint" }, {
|
|
434
|
-
message: `Interactive checkpoint at ${elapsedStr} min (stalled)`,
|
|
435
|
-
detail: { elapsed_minutes: Number(elapsedStr), iterations_done: i - 1, stages: stagesCompleted, auto_continued: false }
|
|
436
|
-
})
|
|
437
|
-
);
|
|
438
|
-
|
|
439
|
-
const lang = getLang(config);
|
|
440
|
-
const answer = await askQuestion(
|
|
441
|
-
`${checkpointMsg}\n\n${msg("checkpoint_options", lang)}`
|
|
442
|
-
);
|
|
443
|
-
|
|
444
|
-
await addCheckpoint(session, { stage: "interactive-checkpoint", elapsed_minutes: Number(elapsedStr), answer });
|
|
445
|
-
|
|
446
|
-
const trimmedAnswer = (answer || "").trim();
|
|
447
|
-
const isExplicitStop = trimmedAnswer === "4" || trimmedAnswer.toLowerCase().startsWith("stop");
|
|
448
|
-
|
|
449
|
-
if (isExplicitStop) {
|
|
450
|
-
await markSessionStatus(session, "stopped");
|
|
451
|
-
emitProgress(
|
|
452
|
-
emitter,
|
|
453
|
-
makeEvent("session:end", { ...eventBase, iteration: i, stage: "user-stop" }, {
|
|
454
|
-
status: "stopped",
|
|
455
|
-
message: "Session stopped by user at checkpoint",
|
|
456
|
-
detail: { approved: false, reason: "user_stopped", elapsed_minutes: Number(elapsedStr), budget: budgetSummary() }
|
|
457
|
-
})
|
|
458
|
-
);
|
|
459
|
-
return { action: "stop", result: { approved: false, sessionId: session.id, reason: "user_stopped", elapsed_minutes: Number(elapsedStr) } };
|
|
460
|
-
}
|
|
122
|
+
const isOutage = stageResult.standbyInfo.isProviderOutage;
|
|
123
|
+
const agent = stageResult.standbyInfo.agent;
|
|
124
|
+
const cooldownUntil = stageResult.standbyInfo.cooldownUntil;
|
|
125
|
+
const cooldownMs = stageResult.standbyInfo.cooldownMs;
|
|
461
126
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
127
|
+
// Rate limit = out of normal flow → Solomon decides immediately
|
|
128
|
+
const solomonResult = await invokeSolomon({
|
|
129
|
+
config, logger, emitter, eventBase, stage: `${stage}_rate_limit`, askQuestion, session,
|
|
130
|
+
iteration: i,
|
|
131
|
+
conflict: {
|
|
132
|
+
stage: `${stage}_rate_limit`,
|
|
133
|
+
task: session.task,
|
|
134
|
+
iterationCount: i,
|
|
135
|
+
maxIterations: config?.max_iterations || 5,
|
|
136
|
+
cooldownUntil,
|
|
137
|
+
cooldownMs,
|
|
138
|
+
history: [{
|
|
139
|
+
agent: stage,
|
|
140
|
+
feedback: `Agent "${agent}" rate-limited. ${isOutage ? "Provider outage (5xx)." : "API rate limit (429)."} ${cooldownUntil ? `Cooldown until ${cooldownUntil}.` : ""}`
|
|
141
|
+
}]
|
|
142
|
+
}
|
|
143
|
+
});
|
|
466
144
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
return {
|
|
476
|
-
}
|
|
477
|
-
const customMinutes = Number.parseInt(trimmedAnswer.replaceAll(/\D/g, ""), 10);
|
|
478
|
-
if (customMinutes > 0) {
|
|
479
|
-
config.session.checkpoint_interval_minutes = customMinutes;
|
|
480
|
-
return { action: "continue_loop", checkpointDisabled, lastCheckpointAt: Date.now() };
|
|
145
|
+
if (solomonResult.action === "approve") {
|
|
146
|
+
// Solomon says skip is safe — only allowed after exhausting alternatives
|
|
147
|
+
logger.info(`Solomon: skip ${stage} after evaluating risk (agent "${agent}" rate-limited)`);
|
|
148
|
+
emitProgress(emitter, makeEvent(`${stage}:rate_limit`, { ...eventBase, stage }, {
|
|
149
|
+
status: "ok",
|
|
150
|
+
message: `Solomon: skip ${stage} (low risk, agent "${agent}" unavailable)`,
|
|
151
|
+
detail: { agent, solomonAction: "approve" }
|
|
152
|
+
}));
|
|
153
|
+
return { handled: true, action: "skip" };
|
|
481
154
|
}
|
|
482
|
-
return { action: "continue_loop", checkpointDisabled, lastCheckpointAt: Date.now() };
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
async function checkSessionTimeout({ askQuestion, elapsedMinutes, config, session, emitter, eventBase, i, budgetSummary }) {
|
|
486
|
-
if (askQuestion || elapsedMinutes <= config.session.max_total_minutes) return;
|
|
487
155
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
async function checkBudgetExceeded({ budgetTracker, config, session, emitter, eventBase, i, budgetLimit, budgetSummary }) {
|
|
501
|
-
if (!budgetTracker.isOverBudget(config?.max_budget_usd)) return;
|
|
156
|
+
if (solomonResult.action === "continue") {
|
|
157
|
+
// Solomon says: wait for cooldown, or retry with alternative agent
|
|
158
|
+
const altAgent = solomonResult.alternativeAgent;
|
|
159
|
+
const waitTarget = solomonResult.waitUntil;
|
|
160
|
+
|
|
161
|
+
if (waitTarget) {
|
|
162
|
+
const waitMs = Math.max(0, new Date(waitTarget).getTime() - Date.now());
|
|
163
|
+
if (waitMs > 0 && waitMs < 10 * 60 * 1000) {
|
|
164
|
+
logger.info(`Solomon: wait ${Math.round(waitMs / 1000)}s for cooldown, then retry ${stage}`);
|
|
165
|
+
await new Promise(r => setTimeout(r, waitMs));
|
|
166
|
+
}
|
|
167
|
+
}
|
|
502
168
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
emitter,
|
|
508
|
-
makeEvent("session:end", { ...eventBase, iteration: i, stage: "budget" }, {
|
|
509
|
-
status: "fail",
|
|
510
|
-
message,
|
|
511
|
-
detail: { approved: false, reason: "budget_exceeded", budget: budgetSummary(), max_budget_usd: budgetLimit }
|
|
512
|
-
})
|
|
513
|
-
);
|
|
514
|
-
throw new Error(message);
|
|
515
|
-
}
|
|
169
|
+
if (altAgent) {
|
|
170
|
+
logger.info(`Solomon: retry ${stage} with alternative agent "${altAgent}"`);
|
|
171
|
+
session._alternative_agent = { stage, provider: altAgent };
|
|
172
|
+
}
|
|
516
173
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
174
|
+
if (solomonResult.humanGuidance) {
|
|
175
|
+
session.last_reviewer_feedback = `Solomon guidance: ${solomonResult.humanGuidance}`;
|
|
176
|
+
}
|
|
177
|
+
await saveSession(session);
|
|
178
|
+
return { handled: true, action: "retry_reviewer_only" };
|
|
520
179
|
}
|
|
521
180
|
|
|
522
|
-
|
|
523
|
-
const isOutage = stageResult.standbyInfo.isProviderOutage;
|
|
524
|
-
const pauseReason = isOutage
|
|
525
|
-
? `Provider outage (${stageResult.standbyInfo.message || "5xx/connection error"}) — retried ${standbyRetries} times. This is NOT a KJ or code problem.`
|
|
526
|
-
: `Rate limit standby exhausted after ${standbyRetries} retries. Agent: ${stageResult.standbyInfo.agent}`;
|
|
527
|
-
|
|
528
|
-
if (standbyRetries >= MAX_STANDBY_RETRIES) {
|
|
529
|
-
session.last_reviewer_feedback = isOutage
|
|
530
|
-
? "IMPORTANT: The previous interruption was caused by a provider outage (API 500 error), NOT by a problem in your code or in Karajan. Continue from where you left off."
|
|
531
|
-
: session.last_reviewer_feedback;
|
|
532
|
-
await pauseSession(session, {
|
|
533
|
-
question: pauseReason,
|
|
534
|
-
context: { iteration: i, stage, reason: isOutage ? "provider_outage" : "standby_exhausted" }
|
|
535
|
-
});
|
|
536
|
-
emitProgress(emitter, makeEvent(`${stage}:rate_limit`, { ...eventBase, stage }, {
|
|
537
|
-
status: "paused",
|
|
538
|
-
message: `Standby exhausted after ${standbyRetries} retries`,
|
|
539
|
-
detail: { agent: stageResult.standbyInfo.agent, sessionId: session.id }
|
|
540
|
-
}));
|
|
181
|
+
if (solomonResult.action === "pause") {
|
|
541
182
|
return {
|
|
542
183
|
handled: true,
|
|
543
184
|
action: "return",
|
|
544
|
-
result: { paused: true, sessionId: session.id, question:
|
|
185
|
+
result: { paused: true, sessionId: session.id, question: solomonResult.question, context: "rate_limit" }
|
|
545
186
|
};
|
|
546
187
|
}
|
|
547
|
-
session.standby_retry_count = standbyRetries + 1;
|
|
548
|
-
await saveSession(session);
|
|
549
|
-
await waitForCooldown({ ...stageResult.standbyInfo, retryCount: standbyRetries, emitter, eventBase, logger, session });
|
|
550
|
-
return { handled: true, action: "retry" };
|
|
551
|
-
}
|
|
552
188
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
async function becariaIncrementalPush({ config, session, gitCtx, task, logger, repo, dispatchComment }) {
|
|
558
|
-
const pushResult = await incrementalPush({ gitCtx, task, logger, session });
|
|
559
|
-
if (!pushResult) return;
|
|
560
|
-
|
|
561
|
-
// Accumulate commits for PG card lifecycle tracking
|
|
562
|
-
const { accumulateCommit } = await import("./planning-game/pipeline-adapter.js");
|
|
563
|
-
for (const c of pushResult.commits) accumulateCommit(session, c);
|
|
564
|
-
|
|
565
|
-
session.becaria_commits = [...(session.becaria_commits ?? []), ...pushResult.commits];
|
|
566
|
-
await saveSession(session);
|
|
567
|
-
|
|
568
|
-
if (!repo) return;
|
|
569
|
-
const feedback = session.last_reviewer_feedback || "N/A";
|
|
570
|
-
await dispatchComment({
|
|
571
|
-
repo, prNumber: session.becaria_pr_number, agent: "Coder",
|
|
572
|
-
body: `Issues corregidos:\n${feedback}\n\nCommits:\n${formatCommitList(pushResult.commits)}`,
|
|
573
|
-
becariaConfig: config.becaria
|
|
574
|
-
});
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
async function becariaCreateEarlyPr({ config, session, emitter, eventBase, gitCtx, task, logger, stageResults, i, repo, dispatchComment }) {
|
|
578
|
-
const earlyPr = await earlyPrCreation({ gitCtx, task, logger, session, stageResults });
|
|
579
|
-
if (!earlyPr) return;
|
|
580
|
-
|
|
581
|
-
// Accumulate commits for PG card lifecycle tracking
|
|
582
|
-
const { accumulateCommit } = await import("./planning-game/pipeline-adapter.js");
|
|
583
|
-
for (const c of earlyPr.commits) accumulateCommit(session, c);
|
|
584
|
-
|
|
585
|
-
session.becaria_pr_number = earlyPr.prNumber;
|
|
586
|
-
session.becaria_pr_url = earlyPr.prUrl;
|
|
587
|
-
session.becaria_commits = earlyPr.commits;
|
|
588
|
-
await saveSession(session);
|
|
589
|
-
emitProgress(emitter, makeEvent("becaria:pr-created", { ...eventBase, stage: "becaria" }, {
|
|
590
|
-
message: `Early PR created: #${earlyPr.prNumber}`,
|
|
591
|
-
detail: { prNumber: earlyPr.prNumber, prUrl: earlyPr.prUrl }
|
|
592
|
-
}));
|
|
593
|
-
|
|
594
|
-
if (!repo) return;
|
|
595
|
-
await dispatchComment({
|
|
596
|
-
repo, prNumber: earlyPr.prNumber, agent: "Coder",
|
|
597
|
-
body: `Iteración ${i} completada.\n\nCommits:\n${formatCommitList(earlyPr.commits)}`,
|
|
598
|
-
becariaConfig: config.becaria
|
|
189
|
+
// Solomon couldn't resolve — pause
|
|
190
|
+
await pauseSession(session, {
|
|
191
|
+
question: `Agent "${agent}" rate-limited. Solomon could not resolve.`,
|
|
192
|
+
context: { iteration: i, stage, reason: "rate_limit" }
|
|
599
193
|
});
|
|
194
|
+
return {
|
|
195
|
+
handled: true,
|
|
196
|
+
action: "return",
|
|
197
|
+
result: { paused: true, sessionId: session.id, question: `Agent "${agent}" rate-limited`, context: "rate_limit" }
|
|
198
|
+
};
|
|
600
199
|
}
|
|
601
200
|
|
|
602
|
-
async function handleBecariaEarlyPrOrPush({ becariaEnabled, config, session, emitter, eventBase, gitCtx, task, logger, stageResults, i }) {
|
|
603
|
-
if (!becariaEnabled) return;
|
|
604
|
-
|
|
605
|
-
try {
|
|
606
|
-
const { dispatchComment } = await import("./becaria/dispatch.js");
|
|
607
|
-
const { detectRepo } = await import("./becaria/repo.js");
|
|
608
|
-
const repo = await detectRepo();
|
|
609
|
-
|
|
610
|
-
if (session.becaria_pr_number) {
|
|
611
|
-
await becariaIncrementalPush({ config, session, gitCtx, task, logger, repo, dispatchComment });
|
|
612
|
-
} else {
|
|
613
|
-
await becariaCreateEarlyPr({ config, session, emitter, eventBase, gitCtx, task, logger, stageResults, i, repo, dispatchComment });
|
|
614
|
-
}
|
|
615
|
-
} catch (err) {
|
|
616
|
-
logger.warn(`BecarIA early PR/push failed (non-blocking): ${err.message}`);
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
201
|
|
|
620
202
|
function emitSolomonAlerts(alerts, emitter, eventBase, logger) {
|
|
621
203
|
for (const alert of alerts) {
|
|
@@ -628,7 +210,7 @@ function emitSolomonAlerts(alerts, emitter, eventBase, logger) {
|
|
|
628
210
|
}
|
|
629
211
|
}
|
|
630
212
|
|
|
631
|
-
async function handleSolomonCheck({ config, session, emitter, eventBase, logger, task, i, askQuestion,
|
|
213
|
+
async function handleSolomonCheck({ config, session, emitter, eventBase, logger, task, i, askQuestion, ciEnabled, blockingIssues }) {
|
|
632
214
|
if (config.pipeline?.solomon?.enabled === false) return { action: "continue" };
|
|
633
215
|
|
|
634
216
|
try {
|
|
@@ -642,15 +224,15 @@ async function handleSolomonCheck({ config, session, emitter, eventBase, logger,
|
|
|
642
224
|
if (pauseResult) return pauseResult;
|
|
643
225
|
}
|
|
644
226
|
|
|
645
|
-
if (
|
|
227
|
+
if (ciEnabled && session.ci_pr_number) {
|
|
646
228
|
const alerts = rulesResult.alerts || [];
|
|
647
229
|
const alertMsg = alerts.length > 0
|
|
648
230
|
? alerts.map(a => `- [${a.severity}] ${a.message}`).join("\n")
|
|
649
231
|
: "No anomalies detected";
|
|
650
|
-
await
|
|
232
|
+
await tryCiComment({
|
|
651
233
|
config, session, logger,
|
|
652
234
|
agent: "Solomon",
|
|
653
|
-
body: `Supervisor check
|
|
235
|
+
body: `Supervisor check iteration ${i}: ${alertMsg}`
|
|
654
236
|
});
|
|
655
237
|
}
|
|
656
238
|
} catch (err) {
|
|
@@ -693,59 +275,8 @@ async function checkSolomonCriticalAlerts({ rulesResult, askQuestion, session, i
|
|
|
693
275
|
return null;
|
|
694
276
|
}
|
|
695
277
|
|
|
696
|
-
function formatBlockingIssues(issues) {
|
|
697
|
-
return issues?.map((x) => `- ${x.id || "ISSUE"} [${x.severity || ""}] ${x.description}`).join("\n") || "";
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
function formatSuggestions(suggestions) {
|
|
701
|
-
return suggestions?.map((s) => {
|
|
702
|
-
const detail = typeof s === "string" ? s : `${s.id || ""} ${s.description || s}`;
|
|
703
|
-
return `- ${detail}`;
|
|
704
|
-
}).join("\n") || "";
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
function buildReviewCommentBody(review, i) {
|
|
708
|
-
const status = review.approved ? "APPROVED" : "REQUEST_CHANGES";
|
|
709
|
-
const blocking = formatBlockingIssues(review.blocking_issues);
|
|
710
|
-
const suggestions = formatSuggestions(review.non_blocking_suggestions);
|
|
711
|
-
let body = `Review iteración ${i}: ${status}`;
|
|
712
|
-
if (blocking) body += `\n\n**Blocking:**\n${blocking}`;
|
|
713
|
-
if (suggestions) body += `\n\n**Suggestions:**\n${suggestions}`;
|
|
714
|
-
return body;
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
async function handleBecariaReviewDispatch({ becariaEnabled, config, session, review, i, logger }) {
|
|
718
|
-
if (!becariaEnabled || !session.becaria_pr_number) return;
|
|
719
|
-
|
|
720
|
-
try {
|
|
721
|
-
const { dispatchReview, dispatchComment } = await import("./becaria/dispatch.js");
|
|
722
|
-
const { detectRepo } = await import("./becaria/repo.js");
|
|
723
|
-
const repo = await detectRepo();
|
|
724
|
-
if (!repo) return;
|
|
725
|
-
|
|
726
|
-
const bc = config.becaria;
|
|
727
|
-
const reviewEvent = review.approved ? "APPROVE" : "REQUEST_CHANGES";
|
|
728
|
-
const reviewBody = review.approved
|
|
729
|
-
? (review.summary || "Approved")
|
|
730
|
-
: (formatBlockingIssues(review.blocking_issues) || review.summary || "Changes requested");
|
|
731
|
-
|
|
732
|
-
await dispatchReview({
|
|
733
|
-
repo, prNumber: session.becaria_pr_number,
|
|
734
|
-
event: reviewEvent, body: reviewBody, agent: "Reviewer", becariaConfig: bc
|
|
735
|
-
});
|
|
736
|
-
|
|
737
|
-
await dispatchComment({
|
|
738
|
-
repo, prNumber: session.becaria_pr_number, agent: "Reviewer",
|
|
739
|
-
body: buildReviewCommentBody(review, i), becariaConfig: bc
|
|
740
|
-
});
|
|
741
|
-
|
|
742
|
-
logger.info(`BecarIA: dispatched review for PR #${session.becaria_pr_number}`);
|
|
743
|
-
} catch (err) {
|
|
744
|
-
logger.warn(`BecarIA dispatch failed (non-blocking): ${err.message}`);
|
|
745
|
-
}
|
|
746
|
-
}
|
|
747
278
|
|
|
748
|
-
async function handlePostLoopStages({ config, session, emitter, eventBase, coderRole, trackBudget, i, task, stageResults,
|
|
279
|
+
async function handlePostLoopStages({ config, session, emitter, eventBase, coderRole, trackBudget, i, task, stageResults, ciEnabled, testerEnabled, securityEnabled, askQuestion, logger }) {
|
|
749
280
|
const postLoopDiff = await generateDiff({ baseRef: session.session_start_sha });
|
|
750
281
|
|
|
751
282
|
if (testerEnabled) {
|
|
@@ -754,10 +285,16 @@ async function handlePostLoopStages({ config, session, emitter, eventBase, coder
|
|
|
754
285
|
iteration: i, task, diff: postLoopDiff, askQuestion
|
|
755
286
|
});
|
|
756
287
|
if (testerResult.action === "pause") return { action: "return", result: testerResult.result };
|
|
757
|
-
if (testerResult.action === "continue")
|
|
288
|
+
if (testerResult.action === "continue") {
|
|
289
|
+
const summary = testerResult.stageResult?.summary || "Tester found issues";
|
|
290
|
+
session.last_reviewer_feedback = `Tester FAILED — fix these issues:\n${summary}`;
|
|
291
|
+
await saveSession(session);
|
|
292
|
+
if (testerResult.stageResult) stageResults.tester = testerResult.stageResult;
|
|
293
|
+
return { action: "continue" };
|
|
294
|
+
}
|
|
758
295
|
if (testerResult.stageResult) {
|
|
759
296
|
stageResults.tester = testerResult.stageResult;
|
|
760
|
-
await
|
|
297
|
+
await tryCiComment({ config, session, logger, agent: "Tester", body: `Tests: ${testerResult.stageResult.summary || "completed"}` });
|
|
761
298
|
}
|
|
762
299
|
}
|
|
763
300
|
|
|
@@ -767,10 +304,16 @@ async function handlePostLoopStages({ config, session, emitter, eventBase, coder
|
|
|
767
304
|
iteration: i, task, diff: postLoopDiff, askQuestion
|
|
768
305
|
});
|
|
769
306
|
if (securityResult.action === "pause") return { action: "return", result: securityResult.result };
|
|
770
|
-
if (securityResult.action === "continue")
|
|
307
|
+
if (securityResult.action === "continue") {
|
|
308
|
+
const summary = securityResult.stageResult?.summary || "Security found issues";
|
|
309
|
+
session.last_reviewer_feedback = `Security FAILED — fix these issues:\n${summary}`;
|
|
310
|
+
await saveSession(session);
|
|
311
|
+
if (securityResult.stageResult) stageResults.security = securityResult.stageResult;
|
|
312
|
+
return { action: "continue" };
|
|
313
|
+
}
|
|
771
314
|
if (securityResult.stageResult) {
|
|
772
315
|
stageResults.security = securityResult.stageResult;
|
|
773
|
-
await
|
|
316
|
+
await tryCiComment({ config, session, logger, agent: "Security", body: `Security scan: ${securityResult.stageResult.summary || "completed"}` });
|
|
774
317
|
}
|
|
775
318
|
}
|
|
776
319
|
|
|
@@ -781,7 +324,7 @@ async function handlePostLoopStages({ config, session, emitter, eventBase, coder
|
|
|
781
324
|
});
|
|
782
325
|
if (auditResult.stageResult) {
|
|
783
326
|
stageResults.audit = auditResult.stageResult;
|
|
784
|
-
await
|
|
327
|
+
await tryCiComment({ config, session, logger, agent: "Audit", body: `Final audit: ${auditResult.stageResult.summary || "completed"}` });
|
|
785
328
|
}
|
|
786
329
|
if (auditResult.action === "retry") {
|
|
787
330
|
// Audit found actionable issues — loop back to coder
|
|
@@ -816,6 +359,31 @@ async function finalizeApprovedSession({ config, gitCtx, task, logger, session,
|
|
|
816
359
|
if (rtkSavings) session.rtk_savings = rtkSavings;
|
|
817
360
|
await saveSession(session);
|
|
818
361
|
|
|
362
|
+
// --- Journal: write final files ---
|
|
363
|
+
const journalDir = session._journalDir;
|
|
364
|
+
if (journalDir) {
|
|
365
|
+
try {
|
|
366
|
+
await writeIterationsJournal(journalDir, session._journalIterations || []);
|
|
367
|
+
await writeDecisionsJournal(journalDir, session._journalDecisions || []);
|
|
368
|
+
const hasTree = await writeTreeJournal(journalDir, session.session_start_sha);
|
|
369
|
+
|
|
370
|
+
const journalFiles = [...(session._journalFiles || [])];
|
|
371
|
+
if (session._journalIterations?.length) journalFiles.push("iterations.md");
|
|
372
|
+
if (session._journalDecisions?.length) journalFiles.push("decisions.md");
|
|
373
|
+
if (hasTree) journalFiles.push("tree.txt");
|
|
374
|
+
|
|
375
|
+
await writeSummaryJournal(journalDir, {
|
|
376
|
+
task: session.task, result: "APPROVED", sessionId: session.id,
|
|
377
|
+
iterations: i, durationMs: Date.now() - (session._startedAt || Date.now()),
|
|
378
|
+
budget: budgetSummary(), stages: stageResults,
|
|
379
|
+
commits: gitResult?.commits || [], files: journalFiles
|
|
380
|
+
});
|
|
381
|
+
logger.info(`Session journal written to ${journalDir}`);
|
|
382
|
+
} catch (err) {
|
|
383
|
+
logger.warn(`Journal write failed (non-blocking): ${err.message}`);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
819
387
|
const endDetail = { approved: true, iterations: i, stages: stageResults, git: gitResult, budget: budgetSummary(), deferredIssues };
|
|
820
388
|
if (rtkSavings) endDetail.rtk_savings = rtkSavings;
|
|
821
389
|
emitProgress(
|
|
@@ -836,8 +404,13 @@ async function finalizeApprovedSession({ config, gitCtx, task, logger, session,
|
|
|
836
404
|
|
|
837
405
|
async function handleReviewerRetryAndSolomon({ config, session, emitter, eventBase, logger, review, task, i, askQuestion }) {
|
|
838
406
|
session.last_reviewer_feedback = review.blocking_issues
|
|
839
|
-
.map((x) =>
|
|
840
|
-
|
|
407
|
+
.map((x) => {
|
|
408
|
+
const parts = [`[${x.severity || "high"}] ${x.id || "ISSUE"}: ${x.description || "Missing description"}`];
|
|
409
|
+
if (x.file) parts.push(` File: ${x.file}${x.line ? `:${x.line}` : ""}`);
|
|
410
|
+
if (x.suggested_fix) parts.push(` Fix: ${x.suggested_fix}`);
|
|
411
|
+
return parts.join("\n");
|
|
412
|
+
})
|
|
413
|
+
.join("\n\n");
|
|
841
414
|
session.reviewer_retry_count = (session.reviewer_retry_count || 0) + 1;
|
|
842
415
|
await saveSession(session);
|
|
843
416
|
|
|
@@ -917,27 +490,13 @@ async function runPreLoopStages({ config, logger, emitter, eventBase, session, f
|
|
|
917
490
|
applyTriageOverrides(pipelineFlags, triageResult.roleOverrides);
|
|
918
491
|
stageResults.triage = triageResult.stageResult;
|
|
919
492
|
|
|
920
|
-
// ---
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
if (neededSkills.length > 0) {
|
|
927
|
-
const skillResult = await autoInstallSkills(neededSkills, projectDir);
|
|
928
|
-
if (skillResult.installed.length > 0) {
|
|
929
|
-
session.autoInstalledSkills = skillResult.installed;
|
|
930
|
-
}
|
|
931
|
-
emitProgress(emitter, makeEvent("skills:auto-install", { ...eventBase, stage: "triage" }, {
|
|
932
|
-
message: skillResult.installed.length > 0
|
|
933
|
-
? `Auto-installed ${skillResult.installed.length} skill(s): ${skillResult.installed.join(", ")}`
|
|
934
|
-
: `Skills detected (${neededSkills.join(", ")}) — all already installed or unavailable`,
|
|
935
|
-
detail: skillResult
|
|
936
|
-
}));
|
|
937
|
-
}
|
|
493
|
+
// --- Persist inline domain if provided via --domain flag ---
|
|
494
|
+
if (flags?.domain) {
|
|
495
|
+
try {
|
|
496
|
+
await persistInlineDomain(flags.domain, config.projectDir || process.cwd());
|
|
497
|
+
} catch (err) {
|
|
498
|
+
logger.warn(`Failed to persist inline domain: ${err.message}`);
|
|
938
499
|
}
|
|
939
|
-
} catch (err) {
|
|
940
|
-
logger.warn(`Skill auto-install failed (non-blocking): ${err.message}`);
|
|
941
500
|
}
|
|
942
501
|
|
|
943
502
|
// --- Domain Curator (after triage + skill auto-install, before planning phases) ---
|
|
@@ -1069,13 +628,43 @@ async function runPreLoopStages({ config, logger, emitter, eventBase, session, f
|
|
|
1069
628
|
// --- Researcher → Planner ---
|
|
1070
629
|
const { plannedTask } = await runPlanningPhases({ config: updatedConfig, logger, emitter, eventBase, session, stageResults, pipelineFlags, coderRole, trackBudget, task, askQuestion });
|
|
1071
630
|
|
|
631
|
+
// --- Update .gitignore with stack-specific entries based on planner/architect output ---
|
|
632
|
+
const projectDir = updatedConfig.projectDir || process.cwd();
|
|
633
|
+
await updateGitignoreForStack(projectDir, { stageResults, task, logger });
|
|
634
|
+
|
|
635
|
+
// --- Auto-install skills based on task + planner output + project detection ---
|
|
636
|
+
// Runs AFTER triage and planner so that the planned task text (which includes
|
|
637
|
+
// planner output like implementation steps) is available for keyword detection.
|
|
638
|
+
// This ensures greenfield projects with no package.json still get correct skills.
|
|
639
|
+
const skillProjectDir = updatedConfig.projectDir || process.cwd();
|
|
640
|
+
try {
|
|
641
|
+
const osAvailable = await isOpenSkillsAvailable();
|
|
642
|
+
if (osAvailable) {
|
|
643
|
+
const neededSkills = await detectNeededSkills(plannedTask, skillProjectDir);
|
|
644
|
+
if (neededSkills.length > 0) {
|
|
645
|
+
const skillResult = await autoInstallSkills(neededSkills, skillProjectDir);
|
|
646
|
+
if (skillResult.installed.length > 0) {
|
|
647
|
+
session.autoInstalledSkills = skillResult.installed;
|
|
648
|
+
}
|
|
649
|
+
emitProgress(emitter, makeEvent("skills:auto-install", { ...eventBase, stage: "skills" }, {
|
|
650
|
+
message: skillResult.installed.length > 0
|
|
651
|
+
? `Auto-installed ${skillResult.installed.length} skill(s): ${skillResult.installed.join(", ")}`
|
|
652
|
+
: `Skills detected (${neededSkills.join(", ")}) — all already installed or unavailable`,
|
|
653
|
+
detail: skillResult
|
|
654
|
+
}));
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
} catch (err) {
|
|
658
|
+
logger.warn(`Skill auto-install failed (non-blocking): ${err.message}`);
|
|
659
|
+
}
|
|
660
|
+
|
|
1072
661
|
return { plannedTask, updatedConfig };
|
|
1073
662
|
}
|
|
1074
663
|
|
|
1075
664
|
async function runCoderAndRefactorerStages({ coderRoleInstance, coderRole, refactorerRole, pipelineFlags, config, logger, emitter, eventBase, session, plannedTask, trackBudget, i }) {
|
|
1076
665
|
const coderResult = await runCoderStage({ coderRoleInstance, coderRole, config, logger, emitter, eventBase, session, plannedTask, trackBudget, iteration: i });
|
|
1077
666
|
if (coderResult?.action === "pause") return { action: "return", result: coderResult.result };
|
|
1078
|
-
const coderStandby = await handleStandbyResult({ stageResult: coderResult, session, emitter, eventBase, i, stage: "coder", logger });
|
|
667
|
+
const coderStandby = await handleStandbyResult({ stageResult: coderResult, session, emitter, eventBase, i, stage: "coder", logger, config });
|
|
1079
668
|
if (coderStandby.handled) {
|
|
1080
669
|
return coderStandby.action === "return"
|
|
1081
670
|
? { action: "return", result: coderStandby.result }
|
|
@@ -1085,7 +674,7 @@ async function runCoderAndRefactorerStages({ coderRoleInstance, coderRole, refac
|
|
|
1085
674
|
if (pipelineFlags.refactorerEnabled) {
|
|
1086
675
|
const refResult = await runRefactorerStage({ refactorerRole, config, logger, emitter, eventBase, session, plannedTask, trackBudget, iteration: i });
|
|
1087
676
|
if (refResult?.action === "pause") return { action: "return", result: refResult.result };
|
|
1088
|
-
const refStandby = await handleStandbyResult({ stageResult: refResult, session, emitter, eventBase, i, stage: "refactorer", logger });
|
|
677
|
+
const refStandby = await handleStandbyResult({ stageResult: refResult, session, emitter, eventBase, i, stage: "refactorer", logger, config });
|
|
1089
678
|
if (refStandby.handled) {
|
|
1090
679
|
return refStandby.action === "return"
|
|
1091
680
|
? { action: "return", result: refStandby.result }
|
|
@@ -1178,7 +767,7 @@ async function runQualityGateStages({ config, logger, emitter, eventBase, sessio
|
|
|
1178
767
|
if (sonarResult.action === "continue") return { action: "continue" };
|
|
1179
768
|
if (sonarResult.stageResult) {
|
|
1180
769
|
stageResults.sonar = sonarResult.stageResult;
|
|
1181
|
-
await
|
|
770
|
+
await tryCiComment({ config, session, logger, agent: "Sonar", body: `SonarQube scan: ${sonarResult.stageResult.summary || "completed"}` });
|
|
1182
771
|
}
|
|
1183
772
|
}
|
|
1184
773
|
|
|
@@ -1219,11 +808,26 @@ async function runReviewerGateStage({ pipelineFlags, reviewerRole, config, logge
|
|
|
1219
808
|
iteration: i, reviewRules, task, repeatDetector, budgetSummary, askQuestion
|
|
1220
809
|
});
|
|
1221
810
|
if (reviewerResult.action === "pause") return { action: "return", result: reviewerResult.result };
|
|
1222
|
-
const revStandby = await handleStandbyResult({ stageResult: reviewerResult, session, emitter, eventBase, i, stage: "reviewer", logger });
|
|
811
|
+
const revStandby = await handleStandbyResult({ stageResult: reviewerResult, session, emitter, eventBase, i, stage: "reviewer", logger, config, askQuestion });
|
|
1223
812
|
if (revStandby.handled) {
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
813
|
+
if (revStandby.action === "return") return { action: "return", result: revStandby.result };
|
|
814
|
+
if (revStandby.action === "skip") {
|
|
815
|
+
// Solomon said skip review — treat as approved
|
|
816
|
+
return { action: "ok", review: { approved: true, blocking_issues: [], non_blocking_suggestions: [], summary: "Review skipped (agent rate-limited, Solomon approved)", confidence: 0.7 } };
|
|
817
|
+
}
|
|
818
|
+
if (revStandby.action === "retry_reviewer_only") {
|
|
819
|
+
// Retry just the reviewer — use alternative agent if Solomon recommended one
|
|
820
|
+
let retryReviewerRole = reviewerRole;
|
|
821
|
+
const alt = session._alternative_agent;
|
|
822
|
+
if (alt?.stage === "reviewer" && alt?.provider) {
|
|
823
|
+
const { createAgent } = await import("./agents/index.js");
|
|
824
|
+
retryReviewerRole = { provider: alt.provider, model: null };
|
|
825
|
+
logger.info(`Retrying reviewer with alternative agent: ${alt.provider}`);
|
|
826
|
+
delete session._alternative_agent;
|
|
827
|
+
}
|
|
828
|
+
return runReviewerGateStage({ pipelineFlags: { reviewerEnabled: true }, reviewerRole: retryReviewerRole, config, logger, emitter, eventBase, session, trackBudget, i, reviewRules, task, repeatDetector, budgetSummary, askQuestion });
|
|
829
|
+
}
|
|
830
|
+
return { action: "retry" };
|
|
1227
831
|
}
|
|
1228
832
|
if (reviewerResult.stalled) return { action: "return", result: reviewerResult.stalledResult };
|
|
1229
833
|
return { action: "ok", review: reviewerResult.review };
|
|
@@ -1233,7 +837,7 @@ async function handleApprovedReview({ config, session, emitter, eventBase, coder
|
|
|
1233
837
|
session.reviewer_retry_count = 0;
|
|
1234
838
|
const postLoopResult = await handlePostLoopStages({
|
|
1235
839
|
config, session, emitter, eventBase, coderRole, trackBudget, i, task, stageResults,
|
|
1236
|
-
|
|
840
|
+
ciEnabled: Boolean(config.ci?.enabled), testerEnabled: pipelineFlags.testerEnabled, securityEnabled: pipelineFlags.securityEnabled, askQuestion, logger
|
|
1237
841
|
});
|
|
1238
842
|
if (postLoopResult.action === "return") return { action: "return", result: postLoopResult.result };
|
|
1239
843
|
if (postLoopResult.action === "continue") return { action: "continue" };
|
|
@@ -1243,7 +847,7 @@ async function handleApprovedReview({ config, session, emitter, eventBase, coder
|
|
|
1243
847
|
}
|
|
1244
848
|
|
|
1245
849
|
async function handleMaxIterationsReached({ session, budgetSummary, emitter, eventBase, config, stageResults, logger, askQuestion, task, rtkTracker }) {
|
|
1246
|
-
|
|
850
|
+
const budget = budgetSummary();
|
|
1247
851
|
const solomonResult = await invokeSolomon({
|
|
1248
852
|
config, logger, emitter, eventBase, stage: "max_iterations", askQuestion, session,
|
|
1249
853
|
iteration: config.max_iterations,
|
|
@@ -1252,17 +856,26 @@ async function handleMaxIterationsReached({ session, budgetSummary, emitter, eve
|
|
|
1252
856
|
task,
|
|
1253
857
|
iterationCount: config.max_iterations,
|
|
1254
858
|
maxIterations: config.max_iterations,
|
|
859
|
+
budget_usd: budget?.total_cost_usd || 0,
|
|
1255
860
|
history: [{ agent: "pipeline", feedback: session.last_reviewer_feedback || "Max iterations reached without reviewer approval" }]
|
|
1256
861
|
}
|
|
1257
862
|
});
|
|
1258
863
|
|
|
864
|
+
// Solomon approved the work — treat as successful completion
|
|
865
|
+
if (solomonResult.action === "approve") {
|
|
866
|
+
logger.info("Solomon approved coder's work at max_iterations checkpoint");
|
|
867
|
+
return { approved: true, sessionId: session.id, reason: "solomon_approved" };
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// Solomon says continue — extend iterations
|
|
1259
871
|
if (solomonResult.action === "continue") {
|
|
1260
872
|
if (solomonResult.humanGuidance) {
|
|
1261
|
-
session.last_reviewer_feedback = `
|
|
873
|
+
session.last_reviewer_feedback = `Solomon guidance: ${solomonResult.humanGuidance}`;
|
|
1262
874
|
}
|
|
1263
875
|
session.reviewer_retry_count = 0;
|
|
1264
876
|
await saveSession(session);
|
|
1265
|
-
|
|
877
|
+
const extraIterations = solomonResult.extraIterations || config.max_iterations;
|
|
878
|
+
return { approved: false, sessionId: session.id, reason: "max_iterations_extended", humanGuidance: solomonResult.humanGuidance, extraIterations };
|
|
1266
879
|
}
|
|
1267
880
|
|
|
1268
881
|
if (solomonResult.action === "pause") {
|
|
@@ -1273,7 +886,7 @@ async function handleMaxIterationsReached({ session, budgetSummary, emitter, eve
|
|
|
1273
886
|
return { paused: true, sessionId: session.id, subtask: solomonResult.subtask, context: "max_iterations_subtask" };
|
|
1274
887
|
}
|
|
1275
888
|
|
|
1276
|
-
// Solomon
|
|
889
|
+
// Solomon couldn't resolve — fail
|
|
1277
890
|
session.budget = budgetSummary();
|
|
1278
891
|
const rtkSavings = rtkTracker?.hasData() ? rtkTracker.summary() : undefined;
|
|
1279
892
|
if (rtkSavings) session.rtk_savings = rtkSavings;
|
|
@@ -1310,6 +923,44 @@ async function tryAutoStartBoard(config, logger, emitter, eventBase) {
|
|
|
1310
923
|
}
|
|
1311
924
|
|
|
1312
925
|
async function initFlowContext({ task, config, logger, emitter, askQuestion, pgTaskId, pgProject, flags }) {
|
|
926
|
+
// Auto-init .karajan/ if missing (copies coder-rules, review-rules, role templates)
|
|
927
|
+
const initProjectDir = config.projectDir || process.cwd();
|
|
928
|
+
await autoInit(initProjectDir, logger);
|
|
929
|
+
|
|
930
|
+
// Smart role assignment: detect installed AIs and assign to roles
|
|
931
|
+
// Only runs if: (a) no roles configured AND (b) not in test environment
|
|
932
|
+
const needsAssignment = !config.roles?.coder?.provider && !config.coder && process.env.NODE_ENV !== "test" && !process.env.VITEST;
|
|
933
|
+
if (needsAssignment) {
|
|
934
|
+
try {
|
|
935
|
+
const { autoAssignRoles, applyRoleAssignments } = await import("./utils/role-assigner.js");
|
|
936
|
+
const { assignments } = await autoAssignRoles(logger);
|
|
937
|
+
if (assignments) config = applyRoleAssignments({ ...config }, assignments);
|
|
938
|
+
} catch { /* non-blocking — defaults will be used */ }
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
// Scope all git diffs to projectDir (prevents leaking unrelated branch changes)
|
|
942
|
+
// When running from a subdirectory of a git repo, use relative path as scope
|
|
943
|
+
let diffScope = config.projectDir || null;
|
|
944
|
+
if (!diffScope) {
|
|
945
|
+
try {
|
|
946
|
+
const { execSync } = await import("node:child_process");
|
|
947
|
+
const repoRoot = execSync("git rev-parse --show-toplevel", { encoding: "utf8" }).trim();
|
|
948
|
+
const cwd = process.cwd();
|
|
949
|
+
if (cwd !== repoRoot && cwd.startsWith(repoRoot)) {
|
|
950
|
+
diffScope = cwd.slice(repoRoot.length + 1);
|
|
951
|
+
logger.info(`Running from subdirectory — diff scoped to ${diffScope}/`);
|
|
952
|
+
}
|
|
953
|
+
} catch { /* git not available */ }
|
|
954
|
+
}
|
|
955
|
+
setDiffProjectDir(diffScope);
|
|
956
|
+
|
|
957
|
+
// Auto-detect Chrome DevTools MCP
|
|
958
|
+
const { detectDevToolsMcp } = await import("./webperf/devtools-detect.js");
|
|
959
|
+
const devToolsAvailable = await detectDevToolsMcp(logger);
|
|
960
|
+
if (devToolsAvailable) {
|
|
961
|
+
config = { ...config, webperf: { ...config.webperf, devtools_mcp: true } };
|
|
962
|
+
}
|
|
963
|
+
|
|
1313
964
|
const ctx = new PipelineContext({ config, session: null, logger, emitter, task, flags });
|
|
1314
965
|
ctx.askQuestion = askQuestion;
|
|
1315
966
|
ctx.pgTaskId = pgTaskId;
|
|
@@ -1330,23 +981,19 @@ async function initFlowContext({ task, config, logger, emitter, askQuestion, pgT
|
|
|
1330
981
|
ctx.trackBudget = trackBudget;
|
|
1331
982
|
|
|
1332
983
|
// --- RTK detection ---
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
const
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
message: "RTK detected — internal commands wrapped for token optimization",
|
|
1347
|
-
detail: { version: rtkResult.version, executorType: "local" }
|
|
1348
|
-
}));
|
|
1349
|
-
}
|
|
984
|
+
const rtkResult = await detectRtk();
|
|
985
|
+
if (rtkResult.available) {
|
|
986
|
+
config = { ...config, rtk: { available: true, version: rtkResult.version } };
|
|
987
|
+
const rtkTracker = new RtkSavingsTracker();
|
|
988
|
+
const rtkRunner = createRtkRunner(true, rtkTracker);
|
|
989
|
+
setDiffRunner(rtkRunner);
|
|
990
|
+
setGitRunner(rtkRunner);
|
|
991
|
+
ctx.rtkTracker = rtkTracker;
|
|
992
|
+
logger.info(`RTK detected (${rtkResult.version}) — wrapping internal git/diff commands with rtk`);
|
|
993
|
+
emitProgress(emitter, makeEvent("rtk:detected", ctx.eventBase, {
|
|
994
|
+
message: "RTK detected — internal commands wrapped for token optimization",
|
|
995
|
+
detail: { version: rtkResult.version, executorType: "local" }
|
|
996
|
+
}));
|
|
1350
997
|
}
|
|
1351
998
|
|
|
1352
999
|
// --- HU Board auto-start ---
|
|
@@ -1372,21 +1019,6 @@ async function initFlowContext({ task, config, logger, emitter, askQuestion, pgT
|
|
|
1372
1019
|
ctx.pgCard = pgAdapterResult.pgCard;
|
|
1373
1020
|
ctx.session.pg_card = ctx.pgCard || null;
|
|
1374
1021
|
|
|
1375
|
-
// --- Proxy startup ---
|
|
1376
|
-
if (config.proxy?.enabled !== false) {
|
|
1377
|
-
try {
|
|
1378
|
-
const proxyResult = await startProxy({ config: config.proxy || {}, sessionId: ctx.session.id });
|
|
1379
|
-
ctx.proxyPort = proxyResult.port;
|
|
1380
|
-
logger.info(`Proxy started on 127.0.0.1:${proxyResult.port}`);
|
|
1381
|
-
emitProgress(emitter, makeEvent("proxy:started", ctx.eventBase, {
|
|
1382
|
-
message: `Proxy started on port ${proxyResult.port}`,
|
|
1383
|
-
detail: { port: proxyResult.port, baseUrls: proxyResult.baseUrls }
|
|
1384
|
-
}));
|
|
1385
|
-
} catch (err) {
|
|
1386
|
-
logger.warn(`Proxy startup failed (non-blocking): ${err.message}`);
|
|
1387
|
-
}
|
|
1388
|
-
}
|
|
1389
|
-
|
|
1390
1022
|
emitProgress(
|
|
1391
1023
|
emitter,
|
|
1392
1024
|
makeEvent("session:start", ctx.eventBase, {
|
|
@@ -1402,6 +1034,38 @@ async function initFlowContext({ task, config, logger, emitter, askQuestion, pgT
|
|
|
1402
1034
|
ctx.plannedTask = preLoopResult.plannedTask;
|
|
1403
1035
|
ctx.config = preLoopResult.updatedConfig;
|
|
1404
1036
|
|
|
1037
|
+
// --- Session Journal: persist pre-loop outputs + display plan summary ---
|
|
1038
|
+
const reportDir = ctx.config.output?.report_dir || ".reviews";
|
|
1039
|
+
try {
|
|
1040
|
+
ctx.journalDir = await createJournalDir(reportDir, ctx.session.id);
|
|
1041
|
+
const journalFiles = await writePreLoopJournal(ctx.journalDir, ctx.stageResults);
|
|
1042
|
+
ctx.journalFiles = journalFiles;
|
|
1043
|
+
ctx.journalIterations = [];
|
|
1044
|
+
ctx.journalDecisions = [];
|
|
1045
|
+
|
|
1046
|
+
// Attach journal state to session so finalizeApprovedSession can access it
|
|
1047
|
+
ctx.session._journalDir = ctx.journalDir;
|
|
1048
|
+
ctx.session._journalFiles = journalFiles;
|
|
1049
|
+
ctx.session._journalIterations = ctx.journalIterations;
|
|
1050
|
+
ctx.session._journalDecisions = ctx.journalDecisions;
|
|
1051
|
+
ctx.session._startedAt = ctx.startedAt;
|
|
1052
|
+
|
|
1053
|
+
// Display plan summary in console before iteration loop
|
|
1054
|
+
const planSummary = buildPlanSummary({
|
|
1055
|
+
pipelineFlags: ctx.pipelineFlags,
|
|
1056
|
+
config: ctx.config,
|
|
1057
|
+
stageResults: ctx.stageResults,
|
|
1058
|
+
task
|
|
1059
|
+
});
|
|
1060
|
+
console.log(planSummary);
|
|
1061
|
+
} catch (err) {
|
|
1062
|
+
logger.warn(`Journal init failed (non-blocking): ${err.message}`);
|
|
1063
|
+
ctx.journalDir = null;
|
|
1064
|
+
ctx.journalFiles = [];
|
|
1065
|
+
ctx.journalIterations = [];
|
|
1066
|
+
ctx.journalDecisions = [];
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1405
1069
|
ctx.gitCtx = await prepareGitAutomation({ config: ctx.config, task, logger, session: ctx.session });
|
|
1406
1070
|
const projectDir = ctx.config.projectDir || process.cwd();
|
|
1407
1071
|
ctx.reviewRules = (await resolveReviewProfile({ mode: ctx.config.review_mode, projectDir })).rules;
|
|
@@ -1414,7 +1078,7 @@ async function runSingleIteration(ctx) {
|
|
|
1414
1078
|
const { config, logger, emitter, eventBase, session, task, iteration: i } = ctx;
|
|
1415
1079
|
|
|
1416
1080
|
const iterStart = Date.now();
|
|
1417
|
-
const
|
|
1081
|
+
const ciEnabled = Boolean(config.ci?.enabled) && ctx.gitCtx?.enabled;
|
|
1418
1082
|
logger.setContext({ iteration: i, stage: "iteration" });
|
|
1419
1083
|
|
|
1420
1084
|
const reviewerRetryCount = session.reviewer_retry_count || 0;
|
|
@@ -1445,8 +1109,8 @@ async function runSingleIteration(ctx) {
|
|
|
1445
1109
|
});
|
|
1446
1110
|
if (qgResult.action === "return" || qgResult.action === "continue") return qgResult;
|
|
1447
1111
|
|
|
1448
|
-
await
|
|
1449
|
-
|
|
1112
|
+
await handleCiEarlyPrOrPush({
|
|
1113
|
+
ciEnabled, config, session, emitter, eventBase, gitCtx: ctx.gitCtx, task, logger,
|
|
1450
1114
|
stageResults: ctx.stageResults, i
|
|
1451
1115
|
});
|
|
1452
1116
|
|
|
@@ -1464,13 +1128,26 @@ async function runSingleIteration(ctx) {
|
|
|
1464
1128
|
}));
|
|
1465
1129
|
session.standby_retry_count = 0;
|
|
1466
1130
|
|
|
1131
|
+
// --- Journal: record iteration ---
|
|
1132
|
+
if (ctx.journalIterations) {
|
|
1133
|
+
ctx.journalIterations.push(formatIteration({
|
|
1134
|
+
iteration: i,
|
|
1135
|
+
coderSummary: ctx.stageResults.coder?.summary || null,
|
|
1136
|
+
reviewerSummary: review?.approved ? `Approved: ${review.raw_summary || ""}` : `Rejected: ${(review?.blocking_issues || []).length} blocking issue(s)`,
|
|
1137
|
+
sonarSummary: ctx.stageResults.sonar?.summary || null,
|
|
1138
|
+
testerSummary: ctx.stageResults.tester?.summary || null,
|
|
1139
|
+
securitySummary: ctx.stageResults.security?.summary || null,
|
|
1140
|
+
durationMs: iterDuration
|
|
1141
|
+
}));
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1467
1144
|
const solomonResult = await handleSolomonCheck({
|
|
1468
1145
|
config, session, emitter, eventBase, logger, task, i, askQuestion: ctx.askQuestion,
|
|
1469
|
-
|
|
1146
|
+
ciEnabled, blockingIssues: review?.blocking_issues
|
|
1470
1147
|
});
|
|
1471
1148
|
if (solomonResult.action === "pause") return { action: "return", result: solomonResult.result };
|
|
1472
1149
|
|
|
1473
|
-
await
|
|
1150
|
+
await handleCiReviewDispatch({ ciEnabled, config, session, review, i, logger });
|
|
1474
1151
|
|
|
1475
1152
|
if (review.approved) {
|
|
1476
1153
|
const approvedResult = await handleApprovedReview({
|
|
@@ -1490,8 +1167,13 @@ async function runSingleIteration(ctx) {
|
|
|
1490
1167
|
} else {
|
|
1491
1168
|
// Solomon is enabled — feed back the blocking issues for the next coder iteration
|
|
1492
1169
|
session.last_reviewer_feedback = review.blocking_issues
|
|
1493
|
-
.map((x) =>
|
|
1494
|
-
|
|
1170
|
+
.map((x) => {
|
|
1171
|
+
const parts = [`[${x.severity || "high"}] ${x.id || "ISSUE"}: ${x.description || "Missing description"}`];
|
|
1172
|
+
if (x.file) parts.push(` File: ${x.file}${x.line ? `:${x.line}` : ""}`);
|
|
1173
|
+
if (x.suggested_fix) parts.push(` Fix: ${x.suggested_fix}`);
|
|
1174
|
+
return parts.join("\n");
|
|
1175
|
+
})
|
|
1176
|
+
.join("\n\n");
|
|
1495
1177
|
await saveSession(session);
|
|
1496
1178
|
}
|
|
1497
1179
|
|
|
@@ -1549,14 +1231,92 @@ async function runIterationLoop(ctx, { task: loopTask, askQuestion, emitter, log
|
|
|
1549
1231
|
ctx.eventBase.iteration = i;
|
|
1550
1232
|
ctx.iteration = i;
|
|
1551
1233
|
|
|
1552
|
-
|
|
1234
|
+
let iterResult;
|
|
1235
|
+
try {
|
|
1236
|
+
iterResult = await runSingleIteration(ctx);
|
|
1237
|
+
} catch (stageError) {
|
|
1238
|
+
// ANY unhandled error in a stage = out of normal flow → Solomon decides
|
|
1239
|
+
logger.warn(`Stage error caught — escalating to Solomon: ${stageError.message}`);
|
|
1240
|
+
const solomonResult = await invokeSolomon({
|
|
1241
|
+
config: ctx.config, logger, emitter, eventBase: ctx.eventBase,
|
|
1242
|
+
stage: "stage_error", askQuestion, session: ctx.session, iteration: i,
|
|
1243
|
+
conflict: {
|
|
1244
|
+
stage: "stage_error",
|
|
1245
|
+
task: loopTask,
|
|
1246
|
+
iterationCount: i,
|
|
1247
|
+
maxIterations: ctx.config.max_iterations,
|
|
1248
|
+
budget_usd: ctx.budgetSummary()?.total_cost_usd || 0,
|
|
1249
|
+
history: [{ agent: "pipeline", feedback: `Stage threw: ${stageError.message}` }]
|
|
1250
|
+
}
|
|
1251
|
+
});
|
|
1252
|
+
|
|
1253
|
+
if (solomonResult.action === "approve") {
|
|
1254
|
+
logger.info("Solomon approved despite stage error");
|
|
1255
|
+
return { approved: true, sessionId: ctx.session.id, reason: "solomon_approved_after_error" };
|
|
1256
|
+
}
|
|
1257
|
+
if (solomonResult.action === "continue") {
|
|
1258
|
+
if (solomonResult.humanGuidance) {
|
|
1259
|
+
ctx.session.last_reviewer_feedback = `Solomon guidance: ${solomonResult.humanGuidance}`;
|
|
1260
|
+
}
|
|
1261
|
+
continue; // next iteration
|
|
1262
|
+
}
|
|
1263
|
+
if (solomonResult.action === "pause") {
|
|
1264
|
+
return { paused: true, sessionId: ctx.session.id, question: solomonResult.question, context: "stage_error" };
|
|
1265
|
+
}
|
|
1266
|
+
// Solomon couldn't resolve — fail
|
|
1267
|
+
await markSessionStatus(ctx.session, "failed");
|
|
1268
|
+
return { approved: false, sessionId: ctx.session.id, reason: "stage_error", error: stageError.message };
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1553
1271
|
if (iterResult.action === "return") {
|
|
1554
1272
|
return iterResult.result;
|
|
1555
1273
|
}
|
|
1556
1274
|
if (iterResult.action === "retry") { i -= 1; }
|
|
1557
1275
|
}
|
|
1558
1276
|
|
|
1277
|
+
// Solomon decides whether to extend iterations or stop
|
|
1559
1278
|
const maxIterResult = await handleMaxIterationsReached({ session: ctx.session, budgetSummary: ctx.budgetSummary, emitter, eventBase: ctx.eventBase, config: ctx.config, stageResults: ctx.stageResults, logger, askQuestion, task: loopTask, rtkTracker: ctx.rtkTracker });
|
|
1279
|
+
|
|
1280
|
+
// Solomon said "continue" — extend iterations and keep going
|
|
1281
|
+
if (maxIterResult.reason === "max_iterations_extended") {
|
|
1282
|
+
const extra = maxIterResult.extraIterations || ctx.config.max_iterations;
|
|
1283
|
+
ctx.config.max_iterations += extra;
|
|
1284
|
+
logger.info(`Solomon extended pipeline by ${extra} iterations (new max: ${ctx.config.max_iterations})`);
|
|
1285
|
+
|
|
1286
|
+
if (maxIterResult.humanGuidance) {
|
|
1287
|
+
ctx.session.last_reviewer_feedback = `Solomon guidance: ${maxIterResult.humanGuidance}`;
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
// Continue the loop
|
|
1291
|
+
while (i < ctx.config.max_iterations) {
|
|
1292
|
+
i += 1;
|
|
1293
|
+
const elapsedMinutes = (Date.now() - ctx.startedAt) / 60000;
|
|
1294
|
+
|
|
1295
|
+
const cpResult = await handleCheckpoint({
|
|
1296
|
+
checkpointDisabled, askQuestion, lastCheckpointAt, checkpointIntervalMs, elapsedMinutes,
|
|
1297
|
+
i, config: ctx.config, budgetTracker: ctx.budgetTracker, stageResults: ctx.stageResults, emitter, eventBase: ctx.eventBase, session: ctx.session, budgetSummary: ctx.budgetSummary, lastCheckpointSnapshot
|
|
1298
|
+
});
|
|
1299
|
+
if (cpResult.action === "stop") return cpResult.result;
|
|
1300
|
+
checkpointDisabled = cpResult.checkpointDisabled;
|
|
1301
|
+
lastCheckpointAt = cpResult.lastCheckpointAt;
|
|
1302
|
+
if (cpResult.lastCheckpointSnapshot !== undefined) lastCheckpointSnapshot = cpResult.lastCheckpointSnapshot;
|
|
1303
|
+
|
|
1304
|
+
await checkSessionTimeout({ askQuestion, elapsedMinutes, config: ctx.config, session: ctx.session, emitter, eventBase: ctx.eventBase, i, budgetSummary: ctx.budgetSummary });
|
|
1305
|
+
await checkBudgetExceeded({ budgetTracker: ctx.budgetTracker, config: ctx.config, session: ctx.session, emitter, eventBase: ctx.eventBase, i, budgetLimit: ctx.budgetLimit, budgetSummary: ctx.budgetSummary });
|
|
1306
|
+
|
|
1307
|
+
ctx.eventBase.iteration = i;
|
|
1308
|
+
ctx.iteration = i;
|
|
1309
|
+
|
|
1310
|
+
const iterResult = await runSingleIteration(ctx);
|
|
1311
|
+
if (iterResult.action === "return") return iterResult.result;
|
|
1312
|
+
if (iterResult.action === "retry") { i -= 1; }
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
// Extended iterations also exhausted — final Solomon call
|
|
1316
|
+
const finalResult = await handleMaxIterationsReached({ session: ctx.session, budgetSummary: ctx.budgetSummary, emitter, eventBase: ctx.eventBase, config: ctx.config, stageResults: ctx.stageResults, logger, askQuestion, task: loopTask, rtkTracker: ctx.rtkTracker });
|
|
1317
|
+
return finalResult;
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1560
1320
|
return maxIterResult;
|
|
1561
1321
|
}
|
|
1562
1322
|
|
|
@@ -1567,7 +1327,29 @@ export async function runFlow({ task, config, logger, flags = {}, emitter = null
|
|
|
1567
1327
|
return handleDryRun({ task, config, flags, emitter, pipelineFlags });
|
|
1568
1328
|
}
|
|
1569
1329
|
|
|
1570
|
-
|
|
1330
|
+
let ctx;
|
|
1331
|
+
try {
|
|
1332
|
+
ctx = await initFlowContext({ task, config, logger, emitter, askQuestion, pgTaskId, pgProject, flags });
|
|
1333
|
+
} catch (initError) {
|
|
1334
|
+
// Pre-loop stage failure → Solomon decides
|
|
1335
|
+
logger.warn(`Init/pre-loop error — escalating to Solomon: ${initError.message}`);
|
|
1336
|
+
const tempSession = { id: "init-error", task, status: "failed" };
|
|
1337
|
+
const solomonResult = await invokeSolomon({
|
|
1338
|
+
config, logger, emitter, eventBase: { sessionId: "init-error", iteration: 0, stage: "init", startedAt: Date.now() },
|
|
1339
|
+
stage: "init_error", askQuestion, session: tempSession, iteration: 0,
|
|
1340
|
+
conflict: {
|
|
1341
|
+
stage: "init_error",
|
|
1342
|
+
task,
|
|
1343
|
+
iterationCount: 0,
|
|
1344
|
+
maxIterations: config.max_iterations || 5,
|
|
1345
|
+
history: [{ agent: "pipeline", feedback: `Initialization failed: ${initError.message}` }]
|
|
1346
|
+
}
|
|
1347
|
+
});
|
|
1348
|
+
if (solomonResult.action === "pause") {
|
|
1349
|
+
return { paused: true, sessionId: "init-error", question: solomonResult.question, context: "init_error" };
|
|
1350
|
+
}
|
|
1351
|
+
throw initError; // Solomon couldn't resolve — propagate
|
|
1352
|
+
}
|
|
1571
1353
|
|
|
1572
1354
|
try {
|
|
1573
1355
|
// --- Analysis-only flow: skip coder/reviewer when coderRequired === false ---
|
|
@@ -1657,14 +1439,6 @@ export async function runFlow({ task, config, logger, flags = {}, emitter = null
|
|
|
1657
1439
|
await writeHistoryRecord({ sessionId: ctx.session.id, task, result, logger });
|
|
1658
1440
|
return result;
|
|
1659
1441
|
} finally {
|
|
1660
|
-
// --- Proxy shutdown ---
|
|
1661
|
-
try {
|
|
1662
|
-
await stopProxy();
|
|
1663
|
-
if (ctx.proxyPort) {
|
|
1664
|
-
logger.info("Proxy stopped");
|
|
1665
|
-
}
|
|
1666
|
-
} catch { /* non-blocking */ }
|
|
1667
|
-
|
|
1668
1442
|
// --- Cleanup auto-installed skills ---
|
|
1669
1443
|
const autoSkills = ctx.session?.autoInstalledSkills;
|
|
1670
1444
|
if (autoSkills && autoSkills.length > 0) {
|