karajan-code 2.7.4 → 2.9.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.
Files changed (192) hide show
  1. package/README.md +1 -1
  2. package/package.json +24 -4
  3. package/scripts/diet-baseline.mjs +170 -0
  4. package/scripts/regen-arch-stats.sh +71 -0
  5. package/src/agents/availability.js +15 -9
  6. package/src/agents/claude-agent.js +143 -45
  7. package/src/agents/model-registry.js +1 -1
  8. package/src/audit/agent-readiness.js +240 -0
  9. package/src/audit/basal-cost.js +92 -9
  10. package/src/audit/deterministic-summary.js +200 -0
  11. package/src/audit/osv-findings.js +162 -0
  12. package/src/audit/semgrep-findings.js +117 -0
  13. package/src/audit/sonar-findings.js +81 -0
  14. package/src/audit/webperf-input.js +90 -0
  15. package/src/bootstrap.js +26 -1
  16. package/src/brain/decision-tracker.js +3 -1
  17. package/src/brain/decisor.js +1 -1
  18. package/src/canvas/parse-md.js +238 -0
  19. package/src/canvas/schema.js +101 -0
  20. package/src/canvas/to-flat.js +85 -0
  21. package/src/canvas/validate.js +163 -0
  22. package/src/checks/binaries.js +3 -3
  23. package/src/checks/ci.js +2 -2
  24. package/src/checks/config-files.js +6 -6
  25. package/src/checks/dir-setup.js +4 -0
  26. package/src/checks/mcp-health.js +6 -0
  27. package/src/checks/ports.js +91 -29
  28. package/src/checks/rtk.js +1 -1
  29. package/src/checks/skills.js +5 -1
  30. package/src/checks/sonar.js +2 -2
  31. package/src/checks/system.js +3 -3
  32. package/src/checks/tokens.js +1 -1
  33. package/src/checks/types.js +0 -12
  34. package/src/cli/_shared.js +30 -0
  35. package/src/cli/register-meta.js +218 -0
  36. package/src/cli/register-pipeline.js +212 -0
  37. package/src/cli/register-plan.js +152 -0
  38. package/src/cli/register-roles-skills.js +38 -0
  39. package/src/cli/register-sonar.js +28 -0
  40. package/src/cli.js +83 -468
  41. package/src/commands/agents.js +18 -6
  42. package/src/commands/architect.js +29 -2
  43. package/src/commands/audit.js +265 -30
  44. package/src/commands/board.js +160 -30
  45. package/src/commands/clean.js +83 -0
  46. package/src/commands/code.js +7 -2
  47. package/src/commands/discover.js +7 -2
  48. package/src/commands/doctor.js +1 -1
  49. package/src/commands/init.js +2 -2
  50. package/src/commands/plan/_shared.js +40 -0
  51. package/src/commands/plan/add-hu.js +21 -0
  52. package/src/commands/plan/delete.js +9 -0
  53. package/src/commands/plan/generate.js +284 -0
  54. package/src/commands/plan/index.js +14 -0
  55. package/src/commands/plan/list.js +21 -0
  56. package/src/commands/plan/ready.js +46 -0
  57. package/src/commands/plan/remove-hu.js +17 -0
  58. package/src/commands/plan/show.js +81 -0
  59. package/src/commands/plan/validate.js +20 -0
  60. package/src/commands/plan.js +14 -291
  61. package/src/commands/researcher.js +7 -2
  62. package/src/commands/resume.js +24 -1
  63. package/src/commands/review.js +7 -2
  64. package/src/commands/run.js +52 -1
  65. package/src/commands/status.js +1 -1
  66. package/src/commands/sync.js +75 -0
  67. package/src/commands/triage.js +7 -2
  68. package/src/commands/undo.js +1 -1
  69. package/src/config/defaults.js +213 -0
  70. package/src/config/loader.js +137 -0
  71. package/src/config/overrides.js +199 -0
  72. package/src/config/role-resolver.js +159 -0
  73. package/src/config/test-harness.js +78 -0
  74. package/src/config.js +30 -632
  75. package/src/git/automation.js +2 -2
  76. package/src/guards/output-guard.js +0 -2
  77. package/src/hu/acceptance-runner.js +52 -10
  78. package/src/hu/store.js +11 -0
  79. package/src/mcp/handlers/direct-handlers.js +2 -5
  80. package/src/mcp/handlers/management-handlers.js +48 -4
  81. package/src/mcp/handlers/run-handler.js +9 -8
  82. package/src/mcp/preflight.js +32 -13
  83. package/src/mcp/server-handlers.js +4 -3
  84. package/src/mcp/suggest-handler.js +3 -2
  85. package/src/mcp/tools.js +15 -0
  86. package/src/orchestrator/agent-fallback.js +1 -1
  87. package/src/orchestrator/ci-integration.js +11 -11
  88. package/src/orchestrator/config-init.js +25 -7
  89. package/src/orchestrator/direct-actions.js +7 -3
  90. package/src/orchestrator/drivers/error-recovery.js +287 -0
  91. package/src/orchestrator/drivers/init-context.js +222 -0
  92. package/src/orchestrator/drivers/iteration-loop.js +301 -0
  93. package/src/orchestrator/drivers/iteration-phases/coder-and-refactorer.js +48 -0
  94. package/src/orchestrator/drivers/iteration-phases/guards.js +84 -0
  95. package/src/orchestrator/drivers/iteration-phases/handle-approved.js +29 -0
  96. package/src/orchestrator/drivers/iteration-phases/quality-gates.js +76 -0
  97. package/src/orchestrator/drivers/iteration-phases/reviewer-gate.js +60 -0
  98. package/src/orchestrator/drivers/post-loop.js +370 -0
  99. package/src/orchestrator/drivers/pre-loop-phases/auto-hu-batch.js +116 -0
  100. package/src/orchestrator/drivers/pre-loop-phases/config-deprecations.js +47 -0
  101. package/src/orchestrator/drivers/pre-loop-phases/ensure-addyosmani-skills.js +84 -0
  102. package/src/orchestrator/drivers/pre-loop-phases/inject-loaded-plan.js +268 -0
  103. package/src/orchestrator/drivers/pre-loop.js +431 -0
  104. package/src/orchestrator/drivers/run-hu-batch.js +367 -0
  105. package/src/orchestrator/feedback-queue.js +0 -1
  106. package/src/orchestrator/flow-control.js +4 -3
  107. package/src/orchestrator/flow-runner.js +121 -2077
  108. package/src/orchestrator/fs-leak-detector.js +156 -0
  109. package/src/orchestrator/hu-sub-pipeline.js +197 -9
  110. package/src/orchestrator/integrations.js +81 -0
  111. package/src/orchestrator/post-loop-stages.js +35 -8
  112. package/src/orchestrator/preflight-checks.js +20 -7
  113. package/src/orchestrator/repair/repair-detector.js +117 -0
  114. package/src/orchestrator/repair/repair-runner.js +142 -0
  115. package/src/orchestrator/reviewer-fallback.js +1 -1
  116. package/src/orchestrator/role-output-compressor.js +0 -1
  117. package/src/orchestrator/session-journal.js +4 -41
  118. package/src/orchestrator/solomon-escalation.js +3 -2
  119. package/src/orchestrator/solomon-rules.js +7 -3
  120. package/src/orchestrator/stages/architect-stage.js +12 -11
  121. package/src/orchestrator/stages/coder-stage.js +56 -7
  122. package/src/orchestrator/stages/hu-reviewer-phases/detect-and-apply-splits.js +127 -0
  123. package/src/orchestrator/stages/hu-reviewer-stage.js +6 -79
  124. package/src/orchestrator/stages/planner-stage.js +1 -1
  125. package/src/orchestrator/stages/research-stage.js +1 -1
  126. package/src/orchestrator/stages/reviewer-stage.js +7 -7
  127. package/src/orchestrator/stages/sonar-stage.js +94 -10
  128. package/src/orchestrator/stages/stage-classes.js +84 -0
  129. package/src/orchestrator/stages/stage-executor.js +1 -1
  130. package/src/orchestrator/stages/triage-stage.js +1 -1
  131. package/src/orchestrator/standby.js +3 -2
  132. package/src/orchestrator/verification-gate.js +20 -12
  133. package/src/plan/adr-generator.js +170 -0
  134. package/src/plan/adr-loader.js +60 -0
  135. package/src/plan/plan-executor.js +19 -1
  136. package/src/plan/plan-hu-ops.js +170 -1
  137. package/src/plan/plan-reviewer.js +199 -0
  138. package/src/plan/plan-schema.js +69 -1
  139. package/src/plan/plan-store.js +40 -17
  140. package/src/plan/spec-detector.js +61 -0
  141. package/src/plan/tests-synthesizer.js +116 -0
  142. package/src/planning-game/client.js +3 -28
  143. package/src/planning-game/decomposition.js +1 -1
  144. package/src/planning-game/pipeline-adapter.js +37 -6
  145. package/src/prompts/audit.js +206 -6
  146. package/src/prompts/coder.js +172 -1
  147. package/src/prompts/hu-reviewer.js +0 -1
  148. package/src/prompts/planner.js +81 -4
  149. package/src/prompts/rtk-snippet.js +1 -1
  150. package/src/review/diff-generator.js +35 -22
  151. package/src/review/snapshot-diff.js +1 -1
  152. package/src/roles/audit-role.js +125 -13
  153. package/src/roles/coder-role.js +25 -3
  154. package/src/roles/planner-role.js +42 -1
  155. package/src/roles/repairer-role.js +181 -0
  156. package/src/roles/tester-role.js +70 -4
  157. package/src/session/mutators.js +304 -0
  158. package/src/session/runtime-overrides.js +59 -0
  159. package/src/{session-store.js → session/store.js} +2 -2
  160. package/src/skills/semantic-detector.js +10 -5
  161. package/src/skills/skill-detector.js +1 -1
  162. package/src/sonar/api.js +1 -1
  163. package/src/sonar/cloud-scanner.js +0 -1
  164. package/src/sonar/credentials.js +14 -6
  165. package/src/sonar/discovery.js +123 -0
  166. package/src/sonar/manager.js +18 -0
  167. package/src/sync/detect-drift.js +113 -0
  168. package/src/tracker-bootstrap.js +44 -0
  169. package/src/utils/agent-detect.js +15 -11
  170. package/src/utils/board-prompt-bridge.js +109 -0
  171. package/src/utils/cli-progress.js +161 -0
  172. package/src/utils/cwd-confirm.js +39 -0
  173. package/src/utils/derive-project-name-from-cwd.js +119 -0
  174. package/src/utils/display/event-handlers.js +1 -1
  175. package/src/utils/display/header.js +1 -1
  176. package/src/utils/display/session-summary.js +3 -3
  177. package/src/utils/display/solomon.js +1 -1
  178. package/src/utils/garbage-collector.js +396 -0
  179. package/src/utils/git.js +16 -4
  180. package/src/utils/locale.js +1 -1
  181. package/src/utils/paths.js +21 -1
  182. package/src/utils/process.js +1 -1
  183. package/src/utils/prompt-project-name.js +35 -0
  184. package/src/utils/role-assigner.js +1 -1
  185. package/src/utils/run-context.js +75 -0
  186. package/src/utils/run-log.js +12 -0
  187. package/src/utils/task-file.js +1 -1
  188. package/src/utils/telemetry.js +13 -1
  189. package/src/webperf/devtools-detect.js +1 -1
  190. package/templates/coder-rules.md +13 -0
  191. package/templates/roles/coder.md +34 -0
  192. package/templates/roles/repairer.md +94 -0
