taskplane 0.29.2 → 0.30.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/bin/gitignore-patterns.mjs +11 -8
- package/bin/rpc-wrapper.mjs +410 -357
- package/bin/taskplane.mjs +533 -250
- package/extensions/reviewer-extension.ts +17 -11
- package/extensions/taskplane/abort.ts +50 -18
- package/extensions/taskplane/agent-bridge-extension.ts +232 -105
- package/extensions/taskplane/agent-host.ts +224 -97
- package/extensions/taskplane/cleanup.ts +71 -42
- package/extensions/taskplane/config-loader.ts +142 -58
- package/extensions/taskplane/config-schema.ts +6 -13
- package/extensions/taskplane/config.ts +10 -2
- package/extensions/taskplane/diagnostic-reports.ts +59 -47
- package/extensions/taskplane/diagnostics.ts +13 -13
- package/extensions/taskplane/discovery.ts +35 -61
- package/extensions/taskplane/engine-worker.ts +53 -46
- package/extensions/taskplane/engine.ts +1760 -602
- package/extensions/taskplane/execution.ts +426 -206
- package/extensions/taskplane/extension.ts +1073 -598
- package/extensions/taskplane/formatting.ts +136 -124
- package/extensions/taskplane/git.ts +0 -2
- package/extensions/taskplane/lane-runner.ts +542 -311
- package/extensions/taskplane/mailbox.ts +57 -49
- package/extensions/taskplane/merge.ts +662 -383
- package/extensions/taskplane/messages.ts +109 -51
- package/extensions/taskplane/migrations.ts +1 -1
- package/extensions/taskplane/path-resolver.ts +8 -9
- package/extensions/taskplane/persistence.ts +425 -262
- package/extensions/taskplane/process-registry.ts +36 -7
- package/extensions/taskplane/quality-gate.ts +107 -55
- package/extensions/taskplane/resume.ts +774 -267
- package/extensions/taskplane/sessions.ts +1 -1
- package/extensions/taskplane/settings-tui.ts +505 -164
- package/extensions/taskplane/sidecar-telemetry.ts +25 -10
- package/extensions/taskplane/supervisor.ts +477 -270
- package/extensions/taskplane/task-executor-core.ts +178 -53
- package/extensions/taskplane/types.ts +186 -108
- package/extensions/taskplane/verification.ts +27 -22
- package/extensions/taskplane/waves.ts +59 -43
- package/extensions/taskplane/workspace.ts +14 -12
- package/extensions/taskplane/worktree.ts +218 -196
- package/package.json +14 -2
|
@@ -19,7 +19,16 @@
|
|
|
19
19
|
* @since TP-104
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
existsSync,
|
|
24
|
+
mkdirSync,
|
|
25
|
+
readFileSync,
|
|
26
|
+
writeFileSync,
|
|
27
|
+
readdirSync,
|
|
28
|
+
rmSync,
|
|
29
|
+
appendFileSync,
|
|
30
|
+
renameSync,
|
|
31
|
+
} from "fs";
|
|
23
32
|
import { join, dirname } from "path";
|
|
24
33
|
|
|
25
34
|
import {
|
|
@@ -41,6 +50,12 @@ import {
|
|
|
41
50
|
type PacketPaths,
|
|
42
51
|
} from "./types.ts";
|
|
43
52
|
|
|
53
|
+
// TP-195: Re-export RuntimeRegistry so dynamic-import references in
|
|
54
|
+
// execution.ts (`import("./process-registry.ts").RuntimeRegistry`) resolve
|
|
55
|
+
// without each call site having to import directly from types.ts. Pure
|
|
56
|
+
// re-export — no runtime impact.
|
|
57
|
+
export type { RuntimeRegistry };
|
|
58
|
+
|
|
44
59
|
// ── Manifest Lifecycle ───────────────────────────────────────────────
|
|
45
60
|
|
|
46
61
|
/**
|
|
@@ -66,7 +81,11 @@ export function writeManifest(stateRoot: string, manifest: RuntimeAgentManifest)
|
|
|
66
81
|
*
|
|
67
82
|
* @since TP-104
|
|
68
83
|
*/
|
|
69
|
-
export function readManifest(
|
|
84
|
+
export function readManifest(
|
|
85
|
+
stateRoot: string,
|
|
86
|
+
batchId: string,
|
|
87
|
+
agentId: RuntimeAgentId,
|
|
88
|
+
): RuntimeAgentManifest | null {
|
|
70
89
|
const path = runtimeManifestPath(stateRoot, batchId, agentId);
|
|
71
90
|
if (!existsSync(path)) return null;
|
|
72
91
|
try {
|
|
@@ -237,7 +256,7 @@ export function isTerminalStatus(status: RuntimeAgentStatus): boolean {
|
|
|
237
256
|
* @since TP-104
|
|
238
257
|
*/
|
|
239
258
|
export function getLiveAgents(registry: RuntimeRegistry): RuntimeAgentManifest[] {
|
|
240
|
-
return Object.values(registry.agents).filter(m => !isTerminalStatus(m.status));
|
|
259
|
+
return Object.values(registry.agents).filter((m) => !isTerminalStatus(m.status));
|
|
241
260
|
}
|
|
242
261
|
|
|
243
262
|
/**
|
|
@@ -245,8 +264,11 @@ export function getLiveAgents(registry: RuntimeRegistry): RuntimeAgentManifest[]
|
|
|
245
264
|
*
|
|
246
265
|
* @since TP-104
|
|
247
266
|
*/
|
|
248
|
-
export function getAgentsByRole(
|
|
249
|
-
|
|
267
|
+
export function getAgentsByRole(
|
|
268
|
+
registry: RuntimeRegistry,
|
|
269
|
+
role: RuntimeAgentRole,
|
|
270
|
+
): RuntimeAgentManifest[] {
|
|
271
|
+
return Object.values(registry.agents).filter((m) => m.role === role);
|
|
250
272
|
}
|
|
251
273
|
|
|
252
274
|
// ── Orphan Detection ─────────────────────────────────────────────────
|
|
@@ -276,7 +298,11 @@ export function detectOrphans(registry: RuntimeRegistry): RuntimeAgentId[] {
|
|
|
276
298
|
*
|
|
277
299
|
* @since TP-104
|
|
278
300
|
*/
|
|
279
|
-
export function markOrphansCrashed(
|
|
301
|
+
export function markOrphansCrashed(
|
|
302
|
+
stateRoot: string,
|
|
303
|
+
batchId: string,
|
|
304
|
+
orphanIds: RuntimeAgentId[],
|
|
305
|
+
): void {
|
|
280
306
|
for (const agentId of orphanIds) {
|
|
281
307
|
updateManifestStatus(stateRoot, batchId, agentId, "crashed");
|
|
282
308
|
}
|
|
@@ -291,7 +317,10 @@ export function markOrphansCrashed(stateRoot: string, batchId: string, orphanIds
|
|
|
291
317
|
*
|
|
292
318
|
* @since TP-104
|
|
293
319
|
*/
|
|
294
|
-
export function cleanupBatchRuntime(
|
|
320
|
+
export function cleanupBatchRuntime(
|
|
321
|
+
stateRoot: string,
|
|
322
|
+
batchId: string,
|
|
323
|
+
): { removed: boolean; error?: string } {
|
|
295
324
|
const root = runtimeRoot(stateRoot, batchId);
|
|
296
325
|
if (!existsSync(root)) return { removed: false };
|
|
297
326
|
try {
|
|
@@ -115,9 +115,7 @@ export function applyVerdictRules(
|
|
|
115
115
|
const failReasons: VerdictFailReason[] = [];
|
|
116
116
|
|
|
117
117
|
// Rule 1: Any status_mismatch category → NEEDS_FIXES
|
|
118
|
-
const statusMismatches = verdict.findings.filter(
|
|
119
|
-
(f) => f.category === "status_mismatch",
|
|
120
|
-
);
|
|
118
|
+
const statusMismatches = verdict.findings.filter((f) => f.category === "status_mismatch");
|
|
121
119
|
if (statusMismatches.length > 0) {
|
|
122
120
|
failReasons.push({
|
|
123
121
|
rule: "status_mismatch",
|
|
@@ -135,9 +133,7 @@ export function applyVerdictRules(
|
|
|
135
133
|
}
|
|
136
134
|
|
|
137
135
|
// Rule 3: Threshold-dependent important check
|
|
138
|
-
const importants = verdict.findings.filter(
|
|
139
|
-
(f) => f.severity === "important",
|
|
140
|
-
);
|
|
136
|
+
const importants = verdict.findings.filter((f) => f.severity === "important");
|
|
141
137
|
|
|
142
138
|
if (threshold === "no_important" && importants.length >= 3) {
|
|
143
139
|
failReasons.push({
|
|
@@ -167,14 +163,8 @@ export function applyVerdictRules(
|
|
|
167
163
|
}
|
|
168
164
|
|
|
169
165
|
// For all_clear threshold: even suggestions-only should fail
|
|
170
|
-
if (
|
|
171
|
-
|
|
172
|
-
failReasons.length === 0 &&
|
|
173
|
-
verdict.findings.length > 0
|
|
174
|
-
) {
|
|
175
|
-
const suggestions = verdict.findings.filter(
|
|
176
|
-
(f) => f.severity === "suggestion",
|
|
177
|
-
);
|
|
166
|
+
if (threshold === "all_clear" && failReasons.length === 0 && verdict.findings.length > 0) {
|
|
167
|
+
const suggestions = verdict.findings.filter((f) => f.severity === "suggestion");
|
|
178
168
|
if (suggestions.length > 0) {
|
|
179
169
|
failReasons.push({
|
|
180
170
|
rule: "important_threshold",
|
|
@@ -228,7 +218,10 @@ export function parseVerdict(jsonString: string | undefined | null): ReviewVerdi
|
|
|
228
218
|
}
|
|
229
219
|
|
|
230
220
|
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
231
|
-
return {
|
|
221
|
+
return {
|
|
222
|
+
...FAIL_OPEN_VERDICT,
|
|
223
|
+
summary: "Verdict is not a JSON object — fail-open policy applied",
|
|
224
|
+
};
|
|
232
225
|
}
|
|
233
226
|
|
|
234
227
|
const obj = raw as Record<string, unknown>;
|
|
@@ -236,7 +229,10 @@ export function parseVerdict(jsonString: string | undefined | null): ReviewVerdi
|
|
|
236
229
|
// Validate verdict field
|
|
237
230
|
const verdict = obj.verdict;
|
|
238
231
|
if (verdict !== "PASS" && verdict !== "NEEDS_FIXES") {
|
|
239
|
-
return {
|
|
232
|
+
return {
|
|
233
|
+
...FAIL_OPEN_VERDICT,
|
|
234
|
+
summary: `Invalid verdict value "${String(verdict)}" — fail-open policy applied`,
|
|
235
|
+
};
|
|
240
236
|
}
|
|
241
237
|
|
|
242
238
|
// Parse confidence with fallback
|
|
@@ -396,7 +392,10 @@ function buildGitDiff(cwd: string): { diff: string; fileList: string } {
|
|
|
396
392
|
try {
|
|
397
393
|
const base = computeDiffBase(cwd);
|
|
398
394
|
if (!base) {
|
|
399
|
-
return {
|
|
395
|
+
return {
|
|
396
|
+
diff: "(git diff unavailable — could not determine base)",
|
|
397
|
+
fileList: "(file list unavailable)",
|
|
398
|
+
};
|
|
400
399
|
}
|
|
401
400
|
|
|
402
401
|
const range = `${base}..HEAD`;
|
|
@@ -407,9 +406,7 @@ function buildGitDiff(cwd: string): { diff: string; fileList: string } {
|
|
|
407
406
|
cwd,
|
|
408
407
|
timeout: 30000,
|
|
409
408
|
});
|
|
410
|
-
const fileList = fileListResult.status === 0
|
|
411
|
-
? fileListResult.stdout.trim()
|
|
412
|
-
: "";
|
|
409
|
+
const fileList = fileListResult.status === 0 ? fileListResult.stdout.trim() : "";
|
|
413
410
|
|
|
414
411
|
// Get full diff (truncated to avoid blowing up context)
|
|
415
412
|
const diffResult = spawnSync("git", ["diff", range], {
|
|
@@ -418,9 +415,7 @@ function buildGitDiff(cwd: string): { diff: string; fileList: string } {
|
|
|
418
415
|
timeout: 30000,
|
|
419
416
|
maxBuffer: 200 * 1024, // 200KB max
|
|
420
417
|
});
|
|
421
|
-
const diff = diffResult.status === 0
|
|
422
|
-
? diffResult.stdout.trim()
|
|
423
|
-
: "(git diff unavailable)";
|
|
418
|
+
const diff = diffResult.status === 0 ? diffResult.stdout.trim() : "(git diff unavailable)";
|
|
424
419
|
|
|
425
420
|
return { diff, fileList };
|
|
426
421
|
} catch {
|
|
@@ -455,13 +450,17 @@ function buildThresholdRules(threshold: PassThreshold): string[] {
|
|
|
455
450
|
const rules: string[] = [];
|
|
456
451
|
|
|
457
452
|
// Common rules — always apply
|
|
458
|
-
rules.push(
|
|
453
|
+
rules.push(
|
|
454
|
+
`- **NEEDS_FIXES** if any finding has category \`status_mismatch\` (checkbox claims work is done but it isn't)`,
|
|
455
|
+
);
|
|
459
456
|
rules.push(`- **NEEDS_FIXES** if any finding has severity \`critical\``);
|
|
460
457
|
|
|
461
458
|
// Threshold-specific rules
|
|
462
459
|
switch (threshold) {
|
|
463
460
|
case "no_critical":
|
|
464
|
-
rules.push(
|
|
461
|
+
rules.push(
|
|
462
|
+
`- **PASS** even if there are \`important\` or \`suggestion\` findings (threshold: \`no_critical\`)`,
|
|
463
|
+
);
|
|
465
464
|
break;
|
|
466
465
|
case "no_important":
|
|
467
466
|
rules.push(`- **NEEDS_FIXES** if 3 or more findings have severity \`important\``);
|
|
@@ -488,22 +487,27 @@ export function generateQualityGatePrompt(context: QualityGateContext, cwd: stri
|
|
|
488
487
|
if (existsSync(context.promptPath)) {
|
|
489
488
|
promptContent = readFileSync(context.promptPath, "utf-8");
|
|
490
489
|
}
|
|
491
|
-
} catch {
|
|
490
|
+
} catch {
|
|
491
|
+
/* fail-open: proceed without */
|
|
492
|
+
}
|
|
492
493
|
|
|
493
494
|
let statusContent = "(STATUS.md not found)";
|
|
494
495
|
try {
|
|
495
496
|
if (existsSync(statusPath)) {
|
|
496
497
|
statusContent = readFileSync(statusPath, "utf-8");
|
|
497
498
|
}
|
|
498
|
-
} catch {
|
|
499
|
+
} catch {
|
|
500
|
+
/* fail-open: proceed without */
|
|
501
|
+
}
|
|
499
502
|
|
|
500
503
|
const { diff, fileList } = buildGitDiff(cwd);
|
|
501
504
|
|
|
502
505
|
// Truncate diff if too long (keep first 100KB)
|
|
503
506
|
const maxDiffLen = 100 * 1024;
|
|
504
|
-
const truncatedDiff =
|
|
505
|
-
|
|
506
|
-
|
|
507
|
+
const truncatedDiff =
|
|
508
|
+
diff.length > maxDiffLen
|
|
509
|
+
? diff.slice(0, maxDiffLen) + "\n\n... (diff truncated at 100KB) ..."
|
|
510
|
+
: diff;
|
|
507
511
|
|
|
508
512
|
return [
|
|
509
513
|
`# Quality Gate Review`,
|
|
@@ -670,9 +674,9 @@ export interface ReconciliationAction {
|
|
|
670
674
|
*/
|
|
671
675
|
function normalizeCheckboxText(text: string): string {
|
|
672
676
|
return text
|
|
673
|
-
.replace(/\*\*|__|``|`/g, "")
|
|
674
|
-
.replace(/\s+/g, " ")
|
|
675
|
-
.replace(/^\s*[-*•]\s*/, "")
|
|
677
|
+
.replace(/\*\*|__|``|`/g, "") // strip bold/code formatting
|
|
678
|
+
.replace(/\s+/g, " ") // collapse whitespace
|
|
679
|
+
.replace(/^\s*[-*•]\s*/, "") // strip leading bullets
|
|
676
680
|
.trim()
|
|
677
681
|
.toLowerCase();
|
|
678
682
|
}
|
|
@@ -718,7 +722,11 @@ export function applyStatusReconciliation(
|
|
|
718
722
|
// No STATUS.md — mark all as unmatched
|
|
719
723
|
for (const r of reconciliations) {
|
|
720
724
|
result.unmatched++;
|
|
721
|
-
result.actions.push({
|
|
725
|
+
result.actions.push({
|
|
726
|
+
checkbox: r.checkbox,
|
|
727
|
+
outcome: "unmatched",
|
|
728
|
+
reason: "STATUS.md not found",
|
|
729
|
+
});
|
|
722
730
|
}
|
|
723
731
|
return result;
|
|
724
732
|
}
|
|
@@ -726,7 +734,11 @@ export function applyStatusReconciliation(
|
|
|
726
734
|
} catch {
|
|
727
735
|
for (const r of reconciliations) {
|
|
728
736
|
result.unmatched++;
|
|
729
|
-
result.actions.push({
|
|
737
|
+
result.actions.push({
|
|
738
|
+
checkbox: r.checkbox,
|
|
739
|
+
outcome: "unmatched",
|
|
740
|
+
reason: "STATUS.md unreadable",
|
|
741
|
+
});
|
|
730
742
|
}
|
|
731
743
|
return result;
|
|
732
744
|
}
|
|
@@ -742,7 +754,11 @@ export function applyStatusReconciliation(
|
|
|
742
754
|
const normalizedRecon = normalizeCheckboxText(recon.checkbox);
|
|
743
755
|
if (!normalizedRecon) {
|
|
744
756
|
result.unmatched++;
|
|
745
|
-
result.actions.push({
|
|
757
|
+
result.actions.push({
|
|
758
|
+
checkbox: recon.checkbox,
|
|
759
|
+
outcome: "unmatched",
|
|
760
|
+
reason: "Empty checkbox text after normalization",
|
|
761
|
+
});
|
|
746
762
|
continue;
|
|
747
763
|
}
|
|
748
764
|
|
|
@@ -755,7 +771,11 @@ export function applyStatusReconciliation(
|
|
|
755
771
|
|
|
756
772
|
const lineText = normalizeCheckboxText(cbMatch[4]);
|
|
757
773
|
// Match if either contains the other (handles paraphrasing)
|
|
758
|
-
if (
|
|
774
|
+
if (
|
|
775
|
+
lineText === normalizedRecon ||
|
|
776
|
+
lineText.includes(normalizedRecon) ||
|
|
777
|
+
normalizedRecon.includes(lineText)
|
|
778
|
+
) {
|
|
759
779
|
matchedIdx = i;
|
|
760
780
|
break;
|
|
761
781
|
}
|
|
@@ -763,7 +783,11 @@ export function applyStatusReconciliation(
|
|
|
763
783
|
|
|
764
784
|
if (matchedIdx === -1) {
|
|
765
785
|
result.unmatched++;
|
|
766
|
-
result.actions.push({
|
|
786
|
+
result.actions.push({
|
|
787
|
+
checkbox: recon.checkbox,
|
|
788
|
+
outcome: "unmatched",
|
|
789
|
+
reason: "No matching checkbox found in STATUS.md",
|
|
790
|
+
});
|
|
767
791
|
continue;
|
|
768
792
|
}
|
|
769
793
|
|
|
@@ -779,7 +803,11 @@ export function applyStatusReconciliation(
|
|
|
779
803
|
if (shouldBeChecked && currentlyChecked) {
|
|
780
804
|
// Already correct
|
|
781
805
|
result.alreadyCorrect++;
|
|
782
|
-
result.actions.push({
|
|
806
|
+
result.actions.push({
|
|
807
|
+
checkbox: recon.checkbox,
|
|
808
|
+
outcome: "no_change",
|
|
809
|
+
reason: "Already checked (done)",
|
|
810
|
+
});
|
|
783
811
|
} else if (!shouldBeChecked && !currentlyChecked) {
|
|
784
812
|
// Already correct (unchecked for not_done or partial)
|
|
785
813
|
// But if partial, might need annotation
|
|
@@ -787,25 +815,38 @@ export function applyStatusReconciliation(
|
|
|
787
815
|
// Add partial annotation
|
|
788
816
|
lines[matchedIdx] = `${cbMatch[1]} ${cbMatch[3]}${currentText} (partial)`;
|
|
789
817
|
result.changed++;
|
|
790
|
-
result.actions.push({
|
|
818
|
+
result.actions.push({
|
|
819
|
+
checkbox: recon.checkbox,
|
|
820
|
+
outcome: "unchecked",
|
|
821
|
+
reason: "Added (partial) annotation",
|
|
822
|
+
});
|
|
791
823
|
} else {
|
|
792
824
|
result.alreadyCorrect++;
|
|
793
|
-
result.actions.push({
|
|
825
|
+
result.actions.push({
|
|
826
|
+
checkbox: recon.checkbox,
|
|
827
|
+
outcome: "no_change",
|
|
828
|
+
reason: `Already unchecked (${recon.actualState})`,
|
|
829
|
+
});
|
|
794
830
|
}
|
|
795
831
|
} else if (shouldBeChecked && !currentlyChecked) {
|
|
796
832
|
// Need to check
|
|
797
833
|
lines[matchedIdx] = `${cbMatch[1]}x${cbMatch[3]}${currentText}`;
|
|
798
834
|
result.changed++;
|
|
799
|
-
result.actions.push({
|
|
835
|
+
result.actions.push({
|
|
836
|
+
checkbox: recon.checkbox,
|
|
837
|
+
outcome: "checked",
|
|
838
|
+
reason: "Work done but box was unchecked",
|
|
839
|
+
});
|
|
800
840
|
} else {
|
|
801
841
|
// currentlyChecked but should not be (not_done or partial)
|
|
802
842
|
const annotation = recon.actualState === "partial" ? " (partial)" : "";
|
|
803
843
|
const cleanText = currentText.replace(/\s*\(partial\)\s*$/, "");
|
|
804
844
|
lines[matchedIdx] = `${cbMatch[1]} ${cbMatch[3]}${cleanText}${annotation}`;
|
|
805
845
|
result.changed++;
|
|
806
|
-
const outcomeReason =
|
|
807
|
-
|
|
808
|
-
|
|
846
|
+
const outcomeReason =
|
|
847
|
+
recon.actualState === "partial"
|
|
848
|
+
? "Unchecked — work partially done"
|
|
849
|
+
: "Unchecked — work not done";
|
|
809
850
|
result.actions.push({ checkbox: recon.checkbox, outcome: "unchecked", reason: outcomeReason });
|
|
810
851
|
}
|
|
811
852
|
}
|
|
@@ -860,10 +901,10 @@ export function generateFeedbackMd(
|
|
|
860
901
|
maxCycles: number,
|
|
861
902
|
passThreshold: PassThreshold = "no_critical",
|
|
862
903
|
): string {
|
|
863
|
-
const criticals = verdict.findings.filter(f => f.severity === "critical");
|
|
864
|
-
const importants = verdict.findings.filter(f => f.severity === "important");
|
|
865
|
-
const suggestions = verdict.findings.filter(f => f.severity === "suggestion");
|
|
866
|
-
const mismatches = verdict.statusReconciliation.filter(r => r.actualState !== "done");
|
|
904
|
+
const criticals = verdict.findings.filter((f) => f.severity === "critical");
|
|
905
|
+
const importants = verdict.findings.filter((f) => f.severity === "important");
|
|
906
|
+
const suggestions = verdict.findings.filter((f) => f.severity === "suggestion");
|
|
907
|
+
const mismatches = verdict.statusReconciliation.filter((r) => r.actualState !== "done");
|
|
867
908
|
|
|
868
909
|
// Under all_clear, suggestions are also blocking
|
|
869
910
|
const includeSuggestions = passThreshold === "all_clear";
|
|
@@ -940,14 +981,21 @@ export function generateFeedbackMd(
|
|
|
940
981
|
lines.push(``);
|
|
941
982
|
}
|
|
942
983
|
|
|
943
|
-
const totalBlocking =
|
|
944
|
-
|
|
984
|
+
const totalBlocking =
|
|
985
|
+
criticals.length +
|
|
986
|
+
importants.length +
|
|
987
|
+
(includeSuggestions ? suggestions.length : 0) +
|
|
988
|
+
mismatches.length;
|
|
945
989
|
|
|
946
990
|
if (totalBlocking === 0) {
|
|
947
991
|
lines.push(`## No blocking findings`);
|
|
948
992
|
lines.push(``);
|
|
949
|
-
lines.push(
|
|
950
|
-
|
|
993
|
+
lines.push(
|
|
994
|
+
`The review returned NEEDS_FIXES but no blocking findings were extracted for threshold \`${passThreshold}\`.`,
|
|
995
|
+
);
|
|
996
|
+
lines.push(
|
|
997
|
+
`This may indicate a threshold or verdict-rule mismatch. Review the REVIEW_VERDICT.json for details.`,
|
|
998
|
+
);
|
|
951
999
|
lines.push(``);
|
|
952
1000
|
}
|
|
953
1001
|
|
|
@@ -978,14 +1026,18 @@ export function buildFixAgentPrompt(
|
|
|
978
1026
|
if (existsSync(statusPath)) {
|
|
979
1027
|
statusContent = readFileSync(statusPath, "utf-8");
|
|
980
1028
|
}
|
|
981
|
-
} catch {
|
|
1029
|
+
} catch {
|
|
1030
|
+
/* proceed without */
|
|
1031
|
+
}
|
|
982
1032
|
|
|
983
1033
|
let promptContent = "(PROMPT.md not found)";
|
|
984
1034
|
try {
|
|
985
1035
|
if (existsSync(context.promptPath)) {
|
|
986
1036
|
promptContent = readFileSync(context.promptPath, "utf-8");
|
|
987
1037
|
}
|
|
988
|
-
} catch {
|
|
1038
|
+
} catch {
|
|
1039
|
+
/* proceed without */
|
|
1040
|
+
}
|
|
989
1041
|
|
|
990
1042
|
return [
|
|
991
1043
|
`# Quality Gate Remediation — Fix Cycle ${cycleNum}`,
|