pi-smart-compact 9.3.1 → 9.4.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 +47 -10
- package/CHANGELOG.md +12 -0
- package/README.md +18 -2
- package/dist/app/model-routing.d.ts +12 -0
- package/dist/app/model-routing.d.ts.map +1 -0
- package/dist/app/preflight.d.ts.map +1 -1
- package/dist/app/register-context-tools.d.ts +5 -0
- package/dist/app/register-context-tools.d.ts.map +1 -0
- package/dist/app/register-smart-compact-command.d.ts +13 -0
- package/dist/app/register-smart-compact-command.d.ts.map +1 -0
- package/dist/app/register-smart-compact-tool.d.ts +13 -0
- package/dist/app/register-smart-compact-tool.d.ts.map +1 -0
- package/dist/app/smart-compact-input.d.ts +1 -1
- package/dist/app/smart-compact-input.d.ts.map +1 -1
- package/dist/app/smart-compact-policy.d.ts +31 -0
- package/dist/app/smart-compact-policy.d.ts.map +1 -0
- package/dist/app/steps/extract.d.ts.map +1 -1
- package/dist/app/steps/metrics.d.ts.map +1 -1
- package/dist/app/steps/persist.d.ts.map +1 -1
- package/dist/app/steps/prepare.d.ts.map +1 -1
- package/dist/app/steps/tier.d.ts.map +1 -1
- package/dist/app/steps/window.d.ts +0 -1
- package/dist/app/steps/window.d.ts.map +1 -1
- package/dist/constants.d.ts +2 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/index.d.ts +2 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6353 -5565
- package/dist/infra/context-graph.d.ts +7 -3
- package/dist/infra/context-graph.d.ts.map +1 -1
- package/dist/infra/fs.d.ts +0 -1
- package/dist/infra/fs.d.ts.map +1 -1
- package/dist/infra/llm-client.d.ts +2 -1
- package/dist/infra/llm-client.d.ts.map +1 -1
- package/dist/infra/paths.d.ts +3 -14
- package/dist/infra/paths.d.ts.map +1 -1
- package/dist/phases/explore.d.ts +0 -8
- 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 +38 -34
- package/dist/provider-scenario-eval.js +330 -215
- package/dist/telemetry-report.js +11 -7
- package/dist/types.d.ts +4 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/ui/backup-overlays.d.ts +9 -0
- package/dist/ui/backup-overlays.d.ts.map +1 -0
- package/dist/ui/metrics-dashboard-overlay.d.ts +12 -0
- package/dist/ui/metrics-dashboard-overlay.d.ts.map +1 -0
- package/dist/ui/open-loops-overlay.d.ts +4 -0
- package/dist/ui/open-loops-overlay.d.ts.map +1 -0
- package/dist/ui/overlays.d.ts +3 -28
- package/dist/ui/overlays.d.ts.map +1 -1
- package/dist/ui/settings-overlay.d.ts +5 -0
- package/dist/ui/settings-overlay.d.ts.map +1 -0
- package/dist/utils/backups.d.ts.map +1 -1
- package/dist/utils/cache.d.ts +2 -18
- package/dist/utils/cache.d.ts.map +1 -1
- package/dist/utils/config.d.ts +7 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/damage.d.ts +4 -0
- package/dist/utils/damage.d.ts.map +1 -1
- package/dist/utils/extraction.d.ts +0 -2
- package/dist/utils/extraction.d.ts.map +1 -1
- package/dist/utils/fingerprint.d.ts.map +1 -1
- package/dist/utils/helpers.d.ts +3 -13
- package/dist/utils/helpers.d.ts.map +1 -1
- package/dist/utils/logger.d.ts +0 -1
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/state.d.ts +1 -2
- package/dist/utils/state.d.ts.map +1 -1
- package/package.json +7 -4
|
@@ -5,7 +5,7 @@ var __require = import.meta.require;
|
|
|
5
5
|
import { ModelRegistry, ModelRuntime } from "@earendil-works/pi-coding-agent";
|
|
6
6
|
|
|
7
7
|
// src/constants.ts
|
|
8
|
-
var VERSION = "9.
|
|
8
|
+
var VERSION = "9.4.0";
|
|
9
9
|
var SETTLED_TRIGGER_COOLDOWN_MS = 10 * 60000;
|
|
10
10
|
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.";
|
|
11
11
|
var PROFILES = {
|
|
@@ -43,6 +43,7 @@ var DEFAULT_CONFIG = {
|
|
|
43
43
|
verificationModel: null,
|
|
44
44
|
summaryThinkingLevel: "minimal",
|
|
45
45
|
segmentationThinkingLevel: "minimal",
|
|
46
|
+
agentToolAccess: "inherit",
|
|
46
47
|
autoTrigger: true,
|
|
47
48
|
autoTriggerStrategy: "native-hook",
|
|
48
49
|
autoTriggerTimeoutMs: 120000,
|
|
@@ -548,12 +549,15 @@ function assertSuccessful(message) {
|
|
|
548
549
|
}
|
|
549
550
|
return message;
|
|
550
551
|
}
|
|
551
|
-
|
|
552
|
+
function resolveProviderWatchdogMs(provider, maxTokens, configuredMs = 0) {
|
|
553
|
+
const multiplier = provider && configuredMs <= 0 ? getProviderCaps(provider).timeoutMultiplier : 1;
|
|
554
|
+
return Math.round(resolveCodexWatchdogMs(maxTokens, configuredMs) * multiplier);
|
|
555
|
+
}
|
|
556
|
+
async function withProviderDeadline(opts, invoke, provider) {
|
|
552
557
|
if (opts.signal?.aborted)
|
|
553
558
|
throw new Error("LLM request aborted before dispatch");
|
|
554
559
|
const controller = new AbortController;
|
|
555
|
-
const
|
|
556
|
-
const watchdogMs = Math.round(resolveCodexWatchdogMs(opts.maxTokens, opts.codexWatchdogMs) * multiplier);
|
|
560
|
+
const watchdogMs = resolveProviderWatchdogMs(provider, opts.maxTokens, opts.codexWatchdogMs);
|
|
557
561
|
const abort = Promise.withResolvers();
|
|
558
562
|
const abortFromCaller = () => {
|
|
559
563
|
controller.abort(opts.signal?.reason);
|
|
@@ -626,7 +630,7 @@ var rawLlmClient = {
|
|
|
626
630
|
return completeChatGptCodex(model, body, bounded);
|
|
627
631
|
const response = bounded.reasoning === undefined ? await (await resolveComplete())(model, body, bounded) : await (await resolveCompleteSimple())(model, body, bounded);
|
|
628
632
|
return assertSuccessful(response);
|
|
629
|
-
}, model.
|
|
633
|
+
}, model.provider);
|
|
630
634
|
}
|
|
631
635
|
};
|
|
632
636
|
var defaultLlmClient = rawLlmClient;
|
|
@@ -718,35 +722,6 @@ function isKnownPathReference(ref, knownPaths) {
|
|
|
718
722
|
});
|
|
719
723
|
}
|
|
720
724
|
|
|
721
|
-
// src/utils/logger.ts
|
|
722
|
-
var DEBUG = process.env.DEBUG?.includes("smart-compact") ?? false;
|
|
723
|
-
function warn(msg, err) {
|
|
724
|
-
const detail = err instanceof Error ? err.message : err ?? "";
|
|
725
|
-
console.error(LOG_PREFIX + " " + msg + (detail ? ": " + detail : ""));
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
// src/infra/paths.ts
|
|
729
|
-
import path from "path";
|
|
730
|
-
import os from "os";
|
|
731
|
-
function home() {
|
|
732
|
-
return process.env.HOME ?? os.homedir();
|
|
733
|
-
}
|
|
734
|
-
function piAgentDir() {
|
|
735
|
-
return path.join(home(), ".pi", "agent");
|
|
736
|
-
}
|
|
737
|
-
function cacheDir() {
|
|
738
|
-
return path.join(piAgentDir(), ".cache");
|
|
739
|
-
}
|
|
740
|
-
function smartCompactCacheDir() {
|
|
741
|
-
return path.join(cacheDir(), "smart-compact");
|
|
742
|
-
}
|
|
743
|
-
function metricsLogFile() {
|
|
744
|
-
return path.join(cacheDir(), "compact-metrics.jsonl");
|
|
745
|
-
}
|
|
746
|
-
function damageReportsFile() {
|
|
747
|
-
return path.join(smartCompactCacheDir(), "damage-reports.jsonl");
|
|
748
|
-
}
|
|
749
|
-
|
|
750
725
|
// src/domain/tool-semantics.ts
|
|
751
726
|
var PATH_KEYS = [
|
|
752
727
|
"path",
|
|
@@ -815,6 +790,35 @@ function classifyToolOperation(args, toolName) {
|
|
|
815
790
|
return "unknown";
|
|
816
791
|
}
|
|
817
792
|
|
|
793
|
+
// src/utils/logger.ts
|
|
794
|
+
var DEBUG = process.env.DEBUG?.includes("smart-compact") ?? false;
|
|
795
|
+
function warn(msg, err) {
|
|
796
|
+
const detail = err instanceof Error ? err.message : err ?? "";
|
|
797
|
+
console.error(LOG_PREFIX + " " + msg + (detail ? ": " + detail : ""));
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
// src/infra/paths.ts
|
|
801
|
+
import path from "path";
|
|
802
|
+
import os from "os";
|
|
803
|
+
function home() {
|
|
804
|
+
const configured = process.env.HOME?.trim() || process.env.USERPROFILE?.trim();
|
|
805
|
+
return configured || os.homedir();
|
|
806
|
+
}
|
|
807
|
+
function piAgentDir() {
|
|
808
|
+
return path.join(home(), ".pi", "agent");
|
|
809
|
+
}
|
|
810
|
+
function cacheDir() {
|
|
811
|
+
return path.join(piAgentDir(), ".cache");
|
|
812
|
+
}
|
|
813
|
+
function smartCompactCacheDir() {
|
|
814
|
+
return path.join(cacheDir(), "smart-compact");
|
|
815
|
+
}
|
|
816
|
+
function metricsLogFile() {
|
|
817
|
+
return path.join(cacheDir(), "compact-metrics.jsonl");
|
|
818
|
+
}
|
|
819
|
+
function damageReportsFile() {
|
|
820
|
+
return path.join(smartCompactCacheDir(), "damage-reports.jsonl");
|
|
821
|
+
}
|
|
818
822
|
// src/utils/file-ref-detect.ts
|
|
819
823
|
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;
|
|
820
824
|
var VERSION_RE = /^v?\d+(?:\.\d+)+(?:[-+][\w.-]+)?$/i;
|
|
@@ -2012,256 +2016,276 @@ function hasSemanticContradiction(source, target) {
|
|
|
2012
2016
|
return conditional && !negative && !tokens.some((token) => CONDITION_MARKERS.has(token));
|
|
2013
2017
|
});
|
|
2014
2018
|
}
|
|
2015
|
-
function
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
const
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
...extraction.errors.filter((error) => !error.resolved).map((error) => ({ message: error.message })),
|
|
2019
|
+
function addGap(accumulator, gap, penalty) {
|
|
2020
|
+
accumulator.gaps.push(gap);
|
|
2021
|
+
accumulator.score -= penalty;
|
|
2022
|
+
}
|
|
2023
|
+
function uniqueByText(items, text) {
|
|
2024
|
+
const seen = new Set;
|
|
2025
|
+
return items.filter((item) => {
|
|
2026
|
+
const key = text(item).toLowerCase().replace(/\s+/g, " ").trim();
|
|
2027
|
+
if (!key || seen.has(key))
|
|
2028
|
+
return false;
|
|
2029
|
+
seen.add(key);
|
|
2030
|
+
return true;
|
|
2031
|
+
});
|
|
2032
|
+
}
|
|
2033
|
+
function collectVerificationEvidence(extraction, continuity, evidence) {
|
|
2034
|
+
const unresolved = uniqueByText([
|
|
2035
|
+
...extraction.errors.flatMap((error) => !error.resolved ? [{ message: error.message }] : []),
|
|
2033
2036
|
...(continuity?.unresolvedErrors ?? []).map((error) => ({
|
|
2034
2037
|
message: error.message
|
|
2035
2038
|
}))
|
|
2036
2039
|
], (item) => item.message);
|
|
2037
|
-
const
|
|
2038
|
-
...extraction.errors.
|
|
2040
|
+
const resolved = uniqueByText([
|
|
2041
|
+
...extraction.errors.flatMap((error) => error.resolved ? [{ message: error.message }] : []),
|
|
2039
2042
|
...(continuity?.resolvedErrors ?? []).map((error) => ({
|
|
2040
2043
|
message: error.message
|
|
2041
2044
|
}))
|
|
2042
2045
|
], (item) => item.message).slice(-5);
|
|
2043
|
-
const steeringConstraints = [
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2046
|
+
const steeringConstraints = [];
|
|
2047
|
+
if (evidence.steering?.focus?.trim()) {
|
|
2048
|
+
steeringConstraints.push({
|
|
2049
|
+
text: "Preserve detail about: " + evidence.steering.focus
|
|
2050
|
+
});
|
|
2051
|
+
}
|
|
2052
|
+
if (evidence.steering?.note?.trim()) {
|
|
2053
|
+
steeringConstraints.push({ text: evidence.steering.note });
|
|
2054
|
+
}
|
|
2055
|
+
const constraints = uniqueByText([
|
|
2056
|
+
...extraction.constraints.flatMap((item) => item.confidence >= 0.8 ? [{ text: item.text }] : []),
|
|
2057
|
+
...(continuity?.constraints ?? []).flatMap((item) => item.confidence >= 0.8 ? [{ text: item.text }] : []),
|
|
2050
2058
|
...steeringConstraints
|
|
2051
2059
|
], (item) => item.text).filter((item) => !isDiagnosticConstraintText(item.text));
|
|
2052
|
-
const
|
|
2053
|
-
...extraction.decisions.
|
|
2054
|
-
...(continuity?.decisions ?? []).
|
|
2060
|
+
const decisions = uniqueByText([
|
|
2061
|
+
...extraction.decisions.flatMap((item) => item.type === "explicit" ? [{ summary: item.summary }] : []),
|
|
2062
|
+
...(continuity?.decisions ?? []).flatMap((item) => item.type === "explicit" ? [{ summary: item.summary }] : [])
|
|
2055
2063
|
], (item) => item.summary);
|
|
2056
|
-
|
|
2057
|
-
|
|
2064
|
+
return {
|
|
2065
|
+
unresolved,
|
|
2066
|
+
resolved,
|
|
2067
|
+
constraints,
|
|
2068
|
+
decisions,
|
|
2069
|
+
goal: extraction.mainGoal ?? continuity?.goal ?? null
|
|
2070
|
+
};
|
|
2071
|
+
}
|
|
2072
|
+
function verifyRequiredSections(parsed, accumulator) {
|
|
2073
|
+
const required = [
|
|
2058
2074
|
{ kind: "goal", penalty: 5 },
|
|
2059
2075
|
{ kind: "progress", penalty: 5 },
|
|
2060
2076
|
{ kind: "critical-context", penalty: 3 }
|
|
2061
2077
|
];
|
|
2062
|
-
for (const
|
|
2063
|
-
if (!findSection(parsed,
|
|
2064
|
-
|
|
2065
|
-
score -= req.penalty;
|
|
2078
|
+
for (const item of required) {
|
|
2079
|
+
if (!findSection(parsed, item.kind)) {
|
|
2080
|
+
addGap(accumulator, { kind: "missing-section", section: item.kind }, item.penalty);
|
|
2066
2081
|
}
|
|
2067
2082
|
}
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
const
|
|
2071
|
-
const
|
|
2072
|
-
const
|
|
2073
|
-
const
|
|
2074
|
-
const
|
|
2075
|
-
|
|
2076
|
-
|
|
2083
|
+
}
|
|
2084
|
+
function verifyPathCoverage(parsed, extraction, continuity, evidence, accumulator) {
|
|
2085
|
+
const modified = extraction.modifiedFiles.map((file) => file.path);
|
|
2086
|
+
const read = extraction.readFiles;
|
|
2087
|
+
const deleted = Array.from(new Set([...extraction.deletedFiles, ...continuity?.deletedFiles ?? []]));
|
|
2088
|
+
const required = [...modified, ...read, ...deleted];
|
|
2089
|
+
const expected = new Set(required);
|
|
2090
|
+
const rendered = buildSummaryPathEvidence(required, evidence.summaryBudgetTokens);
|
|
2091
|
+
const ownerSets = new Map;
|
|
2092
|
+
for (const file of required) {
|
|
2093
|
+
const display = rendered.get(file);
|
|
2077
2094
|
for (const candidate of [
|
|
2078
2095
|
file,
|
|
2079
2096
|
...display ? [decodePathDisplay(display)] : []
|
|
2080
2097
|
]) {
|
|
2081
2098
|
const normalized = normalizePath(candidate);
|
|
2082
|
-
const owners =
|
|
2099
|
+
const owners = ownerSets.get(normalized) ?? new Set;
|
|
2083
2100
|
owners.add(file);
|
|
2084
|
-
|
|
2101
|
+
ownerSets.set(normalized, owners);
|
|
2085
2102
|
}
|
|
2086
2103
|
}
|
|
2087
|
-
const
|
|
2088
|
-
const
|
|
2089
|
-
const modifiedListed =
|
|
2090
|
-
const readListed =
|
|
2091
|
-
const deletedListed =
|
|
2092
|
-
for (const file of
|
|
2093
|
-
const display =
|
|
2094
|
-
if (display && !hasListedPath(modifiedListed, file, display,
|
|
2095
|
-
|
|
2104
|
+
const ownerCounts = new Map(Array.from(ownerSets, ([file, owners]) => [file, owners.size]));
|
|
2105
|
+
const listed = (kind) => collectListedPaths(findSection(parsed, kind)?.body ?? "", expected);
|
|
2106
|
+
const modifiedListed = listed("files-modified");
|
|
2107
|
+
const readListed = listed("files-read");
|
|
2108
|
+
const deletedListed = listed("files-deleted");
|
|
2109
|
+
for (const file of modified) {
|
|
2110
|
+
const display = rendered.get(file);
|
|
2111
|
+
if (display && !hasListedPath(modifiedListed, file, display, ownerCounts)) {
|
|
2112
|
+
addGap(accumulator, { kind: "missing-file", path: file }, 5);
|
|
2096
2113
|
}
|
|
2097
2114
|
}
|
|
2098
|
-
for (const file of
|
|
2099
|
-
const display =
|
|
2100
|
-
if (display && !hasListedPath(readListed, file, display,
|
|
2101
|
-
|
|
2115
|
+
for (const file of read) {
|
|
2116
|
+
const display = rendered.get(file);
|
|
2117
|
+
if (display && !hasListedPath(readListed, file, display, ownerCounts)) {
|
|
2118
|
+
addGap(accumulator, { kind: "missing-read-file", path: file }, 5);
|
|
2102
2119
|
}
|
|
2103
2120
|
}
|
|
2104
|
-
for (const file of
|
|
2105
|
-
const display =
|
|
2106
|
-
if (display && !hasListedPath(deletedListed, file, display,
|
|
2107
|
-
|
|
2121
|
+
for (const file of deleted) {
|
|
2122
|
+
const display = rendered.get(file);
|
|
2123
|
+
if (display && !hasListedPath(deletedListed, file, display, ownerCounts)) {
|
|
2124
|
+
addGap(accumulator, { kind: "missing-deleted-file", path: file }, 5);
|
|
2108
2125
|
}
|
|
2109
2126
|
}
|
|
2110
|
-
|
|
2111
|
-
|
|
2127
|
+
return { modified, read, deleted, rendered };
|
|
2128
|
+
}
|
|
2129
|
+
function verifyErrorEvidence(normalizedSummary, collected, accumulator) {
|
|
2130
|
+
for (const error of collected.unresolved) {
|
|
2112
2131
|
const snippet = summaryEvidenceLine(error.message, TRUNC.ERROR_SNIPPET).toLowerCase().replace(/\\/g, "/");
|
|
2113
2132
|
if (snippet.length > 5 && !normalizedSummary.includes(snippet)) {
|
|
2114
|
-
|
|
2115
|
-
score -= 5;
|
|
2133
|
+
addGap(accumulator, { kind: "missing-error", message: error.message }, 5);
|
|
2116
2134
|
}
|
|
2117
2135
|
}
|
|
2118
|
-
for (const error of
|
|
2136
|
+
for (const error of collected.resolved) {
|
|
2119
2137
|
const snippet = summaryEvidenceLine(error.message, TRUNC.ERROR_SNIPPET).toLowerCase().replace(/\\/g, "/");
|
|
2120
2138
|
if (snippet.length > 5 && !normalizedSummary.includes(snippet)) {
|
|
2121
|
-
|
|
2122
|
-
kind: "missing-error",
|
|
2123
|
-
message: error.message,
|
|
2124
|
-
resolved: true
|
|
2125
|
-
});
|
|
2126
|
-
score -= 2;
|
|
2139
|
+
addGap(accumulator, { kind: "missing-error", message: error.message, resolved: true }, 2);
|
|
2127
2140
|
}
|
|
2128
2141
|
}
|
|
2142
|
+
}
|
|
2143
|
+
function verifySemanticCoverage(parsed, collected, accumulator) {
|
|
2129
2144
|
const constraintTarget = [
|
|
2130
2145
|
findSection(parsed, "constraints")?.body ?? "",
|
|
2131
2146
|
findSection(parsed, "critical-context")?.body ?? ""
|
|
2132
2147
|
].join(`
|
|
2133
2148
|
`);
|
|
2134
|
-
for (const constraint of
|
|
2149
|
+
for (const constraint of collected.constraints) {
|
|
2135
2150
|
if (!hasSemanticEvidence(constraint.text, constraintTarget)) {
|
|
2136
|
-
|
|
2137
|
-
score -= 8;
|
|
2151
|
+
addGap(accumulator, { kind: "missing-constraint", text: constraint.text }, 8);
|
|
2138
2152
|
}
|
|
2139
2153
|
if (hasSemanticContradiction(constraint.text, constraintTarget)) {
|
|
2140
|
-
|
|
2154
|
+
addGap(accumulator, {
|
|
2141
2155
|
kind: "inconsistency",
|
|
2142
2156
|
detail: "semantic-contradiction: constraint contradicts " + constraint.text.slice(0, TRUNC.SNIPPET)
|
|
2143
|
-
});
|
|
2144
|
-
score -= 20;
|
|
2157
|
+
}, 20);
|
|
2145
2158
|
}
|
|
2146
2159
|
}
|
|
2147
|
-
if (
|
|
2160
|
+
if (collected.goal) {
|
|
2148
2161
|
const goalTarget = findSection(parsed, "goal")?.body ?? "";
|
|
2149
|
-
if (!hasSemanticEvidence(
|
|
2150
|
-
|
|
2151
|
-
score -= 12;
|
|
2162
|
+
if (!hasSemanticEvidence(collected.goal, goalTarget)) {
|
|
2163
|
+
addGap(accumulator, { kind: "missing-goal", goal: collected.goal }, 12);
|
|
2152
2164
|
}
|
|
2153
|
-
if (hasSemanticContradiction(
|
|
2154
|
-
|
|
2165
|
+
if (hasSemanticContradiction(collected.goal, goalTarget)) {
|
|
2166
|
+
addGap(accumulator, {
|
|
2155
2167
|
kind: "inconsistency",
|
|
2156
2168
|
detail: "semantic-contradiction: goal polarity or condition changed"
|
|
2157
|
-
});
|
|
2158
|
-
score -= 20;
|
|
2169
|
+
}, 20);
|
|
2159
2170
|
}
|
|
2160
2171
|
}
|
|
2172
|
+
const decisionBody = findSection(parsed, "decisions")?.body ?? "";
|
|
2173
|
+
for (const decision of collected.decisions) {
|
|
2174
|
+
if (!hasSemanticEvidence(decision.summary, decisionBody)) {
|
|
2175
|
+
addGap(accumulator, { kind: "missing-decision", summary: decision.summary }, 8);
|
|
2176
|
+
}
|
|
2177
|
+
if (hasSemanticContradiction(decision.summary, decisionBody)) {
|
|
2178
|
+
addGap(accumulator, {
|
|
2179
|
+
kind: "inconsistency",
|
|
2180
|
+
detail: "semantic-contradiction: decision contradicts " + decision.summary.slice(0, TRUNC.SNIPPET)
|
|
2181
|
+
}, 20);
|
|
2182
|
+
}
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
2185
|
+
function verifyFileReferences(summary, extraction, continuity, evidence, collected, paths, accumulator) {
|
|
2161
2186
|
const groundedEvidence = [
|
|
2162
|
-
...
|
|
2163
|
-
...
|
|
2164
|
-
...
|
|
2165
|
-
...
|
|
2166
|
-
...
|
|
2187
|
+
...collected.unresolved.map((item) => item.message),
|
|
2188
|
+
...collected.resolved.map((item) => item.message),
|
|
2189
|
+
...collected.constraints.map((item) => item.text),
|
|
2190
|
+
...collected.decisions.map((item) => item.summary),
|
|
2191
|
+
...collected.goal ? [collected.goal] : [],
|
|
2167
2192
|
...extraction.lastUserMessages,
|
|
2168
2193
|
...extraction.timeline.map((item) => item.summary),
|
|
2169
2194
|
...extraction.topics.map((item) => item.primaryFile ?? ""),
|
|
2170
2195
|
...continuity?.openLoops.map((item) => item.summary) ?? [],
|
|
2171
2196
|
...continuity?.criticalContext ?? []
|
|
2172
2197
|
];
|
|
2173
|
-
const
|
|
2198
|
+
const groundedFiles = groundedEvidence.flatMap((value) => [
|
|
2174
2199
|
value,
|
|
2175
2200
|
summaryEvidenceLine(value, TRUNC.ERROR_SNIPPET),
|
|
2176
2201
|
summaryEvidenceLine(value, TRUNC.TOPIC_LABEL),
|
|
2177
2202
|
summaryEvidenceLine(value, TRUNC.PREVIEW),
|
|
2178
2203
|
summaryEvidenceLine(value, TRUNC.MESSAGE)
|
|
2179
2204
|
]).flatMap(extractFileRefs);
|
|
2180
|
-
const
|
|
2205
|
+
const renderedPaths = Array.from(paths.rendered.values()).flatMap((line) => [
|
|
2181
2206
|
decodePathDisplay(line),
|
|
2182
2207
|
line.startsWith('"') && line.endsWith('"') ? line.slice(1, -1) : line
|
|
2183
2208
|
]);
|
|
2184
|
-
const
|
|
2185
|
-
...
|
|
2186
|
-
...
|
|
2187
|
-
...
|
|
2209
|
+
const knownFiles = Array.from(new Set([
|
|
2210
|
+
...paths.modified,
|
|
2211
|
+
...paths.read,
|
|
2212
|
+
...paths.deleted,
|
|
2188
2213
|
...extraction.referencedFiles ?? [],
|
|
2189
|
-
...
|
|
2190
|
-
...
|
|
2191
|
-
...
|
|
2214
|
+
...groundedFiles,
|
|
2215
|
+
...renderedPaths,
|
|
2216
|
+
...renderedPaths.flatMap(extractFileRefs),
|
|
2192
2217
|
...continuity?.modifiedFiles ?? [],
|
|
2193
2218
|
...continuity?.readFiles ?? [],
|
|
2194
2219
|
...(continuity?.unresolvedErrors ?? []).flatMap((error) => error.files),
|
|
2195
2220
|
...(continuity?.openLoops ?? []).flatMap((loop) => loop.files)
|
|
2196
2221
|
]));
|
|
2197
2222
|
for (const ref of new Set(extractFileRefs(summary))) {
|
|
2198
|
-
const grounded = isKnownPathReference(ref,
|
|
2199
|
-
if (!grounded)
|
|
2200
|
-
|
|
2201
|
-
score -= 4;
|
|
2202
|
-
}
|
|
2223
|
+
const grounded = isKnownPathReference(ref, knownFiles) || Boolean(evidence.sourceMessages && sourceSupportsFileReference(ref, evidence.sourceMessages));
|
|
2224
|
+
if (!grounded)
|
|
2225
|
+
addGap(accumulator, { kind: "fabricated-file", ref }, 4);
|
|
2203
2226
|
}
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2227
|
+
}
|
|
2228
|
+
function verifyProgressConsistency(parsed, extraction, collected, paths, accumulator) {
|
|
2229
|
+
const progress = findSection(parsed, "progress");
|
|
2230
|
+
if (!progress)
|
|
2231
|
+
return;
|
|
2232
|
+
const done = progress.body.match(/###\s*Done[\s\S]*?(?=###|$)/i)?.[0] ?? "";
|
|
2233
|
+
const blocked = progress.body.match(/###\s*Blocked[\s\S]*?(?=###|$)/i)?.[0] ?? "";
|
|
2234
|
+
if (collected.unresolved.length > 0 && noneBlockerLineIndexes(blocked.split(/\r?\n/).slice(1)).size > 0) {
|
|
2235
|
+
addGap(accumulator, {
|
|
2236
|
+
kind: "inconsistency",
|
|
2237
|
+
detail: "blocked-none: Blocked says none despite unresolved errors"
|
|
2238
|
+
}, 12);
|
|
2239
|
+
}
|
|
2240
|
+
const doneRefs = new Set(extractFileRefs(done).map(normalizePath));
|
|
2241
|
+
for (const file of extraction.modifiedFiles) {
|
|
2242
|
+
const needles = buildUniquePathNeedles(file.path, paths.modified);
|
|
2243
|
+
if (!needles.some((needle) => doneRefs.has(normalizePath(needle))))
|
|
2244
|
+
continue;
|
|
2245
|
+
const unresolved = collected.unresolved.find((error) => {
|
|
2246
|
+
const firstLine = error.message.split(/\r?\n/, 1)[0] ?? "";
|
|
2247
|
+
const refs = extractFileRefs(firstLine).map(normalizePath);
|
|
2248
|
+
return needles.some((needle) => refs.includes(normalizePath(needle)));
|
|
2249
|
+
});
|
|
2250
|
+
if (unresolved) {
|
|
2251
|
+
addGap(accumulator, {
|
|
2211
2252
|
kind: "inconsistency",
|
|
2212
|
-
detail: "
|
|
2213
|
-
});
|
|
2214
|
-
score -= 12;
|
|
2215
|
-
}
|
|
2216
|
-
const doneRefs = new Set(extractFileRefs(doneSection).map(normalizePath));
|
|
2217
|
-
for (const file of extraction.modifiedFiles) {
|
|
2218
|
-
const uniqueNeedles = buildUniquePathNeedles(file.path, modifiedPaths);
|
|
2219
|
-
if (!uniqueNeedles.some((needle) => doneRefs.has(normalizePath(needle))))
|
|
2220
|
-
continue;
|
|
2221
|
-
const unresolved = unresolvedEvidence.find((error) => {
|
|
2222
|
-
const firstLine = error.message.split(/\r?\n/, 1)[0] ?? "";
|
|
2223
|
-
const errorRefs = extractFileRefs(firstLine).map(normalizePath);
|
|
2224
|
-
return uniqueNeedles.some((needle) => errorRefs.includes(normalizePath(needle)));
|
|
2225
|
-
});
|
|
2226
|
-
if (unresolved) {
|
|
2227
|
-
gaps.push({
|
|
2228
|
-
kind: "inconsistency",
|
|
2229
|
-
detail: file.path + " marked Done but has unresolved error"
|
|
2230
|
-
});
|
|
2231
|
-
score -= 5;
|
|
2232
|
-
}
|
|
2253
|
+
detail: file.path + " marked Done but has unresolved error"
|
|
2254
|
+
}, 5);
|
|
2233
2255
|
}
|
|
2234
2256
|
}
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
}
|
|
2241
|
-
if (hasSemanticContradiction(decision.summary, decisionBody)) {
|
|
2242
|
-
gaps.push({
|
|
2243
|
-
kind: "inconsistency",
|
|
2244
|
-
detail: "semantic-contradiction: decision contradicts " + decision.summary.slice(0, TRUNC.SNIPPET)
|
|
2245
|
-
});
|
|
2246
|
-
score -= 20;
|
|
2247
|
-
}
|
|
2257
|
+
}
|
|
2258
|
+
function verifyOpenLoopsAndClaims(summary, parsed, extraction, continuity, evidence, collected, accumulator) {
|
|
2259
|
+
const unresolvedCount = collected.unresolved.length + (continuity?.openLoops.filter((loop) => loop.status !== "resolved").length ?? 0);
|
|
2260
|
+
if (unresolvedCount >= 1 && !findSection(parsed, "open-loops") && !summary.toLowerCase().replace(/\\/g, "/").includes("unresolved")) {
|
|
2261
|
+
addGap(accumulator, { kind: "missing-open-loops", unresolvedCount }, 5);
|
|
2248
2262
|
}
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
const tools = successfulToolEvidence(evidence.sourceMessages);
|
|
2256
|
-
for (const claim of outcomeClaims(summary)) {
|
|
2257
|
-
if (!successfulToolSupportsClaim(claim, tools, extraction)) {
|
|
2258
|
-
gaps.push({ kind: "unsupported-claim", claim });
|
|
2259
|
-
score -= 20;
|
|
2260
|
-
}
|
|
2263
|
+
if (!evidence.sourceMessages)
|
|
2264
|
+
return;
|
|
2265
|
+
const tools = successfulToolEvidence(evidence.sourceMessages);
|
|
2266
|
+
for (const claim of outcomeClaims(summary)) {
|
|
2267
|
+
if (!successfulToolSupportsClaim(claim, tools, extraction)) {
|
|
2268
|
+
addGap(accumulator, { kind: "unsupported-claim", claim }, 20);
|
|
2261
2269
|
}
|
|
2262
2270
|
}
|
|
2263
|
-
|
|
2264
|
-
|
|
2271
|
+
}
|
|
2272
|
+
function verifySummary(summary, extraction, continuity = null, evidence = {}) {
|
|
2273
|
+
const parsed = parseSummary(summary);
|
|
2274
|
+
const accumulator = { gaps: [], score: 100 };
|
|
2275
|
+
const collected = collectVerificationEvidence(extraction, continuity, evidence);
|
|
2276
|
+
verifyRequiredSections(parsed, accumulator);
|
|
2277
|
+
const paths = verifyPathCoverage(parsed, extraction, continuity, evidence, accumulator);
|
|
2278
|
+
verifyErrorEvidence(summary.toLowerCase().replace(/\\/g, "/").replace(/\s+/g, " "), collected, accumulator);
|
|
2279
|
+
verifySemanticCoverage(parsed, collected, accumulator);
|
|
2280
|
+
verifyFileReferences(summary, extraction, continuity, evidence, collected, paths, accumulator);
|
|
2281
|
+
verifyProgressConsistency(parsed, extraction, collected, paths, accumulator);
|
|
2282
|
+
verifyOpenLoopsAndClaims(summary, parsed, extraction, continuity, evidence, collected, accumulator);
|
|
2283
|
+
const score = Math.max(0, accumulator.score);
|
|
2284
|
+
return {
|
|
2285
|
+
ok: accumulator.gaps.length === 0 && score >= 85,
|
|
2286
|
+
gaps: accumulator.gaps,
|
|
2287
|
+
score
|
|
2288
|
+
};
|
|
2265
2289
|
}
|
|
2266
2290
|
|
|
2267
2291
|
// scripts/provider-scenario-eval.ts
|
|
@@ -2282,12 +2306,35 @@ Latest request: fix the test, run the release gate, and do not publish.`,
|
|
|
2282
2306
|
],
|
|
2283
2307
|
readFiles: ["package.json"],
|
|
2284
2308
|
deletedFiles: [],
|
|
2285
|
-
errors: [
|
|
2286
|
-
|
|
2287
|
-
|
|
2309
|
+
errors: [
|
|
2310
|
+
{
|
|
2311
|
+
index: 10,
|
|
2312
|
+
tool: "test",
|
|
2313
|
+
message: "expiry boundary test still fails by one second",
|
|
2314
|
+
retryAttempted: true,
|
|
2315
|
+
resolved: false
|
|
2316
|
+
}
|
|
2317
|
+
],
|
|
2318
|
+
decisions: [
|
|
2319
|
+
{
|
|
2320
|
+
index: 4,
|
|
2321
|
+
type: "explicit",
|
|
2322
|
+
summary: "Reuse node:crypto and preserve the AuthService API"
|
|
2323
|
+
}
|
|
2324
|
+
],
|
|
2325
|
+
constraints: [
|
|
2326
|
+
{
|
|
2327
|
+
index: 1,
|
|
2328
|
+
text: "Do not add dependencies",
|
|
2329
|
+
category: "prohibition",
|
|
2330
|
+
confidence: 1
|
|
2331
|
+
}
|
|
2332
|
+
],
|
|
2288
2333
|
topics: [],
|
|
2289
2334
|
timeline: [],
|
|
2290
|
-
lastUserMessages: [
|
|
2335
|
+
lastUserMessages: [
|
|
2336
|
+
"Fix the test, run the release gate, and do not publish"
|
|
2337
|
+
],
|
|
2291
2338
|
lastErrors: []
|
|
2292
2339
|
}
|
|
2293
2340
|
},
|
|
@@ -2301,14 +2348,35 @@ The network timeout was resolved. Open loop: add a regression check for two simu
|
|
|
2301
2348
|
extraction: {
|
|
2302
2349
|
mainGoal: "Stop duplicate background jobs",
|
|
2303
2350
|
messageCount: 18,
|
|
2304
|
-
modifiedFiles: [
|
|
2351
|
+
modifiedFiles: [
|
|
2352
|
+
{ path: "src/queue.ts", toolCalls: 2, lastModifiedIndex: 13 }
|
|
2353
|
+
],
|
|
2305
2354
|
readFiles: ["src/queue.ts", "src/worker.ts"],
|
|
2306
2355
|
deletedFiles: [],
|
|
2307
2356
|
errors: [],
|
|
2308
|
-
decisions: [
|
|
2309
|
-
|
|
2357
|
+
decisions: [
|
|
2358
|
+
{
|
|
2359
|
+
index: 9,
|
|
2360
|
+
type: "explicit",
|
|
2361
|
+
summary: "Commit the idempotency key before retry scheduling"
|
|
2362
|
+
}
|
|
2363
|
+
],
|
|
2364
|
+
constraints: [
|
|
2365
|
+
{
|
|
2366
|
+
index: 10,
|
|
2367
|
+
text: "Keep concurrency at two",
|
|
2368
|
+
category: "requirement",
|
|
2369
|
+
confidence: 1
|
|
2370
|
+
}
|
|
2371
|
+
],
|
|
2310
2372
|
topics: [],
|
|
2311
|
-
timeline: [
|
|
2373
|
+
timeline: [
|
|
2374
|
+
{
|
|
2375
|
+
index: 16,
|
|
2376
|
+
event: "open-loop",
|
|
2377
|
+
summary: "Add a simultaneous-session regression check"
|
|
2378
|
+
}
|
|
2379
|
+
],
|
|
2312
2380
|
lastUserMessages: ["Add the regression check next"],
|
|
2313
2381
|
lastErrors: []
|
|
2314
2382
|
}
|
|
@@ -2323,18 +2391,48 @@ Critical paths: src/app/run-smart-compact.ts, src/infra/llm-client.ts, scripts/e
|
|
|
2323
2391
|
extraction: {
|
|
2324
2392
|
mainGoal: "Prepare v8 without publishing",
|
|
2325
2393
|
messageCount: 30,
|
|
2326
|
-
modifiedFiles: [
|
|
2394
|
+
modifiedFiles: [
|
|
2395
|
+
{
|
|
2396
|
+
path: "src/app/run-smart-compact.ts",
|
|
2397
|
+
toolCalls: 2,
|
|
2398
|
+
lastModifiedIndex: 20
|
|
2399
|
+
}
|
|
2400
|
+
],
|
|
2327
2401
|
readFiles: ["src/infra/llm-client.ts", "scripts/eval-gate.ts"],
|
|
2328
2402
|
deletedFiles: [],
|
|
2329
2403
|
errors: [],
|
|
2330
|
-
decisions: [
|
|
2404
|
+
decisions: [
|
|
2405
|
+
{
|
|
2406
|
+
index: 5,
|
|
2407
|
+
type: "explicit",
|
|
2408
|
+
summary: "Do not change the selected model automatically"
|
|
2409
|
+
}
|
|
2410
|
+
],
|
|
2331
2411
|
constraints: [
|
|
2332
|
-
{
|
|
2333
|
-
|
|
2412
|
+
{
|
|
2413
|
+
index: 2,
|
|
2414
|
+
text: "Do not publish",
|
|
2415
|
+
category: "prohibition",
|
|
2416
|
+
confidence: 1
|
|
2417
|
+
},
|
|
2418
|
+
{
|
|
2419
|
+
index: 6,
|
|
2420
|
+
text: "Keep Pi peer dependencies as wildcards",
|
|
2421
|
+
category: "requirement",
|
|
2422
|
+
confidence: 1
|
|
2423
|
+
}
|
|
2334
2424
|
],
|
|
2335
2425
|
topics: [],
|
|
2336
|
-
timeline: [
|
|
2337
|
-
|
|
2426
|
+
timeline: [
|
|
2427
|
+
{
|
|
2428
|
+
index: 28,
|
|
2429
|
+
event: "open-loop",
|
|
2430
|
+
summary: "Implement telemetry canary and dashboard confidence"
|
|
2431
|
+
}
|
|
2432
|
+
],
|
|
2433
|
+
lastUserMessages: [
|
|
2434
|
+
"Continue provider evaluation, then telemetry and dashboard confidence"
|
|
2435
|
+
],
|
|
2338
2436
|
lastErrors: []
|
|
2339
2437
|
}
|
|
2340
2438
|
}
|
|
@@ -2380,7 +2478,15 @@ for (const model of models) {
|
|
|
2380
2478
|
const auth = await registry.getApiKeyAndHeaders(model);
|
|
2381
2479
|
if (!auth.ok || !auth.apiKey) {
|
|
2382
2480
|
for (const scenario of scenarios)
|
|
2383
|
-
results.push({
|
|
2481
|
+
results.push({
|
|
2482
|
+
model: label,
|
|
2483
|
+
scenario: scenario.name,
|
|
2484
|
+
score: 0,
|
|
2485
|
+
latencyMs: 0,
|
|
2486
|
+
input: 0,
|
|
2487
|
+
output: 0,
|
|
2488
|
+
error: "auth unavailable"
|
|
2489
|
+
});
|
|
2384
2490
|
continue;
|
|
2385
2491
|
}
|
|
2386
2492
|
for (const scenario of scenarios) {
|
|
@@ -2391,7 +2497,13 @@ for (const model of models) {
|
|
|
2391
2497
|
try {
|
|
2392
2498
|
const response = await rawLlmClient.complete(model, {
|
|
2393
2499
|
systemPrompt,
|
|
2394
|
-
messages: [
|
|
2500
|
+
messages: [
|
|
2501
|
+
{
|
|
2502
|
+
role: "user",
|
|
2503
|
+
content: [{ type: "text", text: scenario.transcript }],
|
|
2504
|
+
timestamp: Date.now()
|
|
2505
|
+
}
|
|
2506
|
+
]
|
|
2395
2507
|
}, {
|
|
2396
2508
|
apiKey: auth.apiKey,
|
|
2397
2509
|
headers: auth.headers,
|
|
@@ -2400,7 +2512,7 @@ for (const model of models) {
|
|
|
2400
2512
|
codexWatchdogMs: 60000,
|
|
2401
2513
|
signal: controller.signal
|
|
2402
2514
|
});
|
|
2403
|
-
const summary = response.content.
|
|
2515
|
+
const summary = response.content.flatMap((item) => item.type === "text" ? [item.text] : []).join(`
|
|
2404
2516
|
`).trim();
|
|
2405
2517
|
results.push({
|
|
2406
2518
|
model: label,
|
|
@@ -2425,12 +2537,15 @@ for (const model of models) {
|
|
|
2425
2537
|
}
|
|
2426
2538
|
}
|
|
2427
2539
|
}
|
|
2540
|
+
function tableCell(value) {
|
|
2541
|
+
return String(value).replace(/\\/g, "\\\\").replace(/\|/g, "\\|").replace(/\r?\n/g, " ");
|
|
2542
|
+
}
|
|
2428
2543
|
console.log(`# Live Provider Scenario Matrix
|
|
2429
2544
|
`);
|
|
2430
2545
|
console.log("| Model | Scenario | Verify | Latency | Input | Output | Status |");
|
|
2431
2546
|
console.log("|---|---|---:|---:|---:|---:|---|");
|
|
2432
2547
|
for (const result of results) {
|
|
2433
|
-
console.log("| " + result.model + " | " + result.scenario + " | " + result.score + " | " + result.latencyMs + "ms | " + result.input + " | " + result.output + " | " + (result.error ? "error: " + result.error
|
|
2548
|
+
console.log("| " + tableCell(result.model) + " | " + tableCell(result.scenario) + " | " + result.score + " | " + result.latencyMs + "ms | " + result.input + " | " + result.output + " | " + (result.error ? "error: " + tableCell(result.error) : "ok") + " |");
|
|
2434
2549
|
}
|
|
2435
2550
|
console.log(`
|
|
2436
2551
|
Advisory only: stage routes stay on the selected model unless explicitly configured.`);
|