@@ -0,0 +1,301 @@
1
+ /**
2
+ * Iteration-loop driver — extracted from src/orchestrator/flow-runner.js
3
+ * in TSK-0335 (Oleada 3 of the v2.7.4 audit refactor).
4
+ *
5
+ * Everything inside the coder→reviewer loop:
6
+ *
7
+ * - runCoderAndRefactorerStages: coder (+ refactorer if enabled), with
8
+ * standby handling per stage.
9
+ * - runGuardStages: deterministic output/perf guards on the diff.
10
+ * - runQualityGateStages: TDD check, Sonar (local + cloud), Impeccable.
11
+ * - runReviewerGateStage: reviewer role, with standby handling.
12
+ * - handleApprovedReview: wire post-loop stages + finalize session.
13
+ * - runSingleIteration: one coder→reviewer cycle (the iteration body).
14
+ * - runIterationLoop: the while loop, checkpoints, budget/time
15
+ * gates, error recovery via Solomon,
16
+ * max-iterations extension path.
17
+ *
18
+ * Extracted verbatim; no behavior change. flow-runner.js now imports these
19
+ * instead of defining them. Part of breaking up the 2254-LOC god-module —
20
+ * see docs/ARCHITECTURE.md and the TSK-0335 acceptance criteria.
21
+ */
22
+
23
+ import { emitProgress, makeEvent } from "../../utils/events.js";
24
+ import { msg, getLang } from "../../utils/messages.js";
25
+ import { saveSession, markSessionStatus } from "../../session/store.js";
26
+ import {
27
+ setReviewerFeedback, resetRetryCount,
28
+ } from "../../session/mutators.js";
29
+ import { invokeSolomon } from "../solomon-escalation.js";
30
+ import {
31
+ handleCiEarlyPrOrPush, handleCiReviewDispatch,
32
+ } from "../ci-integration.js";
33
+ import {
34
+ handleCheckpoint, checkSessionTimeout, checkBudgetExceeded,
35
+ takeCheckpointSnapshot,
36
+ } from "../flow-control.js";
37
+ import {
38
+ handleSolomonCheck, handleReviewerRetryAndSolomon,
39
+ } from "./error-recovery.js";
40
+ import { handleMaxIterationsReached } from "./post-loop.js";
41
+ import { runCoderAndRefactorerStages } from "./iteration-phases/coder-and-refactorer.js";
42
+ import { runGuardStages } from "./iteration-phases/guards.js";
43
+ import { runQualityGateStages } from "./iteration-phases/quality-gates.js";
44
+ import { runReviewerGateStage } from "./iteration-phases/reviewer-gate.js";
45
+ import { handleApprovedReview } from "./iteration-phases/handle-approved.js";
46
+
47
+ // `runCoderAndRefactorerStages`, `runGuardStages`, `runQualityGateStages`,
48
+ // `runReviewerGateStage` and `handleApprovedReview` were extracted to
49
+ // ./iteration-phases/ in the v2.7.x audit follow-up (same pattern as
50
+ // pre-loop-phases/). The back-compat re-exports were removed in
51
+ // KJC-TSK-0354 PR-D — no caller imported them via this module.
52
+
53
+ async function runSingleIteration(ctx) {
54
+ // Use plannedTask (HU-scoped or planner-enriched) over the raw original task.
55
+ // When running per-HU sub-pipelines, plannedTask is the HU's text, not the full spec.
56
+ const { config, logger, emitter, eventBase, session, iteration: i } = ctx;
57
+ const task = ctx.plannedTask || ctx.task;
58
+
59
+ const iterStart = Date.now();
60
+ const ciEnabled = Boolean(config.ci?.enabled) && ctx.gitCtx?.enabled;
61
+ logger.setContext({ iteration: i, stage: "iteration" });
62
+
63
+ const reviewerRetryCount = session.reviewer_retry_count || 0;
64
+ const maxReviewerRetries = config.session.max_reviewer_retries ?? config.session.fail_fast_repeats;
65
+ const iterLang = getLang(config);
66
+ const iterMsg = msg("pipeline_iteration", iterLang, { current: i, max: config.max_iterations });
67
+ emitProgress(emitter, makeEvent("iteration:start", { ...eventBase, stage: "iteration" }, {
68
+ message: iterMsg,
69
+ detail: { iteration: i, maxIterations: config.max_iterations, reviewerRetryCount, maxReviewerRetries }
70
+ }));
71
+ logger.info(iterMsg);
72
+
73
+ const crResult = await runCoderAndRefactorerStages({
74
+ coderRoleInstance: ctx.coderRoleInstance, coderRole: ctx.coderRole, refactorerRole: ctx.refactorerRole,
75
+ pipelineFlags: ctx.pipelineFlags, config, logger, emitter, eventBase, session,
76
+ plannedTask: ctx.plannedTask, trackBudget: ctx.trackBudget, i, brainCtx: ctx.brainCtx
77
+ });
78
+ if (crResult.action === "return" || crResult.action === "retry") return crResult;
79
+
80
+ const guardResult = await runGuardStages({ config, logger, emitter, eventBase, session, iteration: i });
81
+ if (guardResult.action === "return") return guardResult;
82
+
83
+ const qgResult = await runQualityGateStages({
84
+ config, logger, emitter, eventBase, session, trackBudget: ctx.trackBudget, i,
85
+ askQuestion: ctx.askQuestion, repeatDetector: ctx.repeatDetector, budgetSummary: ctx.budgetSummary,
86
+ sonarState: ctx.sonarState, task, stageResults: ctx.stageResults, coderRole: ctx.coderRole,
87
+ pipelineFlags: ctx.pipelineFlags, brainCtx: ctx.brainCtx
88
+ });
89
+ if (qgResult.action === "return" || qgResult.action === "continue") return qgResult;
90
+
91
+ await handleCiEarlyPrOrPush({
92
+ ciEnabled, config, session, emitter, eventBase, gitCtx: ctx.gitCtx, task, logger,
93
+ stageResults: ctx.stageResults, i
94
+ });
95
+
96
+ const revResult = await runReviewerGateStage({
97
+ pipelineFlags: ctx.pipelineFlags, reviewerRole: ctx.reviewerRole, config, logger, emitter, eventBase,
98
+ session, trackBudget: ctx.trackBudget, i, reviewRules: ctx.reviewRules, task,
99
+ repeatDetector: ctx.repeatDetector, budgetSummary: ctx.budgetSummary, askQuestion: ctx.askQuestion,
100
+ brainCtx: ctx.brainCtx
101
+ });
102
+ if (revResult.action === "return" || revResult.action === "retry") return revResult;
103
+ const review = revResult.review;
104
+
105
+ const iterDuration = Date.now() - iterStart;
106
+ emitProgress(emitter, makeEvent("iteration:end", { ...eventBase, stage: "iteration" }, {
107
+ message: `Iteration ${i} completed`, detail: { duration: iterDuration }
108
+ }));
109
+ resetRetryCount(session, "standby");
110
+
111
+ // --- Journal: record iteration ---
112
+ // Uses the richer iteration-logger from TSK-0286: captures blocking-issue
113
+ // details, sonar counts and Solomon rulings in addition to summaries.
114
+ if (ctx.journalIterations) {
115
+ try {
116
+ const { recordIteration, extractIterationData } = await import("../../session/journal/iteration-logger.js");
117
+ const iterationData = extractIterationData({
118
+ iteration: i,
119
+ durationMs: iterDuration,
120
+ stageResults: ctx.stageResults,
121
+ session: ctx.session,
122
+ });
123
+ // Derive a richer reviewer summary if none was produced.
124
+ if (!iterationData.reviewerSummary && review) {
125
+ iterationData.reviewerSummary = review.approved
126
+ ? `Approved: ${review.raw_summary || ""}`
127
+ : `Rejected: ${(review?.blocking_issues || []).length} blocking issue(s)`;
128
+ }
129
+ if (!iterationData.blockingIssues || iterationData.blockingIssues.length === 0) {
130
+ iterationData.blockingIssues = review?.blocking_issues || [];
131
+ }
132
+ recordIteration(ctx.session, iterationData);
133
+ } catch (err) {
134
+ logger.warn(`Iteration journal record failed (non-blocking): ${err.message}`);
135
+ }
136
+ }
137
+
138
+ const solomonResult = await handleSolomonCheck({
139
+ config, session, emitter, eventBase, logger, task, i, askQuestion: ctx.askQuestion,
140
+ ciEnabled, blockingIssues: review?.blocking_issues, brainCtx: ctx.brainCtx
141
+ });
142
+ if (solomonResult.action === "pause") return { action: "return", result: solomonResult.result };
143
+
144
+ await handleCiReviewDispatch({ ciEnabled, config, session, review, i, logger });
145
+
146
+ if (review.approved) {
147
+ const approvedResult = await handleApprovedReview({
148
+ config, session, emitter, eventBase, coderRole: ctx.coderRole, trackBudget: ctx.trackBudget, i, task,
149
+ stageResults: ctx.stageResults, pipelineFlags: ctx.pipelineFlags, askQuestion: ctx.askQuestion, logger,
150
+ gitCtx: ctx.gitCtx, budgetSummary: ctx.budgetSummary, pgCard: ctx.pgCard, pgProject: ctx.pgProject, review,
151
+ rtkTracker: ctx.rtkTracker, brainCtx: ctx.brainCtx
152
+ });
153
+ if (approvedResult.action === "return" || approvedResult.action === "continue") return approvedResult;
154
+ }
155
+
156
+ // Solomon already evaluated the rejection in runReviewerStage -> handleReviewerRejection
157
+ // Only use retry counter as fallback if Solomon is disabled
158
+ if (!config.pipeline?.solomon?.enabled) {
159
+ const retryResult = await handleReviewerRetryAndSolomon({ config, session, emitter, eventBase, logger, review, task, i, askQuestion: ctx.askQuestion });
160
+ if (retryResult.action === "return") return retryResult;
161
+ } else {
162
+ // Solomon is enabled — feed back the blocking issues for the next coder iteration
163
+ setReviewerFeedback(session, review.blocking_issues
164
+ .map((x) => {
165
+ const parts = [`[${x.severity || "high"}] ${x.id || "ISSUE"}: ${x.description || "Missing description"}`];
166
+ if (x.file) parts.push(` File: ${x.file}${x.line ? `:${x.line}` : ""}`);
167
+ if (x.suggested_fix) parts.push(` Fix: ${x.suggested_fix}`);
168
+ return parts.join("\n");
169
+ })
170
+ .join("\n\n"));
171
+ await saveSession(session);
172
+ }
173
+
174
+ return { action: "next" };
175
+ }
176
+
177
+
178
+ /**
179
+ * Run the standard iteration loop for a given task using the pipeline context.
180
+ * Returns a result object with at least { approved: boolean }.
181
+ * Used both as the main loop and as the per-HU callback in sub-pipeline mode.
182
+ */
183
+ export async function runIterationLoop(ctx, { task: loopTask, askQuestion, emitter, logger }) {
184
+ ctx.plannedTask = loopTask;
185
+
186
+ const checkpointIntervalMs = (ctx.config.session.checkpoint_interval_minutes ?? 5) * 60 * 1000;
187
+ let lastCheckpointAt = Date.now();
188
+ let checkpointDisabled = false;
189
+ let lastCheckpointSnapshot = takeCheckpointSnapshot(ctx.session);
190
+
191
+ let i = 0;
192
+ while (i < ctx.config.max_iterations) {
193
+ i += 1;
194
+ const elapsedMinutes = (Date.now() - ctx.startedAt) / 60000;
195
+
196
+ const cpResult = await handleCheckpoint({
197
+ checkpointDisabled, askQuestion, lastCheckpointAt, checkpointIntervalMs, elapsedMinutes,
198
+ i, config: ctx.config, budgetTracker: ctx.budgetTracker, stageResults: ctx.stageResults, emitter, eventBase: ctx.eventBase, session: ctx.session, budgetSummary: ctx.budgetSummary, lastCheckpointSnapshot
199
+ });
200
+ if (cpResult.action === "stop") {
201
+ return cpResult.result;
202
+ }
203
+ checkpointDisabled = cpResult.checkpointDisabled;
204
+ lastCheckpointAt = cpResult.lastCheckpointAt;
205
+ if (cpResult.lastCheckpointSnapshot !== undefined) lastCheckpointSnapshot = cpResult.lastCheckpointSnapshot;
206
+
207
+ await checkSessionTimeout({ askQuestion, elapsedMinutes, config: ctx.config, session: ctx.session, emitter, eventBase: ctx.eventBase, i, budgetSummary: ctx.budgetSummary });
208
+ await checkBudgetExceeded({ budgetTracker: ctx.budgetTracker, config: ctx.config, session: ctx.session, emitter, eventBase: ctx.eventBase, i, budgetLimit: ctx.budgetLimit, budgetSummary: ctx.budgetSummary });
209
+
210
+ ctx.eventBase.iteration = i;
211
+ ctx.iteration = i;
212
+
213
+ let iterResult;
214
+ try {
215
+ iterResult = await runSingleIteration(ctx);
216
+ } catch (stageError) {
217
+ // ANY unhandled error in a stage = out of normal flow → Solomon decides
218
+ logger.warn(`Stage error caught — escalating to Solomon: ${stageError.message}`);
219
+ const solomonResult = await invokeSolomon({
220
+ config: ctx.config, logger, emitter, eventBase: ctx.eventBase,
221
+ stage: "stage_error", askQuestion, session: ctx.session, iteration: i,
222
+ conflict: {
223
+ stage: "stage_error",
224
+ task: loopTask,
225
+ iterationCount: i,
226
+ maxIterations: ctx.config.max_iterations,
227
+ budget_usd: ctx.budgetSummary()?.total_cost_usd || 0,
228
+ history: [{ agent: "pipeline", feedback: `Stage threw: ${stageError.message}` }]
229
+ }
230
+ });
231
+
232
+ if (solomonResult.action === "approve") {
233
+ logger.info("Solomon approved despite stage error");
234
+ return { approved: true, sessionId: ctx.session.id, reason: "solomon_approved_after_error" };
235
+ }
236
+ if (solomonResult.action === "continue") {
237
+ if (solomonResult.humanGuidance) {
238
+ setReviewerFeedback(ctx.session, `Solomon guidance: ${solomonResult.humanGuidance}`);
239
+ }
240
+ continue; // next iteration
241
+ }
242
+ if (solomonResult.action === "pause") {
243
+ return { paused: true, sessionId: ctx.session.id, question: solomonResult.question, context: "stage_error" };
244
+ }
245
+ // Solomon couldn't resolve — fail
246
+ await markSessionStatus(ctx.session, "failed");
247
+ return { approved: false, sessionId: ctx.session.id, reason: "stage_error", error: stageError.message };
248
+ }
249
+
250
+ if (iterResult.action === "return") {
251
+ return iterResult.result;
252
+ }
253
+ if (iterResult.action === "retry") { i -= 1; }
254
+ }
255
+
256
+ // Solomon decides whether to extend iterations or stop
257
+ 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, brainCtx: ctx.brainCtx });
258
+
259
+ // Solomon said "continue" — extend iterations and keep going
260
+ if (maxIterResult.reason === "max_iterations_extended") {
261
+ const extra = maxIterResult.extraIterations || ctx.config.max_iterations;
262
+ ctx.config.max_iterations += extra;
263
+ logger.info(`Solomon extended pipeline by ${extra} iterations (new max: ${ctx.config.max_iterations})`);
264
+
265
+ if (maxIterResult.humanGuidance) {
266
+ setReviewerFeedback(ctx.session, `Solomon guidance: ${maxIterResult.humanGuidance}`);
267
+ }
268
+
269
+ // Continue the loop
270
+ while (i < ctx.config.max_iterations) {
271
+ i += 1;
272
+ const elapsedMinutes = (Date.now() - ctx.startedAt) / 60000;
273
+
274
+ const cpResult = await handleCheckpoint({
275
+ checkpointDisabled, askQuestion, lastCheckpointAt, checkpointIntervalMs, elapsedMinutes,
276
+ i, config: ctx.config, budgetTracker: ctx.budgetTracker, stageResults: ctx.stageResults, emitter, eventBase: ctx.eventBase, session: ctx.session, budgetSummary: ctx.budgetSummary, lastCheckpointSnapshot
277
+ });
278
+ if (cpResult.action === "stop") return cpResult.result;
279
+ checkpointDisabled = cpResult.checkpointDisabled;
280
+ lastCheckpointAt = cpResult.lastCheckpointAt;
281
+ if (cpResult.lastCheckpointSnapshot !== undefined) lastCheckpointSnapshot = cpResult.lastCheckpointSnapshot;
282
+
283
+ await checkSessionTimeout({ askQuestion, elapsedMinutes, config: ctx.config, session: ctx.session, emitter, eventBase: ctx.eventBase, i, budgetSummary: ctx.budgetSummary });
284
+ await checkBudgetExceeded({ budgetTracker: ctx.budgetTracker, config: ctx.config, session: ctx.session, emitter, eventBase: ctx.eventBase, i, budgetLimit: ctx.budgetLimit, budgetSummary: ctx.budgetSummary });
285
+
286
+ ctx.eventBase.iteration = i;
287
+ ctx.iteration = i;
288
+
289
+ const iterResult = await runSingleIteration(ctx);
290
+ if (iterResult.action === "return") return iterResult.result;
291
+ if (iterResult.action === "retry") { i -= 1; }
292
+ }
293
+
294
+ // Extended iterations also exhausted — final Solomon call
295
+ 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, brainCtx: ctx.brainCtx });
296
+ return finalResult;
297
+ }
298
+
299
+ return maxIterResult;
300
+ }
301
+
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Iteration phase: coder (+ refactorer if enabled) with standby handling.
3
+ *
4
+ * Extracted verbatim from `src/orchestrator/drivers/iteration-loop.js` in
5
+ * the v2.7.x audit follow-up to keep the iteration-loop driver under the
6
+ * 600-LOC ceiling. Behaviour is byte-for-byte identical to the inlined
7
+ * version. The extraction follows the same pattern as
8
+ * `pre-loop-phases/` (see TSK-0337).
9
+ *
10
+ * Returns { action: "ok" | "return" | "retry", result? }.
11
+ * - "ok" → caller continues to the next phase.
12
+ * - "return" → caller returns `result` to its own caller (pause/abort).
13
+ * - "retry" → caller decrements its iteration counter and retries.
14
+ */
15
+
16
+ import { runRefactorerStage } from "../../iteration-stages.js";
17
+ import { stageRegistry } from "../../stages/stage-classes.js";
18
+ import { runStage } from "../../stages/stage-executor.js";
19
+ import { handleStandbyResult } from "../error-recovery.js";
20
+
21
+ export async function runCoderAndRefactorerStages({ coderRoleInstance, coderRole, refactorerRole, pipelineFlags, config, logger, emitter, eventBase, session, plannedTask, trackBudget, i, brainCtx }) {
22
+ // Coder via StageRegistry (TSK-0336). canRun = coderRequired !== false; in
23
+ // analysis-only task types coderRequired is set to false by policy, so the
24
+ // stage is never even entered — matches the previous guard at the top of
25
+ // runFlow.
26
+ const coderCtx = { coderRoleInstance, coderRole, config, logger, emitter, eventBase, session, plannedTask, trackBudget, iteration: i, brainCtx, pipelineFlags };
27
+ const coderResult = await runStage(stageRegistry.get("coder"), coderCtx);
28
+ if (coderResult?.action === "pause") return { action: "return", result: coderResult.result };
29
+ const coderStandby = await handleStandbyResult({ stageResult: coderResult, session, emitter, eventBase, i, stage: "coder", logger, config });
30
+ if (coderStandby.handled) {
31
+ return coderStandby.action === "return"
32
+ ? { action: "return", result: coderStandby.result }
33
+ : { action: "retry" };
34
+ }
35
+
36
+ if (pipelineFlags.refactorerEnabled) {
37
+ const refResult = await runRefactorerStage({ refactorerRole, config, logger, emitter, eventBase, session, plannedTask, trackBudget, iteration: i });
38
+ if (refResult?.action === "pause") return { action: "return", result: refResult.result };
39
+ const refStandby = await handleStandbyResult({ stageResult: refResult, session, emitter, eventBase, i, stage: "refactorer", logger, config });
40
+ if (refStandby.handled) {
41
+ return refStandby.action === "return"
42
+ ? { action: "return", result: refStandby.result }
43
+ : { action: "retry" };
44
+ }
45
+ }
46
+
47
+ return { action: "ok" };
48
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Iteration phase: deterministic output / perf guards on the diff.
3
+ *
4
+ * Extracted verbatim from `src/orchestrator/drivers/iteration-loop.js` in
5
+ * the v2.7.x audit follow-up to keep the iteration-loop driver under the
6
+ * 600-LOC ceiling. Behaviour is byte-for-byte identical to the inlined
7
+ * version.
8
+ *
9
+ * Returns { action: "ok" | "return", result? }.
10
+ * - "ok" → caller continues to the next phase.
11
+ * - "return" → caller returns `result` (output guard blocked on critical
12
+ * violations).
13
+ */
14
+
15
+ import { emitProgress, makeEvent } from "../../../utils/events.js";
16
+ import { markSessionStatus, addCheckpoint } from "../../../session/store.js";
17
+ import { generateDiff, computeBaseRef } from "../../../review/diff-generator.js";
18
+ import { scanDiff } from "../../../guards/output-guard.js";
19
+ import { scanPerfDiff } from "../../../guards/perf-guard.js";
20
+
21
+ export async function runGuardStages({ config, logger, emitter, eventBase, session, iteration }) {
22
+ const outputEnabled = config.guards?.output?.enabled !== false;
23
+ const perfEnabled = config.guards?.perf?.enabled !== false;
24
+
25
+ if (!outputEnabled && !perfEnabled) return { action: "ok" };
26
+
27
+ const baseBranch = config.base_branch || "main";
28
+ let diff;
29
+ try {
30
+ const baseRef = await computeBaseRef({ baseBranch });
31
+ diff = await generateDiff({ baseRef });
32
+ } catch {
33
+ logger.warn("Guards: could not generate diff, skipping");
34
+ return { action: "ok" };
35
+ }
36
+
37
+ if (!diff) return { action: "ok" };
38
+
39
+ if (outputEnabled) {
40
+ const outputResult = scanDiff(diff, config);
41
+ if (outputResult.violations.length > 0) {
42
+ const critical = outputResult.violations.filter(v => v.severity === "critical");
43
+ const warnings = outputResult.violations.filter(v => v.severity === "warning");
44
+ emitProgress(emitter, makeEvent("guard:output", { ...eventBase, stage: "guard" }, {
45
+ message: `Output guard: ${critical.length} critical, ${warnings.length} warnings`,
46
+ detail: { violations: outputResult.violations, executorType: "local" }
47
+ }));
48
+ logger.info(`Output guard: ${outputResult.violations.length} violation(s) found`);
49
+ for (const v of outputResult.violations) {
50
+ logger.info(` [${v.severity}] ${v.file}:${v.line} — ${v.message}`);
51
+ }
52
+ await addCheckpoint(session, { stage: "guard-output", iteration, pass: outputResult.pass, violations: outputResult.violations.length });
53
+
54
+ if (!outputResult.pass && config.guards.output.on_violation === "block") {
55
+ await markSessionStatus(session, "failed");
56
+ emitProgress(emitter, makeEvent("guard:blocked", { ...eventBase, stage: "guard" }, {
57
+ message: "Output guard blocked: critical violations detected",
58
+ detail: { violations: critical }
59
+ }));
60
+ return {
61
+ action: "return",
62
+ result: { approved: false, sessionId: session.id, reason: "guard_blocked", violations: critical }
63
+ };
64
+ }
65
+ }
66
+ }
67
+
68
+ if (perfEnabled) {
69
+ const perfResult = scanPerfDiff(diff, config);
70
+ if (!perfResult.skipped && perfResult.violations.length > 0) {
71
+ emitProgress(emitter, makeEvent("guard:perf", { ...eventBase, stage: "guard" }, {
72
+ message: `Perf guard: ${perfResult.violations.length} issue(s)`,
73
+ detail: { violations: perfResult.violations, executorType: "local" }
74
+ }));
75
+ logger.info(`Perf guard: ${perfResult.violations.length} issue(s) found`);
76
+ for (const v of perfResult.violations) {
77
+ logger.info(` [${v.severity}] ${v.file}:${v.line} — ${v.message}`);
78
+ }
79
+ await addCheckpoint(session, { stage: "guard-perf", iteration, pass: perfResult.pass, violations: perfResult.violations.length });
80
+ }
81
+ }
82
+
83
+ return { action: "ok" };
84
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Iteration phase: post-loop wiring + session finalization when the
3
+ * reviewer approves.
4
+ *
5
+ * Extracted verbatim from `src/orchestrator/drivers/iteration-loop.js` in
6
+ * the v2.7.x audit follow-up to keep the iteration-loop driver under the
7
+ * 600-LOC ceiling. Behaviour is byte-for-byte identical to the inlined
8
+ * version.
9
+ *
10
+ * Returns { action: "return" | "continue", result? }.
11
+ * - "return" → caller returns `result` (final approved session).
12
+ * - "continue" → caller continues the iteration loop.
13
+ */
14
+
15
+ import { resetRetryCount } from "../../../session/mutators.js";
16
+ import { handlePostLoopStages, finalizeApprovedSession } from "../post-loop.js";
17
+
18
+ export async function handleApprovedReview({ config, session, emitter, eventBase, coderRole, trackBudget, i, task, stageResults, pipelineFlags, askQuestion, logger, gitCtx, budgetSummary, pgCard, pgProject, review, rtkTracker, brainCtx }) {
19
+ resetRetryCount(session, "reviewer");
20
+ const postLoopResult = await handlePostLoopStages({
21
+ config, session, emitter, eventBase, coderRole, trackBudget, i, task, stageResults,
22
+ ciEnabled: Boolean(config.ci?.enabled), testerEnabled: pipelineFlags.testerEnabled, securityEnabled: pipelineFlags.securityEnabled, askQuestion, logger, brainCtx
23
+ });
24
+ if (postLoopResult.action === "return") return { action: "return", result: postLoopResult.result };
25
+ if (postLoopResult.action === "continue") return { action: "continue" };
26
+
27
+ const result = await finalizeApprovedSession({ config, gitCtx, task, logger, session, stageResults, emitter, eventBase, budgetSummary, pgCard, pgProject, review, i, rtkTracker });
28
+ return { action: "return", result };
29
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Iteration phase: quality gates (TDD check, SonarQube local + cloud,
3
+ * Impeccable design audit).
4
+ *
5
+ * Extracted verbatim from `src/orchestrator/drivers/iteration-loop.js` in
6
+ * the v2.7.x audit follow-up to keep the iteration-loop driver under the
7
+ * 600-LOC ceiling. Behaviour is byte-for-byte identical to the inlined
8
+ * version.
9
+ *
10
+ * Returns { action: "ok" | "return" | "continue", result? }.
11
+ * - "ok" → caller continues to the next phase.
12
+ * - "return" → caller returns `result` (pause / stalled / blocked).
13
+ * - "continue" → caller continues the iteration loop (next iteration).
14
+ */
15
+
16
+ import { generateDiff } from "../../../review/diff-generator.js";
17
+ import {
18
+ runTddCheckStage, runSonarStage, runSonarCloudStage,
19
+ } from "../../iteration-stages.js";
20
+ import { runImpeccableStage } from "../../post-loop-stages.js";
21
+ import { tryCiComment } from "../../ci-integration.js";
22
+
23
+ export async function runQualityGateStages({ config, logger, emitter, eventBase, session, trackBudget, i, askQuestion, repeatDetector, budgetSummary, sonarState, task, stageResults, coderRole, pipelineFlags, brainCtx }) {
24
+ const tddResult = await runTddCheckStage({ config, logger, emitter, eventBase, session, trackBudget, iteration: i, askQuestion, task, brainCtx });
25
+ if (tddResult.action === "pause") return { action: "return", result: tddResult.result };
26
+ if (tddResult.action === "continue") return { action: "continue" };
27
+
28
+ // Sonar runs for code tasks per policy. Since v2.7.4 it is NOT
29
+ // toggleable via config — that's intrinsic to Karajan. The taskType
30
+ // policy (resolved_policies.sonar) is the single source of truth.
31
+ // Solomon may skip a single iteration via rule alerts; that's a
32
+ // runtime decision, not a config option.
33
+ //
34
+ // Test-harness escape hatch via config.testHarness.disableSonarStage
35
+ // — production code reads `config?.testHarness?.disableSonarStage`,
36
+ // never `globalThis.*`. The legacy override surface is documented
37
+ // (and exclusively read) in src/config/test-harness.js. ESLint rule
38
+ // (#557) blocks any re-introduction of `globalThis.__KJ_*` outside
39
+ // that one file.
40
+ const sonarStageDisabledForTest = config?.testHarness?.disableSonarStage === true;
41
+ if (!sonarStageDisabledForTest && session.resolved_policies?.sonar !== false) {
42
+ const sonarResult = await runSonarStage({
43
+ config, logger, emitter, eventBase, session, trackBudget, iteration: i,
44
+ repeatDetector, budgetSummary, sonarState, askQuestion, task, brainCtx
45
+ });
46
+ if (sonarResult.action === "stalled" || sonarResult.action === "pause") return { action: "return", result: sonarResult.result };
47
+ if (sonarResult.action === "continue") return { action: "continue" };
48
+ if (sonarResult.stageResult) {
49
+ stageResults.sonar = sonarResult.stageResult;
50
+ await tryCiComment({ config, session, logger, agent: "Sonar", body: `SonarQube scan: ${sonarResult.stageResult.summary || "completed"}` });
51
+ }
52
+ }
53
+
54
+ if (config.sonarcloud?.enabled) {
55
+ const cloudResult = await runSonarCloudStage({
56
+ config, logger, emitter, eventBase, session, trackBudget, iteration: i
57
+ });
58
+ if (cloudResult.stageResult) {
59
+ stageResults.sonarcloud = cloudResult.stageResult;
60
+ }
61
+ }
62
+
63
+ if (pipelineFlags?.impeccableEnabled) {
64
+ const diff = await generateDiff({ baseRef: session.session_start_sha });
65
+ const impeccableMode = pipelineFlags?.impeccableMode || "audit";
66
+ const impeccableResult = await runImpeccableStage({
67
+ config, logger, emitter, eventBase, session, coderRole, trackBudget,
68
+ iteration: i, task, diff, mode: impeccableMode
69
+ });
70
+ if (impeccableResult.stageResult) {
71
+ stageResults.impeccable = impeccableResult.stageResult;
72
+ }
73
+ }
74
+
75
+ return { action: "ok" };
76
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Iteration phase: reviewer stage with standby handling.
3
+ *
4
+ * Extracted verbatim from `src/orchestrator/drivers/iteration-loop.js` in
5
+ * the v2.7.x audit follow-up to keep the iteration-loop driver under the
6
+ * 600-LOC ceiling. Behaviour is byte-for-byte identical to the inlined
7
+ * version.
8
+ *
9
+ * Returns { action: "ok" | "return" | "retry", review?, result? }.
10
+ * - "ok" → caller proceeds with `review` (approved or with blocking
11
+ * issues).
12
+ * - "return" → caller returns `result` (pause / stalled).
13
+ * - "retry" → caller decrements its iteration counter and retries.
14
+ */
15
+
16
+ import { stageRegistry } from "../../stages/stage-classes.js";
17
+ import { runStage } from "../../stages/stage-executor.js";
18
+ import { handleStandbyResult } from "../error-recovery.js";
19
+
20
+ export async function runReviewerGateStage({ pipelineFlags, reviewerRole, config, logger, emitter, eventBase, session, trackBudget, i, reviewRules, task, repeatDetector, budgetSummary, askQuestion, brainCtx }) {
21
+ // Reviewer via StageRegistry (TSK-0336). ReviewerStage.canRun returns
22
+ // `reviewerEnabled !== false`; when false, runStage returns null and we
23
+ // synthesize the "disabled-by-pipeline" stub (same shape as the previous
24
+ // early-return). Otherwise execute() runs runReviewerStage.
25
+ const reviewerCtx = {
26
+ reviewerRole, config, logger, emitter, eventBase, session, trackBudget,
27
+ iteration: i, reviewRules, task, repeatDetector, budgetSummary, askQuestion,
28
+ brainCtx, pipelineFlags,
29
+ };
30
+ const reviewerResult = await runStage(stageRegistry.get("reviewer"), reviewerCtx);
31
+ if (reviewerResult === null) {
32
+ return {
33
+ action: "ok",
34
+ review: { approved: true, blocking_issues: [], non_blocking_suggestions: [], summary: "Reviewer disabled by pipeline", confidence: 1 }
35
+ };
36
+ }
37
+ if (reviewerResult.action === "pause") return { action: "return", result: reviewerResult.result };
38
+ const revStandby = await handleStandbyResult({ stageResult: reviewerResult, session, emitter, eventBase, i, stage: "reviewer", logger, config, askQuestion });
39
+ if (revStandby.handled) {
40
+ if (revStandby.action === "return") return { action: "return", result: revStandby.result };
41
+ if (revStandby.action === "skip") {
42
+ // Solomon said skip review — treat as approved
43
+ return { action: "ok", review: { approved: true, blocking_issues: [], non_blocking_suggestions: [], summary: "Review skipped (agent rate-limited, Solomon approved)", confidence: 0.7 } };
44
+ }
45
+ if (revStandby.action === "retry_reviewer_only") {
46
+ // Retry just the reviewer — use alternative agent if Solomon recommended one
47
+ let retryReviewerRole = reviewerRole;
48
+ const alt = session._alternative_agent;
49
+ if (alt?.stage === "reviewer" && alt?.provider) {
50
+ retryReviewerRole = { provider: alt.provider, model: null };
51
+ logger.info(`Retrying reviewer with alternative agent: ${alt.provider}`);
52
+ delete session._alternative_agent;
53
+ }
54
+ return runReviewerGateStage({ pipelineFlags: { reviewerEnabled: true }, reviewerRole: retryReviewerRole, config, logger, emitter, eventBase, session, trackBudget, i, reviewRules, task, repeatDetector, budgetSummary, askQuestion });
55
+ }
56
+ return { action: "retry" };
57
+ }
58
+ if (reviewerResult.stalled) return { action: "return", result: reviewerResult.stalledResult };
59
+ return { action: "ok", review: reviewerResult.review };
60
+ }