pi-smart-compact 8.0.8 → 9.1.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/ARCHITECTURE.md +90 -70
- package/CHANGELOG.md +45 -2
- package/README.md +52 -30
- package/SECURITY.md +1 -1
- package/dist/app/native-continuity-bridge.d.ts.map +1 -1
- package/dist/app/pending-slot.d.ts.map +1 -1
- package/dist/app/preflight.d.ts +2 -1
- package/dist/app/preflight.d.ts.map +1 -1
- package/dist/app/run-context.d.ts +10 -10
- package/dist/app/run-context.d.ts.map +1 -1
- package/dist/app/run-smart-compact.d.ts +18 -2
- package/dist/app/run-smart-compact.d.ts.map +1 -1
- package/dist/app/session-run-lock.d.ts.map +1 -1
- package/dist/app/smart-compact-input.d.ts +23 -0
- package/dist/app/smart-compact-input.d.ts.map +1 -0
- package/dist/app/steps/extract.d.ts.map +1 -1
- package/dist/app/steps/metrics.d.ts +4 -4
- package/dist/app/steps/metrics.d.ts.map +1 -1
- package/dist/app/steps/persist.d.ts +2 -3
- package/dist/app/steps/persist.d.ts.map +1 -1
- package/dist/app/steps/prepare.d.ts +4 -6
- package/dist/app/steps/prepare.d.ts.map +1 -1
- package/dist/app/steps/recover.d.ts +1 -1
- package/dist/app/steps/recover.d.ts.map +1 -1
- package/dist/app/steps/state.d.ts.map +1 -1
- package/dist/app/steps/synthesize.d.ts.map +1 -1
- package/dist/app/steps/verify.d.ts.map +1 -1
- package/dist/app/steps/window.d.ts +5 -1
- package/dist/app/steps/window.d.ts.map +1 -1
- package/dist/constants.d.ts +25 -4
- package/dist/constants.d.ts.map +1 -1
- package/dist/domain/scrub.d.ts.map +1 -1
- package/dist/domain/summary-parse.d.ts +1 -1
- package/dist/domain/summary-parse.d.ts.map +1 -1
- package/dist/domain/summary-schema.d.ts +1 -1
- package/dist/domain/summary-schema.d.ts.map +1 -1
- package/dist/domain/tool-semantics.d.ts +15 -9
- package/dist/domain/tool-semantics.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3105 -1389
- package/dist/infra/ai-messages.d.ts +6 -0
- package/dist/infra/ai-messages.d.ts.map +1 -1
- package/dist/infra/context-graph.d.ts.map +1 -1
- package/dist/infra/fs.d.ts +13 -15
- package/dist/infra/fs.d.ts.map +1 -1
- package/dist/infra/git.d.ts +5 -11
- package/dist/infra/git.d.ts.map +1 -1
- package/dist/infra/llm-client.d.ts +2 -0
- package/dist/infra/llm-client.d.ts.map +1 -1
- package/dist/infra/paths.d.ts +4 -2
- package/dist/infra/paths.d.ts.map +1 -1
- package/dist/infra/session-identity.d.ts +11 -0
- package/dist/infra/session-identity.d.ts.map +1 -1
- package/dist/infra/synthesis-cache.d.ts.map +1 -1
- package/dist/phases/explore.d.ts +2 -1
- package/dist/phases/explore.d.ts.map +1 -1
- package/dist/phases/synthesize.d.ts +4 -1
- package/dist/phases/synthesize.d.ts.map +1 -1
- package/dist/phases/verify.d.ts +10 -3
- package/dist/phases/verify.d.ts.map +1 -1
- package/dist/provider-eval.js +278 -27
- package/dist/provider-scenario-eval.js +422 -49
- package/dist/telemetry-report.js +278 -27
- package/dist/types.d.ts +51 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/ui/overlays.d.ts +2 -1
- package/dist/ui/overlays.d.ts.map +1 -1
- package/dist/utils/backups.d.ts +31 -0
- package/dist/utils/backups.d.ts.map +1 -0
- package/dist/utils/cache.d.ts +3 -2
- package/dist/utils/cache.d.ts.map +1 -1
- package/dist/utils/damage.d.ts.map +1 -1
- package/dist/utils/extraction.d.ts +8 -1
- package/dist/utils/extraction.d.ts.map +1 -1
- package/dist/utils/fingerprint.d.ts +5 -1
- package/dist/utils/fingerprint.d.ts.map +1 -1
- package/dist/utils/helpers.d.ts +15 -37
- package/dist/utils/helpers.d.ts.map +1 -1
- package/dist/utils/pruning.d.ts.map +1 -1
- package/dist/utils/session-log.d.ts +10 -18
- package/dist/utils/session-log.d.ts.map +1 -1
- package/dist/utils/state.d.ts +2 -2
- package/dist/utils/state.d.ts.map +1 -1
- package/dist/utils/tokens.d.ts +2 -0
- package/dist/utils/tokens.d.ts.map +1 -1
- package/dist/utils/type-guards.d.ts +2 -0
- package/dist/utils/type-guards.d.ts.map +1 -1
- package/package.json +2 -2
|
@@ -82,6 +82,35 @@ function assertSuccessful(message) {
|
|
|
82
82
|
}
|
|
83
83
|
return message;
|
|
84
84
|
}
|
|
85
|
+
async function withProviderDeadline(opts, invoke) {
|
|
86
|
+
if (opts.signal?.aborted)
|
|
87
|
+
throw new Error("LLM request aborted before dispatch");
|
|
88
|
+
const controller = new AbortController;
|
|
89
|
+
const watchdogMs = resolveCodexWatchdogMs(opts.maxTokens, opts.codexWatchdogMs);
|
|
90
|
+
const abort = Promise.withResolvers();
|
|
91
|
+
const abortFromCaller = () => {
|
|
92
|
+
controller.abort(opts.signal?.reason);
|
|
93
|
+
abort.reject(new Error("LLM request aborted by caller"));
|
|
94
|
+
};
|
|
95
|
+
opts.signal?.addEventListener("abort", abortFromCaller, { once: true });
|
|
96
|
+
const timeout = Promise.withResolvers();
|
|
97
|
+
const timer = setTimeout(() => {
|
|
98
|
+
controller.abort("provider-watchdog");
|
|
99
|
+
timeout.reject(new Error("Provider watchdog stopped generation after " + watchdogMs + "ms"));
|
|
100
|
+
}, watchdogMs);
|
|
101
|
+
if (typeof timer === "object" && "unref" in timer)
|
|
102
|
+
timer.unref();
|
|
103
|
+
try {
|
|
104
|
+
return await Promise.race([
|
|
105
|
+
invoke({ ...opts, signal: controller.signal }),
|
|
106
|
+
abort.promise,
|
|
107
|
+
timeout.promise
|
|
108
|
+
]);
|
|
109
|
+
} finally {
|
|
110
|
+
clearTimeout(timer);
|
|
111
|
+
opts.signal?.removeEventListener("abort", abortFromCaller);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
85
114
|
async function completeChatGptCodex(model, body, opts) {
|
|
86
115
|
const controller = new AbortController;
|
|
87
116
|
const watchdogMs = resolveCodexWatchdogMs(opts.maxTokens, opts.codexWatchdogMs);
|
|
@@ -125,10 +154,12 @@ async function completeChatGptCodex(model, body, opts) {
|
|
|
125
154
|
var rawLlmClient = {
|
|
126
155
|
complete: async (model, body, originalOpts) => {
|
|
127
156
|
const opts = withCodexWireLimit(model, originalOpts);
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
157
|
+
return withProviderDeadline(opts, async (bounded) => {
|
|
158
|
+
if (isChatGptCodex(model))
|
|
159
|
+
return completeChatGptCodex(model, body, bounded);
|
|
160
|
+
const response = bounded.reasoning === undefined ? await (await resolveComplete())(model, body, bounded) : await (await resolveCompleteSimple())(model, body, bounded);
|
|
161
|
+
return assertSuccessful(response);
|
|
162
|
+
});
|
|
132
163
|
}
|
|
133
164
|
};
|
|
134
165
|
var defaultLlmClient = rawLlmClient;
|
|
@@ -138,7 +169,7 @@ function getLlmClient() {
|
|
|
138
169
|
}
|
|
139
170
|
|
|
140
171
|
// src/constants.ts
|
|
141
|
-
var VERSION = "
|
|
172
|
+
var VERSION = "9.1.0";
|
|
142
173
|
var COMPACT_SYSTEM_PREFIX = "You are an expert conversation summarizer for a coding agent. " + "Produce structured markdown summaries. " + "Follow output format exactly. " + "Use EXACT names \u2014 never paraphrase code identifiers. " + "Trust deterministic extraction data over intuition.";
|
|
143
174
|
var PROFILES = {
|
|
144
175
|
light: {
|
|
@@ -180,7 +211,7 @@ var DEFAULT_CONFIG = {
|
|
|
180
211
|
backupEnabled: true,
|
|
181
212
|
backupDir: "",
|
|
182
213
|
minContextPercent: 60,
|
|
183
|
-
requireApproval:
|
|
214
|
+
requireApproval: true,
|
|
184
215
|
scrubSecrets: true,
|
|
185
216
|
scrubPii: false,
|
|
186
217
|
maxLlmCalls: 8,
|
|
@@ -220,6 +251,8 @@ Output EXACTLY this format:
|
|
|
220
251
|
- **[Decision]**: [Rationale]
|
|
221
252
|
` + `## Files Modified
|
|
222
253
|
- [Verified list from deterministic extraction]
|
|
254
|
+
` + `## Files Deleted
|
|
255
|
+
- [Verified deleted paths from deterministic extraction]
|
|
223
256
|
` + `## Files Read
|
|
224
257
|
- [Verified list from deterministic extraction]
|
|
225
258
|
` + `## Next Steps
|
|
@@ -243,6 +276,7 @@ Rules for Accuracy:
|
|
|
243
276
|
` + `**Summary**: [2-4 sentences: what happened, errors, code changes with paths]
|
|
244
277
|
` + `**Decisions**: [comma-separated, or "None"]
|
|
245
278
|
` + `**Modified**: [comma-separated paths, or "None"]
|
|
279
|
+
` + `**Deleted**: [comma-separated paths, or "None"]
|
|
246
280
|
` + `**Read**: [comma-separated paths, or "None"]
|
|
247
281
|
`;
|
|
248
282
|
var ASSEMBLY_PROMPT_PREFIX = `Merge these topic summaries into ONE coherent summary.
|
|
@@ -274,6 +308,8 @@ var ASSEMBLY_PROMPT_PREFIX = `Merge these topic summaries into ONE coherent summ
|
|
|
274
308
|
- **[Decision]**: [Rationale]
|
|
275
309
|
` + `## Files Modified
|
|
276
310
|
- [Verified deterministic list]
|
|
311
|
+
` + `## Files Deleted
|
|
312
|
+
- [Verified deterministic deleted paths]
|
|
277
313
|
` + `## Files Read
|
|
278
314
|
- [Verified deterministic list]
|
|
279
315
|
` + `## Next Steps
|
|
@@ -289,6 +325,14 @@ var FIVE_MINUTES_MS = 5 * 60 * 1000;
|
|
|
289
325
|
var ONE_HOUR_MS = 60 * 60 * 1000;
|
|
290
326
|
var SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000;
|
|
291
327
|
var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000;
|
|
328
|
+
var ID_PREFIX = {
|
|
329
|
+
PROJECT: "proj-",
|
|
330
|
+
COMPACT_SESSION: "sc-",
|
|
331
|
+
MULTI_TOOL_USE_SYNTHETIC: "mtu_",
|
|
332
|
+
OPEN_LOOP: "loop-",
|
|
333
|
+
DECISION: "decision-",
|
|
334
|
+
ERROR: "error-"
|
|
335
|
+
};
|
|
292
336
|
var TUNING = {
|
|
293
337
|
EMA_PREV: 0.7,
|
|
294
338
|
EMA_SAMPLE: 0.3,
|
|
@@ -328,6 +372,7 @@ var TRUNC = {
|
|
|
328
372
|
BACKUP_PREVIEW_LINES: 5,
|
|
329
373
|
FINGERPRINT_SEG: 2
|
|
330
374
|
};
|
|
375
|
+
var LIKELY_ERROR_RE = /(?:command not found|no such file|permission denied|syntax error|cannot find|module not found|compilation error|build failed|test failed|^FAIL\b|ERROR:)/i;
|
|
331
376
|
var METRICS_BUFFER_MAX = 200;
|
|
332
377
|
var RUNTIME_LOG_MAX_BYTES = 5 * 1024 * 1024;
|
|
333
378
|
var EXPLORER_SYSTEM_PROMPT = `You are a conversation analyst. You have deterministic extraction data and can query the raw conversation using tools.
|
|
@@ -406,6 +451,15 @@ function calibrationKey(provider, model) {
|
|
|
406
451
|
}
|
|
407
452
|
|
|
408
453
|
// src/utils/type-guards.ts
|
|
454
|
+
function isRecord(value) {
|
|
455
|
+
return typeof value === "object" && value !== null;
|
|
456
|
+
}
|
|
457
|
+
function isTextBlock(c) {
|
|
458
|
+
return isRecord(c) && c.type === "text" && typeof c.text === "string";
|
|
459
|
+
}
|
|
460
|
+
function isToolCallBlock(c) {
|
|
461
|
+
return isRecord(c) && c.type === "toolCall" && typeof c.name === "string" && isRecord(c.arguments);
|
|
462
|
+
}
|
|
409
463
|
var KNOWN_METHODS = new Set(["eesv", "single-pass", "heuristic"]);
|
|
410
464
|
var KNOWN_PROFILES = new Set(["light", "balanced", "aggressive"]);
|
|
411
465
|
var KNOWN_MODES = new Set(["balanced", "aggressive", "fast", "thorough"]);
|
|
@@ -464,6 +518,74 @@ function isKnownPathReference(ref, knownPaths) {
|
|
|
464
518
|
});
|
|
465
519
|
}
|
|
466
520
|
|
|
521
|
+
// src/domain/tool-semantics.ts
|
|
522
|
+
var PATH_KEYS = [
|
|
523
|
+
"path",
|
|
524
|
+
"file_path",
|
|
525
|
+
"filePath",
|
|
526
|
+
"filename",
|
|
527
|
+
"file",
|
|
528
|
+
"target_file",
|
|
529
|
+
"file_uri",
|
|
530
|
+
"absolute_path"
|
|
531
|
+
];
|
|
532
|
+
var PAYLOAD_KEYS = [
|
|
533
|
+
"content",
|
|
534
|
+
"newText",
|
|
535
|
+
"oldText",
|
|
536
|
+
"new_str",
|
|
537
|
+
"old_str",
|
|
538
|
+
"new_string",
|
|
539
|
+
"old_string",
|
|
540
|
+
"edits",
|
|
541
|
+
"patch",
|
|
542
|
+
"replacement"
|
|
543
|
+
];
|
|
544
|
+
var COMMAND_KEYS = ["command", "cmd", "script"];
|
|
545
|
+
function hasPresent(args, keys) {
|
|
546
|
+
return keys.some((k) => args[k] != null);
|
|
547
|
+
}
|
|
548
|
+
function extractToolPath(args) {
|
|
549
|
+
if (!args || typeof args !== "object")
|
|
550
|
+
return;
|
|
551
|
+
const a = args;
|
|
552
|
+
for (const k of PATH_KEYS) {
|
|
553
|
+
const v = a[k];
|
|
554
|
+
if (typeof v === "string" && v.length > 0)
|
|
555
|
+
return v;
|
|
556
|
+
}
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
function normalizeToolName(name) {
|
|
560
|
+
if (typeof name !== "string")
|
|
561
|
+
return "";
|
|
562
|
+
return name.replace(/^functions[.:/_-]+/i, "").replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
563
|
+
}
|
|
564
|
+
function nameHas(name, hints) {
|
|
565
|
+
const words = name.split("_");
|
|
566
|
+
return hints.some((hint) => words.includes(hint));
|
|
567
|
+
}
|
|
568
|
+
function classifyToolOperation(args, toolName) {
|
|
569
|
+
const a = args && typeof args === "object" ? args : {};
|
|
570
|
+
const name = normalizeToolName(toolName);
|
|
571
|
+
const hasPath = extractToolPath(a) !== undefined;
|
|
572
|
+
if (hasPath && hasPresent(a, PAYLOAD_KEYS))
|
|
573
|
+
return "mutate";
|
|
574
|
+
if (hasPresent(a, COMMAND_KEYS))
|
|
575
|
+
return "execute";
|
|
576
|
+
if (hasPath && hasPresent(a, ["text"]) && nameHas(name, ["write", "edit", "patch", "replace", "append", "create", "update", "insert"]))
|
|
577
|
+
return "mutate";
|
|
578
|
+
if (hasPath && nameHas(name, ["delete", "remove", "unlink"]))
|
|
579
|
+
return "delete";
|
|
580
|
+
if (hasPresent(a, ["pattern", "query", "glob"]) || nameHas(name, ["grep", "search", "find", "glob", "rg"]))
|
|
581
|
+
return "search";
|
|
582
|
+
if (nameHas(name, ["list", "ls", "tree"]))
|
|
583
|
+
return "list";
|
|
584
|
+
if (hasPath || nameHas(name, ["read"]))
|
|
585
|
+
return "read";
|
|
586
|
+
return "unknown";
|
|
587
|
+
}
|
|
588
|
+
|
|
467
589
|
// src/utils/file-ref-detect.ts
|
|
468
590
|
var CODE_EXT_RE = /\.(ts|tsx|js|jsx|mjs|cjs|rs|py|go|java|rb|cs|cpp|c|h|hpp|swift|kt|scala|php|css|scss|html|json|yaml|yml|toml|md|mdx|sh|sql|tf|ini|env|lock|gradle|xml)$/i;
|
|
469
591
|
var VERSION_RE = /^v?\d+(?:\.\d+)+(?:[-+][\w.-]+)?$/i;
|
|
@@ -499,6 +621,8 @@ function classifyHeading(raw) {
|
|
|
499
621
|
return "files-modified";
|
|
500
622
|
if (text.includes("file") && (text.includes("read") || text.includes("viewed")))
|
|
501
623
|
return "files-read";
|
|
624
|
+
if (text.includes("file") && (text.includes("delet") || text.includes("remov")))
|
|
625
|
+
return "files-deleted";
|
|
502
626
|
if (text.includes("next step") || text === "next actions")
|
|
503
627
|
return "next-steps";
|
|
504
628
|
if (text.includes("critical context") || text === "important context")
|
|
@@ -532,6 +656,7 @@ function parseSummary(markdown) {
|
|
|
532
656
|
let currentHeading = "";
|
|
533
657
|
let currentKind = "unknown";
|
|
534
658
|
let bodyLines = [];
|
|
659
|
+
let fence = null;
|
|
535
660
|
let started = false;
|
|
536
661
|
const flush = () => {
|
|
537
662
|
if (!started)
|
|
@@ -545,16 +670,31 @@ function parseSummary(markdown) {
|
|
|
545
670
|
sections.push({ kind: currentKind, heading: currentHeading.trim(), body });
|
|
546
671
|
};
|
|
547
672
|
for (const line of lines) {
|
|
548
|
-
const
|
|
549
|
-
if (
|
|
550
|
-
const
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
673
|
+
const fenceMatch = line.match(/^\s{0,3}(`{3,}|~{3,})(.*)$/);
|
|
674
|
+
if (fenceMatch) {
|
|
675
|
+
const marker = fenceMatch[1][0];
|
|
676
|
+
const markerLength = fenceMatch[1].length;
|
|
677
|
+
if (!fence) {
|
|
678
|
+
fence = { marker, length: markerLength };
|
|
679
|
+
} else if (marker === fence.marker && markerLength >= fence.length && !fenceMatch[2].trim()) {
|
|
680
|
+
fence = null;
|
|
681
|
+
}
|
|
682
|
+
if (started)
|
|
683
|
+
bodyLines.push(line);
|
|
684
|
+
continue;
|
|
685
|
+
}
|
|
686
|
+
if (!fence) {
|
|
687
|
+
const heading = line.match(HEADING_RE);
|
|
688
|
+
if (heading) {
|
|
689
|
+
const kind = classifyHeading(heading[2]);
|
|
690
|
+
if (heading[1].length <= 2 || kind !== "unknown") {
|
|
691
|
+
flush();
|
|
692
|
+
currentHeading = "## " + heading[2].trim();
|
|
693
|
+
currentKind = kind;
|
|
694
|
+
bodyLines = [];
|
|
695
|
+
started = true;
|
|
696
|
+
continue;
|
|
697
|
+
}
|
|
558
698
|
}
|
|
559
699
|
}
|
|
560
700
|
if (started)
|
|
@@ -569,12 +709,68 @@ function findSection(summary, kind) {
|
|
|
569
709
|
}
|
|
570
710
|
|
|
571
711
|
// src/utils/extraction.ts
|
|
712
|
+
function nestedToolCallId(wrapperId, messageIndex, toolIndex, nestedId) {
|
|
713
|
+
return typeof nestedId === "string" ? nestedId : wrapperId ? wrapperId + "_" + toolIndex : ID_PREFIX.MULTI_TOOL_USE_SYNTHETIC + messageIndex + "_" + toolIndex;
|
|
714
|
+
}
|
|
715
|
+
function flattenToolCallBlock(b) {
|
|
716
|
+
if (!isToolCallBlock(b))
|
|
717
|
+
return [];
|
|
718
|
+
if (b.name === "multi_tool_use.parallel" && Array.isArray(b.arguments?.tool_uses)) {
|
|
719
|
+
return b.arguments.tool_uses.map((u) => {
|
|
720
|
+
const recipient = u?.recipient_name ?? "";
|
|
721
|
+
return {
|
|
722
|
+
name: recipient.replace(/^functions\./, ""),
|
|
723
|
+
id: u?.id ?? undefined,
|
|
724
|
+
arguments: u?.parameters ?? {}
|
|
725
|
+
};
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
return [{ name: b.name, id: b.id, arguments: b.arguments }];
|
|
729
|
+
}
|
|
730
|
+
function extractText(content) {
|
|
731
|
+
if (typeof content === "string")
|
|
732
|
+
return content;
|
|
733
|
+
if (!Array.isArray(content))
|
|
734
|
+
return "";
|
|
735
|
+
return content.map((b) => {
|
|
736
|
+
if (typeof b === "string")
|
|
737
|
+
return b;
|
|
738
|
+
if (isTextBlock(b))
|
|
739
|
+
return b.text;
|
|
740
|
+
return "";
|
|
741
|
+
}).join("");
|
|
742
|
+
}
|
|
743
|
+
function buildToolCallIndex(msgs) {
|
|
744
|
+
const idx = new Map;
|
|
745
|
+
for (let i = 0;i < msgs.length; i++) {
|
|
746
|
+
const m = msgs[i];
|
|
747
|
+
if (m.role !== "assistant")
|
|
748
|
+
continue;
|
|
749
|
+
const blocks = Array.isArray(m.content) ? m.content : [];
|
|
750
|
+
for (const b of blocks) {
|
|
751
|
+
if (!isToolCallBlock(b))
|
|
752
|
+
continue;
|
|
753
|
+
if (b.id) {
|
|
754
|
+
idx.set(b.id, { name: b.name, arguments: b.arguments, msgIndex: i });
|
|
755
|
+
}
|
|
756
|
+
if (b.name === "multi_tool_use.parallel" && Array.isArray(b.arguments?.tool_uses)) {
|
|
757
|
+
const nested = flattenToolCallBlock(b);
|
|
758
|
+
for (let t = 0;t < nested.length; t++) {
|
|
759
|
+
const tool = nested[t];
|
|
760
|
+
const id = nestedToolCallId(b.id, i, t, tool.id);
|
|
761
|
+
idx.set(id, { name: tool.name, arguments: tool.arguments, msgIndex: i });
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
return idx;
|
|
767
|
+
}
|
|
572
768
|
var CONSTRAINT_PATTERNS = [
|
|
573
769
|
{ re: /\b(?:must|need|require|has to|important)\b.*\b(?:be|use|have|include|support)\b/i, cat: "requirement", conf: TUNING.CONFIDENCE_HIGH },
|
|
574
770
|
{ re: /\b(?:don't|never|avoid|shouldn't|must not|do not|no\s+(?:need|want))\b/i, cat: "prohibition", conf: TUNING.CONFIDENCE_MEDIUM },
|
|
575
771
|
{ re: /\b(?:prefer|like|want|would rather|should)\b.*\b(?:use|be|have|with)\b/i, cat: "preference", conf: TUNING.CONFIDENCE_LOW },
|
|
576
|
-
{ re: /(?<![A-Za-z0-9_])(?:
|
|
577
|
-
{ re: /(?<![A-Za-z0-9_])(?:
|
|
772
|
+
{ re: /(?<![A-Za-z0-9_])(?:yapma|kullanma|sak\u0131n|sak\u0131nha|asla(?:\s+(?:kullanma|yapma|getirme))?|bunu yapma)(?![A-Za-z0-9_])/iu, cat: "prohibition", conf: TUNING.CONFIDENCE_MEDIUM },
|
|
773
|
+
{ re: /(?<![A-Za-z0-9_])(?:kritik|kritikal|\u00F6nemli|onemli|\u015Fart|sart|zorunlu|\u015Fart ko\u015Ful|\u00F6nemli \u015Fart|kesinlikle|kesinlikle \u015Fart|b\u00F6yle olsun|b\u00F6yle yap\u0131n|\u015F\u00F6yle olsun|\u015F\u00F6yle yap\u0131n)(?![A-Za-z0-9_])/iu, cat: "requirement", conf: TUNING.CONFIDENCE_MEDIUM },
|
|
578
774
|
{ re: /(?<![A-Za-z0-9_])(?:tercih|isterim|olsun|kullanal\u0131m|yapal\u0131m|istiyorum)(?![A-Za-z0-9_])/iu, cat: "preference", conf: TUNING.CONFIDENCE_LOW }
|
|
579
775
|
];
|
|
580
776
|
function isDiagnosticConstraintText(text) {
|
|
@@ -641,7 +837,6 @@ function readJsonlTail(target, limit, maxBytes = 512 * 1024) {
|
|
|
641
837
|
}
|
|
642
838
|
return values.slice(-limit);
|
|
643
839
|
}
|
|
644
|
-
var scheduledTailTrims = new Set;
|
|
645
840
|
|
|
646
841
|
// src/infra/clock.ts
|
|
647
842
|
var systemClock = {
|
|
@@ -655,14 +850,23 @@ import crypto from "crypto";
|
|
|
655
850
|
var SECRET_PATTERNS = [
|
|
656
851
|
{ kind: "private-key", regex: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g },
|
|
657
852
|
{ kind: "aws-access-key", regex: /\bAKIA[0-9A-Z]{16}\b/g },
|
|
658
|
-
{ kind: "
|
|
853
|
+
{ kind: "google-api-key", regex: /\bAIza[0-9A-Za-z_-]{30,}\b/g },
|
|
854
|
+
{ kind: "stripe-key", regex: /\b[rs]k_(?:live|test)_[0-9A-Za-z]{16,}\b/g },
|
|
855
|
+
{ kind: "gitlab-token", regex: /\bglpat-[0-9A-Za-z_-]{20,}\b/g },
|
|
856
|
+
{ kind: "npm-token", regex: /\bnpm_[0-9A-Za-z]{30,}\b/g },
|
|
857
|
+
{ kind: "github-token", regex: /\bgh[pousr]_[A-Za-z0-9]{20,}\b/g },
|
|
659
858
|
{ kind: "api-key", regex: /\bsk-(?:ant-)?[A-Za-z0-9_-]{20,}\b/g },
|
|
660
|
-
{ kind: "slack-token", regex: /\bxox[baprs]-[A-Za-z0-9-]{
|
|
859
|
+
{ kind: "slack-token", regex: /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g },
|
|
661
860
|
{ kind: "jwt", regex: /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g },
|
|
662
|
-
{ kind: "bearer-token", regex: /\bBearer\s+[A-Za-z0-9._~+\/-]{
|
|
861
|
+
{ kind: "bearer-token", regex: /\bBearer\s+[A-Za-z0-9._~+\/-]{12,}=*/gi, replacement: () => "Bearer [REDACTED:bearer-token]" },
|
|
862
|
+
{
|
|
863
|
+
kind: "connection-password",
|
|
864
|
+
regex: /\b([a-z][a-z0-9+.-]*:\/\/[^:\s/@]+:)[^@\s/]+(@)/gi,
|
|
865
|
+
replacement: (prefix, suffix) => prefix + "[REDACTED:password]" + suffix
|
|
866
|
+
},
|
|
663
867
|
{
|
|
664
868
|
kind: "credential",
|
|
665
|
-
regex: /\b(api[_-]?key|access[_-]?token|auth[_-]?token|token|password|passwd|secret)\s*([:=])\s*["']?([
|
|
869
|
+
regex: /\b((?:[A-Za-z0-9]+[_-])*(?:api[_-]?key|access[_-]?token|auth[_-]?token|token|password|passwd|secret(?:[_-]?(?:access)?[_-]?key)?|client[_-]?secret)(?:[_-][A-Za-z0-9]+)*)\s*([:=])\s*["']?([^\s"']{16,})["']?/gi,
|
|
666
870
|
replacement: (name, separator) => name + separator + "[REDACTED:credential]"
|
|
667
871
|
}
|
|
668
872
|
];
|
|
@@ -690,6 +894,42 @@ function mergeFindings(target, findings) {
|
|
|
690
894
|
for (const finding of findings)
|
|
691
895
|
target.set(finding.kind, (target.get(finding.kind) ?? 0) + finding.count);
|
|
692
896
|
}
|
|
897
|
+
var SECRET_KEY_NAMES = {
|
|
898
|
+
api_key: true,
|
|
899
|
+
apikey: true,
|
|
900
|
+
access_token: true,
|
|
901
|
+
auth_token: true,
|
|
902
|
+
authorization: true,
|
|
903
|
+
password: true,
|
|
904
|
+
passwd: true,
|
|
905
|
+
secret: true,
|
|
906
|
+
secret_key: true,
|
|
907
|
+
secret_access_key: true,
|
|
908
|
+
client_secret: true,
|
|
909
|
+
private_key: true,
|
|
910
|
+
database_url: true,
|
|
911
|
+
connection_string: true,
|
|
912
|
+
token: true,
|
|
913
|
+
refresh_token: true,
|
|
914
|
+
session_token: true,
|
|
915
|
+
credential: true,
|
|
916
|
+
credentials: true,
|
|
917
|
+
cookie: true,
|
|
918
|
+
set_cookie: true,
|
|
919
|
+
otp: true,
|
|
920
|
+
one_time_password: true,
|
|
921
|
+
pin: true,
|
|
922
|
+
passcode: true
|
|
923
|
+
};
|
|
924
|
+
function normalizeObjectKey(key) {
|
|
925
|
+
return key.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
926
|
+
}
|
|
927
|
+
function isSecretBearingKey(key) {
|
|
928
|
+
const normalized = normalizeObjectKey(key);
|
|
929
|
+
if (SECRET_KEY_NAMES[normalized])
|
|
930
|
+
return true;
|
|
931
|
+
return /(?:^|_)(?:api_key|access_token|auth_token|password|passwd|secret_access_key|client_secret|private_key|refresh_token|session_token|one_time_password|passcode)(?:_|$)/.test(normalized);
|
|
932
|
+
}
|
|
693
933
|
|
|
694
934
|
class SecretScrubber {
|
|
695
935
|
secretsEnabled;
|
|
@@ -719,6 +959,10 @@ class SecretScrubber {
|
|
|
719
959
|
scrubValue(input) {
|
|
720
960
|
const findings = new Map;
|
|
721
961
|
const seen = new WeakMap;
|
|
962
|
+
const recordCredential = () => {
|
|
963
|
+
findings.set("credential", (findings.get("credential") ?? 0) + 1);
|
|
964
|
+
this.total++;
|
|
965
|
+
};
|
|
722
966
|
const visit = (value2) => {
|
|
723
967
|
if (typeof value2 === "string") {
|
|
724
968
|
const result = this.scrubText(value2);
|
|
@@ -739,8 +983,15 @@ class SecretScrubber {
|
|
|
739
983
|
}
|
|
740
984
|
const output = {};
|
|
741
985
|
seen.set(value2, output);
|
|
742
|
-
for (const [key, item] of Object.entries(value2))
|
|
743
|
-
|
|
986
|
+
for (const [key, item] of Object.entries(value2)) {
|
|
987
|
+
const carriesSecret = typeof item === "string" ? item.length > 0 : item != null;
|
|
988
|
+
if (this.secretsEnabled && isSecretBearingKey(key) && carriesSecret) {
|
|
989
|
+
output[key] = "[REDACTED:credential]";
|
|
990
|
+
recordCredential();
|
|
991
|
+
} else {
|
|
992
|
+
output[key] = visit(item);
|
|
993
|
+
}
|
|
994
|
+
}
|
|
744
995
|
return output;
|
|
745
996
|
};
|
|
746
997
|
const value = visit(input);
|
|
@@ -999,8 +1250,8 @@ function readMetricsLog(limit = 100) {
|
|
|
999
1250
|
const fd = fs2.openSync(logPath, "r");
|
|
1000
1251
|
try {
|
|
1001
1252
|
const buf = Buffer.alloc(wantBytes);
|
|
1002
|
-
fs2.readSync(fd, buf, 0, wantBytes, startPos);
|
|
1003
|
-
let text = buf.toString("utf8");
|
|
1253
|
+
const bytesRead = fs2.readSync(fd, buf, 0, wantBytes, startPos);
|
|
1254
|
+
let text = buf.subarray(0, bytesRead).toString("utf8");
|
|
1004
1255
|
if (startPos > 0) {
|
|
1005
1256
|
const nl = text.indexOf(`
|
|
1006
1257
|
`);
|
|
@@ -1028,6 +1279,79 @@ function readMetricsLog(limit = 100) {
|
|
|
1028
1279
|
}
|
|
1029
1280
|
|
|
1030
1281
|
// src/phases/verify.ts
|
|
1282
|
+
var HIGH_RISK_OUTCOME_RE = /(?:\ball\s+tests?\s+(?:pass|passed|passing)\b|\btests?\s+(?:pass|passed|passing)\b|\b(?:build|deployment|migration)\s+(?:completed|succeeded|passed|successful)\b|\b(?:deployed|published|released)\b|\b(?:bug|issue|error)\s+(?:fixed|resolved)\b|\bno\s+(?:errors?|failures?)\b|\bcompleted successfully\b|\btestler?\s+(?:ge\u00E7ti|ba\u015Far\u0131l\u0131)\b|\bba\u015Far\u0131yla\s+(?:tamamland\u0131|da\u011F\u0131t\u0131ld\u0131|yay\u0131nland\u0131)\b|\b(?:deploy edildi|yay\u0131nland\u0131|hata yok)\b)/iu;
|
|
1283
|
+
var NEGATED_OUTCOME_RE = /\b(?:not|never|pending|failed|failing|unresolved|hen\u00FCz|de\u011Fil|ba\u015Far\u0131s\u0131z)\b/iu;
|
|
1284
|
+
function outcomeClaims(summary) {
|
|
1285
|
+
return Array.from(new Set(summary.split(/\r?\n/).map((line) => line.replace(/^\s*(?:[-*+]|\d+[.)])\s+/, "").replace(/^\[[ x]\]\s+/i, "").trim()).filter((line) => line.length > 0 && !line.startsWith("#")).filter((line) => HIGH_RISK_OUTCOME_RE.test(line)).filter((line) => /\bno\s+(?:errors?|failures?)\b/i.test(line) || !NEGATED_OUTCOME_RE.test(line)))).slice(0, 12);
|
|
1286
|
+
}
|
|
1287
|
+
function classifyOutcomeClaim(claim) {
|
|
1288
|
+
const lower = claim.toLowerCase();
|
|
1289
|
+
if (/\btests?\b|\btestler?\b/.test(lower))
|
|
1290
|
+
return "test";
|
|
1291
|
+
if (/\bbuild\b|\bcompil(?:e|ed|ation)\b|\btypecheck\b/.test(lower))
|
|
1292
|
+
return "build";
|
|
1293
|
+
if (/\bdeploy(?:ed|ment)?\b|\bpublish(?:ed)?\b|\breleas(?:e|ed)\b/.test(lower))
|
|
1294
|
+
return "release";
|
|
1295
|
+
if (/\bbug\b|\bissue\b|\berror\b|\bfail(?:ed|ure)?\b|\bhata\b/.test(lower))
|
|
1296
|
+
return "error";
|
|
1297
|
+
if (/\bfile\b|\bdosya\b/.test(lower))
|
|
1298
|
+
return "file";
|
|
1299
|
+
return "generic";
|
|
1300
|
+
}
|
|
1301
|
+
var successfulToolEvidenceCache = new WeakMap;
|
|
1302
|
+
function successfulToolEvidence(messages) {
|
|
1303
|
+
const cached = successfulToolEvidenceCache.get(messages);
|
|
1304
|
+
if (cached)
|
|
1305
|
+
return cached;
|
|
1306
|
+
const toolCalls = buildToolCallIndex(messages);
|
|
1307
|
+
const evidence = [];
|
|
1308
|
+
for (const message of messages) {
|
|
1309
|
+
if (message.role !== "toolResult" || message.isError)
|
|
1310
|
+
continue;
|
|
1311
|
+
const call = toolCalls.get(message.toolCallId ?? "");
|
|
1312
|
+
if (!call)
|
|
1313
|
+
continue;
|
|
1314
|
+
const result = extractText(message.content).slice(0, 8000);
|
|
1315
|
+
if (!result.trim() || LIKELY_ERROR_RE.test(result))
|
|
1316
|
+
continue;
|
|
1317
|
+
const command = [call.arguments.command, call.arguments.cmd, call.arguments.script].find((value) => typeof value === "string") ?? "";
|
|
1318
|
+
evidence.push({
|
|
1319
|
+
name: normalizeToolName(call.name),
|
|
1320
|
+
operation: classifyToolOperation(call.arguments, call.name),
|
|
1321
|
+
command,
|
|
1322
|
+
path: extractToolPath(call.arguments),
|
|
1323
|
+
result
|
|
1324
|
+
});
|
|
1325
|
+
}
|
|
1326
|
+
successfulToolEvidenceCache.set(messages, evidence);
|
|
1327
|
+
return evidence;
|
|
1328
|
+
}
|
|
1329
|
+
function successfulToolSupportsClaim(claim, tools, extraction) {
|
|
1330
|
+
const shape = semanticShape(claim);
|
|
1331
|
+
const category = classifyOutcomeClaim(claim);
|
|
1332
|
+
if (category === "error" && extraction.errors.some((error) => error.resolved && hasSemanticEvidence(claim, error.message)))
|
|
1333
|
+
return true;
|
|
1334
|
+
if (category === "file" && extraction.modifiedFiles.some((file) => claim.toLowerCase().includes(file.path.toLowerCase())))
|
|
1335
|
+
return true;
|
|
1336
|
+
for (const tool of tools) {
|
|
1337
|
+
const operationText = tool.name + " " + tool.command;
|
|
1338
|
+
const operationSupports = category === "test" ? /\b(?:test|tests|pytest|jest|vitest|mocha|rspec)\b/i.test(operationText) : category === "build" ? /\b(?:build|compile|typecheck|tsc|check)\b/i.test(operationText) : category === "release" ? /\b(?:deploy|publish|release)\b/i.test(operationText) : category === "file" ? tool.operation === "mutate" || tool.operation === "delete" : category === "error" ? tool.operation === "execute" || tool.operation === "mutate" || tool.operation === "delete" : tool.operation !== "read" && tool.operation !== "search" && tool.operation !== "list";
|
|
1339
|
+
if (!operationSupports)
|
|
1340
|
+
continue;
|
|
1341
|
+
if (hasSemanticEvidence(claim, tool.result))
|
|
1342
|
+
return true;
|
|
1343
|
+
const lower = tool.result.toLowerCase();
|
|
1344
|
+
if (category === "test" && /\b\d+\s+(?:tests?\s+)?pass(?:ed)?\b/.test(lower) && !/\b(?:fail(?:ed|ures?)?|errors?)\s*[:=]?\s*[1-9]\d*\b/.test(lower))
|
|
1345
|
+
return true;
|
|
1346
|
+
if (category === "build" && /\b(?:succeeded|successful|passed|exit(?:ed)?\s+(?:code\s+)?0)\b/.test(lower))
|
|
1347
|
+
return true;
|
|
1348
|
+
if (category === "release" && /\b(?:succeeded|successful|completed|published|deployed|released)\b/.test(lower))
|
|
1349
|
+
return true;
|
|
1350
|
+
if (category === "error" && shape.concepts.length > 0 && /\b(?:fixed|resolved|passed|succeeded|successful)\b/.test(lower) && hasSemanticEvidence(claim, tool.result))
|
|
1351
|
+
return true;
|
|
1352
|
+
}
|
|
1353
|
+
return false;
|
|
1354
|
+
}
|
|
1031
1355
|
var NEGATION_MARKERS = new Set([
|
|
1032
1356
|
"no",
|
|
1033
1357
|
"not",
|
|
@@ -1108,9 +1432,15 @@ function stemToken(token) {
|
|
|
1108
1432
|
function semanticTokens(text) {
|
|
1109
1433
|
return (text.normalize("NFKC").match(/[\p{L}\p{N}_-]+/gu) ?? []).map(stemToken).filter((token) => token.length > 2);
|
|
1110
1434
|
}
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1435
|
+
var semanticShapeCache = new Map;
|
|
1436
|
+
var semanticFragmentCache = new Map;
|
|
1437
|
+
function semanticFragments(text) {
|
|
1438
|
+
const cached = lruGet(semanticFragmentCache, text);
|
|
1439
|
+
if (cached)
|
|
1440
|
+
return cached;
|
|
1441
|
+
const fragments = Array.from(new Set(text.split(/\r?\n/).flatMap((line) => [line, ...line.split(/[.;]/)]).map((part) => part.replace(/^\s*[-*\d.)]+\s*/, "").trim()).filter(Boolean))).map(semanticTokens);
|
|
1442
|
+
lruSet(semanticFragmentCache, text, fragments, 256);
|
|
1443
|
+
return fragments;
|
|
1114
1444
|
}
|
|
1115
1445
|
function hasNearbyMarker(tokens, anchor, markers) {
|
|
1116
1446
|
return tokens.some((token, index) => token === anchor && tokens.slice(Math.max(0, index - 2), index + 3).some((near) => markers.has(near)));
|
|
@@ -1134,20 +1464,24 @@ function hasEffectiveTargetNegation(tokens, anchor) {
|
|
|
1134
1464
|
});
|
|
1135
1465
|
}
|
|
1136
1466
|
function semanticShape(source) {
|
|
1467
|
+
const cached = lruGet(semanticShapeCache, source);
|
|
1468
|
+
if (cached)
|
|
1469
|
+
return cached;
|
|
1137
1470
|
const sourceTokens = semanticTokens(source);
|
|
1138
1471
|
const concepts = Array.from(new Set(sourceTokens.filter((token) => !/^\d+$/.test(token) && !SEMANTIC_STOP.has(token) && !NEGATION_MARKERS.has(token) && !CONDITION_MARKERS.has(token))));
|
|
1139
1472
|
const negative = sourceTokens.some((token) => NEGATION_MARKERS.has(token));
|
|
1140
1473
|
const conditional = sourceTokens.some((token) => CONDITION_MARKERS.has(token));
|
|
1141
1474
|
const anchor = concepts.find((concept) => hasNearbyMarker(sourceTokens, concept, NEGATION_MARKERS)) ?? concepts[0] ?? "";
|
|
1142
|
-
|
|
1475
|
+
const shape = { sourceTokens, concepts, anchor, negative, conditional };
|
|
1476
|
+
lruSet(semanticShapeCache, source, shape, 512);
|
|
1477
|
+
return shape;
|
|
1143
1478
|
}
|
|
1144
1479
|
function hasSemanticEvidence(source, target) {
|
|
1145
1480
|
const { sourceTokens, concepts, anchor, negative, conditional } = semanticShape(source);
|
|
1146
1481
|
if (!concepts.length)
|
|
1147
1482
|
return true;
|
|
1148
1483
|
const required = Math.min(concepts.length, Math.max(1, Math.ceil(concepts.length * 0.6)));
|
|
1149
|
-
return
|
|
1150
|
-
const tokens = semanticTokens(fragment);
|
|
1484
|
+
return semanticFragments(target).some((tokens) => {
|
|
1151
1485
|
const overlap = concepts.filter((concept) => tokens.includes(concept)).length;
|
|
1152
1486
|
if (overlap < required)
|
|
1153
1487
|
return false;
|
|
@@ -1169,8 +1503,7 @@ function hasSemanticContradiction(source, target) {
|
|
|
1169
1503
|
if (!anchor)
|
|
1170
1504
|
return false;
|
|
1171
1505
|
const required = Math.min(concepts.length, Math.max(1, Math.ceil(concepts.length * 0.6)));
|
|
1172
|
-
return
|
|
1173
|
-
const tokens = semanticTokens(fragment);
|
|
1506
|
+
return semanticFragments(target).some((tokens) => {
|
|
1174
1507
|
if (!tokens.includes(anchor))
|
|
1175
1508
|
return false;
|
|
1176
1509
|
const overlap = concepts.filter((concept) => tokens.includes(concept)).length;
|
|
@@ -1186,7 +1519,7 @@ function hasSemanticContradiction(source, target) {
|
|
|
1186
1519
|
return conditional && !negative && !tokens.some((token) => CONDITION_MARKERS.has(token));
|
|
1187
1520
|
});
|
|
1188
1521
|
}
|
|
1189
|
-
function verifySummary(summary, extraction, continuity = null) {
|
|
1522
|
+
function verifySummary(summary, extraction, continuity = null, evidence = {}) {
|
|
1190
1523
|
const parsed = parseSummary(summary);
|
|
1191
1524
|
const gaps = [];
|
|
1192
1525
|
const lower = summary.toLowerCase().replace(/\\/g, "/");
|
|
@@ -1206,9 +1539,18 @@ function verifySummary(summary, extraction, continuity = null) {
|
|
|
1206
1539
|
...extraction.errors.filter((error) => !error.resolved).map((error) => ({ message: error.message })),
|
|
1207
1540
|
...(continuity?.unresolvedErrors ?? []).map((error) => ({ message: error.message }))
|
|
1208
1541
|
], (item) => item.message);
|
|
1542
|
+
const resolvedEvidence = uniqueByText([
|
|
1543
|
+
...extraction.errors.filter((error) => error.resolved).map((error) => ({ message: error.message })),
|
|
1544
|
+
...(continuity?.resolvedErrors ?? []).map((error) => ({ message: error.message }))
|
|
1545
|
+
], (item) => item.message).slice(-5);
|
|
1546
|
+
const steeringConstraints = [
|
|
1547
|
+
evidence.steering?.focus ? { text: "Preserve detail about: " + evidence.steering.focus } : null,
|
|
1548
|
+
evidence.steering?.note ? { text: evidence.steering.note } : null
|
|
1549
|
+
].filter((item) => Boolean(item?.text.trim()));
|
|
1209
1550
|
const constraintEvidence = uniqueByText([
|
|
1210
1551
|
...extraction.constraints.filter((item) => item.confidence >= 0.8).map((item) => ({ text: item.text })),
|
|
1211
|
-
...(continuity?.constraints ?? []).filter((item) => item.confidence >= 0.8).map((item) => ({ text: item.text }))
|
|
1552
|
+
...(continuity?.constraints ?? []).filter((item) => item.confidence >= 0.8).map((item) => ({ text: item.text })),
|
|
1553
|
+
...steeringConstraints
|
|
1212
1554
|
], (item) => item.text).filter((item) => !isDiagnosticConstraintText(item.text));
|
|
1213
1555
|
const decisionEvidence = uniqueByText([
|
|
1214
1556
|
...extraction.decisions.filter((item) => item.type === "explicit").map((item) => ({ summary: item.summary })),
|
|
@@ -1226,16 +1568,29 @@ function verifySummary(summary, extraction, continuity = null) {
|
|
|
1226
1568
|
score -= req.penalty;
|
|
1227
1569
|
}
|
|
1228
1570
|
}
|
|
1571
|
+
const listedPaths = (kind) => new Set((findSection(parsed, kind)?.body ?? "").split(`
|
|
1572
|
+
`).map((line) => line.replace(/^\s*(?:[-*+]|\d+[.)])\s+/, "").replace(/^\[[ x]\]\s+/i, "").trim()).map((line) => line.startsWith("`") && line.endsWith("`") ? line.slice(1, -1) : line).filter((line) => line.length > 0 && !/^none(?: recorded)?[.!]?$/i.test(line)).map(normalizePath));
|
|
1229
1573
|
const modifiedPaths = extraction.modifiedFiles.map((file) => file.path);
|
|
1230
|
-
const
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
if (!
|
|
1235
|
-
gaps.push({ kind: "missing-file", path: file
|
|
1236
|
-
|
|
1237
|
-
|
|
1574
|
+
const modifiedListed = listedPaths("files-modified");
|
|
1575
|
+
const readListed = listedPaths("files-read");
|
|
1576
|
+
const deletedListed = listedPaths("files-deleted");
|
|
1577
|
+
for (const file of modifiedPaths) {
|
|
1578
|
+
if (!modifiedListed.has(normalizePath(file)))
|
|
1579
|
+
gaps.push({ kind: "missing-file", path: file });
|
|
1580
|
+
}
|
|
1581
|
+
for (const file of extraction.readFiles) {
|
|
1582
|
+
if (!readListed.has(normalizePath(file)))
|
|
1583
|
+
gaps.push({ kind: "missing-read-file", path: file });
|
|
1584
|
+
}
|
|
1585
|
+
const deletedEvidence = Array.from(new Set([
|
|
1586
|
+
...extraction.deletedFiles,
|
|
1587
|
+
...continuity?.deletedFiles ?? []
|
|
1588
|
+
]));
|
|
1589
|
+
for (const file of deletedEvidence) {
|
|
1590
|
+
if (!deletedListed.has(normalizePath(file)))
|
|
1591
|
+
gaps.push({ kind: "missing-deleted-file", path: file });
|
|
1238
1592
|
}
|
|
1593
|
+
score -= gaps.filter((gap) => gap.kind === "missing-file" || gap.kind === "missing-read-file" || gap.kind === "missing-deleted-file").length * 5;
|
|
1239
1594
|
for (const error of unresolvedEvidence) {
|
|
1240
1595
|
const snippet = summaryEvidenceLine(error.message, TRUNC.ERROR_SNIPPET).toLowerCase();
|
|
1241
1596
|
if (snippet.length > 5 && !normalizedSummary.includes(snippet)) {
|
|
@@ -1243,6 +1598,13 @@ function verifySummary(summary, extraction, continuity = null) {
|
|
|
1243
1598
|
score -= 5;
|
|
1244
1599
|
}
|
|
1245
1600
|
}
|
|
1601
|
+
for (const error of resolvedEvidence) {
|
|
1602
|
+
const snippet = summaryEvidenceLine(error.message, TRUNC.ERROR_SNIPPET).toLowerCase();
|
|
1603
|
+
if (snippet.length > 5 && !normalizedSummary.includes(snippet)) {
|
|
1604
|
+
gaps.push({ kind: "missing-error", message: error.message, resolved: true });
|
|
1605
|
+
score -= 2;
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1246
1608
|
const constraintTarget = [
|
|
1247
1609
|
findSection(parsed, "constraints")?.body ?? "",
|
|
1248
1610
|
findSection(parsed, "critical-context")?.body ?? ""
|
|
@@ -1303,16 +1665,18 @@ function verifySummary(summary, extraction, continuity = null) {
|
|
|
1303
1665
|
gaps.push({ kind: "inconsistency", detail: "blocked-none: Blocked says none despite unresolved errors" });
|
|
1304
1666
|
score -= 12;
|
|
1305
1667
|
}
|
|
1668
|
+
const doneRefs = new Set(extractFileRefs(doneSection).map(normalizePath));
|
|
1306
1669
|
for (const file of extraction.modifiedFiles) {
|
|
1307
|
-
const
|
|
1308
|
-
if (!
|
|
1670
|
+
const uniqueNeedles = buildUniquePathNeedles(file.path, modifiedPaths);
|
|
1671
|
+
if (!uniqueNeedles.some((needle) => doneRefs.has(normalizePath(needle))))
|
|
1309
1672
|
continue;
|
|
1310
1673
|
const unresolved = unresolvedEvidence.find((error) => {
|
|
1311
1674
|
const firstLine = error.message.split(/\r?\n/, 1)[0] ?? "";
|
|
1312
|
-
|
|
1675
|
+
const errorRefs = extractFileRefs(firstLine).map(normalizePath);
|
|
1676
|
+
return uniqueNeedles.some((needle) => errorRefs.includes(normalizePath(needle)));
|
|
1313
1677
|
});
|
|
1314
1678
|
if (unresolved) {
|
|
1315
|
-
gaps.push({ kind: "inconsistency", detail:
|
|
1679
|
+
gaps.push({ kind: "inconsistency", detail: file.path + " marked Done but has unresolved error" });
|
|
1316
1680
|
score -= 5;
|
|
1317
1681
|
}
|
|
1318
1682
|
}
|
|
@@ -1333,6 +1697,15 @@ function verifySummary(summary, extraction, continuity = null) {
|
|
|
1333
1697
|
gaps.push({ kind: "missing-open-loops", unresolvedCount });
|
|
1334
1698
|
score -= 5;
|
|
1335
1699
|
}
|
|
1700
|
+
if (evidence.sourceMessages) {
|
|
1701
|
+
const tools = successfulToolEvidence(evidence.sourceMessages);
|
|
1702
|
+
for (const claim of outcomeClaims(summary)) {
|
|
1703
|
+
if (!successfulToolSupportsClaim(claim, tools, extraction)) {
|
|
1704
|
+
gaps.push({ kind: "unsupported-claim", claim });
|
|
1705
|
+
score -= 20;
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1336
1709
|
const finalScore = Math.max(0, score);
|
|
1337
1710
|
return { ok: gaps.length === 0 && finalScore >= 85, gaps, score: finalScore };
|
|
1338
1711
|
}
|