pi-smart-compact 9.0.0 → 9.2.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 +61 -36
- package/CHANGELOG.md +28 -0
- package/README.md +30 -5
- 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/settled-auto-trigger.d.ts +20 -0
- package/dist/app/settled-auto-trigger.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.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 +8 -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 +1216 -364
- 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 +3 -2
- package/dist/phases/verify.d.ts.map +1 -1
- package/dist/provider-eval.js +216 -33
- package/dist/provider-scenario-eval.js +279 -57
- package/dist/telemetry-report.js +216 -33
- package/dist/types.d.ts +12 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/ui/dashboard-format.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,8 @@ function getLlmClient() {
|
|
|
169
169
|
}
|
|
170
170
|
|
|
171
171
|
// src/constants.ts
|
|
172
|
-
var VERSION = "9.
|
|
172
|
+
var VERSION = "9.2.0";
|
|
173
|
+
var SETTLED_TRIGGER_COOLDOWN_MS = 10 * 60000;
|
|
173
174
|
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
175
|
var PROFILES = {
|
|
175
176
|
light: {
|
|
@@ -207,6 +208,7 @@ var DEFAULT_CONFIG = {
|
|
|
207
208
|
summaryThinkingLevel: "minimal",
|
|
208
209
|
segmentationThinkingLevel: "minimal",
|
|
209
210
|
autoTrigger: true,
|
|
211
|
+
autoTriggerStrategy: "native-hook",
|
|
210
212
|
autoTriggerTimeoutMs: 120000,
|
|
211
213
|
backupEnabled: true,
|
|
212
214
|
backupDir: "",
|
|
@@ -325,6 +327,14 @@ var FIVE_MINUTES_MS = 5 * 60 * 1000;
|
|
|
325
327
|
var ONE_HOUR_MS = 60 * 60 * 1000;
|
|
326
328
|
var SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000;
|
|
327
329
|
var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000;
|
|
330
|
+
var ID_PREFIX = {
|
|
331
|
+
PROJECT: "proj-",
|
|
332
|
+
COMPACT_SESSION: "sc-",
|
|
333
|
+
MULTI_TOOL_USE_SYNTHETIC: "mtu_",
|
|
334
|
+
OPEN_LOOP: "loop-",
|
|
335
|
+
DECISION: "decision-",
|
|
336
|
+
ERROR: "error-"
|
|
337
|
+
};
|
|
328
338
|
var TUNING = {
|
|
329
339
|
EMA_PREV: 0.7,
|
|
330
340
|
EMA_SAMPLE: 0.3,
|
|
@@ -443,8 +453,14 @@ function calibrationKey(provider, model) {
|
|
|
443
453
|
}
|
|
444
454
|
|
|
445
455
|
// src/utils/type-guards.ts
|
|
456
|
+
function isRecord(value) {
|
|
457
|
+
return typeof value === "object" && value !== null;
|
|
458
|
+
}
|
|
446
459
|
function isTextBlock(c) {
|
|
447
|
-
return
|
|
460
|
+
return isRecord(c) && c.type === "text" && typeof c.text === "string";
|
|
461
|
+
}
|
|
462
|
+
function isToolCallBlock(c) {
|
|
463
|
+
return isRecord(c) && c.type === "toolCall" && typeof c.name === "string" && isRecord(c.arguments);
|
|
448
464
|
}
|
|
449
465
|
var KNOWN_METHODS = new Set(["eesv", "single-pass", "heuristic"]);
|
|
450
466
|
var KNOWN_PROFILES = new Set(["light", "balanced", "aggressive"]);
|
|
@@ -465,9 +481,37 @@ var GENERIC_BASENAMES = new Set([
|
|
|
465
481
|
"lib.rs",
|
|
466
482
|
"__init__.py"
|
|
467
483
|
]);
|
|
484
|
+
var MIN_BARE_BASENAME_LEN = 5;
|
|
468
485
|
function normalizePath(filePath) {
|
|
469
486
|
return filePath.replace(/\\/g, "/").replace(/^\.\//, "").toLowerCase();
|
|
470
487
|
}
|
|
488
|
+
function buildPathNeedles(filePath) {
|
|
489
|
+
const parts = normalizePath(filePath).split("/").filter(Boolean);
|
|
490
|
+
if (parts.length === 0)
|
|
491
|
+
return [];
|
|
492
|
+
const needles = [];
|
|
493
|
+
const basename = parts[parts.length - 1];
|
|
494
|
+
if (!GENERIC_BASENAMES.has(basename) && basename.length >= MIN_BARE_BASENAME_LEN) {
|
|
495
|
+
needles.push(basename);
|
|
496
|
+
}
|
|
497
|
+
for (let j = parts.length - 2;j >= 0; j--) {
|
|
498
|
+
needles.push(parts.slice(j).join("/"));
|
|
499
|
+
}
|
|
500
|
+
return needles;
|
|
501
|
+
}
|
|
502
|
+
function buildUniquePathNeedles(filePath, allPaths) {
|
|
503
|
+
const normalized = allPaths.map(normalizePath);
|
|
504
|
+
return buildPathNeedles(filePath).filter((needle) => {
|
|
505
|
+
let owners = 0;
|
|
506
|
+
for (const candidate of normalized) {
|
|
507
|
+
if (candidate === needle || candidate.endsWith("/" + needle))
|
|
508
|
+
owners++;
|
|
509
|
+
if (owners > 1)
|
|
510
|
+
return false;
|
|
511
|
+
}
|
|
512
|
+
return owners === 1;
|
|
513
|
+
});
|
|
514
|
+
}
|
|
471
515
|
function isKnownPathReference(ref, knownPaths) {
|
|
472
516
|
const normalizedRef = normalizePath(ref);
|
|
473
517
|
return knownPaths.some((path) => {
|
|
@@ -476,6 +520,74 @@ function isKnownPathReference(ref, knownPaths) {
|
|
|
476
520
|
});
|
|
477
521
|
}
|
|
478
522
|
|
|
523
|
+
// src/domain/tool-semantics.ts
|
|
524
|
+
var PATH_KEYS = [
|
|
525
|
+
"path",
|
|
526
|
+
"file_path",
|
|
527
|
+
"filePath",
|
|
528
|
+
"filename",
|
|
529
|
+
"file",
|
|
530
|
+
"target_file",
|
|
531
|
+
"file_uri",
|
|
532
|
+
"absolute_path"
|
|
533
|
+
];
|
|
534
|
+
var PAYLOAD_KEYS = [
|
|
535
|
+
"content",
|
|
536
|
+
"newText",
|
|
537
|
+
"oldText",
|
|
538
|
+
"new_str",
|
|
539
|
+
"old_str",
|
|
540
|
+
"new_string",
|
|
541
|
+
"old_string",
|
|
542
|
+
"edits",
|
|
543
|
+
"patch",
|
|
544
|
+
"replacement"
|
|
545
|
+
];
|
|
546
|
+
var COMMAND_KEYS = ["command", "cmd", "script"];
|
|
547
|
+
function hasPresent(args, keys) {
|
|
548
|
+
return keys.some((k) => args[k] != null);
|
|
549
|
+
}
|
|
550
|
+
function extractToolPath(args) {
|
|
551
|
+
if (!args || typeof args !== "object")
|
|
552
|
+
return;
|
|
553
|
+
const a = args;
|
|
554
|
+
for (const k of PATH_KEYS) {
|
|
555
|
+
const v = a[k];
|
|
556
|
+
if (typeof v === "string" && v.length > 0)
|
|
557
|
+
return v;
|
|
558
|
+
}
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
function normalizeToolName(name) {
|
|
562
|
+
if (typeof name !== "string")
|
|
563
|
+
return "";
|
|
564
|
+
return name.replace(/^functions[.:/_-]+/i, "").replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
565
|
+
}
|
|
566
|
+
function nameHas(name, hints) {
|
|
567
|
+
const words = name.split("_");
|
|
568
|
+
return hints.some((hint) => words.includes(hint));
|
|
569
|
+
}
|
|
570
|
+
function classifyToolOperation(args, toolName) {
|
|
571
|
+
const a = args && typeof args === "object" ? args : {};
|
|
572
|
+
const name = normalizeToolName(toolName);
|
|
573
|
+
const hasPath = extractToolPath(a) !== undefined;
|
|
574
|
+
if (hasPath && hasPresent(a, PAYLOAD_KEYS))
|
|
575
|
+
return "mutate";
|
|
576
|
+
if (hasPresent(a, COMMAND_KEYS))
|
|
577
|
+
return "execute";
|
|
578
|
+
if (hasPath && hasPresent(a, ["text"]) && nameHas(name, ["write", "edit", "patch", "replace", "append", "create", "update", "insert"]))
|
|
579
|
+
return "mutate";
|
|
580
|
+
if (hasPath && nameHas(name, ["delete", "remove", "unlink"]))
|
|
581
|
+
return "delete";
|
|
582
|
+
if (hasPresent(a, ["pattern", "query", "glob"]) || nameHas(name, ["grep", "search", "find", "glob", "rg"]))
|
|
583
|
+
return "search";
|
|
584
|
+
if (nameHas(name, ["list", "ls", "tree"]))
|
|
585
|
+
return "list";
|
|
586
|
+
if (hasPath || nameHas(name, ["read"]))
|
|
587
|
+
return "read";
|
|
588
|
+
return "unknown";
|
|
589
|
+
}
|
|
590
|
+
|
|
479
591
|
// src/utils/file-ref-detect.ts
|
|
480
592
|
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
593
|
var VERSION_RE = /^v?\d+(?:\.\d+)+(?:[-+][\w.-]+)?$/i;
|
|
@@ -546,6 +658,7 @@ function parseSummary(markdown) {
|
|
|
546
658
|
let currentHeading = "";
|
|
547
659
|
let currentKind = "unknown";
|
|
548
660
|
let bodyLines = [];
|
|
661
|
+
let fence = null;
|
|
549
662
|
let started = false;
|
|
550
663
|
const flush = () => {
|
|
551
664
|
if (!started)
|
|
@@ -559,16 +672,31 @@ function parseSummary(markdown) {
|
|
|
559
672
|
sections.push({ kind: currentKind, heading: currentHeading.trim(), body });
|
|
560
673
|
};
|
|
561
674
|
for (const line of lines) {
|
|
562
|
-
const
|
|
563
|
-
if (
|
|
564
|
-
const
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
675
|
+
const fenceMatch = line.match(/^\s{0,3}(`{3,}|~{3,})(.*)$/);
|
|
676
|
+
if (fenceMatch) {
|
|
677
|
+
const marker = fenceMatch[1][0];
|
|
678
|
+
const markerLength = fenceMatch[1].length;
|
|
679
|
+
if (!fence) {
|
|
680
|
+
fence = { marker, length: markerLength };
|
|
681
|
+
} else if (marker === fence.marker && markerLength >= fence.length && !fenceMatch[2].trim()) {
|
|
682
|
+
fence = null;
|
|
683
|
+
}
|
|
684
|
+
if (started)
|
|
685
|
+
bodyLines.push(line);
|
|
686
|
+
continue;
|
|
687
|
+
}
|
|
688
|
+
if (!fence) {
|
|
689
|
+
const heading = line.match(HEADING_RE);
|
|
690
|
+
if (heading) {
|
|
691
|
+
const kind = classifyHeading(heading[2]);
|
|
692
|
+
if (heading[1].length <= 2 || kind !== "unknown") {
|
|
693
|
+
flush();
|
|
694
|
+
currentHeading = "## " + heading[2].trim();
|
|
695
|
+
currentKind = kind;
|
|
696
|
+
bodyLines = [];
|
|
697
|
+
started = true;
|
|
698
|
+
continue;
|
|
699
|
+
}
|
|
572
700
|
}
|
|
573
701
|
}
|
|
574
702
|
if (started)
|
|
@@ -583,6 +711,24 @@ function findSection(summary, kind) {
|
|
|
583
711
|
}
|
|
584
712
|
|
|
585
713
|
// src/utils/extraction.ts
|
|
714
|
+
function nestedToolCallId(wrapperId, messageIndex, toolIndex, nestedId) {
|
|
715
|
+
return typeof nestedId === "string" ? nestedId : wrapperId ? wrapperId + "_" + toolIndex : ID_PREFIX.MULTI_TOOL_USE_SYNTHETIC + messageIndex + "_" + toolIndex;
|
|
716
|
+
}
|
|
717
|
+
function flattenToolCallBlock(b) {
|
|
718
|
+
if (!isToolCallBlock(b))
|
|
719
|
+
return [];
|
|
720
|
+
if (b.name === "multi_tool_use.parallel" && Array.isArray(b.arguments?.tool_uses)) {
|
|
721
|
+
return b.arguments.tool_uses.map((u) => {
|
|
722
|
+
const recipient = u?.recipient_name ?? "";
|
|
723
|
+
return {
|
|
724
|
+
name: recipient.replace(/^functions\./, ""),
|
|
725
|
+
id: u?.id ?? undefined,
|
|
726
|
+
arguments: u?.parameters ?? {}
|
|
727
|
+
};
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
return [{ name: b.name, id: b.id, arguments: b.arguments }];
|
|
731
|
+
}
|
|
586
732
|
function extractText(content) {
|
|
587
733
|
if (typeof content === "string")
|
|
588
734
|
return content;
|
|
@@ -596,6 +742,31 @@ function extractText(content) {
|
|
|
596
742
|
return "";
|
|
597
743
|
}).join("");
|
|
598
744
|
}
|
|
745
|
+
function buildToolCallIndex(msgs) {
|
|
746
|
+
const idx = new Map;
|
|
747
|
+
for (let i = 0;i < msgs.length; i++) {
|
|
748
|
+
const m = msgs[i];
|
|
749
|
+
if (m.role !== "assistant")
|
|
750
|
+
continue;
|
|
751
|
+
const blocks = Array.isArray(m.content) ? m.content : [];
|
|
752
|
+
for (const b of blocks) {
|
|
753
|
+
if (!isToolCallBlock(b))
|
|
754
|
+
continue;
|
|
755
|
+
if (b.id) {
|
|
756
|
+
idx.set(b.id, { name: b.name, arguments: b.arguments, msgIndex: i });
|
|
757
|
+
}
|
|
758
|
+
if (b.name === "multi_tool_use.parallel" && Array.isArray(b.arguments?.tool_uses)) {
|
|
759
|
+
const nested = flattenToolCallBlock(b);
|
|
760
|
+
for (let t = 0;t < nested.length; t++) {
|
|
761
|
+
const tool = nested[t];
|
|
762
|
+
const id = nestedToolCallId(b.id, i, t, tool.id);
|
|
763
|
+
idx.set(id, { name: tool.name, arguments: tool.arguments, msgIndex: i });
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
return idx;
|
|
769
|
+
}
|
|
599
770
|
var CONSTRAINT_PATTERNS = [
|
|
600
771
|
{ re: /\b(?:must|need|require|has to|important)\b.*\b(?:be|use|have|include|support)\b/i, cat: "requirement", conf: TUNING.CONFIDENCE_HIGH },
|
|
601
772
|
{ 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 +896,41 @@ function mergeFindings(target, findings) {
|
|
|
725
896
|
for (const finding of findings)
|
|
726
897
|
target.set(finding.kind, (target.get(finding.kind) ?? 0) + finding.count);
|
|
727
898
|
}
|
|
728
|
-
var SECRET_KEY_NAMES =
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
899
|
+
var SECRET_KEY_NAMES = {
|
|
900
|
+
api_key: true,
|
|
901
|
+
apikey: true,
|
|
902
|
+
access_token: true,
|
|
903
|
+
auth_token: true,
|
|
904
|
+
authorization: true,
|
|
905
|
+
password: true,
|
|
906
|
+
passwd: true,
|
|
907
|
+
secret: true,
|
|
908
|
+
secret_key: true,
|
|
909
|
+
secret_access_key: true,
|
|
910
|
+
client_secret: true,
|
|
911
|
+
private_key: true,
|
|
912
|
+
database_url: true,
|
|
913
|
+
connection_string: true,
|
|
914
|
+
token: true,
|
|
915
|
+
refresh_token: true,
|
|
916
|
+
session_token: true,
|
|
917
|
+
credential: true,
|
|
918
|
+
credentials: true,
|
|
919
|
+
cookie: true,
|
|
920
|
+
set_cookie: true,
|
|
921
|
+
otp: true,
|
|
922
|
+
one_time_password: true,
|
|
923
|
+
pin: true,
|
|
924
|
+
passcode: true
|
|
925
|
+
};
|
|
744
926
|
function normalizeObjectKey(key) {
|
|
745
927
|
return key.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
746
928
|
}
|
|
747
929
|
function isSecretBearingKey(key) {
|
|
748
930
|
const normalized = normalizeObjectKey(key);
|
|
749
|
-
if (SECRET_KEY_NAMES
|
|
931
|
+
if (SECRET_KEY_NAMES[normalized])
|
|
750
932
|
return true;
|
|
751
|
-
return /(?:^|_)(?:api_key|access_token|auth_token|password|passwd|secret_access_key|client_secret|private_key)(?:_|$)/.test(normalized);
|
|
933
|
+
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
934
|
}
|
|
753
935
|
|
|
754
936
|
class SecretScrubber {
|
|
@@ -804,7 +986,8 @@ class SecretScrubber {
|
|
|
804
986
|
const output = {};
|
|
805
987
|
seen.set(value2, output);
|
|
806
988
|
for (const [key, item] of Object.entries(value2)) {
|
|
807
|
-
|
|
989
|
+
const carriesSecret = typeof item === "string" ? item.length > 0 : item != null;
|
|
990
|
+
if (this.secretsEnabled && isSecretBearingKey(key) && carriesSecret) {
|
|
808
991
|
output[key] = "[REDACTED:credential]";
|
|
809
992
|
recordCredential();
|
|
810
993
|
} else {
|
|
@@ -1069,8 +1252,8 @@ function readMetricsLog(limit = 100) {
|
|
|
1069
1252
|
const fd = fs2.openSync(logPath, "r");
|
|
1070
1253
|
try {
|
|
1071
1254
|
const buf = Buffer.alloc(wantBytes);
|
|
1072
|
-
fs2.readSync(fd, buf, 0, wantBytes, startPos);
|
|
1073
|
-
let text = buf.toString("utf8");
|
|
1255
|
+
const bytesRead = fs2.readSync(fd, buf, 0, wantBytes, startPos);
|
|
1256
|
+
let text = buf.subarray(0, bytesRead).toString("utf8");
|
|
1074
1257
|
if (startPos > 0) {
|
|
1075
1258
|
const nl = text.indexOf(`
|
|
1076
1259
|
`);
|
|
@@ -1117,29 +1300,56 @@ function classifyOutcomeClaim(claim) {
|
|
|
1117
1300
|
return "file";
|
|
1118
1301
|
return "generic";
|
|
1119
1302
|
}
|
|
1120
|
-
|
|
1303
|
+
var successfulToolEvidenceCache = new WeakMap;
|
|
1304
|
+
function successfulToolEvidence(messages) {
|
|
1305
|
+
const cached = successfulToolEvidenceCache.get(messages);
|
|
1306
|
+
if (cached)
|
|
1307
|
+
return cached;
|
|
1308
|
+
const toolCalls = buildToolCallIndex(messages);
|
|
1309
|
+
const evidence = [];
|
|
1310
|
+
for (const message of messages) {
|
|
1311
|
+
if (message.role !== "toolResult" || message.isError)
|
|
1312
|
+
continue;
|
|
1313
|
+
const call = toolCalls.get(message.toolCallId ?? "");
|
|
1314
|
+
if (!call)
|
|
1315
|
+
continue;
|
|
1316
|
+
const result = extractText(message.content).slice(0, 8000);
|
|
1317
|
+
if (!result.trim() || LIKELY_ERROR_RE.test(result))
|
|
1318
|
+
continue;
|
|
1319
|
+
const command = [call.arguments.command, call.arguments.cmd, call.arguments.script].find((value) => typeof value === "string") ?? "";
|
|
1320
|
+
evidence.push({
|
|
1321
|
+
name: normalizeToolName(call.name),
|
|
1322
|
+
operation: classifyToolOperation(call.arguments, call.name),
|
|
1323
|
+
command,
|
|
1324
|
+
path: extractToolPath(call.arguments),
|
|
1325
|
+
result
|
|
1326
|
+
});
|
|
1327
|
+
}
|
|
1328
|
+
successfulToolEvidenceCache.set(messages, evidence);
|
|
1329
|
+
return evidence;
|
|
1330
|
+
}
|
|
1331
|
+
function successfulToolSupportsClaim(claim, tools, extraction) {
|
|
1121
1332
|
const shape = semanticShape(claim);
|
|
1122
1333
|
const category = classifyOutcomeClaim(claim);
|
|
1123
1334
|
if (category === "error" && extraction.errors.some((error) => error.resolved && hasSemanticEvidence(claim, error.message)))
|
|
1124
1335
|
return true;
|
|
1125
1336
|
if (category === "file" && extraction.modifiedFiles.some((file) => claim.toLowerCase().includes(file.path.toLowerCase())))
|
|
1126
1337
|
return true;
|
|
1127
|
-
for (const
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
if (!bounded.trim() || LIKELY_ERROR_RE.test(bounded))
|
|
1338
|
+
for (const tool of tools) {
|
|
1339
|
+
const operationText = tool.name + " " + tool.command;
|
|
1340
|
+
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";
|
|
1341
|
+
if (!operationSupports)
|
|
1132
1342
|
continue;
|
|
1133
|
-
if (hasSemanticEvidence(claim,
|
|
1343
|
+
if (hasSemanticEvidence(claim, tool.result))
|
|
1134
1344
|
return true;
|
|
1135
|
-
const lower =
|
|
1345
|
+
const lower = tool.result.toLowerCase();
|
|
1136
1346
|
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
1347
|
return true;
|
|
1138
|
-
if (category === "build" && /\b(?:
|
|
1348
|
+
if (category === "build" && /\b(?:succeeded|successful|passed|exit(?:ed)?\s+(?:code\s+)?0)\b/.test(lower))
|
|
1139
1349
|
return true;
|
|
1140
|
-
if (category === "release" && /\b(?:
|
|
1350
|
+
if (category === "release" && /\b(?:succeeded|successful|completed|published|deployed|released)\b/.test(lower))
|
|
1141
1351
|
return true;
|
|
1142
|
-
if (category === "error" && shape.concepts.length > 0 && /\b(?:fixed|resolved|passed|succeeded|successful)\b/.test(lower) && hasSemanticEvidence(claim,
|
|
1352
|
+
if (category === "error" && shape.concepts.length > 0 && /\b(?:fixed|resolved|passed|succeeded|successful)\b/.test(lower) && hasSemanticEvidence(claim, tool.result))
|
|
1143
1353
|
return true;
|
|
1144
1354
|
}
|
|
1145
1355
|
return false;
|
|
@@ -1224,9 +1434,15 @@ function stemToken(token) {
|
|
|
1224
1434
|
function semanticTokens(text) {
|
|
1225
1435
|
return (text.normalize("NFKC").match(/[\p{L}\p{N}_-]+/gu) ?? []).map(stemToken).filter((token) => token.length > 2);
|
|
1226
1436
|
}
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1437
|
+
var semanticShapeCache = new Map;
|
|
1438
|
+
var semanticFragmentCache = new Map;
|
|
1439
|
+
function semanticFragments(text) {
|
|
1440
|
+
const cached = lruGet(semanticFragmentCache, text);
|
|
1441
|
+
if (cached)
|
|
1442
|
+
return cached;
|
|
1443
|
+
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);
|
|
1444
|
+
lruSet(semanticFragmentCache, text, fragments, 256);
|
|
1445
|
+
return fragments;
|
|
1230
1446
|
}
|
|
1231
1447
|
function hasNearbyMarker(tokens, anchor, markers) {
|
|
1232
1448
|
return tokens.some((token, index) => token === anchor && tokens.slice(Math.max(0, index - 2), index + 3).some((near) => markers.has(near)));
|
|
@@ -1250,20 +1466,24 @@ function hasEffectiveTargetNegation(tokens, anchor) {
|
|
|
1250
1466
|
});
|
|
1251
1467
|
}
|
|
1252
1468
|
function semanticShape(source) {
|
|
1469
|
+
const cached = lruGet(semanticShapeCache, source);
|
|
1470
|
+
if (cached)
|
|
1471
|
+
return cached;
|
|
1253
1472
|
const sourceTokens = semanticTokens(source);
|
|
1254
1473
|
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
1474
|
const negative = sourceTokens.some((token) => NEGATION_MARKERS.has(token));
|
|
1256
1475
|
const conditional = sourceTokens.some((token) => CONDITION_MARKERS.has(token));
|
|
1257
1476
|
const anchor = concepts.find((concept) => hasNearbyMarker(sourceTokens, concept, NEGATION_MARKERS)) ?? concepts[0] ?? "";
|
|
1258
|
-
|
|
1477
|
+
const shape = { sourceTokens, concepts, anchor, negative, conditional };
|
|
1478
|
+
lruSet(semanticShapeCache, source, shape, 512);
|
|
1479
|
+
return shape;
|
|
1259
1480
|
}
|
|
1260
1481
|
function hasSemanticEvidence(source, target) {
|
|
1261
1482
|
const { sourceTokens, concepts, anchor, negative, conditional } = semanticShape(source);
|
|
1262
1483
|
if (!concepts.length)
|
|
1263
1484
|
return true;
|
|
1264
1485
|
const required = Math.min(concepts.length, Math.max(1, Math.ceil(concepts.length * 0.6)));
|
|
1265
|
-
return
|
|
1266
|
-
const tokens = semanticTokens(fragment);
|
|
1486
|
+
return semanticFragments(target).some((tokens) => {
|
|
1267
1487
|
const overlap = concepts.filter((concept) => tokens.includes(concept)).length;
|
|
1268
1488
|
if (overlap < required)
|
|
1269
1489
|
return false;
|
|
@@ -1285,8 +1505,7 @@ function hasSemanticContradiction(source, target) {
|
|
|
1285
1505
|
if (!anchor)
|
|
1286
1506
|
return false;
|
|
1287
1507
|
const required = Math.min(concepts.length, Math.max(1, Math.ceil(concepts.length * 0.6)));
|
|
1288
|
-
return
|
|
1289
|
-
const tokens = semanticTokens(fragment);
|
|
1508
|
+
return semanticFragments(target).some((tokens) => {
|
|
1290
1509
|
if (!tokens.includes(anchor))
|
|
1291
1510
|
return false;
|
|
1292
1511
|
const overlap = concepts.filter((concept) => tokens.includes(concept)).length;
|
|
@@ -1448,16 +1667,18 @@ function verifySummary(summary, extraction, continuity = null, evidence = {}) {
|
|
|
1448
1667
|
gaps.push({ kind: "inconsistency", detail: "blocked-none: Blocked says none despite unresolved errors" });
|
|
1449
1668
|
score -= 12;
|
|
1450
1669
|
}
|
|
1670
|
+
const doneRefs = new Set(extractFileRefs(doneSection).map(normalizePath));
|
|
1451
1671
|
for (const file of extraction.modifiedFiles) {
|
|
1452
|
-
const
|
|
1453
|
-
if (!
|
|
1672
|
+
const uniqueNeedles = buildUniquePathNeedles(file.path, modifiedPaths);
|
|
1673
|
+
if (!uniqueNeedles.some((needle) => doneRefs.has(normalizePath(needle))))
|
|
1454
1674
|
continue;
|
|
1455
1675
|
const unresolved = unresolvedEvidence.find((error) => {
|
|
1456
1676
|
const firstLine = error.message.split(/\r?\n/, 1)[0] ?? "";
|
|
1457
|
-
|
|
1677
|
+
const errorRefs = extractFileRefs(firstLine).map(normalizePath);
|
|
1678
|
+
return uniqueNeedles.some((needle) => errorRefs.includes(normalizePath(needle)));
|
|
1458
1679
|
});
|
|
1459
1680
|
if (unresolved) {
|
|
1460
|
-
gaps.push({ kind: "inconsistency", detail:
|
|
1681
|
+
gaps.push({ kind: "inconsistency", detail: file.path + " marked Done but has unresolved error" });
|
|
1461
1682
|
score -= 5;
|
|
1462
1683
|
}
|
|
1463
1684
|
}
|
|
@@ -1479,8 +1700,9 @@ function verifySummary(summary, extraction, continuity = null, evidence = {}) {
|
|
|
1479
1700
|
score -= 5;
|
|
1480
1701
|
}
|
|
1481
1702
|
if (evidence.sourceMessages) {
|
|
1703
|
+
const tools = successfulToolEvidence(evidence.sourceMessages);
|
|
1482
1704
|
for (const claim of outcomeClaims(summary)) {
|
|
1483
|
-
if (!successfulToolSupportsClaim(claim,
|
|
1705
|
+
if (!successfulToolSupportsClaim(claim, tools, extraction)) {
|
|
1484
1706
|
gaps.push({ kind: "unsupported-claim", claim });
|
|
1485
1707
|
score -= 20;
|
|
1486
1708
|
}
|