pi-smart-compact 9.0.0 → 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 +45 -35
- package/CHANGELOG.md +17 -0
- 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 +3 -3
- package/dist/app/preflight.d.ts.map +1 -1
- 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/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.map +1 -1
- package/dist/app/steps/synthesize.d.ts.map +1 -1
- package/dist/app/steps/window.d.ts +1 -1
- package/dist/app/steps/window.d.ts.map +1 -1
- package/dist/constants.d.ts +5 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/domain/scrub.d.ts.map +1 -1
- package/dist/domain/summary-parse.d.ts.map +1 -1
- package/dist/domain/tool-semantics.d.ts +6 -9
- package/dist/domain/tool-semantics.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1109 -347
- 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 +2 -0
- package/dist/infra/fs.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.map +1 -1
- package/dist/phases/verify.d.ts.map +1 -1
- package/dist/provider-eval.js +214 -33
- package/dist/provider-scenario-eval.js +277 -57
- package/dist/telemetry-report.js +214 -33
- package/dist/types.d.ts +6 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/cache.d.ts +2 -2
- package/dist/utils/cache.d.ts.map +1 -1
- package/dist/utils/extraction.d.ts +2 -1
- package/dist/utils/extraction.d.ts.map +1 -1
- package/dist/utils/helpers.d.ts +14 -2
- package/dist/utils/helpers.d.ts.map +1 -1
- package/dist/utils/session-log.d.ts +1 -1
- package/dist/utils/session-log.d.ts.map +1 -1
- 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 +1 -1
|
@@ -169,7 +169,7 @@ function getLlmClient() {
|
|
|
169
169
|
}
|
|
170
170
|
|
|
171
171
|
// src/constants.ts
|
|
172
|
-
var VERSION = "9.
|
|
172
|
+
var VERSION = "9.1.0";
|
|
173
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.";
|
|
174
174
|
var PROFILES = {
|
|
175
175
|
light: {
|
|
@@ -325,6 +325,14 @@ var FIVE_MINUTES_MS = 5 * 60 * 1000;
|
|
|
325
325
|
var ONE_HOUR_MS = 60 * 60 * 1000;
|
|
326
326
|
var SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000;
|
|
327
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
|
+
};
|
|
328
336
|
var TUNING = {
|
|
329
337
|
EMA_PREV: 0.7,
|
|
330
338
|
EMA_SAMPLE: 0.3,
|
|
@@ -443,8 +451,14 @@ function calibrationKey(provider, model) {
|
|
|
443
451
|
}
|
|
444
452
|
|
|
445
453
|
// src/utils/type-guards.ts
|
|
454
|
+
function isRecord(value) {
|
|
455
|
+
return typeof value === "object" && value !== null;
|
|
456
|
+
}
|
|
446
457
|
function isTextBlock(c) {
|
|
447
|
-
return
|
|
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);
|
|
448
462
|
}
|
|
449
463
|
var KNOWN_METHODS = new Set(["eesv", "single-pass", "heuristic"]);
|
|
450
464
|
var KNOWN_PROFILES = new Set(["light", "balanced", "aggressive"]);
|
|
@@ -465,9 +479,37 @@ var GENERIC_BASENAMES = new Set([
|
|
|
465
479
|
"lib.rs",
|
|
466
480
|
"__init__.py"
|
|
467
481
|
]);
|
|
482
|
+
var MIN_BARE_BASENAME_LEN = 5;
|
|
468
483
|
function normalizePath(filePath) {
|
|
469
484
|
return filePath.replace(/\\/g, "/").replace(/^\.\//, "").toLowerCase();
|
|
470
485
|
}
|
|
486
|
+
function buildPathNeedles(filePath) {
|
|
487
|
+
const parts = normalizePath(filePath).split("/").filter(Boolean);
|
|
488
|
+
if (parts.length === 0)
|
|
489
|
+
return [];
|
|
490
|
+
const needles = [];
|
|
491
|
+
const basename = parts[parts.length - 1];
|
|
492
|
+
if (!GENERIC_BASENAMES.has(basename) && basename.length >= MIN_BARE_BASENAME_LEN) {
|
|
493
|
+
needles.push(basename);
|
|
494
|
+
}
|
|
495
|
+
for (let j = parts.length - 2;j >= 0; j--) {
|
|
496
|
+
needles.push(parts.slice(j).join("/"));
|
|
497
|
+
}
|
|
498
|
+
return needles;
|
|
499
|
+
}
|
|
500
|
+
function buildUniquePathNeedles(filePath, allPaths) {
|
|
501
|
+
const normalized = allPaths.map(normalizePath);
|
|
502
|
+
return buildPathNeedles(filePath).filter((needle) => {
|
|
503
|
+
let owners = 0;
|
|
504
|
+
for (const candidate of normalized) {
|
|
505
|
+
if (candidate === needle || candidate.endsWith("/" + needle))
|
|
506
|
+
owners++;
|
|
507
|
+
if (owners > 1)
|
|
508
|
+
return false;
|
|
509
|
+
}
|
|
510
|
+
return owners === 1;
|
|
511
|
+
});
|
|
512
|
+
}
|
|
471
513
|
function isKnownPathReference(ref, knownPaths) {
|
|
472
514
|
const normalizedRef = normalizePath(ref);
|
|
473
515
|
return knownPaths.some((path) => {
|
|
@@ -476,6 +518,74 @@ function isKnownPathReference(ref, knownPaths) {
|
|
|
476
518
|
});
|
|
477
519
|
}
|
|
478
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
|
+
|
|
479
589
|
// src/utils/file-ref-detect.ts
|
|
480
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;
|
|
481
591
|
var VERSION_RE = /^v?\d+(?:\.\d+)+(?:[-+][\w.-]+)?$/i;
|
|
@@ -546,6 +656,7 @@ function parseSummary(markdown) {
|
|
|
546
656
|
let currentHeading = "";
|
|
547
657
|
let currentKind = "unknown";
|
|
548
658
|
let bodyLines = [];
|
|
659
|
+
let fence = null;
|
|
549
660
|
let started = false;
|
|
550
661
|
const flush = () => {
|
|
551
662
|
if (!started)
|
|
@@ -559,16 +670,31 @@ function parseSummary(markdown) {
|
|
|
559
670
|
sections.push({ kind: currentKind, heading: currentHeading.trim(), body });
|
|
560
671
|
};
|
|
561
672
|
for (const line of lines) {
|
|
562
|
-
const
|
|
563
|
-
if (
|
|
564
|
-
const
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
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
|
+
}
|
|
572
698
|
}
|
|
573
699
|
}
|
|
574
700
|
if (started)
|
|
@@ -583,6 +709,24 @@ function findSection(summary, kind) {
|
|
|
583
709
|
}
|
|
584
710
|
|
|
585
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
|
+
}
|
|
586
730
|
function extractText(content) {
|
|
587
731
|
if (typeof content === "string")
|
|
588
732
|
return content;
|
|
@@ -596,6 +740,31 @@ function extractText(content) {
|
|
|
596
740
|
return "";
|
|
597
741
|
}).join("");
|
|
598
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
|
+
}
|
|
599
768
|
var CONSTRAINT_PATTERNS = [
|
|
600
769
|
{ re: /\b(?:must|need|require|has to|important)\b.*\b(?:be|use|have|include|support)\b/i, cat: "requirement", conf: TUNING.CONFIDENCE_HIGH },
|
|
601
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 },
|
|
@@ -725,30 +894,41 @@ function mergeFindings(target, findings) {
|
|
|
725
894
|
for (const finding of findings)
|
|
726
895
|
target.set(finding.kind, (target.get(finding.kind) ?? 0) + finding.count);
|
|
727
896
|
}
|
|
728
|
-
var SECRET_KEY_NAMES =
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
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
|
+
};
|
|
744
924
|
function normalizeObjectKey(key) {
|
|
745
925
|
return key.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
746
926
|
}
|
|
747
927
|
function isSecretBearingKey(key) {
|
|
748
928
|
const normalized = normalizeObjectKey(key);
|
|
749
|
-
if (SECRET_KEY_NAMES
|
|
929
|
+
if (SECRET_KEY_NAMES[normalized])
|
|
750
930
|
return true;
|
|
751
|
-
return /(?:^|_)(?:api_key|access_token|auth_token|password|passwd|secret_access_key|client_secret|private_key)(?:_|$)/.test(normalized);
|
|
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);
|
|
752
932
|
}
|
|
753
933
|
|
|
754
934
|
class SecretScrubber {
|
|
@@ -804,7 +984,8 @@ class SecretScrubber {
|
|
|
804
984
|
const output = {};
|
|
805
985
|
seen.set(value2, output);
|
|
806
986
|
for (const [key, item] of Object.entries(value2)) {
|
|
807
|
-
|
|
987
|
+
const carriesSecret = typeof item === "string" ? item.length > 0 : item != null;
|
|
988
|
+
if (this.secretsEnabled && isSecretBearingKey(key) && carriesSecret) {
|
|
808
989
|
output[key] = "[REDACTED:credential]";
|
|
809
990
|
recordCredential();
|
|
810
991
|
} else {
|
|
@@ -1069,8 +1250,8 @@ function readMetricsLog(limit = 100) {
|
|
|
1069
1250
|
const fd = fs2.openSync(logPath, "r");
|
|
1070
1251
|
try {
|
|
1071
1252
|
const buf = Buffer.alloc(wantBytes);
|
|
1072
|
-
fs2.readSync(fd, buf, 0, wantBytes, startPos);
|
|
1073
|
-
let text = buf.toString("utf8");
|
|
1253
|
+
const bytesRead = fs2.readSync(fd, buf, 0, wantBytes, startPos);
|
|
1254
|
+
let text = buf.subarray(0, bytesRead).toString("utf8");
|
|
1074
1255
|
if (startPos > 0) {
|
|
1075
1256
|
const nl = text.indexOf(`
|
|
1076
1257
|
`);
|
|
@@ -1117,29 +1298,56 @@ function classifyOutcomeClaim(claim) {
|
|
|
1117
1298
|
return "file";
|
|
1118
1299
|
return "generic";
|
|
1119
1300
|
}
|
|
1120
|
-
|
|
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) {
|
|
1121
1330
|
const shape = semanticShape(claim);
|
|
1122
1331
|
const category = classifyOutcomeClaim(claim);
|
|
1123
1332
|
if (category === "error" && extraction.errors.some((error) => error.resolved && hasSemanticEvidence(claim, error.message)))
|
|
1124
1333
|
return true;
|
|
1125
1334
|
if (category === "file" && extraction.modifiedFiles.some((file) => claim.toLowerCase().includes(file.path.toLowerCase())))
|
|
1126
1335
|
return true;
|
|
1127
|
-
for (const
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
if (!bounded.trim() || LIKELY_ERROR_RE.test(bounded))
|
|
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)
|
|
1132
1340
|
continue;
|
|
1133
|
-
if (hasSemanticEvidence(claim,
|
|
1341
|
+
if (hasSemanticEvidence(claim, tool.result))
|
|
1134
1342
|
return true;
|
|
1135
|
-
const lower =
|
|
1343
|
+
const lower = tool.result.toLowerCase();
|
|
1136
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))
|
|
1137
1345
|
return true;
|
|
1138
|
-
if (category === "build" && /\b(?:
|
|
1346
|
+
if (category === "build" && /\b(?:succeeded|successful|passed|exit(?:ed)?\s+(?:code\s+)?0)\b/.test(lower))
|
|
1139
1347
|
return true;
|
|
1140
|
-
if (category === "release" && /\b(?:
|
|
1348
|
+
if (category === "release" && /\b(?:succeeded|successful|completed|published|deployed|released)\b/.test(lower))
|
|
1141
1349
|
return true;
|
|
1142
|
-
if (category === "error" && shape.concepts.length > 0 && /\b(?:fixed|resolved|passed|succeeded|successful)\b/.test(lower) && hasSemanticEvidence(claim,
|
|
1350
|
+
if (category === "error" && shape.concepts.length > 0 && /\b(?:fixed|resolved|passed|succeeded|successful)\b/.test(lower) && hasSemanticEvidence(claim, tool.result))
|
|
1143
1351
|
return true;
|
|
1144
1352
|
}
|
|
1145
1353
|
return false;
|
|
@@ -1224,9 +1432,15 @@ function stemToken(token) {
|
|
|
1224
1432
|
function semanticTokens(text) {
|
|
1225
1433
|
return (text.normalize("NFKC").match(/[\p{L}\p{N}_-]+/gu) ?? []).map(stemToken).filter((token) => token.length > 2);
|
|
1226
1434
|
}
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
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;
|
|
1230
1444
|
}
|
|
1231
1445
|
function hasNearbyMarker(tokens, anchor, markers) {
|
|
1232
1446
|
return tokens.some((token, index) => token === anchor && tokens.slice(Math.max(0, index - 2), index + 3).some((near) => markers.has(near)));
|
|
@@ -1250,20 +1464,24 @@ function hasEffectiveTargetNegation(tokens, anchor) {
|
|
|
1250
1464
|
});
|
|
1251
1465
|
}
|
|
1252
1466
|
function semanticShape(source) {
|
|
1467
|
+
const cached = lruGet(semanticShapeCache, source);
|
|
1468
|
+
if (cached)
|
|
1469
|
+
return cached;
|
|
1253
1470
|
const sourceTokens = semanticTokens(source);
|
|
1254
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))));
|
|
1255
1472
|
const negative = sourceTokens.some((token) => NEGATION_MARKERS.has(token));
|
|
1256
1473
|
const conditional = sourceTokens.some((token) => CONDITION_MARKERS.has(token));
|
|
1257
1474
|
const anchor = concepts.find((concept) => hasNearbyMarker(sourceTokens, concept, NEGATION_MARKERS)) ?? concepts[0] ?? "";
|
|
1258
|
-
|
|
1475
|
+
const shape = { sourceTokens, concepts, anchor, negative, conditional };
|
|
1476
|
+
lruSet(semanticShapeCache, source, shape, 512);
|
|
1477
|
+
return shape;
|
|
1259
1478
|
}
|
|
1260
1479
|
function hasSemanticEvidence(source, target) {
|
|
1261
1480
|
const { sourceTokens, concepts, anchor, negative, conditional } = semanticShape(source);
|
|
1262
1481
|
if (!concepts.length)
|
|
1263
1482
|
return true;
|
|
1264
1483
|
const required = Math.min(concepts.length, Math.max(1, Math.ceil(concepts.length * 0.6)));
|
|
1265
|
-
return
|
|
1266
|
-
const tokens = semanticTokens(fragment);
|
|
1484
|
+
return semanticFragments(target).some((tokens) => {
|
|
1267
1485
|
const overlap = concepts.filter((concept) => tokens.includes(concept)).length;
|
|
1268
1486
|
if (overlap < required)
|
|
1269
1487
|
return false;
|
|
@@ -1285,8 +1503,7 @@ function hasSemanticContradiction(source, target) {
|
|
|
1285
1503
|
if (!anchor)
|
|
1286
1504
|
return false;
|
|
1287
1505
|
const required = Math.min(concepts.length, Math.max(1, Math.ceil(concepts.length * 0.6)));
|
|
1288
|
-
return
|
|
1289
|
-
const tokens = semanticTokens(fragment);
|
|
1506
|
+
return semanticFragments(target).some((tokens) => {
|
|
1290
1507
|
if (!tokens.includes(anchor))
|
|
1291
1508
|
return false;
|
|
1292
1509
|
const overlap = concepts.filter((concept) => tokens.includes(concept)).length;
|
|
@@ -1448,16 +1665,18 @@ function verifySummary(summary, extraction, continuity = null, evidence = {}) {
|
|
|
1448
1665
|
gaps.push({ kind: "inconsistency", detail: "blocked-none: Blocked says none despite unresolved errors" });
|
|
1449
1666
|
score -= 12;
|
|
1450
1667
|
}
|
|
1668
|
+
const doneRefs = new Set(extractFileRefs(doneSection).map(normalizePath));
|
|
1451
1669
|
for (const file of extraction.modifiedFiles) {
|
|
1452
|
-
const
|
|
1453
|
-
if (!
|
|
1670
|
+
const uniqueNeedles = buildUniquePathNeedles(file.path, modifiedPaths);
|
|
1671
|
+
if (!uniqueNeedles.some((needle) => doneRefs.has(normalizePath(needle))))
|
|
1454
1672
|
continue;
|
|
1455
1673
|
const unresolved = unresolvedEvidence.find((error) => {
|
|
1456
1674
|
const firstLine = error.message.split(/\r?\n/, 1)[0] ?? "";
|
|
1457
|
-
|
|
1675
|
+
const errorRefs = extractFileRefs(firstLine).map(normalizePath);
|
|
1676
|
+
return uniqueNeedles.some((needle) => errorRefs.includes(normalizePath(needle)));
|
|
1458
1677
|
});
|
|
1459
1678
|
if (unresolved) {
|
|
1460
|
-
gaps.push({ kind: "inconsistency", detail:
|
|
1679
|
+
gaps.push({ kind: "inconsistency", detail: file.path + " marked Done but has unresolved error" });
|
|
1461
1680
|
score -= 5;
|
|
1462
1681
|
}
|
|
1463
1682
|
}
|
|
@@ -1479,8 +1698,9 @@ function verifySummary(summary, extraction, continuity = null, evidence = {}) {
|
|
|
1479
1698
|
score -= 5;
|
|
1480
1699
|
}
|
|
1481
1700
|
if (evidence.sourceMessages) {
|
|
1701
|
+
const tools = successfulToolEvidence(evidence.sourceMessages);
|
|
1482
1702
|
for (const claim of outcomeClaims(summary)) {
|
|
1483
|
-
if (!successfulToolSupportsClaim(claim,
|
|
1703
|
+
if (!successfulToolSupportsClaim(claim, tools, extraction)) {
|
|
1484
1704
|
gaps.push({ kind: "unsupported-claim", claim });
|
|
1485
1705
|
score -= 20;
|
|
1486
1706
|
}
|