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.
- package/README.md +1 -1
- 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/audit/basal-cost.js +92 -9
- package/src/audit/deterministic-summary.js +200 -0
- package/src/audit/osv-findings.js +162 -0
- package/src/audit/semgrep-findings.js +117 -0
- package/src/audit/sonar-findings.js +81 -0
- package/src/audit/webperf-input.js +90 -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 +3 -3
- package/src/checks/ci.js +2 -2
- package/src/checks/config-files.js +6 -6
- package/src/checks/dir-setup.js +4 -0
- package/src/checks/mcp-health.js +6 -0
- package/src/checks/ports.js +91 -29
- package/src/checks/rtk.js +1 -1
- package/src/checks/skills.js +5 -1
- package/src/checks/sonar.js +2 -2
- package/src/checks/system.js +3 -3
- package/src/checks/tokens.js +1 -1
- package/src/checks/types.js +0 -12
- package/src/cli/_shared.js +30 -0
- package/src/cli/register-meta.js +218 -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 +265 -30
- 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/doctor.js +1 -1
- 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 +78 -0
- package/src/config.js +30 -632
- 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 +11 -11
- package/src/orchestrator/config-init.js +25 -7
- package/src/orchestrator/direct-actions.js +7 -3
- 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 +301 -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 +431 -0
- package/src/orchestrator/drivers/run-hu-batch.js +367 -0
- package/src/orchestrator/feedback-queue.js +0 -1
- package/src/orchestrator/flow-control.js +4 -3
- package/src/orchestrator/flow-runner.js +121 -2077
- package/src/orchestrator/fs-leak-detector.js +156 -0
- package/src/orchestrator/hu-sub-pipeline.js +197 -9
- package/src/orchestrator/integrations.js +81 -0
- package/src/orchestrator/post-loop-stages.js +35 -8
- package/src/orchestrator/preflight-checks.js +20 -7
- 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/role-output-compressor.js +0 -1
- package/src/orchestrator/session-journal.js +4 -41
- 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 +84 -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 -17
- package/src/plan/spec-detector.js +61 -0
- package/src/plan/tests-synthesizer.js +116 -0
- package/src/planning-game/client.js +3 -28
- package/src/planning-game/decomposition.js +1 -1
- package/src/planning-game/pipeline-adapter.js +37 -6
- package/src/prompts/audit.js +206 -6
- package/src/prompts/coder.js +172 -1
- package/src/prompts/hu-reviewer.js +0 -1
- package/src/prompts/planner.js +81 -4
- package/src/prompts/rtk-snippet.js +1 -1
- package/src/review/diff-generator.js +35 -22
- package/src/review/snapshot-diff.js +1 -1
- package/src/roles/audit-role.js +125 -13
- 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} +2 -2
- package/src/skills/semantic-detector.js +10 -5
- package/src/skills/skill-detector.js +1 -1
- package/src/sonar/api.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/display/event-handlers.js +1 -1
- package/src/utils/display/header.js +1 -1
- package/src/utils/display/session-summary.js +3 -3
- package/src/utils/display/solomon.js +1 -1
- 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/role-assigner.js +1 -1
- 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
|
@@ -16,7 +16,6 @@ import { emitProgress, makeEvent } from "../utils/events.js";
|
|
|
16
16
|
import { msg, getLang } from "../utils/messages.js";
|
|
17
17
|
import {
|
|
18
18
|
resolveSonarHost,
|
|
19
|
-
resolveSonarToken,
|
|
20
19
|
resolveSonarTokenAsync,
|
|
21
20
|
resolveSonarCredentials,
|
|
22
21
|
} from "../sonar/config-resolver.js";
|
|
@@ -30,6 +29,7 @@ import { getPortChecks } from "../checks/ports.js";
|
|
|
30
29
|
import { getTokenChecks } from "../checks/tokens.js";
|
|
31
30
|
import { getMcpHealthChecks } from "../checks/mcp-health.js";
|
|
32
31
|
import { getSkillsChecks } from "../checks/skills.js";
|
|
32
|
+
import { resolveTestHarness } from "../config/test-harness.js";
|
|
33
33
|
|
|
34
34
|
function parseJsonSafe(text) {
|
|
35
35
|
try {
|
|
@@ -168,6 +168,14 @@ async function checkSecurityAgent(config) {
|
|
|
168
168
|
* @returns {{ ok: boolean, checks: object[], remediations: string[], configOverrides: object, warnings: string[], errors: object[] }}
|
|
169
169
|
*/
|
|
170
170
|
export async function runPreflightChecks({ config, logger, emitter, eventBase, resolvedPolicies, securityEnabled }) {
|
|
171
|
+
// Defensive test-harness resolution for direct callers that bypass loadConfig()
|
|
172
|
+
// (notably tests/preflight-checks.test.js and tests/sonar-token-flow.test.js
|
|
173
|
+
// which construct raw configs). Production callers go through loadConfig
|
|
174
|
+
// which already populates config.testHarness once via resolveTestHarness().
|
|
175
|
+
// Idempotent — no-op when already present.
|
|
176
|
+
if (config && !config.testHarness) {
|
|
177
|
+
config.testHarness = resolveTestHarness(config.testHarness);
|
|
178
|
+
}
|
|
171
179
|
// Sonar is intrinsic to Karajan for code tasks (sw/refactor/add-tests).
|
|
172
180
|
// Since v2.7.4 it is NOT toggleable via `config.sonarqube.enabled` —
|
|
173
181
|
// that field is ignored (deprecation warning emitted at config load).
|
|
@@ -177,11 +185,14 @@ export async function runPreflightChecks({ config, logger, emitter, eventBase, r
|
|
|
177
185
|
// no-code skip it. Solomon may decide to skip a single iteration via
|
|
178
186
|
// rule alerts — that's a runtime decision, not a config option.
|
|
179
187
|
//
|
|
180
|
-
// Test-
|
|
181
|
-
//
|
|
182
|
-
//
|
|
183
|
-
//
|
|
184
|
-
|
|
188
|
+
// Test-harness escape hatch via config.testHarness.disableSonarStage
|
|
189
|
+
// — production code reads `config?.testHarness?.disableSonarStage`,
|
|
190
|
+
// never `globalThis.*`. The legacy `globalThis.__KJ_DISABLE_SONAR_STAGE`
|
|
191
|
+
// shape is documented (and exclusively read) in
|
|
192
|
+
// src/config/test-harness.js, where the loader translates it into
|
|
193
|
+
// the typed config slot above. ESLint rule (#557) blocks any
|
|
194
|
+
// re-introduction outside that one file.
|
|
195
|
+
const sonarStageDisabledForTest = config?.testHarness?.disableSonarStage === true;
|
|
185
196
|
const sonarEnabled = !sonarStageDisabledForTest && resolvedPolicies.sonar !== false;
|
|
186
197
|
const isExternalSonar = Boolean(config.sonarqube?.external);
|
|
187
198
|
const sonarHost = resolveSonarHost(config.sonarqube?.host);
|
|
@@ -196,7 +207,9 @@ export async function runPreflightChecks({ config, logger, emitter, eventBase, r
|
|
|
196
207
|
};
|
|
197
208
|
|
|
198
209
|
// Resolve extended preflight opt-in early so the short-circuit below can see it.
|
|
199
|
-
|
|
210
|
+
// Post-v2.7.5 this no longer reads globalThis directly — config.testHarness
|
|
211
|
+
// is populated by the loader from the global or the default.
|
|
212
|
+
const extendedDefault = config?.testHarness?.defaultPreflightExtended ?? true;
|
|
200
213
|
const extendedEnabled = config?.preflight?.extended ?? extendedDefault;
|
|
201
214
|
|
|
202
215
|
// Preserve legacy short-circuit: if nothing at all needs checking, skip
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Heuristics for classifying an acceptance-test failure as "possibly
|
|
3
|
+
* infeasible" — i.e. the test itself looks broken, not the code.
|
|
4
|
+
*
|
|
5
|
+
* Used by the HU sub-pipeline to decide when to invoke the Repairer
|
|
6
|
+
* role instead of feeding the failure back to the coder for ANOTHER
|
|
7
|
+
* iteration. Each iteration costs real money + time; classifying
|
|
8
|
+
* misroutes is the difference between a 3-minute repair and a
|
|
9
|
+
* 30-minute budget burn.
|
|
10
|
+
*
|
|
11
|
+
* The classifier is deliberately CONSERVATIVE: false negatives
|
|
12
|
+
* (treating a broken test as just-failed) cost an extra iteration;
|
|
13
|
+
* false positives (treating a real failure as infeasible) waste a
|
|
14
|
+
* Repairer call AND give the coder a spec change it shouldn't
|
|
15
|
+
* receive. We err toward false negatives.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Signatures that strongly suggest the TEST is broken, not the code.
|
|
20
|
+
* Each is a regex applied to the failing command's stderr+stdout.
|
|
21
|
+
*/
|
|
22
|
+
const INFEASIBLE_SIGNATURES = [
|
|
23
|
+
// jq: `length as $n | .field` after a transform — the binding doesn't
|
|
24
|
+
// change the value flowing through, so `.field` is applied to the wrong
|
|
25
|
+
// thing. Real-world example from 2026-04-29: "Cannot index array with
|
|
26
|
+
// string \"filesOver300LOC\"".
|
|
27
|
+
{ kind: "jq-as-binding-misuse", re: /Cannot index array with string/i },
|
|
28
|
+
// bash -n / shell parse errors before the command even runs.
|
|
29
|
+
{ kind: "shell-parse-error", re: /syntax error|unexpected token|unexpected EOF/i },
|
|
30
|
+
// jq: invalid query syntax detected by jq itself.
|
|
31
|
+
{ kind: "jq-syntax", re: /jq: error \(at .*\): (?!Cannot index)/i },
|
|
32
|
+
// command not found = test references a tool that doesn't exist in
|
|
33
|
+
// this environment. Could be a fixable typo or a real env mismatch.
|
|
34
|
+
{ kind: "missing-tool", re: /command not found|: not found$|No such file or directory.*\.(sh|mjs|js|py)/im },
|
|
35
|
+
// jq fails to parse the JSON input — the input file is wrong, the
|
|
36
|
+
// path is wrong, or the assumed schema is off.
|
|
37
|
+
{ kind: "jq-parse-input", re: /jq: error: Could not (open|parse)/i },
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Compute a stable signature for a single failing test outcome — the
|
|
42
|
+
* test command + a normalized error fingerprint. Used to detect when
|
|
43
|
+
* the SAME test is failing the SAME way across iterations; that's the
|
|
44
|
+
* pattern that argues for a Repairer call (vs. a coder retry).
|
|
45
|
+
*
|
|
46
|
+
* @param {string} cmd
|
|
47
|
+
* @param {string} errorOutput
|
|
48
|
+
* @returns {string}
|
|
49
|
+
*/
|
|
50
|
+
export function failureSignature(cmd, errorOutput) {
|
|
51
|
+
const cmdNorm = String(cmd || "").trim().slice(0, 200);
|
|
52
|
+
const errNorm = String(errorOutput || "")
|
|
53
|
+
.replace(/\s+/g, " ") // collapse whitespace
|
|
54
|
+
.replace(/\d+/g, "N") // numbers tend to drift; line numbers, counts
|
|
55
|
+
.replace(/\/[^\s]+/g, "/PATH") // absolute paths drift across runs
|
|
56
|
+
.trim()
|
|
57
|
+
.slice(0, 300);
|
|
58
|
+
return `${cmdNorm}|||${errNorm}`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Classify a single test failure as `infeasible` or `regular`.
|
|
63
|
+
*
|
|
64
|
+
* @param {object} args
|
|
65
|
+
* @param {string} args.cmd — the command that ran
|
|
66
|
+
* @param {string} args.errorOutput — stdout + stderr from the failing run
|
|
67
|
+
* @param {number} args.consecutive — how many iterations in a row this
|
|
68
|
+
* same signature has failed
|
|
69
|
+
* @param {number} [args.threshold=2] — min consecutive failures before
|
|
70
|
+
* infeasibility is even considered.
|
|
71
|
+
* Below this, we always say "regular"
|
|
72
|
+
* (let the coder try once more).
|
|
73
|
+
* @returns {{infeasible: boolean, kind: string|null, signature: string}}
|
|
74
|
+
*/
|
|
75
|
+
export function classifyFailure({ cmd, errorOutput, consecutive, threshold = 2 }) {
|
|
76
|
+
const signature = failureSignature(cmd, errorOutput);
|
|
77
|
+
if (!Number.isFinite(consecutive) || consecutive < threshold) {
|
|
78
|
+
return { infeasible: false, kind: null, signature };
|
|
79
|
+
}
|
|
80
|
+
for (const sig of INFEASIBLE_SIGNATURES) {
|
|
81
|
+
if (sig.re.test(errorOutput || "")) {
|
|
82
|
+
return { infeasible: true, kind: sig.kind, signature };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return { infeasible: false, kind: null, signature };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Track consecutive identical failures across iterations of the same HU.
|
|
90
|
+
* Caller passes the same instance across iterations; the tracker
|
|
91
|
+
* remembers the last signature per command.
|
|
92
|
+
*/
|
|
93
|
+
export function createFailureTracker() {
|
|
94
|
+
// Map<cmd, { signature, count }>
|
|
95
|
+
const state = new Map();
|
|
96
|
+
return {
|
|
97
|
+
/**
|
|
98
|
+
* Record a failure and return how many consecutive iterations have
|
|
99
|
+
* produced the same signature for this cmd.
|
|
100
|
+
* @returns {number}
|
|
101
|
+
*/
|
|
102
|
+
record(cmd, errorOutput) {
|
|
103
|
+
const signature = failureSignature(cmd, errorOutput);
|
|
104
|
+
const prev = state.get(cmd);
|
|
105
|
+
if (prev && prev.signature === signature) {
|
|
106
|
+
prev.count += 1;
|
|
107
|
+
return prev.count;
|
|
108
|
+
}
|
|
109
|
+
state.set(cmd, { signature, count: 1 });
|
|
110
|
+
return 1;
|
|
111
|
+
},
|
|
112
|
+
/** Reset tracker for a cmd that just passed (stops false positives). */
|
|
113
|
+
reset(cmd) { state.delete(cmd); },
|
|
114
|
+
/** Diagnostic: dump current state. */
|
|
115
|
+
snapshot() { return Object.fromEntries(state); },
|
|
116
|
+
};
|
|
117
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repair runner — orchestrates the Repairer role end-to-end.
|
|
3
|
+
*
|
|
4
|
+
* Called from run-hu-batch when an acceptance test failure has been
|
|
5
|
+
* classified as `infeasible` by repair-detector. The runner:
|
|
6
|
+
* 1. Builds the prompt context (failing test, error output, HU
|
|
7
|
+
* intent, sibling tests).
|
|
8
|
+
* 2. Invokes RepairerRole.
|
|
9
|
+
* 3. If verdict=fixable: patches the plan JSON's acceptance_tests
|
|
10
|
+
* AND the in-memory batch story so the next coder iteration sees
|
|
11
|
+
* the corrected test. Saves both. Returns { repaired: true }.
|
|
12
|
+
* 4. If verdict=unfixable: returns { repaired: false, escalate: true,
|
|
13
|
+
* reason } so the caller can hand off to Solomon / FDE.
|
|
14
|
+
* 5. If RepairerRole itself fails (agent error): returns
|
|
15
|
+
* { repaired: false, escalate: false, error } so the caller
|
|
16
|
+
* decides whether to retry coder or escalate.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { RepairerRole } from "../../roles/repairer-role.js";
|
|
20
|
+
import { saveHuBatch } from "../../hu/store.js";
|
|
21
|
+
import { emitProgress, makeEvent } from "../../utils/events.js";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @param {object} args
|
|
25
|
+
* @param {object} args.story — the HU story object (in-memory batch slice). MUTATED.
|
|
26
|
+
* @param {number} args.testIndex — index in story.acceptance_tests of the failing test.
|
|
27
|
+
* @param {string} args.errorOutput — last failing run's stderr+stdout.
|
|
28
|
+
* @param {string} args.batchSessionId
|
|
29
|
+
* @param {object} args.batch — full batch object. MUTATED (story is a slice).
|
|
30
|
+
* @param {object} args.config
|
|
31
|
+
* @param {object} args.logger
|
|
32
|
+
* @param {object} args.emitter
|
|
33
|
+
* @param {object} args.eventBase
|
|
34
|
+
* @param {string} args.failureKind — repair-detector's `kind` (e.g. "jq-as-binding-misuse").
|
|
35
|
+
* @param {Function} [args.savePlanPatch] — async (huId, fieldPatch) => void; if provided,
|
|
36
|
+
* patches the plan JSON on disk too.
|
|
37
|
+
* @returns {Promise<{repaired: boolean, escalate?: boolean, reason?: string, error?: string}>}
|
|
38
|
+
*/
|
|
39
|
+
export async function runRepair({
|
|
40
|
+
story, testIndex, errorOutput, batchSessionId, batch,
|
|
41
|
+
config, logger, emitter, eventBase, failureKind, savePlanPatch,
|
|
42
|
+
}) {
|
|
43
|
+
if (!story || !Array.isArray(story.acceptance_tests) || !story.acceptance_tests[testIndex]) {
|
|
44
|
+
return { repaired: false, escalate: false, error: "repair-runner: invalid story or testIndex" };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const failingTest = story.acceptance_tests[testIndex];
|
|
48
|
+
const siblingTests = story.acceptance_tests.filter((_, i) => i !== testIndex);
|
|
49
|
+
|
|
50
|
+
emitProgress(emitter, makeEvent("repair:start", { ...eventBase, stage: "repair" }, {
|
|
51
|
+
message: `Repairer invoked for HU ${story.id} test #${testIndex} (kind=${failureKind})`,
|
|
52
|
+
detail: {
|
|
53
|
+
huId: story.id,
|
|
54
|
+
testIndex,
|
|
55
|
+
testContent: typeof failingTest === "object" ? failingTest.content : failingTest,
|
|
56
|
+
kind: failureKind,
|
|
57
|
+
},
|
|
58
|
+
}));
|
|
59
|
+
|
|
60
|
+
const repairer = new RepairerRole({ config, logger, emitter });
|
|
61
|
+
await repairer.init({ task: story.scope || story.title || story.id, sessionId: batchSessionId, iteration: 0 });
|
|
62
|
+
|
|
63
|
+
let result;
|
|
64
|
+
try {
|
|
65
|
+
result = await repairer.run({
|
|
66
|
+
failingTest,
|
|
67
|
+
errorOutput,
|
|
68
|
+
huTitle: story.title || story.id,
|
|
69
|
+
huScope: story.scope || "",
|
|
70
|
+
siblingTests,
|
|
71
|
+
iteration: 0,
|
|
72
|
+
});
|
|
73
|
+
} catch (err) {
|
|
74
|
+
logger.warn(`Repairer threw: ${err.message}`);
|
|
75
|
+
emitProgress(emitter, makeEvent("repair:end", { ...eventBase, stage: "repair" }, {
|
|
76
|
+
status: "fail",
|
|
77
|
+
message: `Repairer error: ${err.message}`,
|
|
78
|
+
detail: { huId: story.id, testIndex },
|
|
79
|
+
}));
|
|
80
|
+
return { repaired: false, escalate: false, error: err.message };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (!result?.ok) {
|
|
84
|
+
logger.warn(`Repairer failed: ${result?.summary || "unknown"}`);
|
|
85
|
+
emitProgress(emitter, makeEvent("repair:end", { ...eventBase, stage: "repair" }, {
|
|
86
|
+
status: "fail",
|
|
87
|
+
message: `Repairer could not produce a verdict: ${result?.summary || "unknown"}`,
|
|
88
|
+
detail: { huId: story.id, testIndex },
|
|
89
|
+
}));
|
|
90
|
+
return { repaired: false, escalate: false, error: result?.summary || "no verdict" };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const { verdict, fixed_test: fixedTest, reason } = result.result;
|
|
94
|
+
|
|
95
|
+
if (verdict === "unfixable") {
|
|
96
|
+
logger.info(`Repairer: HU ${story.id} test #${testIndex} unfixable — ${reason}`);
|
|
97
|
+
emitProgress(emitter, makeEvent("repair:end", { ...eventBase, stage: "repair" }, {
|
|
98
|
+
status: "escalate",
|
|
99
|
+
message: `Repairer: unfixable — ${reason}`,
|
|
100
|
+
detail: { huId: story.id, testIndex, reason },
|
|
101
|
+
}));
|
|
102
|
+
return { repaired: false, escalate: true, reason };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (verdict !== "fixable" || !fixedTest) {
|
|
106
|
+
logger.warn(`Repairer returned unexpected verdict: ${verdict}`);
|
|
107
|
+
return { repaired: false, escalate: false, error: `unexpected verdict: ${verdict}` };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Apply the fix in-memory and persist when we have the batch handle.
|
|
111
|
+
// Some callers can't pass `batch` from their scope (closure depth);
|
|
112
|
+
// the in-memory mutation of `story.acceptance_tests` is enough for
|
|
113
|
+
// the next iteration of the same HU loop because every acceptance
|
|
114
|
+
// run reads the live story object. Disk persistence is a "next run
|
|
115
|
+
// sees the fix too" optimisation; PR #544's reconcile keeps the
|
|
116
|
+
// plan JSON consistent across runs in either case.
|
|
117
|
+
story.acceptance_tests[testIndex] = fixedTest;
|
|
118
|
+
if (batch && batchSessionId) {
|
|
119
|
+
await saveHuBatch(batchSessionId, batch);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (typeof savePlanPatch === "function") {
|
|
123
|
+
try {
|
|
124
|
+
await savePlanPatch(story.id, { acceptance_tests: story.acceptance_tests });
|
|
125
|
+
} catch (err) {
|
|
126
|
+
logger.warn(`Repairer: in-memory + batch fixed but plan-patch failed: ${err.message}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
logger.info(`Repairer: HU ${story.id} test #${testIndex} REPAIRED — ${reason}`);
|
|
131
|
+
emitProgress(emitter, makeEvent("repair:end", { ...eventBase, stage: "repair" }, {
|
|
132
|
+
status: "ok",
|
|
133
|
+
message: `Repairer fixed HU ${story.id} test #${testIndex}: ${reason}`,
|
|
134
|
+
detail: {
|
|
135
|
+
huId: story.id,
|
|
136
|
+
testIndex,
|
|
137
|
+
reason,
|
|
138
|
+
fixedContent: typeof fixedTest === "object" ? fixedTest.content : fixedTest,
|
|
139
|
+
},
|
|
140
|
+
}));
|
|
141
|
+
return { repaired: true, reason };
|
|
142
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ReviewerRole } from "../roles/reviewer-role.js";
|
|
2
2
|
import { createAgent } from "../agents/index.js";
|
|
3
|
-
import { addCheckpoint } from "../session
|
|
3
|
+
import { addCheckpoint } from "../session/store.js";
|
|
4
4
|
|
|
5
5
|
export async function runReviewerWithFallback({ reviewerName, config, logger, emitter, reviewInput, session, iteration, onAttemptResult }) {
|
|
6
6
|
const fallbackReviewer = config.reviewer_options?.fallback_reviewer;
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
// Creates .reviews/session_*/ directories containing markdown files per stage.
|
|
3
3
|
import fs from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
5
|
-
import { runCommand } from "../utils/process.js";
|
|
6
5
|
|
|
7
6
|
/**
|
|
8
7
|
* Create the journal directory for a session.
|
|
@@ -174,29 +173,6 @@ export function formatDecision({ timestamp, trigger, context, action, reasoning
|
|
|
174
173
|
return lines.join("\n");
|
|
175
174
|
}
|
|
176
175
|
|
|
177
|
-
// ── Tree of affected files ───────────────────────────────────────────────────
|
|
178
|
-
|
|
179
|
-
export async function generateFileTree(baseRef) {
|
|
180
|
-
try {
|
|
181
|
-
const result = await runCommand("git", ["diff", "--name-status", `${baseRef}...HEAD`]);
|
|
182
|
-
if (result.exitCode !== 0 || !result.stdout?.trim()) return null;
|
|
183
|
-
|
|
184
|
-
const lines = ["# Affected Files\n"];
|
|
185
|
-
const statusMap = { M: "modified", A: "added", D: "deleted", R: "renamed" };
|
|
186
|
-
|
|
187
|
-
for (const line of result.stdout.trim().split("\n")) {
|
|
188
|
-
const [status, ...fileParts] = line.split("\t");
|
|
189
|
-
const file = fileParts.join("\t");
|
|
190
|
-
const label = statusMap[status?.[0]] || status;
|
|
191
|
-
lines.push(`- [${label}] ${file}`);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
return lines.join("\n");
|
|
195
|
-
} catch {
|
|
196
|
-
return null;
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
176
|
// ── Summary ──────────────────────────────────────────────────────────────────
|
|
201
177
|
|
|
202
178
|
export function generateSummary({ task, result, sessionId, iterations, durationMs, budget, stages, commits, files }) {
|
|
@@ -325,20 +301,7 @@ export async function writeDecisionsJournal(journalDir, decisionEntries) {
|
|
|
325
301
|
await writeJournalFile(journalDir, "decisions.md", content);
|
|
326
302
|
}
|
|
327
303
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
const tree = await generateFileTree(baseRef);
|
|
333
|
-
if (tree) await writeJournalFile(journalDir, "tree.txt", tree);
|
|
334
|
-
return Boolean(tree);
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
/**
|
|
338
|
-
* Write the session summary.
|
|
339
|
-
*/
|
|
340
|
-
export async function writeSummaryJournal(journalDir, summaryData) {
|
|
341
|
-
const files = summaryData.files || [];
|
|
342
|
-
const content = generateSummary({ ...summaryData, files: [...files, "summary.md"] });
|
|
343
|
-
await writeJournalFile(journalDir, "summary.md", content);
|
|
344
|
-
}
|
|
304
|
+
// writeTreeJournal / writeSummaryJournal / generateFileTree were
|
|
305
|
+
// superseded in v2.7.x by src/session/journal/{tree-writer,summary-writer}.js
|
|
306
|
+
// (the V2 split). post-loop.js dynamically imports the V2 versions; this
|
|
307
|
+
// orchestrator-side copy is no longer wired anywhere. KJC-TSK-0354 PR-D.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { SolomonRole } from "../roles/solomon-role.js";
|
|
2
|
-
import { addCheckpoint, pauseSession, saveSession } from "../session
|
|
2
|
+
import { addCheckpoint, pauseSession, saveSession } from "../session/store.js";
|
|
3
|
+
import { setSuggestions } from "../session/mutators.js";
|
|
3
4
|
import { emitProgress, makeEvent } from "../utils/events.js";
|
|
4
5
|
import { msg, getLang } from "../utils/messages.js";
|
|
5
6
|
|
|
@@ -158,7 +159,7 @@ export async function invokeSolomon({ config, logger, emitter, eventBase, stage,
|
|
|
158
159
|
|
|
159
160
|
// Clear processed suggestions from session
|
|
160
161
|
if (pendingSuggestions) {
|
|
161
|
-
session
|
|
162
|
+
setSuggestions(session, []);
|
|
162
163
|
try { await saveSession(session); } catch { /* best-effort */ }
|
|
163
164
|
}
|
|
164
165
|
|
|
@@ -118,11 +118,15 @@ export async function buildRulesContext({ session, task, iteration, blockingIssu
|
|
|
118
118
|
|
|
119
119
|
// Count files changed via git
|
|
120
120
|
try {
|
|
121
|
-
|
|
121
|
+
// Audit follow-up: was using execaCommand with template-string
|
|
122
|
+
// interpolation of `baseRef`. baseRef comes from session state, not
|
|
123
|
+
// user input, but the shape is a known shell-injection vector.
|
|
124
|
+
// Switched to execa with arg arrays so no shell expansion happens.
|
|
125
|
+
const { execa } = await import("execa");
|
|
122
126
|
const baseRef = session.session_start_sha || "HEAD~1";
|
|
123
127
|
|
|
124
128
|
// Files changed
|
|
125
|
-
const diffResult = await
|
|
129
|
+
const diffResult = await execa("git", ["diff", "--name-only", String(baseRef)], { reject: false });
|
|
126
130
|
if (diffResult.stdout) {
|
|
127
131
|
const files = diffResult.stdout.split("\n").filter(Boolean);
|
|
128
132
|
context.filesChanged = files.length;
|
|
@@ -142,7 +146,7 @@ export async function buildRulesContext({ session, task, iteration, blockingIssu
|
|
|
142
146
|
// Detect new dependencies
|
|
143
147
|
if (files.includes("package.json")) {
|
|
144
148
|
try {
|
|
145
|
-
const pkgDiff = await
|
|
149
|
+
const pkgDiff = await execa("git", ["diff", String(baseRef), "--", "package.json"], { reject: false });
|
|
146
150
|
const addedDeps = (pkgDiff.stdout || "").split("\n")
|
|
147
151
|
.filter(line => line.startsWith("+") && line.includes('"') && !line.startsWith("+++"))
|
|
148
152
|
.map(line => {
|
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
|
|
6
6
|
import { ArchitectRole } from "../../roles/architect-role.js";
|
|
7
7
|
import { createAgent } from "../../agents/index.js";
|
|
8
|
-
import {
|
|
9
|
-
import { addCheckpoint } from "../../session-store.js";
|
|
8
|
+
import { addCheckpoint } from "../../session/store.js";
|
|
10
9
|
import { emitProgress, makeEvent, emitAgentOutput } from "../../utils/events.js";
|
|
11
10
|
import { createStallDetector } from "../../utils/stall-detector.js";
|
|
11
|
+
import { getIntegration } from "../integrations.js";
|
|
12
12
|
|
|
13
13
|
async function handleArchitectClarification({ architectOutput, askQuestion, config, logger, emitter, eventBase, session, architectOnOutput, architectProvider, coderRole, researchContext, discoverResult, triageLevel, trackBudget }) {
|
|
14
14
|
if (!architectOutput.ok
|
|
@@ -140,9 +140,10 @@ export async function runArchitectStage({ config, logger, emitter, eventBase, se
|
|
|
140
140
|
|
|
141
141
|
const architectContext = architectOutput.ok ? architectOutput.result : null;
|
|
142
142
|
|
|
143
|
-
//
|
|
144
|
-
//
|
|
145
|
-
//
|
|
143
|
+
// TSK-0339: ADR creation is now a tracker hook. The orchestrator ships the
|
|
144
|
+
// tradeoffs; the registered adapter decides whether/how to persist them
|
|
145
|
+
// (PG creates ADR cards, another tracker might create Jira issues, an
|
|
146
|
+
// unregistered tracker is a no-op).
|
|
146
147
|
const tradeoffs = architectOutput.result?.architecture?.tradeoffs;
|
|
147
148
|
if (architectOutput.ok
|
|
148
149
|
&& architectOutput.result?.verdict === "ready"
|
|
@@ -150,17 +151,17 @@ export async function runArchitectStage({ config, logger, emitter, eventBase, se
|
|
|
150
151
|
&& session.pg_task_id
|
|
151
152
|
&& session.pg_project_id) {
|
|
152
153
|
try {
|
|
153
|
-
const
|
|
154
|
-
const adrResult = await createArchitectADRs({
|
|
154
|
+
const adrResult = await getIntegration("tracker")?.createAdrsFromTradeoffs?.({
|
|
155
155
|
tradeoffs,
|
|
156
156
|
pgTaskId: session.pg_task_id,
|
|
157
157
|
pgProject: session.pg_project_id,
|
|
158
158
|
taskTitle: session.task,
|
|
159
|
-
mcpClient: pgClient
|
|
160
159
|
});
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
160
|
+
if (adrResult) {
|
|
161
|
+
stageResult.adrs = adrResult;
|
|
162
|
+
if (adrResult.created > 0) {
|
|
163
|
+
logger.info(`Architect: created ${adrResult.created} ADR(s) in tracker`);
|
|
164
|
+
}
|
|
164
165
|
}
|
|
165
166
|
} catch (err) {
|
|
166
167
|
logger.warn(`Architect: failed to create ADRs: ${err.message}`);
|
|
@@ -6,7 +6,10 @@
|
|
|
6
6
|
import { createAgent } from "../../agents/index.js";
|
|
7
7
|
import { CoderRole } from "../../roles/coder-role.js";
|
|
8
8
|
import { RefactorerRole } from "../../roles/refactorer-role.js";
|
|
9
|
-
import { addCheckpoint, markSessionStatus, saveSession } from "../../session
|
|
9
|
+
import { addCheckpoint, markSessionStatus, saveSession } from "../../session/store.js";
|
|
10
|
+
import {
|
|
11
|
+
setReviewerFeedback, incrementRetryCount, resetRetryCount,
|
|
12
|
+
} from "../../session/mutators.js";
|
|
10
13
|
import { generateDiff, getUntrackedFiles } from "../../review/diff-generator.js";
|
|
11
14
|
import { evaluateTddPolicy } from "../../review/tdd-policy.js";
|
|
12
15
|
import { buildDeferredContext } from "../../review/scope-filter.js";
|
|
@@ -15,8 +18,9 @@ import { runCoderWithFallback } from "../agent-fallback.js";
|
|
|
15
18
|
import { invokeSolomon } from "../solomon-escalation.js";
|
|
16
19
|
import { detectRateLimit } from "../../utils/rate-limit-detector.js";
|
|
17
20
|
import { createStallDetector } from "../../utils/stall-detector.js";
|
|
21
|
+
import { snapshotHomeTopLevel, detectNewHomeEntries, formatLeakMessage, verifyLeaksAgainstTranscript } from "../fs-leak-detector.js";
|
|
18
22
|
|
|
19
|
-
export async function runCoderStage({ coderRoleInstance, coderRole, config, logger, emitter, eventBase, session, plannedTask, trackBudget, iteration, brainCtx }) {
|
|
23
|
+
export async function runCoderStage({ coderRoleInstance, coderRole, config, logger, emitter, eventBase, session, plannedTask, trackBudget, iteration, brainCtx, acceptanceTests = null, adrs = null, specSection = null, reviewerFindings = null, huId = null }) {
|
|
20
24
|
logger.setContext({ iteration, stage: "coder" });
|
|
21
25
|
emitProgress(
|
|
22
26
|
emitter,
|
|
@@ -42,6 +46,12 @@ export async function runCoderStage({ coderRoleInstance, coderRole, config, logg
|
|
|
42
46
|
onOutput: coderOnOutput, emitter, eventBase, stage: "coder", provider: coderRole.provider
|
|
43
47
|
});
|
|
44
48
|
const coderStart = Date.now();
|
|
49
|
+
// KJC-BUG-0032 (PR-I): snapshot $HOME's top-level entries BEFORE
|
|
50
|
+
// the coder runs so we can detect leaks afterwards. The bug: a HU
|
|
51
|
+
// titled "Initialize project skeleton: create assistant/…" had
|
|
52
|
+
// Claude run `cd /home/manu/assistant && pnpm init …` and 36 MB
|
|
53
|
+
// of code landed outside projectDir, outside any git repo.
|
|
54
|
+
const homeSnapshotBeforeCoder = snapshotHomeTopLevel();
|
|
45
55
|
let coderExecResult;
|
|
46
56
|
try {
|
|
47
57
|
coderExecResult = await coderRoleInstance.execute({
|
|
@@ -49,6 +59,13 @@ export async function runCoderStage({ coderRoleInstance, coderRole, config, logg
|
|
|
49
59
|
reviewerFeedback,
|
|
50
60
|
sonarSummary: session.last_sonar_summary,
|
|
51
61
|
deferredContext: buildDeferredContext(session.deferred_issues),
|
|
62
|
+
// Tests-first contract flows through to buildCoderPrompt which
|
|
63
|
+
// renders the "Acceptance Tests — MUST pass" section.
|
|
64
|
+
acceptanceTests,
|
|
65
|
+
// PR F (v2.7.5): plan-aware context. ADRs apply to every HU on
|
|
66
|
+
// the plan, the rest are scoped to this HU. All four pass through
|
|
67
|
+
// untouched when the caller omits them (legacy single-task runs).
|
|
68
|
+
adrs, specSection, reviewerFindings, huId,
|
|
52
69
|
onOutput: coderStall.onOutput
|
|
53
70
|
});
|
|
54
71
|
} finally {
|
|
@@ -129,6 +146,38 @@ export async function runCoderStage({ coderRoleInstance, coderRole, config, logg
|
|
|
129
146
|
throw new Error(`Coder failed: ${details}`);
|
|
130
147
|
}
|
|
131
148
|
|
|
149
|
+
// KJC-BUG-0032 (PR-I): scan $HOME for new top-level entries the
|
|
150
|
+
// coder created OUTSIDE projectDir. Stop the HU loudly with a
|
|
151
|
+
// plain-Spanish message instead of letting the user discover by
|
|
152
|
+
// accident hours later that 36 MB of code is in the wrong place.
|
|
153
|
+
// Runs BEFORE the success-path verification so a leaked HU doesn't
|
|
154
|
+
// get marked as "Coder applied changes".
|
|
155
|
+
const projectDirAbs = config.projectDir || process.cwd();
|
|
156
|
+
const candidateLeaks = detectNewHomeEntries(homeSnapshotBeforeCoder, projectDirAbs);
|
|
157
|
+
// Issue #546: snapshot-diff alone produces false positives when other
|
|
158
|
+
// processes write to $HOME during the iteration window. Filter the
|
|
159
|
+
// detected entries against the coder's transcript: only flag those
|
|
160
|
+
// the coder demonstrably referenced.
|
|
161
|
+
const coderTranscript = coderExecResult.result?.output || "";
|
|
162
|
+
const fsLeaks = verifyLeaksAgainstTranscript(candidateLeaks, coderTranscript);
|
|
163
|
+
if (candidateLeaks.length > 0 && fsLeaks.length === 0) {
|
|
164
|
+
logger.warn(`fs-leak-detector: ${candidateLeaks.length} new $HOME entr(y/ies) detected but none referenced in the coder transcript — likely a concurrent host-side write, not flagging (#546).`);
|
|
165
|
+
}
|
|
166
|
+
if (fsLeaks.length > 0) {
|
|
167
|
+
const msg = formatLeakMessage(fsLeaks, projectDirAbs);
|
|
168
|
+
logger.error(msg);
|
|
169
|
+
emitProgress(
|
|
170
|
+
emitter,
|
|
171
|
+
makeEvent("coder:fs-leak", { ...eventBase, stage: "coder" }, {
|
|
172
|
+
status: "fail",
|
|
173
|
+
message: `Coder escribió fuera del projectDir: ${fsLeaks.length} ruta(s) detectada(s)`,
|
|
174
|
+
detail: { provider: coderRole.provider, leaks: fsLeaks, projectDir: projectDirAbs }
|
|
175
|
+
})
|
|
176
|
+
);
|
|
177
|
+
await markSessionStatus(session, "failed");
|
|
178
|
+
throw new Error(msg);
|
|
179
|
+
}
|
|
180
|
+
|
|
132
181
|
// Measure files changed so stale detection (solomon-rules) has accurate data
|
|
133
182
|
let filesChanged = 0;
|
|
134
183
|
try {
|
|
@@ -252,7 +301,7 @@ function handleSolomonAction(solomonResult, session, contextPrefix) {
|
|
|
252
301
|
async function handleSolomonContinue(solomonResult, session, counterField, brainCtx) {
|
|
253
302
|
if (solomonResult.action !== "continue") return false;
|
|
254
303
|
if (solomonResult.humanGuidance) {
|
|
255
|
-
session.last_reviewer_feedback
|
|
304
|
+
setReviewerFeedback(session, `${session.last_reviewer_feedback ?? ""}\nUser guidance: ${solomonResult.humanGuidance}`);
|
|
256
305
|
// Brain: also push user guidance into feedback queue when enabled
|
|
257
306
|
if (brainCtx?.enabled) {
|
|
258
307
|
const { processRoleOutput } = await import("../brain-coordinator.js");
|
|
@@ -265,13 +314,13 @@ async function handleSolomonContinue(solomonResult, session, counterField, brain
|
|
|
265
314
|
}
|
|
266
315
|
|
|
267
316
|
async function handleTddFailure({ tddEval, config, logger, emitter, eventBase, session, iteration, askQuestion, task, brainCtx }) {
|
|
268
|
-
session
|
|
317
|
+
setReviewerFeedback(session, tddEval.message);
|
|
269
318
|
// Brain: push TDD failure into feedback queue when enabled
|
|
270
319
|
if (brainCtx?.enabled) {
|
|
271
320
|
const { processRoleOutput } = await import("../brain-coordinator.js");
|
|
272
321
|
processRoleOutput(brainCtx, { roleName: "tdd", output: { verdict: "fail", summary: tddEval.message }, iteration });
|
|
273
322
|
}
|
|
274
|
-
session
|
|
323
|
+
incrementRetryCount(session, "repeated_issue");
|
|
275
324
|
await saveSession(session);
|
|
276
325
|
|
|
277
326
|
if (session.repeated_issue_count < config.session.fail_fast_repeats) {
|
|
@@ -285,7 +334,7 @@ async function handleTddFailure({ tddEval, config, logger, emitter, eventBase, s
|
|
|
285
334
|
message: `TDD sub-loop limit reached (${session.repeated_issue_count}/${config.session.fail_fast_repeats}) — Brain handling`,
|
|
286
335
|
detail: { subloop: "tdd", retryCount: session.repeated_issue_count, reason: tddEval.reason }
|
|
287
336
|
}));
|
|
288
|
-
session
|
|
337
|
+
resetRetryCount(session, "repeated_issue");
|
|
289
338
|
await saveSession(session);
|
|
290
339
|
return { action: "continue" };
|
|
291
340
|
}
|
|
@@ -320,7 +369,7 @@ async function handleTddFailure({ tddEval, config, logger, emitter, eventBase, s
|
|
|
320
369
|
return { action: "continue" };
|
|
321
370
|
}
|
|
322
371
|
|
|
323
|
-
export async function runTddCheckStage({ config, logger, emitter, eventBase, session, trackBudget, iteration, askQuestion, task, brainCtx }) {
|
|
372
|
+
export async function runTddCheckStage({ config, logger, emitter, eventBase, session, trackBudget: _trackBudget, iteration, askQuestion, task, brainCtx }) {
|
|
324
373
|
logger.setContext({ iteration, stage: "tdd" });
|
|
325
374
|
let tddDiff, untrackedFiles;
|
|
326
375
|
try {
|