karajan-code 1.36.1 → 1.38.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 +31 -32
- package/docs/README.es.md +131 -237
- package/package.json +1 -1
- package/src/agents/resolve-bin.js +26 -14
- package/src/hu/store.js +57 -0
- package/src/orchestrator/hu-sub-pipeline.js +166 -0
- package/src/orchestrator/iteration-stages.js +50 -15
- package/src/orchestrator/post-loop-stages.js +25 -11
- package/src/orchestrator/pre-loop-stages.js +147 -43
- package/src/orchestrator/preflight-checks.js +3 -3
- package/src/orchestrator/solomon-escalation.js +3 -2
- package/src/orchestrator.js +100 -15
- package/src/planning-game/pipeline-adapter.js +37 -0
- package/src/prompts/hu-reviewer.js +55 -0
- package/src/prompts/triage.js +4 -2
- package/src/utils/display.js +133 -23
- package/src/utils/injection-guard.js +171 -0
- package/src/utils/os-detect.js +17 -8
- package/src/utils/process.js +7 -4
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HU Sub-Pipeline — executes each certified HU as an independent sub-pipeline
|
|
3
|
+
* with per-HU state tracking, topological ordering, and dependency-aware blocking.
|
|
4
|
+
*/
|
|
5
|
+
import { topologicalSort } from "../hu/graph.js";
|
|
6
|
+
import { updateStoryStatus, loadHuBatch, saveHuBatch, HU_STATUS } from "../hu/store.js";
|
|
7
|
+
import { emitProgress, makeEvent } from "../utils/events.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Determine whether the HU reviewer result requires a sub-pipeline
|
|
11
|
+
* (more than one certified story).
|
|
12
|
+
* @param {object|null} huReviewerResult - stageResults.huReviewer
|
|
13
|
+
* @returns {boolean}
|
|
14
|
+
*/
|
|
15
|
+
export function needsSubPipeline(huReviewerResult) {
|
|
16
|
+
if (!huReviewerResult?.ok) return false;
|
|
17
|
+
const certified = (huReviewerResult.stories || []).filter(
|
|
18
|
+
s => s.status === "certified"
|
|
19
|
+
);
|
|
20
|
+
return certified.length > 1;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Mark all stories that depend (directly or transitively) on a failed story as "blocked".
|
|
25
|
+
* @param {object} batch - The HU batch object.
|
|
26
|
+
* @param {string} failedId - The ID of the failed story.
|
|
27
|
+
* @returns {string[]} IDs of newly blocked stories.
|
|
28
|
+
*/
|
|
29
|
+
export function blockDependents(batch, failedId) {
|
|
30
|
+
const blocked = [];
|
|
31
|
+
const failedSet = new Set([failedId]);
|
|
32
|
+
// Iterate until no new blocked stories are found (handles transitive deps)
|
|
33
|
+
let changed = true;
|
|
34
|
+
while (changed) {
|
|
35
|
+
changed = false;
|
|
36
|
+
for (const story of batch.stories) {
|
|
37
|
+
if (failedSet.has(story.id)) continue;
|
|
38
|
+
if (story.status === HU_STATUS.BLOCKED) continue;
|
|
39
|
+
const deps = story.blocked_by || [];
|
|
40
|
+
if (deps.some(dep => failedSet.has(dep))) {
|
|
41
|
+
updateStoryStatus(batch, story.id, HU_STATUS.BLOCKED);
|
|
42
|
+
failedSet.add(story.id);
|
|
43
|
+
blocked.push(story.id);
|
|
44
|
+
changed = true;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return blocked;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Build task description for the coder from a certified HU story.
|
|
53
|
+
* @param {object} story - A story object from the batch.
|
|
54
|
+
* @returns {string}
|
|
55
|
+
*/
|
|
56
|
+
function buildHuTask(story) {
|
|
57
|
+
if (story.certified?.text) return story.certified.text;
|
|
58
|
+
if (story.original?.text) return story.original.text;
|
|
59
|
+
return `Implement HU ${story.id}`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Run all certified HUs as sub-pipelines in topological order.
|
|
64
|
+
*
|
|
65
|
+
* @param {object} params
|
|
66
|
+
* @param {object} params.huReviewerResult - stageResults.huReviewer (from pre-loop)
|
|
67
|
+
* @param {Function} params.runIterationFn - async (task) => { approved, result, ... }
|
|
68
|
+
* Callback that runs the full coder→sonar→reviewer iteration loop for a given task.
|
|
69
|
+
* Returns an object with at least { approved: boolean }.
|
|
70
|
+
* @param {object} params.emitter - Event emitter
|
|
71
|
+
* @param {object} params.eventBase - Base event fields
|
|
72
|
+
* @param {object} params.logger - Logger instance
|
|
73
|
+
* @returns {Promise<{approved: boolean, results: object[], blockedIds: string[]}>}
|
|
74
|
+
*/
|
|
75
|
+
export async function runHuSubPipeline({ huReviewerResult, runIterationFn, emitter, eventBase, logger }) {
|
|
76
|
+
const batchSessionId = huReviewerResult.batchSessionId;
|
|
77
|
+
let batch;
|
|
78
|
+
try {
|
|
79
|
+
batch = await loadHuBatch(batchSessionId);
|
|
80
|
+
} catch {
|
|
81
|
+
logger.warn("HU sub-pipeline: could not load batch, falling back to in-memory stories");
|
|
82
|
+
// Build a minimal batch from the stage result
|
|
83
|
+
batch = {
|
|
84
|
+
session_id: batchSessionId,
|
|
85
|
+
stories: huReviewerResult.stories || []
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const certifiedStories = batch.stories.filter(s => s.status === HU_STATUS.CERTIFIED);
|
|
90
|
+
let orderedIds;
|
|
91
|
+
try {
|
|
92
|
+
orderedIds = topologicalSort(certifiedStories);
|
|
93
|
+
} catch {
|
|
94
|
+
orderedIds = certifiedStories.map(s => s.id);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const results = [];
|
|
98
|
+
const blockedIds = [];
|
|
99
|
+
let allApproved = true;
|
|
100
|
+
|
|
101
|
+
for (const storyId of orderedIds) {
|
|
102
|
+
const story = batch.stories.find(s => s.id === storyId);
|
|
103
|
+
if (!story) continue;
|
|
104
|
+
|
|
105
|
+
// Skip if already blocked by a failed dependency
|
|
106
|
+
if (story.status === HU_STATUS.BLOCKED) {
|
|
107
|
+
blockedIds.push(story.id);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const huTask = buildHuTask(story);
|
|
112
|
+
|
|
113
|
+
// --- hu:start ---
|
|
114
|
+
emitProgress(emitter, makeEvent("hu:start", { ...eventBase, stage: "hu-sub-pipeline" }, {
|
|
115
|
+
message: `Starting HU ${story.id}`,
|
|
116
|
+
detail: { huId: story.id, title: story.certified?.title || story.id }
|
|
117
|
+
}));
|
|
118
|
+
|
|
119
|
+
// Update status to coding
|
|
120
|
+
updateStoryStatus(batch, story.id, HU_STATUS.CODING);
|
|
121
|
+
await saveHuBatch(batchSessionId, batch);
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
const iterResult = await runIterationFn(huTask);
|
|
125
|
+
const approved = Boolean(iterResult?.approved);
|
|
126
|
+
|
|
127
|
+
if (approved) {
|
|
128
|
+
updateStoryStatus(batch, story.id, HU_STATUS.DONE);
|
|
129
|
+
emitProgress(emitter, makeEvent("hu:end", { ...eventBase, stage: "hu-sub-pipeline" }, {
|
|
130
|
+
status: "ok",
|
|
131
|
+
message: `HU ${story.id} completed successfully`,
|
|
132
|
+
detail: { huId: story.id, approved: true }
|
|
133
|
+
}));
|
|
134
|
+
results.push({ huId: story.id, approved: true, result: iterResult });
|
|
135
|
+
} else {
|
|
136
|
+
updateStoryStatus(batch, story.id, HU_STATUS.FAILED);
|
|
137
|
+
const newlyBlocked = blockDependents(batch, story.id);
|
|
138
|
+
blockedIds.push(...newlyBlocked);
|
|
139
|
+
allApproved = false;
|
|
140
|
+
|
|
141
|
+
emitProgress(emitter, makeEvent("hu:end", { ...eventBase, stage: "hu-sub-pipeline" }, {
|
|
142
|
+
status: "fail",
|
|
143
|
+
message: `HU ${story.id} failed — ${newlyBlocked.length} dependent(s) blocked`,
|
|
144
|
+
detail: { huId: story.id, approved: false, blockedDependents: newlyBlocked, reason: iterResult?.reason }
|
|
145
|
+
}));
|
|
146
|
+
results.push({ huId: story.id, approved: false, result: iterResult, blockedDependents: newlyBlocked });
|
|
147
|
+
}
|
|
148
|
+
} catch (err) {
|
|
149
|
+
updateStoryStatus(batch, story.id, HU_STATUS.FAILED);
|
|
150
|
+
const newlyBlocked = blockDependents(batch, story.id);
|
|
151
|
+
blockedIds.push(...newlyBlocked);
|
|
152
|
+
allApproved = false;
|
|
153
|
+
|
|
154
|
+
emitProgress(emitter, makeEvent("hu:end", { ...eventBase, stage: "hu-sub-pipeline" }, {
|
|
155
|
+
status: "fail",
|
|
156
|
+
message: `HU ${story.id} threw: ${err.message}`,
|
|
157
|
+
detail: { huId: story.id, approved: false, error: err.message, blockedDependents: newlyBlocked }
|
|
158
|
+
}));
|
|
159
|
+
results.push({ huId: story.id, approved: false, error: err.message, blockedDependents: newlyBlocked });
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
await saveHuBatch(batchSessionId, batch);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return { approved: allApproved, results, blockedIds };
|
|
166
|
+
}
|
|
@@ -21,7 +21,7 @@ export async function runCoderStage({ coderRoleInstance, coderRole, config, logg
|
|
|
21
21
|
emitter,
|
|
22
22
|
makeEvent("coder:start", { ...eventBase, stage: "coder" }, {
|
|
23
23
|
message: `Coder (${coderRole.provider}) running`,
|
|
24
|
-
detail: { coder: coderRole.provider }
|
|
24
|
+
detail: { coder: coderRole.provider, provider: coderRole.provider, executorType: "agent" }
|
|
25
25
|
})
|
|
26
26
|
);
|
|
27
27
|
|
|
@@ -89,7 +89,8 @@ export async function runCoderStage({ coderRoleInstance, coderRole, config, logg
|
|
|
89
89
|
emitProgress(
|
|
90
90
|
emitter,
|
|
91
91
|
makeEvent("coder:end", { ...eventBase, stage: "coder" }, {
|
|
92
|
-
message: `Coder completed (fallback: ${fallbackCoder})
|
|
92
|
+
message: `Coder completed (fallback: ${fallbackCoder})`,
|
|
93
|
+
detail: { provider: fallbackCoder, executorType: "agent" }
|
|
93
94
|
})
|
|
94
95
|
);
|
|
95
96
|
return;
|
|
@@ -114,7 +115,8 @@ export async function runCoderStage({ coderRoleInstance, coderRole, config, logg
|
|
|
114
115
|
emitter,
|
|
115
116
|
makeEvent("coder:end", { ...eventBase, stage: "coder" }, {
|
|
116
117
|
status: "fail",
|
|
117
|
-
message: `Coder failed: ${details}
|
|
118
|
+
message: `Coder failed: ${details}`,
|
|
119
|
+
detail: { provider: coderRole.provider, executorType: "agent" }
|
|
118
120
|
})
|
|
119
121
|
);
|
|
120
122
|
throw new Error(`Coder failed: ${details}`);
|
|
@@ -124,7 +126,8 @@ export async function runCoderStage({ coderRoleInstance, coderRole, config, logg
|
|
|
124
126
|
emitProgress(
|
|
125
127
|
emitter,
|
|
126
128
|
makeEvent("coder:end", { ...eventBase, stage: "coder" }, {
|
|
127
|
-
message: "Coder completed"
|
|
129
|
+
message: "Coder completed",
|
|
130
|
+
detail: { provider: coderRole.provider, executorType: "agent" }
|
|
128
131
|
})
|
|
129
132
|
);
|
|
130
133
|
}
|
|
@@ -135,7 +138,7 @@ export async function runRefactorerStage({ refactorerRole, config, logger, emitt
|
|
|
135
138
|
emitter,
|
|
136
139
|
makeEvent("refactorer:start", { ...eventBase, stage: "refactorer" }, {
|
|
137
140
|
message: `Refactorer (${refactorerRole.provider}) running`,
|
|
138
|
-
detail: { refactorer: refactorerRole.provider }
|
|
141
|
+
detail: { refactorer: refactorerRole.provider, provider: refactorerRole.provider, executorType: "agent" }
|
|
139
142
|
})
|
|
140
143
|
);
|
|
141
144
|
const refactorerOnOutput = ({ stream, line }) => {
|
|
@@ -184,7 +187,8 @@ export async function runRefactorerStage({ refactorerRole, config, logger, emitt
|
|
|
184
187
|
emitter,
|
|
185
188
|
makeEvent("refactorer:end", { ...eventBase, stage: "refactorer" }, {
|
|
186
189
|
status: "fail",
|
|
187
|
-
message: `Refactorer failed: ${details}
|
|
190
|
+
message: `Refactorer failed: ${details}`,
|
|
191
|
+
detail: { provider: refactorerRole.provider, executorType: "agent" }
|
|
188
192
|
})
|
|
189
193
|
);
|
|
190
194
|
throw new Error(`Refactorer failed: ${details}`);
|
|
@@ -193,7 +197,8 @@ export async function runRefactorerStage({ refactorerRole, config, logger, emitt
|
|
|
193
197
|
emitProgress(
|
|
194
198
|
emitter,
|
|
195
199
|
makeEvent("refactorer:end", { ...eventBase, stage: "refactorer" }, {
|
|
196
|
-
message: "Refactorer completed"
|
|
200
|
+
message: "Refactorer completed",
|
|
201
|
+
detail: { provider: refactorerRole.provider, executorType: "agent" }
|
|
197
202
|
})
|
|
198
203
|
);
|
|
199
204
|
}
|
|
@@ -287,7 +292,8 @@ export async function runTddCheckStage({ config, logger, emitter, eventBase, ses
|
|
|
287
292
|
ok: tddEval.ok,
|
|
288
293
|
reason: tddEval.reason,
|
|
289
294
|
sourceFiles: tddEval.sourceFiles?.length || 0,
|
|
290
|
-
testFiles: tddEval.testFiles?.length || 0
|
|
295
|
+
testFiles: tddEval.testFiles?.length || 0,
|
|
296
|
+
executorType: "local"
|
|
291
297
|
}
|
|
292
298
|
})
|
|
293
299
|
);
|
|
@@ -386,7 +392,8 @@ export async function runSonarStage({ config, logger, emitter, eventBase, sessio
|
|
|
386
392
|
emitProgress(
|
|
387
393
|
emitter,
|
|
388
394
|
makeEvent("sonar:start", { ...eventBase, stage: "sonar" }, {
|
|
389
|
-
message: "SonarQube scanning"
|
|
395
|
+
message: "SonarQube scanning",
|
|
396
|
+
detail: { provider: "sonarqube", executorType: "local" }
|
|
390
397
|
})
|
|
391
398
|
);
|
|
392
399
|
|
|
@@ -470,7 +477,7 @@ export async function runSonarStage({ config, logger, emitter, eventBase, sessio
|
|
|
470
477
|
makeEvent("sonar:end", { ...eventBase, stage: "sonar" }, {
|
|
471
478
|
status: sonarResult.blocking ? "fail" : "ok",
|
|
472
479
|
message: `Quality gate: ${sonarResult.gateStatus}`,
|
|
473
|
-
detail: { projectKey: sonarResult.projectKey, gateStatus: sonarResult.gateStatus, openIssues: sonarResult.openIssuesTotal }
|
|
480
|
+
detail: { projectKey: sonarResult.projectKey, gateStatus: sonarResult.gateStatus, openIssues: sonarResult.openIssuesTotal, provider: "sonarqube", executorType: "local" }
|
|
474
481
|
})
|
|
475
482
|
);
|
|
476
483
|
|
|
@@ -498,7 +505,8 @@ export async function runSonarCloudStage({ config, logger, emitter, eventBase, s
|
|
|
498
505
|
emitProgress(
|
|
499
506
|
emitter,
|
|
500
507
|
makeEvent("sonarcloud:start", { ...eventBase, stage: "sonarcloud" }, {
|
|
501
|
-
message: "SonarCloud scanning"
|
|
508
|
+
message: "SonarCloud scanning",
|
|
509
|
+
detail: { provider: "sonarcloud", executorType: "local" }
|
|
502
510
|
})
|
|
503
511
|
);
|
|
504
512
|
|
|
@@ -532,7 +540,7 @@ export async function runSonarCloudStage({ config, logger, emitter, eventBase, s
|
|
|
532
540
|
makeEvent("sonarcloud:end", { ...eventBase, stage: "sonarcloud" }, {
|
|
533
541
|
status,
|
|
534
542
|
message,
|
|
535
|
-
detail: { projectKey: result.projectKey, exitCode: result.exitCode }
|
|
543
|
+
detail: { projectKey: result.projectKey, exitCode: result.exitCode, provider: "sonarcloud", executorType: "local" }
|
|
536
544
|
})
|
|
537
545
|
);
|
|
538
546
|
|
|
@@ -681,7 +689,7 @@ export async function runReviewerStage({ reviewerRole, config, logger, emitter,
|
|
|
681
689
|
emitter,
|
|
682
690
|
makeEvent("reviewer:start", { ...eventBase, stage: "reviewer" }, {
|
|
683
691
|
message: `Reviewer (${reviewerRole.provider}) running`,
|
|
684
|
-
detail: { reviewer: reviewerRole.provider }
|
|
692
|
+
detail: { reviewer: reviewerRole.provider, provider: reviewerRole.provider, executorType: "agent" }
|
|
685
693
|
})
|
|
686
694
|
);
|
|
687
695
|
|
|
@@ -692,6 +700,30 @@ export async function runReviewerStage({ reviewerRole, config, logger, emitter,
|
|
|
692
700
|
logger.warn(`Review diff generation failed: ${err.message}`);
|
|
693
701
|
return { approved: false, blocking_issues: [{ description: `Diff generation failed: ${err.message}` }], non_blocking_suggestions: [], summary: `Reviewer failed: cannot generate diff — ${err.message}`, confidence: 0 };
|
|
694
702
|
}
|
|
703
|
+
|
|
704
|
+
// Injection guard: scan diff before sending to AI reviewer
|
|
705
|
+
const { scanDiff } = await import("../utils/injection-guard.js");
|
|
706
|
+
const guardResult = scanDiff(diff);
|
|
707
|
+
if (!guardResult.clean) {
|
|
708
|
+
logger.warn(`Injection guard: ${guardResult.summary}`);
|
|
709
|
+
emitProgress(emitter, makeEvent("guard:injection", { ...eventBase, stage: "reviewer" }, {
|
|
710
|
+
message: `Injection guard blocked review: ${guardResult.summary}`,
|
|
711
|
+
detail: { findings: guardResult.findings, summary: guardResult.summary }
|
|
712
|
+
}));
|
|
713
|
+
return {
|
|
714
|
+
approved: false,
|
|
715
|
+
blocking_issues: guardResult.findings.map((f) => ({
|
|
716
|
+
id: `INJECTION_${f.type.toUpperCase()}`,
|
|
717
|
+
severity: "critical",
|
|
718
|
+
description: `Potential prompt injection (${f.type}): ${f.snippet}`,
|
|
719
|
+
line: f.line,
|
|
720
|
+
})),
|
|
721
|
+
non_blocking_suggestions: [],
|
|
722
|
+
summary: `Review blocked by injection guard: ${guardResult.summary}`,
|
|
723
|
+
confidence: 1,
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
|
|
695
727
|
const reviewerOnOutput = ({ stream, line }) => {
|
|
696
728
|
emitProgress(emitter, makeEvent("agent:output", { ...eventBase, stage: "reviewer" }, {
|
|
697
729
|
message: line,
|
|
@@ -754,7 +786,8 @@ export async function runReviewerStage({ reviewerRole, config, logger, emitter,
|
|
|
754
786
|
emitter,
|
|
755
787
|
makeEvent("reviewer:end", { ...eventBase, stage: "reviewer" }, {
|
|
756
788
|
status: "fail",
|
|
757
|
-
message: `Reviewer failed: ${details}
|
|
789
|
+
message: `Reviewer failed: ${details}`,
|
|
790
|
+
detail: { provider: reviewerRole.provider, executorType: "agent" }
|
|
758
791
|
})
|
|
759
792
|
);
|
|
760
793
|
throw new Error(`Reviewer failed: ${details}`);
|
|
@@ -836,7 +869,9 @@ export async function runReviewerStage({ reviewerRole, config, logger, emitter,
|
|
|
836
869
|
blockingCount: review.blocking_issues.length,
|
|
837
870
|
issues: review.blocking_issues.map(
|
|
838
871
|
(x) => `${x.id || "ISSUE"}: ${x.description || "Missing description"}`
|
|
839
|
-
)
|
|
872
|
+
),
|
|
873
|
+
provider: reviewerRole.provider,
|
|
874
|
+
executorType: "agent"
|
|
840
875
|
}
|
|
841
876
|
})
|
|
842
877
|
);
|
|
@@ -98,7 +98,8 @@ export async function runTesterStage({ config, logger, emitter, eventBase, sessi
|
|
|
98
98
|
emitProgress(
|
|
99
99
|
emitter,
|
|
100
100
|
makeEvent("tester:start", { ...eventBase, stage: "tester" }, {
|
|
101
|
-
message: "Tester evaluating test quality"
|
|
101
|
+
message: "Tester evaluating test quality",
|
|
102
|
+
detail: { provider: config?.roles?.tester?.provider || config?.roles?.coder?.provider || config?.coder || "claude", executorType: "agent" }
|
|
102
103
|
})
|
|
103
104
|
);
|
|
104
105
|
|
|
@@ -126,11 +127,13 @@ export async function runTesterStage({ config, logger, emitter, eventBase, sessi
|
|
|
126
127
|
attempts: attempts.length > 1 ? attempts : undefined
|
|
127
128
|
});
|
|
128
129
|
|
|
130
|
+
const testerProvider = provider || coderRole.provider;
|
|
129
131
|
emitProgress(
|
|
130
132
|
emitter,
|
|
131
133
|
makeEvent("tester:end", { ...eventBase, stage: "tester" }, {
|
|
132
134
|
status: testerOutput.ok ? "ok" : "fail",
|
|
133
|
-
message: testerOutput.ok ? "Tester passed" : `Tester: ${testerOutput.summary}
|
|
135
|
+
message: testerOutput.ok ? "Tester passed" : `Tester: ${testerOutput.summary}`,
|
|
136
|
+
detail: { ok: testerOutput.ok, summary: testerOutput.summary, provider: testerProvider, executorType: "agent" }
|
|
134
137
|
})
|
|
135
138
|
);
|
|
136
139
|
|
|
@@ -143,7 +146,7 @@ export async function runTesterStage({ config, logger, emitter, eventBase, sessi
|
|
|
143
146
|
makeEvent("tester:auto-continue", { ...eventBase, stage: "tester" }, {
|
|
144
147
|
status: "warn",
|
|
145
148
|
message: `Tester issues are advisory (reviewer approved), continuing: ${testerOutput.summary}`,
|
|
146
|
-
detail: { summary: testerOutput.summary, auto_continued: true }
|
|
149
|
+
detail: { summary: testerOutput.summary, auto_continued: true, provider: testerProvider, executorType: "agent" }
|
|
147
150
|
})
|
|
148
151
|
);
|
|
149
152
|
return { action: "ok", stageResult: { ok: false, summary: testerOutput.summary || "Tester issues (advisory)", auto_continued: true } };
|
|
@@ -158,7 +161,8 @@ export async function runSecurityStage({ config, logger, emitter, eventBase, ses
|
|
|
158
161
|
emitProgress(
|
|
159
162
|
emitter,
|
|
160
163
|
makeEvent("security:start", { ...eventBase, stage: "security" }, {
|
|
161
|
-
message: "Security auditing code"
|
|
164
|
+
message: "Security auditing code",
|
|
165
|
+
detail: { provider: config?.roles?.security?.provider || config?.roles?.coder?.provider || config?.coder || "claude", executorType: "agent" }
|
|
162
166
|
})
|
|
163
167
|
);
|
|
164
168
|
|
|
@@ -186,11 +190,13 @@ export async function runSecurityStage({ config, logger, emitter, eventBase, ses
|
|
|
186
190
|
attempts: attempts.length > 1 ? attempts : undefined
|
|
187
191
|
});
|
|
188
192
|
|
|
193
|
+
const securityProvider = provider || coderRole.provider;
|
|
189
194
|
emitProgress(
|
|
190
195
|
emitter,
|
|
191
196
|
makeEvent("security:end", { ...eventBase, stage: "security" }, {
|
|
192
197
|
status: securityOutput.ok ? "ok" : "fail",
|
|
193
|
-
message: securityOutput.ok ? "Security audit passed" : `Security: ${securityOutput.summary}
|
|
198
|
+
message: securityOutput.ok ? "Security audit passed" : `Security: ${securityOutput.summary}`,
|
|
199
|
+
detail: { ok: securityOutput.ok, summary: securityOutput.summary, provider: securityProvider, executorType: "agent" }
|
|
194
200
|
})
|
|
195
201
|
);
|
|
196
202
|
|
|
@@ -246,7 +252,8 @@ export async function runImpeccableStage({ config, logger, emitter, eventBase, s
|
|
|
246
252
|
emitProgress(
|
|
247
253
|
emitter,
|
|
248
254
|
makeEvent("impeccable:start", { ...eventBase, stage: "impeccable" }, {
|
|
249
|
-
message: "Impeccable auditing frontend design quality"
|
|
255
|
+
message: "Impeccable auditing frontend design quality",
|
|
256
|
+
detail: { provider: config?.roles?.impeccable?.provider || coderRole.provider, executorType: "agent" }
|
|
250
257
|
})
|
|
251
258
|
);
|
|
252
259
|
|
|
@@ -277,13 +284,15 @@ export async function runImpeccableStage({ config, logger, emitter, eventBase, s
|
|
|
277
284
|
});
|
|
278
285
|
|
|
279
286
|
const verdict = impeccableOutput.result?.verdict || "APPROVED";
|
|
287
|
+
const impeccableProvider = config?.roles?.impeccable?.provider || coderRole.provider;
|
|
280
288
|
emitProgress(
|
|
281
289
|
emitter,
|
|
282
290
|
makeEvent("impeccable:end", { ...eventBase, stage: "impeccable" }, {
|
|
283
291
|
status: impeccableOutput.ok ? "ok" : "fail",
|
|
284
292
|
message: impeccableOutput.ok
|
|
285
293
|
? (verdict === "IMPROVED" ? "Impeccable applied design fixes" : "Impeccable audit passed")
|
|
286
|
-
: `Impeccable: ${impeccableOutput.summary}
|
|
294
|
+
: `Impeccable: ${impeccableOutput.summary}`,
|
|
295
|
+
detail: { provider: impeccableProvider, executorType: "agent" }
|
|
287
296
|
})
|
|
288
297
|
);
|
|
289
298
|
|
|
@@ -296,7 +305,8 @@ export async function runFinalAuditStage({ config, logger, emitter, eventBase, s
|
|
|
296
305
|
emitProgress(
|
|
297
306
|
emitter,
|
|
298
307
|
makeEvent("audit:start", { ...eventBase, stage: "audit" }, {
|
|
299
|
-
message: "Final audit — verifying code quality"
|
|
308
|
+
message: "Final audit — verifying code quality",
|
|
309
|
+
detail: { provider: config?.roles?.audit?.provider || config?.roles?.coder?.provider || config?.coder || "claude", executorType: "agent" }
|
|
300
310
|
})
|
|
301
311
|
);
|
|
302
312
|
|
|
@@ -324,6 +334,7 @@ export async function runFinalAuditStage({ config, logger, emitter, eventBase, s
|
|
|
324
334
|
attempts: attempts.length > 1 ? attempts : undefined
|
|
325
335
|
});
|
|
326
336
|
|
|
337
|
+
const auditProvider = provider || coderRole.provider;
|
|
327
338
|
if (!auditOutput.ok) {
|
|
328
339
|
// Audit agent failed to run — treat as advisory, don't block pipeline
|
|
329
340
|
logger.warn(`Audit agent error (advisory): ${auditOutput.summary}`);
|
|
@@ -331,7 +342,8 @@ export async function runFinalAuditStage({ config, logger, emitter, eventBase, s
|
|
|
331
342
|
emitter,
|
|
332
343
|
makeEvent("audit:end", { ...eventBase, stage: "audit" }, {
|
|
333
344
|
status: "warn",
|
|
334
|
-
message: `Audit: agent error (advisory), continuing — ${auditOutput.summary}
|
|
345
|
+
message: `Audit: agent error (advisory), continuing — ${auditOutput.summary}`,
|
|
346
|
+
detail: { provider: auditProvider, executorType: "agent" }
|
|
335
347
|
})
|
|
336
348
|
);
|
|
337
349
|
return { action: "ok", stageResult: { ok: false, summary: auditOutput.summary || "Audit agent error (advisory)", auto_continued: true } };
|
|
@@ -374,7 +386,8 @@ export async function runFinalAuditStage({ config, logger, emitter, eventBase, s
|
|
|
374
386
|
emitter,
|
|
375
387
|
makeEvent("audit:end", { ...eventBase, stage: "audit" }, {
|
|
376
388
|
status: "fail",
|
|
377
|
-
message: `Audit: ${criticalCount + highCount} issue(s) found, sending back to coder
|
|
389
|
+
message: `Audit: ${criticalCount + highCount} issue(s) found, sending back to coder`,
|
|
390
|
+
detail: { provider: auditProvider, executorType: "agent" }
|
|
378
391
|
})
|
|
379
392
|
);
|
|
380
393
|
|
|
@@ -392,7 +405,8 @@ export async function runFinalAuditStage({ config, logger, emitter, eventBase, s
|
|
|
392
405
|
emitter,
|
|
393
406
|
makeEvent("audit:end", { ...eventBase, stage: "audit" }, {
|
|
394
407
|
status: "ok",
|
|
395
|
-
message: certifiedMsg
|
|
408
|
+
message: certifiedMsg,
|
|
409
|
+
detail: { provider: auditProvider, executorType: "agent" }
|
|
396
410
|
})
|
|
397
411
|
);
|
|
398
412
|
|