karajan-code 2.7.3 → 2.8.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 +4 -4
- package/package.json +24 -4
- package/scripts/diet-baseline.mjs +170 -0
- package/scripts/regen-arch-stats.sh +71 -0
- package/src/agents/availability.js +15 -9
- package/src/agents/claude-agent.js +143 -45
- package/src/agents/model-registry.js +1 -1
- package/src/audit/agent-readiness.js +240 -0
- package/src/bootstrap.js +26 -1
- package/src/brain/decision-tracker.js +3 -1
- package/src/brain/decisor.js +1 -1
- package/src/canvas/parse-md.js +238 -0
- package/src/canvas/schema.js +101 -0
- package/src/canvas/to-flat.js +85 -0
- package/src/canvas/validate.js +163 -0
- package/src/checks/binaries.js +1 -1
- package/src/checks/ports.js +97 -27
- package/src/checks/runner.js +26 -10
- package/src/checks/sonar.js +1 -1
- package/src/checks/system.js +1 -1
- package/src/checks/tokens.js +77 -24
- package/src/cli/_shared.js +30 -0
- package/src/cli/register-meta.js +204 -0
- package/src/cli/register-pipeline.js +212 -0
- package/src/cli/register-plan.js +152 -0
- package/src/cli/register-roles-skills.js +38 -0
- package/src/cli/register-sonar.js +28 -0
- package/src/cli.js +83 -468
- package/src/commands/agents.js +18 -6
- package/src/commands/architect.js +29 -2
- package/src/commands/audit.js +27 -3
- package/src/commands/board.js +160 -30
- package/src/commands/clean.js +83 -0
- package/src/commands/code.js +7 -2
- package/src/commands/discover.js +7 -2
- package/src/commands/init.js +2 -2
- package/src/commands/plan/_shared.js +40 -0
- package/src/commands/plan/add-hu.js +21 -0
- package/src/commands/plan/delete.js +9 -0
- package/src/commands/plan/generate.js +284 -0
- package/src/commands/plan/index.js +14 -0
- package/src/commands/plan/list.js +21 -0
- package/src/commands/plan/ready.js +46 -0
- package/src/commands/plan/remove-hu.js +17 -0
- package/src/commands/plan/show.js +81 -0
- package/src/commands/plan/validate.js +20 -0
- package/src/commands/plan.js +14 -291
- package/src/commands/researcher.js +7 -2
- package/src/commands/resume.js +24 -1
- package/src/commands/review.js +7 -2
- package/src/commands/run.js +52 -1
- package/src/commands/status.js +1 -1
- package/src/commands/sync.js +75 -0
- package/src/commands/triage.js +7 -2
- package/src/commands/undo.js +1 -1
- package/src/config/defaults.js +213 -0
- package/src/config/loader.js +137 -0
- package/src/config/overrides.js +199 -0
- package/src/config/role-resolver.js +159 -0
- package/src/config/test-harness.js +79 -0
- package/src/config.js +30 -609
- package/src/git/automation.js +2 -2
- package/src/guards/output-guard.js +0 -2
- package/src/hu/acceptance-runner.js +52 -10
- package/src/hu/store.js +11 -0
- package/src/mcp/handlers/direct-handlers.js +2 -5
- package/src/mcp/handlers/management-handlers.js +48 -4
- package/src/mcp/handlers/run-handler.js +9 -8
- package/src/mcp/preflight.js +32 -13
- package/src/mcp/server-handlers.js +4 -3
- package/src/mcp/suggest-handler.js +3 -2
- package/src/mcp/tools.js +15 -0
- package/src/orchestrator/agent-fallback.js +1 -1
- package/src/orchestrator/ci-integration.js +10 -10
- package/src/orchestrator/config-init.js +25 -7
- package/src/orchestrator/direct-actions.js +6 -2
- package/src/orchestrator/drivers/error-recovery.js +287 -0
- package/src/orchestrator/drivers/init-context.js +222 -0
- package/src/orchestrator/drivers/iteration-loop.js +311 -0
- package/src/orchestrator/drivers/iteration-phases/coder-and-refactorer.js +48 -0
- package/src/orchestrator/drivers/iteration-phases/guards.js +84 -0
- package/src/orchestrator/drivers/iteration-phases/handle-approved.js +29 -0
- package/src/orchestrator/drivers/iteration-phases/quality-gates.js +76 -0
- package/src/orchestrator/drivers/iteration-phases/reviewer-gate.js +60 -0
- package/src/orchestrator/drivers/post-loop.js +370 -0
- package/src/orchestrator/drivers/pre-loop-phases/auto-hu-batch.js +116 -0
- package/src/orchestrator/drivers/pre-loop-phases/config-deprecations.js +47 -0
- package/src/orchestrator/drivers/pre-loop-phases/ensure-addyosmani-skills.js +84 -0
- package/src/orchestrator/drivers/pre-loop-phases/inject-loaded-plan.js +268 -0
- package/src/orchestrator/drivers/pre-loop.js +434 -0
- package/src/orchestrator/drivers/run-hu-batch.js +367 -0
- package/src/orchestrator/flow-control.js +4 -3
- package/src/orchestrator/flow-runner.js +121 -2019
- package/src/orchestrator/fs-leak-detector.js +156 -0
- package/src/orchestrator/hu-sub-pipeline.js +197 -9
- package/src/orchestrator/integrations.js +88 -0
- package/src/orchestrator/post-loop-stages.js +35 -8
- package/src/orchestrator/preflight-checks.js +30 -3
- package/src/orchestrator/repair/repair-detector.js +117 -0
- package/src/orchestrator/repair/repair-runner.js +142 -0
- package/src/orchestrator/reviewer-fallback.js +1 -1
- package/src/orchestrator/solomon-escalation.js +3 -2
- package/src/orchestrator/solomon-rules.js +7 -3
- package/src/orchestrator/stages/architect-stage.js +12 -11
- package/src/orchestrator/stages/coder-stage.js +56 -7
- package/src/orchestrator/stages/hu-reviewer-phases/detect-and-apply-splits.js +127 -0
- package/src/orchestrator/stages/hu-reviewer-stage.js +6 -79
- package/src/orchestrator/stages/planner-stage.js +1 -1
- package/src/orchestrator/stages/research-stage.js +1 -1
- package/src/orchestrator/stages/reviewer-stage.js +7 -7
- package/src/orchestrator/stages/sonar-stage.js +94 -10
- package/src/orchestrator/stages/stage-classes.js +86 -0
- package/src/orchestrator/stages/stage-executor.js +1 -1
- package/src/orchestrator/stages/triage-stage.js +1 -1
- package/src/orchestrator/standby.js +3 -2
- package/src/orchestrator/verification-gate.js +20 -12
- package/src/plan/adr-generator.js +170 -0
- package/src/plan/adr-loader.js +60 -0
- package/src/plan/plan-executor.js +19 -1
- package/src/plan/plan-hu-ops.js +170 -1
- package/src/plan/plan-reviewer.js +199 -0
- package/src/plan/plan-schema.js +69 -1
- package/src/plan/plan-store.js +40 -3
- package/src/plan/spec-detector.js +61 -0
- package/src/plan/tests-synthesizer.js +116 -0
- package/src/planning-game/client.js +3 -14
- package/src/planning-game/decomposition.js +1 -1
- package/src/planning-game/pipeline-adapter.js +36 -5
- package/src/prompts/coder.js +172 -1
- package/src/prompts/hu-reviewer.js +0 -1
- package/src/prompts/planner.js +81 -4
- package/src/review/diff-generator.js +35 -22
- package/src/review/snapshot-diff.js +1 -1
- package/src/roles/coder-role.js +25 -3
- package/src/roles/planner-role.js +42 -1
- package/src/roles/repairer-role.js +181 -0
- package/src/roles/tester-role.js +70 -4
- package/src/session/mutators.js +304 -0
- package/src/session/runtime-overrides.js +59 -0
- package/src/{session-store.js → session/store.js} +8 -2
- package/src/skills/semantic-detector.js +10 -5
- package/src/skills/skill-detector.js +1 -1
- package/src/sonar/cloud-scanner.js +0 -1
- package/src/sonar/credentials.js +14 -6
- package/src/sonar/discovery.js +123 -0
- package/src/sonar/manager.js +18 -0
- package/src/sync/detect-drift.js +113 -0
- package/src/tracker-bootstrap.js +44 -0
- package/src/utils/agent-detect.js +15 -11
- package/src/utils/board-prompt-bridge.js +109 -0
- package/src/utils/cli-progress.js +161 -0
- package/src/utils/cwd-confirm.js +39 -0
- package/src/utils/derive-project-name-from-cwd.js +119 -0
- package/src/utils/garbage-collector.js +396 -0
- package/src/utils/git.js +16 -4
- package/src/utils/locale.js +1 -1
- package/src/utils/paths.js +21 -1
- package/src/utils/process.js +1 -1
- package/src/utils/prompt-project-name.js +35 -0
- package/src/utils/run-context.js +75 -0
- package/src/utils/run-log.js +12 -0
- package/src/utils/task-file.js +1 -1
- package/src/utils/telemetry.js +13 -1
- package/src/webperf/devtools-detect.js +1 -1
- package/templates/coder-rules.md +13 -0
- package/templates/roles/coder.md +34 -0
- package/templates/roles/repairer.md +94 -0
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HU sub-pipeline driver — extracted from flow-runner.js in PR-L
|
|
3
|
+
* (audit follow-up). Was 244 LOC inlined inside _runFlowInner, the
|
|
4
|
+
* second-worst long-function per `kj audit`.
|
|
5
|
+
*
|
|
6
|
+
* Behaviour identical to the inlined version — the closure passed to
|
|
7
|
+
* runHuSubPipeline is moved verbatim, only its surrounding context
|
|
8
|
+
* (logger, emitter, ctx, askQuestion) becomes function parameters.
|
|
9
|
+
*
|
|
10
|
+
* The extraction also fixes a latent bug: flow-runner.js called
|
|
11
|
+
* `runCoderStage`, `runSonarStage`, `generateDiff` and
|
|
12
|
+
* `runSecurityStage` without static OR dynamic imports. Those
|
|
13
|
+
* references would ReferenceError at runtime on the HU sub-pipeline
|
|
14
|
+
* path; the fact that no test caught it suggests the path was rarely
|
|
15
|
+
* exercised end-to-end (preflight failures often abort earlier).
|
|
16
|
+
* This module imports them explicitly.
|
|
17
|
+
*
|
|
18
|
+
* Returns:
|
|
19
|
+
* - { handled: true, result } — needsSubPipeline matched; caller
|
|
20
|
+
* must short-circuit and return `result`.
|
|
21
|
+
* - { handled: false } — caller falls through to the standard
|
|
22
|
+
* single-task pipeline (runIterationLoop).
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { emitProgress, makeEvent } from "../../utils/events.js";
|
|
26
|
+
import { needsSubPipeline, runHuSubPipeline } from "../hu-sub-pipeline.js";
|
|
27
|
+
import { runCoderStage } from "../stages/coder-stage.js";
|
|
28
|
+
import { runSonarStage } from "../stages/sonar-stage.js";
|
|
29
|
+
import { runIterationLoop } from "./iteration-loop.js";
|
|
30
|
+
import { classifyFailure, createFailureTracker } from "../repair/repair-detector.js";
|
|
31
|
+
import { runRepair } from "../repair/repair-runner.js";
|
|
32
|
+
import { writeHistoryRecord } from "./post-loop.js";
|
|
33
|
+
import { setReviewerFeedback } from "../../session/mutators.js";
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @param {object} args
|
|
37
|
+
* @param {object} args.ctx — PipelineContext from initFlowContext
|
|
38
|
+
* @param {string} args.task — original task description (for writeHistoryRecord)
|
|
39
|
+
* @param {object} args.askQuestion
|
|
40
|
+
* @param {object} args.emitter
|
|
41
|
+
* @param {object} args.logger
|
|
42
|
+
* @returns {Promise<{handled: boolean, result?: object}>}
|
|
43
|
+
*/
|
|
44
|
+
export async function runHuBatch({ ctx, task, askQuestion, emitter, logger }) {
|
|
45
|
+
if (!needsSubPipeline(ctx.stageResults.huReviewer)) {
|
|
46
|
+
return { handled: false };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// --- HU Sub-Pipeline: run each certified HU as an independent iteration loop ---
|
|
50
|
+
logger.info(`HU sub-pipeline: ${ctx.stageResults.huReviewer.certified} certified stories — running each as a sub-pipeline`);
|
|
51
|
+
emitProgress(emitter, makeEvent("hu:sub-pipeline:start", { ...ctx.eventBase, stage: "hu-sub-pipeline" }, {
|
|
52
|
+
message: `Running ${ctx.stageResults.huReviewer.certified} HUs as sub-pipelines`,
|
|
53
|
+
detail: { total: ctx.stageResults.huReviewer.total, certified: ctx.stageResults.huReviewer.certified }
|
|
54
|
+
}));
|
|
55
|
+
|
|
56
|
+
// Per-HU pipeline: focused max_iterations, fresh Brain state, own git branch.
|
|
57
|
+
const originalMaxIterations = ctx.config.max_iterations;
|
|
58
|
+
const huMaxIterations = ctx.config.hu_max_iterations ?? 3;
|
|
59
|
+
const huBranches = new Map();
|
|
60
|
+
const { prepareHuBranch, finalizeHuCommit } = await import("../../git/hu-automation.js");
|
|
61
|
+
|
|
62
|
+
// PR F (v2.7.5): the per-HU loop needs three pieces of plan context:
|
|
63
|
+
// • ADRs (loaded once from disk, applies to every HU on this plan)
|
|
64
|
+
// • the plan-reviewer findings (rides on the huBatch)
|
|
65
|
+
// • each HU's spec_section (rides on the story)
|
|
66
|
+
// Loading ADRs once outside the loop avoids hitting the filesystem
|
|
67
|
+
// per-HU. When the plan never went through `kj plan` (legacy
|
|
68
|
+
// auto-generated batch), planId is null → loadActiveAdrs falls back
|
|
69
|
+
// to the "_loose" bucket and returns [] silently.
|
|
70
|
+
const planId = ctx.stageResults.huReviewer.planId || null;
|
|
71
|
+
const reviewerFindings = ctx.stageResults.huReviewer.review || null;
|
|
72
|
+
let planAdrs = [];
|
|
73
|
+
try {
|
|
74
|
+
const { loadActiveAdrs } = await import("../../plan/adr-loader.js");
|
|
75
|
+
planAdrs = await loadActiveAdrs(ctx.config.projectDir || process.cwd(), planId);
|
|
76
|
+
if (planAdrs.length > 0) {
|
|
77
|
+
logger.info(`Plan ${planId || "(loose)"}: loaded ${planAdrs.length} ADR(s) for coder context`);
|
|
78
|
+
}
|
|
79
|
+
} catch (err) {
|
|
80
|
+
logger.warn(`ADR loader failed (non-blocking): ${err.message}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const subPipelineResult = await runHuSubPipeline({
|
|
84
|
+
huReviewerResult: ctx.stageResults.huReviewer,
|
|
85
|
+
runIterationFn: async (huTask, story) => {
|
|
86
|
+
ctx.config.max_iterations = huMaxIterations;
|
|
87
|
+
if (ctx.brainCtx?.enabled) {
|
|
88
|
+
ctx.brainCtx.extensionCount = 0;
|
|
89
|
+
const { createBrainContext } = await import("../brain-coordinator.js");
|
|
90
|
+
const fresh = createBrainContext({ enabled: true });
|
|
91
|
+
ctx.brainCtx.feedbackQueue = fresh.feedbackQueue;
|
|
92
|
+
ctx.brainCtx.verificationTracker = fresh.verificationTracker;
|
|
93
|
+
}
|
|
94
|
+
// Apply per-HU policies based on task_type (infra skips reviewer/sonar/tdd)
|
|
95
|
+
const { applyPolicies } = await import("../../guards/policy-resolver.js");
|
|
96
|
+
const huPolicies = applyPolicies({ taskType: story.task_type, policies: ctx.config.policies });
|
|
97
|
+
const savedFlags = { ...ctx.pipelineFlags };
|
|
98
|
+
if (!huPolicies.reviewer) ctx.pipelineFlags.reviewerEnabled = false;
|
|
99
|
+
if (!huPolicies.tdd) ctx.config.development = { ...ctx.config.development, methodology: "standard", require_test_changes: false };
|
|
100
|
+
if (!huPolicies.sonar) ctx.config.sonarqube = { ...ctx.config.sonarqube, enabled: false };
|
|
101
|
+
if (!huPolicies.testsRequired) ctx.pipelineFlags.testerEnabled = false;
|
|
102
|
+
logger.info(`HU ${story.id} (${story.task_type}): policies → reviewer=${huPolicies.reviewer}, tdd=${huPolicies.tdd}, sonar=${huPolicies.sonar}, tests=${huPolicies.testsRequired}`);
|
|
103
|
+
|
|
104
|
+
const branchName = await prepareHuBranch({ story, huBranches, config: ctx.config, logger });
|
|
105
|
+
const projectDir = ctx.config.projectDir || process.cwd();
|
|
106
|
+
|
|
107
|
+
// If HU has acceptance_tests, Brain runs them as the gate instead of
|
|
108
|
+
// the standard reviewer/tester pipeline. This is the radical fix:
|
|
109
|
+
// concrete executable tests replace subjective reviewer opinions.
|
|
110
|
+
if (story.acceptance_tests?.length > 0) {
|
|
111
|
+
const { runAcceptanceTests, buildDiagnosticPrompt } = await import("../../hu/acceptance-runner.js");
|
|
112
|
+
// Issue #538 — Repairer runtime safety net. The tracker remembers
|
|
113
|
+
// each test's last failure signature; classifyFailure flags
|
|
114
|
+
// structural bugs (broken jq, shell parse error, missing tool)
|
|
115
|
+
// after 2 consecutive identical failures so we don't burn 5
|
|
116
|
+
// iterations against an irreparable test (the 2026-04-29 bug).
|
|
117
|
+
const failureTracker = createFailureTracker();
|
|
118
|
+
|
|
119
|
+
for (let attempt = 1; attempt <= ctx.config.max_iterations; attempt++) {
|
|
120
|
+
logger.info(`HU ${story.id}: coder iteration ${attempt}/${ctx.config.max_iterations}`);
|
|
121
|
+
emitProgress(emitter, makeEvent("iteration:start", { ...ctx.eventBase, stage: "iteration" }, {
|
|
122
|
+
message: `Iteration ${attempt}/${ctx.config.max_iterations}`,
|
|
123
|
+
detail: { iteration: attempt, maxIterations: ctx.config.max_iterations }
|
|
124
|
+
}));
|
|
125
|
+
|
|
126
|
+
// Coder runs with the HU task + any diagnostic feedback from previous attempt.
|
|
127
|
+
// Phase 2 of tests-first (v2.7.5): also hand over the declared
|
|
128
|
+
// acceptance_tests so the coder has the contract from turn 1,
|
|
129
|
+
// not only after the first failed run.
|
|
130
|
+
const coderResult = await runCoderStage({
|
|
131
|
+
coderRoleInstance: ctx.coderRoleInstance, coderRole: ctx.coderRole,
|
|
132
|
+
config: ctx.config, logger, emitter, eventBase: ctx.eventBase,
|
|
133
|
+
session: ctx.session, plannedTask: ctx.plannedTask,
|
|
134
|
+
trackBudget: ctx.trackBudget, iteration: attempt, brainCtx: ctx.brainCtx,
|
|
135
|
+
acceptanceTests: story.acceptance_tests,
|
|
136
|
+
// PR F (v2.7.5): plan-aware coder context. ADRs are
|
|
137
|
+
// shared across the plan, the rest are scoped to this
|
|
138
|
+
// HU. buildCoderPrompt skips any section whose data is
|
|
139
|
+
// null/empty so legacy paths render the same prompt.
|
|
140
|
+
adrs: planAdrs,
|
|
141
|
+
specSection: story.spec_section || null,
|
|
142
|
+
reviewerFindings,
|
|
143
|
+
huId: story.id
|
|
144
|
+
});
|
|
145
|
+
if (coderResult?.action === "standby" || coderResult?.action === "pause") {
|
|
146
|
+
return coderResult?.result || { approved: false, reason: "coder_failed" };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Sonar quality gate (sw task_type only, when policies say sonar=true)
|
|
150
|
+
if (huPolicies.sonar && ctx.config.sonarqube?.enabled) {
|
|
151
|
+
try {
|
|
152
|
+
const sonarResult = await runSonarStage({
|
|
153
|
+
config: ctx.config, logger, emitter, eventBase: ctx.eventBase,
|
|
154
|
+
session: ctx.session, trackBudget: ctx.trackBudget, iteration: attempt,
|
|
155
|
+
// sonarState is required: runSonarStage reads/writes
|
|
156
|
+
// .issuesInitial / .issuesFinal on it. Forgetting this
|
|
157
|
+
// surfaces as `Cannot read properties of undefined
|
|
158
|
+
// (reading 'issuesInitial')` and the catch below swallows
|
|
159
|
+
// it as "sonar stage failed (non-blocking)" — the gate
|
|
160
|
+
// never actually runs for the HU, every iteration burns
|
|
161
|
+
// budget for nothing. Mirrors iteration-loop.js.
|
|
162
|
+
sonarState: ctx.sonarState,
|
|
163
|
+
repeatDetector: ctx.repeatDetector,
|
|
164
|
+
budgetSummary: ctx.budgetSummary,
|
|
165
|
+
askQuestion, brainCtx: ctx.brainCtx
|
|
166
|
+
});
|
|
167
|
+
if (sonarResult?.action === "continue") {
|
|
168
|
+
// Sonar failed — add to feedback for next coder attempt
|
|
169
|
+
ctx.plannedTask = `${huTask}\n\n--- SONAR FAILURE ---\n${ctx.session.last_reviewer_feedback}`;
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
} catch (err) {
|
|
173
|
+
logger.warn(`HU ${story.id}: sonar stage failed (non-blocking): ${err.message}`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Brain runs acceptance tests
|
|
178
|
+
logger.info(`HU ${story.id}: running ${story.acceptance_tests.length} acceptance tests`);
|
|
179
|
+
emitProgress(emitter, makeEvent("hu:acceptance-start", { ...ctx.eventBase, stage: "acceptance" }, {
|
|
180
|
+
message: `Running ${story.acceptance_tests.length} acceptance tests`,
|
|
181
|
+
detail: { huId: story.id, testCount: story.acceptance_tests.length }
|
|
182
|
+
}));
|
|
183
|
+
|
|
184
|
+
const testResult = await runAcceptanceTests(story.acceptance_tests, projectDir);
|
|
185
|
+
emitProgress(emitter, makeEvent("hu:acceptance-end", { ...ctx.eventBase, stage: "acceptance" }, {
|
|
186
|
+
status: testResult.allPassed ? "ok" : "fail",
|
|
187
|
+
message: testResult.summary,
|
|
188
|
+
detail: { allPassed: testResult.allPassed, results: testResult.results.map(r => ({ cmd: r.cmd, passed: r.passed })) }
|
|
189
|
+
}));
|
|
190
|
+
|
|
191
|
+
if (testResult.allPassed) {
|
|
192
|
+
// Phase 3 of tests-first (v2.7.5): the shell gate is green,
|
|
193
|
+
// but any Gherkin scenarios are still "pending" — they need
|
|
194
|
+
// the tester to translate them into real test files before
|
|
195
|
+
// we can call the HU done. If none are pending, approve
|
|
196
|
+
// immediately like before.
|
|
197
|
+
const pendingGherkinTests = (story.acceptance_tests || []).filter(
|
|
198
|
+
(t) => t && typeof t === "object" && t.type === "gherkin"
|
|
199
|
+
);
|
|
200
|
+
if (pendingGherkinTests.length === 0) {
|
|
201
|
+
logger.info(`HU ${story.id}: all shell acceptance tests PASSED, no Gherkin to translate — approved`);
|
|
202
|
+
await finalizeHuCommit({ story, branchName, config: ctx.config, logger });
|
|
203
|
+
return { approved: true, sessionId: ctx.session.id, reason: "acceptance_tests_passed" };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Shell tests passed; Gherkin needs translation. Invoke the
|
|
207
|
+
// tester with both so it writes code tests for the Gherkin,
|
|
208
|
+
// runs the suite, and returns verdict.
|
|
209
|
+
logger.info(`HU ${story.id}: shell gate passed, asking tester to translate ${pendingGherkinTests.length} Gherkin scenario(s)`);
|
|
210
|
+
const { runTesterStage } = await import("../post-loop-stages.js");
|
|
211
|
+
const shellResults = testResult.results.filter((r) => r.type !== "gherkin");
|
|
212
|
+
const testerOutcome = await runTesterStage({
|
|
213
|
+
config: ctx.config, logger, emitter, eventBase: ctx.eventBase,
|
|
214
|
+
session: ctx.session, coderRole: ctx.coderRole, trackBudget: ctx.trackBudget,
|
|
215
|
+
iteration: attempt, task: huTask, diff: null, askQuestion,
|
|
216
|
+
pendingGherkinTests, shellTestResults: shellResults,
|
|
217
|
+
});
|
|
218
|
+
const testerStage = testerOutcome?.stageResult;
|
|
219
|
+
if (testerOutcome?.action !== "continue" && testerStage?.verdict === "pass") {
|
|
220
|
+
logger.info(`HU ${story.id}: tester translated Gherkin and verdict=pass — approved`);
|
|
221
|
+
await finalizeHuCommit({ story, branchName, config: ctx.config, logger });
|
|
222
|
+
return { approved: true, sessionId: ctx.session.id, reason: "acceptance_tests_passed" };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Tester rejected (translated tests failed or coverage
|
|
226
|
+
// inadequate). Feed its diagnostic back to the coder for
|
|
227
|
+
// another iteration.
|
|
228
|
+
const failing = testerStage?.failing_scenarios?.join("\n- ") || testerStage?.summary || "tester rejected";
|
|
229
|
+
const diagnostic = [
|
|
230
|
+
"Tester translated the Gherkin acceptance tests and some FAILED:",
|
|
231
|
+
failing ? `- ${failing}` : "",
|
|
232
|
+
"",
|
|
233
|
+
"Fix the implementation so every scenario passes. Do NOT soften the scenarios.",
|
|
234
|
+
].filter(Boolean).join("\n");
|
|
235
|
+
logger.warn(`HU ${story.id}: tester verdict=fail after Gherkin translation — sending back to coder`);
|
|
236
|
+
setReviewerFeedback(ctx.session, diagnostic);
|
|
237
|
+
ctx.plannedTask = `${huTask}\n\n--- GHERKIN TRANSLATION FAILURES ---\n${diagnostic}`;
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Brain diagnoses failures and sends concrete fix to coder
|
|
242
|
+
const failed = testResult.results.filter(r => !r.passed);
|
|
243
|
+
|
|
244
|
+
// Repairer (#538): for each failing shell test, see if it's been
|
|
245
|
+
// failing the same way 2× in a row AND looks structurally broken
|
|
246
|
+
// (jq syntax, shell parse error, missing tool, etc.). If yes,
|
|
247
|
+
// invoke RepairerRole to rewrite the test in the plan + batch
|
|
248
|
+
// before iterating again. If unfixable, escalate (fail the HU
|
|
249
|
+
// immediately rather than burn the rest of max_iterations).
|
|
250
|
+
let repairedAny = false;
|
|
251
|
+
let repairEscalate = null;
|
|
252
|
+
for (const f of failed) {
|
|
253
|
+
if (f.type !== "shell") continue;
|
|
254
|
+
const consecutive = failureTracker.record(f.cmd, f.output);
|
|
255
|
+
const cls = classifyFailure({
|
|
256
|
+
cmd: f.cmd, errorOutput: f.output, consecutive,
|
|
257
|
+
});
|
|
258
|
+
if (!cls.infeasible) continue;
|
|
259
|
+
const testIndex = story.acceptance_tests.findIndex((t) => {
|
|
260
|
+
const c = typeof t === "object" ? t.content : t;
|
|
261
|
+
return c === f.cmd;
|
|
262
|
+
});
|
|
263
|
+
if (testIndex < 0) continue;
|
|
264
|
+
// Mutate `story.acceptance_tests` in place. The same story
|
|
265
|
+
// reference lives inside runHuSubPipeline's batch (passed
|
|
266
|
+
// by ref), so the next iteration's acceptance runner sees
|
|
267
|
+
// the fixed test without us needing batch+batchSessionId
|
|
268
|
+
// in this scope. The reconcile from PR #544 will sync the
|
|
269
|
+
// plan JSON on the next run.
|
|
270
|
+
const repair = await runRepair({
|
|
271
|
+
story, testIndex, errorOutput: f.output,
|
|
272
|
+
batchSessionId: ctx.stageResults.huReviewer.batchSessionId,
|
|
273
|
+
// batch unavailable in this closure scope — runRepair
|
|
274
|
+
// gates saveHuBatch on its presence and skips when
|
|
275
|
+
// missing; the in-memory mutation alone is sufficient
|
|
276
|
+
// for the next acceptance run within this same HU loop.
|
|
277
|
+
batch: null,
|
|
278
|
+
config: ctx.config, logger, emitter, eventBase: ctx.eventBase,
|
|
279
|
+
failureKind: cls.kind,
|
|
280
|
+
savePlanPatch: null,
|
|
281
|
+
});
|
|
282
|
+
if (repair.escalate) { repairEscalate = repair.reason; break; }
|
|
283
|
+
if (repair.repaired) {
|
|
284
|
+
repairedAny = true;
|
|
285
|
+
failureTracker.reset(f.cmd);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (repairEscalate) {
|
|
290
|
+
logger.warn(`HU ${story.id}: Repairer escalated — ${repairEscalate}`);
|
|
291
|
+
return { approved: false, sessionId: ctx.session.id, reason: "repair_escalated" };
|
|
292
|
+
}
|
|
293
|
+
if (repairedAny) {
|
|
294
|
+
// Tests were rewritten — restart iteration with the fixed
|
|
295
|
+
// tests in place; do NOT increment attempt against the user
|
|
296
|
+
// because the failure was the planner's bug, not the coder's.
|
|
297
|
+
logger.info(`HU ${story.id}: Repairer fixed test(s); restarting acceptance gate without consuming iteration`);
|
|
298
|
+
attempt -= 1;
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// No repair happened — normal coder feedback path.
|
|
303
|
+
const diagnostic = buildDiagnosticPrompt(failed);
|
|
304
|
+
logger.warn(`HU ${story.id}: ${failed.length} acceptance test(s) FAILED — sending diagnostic to coder`);
|
|
305
|
+
setReviewerFeedback(ctx.session, diagnostic);
|
|
306
|
+
ctx.plannedTask = `${huTask}\n\n--- ACCEPTANCE TEST FAILURES ---\n${diagnostic}`;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// All iterations exhausted
|
|
310
|
+
logger.warn(`HU ${story.id}: max iterations reached with acceptance tests still failing`);
|
|
311
|
+
return { approved: false, sessionId: ctx.session.id, reason: "acceptance_tests_failed" };
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Fallback: no acceptance_tests → standard pipeline (reviewer/tester)
|
|
315
|
+
try {
|
|
316
|
+
const result = await runIterationLoop(ctx, { task: huTask, askQuestion, emitter, logger });
|
|
317
|
+
if (result?.approved) {
|
|
318
|
+
await finalizeHuCommit({ story, branchName, config: ctx.config, logger });
|
|
319
|
+
}
|
|
320
|
+
return result;
|
|
321
|
+
} finally {
|
|
322
|
+
ctx.config.max_iterations = originalMaxIterations;
|
|
323
|
+
Object.assign(ctx.pipelineFlags, savedFlags);
|
|
324
|
+
}
|
|
325
|
+
},
|
|
326
|
+
emitter,
|
|
327
|
+
eventBase: ctx.eventBase,
|
|
328
|
+
logger,
|
|
329
|
+
config: ctx.config,
|
|
330
|
+
// PR1 (live HU status): forward the per-HU status updater so
|
|
331
|
+
// hu-sub-pipeline can patch the plan JSON on every transition,
|
|
332
|
+
// not only at the end of the run. The board's chokidar then
|
|
333
|
+
// fires per-HU and the Kanban columns update in real time.
|
|
334
|
+
onStatusChange: ctx.session?._liveStatusUpdater || null,
|
|
335
|
+
// PR3 (per-HU outcome): same pattern, but for the rich
|
|
336
|
+
// outcome blob written ONCE per HU at the end of runSingleHu.
|
|
337
|
+
onOutcome: ctx.session?._liveOutcomeUpdater || null
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
emitProgress(emitter, makeEvent("hu:sub-pipeline:end", { ...ctx.eventBase, stage: "hu-sub-pipeline" }, {
|
|
341
|
+
status: subPipelineResult.approved ? "ok" : "fail",
|
|
342
|
+
message: subPipelineResult.approved
|
|
343
|
+
? "All HUs completed successfully"
|
|
344
|
+
: `Sub-pipeline finished with failures (${subPipelineResult.blockedIds.length} blocked)`,
|
|
345
|
+
detail: { results: subPipelineResult.results, blockedIds: subPipelineResult.blockedIds }
|
|
346
|
+
}));
|
|
347
|
+
|
|
348
|
+
// Sync results back to plan file if this was a plan-based run
|
|
349
|
+
if (ctx.session._syncResultsToPlan) {
|
|
350
|
+
try {
|
|
351
|
+
await ctx.session._syncResultsToPlan(subPipelineResult);
|
|
352
|
+
logger.info("Plan updated with execution results");
|
|
353
|
+
} catch (err) {
|
|
354
|
+
logger.warn(`Failed to sync results to plan: ${err.message}`);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const finalResult = {
|
|
359
|
+
approved: subPipelineResult.approved,
|
|
360
|
+
sessionId: ctx.session.id,
|
|
361
|
+
huResults: subPipelineResult.results,
|
|
362
|
+
blockedIds: subPipelineResult.blockedIds,
|
|
363
|
+
planId: ctx.session._planRef?.planId || null
|
|
364
|
+
};
|
|
365
|
+
await writeHistoryRecord({ sessionId: ctx.session.id, task, result: finalResult, logger });
|
|
366
|
+
return { handled: true, result: finalResult };
|
|
367
|
+
}
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* Session flow control — checkpoints, timeouts, budget checks.
|
|
3
3
|
* Extracted from orchestrator.js — session management, not orchestration.
|
|
4
4
|
*/
|
|
5
|
-
import { markSessionStatus, addCheckpoint } from "../session
|
|
5
|
+
import { markSessionStatus, addCheckpoint } from "../session/store.js";
|
|
6
|
+
import { setRetryCount } from "../session/mutators.js";
|
|
6
7
|
import { emitProgress, makeEvent } from "../utils/events.js";
|
|
7
8
|
import { msg, getLang } from "../utils/messages.js";
|
|
8
9
|
|
|
@@ -33,12 +34,12 @@ export function takeCheckpointSnapshot(session) {
|
|
|
33
34
|
*/
|
|
34
35
|
export function shouldAutoContinueCheckpoint(session, hasProgress) {
|
|
35
36
|
if (hasProgress) {
|
|
36
|
-
session
|
|
37
|
+
setRetryCount(session, "checkpoint_stall", 0);
|
|
37
38
|
return { autoContinue: true, reason: "progress_detected" };
|
|
38
39
|
}
|
|
39
40
|
const wasRateLimited = (session.standby_retry_count || 0) > 0;
|
|
40
41
|
const consecutiveStalls = (session._checkpoint_stall_count || 0) + 1;
|
|
41
|
-
session
|
|
42
|
+
setRetryCount(session, "checkpoint_stall", consecutiveStalls);
|
|
42
43
|
if (wasRateLimited && consecutiveStalls < 3) {
|
|
43
44
|
return { autoContinue: true, reason: "recoverable_stall" };
|
|
44
45
|
}
|