@vibgrate/cli 2026.722.1 → 2026.727.1
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/DOCS.md +418 -196
- package/README.md +34 -7
- package/dist/baseline-K7YBFKZI.js +7 -0
- package/dist/{baseline-J44M7MWP.js.map → baseline-K7YBFKZI.js.map} +1 -1
- package/dist/chunk-3BVSDMHV.js +2496 -0
- package/dist/chunk-3BVSDMHV.js.map +1 -0
- package/dist/{chunk-PY3DNX5H.js → chunk-3Q7QBWFW.js} +13 -2
- package/dist/chunk-3Q7QBWFW.js.map +1 -0
- package/dist/chunk-4CQ3MGNN.js +136 -0
- package/dist/chunk-4CQ3MGNN.js.map +1 -0
- package/dist/chunk-5I4VSBWQ.js +764 -0
- package/dist/chunk-5I4VSBWQ.js.map +1 -0
- package/dist/chunk-5Q54GEHM.js +29 -0
- package/dist/chunk-5Q54GEHM.js.map +1 -0
- package/dist/{chunk-XKXKWYJF.js → chunk-65MEOA4E.js} +21 -18
- package/dist/chunk-65MEOA4E.js.map +1 -0
- package/dist/{chunk-WNIIKCNF.js → chunk-6IOQJGFT.js} +29 -238
- package/dist/chunk-6IOQJGFT.js.map +1 -0
- package/dist/{chunk-CS37OBE3.js → chunk-7JHPQ6WF.js} +791 -783
- package/dist/chunk-7JHPQ6WF.js.map +1 -0
- package/dist/chunk-BPF3YX2U.js +64 -0
- package/dist/chunk-BPF3YX2U.js.map +1 -0
- package/dist/chunk-BWPBB45M.js +88 -0
- package/dist/chunk-BWPBB45M.js.map +1 -0
- package/dist/chunk-C4CKTAZP.js +1695 -0
- package/dist/chunk-C4CKTAZP.js.map +1 -0
- package/dist/chunk-EDAAG7BL.js +612 -0
- package/dist/chunk-EDAAG7BL.js.map +1 -0
- package/dist/chunk-EDBV33B7.js +57 -0
- package/dist/chunk-EDBV33B7.js.map +1 -0
- package/dist/chunk-HABUT7HW.js +233 -0
- package/dist/chunk-HABUT7HW.js.map +1 -0
- package/dist/{chunk-EXQV26MF.js → chunk-HYZVHJ3Q.js} +3 -3
- package/dist/{chunk-EXQV26MF.js.map → chunk-HYZVHJ3Q.js.map} +1 -1
- package/dist/chunk-IS7VTH2Q.js +627 -0
- package/dist/chunk-IS7VTH2Q.js.map +1 -0
- package/dist/chunk-IZDH67DX.js +289 -0
- package/dist/chunk-IZDH67DX.js.map +1 -0
- package/dist/chunk-J7LKEB2R.js +51 -0
- package/dist/chunk-J7LKEB2R.js.map +1 -0
- package/dist/chunk-JIBOJTGS.js +313 -0
- package/dist/chunk-JIBOJTGS.js.map +1 -0
- package/dist/chunk-JZYGEZTU.js +82 -0
- package/dist/chunk-JZYGEZTU.js.map +1 -0
- package/dist/chunk-K7REHDQS.js +776 -0
- package/dist/chunk-K7REHDQS.js.map +1 -0
- package/dist/chunk-KHJO4D5N.js +108 -0
- package/dist/chunk-KHJO4D5N.js.map +1 -0
- package/dist/chunk-NDMDSPFX.js +69 -0
- package/dist/chunk-NDMDSPFX.js.map +1 -0
- package/dist/chunk-OOIG3RUM.js +276 -0
- package/dist/chunk-OOIG3RUM.js.map +1 -0
- package/dist/{chunk-6CXTPC74.js → chunk-OXQNQW5J.js} +27 -6
- package/dist/chunk-OXQNQW5J.js.map +1 -0
- package/dist/chunk-PMYWZRRS.js +62 -0
- package/dist/chunk-PMYWZRRS.js.map +1 -0
- package/dist/chunk-PXHNTC6D.js +382 -0
- package/dist/chunk-PXHNTC6D.js.map +1 -0
- package/dist/{chunk-VXT6LUGP.js → chunk-S6CYDZEW.js} +1395 -1975
- package/dist/chunk-S6CYDZEW.js.map +1 -0
- package/dist/chunk-SZLF66FJ.js +843 -0
- package/dist/chunk-SZLF66FJ.js.map +1 -0
- package/dist/chunk-VXGV6NTD.js +476 -0
- package/dist/chunk-VXGV6NTD.js.map +1 -0
- package/dist/{chunk-JSNHZCIV.js → chunk-WLETRGAC.js} +16 -9
- package/dist/chunk-WLETRGAC.js.map +1 -0
- package/dist/{chunk-2EVQKZTO.js → chunk-YDAT7LD2.js} +190 -13
- package/dist/chunk-YDAT7LD2.js.map +1 -0
- package/dist/cli.js +3451 -509
- package/dist/cli.js.map +1 -1
- package/dist/execution-env-3BN7Z737.js +3 -0
- package/dist/execution-env-3BN7Z737.js.map +1 -0
- package/dist/federation-EPZDUWCK.js +6 -0
- package/dist/federation-EPZDUWCK.js.map +1 -0
- package/dist/git-ref-HPRLUFFW.js +3 -0
- package/dist/git-ref-HPRLUFFW.js.map +1 -0
- package/dist/graph-backend-ZMSYOZMW.js +10 -0
- package/dist/graph-backend-ZMSYOZMW.js.map +1 -0
- package/dist/index.d.ts +432 -126
- package/dist/index.js +14 -7
- package/dist/index.js.map +1 -1
- package/dist/{interactive-736PBC7J.js → interactive-ICL7D7LO.js} +185 -365
- package/dist/interactive-ICL7D7LO.js.map +1 -0
- package/dist/llm-host-2RAYQSDQ.js +8 -0
- package/dist/llm-host-2RAYQSDQ.js.map +1 -0
- package/dist/load-OHVKDX4R.js +7 -0
- package/dist/load-OHVKDX4R.js.map +1 -0
- package/dist/local-runtime-OJFLX2HI.js +4 -0
- package/dist/local-runtime-OJFLX2HI.js.map +1 -0
- package/dist/model-execution-profile-MQ2QCP24.js +3 -0
- package/dist/model-execution-profile-MQ2QCP24.js.map +1 -0
- package/dist/model-orchestrator-FFTZI3U2.js +8 -0
- package/dist/model-orchestrator-FFTZI3U2.js.map +1 -0
- package/dist/parse-worker.d.ts +1 -1
- package/dist/parse-worker.js +1 -1
- package/dist/paths-MCH75O3G.js +5 -0
- package/dist/paths-MCH75O3G.js.map +1 -0
- package/dist/resolve-gguf-4UPIGPEE.js +7 -0
- package/dist/resolve-gguf-4UPIGPEE.js.map +1 -0
- package/dist/runtime-session-IDU5BDUJ.js +17 -0
- package/dist/runtime-session-IDU5BDUJ.js.map +1 -0
- package/dist/session-L2OSDRPI.js +10 -0
- package/dist/{session-POJUDFPH.js.map → session-L2OSDRPI.js.map} +1 -1
- package/dist/{stream-json-Q2JGWPHB.js → stream-json-F74W5FCK.js} +16 -6
- package/dist/stream-json-F74W5FCK.js.map +1 -0
- package/dist/{types-Jl6RJ175.d.ts → types-ZMg1mSuu.d.ts} +3 -1
- package/dist/vgd-EGBQD7BW.js +16 -0
- package/dist/vgd-EGBQD7BW.js.map +1 -0
- package/package.json +9 -6
- package/dist/baseline-J44M7MWP.js +0 -7
- package/dist/chunk-2EVQKZTO.js.map +0 -1
- package/dist/chunk-6CXTPC74.js.map +0 -1
- package/dist/chunk-CS37OBE3.js.map +0 -1
- package/dist/chunk-GGJZA3Q6.js +0 -961
- package/dist/chunk-GGJZA3Q6.js.map +0 -1
- package/dist/chunk-JBXNQCGE.js +0 -484
- package/dist/chunk-JBXNQCGE.js.map +0 -1
- package/dist/chunk-JSNHZCIV.js.map +0 -1
- package/dist/chunk-PY3DNX5H.js.map +0 -1
- package/dist/chunk-VXT6LUGP.js.map +0 -1
- package/dist/chunk-WNIIKCNF.js.map +0 -1
- package/dist/chunk-XKXKWYJF.js.map +0 -1
- package/dist/interactive-736PBC7J.js.map +0 -1
- package/dist/session-POJUDFPH.js +0 -6
- package/dist/stream-json-Q2JGWPHB.js.map +0 -1
|
@@ -0,0 +1,2496 @@
|
|
|
1
|
+
import { localGraphBackend } from './chunk-4CQ3MGNN.js';
|
|
2
|
+
import { buildIdentifierTrieFromGraph, KvBlockRegistry, graphDraftCandidates, extractIdentifiers, scanIdentifiersAgainstTrie } from './chunk-JIBOJTGS.js';
|
|
3
|
+
import { buildAgentMessages, redactSecrets, unifiedDiff, applyEdit } from './chunk-7JHPQ6WF.js';
|
|
4
|
+
import { resolve, resolveLib, loadCatalog, readDoc, localPackageDocs, resolveVersion } from './chunk-C4CKTAZP.js';
|
|
5
|
+
import { resolveOne } from './chunk-J7LKEB2R.js';
|
|
6
|
+
import { summarizeCapsule, buildCodeContext, buildTaskCapsule, capsuleToCodeContext, recordCliCall, CLI_TOOL_ALIASES, CAPSULE_RANKING_VERSION } from './chunk-EDAAG7BL.js';
|
|
7
|
+
import { impactOf } from './chunk-6IOQJGFT.js';
|
|
8
|
+
import { parseSource, langForExtension } from './chunk-3Q7QBWFW.js';
|
|
9
|
+
import { repositoryIdFromRoot } from './chunk-BWPBB45M.js';
|
|
10
|
+
import { hashString, canonicalize } from './chunk-JFGYT6BI.js';
|
|
11
|
+
import { spawnSync } from 'child_process';
|
|
12
|
+
import * as fs from 'fs';
|
|
13
|
+
import * as path from 'path';
|
|
14
|
+
|
|
15
|
+
// src/code/capsule-delta.ts
|
|
16
|
+
var CAPSULE_DELTA_SCHEMA_VERSION = "capsule-delta/0";
|
|
17
|
+
function buildCapsuleDelta(previous, next) {
|
|
18
|
+
const prevPrimary = keySet(previous.primary);
|
|
19
|
+
const nextPrimary = keySet(next.primary);
|
|
20
|
+
const prevSupport = keySet(previous.supporting);
|
|
21
|
+
keySet(next.supporting);
|
|
22
|
+
const addedPrimary = next.primary.filter((p) => !prevPrimary.has(symKey(p))).map((p) => ({ qualifiedName: p.qualifiedName, file: p.file, kind: p.kind }));
|
|
23
|
+
const removedPrimary = previous.primary.filter((p) => !nextPrimary.has(symKey(p))).map((p) => ({ qualifiedName: p.qualifiedName, file: p.file }));
|
|
24
|
+
const addedSupporting = next.supporting.filter((p) => !prevSupport.has(symKey(p))).map((p) => ({ qualifiedName: p.qualifiedName, file: p.file, kind: p.kind }));
|
|
25
|
+
const changedSourceFiles = diffSourceFiles(previous.sourceSlices, next.sourceSlices);
|
|
26
|
+
const verificationDelta = diffVerification(previous.verificationPlan, next.verificationPlan);
|
|
27
|
+
const delta = {
|
|
28
|
+
schemaVersion: CAPSULE_DELTA_SCHEMA_VERSION,
|
|
29
|
+
changedSourceFiles,
|
|
30
|
+
addedPrimary,
|
|
31
|
+
removedPrimary,
|
|
32
|
+
addedSupporting,
|
|
33
|
+
verificationDelta,
|
|
34
|
+
rendered: "",
|
|
35
|
+
tokensEstimate: 0
|
|
36
|
+
};
|
|
37
|
+
delta.rendered = renderCapsuleDelta(delta, next);
|
|
38
|
+
delta.tokensEstimate = Math.max(1, Math.ceil(delta.rendered.length / 4));
|
|
39
|
+
return delta;
|
|
40
|
+
}
|
|
41
|
+
function isEmptyCapsuleDelta(d) {
|
|
42
|
+
return d.changedSourceFiles.length === 0 && d.addedPrimary.length === 0 && d.removedPrimary.length === 0 && d.addedSupporting.length === 0 && d.verificationDelta.syntaxFilesAdded.length === 0 && d.verificationDelta.syntaxFilesRemoved.length === 0 && d.verificationDelta.notesAdded.length === 0;
|
|
43
|
+
}
|
|
44
|
+
function keySet(syms) {
|
|
45
|
+
return new Set(syms.map(symKey));
|
|
46
|
+
}
|
|
47
|
+
function symKey(p) {
|
|
48
|
+
return `${p.qualifiedName}\0${p.file}`;
|
|
49
|
+
}
|
|
50
|
+
function diffSourceFiles(prev, next) {
|
|
51
|
+
const prevHash = /* @__PURE__ */ new Map();
|
|
52
|
+
for (const s of prev) {
|
|
53
|
+
const k = `${s.file}:${s.start}-${s.end}`;
|
|
54
|
+
prevHash.set(k, s.contentHash);
|
|
55
|
+
}
|
|
56
|
+
const changed = /* @__PURE__ */ new Set();
|
|
57
|
+
for (const s of next) {
|
|
58
|
+
const k = `${s.file}:${s.start}-${s.end}`;
|
|
59
|
+
const old = prevHash.get(k);
|
|
60
|
+
if (old !== s.contentHash) changed.add(s.file);
|
|
61
|
+
}
|
|
62
|
+
const nextFiles = new Set(next.map((s) => s.file));
|
|
63
|
+
for (const s of prev) {
|
|
64
|
+
if (!nextFiles.has(s.file)) changed.add(s.file);
|
|
65
|
+
}
|
|
66
|
+
return [...changed].sort();
|
|
67
|
+
}
|
|
68
|
+
function diffVerification(prev, next) {
|
|
69
|
+
const prevFiles = new Set(prev.syntaxFiles);
|
|
70
|
+
const nextFiles = new Set(next.syntaxFiles);
|
|
71
|
+
const prevNotes = new Set(prev.notes);
|
|
72
|
+
return {
|
|
73
|
+
syntaxFilesAdded: next.syntaxFiles.filter((f) => !prevFiles.has(f)).sort(),
|
|
74
|
+
syntaxFilesRemoved: prev.syntaxFiles.filter((f) => !nextFiles.has(f)).sort(),
|
|
75
|
+
notesAdded: next.notes.filter((n) => !prevNotes.has(n))
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function renderCapsuleDelta(d, next) {
|
|
79
|
+
const lines = [
|
|
80
|
+
"## Capsule delta (since last context)",
|
|
81
|
+
`Instruction: ${next.instruction}`
|
|
82
|
+
];
|
|
83
|
+
if (d.changedSourceFiles.length) {
|
|
84
|
+
lines.push(`Source updated: ${d.changedSourceFiles.join(", ")}`);
|
|
85
|
+
}
|
|
86
|
+
if (d.addedPrimary.length) {
|
|
87
|
+
lines.push("New primary:");
|
|
88
|
+
for (const p of d.addedPrimary) lines.push(`- ${p.qualifiedName} (${p.kind}) ${p.file}`);
|
|
89
|
+
}
|
|
90
|
+
if (d.removedPrimary.length) {
|
|
91
|
+
lines.push("Removed primary:");
|
|
92
|
+
for (const p of d.removedPrimary) lines.push(`- ${p.qualifiedName} ${p.file}`);
|
|
93
|
+
}
|
|
94
|
+
if (d.addedSupporting.length) {
|
|
95
|
+
lines.push("New supporting:");
|
|
96
|
+
for (const p of d.addedSupporting.slice(0, 8)) lines.push(`- ${p.qualifiedName} ${p.file}`);
|
|
97
|
+
}
|
|
98
|
+
if (d.verificationDelta.syntaxFilesAdded.length || d.verificationDelta.notesAdded.length) {
|
|
99
|
+
lines.push("Verification plan updates:");
|
|
100
|
+
for (const f of d.verificationDelta.syntaxFilesAdded) lines.push(`- +syntax ${f}`);
|
|
101
|
+
for (const n of d.verificationDelta.notesAdded.slice(0, 4)) lines.push(`- +note ${n}`);
|
|
102
|
+
}
|
|
103
|
+
if (isEmptyCapsuleDelta(d)) {
|
|
104
|
+
lines.push("(no structural capsule changes)");
|
|
105
|
+
}
|
|
106
|
+
const changed = new Set(d.changedSourceFiles);
|
|
107
|
+
const slices = next.sourceSlices.filter((s) => changed.has(s.file)).slice(0, 6);
|
|
108
|
+
if (slices.length) {
|
|
109
|
+
lines.push("", "### Updated source slices");
|
|
110
|
+
for (const s of slices) {
|
|
111
|
+
lines.push(`\`\`\`${s.file}:${s.start}-${s.end}`, s.content, "```");
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return lines.join("\n");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// src/code/advanced-mode.ts
|
|
118
|
+
var DISCOVERY_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
119
|
+
"search_code",
|
|
120
|
+
"read_file",
|
|
121
|
+
"list_files",
|
|
122
|
+
"graph_impact",
|
|
123
|
+
"library_docs"
|
|
124
|
+
]);
|
|
125
|
+
function createDiscoveryGateState() {
|
|
126
|
+
return { discoveryCount: 0, mutations: 0, inspectChangeUsed: false };
|
|
127
|
+
}
|
|
128
|
+
function gateDiscoveryTool(name, state, options) {
|
|
129
|
+
if (options.advancedMode) return { allow: true };
|
|
130
|
+
if (!DISCOVERY_TOOL_NAMES.has(name)) return { allow: true };
|
|
131
|
+
if (state.mutations > 0 || state.inspectChangeUsed) return { allow: true };
|
|
132
|
+
const soft = options.softLimit ?? 2;
|
|
133
|
+
const hard = options.hardLimit ?? 6;
|
|
134
|
+
if (hard > 0 && state.discoveryCount >= hard) {
|
|
135
|
+
return {
|
|
136
|
+
allow: false,
|
|
137
|
+
reason: `Discovery tools are limited for capsule-first runs (${hard} free calls used). Use inspect_task / inspect_change / apply_patch / edit_file with the capsule evidence, or re-run with advanced mode if you need free search.`
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
if (state.discoveryCount >= soft) {
|
|
141
|
+
return {
|
|
142
|
+
allow: true,
|
|
143
|
+
note: `(capsule-first) Prefer the Task Capsule evidence over further navigation. Call inspect_change before multi-file edits. Discovery calls so far: ${state.discoveryCount + 1}.`
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
return { allow: true };
|
|
147
|
+
}
|
|
148
|
+
function recordDiscoveryGateOutcome(name, state, result) {
|
|
149
|
+
if (DISCOVERY_TOOL_NAMES.has(name)) state.discoveryCount++;
|
|
150
|
+
if (result.mutated) state.mutations++;
|
|
151
|
+
if (name === "inspect_change") state.inspectChangeUsed = true;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// src/code/session-graph.ts
|
|
155
|
+
async function materializeSessionGraph(base, overlay, options = {}) {
|
|
156
|
+
return applyOverlayToGraph(base, overlay.snapshot(), {
|
|
157
|
+
parseFile: options.parseFile ?? defaultParse,
|
|
158
|
+
readContent: (rel) => {
|
|
159
|
+
const entry = overlay.snapshot().get(normalize(rel));
|
|
160
|
+
if (!entry) return null;
|
|
161
|
+
if (entry.kind === "delete") return null;
|
|
162
|
+
return entry.content;
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
async function applyOverlayToGraph(base, dirty, options = {}) {
|
|
167
|
+
if (dirty.size === 0) {
|
|
168
|
+
return { graph: base, deleted: [], rewritten: [], reparsed: [] };
|
|
169
|
+
}
|
|
170
|
+
const deleted = [];
|
|
171
|
+
const rewritten = [];
|
|
172
|
+
for (const [file, entry] of [...dirty.entries()].sort(([a], [b]) => a.localeCompare(b))) {
|
|
173
|
+
if (entry.kind === "delete") deleted.push(file);
|
|
174
|
+
else rewritten.push(file);
|
|
175
|
+
}
|
|
176
|
+
const drop = new Set([...deleted, ...rewritten].map(normalize));
|
|
177
|
+
const keptNodes = base.nodes.filter((n) => !drop.has(normalize(n.file)));
|
|
178
|
+
const keptIds = new Set(keptNodes.map((n) => n.id));
|
|
179
|
+
const keptEdges = base.edges.filter((e) => keptIds.has(e.src) && keptIds.has(e.dst));
|
|
180
|
+
const reparsed = [];
|
|
181
|
+
const newNodes = [];
|
|
182
|
+
const newEdges = [];
|
|
183
|
+
const parseFile = options.parseFile;
|
|
184
|
+
const readContent = options.readContent;
|
|
185
|
+
if (parseFile && readContent) {
|
|
186
|
+
for (const file of rewritten) {
|
|
187
|
+
const content = readContent(file);
|
|
188
|
+
if (content === null) continue;
|
|
189
|
+
const lang = langIdFor(file);
|
|
190
|
+
if (!lang) continue;
|
|
191
|
+
try {
|
|
192
|
+
const fp = await parseFile(file, content, lang);
|
|
193
|
+
const resolved = resolve([fp]);
|
|
194
|
+
for (const n of resolved.nodes) {
|
|
195
|
+
newNodes.push(withAnalysisDefaults(n));
|
|
196
|
+
}
|
|
197
|
+
for (const e of resolved.edges) {
|
|
198
|
+
newEdges.push(e);
|
|
199
|
+
}
|
|
200
|
+
reparsed.push(file);
|
|
201
|
+
} catch {
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
const nodes = [...keptNodes, ...newNodes].sort((a, b) => a.id.localeCompare(b.id));
|
|
206
|
+
const edges = [...keptEdges, ...newEdges].sort((a, b) => a.id.localeCompare(b.id));
|
|
207
|
+
const nodeIds = new Set(nodes.map((n) => n.id));
|
|
208
|
+
const edgesFinal = edges.filter((e) => nodeIds.has(e.src) && nodeIds.has(e.dst));
|
|
209
|
+
const graph = {
|
|
210
|
+
...base,
|
|
211
|
+
nodes,
|
|
212
|
+
edges: edgesFinal,
|
|
213
|
+
meta: {
|
|
214
|
+
...base.meta,
|
|
215
|
+
counts: {
|
|
216
|
+
...base.meta.counts,
|
|
217
|
+
nodes: nodes.length,
|
|
218
|
+
edges: edgesFinal.length
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
provenance: {
|
|
222
|
+
...base.provenance,
|
|
223
|
+
// Session view fingerprint — not a corpus commit; consumers must not confuse with disk map.
|
|
224
|
+
corpusHash: `${base.provenance.corpusHash}:session:${deleted.length}:${rewritten.length}:${reparsed.join(",")}`
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
return { graph, deleted, rewritten, reparsed };
|
|
228
|
+
}
|
|
229
|
+
async function defaultParse(rel, source, langId) {
|
|
230
|
+
return parseSource(rel, langId, source);
|
|
231
|
+
}
|
|
232
|
+
function langIdFor(rel) {
|
|
233
|
+
const ext = rel.includes(".") ? rel.slice(rel.lastIndexOf(".")) : "";
|
|
234
|
+
const def = langForExtension(ext);
|
|
235
|
+
return def?.id ?? null;
|
|
236
|
+
}
|
|
237
|
+
function withAnalysisDefaults(n) {
|
|
238
|
+
return {
|
|
239
|
+
...n,
|
|
240
|
+
importance: n.importance ?? 0.5,
|
|
241
|
+
centrality: n.centrality ?? { degree: 0, pagerank: 0, betweenness: 0, eigenvector: 0 },
|
|
242
|
+
area: n.area ?? 0,
|
|
243
|
+
isHub: n.isHub ?? false,
|
|
244
|
+
tested: n.tested ?? false
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
function normalize(file) {
|
|
248
|
+
return file.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// src/code/worktree-overlay.ts
|
|
252
|
+
var DEFAULT_EXTS = /* @__PURE__ */ new Set([
|
|
253
|
+
".ts",
|
|
254
|
+
".tsx",
|
|
255
|
+
".js",
|
|
256
|
+
".jsx",
|
|
257
|
+
".mjs",
|
|
258
|
+
".cjs",
|
|
259
|
+
".py",
|
|
260
|
+
".go",
|
|
261
|
+
".rs",
|
|
262
|
+
".java",
|
|
263
|
+
".cs",
|
|
264
|
+
".rb",
|
|
265
|
+
".kt",
|
|
266
|
+
".swift"
|
|
267
|
+
]);
|
|
268
|
+
function listWorktreeDirtyFiles(root, options = {}) {
|
|
269
|
+
const max = options.maxFiles ?? 80;
|
|
270
|
+
const exts = options.extensions ? new Set(options.extensions) : DEFAULT_EXTS;
|
|
271
|
+
const body = options.porcelain ?? (() => {
|
|
272
|
+
try {
|
|
273
|
+
const res = spawnSync("git", ["status", "--porcelain", "-uall"], {
|
|
274
|
+
cwd: root,
|
|
275
|
+
encoding: "utf8",
|
|
276
|
+
timeout: 15e3
|
|
277
|
+
});
|
|
278
|
+
if (res.status !== 0) return "";
|
|
279
|
+
return res.stdout ?? "";
|
|
280
|
+
} catch {
|
|
281
|
+
return "";
|
|
282
|
+
}
|
|
283
|
+
})();
|
|
284
|
+
const files = /* @__PURE__ */ new Set();
|
|
285
|
+
for (const line of body.split("\n")) {
|
|
286
|
+
if (!line.trim()) continue;
|
|
287
|
+
const ren = /^.. (.+) -> (.+)$/.exec(line);
|
|
288
|
+
const raw = ren ? ren[2] : line.slice(3).trim();
|
|
289
|
+
const rel = raw.replace(/^"|"$/g, "").replace(/\\"/g, '"');
|
|
290
|
+
if (!rel || rel.startsWith(".git/")) continue;
|
|
291
|
+
const ext = path.extname(rel).toLowerCase();
|
|
292
|
+
if (exts.size && !exts.has(ext)) continue;
|
|
293
|
+
files.add(rel.replace(/\\/g, "/"));
|
|
294
|
+
}
|
|
295
|
+
return [...files].sort().slice(0, max);
|
|
296
|
+
}
|
|
297
|
+
function buildWorktreeOverlayEntries(root, dirtyFiles, options = {}) {
|
|
298
|
+
const read = options.readFile ?? ((abs) => {
|
|
299
|
+
try {
|
|
300
|
+
return fs.readFileSync(abs, "utf8");
|
|
301
|
+
} catch {
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
const out = /* @__PURE__ */ new Map();
|
|
306
|
+
for (const rel of dirtyFiles) {
|
|
307
|
+
const abs = path.resolve(root, rel);
|
|
308
|
+
const content = read(abs);
|
|
309
|
+
if (content === null) out.set(normalize2(rel), { kind: "delete" });
|
|
310
|
+
else out.set(normalize2(rel), { kind: "write", content });
|
|
311
|
+
}
|
|
312
|
+
return out;
|
|
313
|
+
}
|
|
314
|
+
async function materializeWorktreeGraph(base, root, options = {}) {
|
|
315
|
+
const dirtyFiles = options.dirtyFiles ?? listWorktreeDirtyFiles(root, options);
|
|
316
|
+
if (!dirtyFiles.length) {
|
|
317
|
+
return { graph: base, deleted: [], rewritten: [], reparsed: [], dirtyFiles: [] };
|
|
318
|
+
}
|
|
319
|
+
const entries = buildWorktreeOverlayEntries(root, dirtyFiles, { readFile: options.readFile });
|
|
320
|
+
const result = await applyOverlayToGraph(base, entries, {
|
|
321
|
+
parseFile: options.parseFile,
|
|
322
|
+
readContent: (rel) => {
|
|
323
|
+
const e = entries.get(normalize2(rel));
|
|
324
|
+
if (!e || e.kind === "delete") return null;
|
|
325
|
+
return e.content;
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
return { ...result, dirtyFiles };
|
|
329
|
+
}
|
|
330
|
+
function normalize2(file) {
|
|
331
|
+
return file.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// src/code/patch-ir.ts
|
|
335
|
+
var PATCH_IR_SCHEMA_VERSION = "patch-ir/0";
|
|
336
|
+
function patchIRToCodeEdits(patch) {
|
|
337
|
+
const out = [];
|
|
338
|
+
for (const op of patch.operations) {
|
|
339
|
+
if (op.op === "replace-text") {
|
|
340
|
+
out.push({
|
|
341
|
+
op: "replace",
|
|
342
|
+
file: op.file,
|
|
343
|
+
search: op.search,
|
|
344
|
+
replace: op.replace,
|
|
345
|
+
anchorSymbol: op.anchorSymbol ?? void 0
|
|
346
|
+
});
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
349
|
+
if (op.op === "create-file") {
|
|
350
|
+
out.push({ op: "create", file: op.file, content: op.content });
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
if (op.op === "delete-file") {
|
|
354
|
+
out.push({ op: "delete", file: op.file });
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
return out;
|
|
359
|
+
}
|
|
360
|
+
function validatePatchIR(patch) {
|
|
361
|
+
const errors = [];
|
|
362
|
+
if (patch.schemaVersion !== PATCH_IR_SCHEMA_VERSION) {
|
|
363
|
+
errors.push(`unsupported schemaVersion: ${String(patch.schemaVersion)}`);
|
|
364
|
+
}
|
|
365
|
+
if (!Array.isArray(patch.operations) || patch.operations.length === 0) {
|
|
366
|
+
errors.push("operations must be a non-empty array");
|
|
367
|
+
} else {
|
|
368
|
+
patch.operations.forEach((op, i) => {
|
|
369
|
+
if (!op || typeof op !== "object" || !("op" in op)) {
|
|
370
|
+
errors.push(`operations[${i}]: missing op`);
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
switch (op.op) {
|
|
374
|
+
case "replace-text":
|
|
375
|
+
if (!op.file?.trim()) errors.push(`operations[${i}]: replace-text requires file`);
|
|
376
|
+
if (typeof op.search !== "string") errors.push(`operations[${i}]: replace-text requires search`);
|
|
377
|
+
if (typeof op.replace !== "string") errors.push(`operations[${i}]: replace-text requires replace`);
|
|
378
|
+
break;
|
|
379
|
+
case "replace-range":
|
|
380
|
+
if (!op.file?.trim()) errors.push(`operations[${i}]: replace-range requires file`);
|
|
381
|
+
if (!Number.isInteger(op.start) || op.start < 1) errors.push(`operations[${i}]: replace-range start must be >= 1`);
|
|
382
|
+
if (!Number.isInteger(op.end) || op.end < op.start) errors.push(`operations[${i}]: replace-range end must be >= start`);
|
|
383
|
+
if (typeof op.replacement !== "string") errors.push(`operations[${i}]: replace-range requires replacement`);
|
|
384
|
+
break;
|
|
385
|
+
case "create-file":
|
|
386
|
+
if (!op.file?.trim()) errors.push(`operations[${i}]: create-file requires file`);
|
|
387
|
+
if (typeof op.content !== "string") errors.push(`operations[${i}]: create-file requires content`);
|
|
388
|
+
break;
|
|
389
|
+
case "delete-file":
|
|
390
|
+
if (!op.file?.trim()) errors.push(`operations[${i}]: delete-file requires file`);
|
|
391
|
+
break;
|
|
392
|
+
case "replace-symbol":
|
|
393
|
+
if (!op.symbolId?.trim()) errors.push(`operations[${i}]: replace-symbol requires symbolId`);
|
|
394
|
+
if (typeof op.replacement !== "string") errors.push(`operations[${i}]: replace-symbol requires replacement`);
|
|
395
|
+
break;
|
|
396
|
+
default:
|
|
397
|
+
errors.push(`operations[${i}]: unknown op`);
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
return { ok: errors.length === 0, errors };
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// src/code/apply-patch-ir.ts
|
|
405
|
+
function applyPatchIR(patch, options) {
|
|
406
|
+
const structural = validatePatchIR(patch);
|
|
407
|
+
if (!structural.ok) {
|
|
408
|
+
return { ok: false, files: /* @__PURE__ */ new Map(), ops: [], errors: structural.errors };
|
|
409
|
+
}
|
|
410
|
+
const spansForFile = options.spansForFile ?? (() => []);
|
|
411
|
+
const locateSymbol = options.locateSymbol ?? (() => null);
|
|
412
|
+
const working = /* @__PURE__ */ new Map();
|
|
413
|
+
const befores = /* @__PURE__ */ new Map();
|
|
414
|
+
const ops = [];
|
|
415
|
+
const errors = [];
|
|
416
|
+
const read = (file) => {
|
|
417
|
+
if (working.has(file)) return working.get(file);
|
|
418
|
+
const before = options.readFile(file);
|
|
419
|
+
befores.set(file, before);
|
|
420
|
+
working.set(file, before);
|
|
421
|
+
return before;
|
|
422
|
+
};
|
|
423
|
+
const write = (file, content) => {
|
|
424
|
+
if (!befores.has(file)) befores.set(file, options.readFile(file));
|
|
425
|
+
working.set(file, content);
|
|
426
|
+
};
|
|
427
|
+
for (const assumption of patch.assumptions) {
|
|
428
|
+
const check = checkAssumption(assumption, (f) => options.readFile(f), locateSymbol);
|
|
429
|
+
if (!check.ok) {
|
|
430
|
+
errors.push(check.reason ?? "assumption failed");
|
|
431
|
+
ops.push({
|
|
432
|
+
index: -1,
|
|
433
|
+
op: "replace-text",
|
|
434
|
+
file: assumption.file ?? "",
|
|
435
|
+
status: "assumption-failed",
|
|
436
|
+
reason: check.reason
|
|
437
|
+
});
|
|
438
|
+
if (options.transactional) {
|
|
439
|
+
return { ok: false, files: /* @__PURE__ */ new Map(), ops, errors };
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
if (options.transactional && errors.length) {
|
|
444
|
+
return { ok: false, files: /* @__PURE__ */ new Map(), ops, errors };
|
|
445
|
+
}
|
|
446
|
+
if (options.transactional) {
|
|
447
|
+
const preview = applyAll(patch.operations, read, write, spansForFile, locateSymbol, ops);
|
|
448
|
+
const failed = preview.some((o) => o.status !== "applied" && o.status !== "no-op");
|
|
449
|
+
if (failed) {
|
|
450
|
+
const failErrors = preview.filter((o) => o.status !== "applied" && o.status !== "no-op").map((o) => o.reason ?? o.status);
|
|
451
|
+
return { ok: false, files: /* @__PURE__ */ new Map(), ops: preview, errors: failErrors };
|
|
452
|
+
}
|
|
453
|
+
} else {
|
|
454
|
+
applyAll(patch.operations, read, write, spansForFile, locateSymbol, ops);
|
|
455
|
+
}
|
|
456
|
+
const files = /* @__PURE__ */ new Map();
|
|
457
|
+
for (const [file, after] of working) {
|
|
458
|
+
files.set(file, { before: befores.get(file) ?? null, after });
|
|
459
|
+
}
|
|
460
|
+
const hardFail = ops.some((o) => o.status === "assumption-failed" || o.status === "unsupported" || o.status === "invalid" || o.status === "not-found" || o.status === "ambiguous" || o.status === "conflict");
|
|
461
|
+
return {
|
|
462
|
+
ok: !hardFail && errors.length === 0,
|
|
463
|
+
files,
|
|
464
|
+
ops,
|
|
465
|
+
errors
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
function applyAll(operations, read, write, spansForFile, locateSymbol, ops) {
|
|
469
|
+
const out = ops;
|
|
470
|
+
out.length = 0;
|
|
471
|
+
for (let i = 0; i < operations.length; i++) {
|
|
472
|
+
const op = operations[i];
|
|
473
|
+
const result = applyOne(op, i, read, write, spansForFile, locateSymbol);
|
|
474
|
+
out.push(result);
|
|
475
|
+
}
|
|
476
|
+
return out;
|
|
477
|
+
}
|
|
478
|
+
function applyOne(op, index, read, write, spansForFile, locateSymbol) {
|
|
479
|
+
if (op.op === "replace-text" || op.op === "create-file" || op.op === "delete-file") {
|
|
480
|
+
const edits = patchIRToCodeEdits({
|
|
481
|
+
operations: [op]});
|
|
482
|
+
const edit = edits[0];
|
|
483
|
+
const file = edit.file;
|
|
484
|
+
const before = read(file);
|
|
485
|
+
const { content, outcome } = applyEdit(before, edit, spansForFile(file));
|
|
486
|
+
if (outcome.status === "applied" || outcome.status === "no-op") {
|
|
487
|
+
write(file, content);
|
|
488
|
+
}
|
|
489
|
+
return { index, op: op.op, file, status: outcome.status, reason: outcome.reason };
|
|
490
|
+
}
|
|
491
|
+
if (op.op === "replace-range") {
|
|
492
|
+
const file = op.file;
|
|
493
|
+
const before = read(file);
|
|
494
|
+
if (before === null) {
|
|
495
|
+
return { index, op: op.op, file, status: "not-found", reason: `${file} does not exist` };
|
|
496
|
+
}
|
|
497
|
+
if (op.expectedHash) {
|
|
498
|
+
const h = hashString(before);
|
|
499
|
+
if (h !== op.expectedHash) {
|
|
500
|
+
return { index, op: op.op, file, status: "conflict", reason: `file hash mismatch for ${file} (stale capsule or concurrent edit)` };
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
const lines = before.split("\n");
|
|
504
|
+
if (op.start < 1 || op.end > lines.length || op.end < op.start) {
|
|
505
|
+
return { index, op: op.op, file, status: "invalid", reason: `replace-range ${op.start}-${op.end} outside file (${lines.length} lines)` };
|
|
506
|
+
}
|
|
507
|
+
const next = [...lines.slice(0, op.start - 1), ...op.replacement.split("\n"), ...lines.slice(op.end)].join("\n");
|
|
508
|
+
write(file, next);
|
|
509
|
+
return { index, op: op.op, file, status: next === before ? "no-op" : "applied" };
|
|
510
|
+
}
|
|
511
|
+
if (op.op === "replace-symbol") {
|
|
512
|
+
const loc = locateSymbol(op.symbolId);
|
|
513
|
+
if (!loc) {
|
|
514
|
+
return { index, op: op.op, file: op.file ?? "", status: "not-found", reason: `symbol ${op.symbolId} not in the map` };
|
|
515
|
+
}
|
|
516
|
+
const file = op.file?.trim() ? op.file : loc.file;
|
|
517
|
+
if (normalize3(file) !== normalize3(loc.file)) {
|
|
518
|
+
return { index, op: op.op, file, status: "conflict", reason: `symbol ${op.symbolId} lives in ${loc.file}, not ${file}` };
|
|
519
|
+
}
|
|
520
|
+
const rangeOp = {
|
|
521
|
+
op: "replace-range",
|
|
522
|
+
file: loc.file,
|
|
523
|
+
start: loc.start,
|
|
524
|
+
end: loc.end,
|
|
525
|
+
replacement: op.replacement,
|
|
526
|
+
expectedHash: op.expectedHash,
|
|
527
|
+
anchorSymbol: op.symbolId
|
|
528
|
+
};
|
|
529
|
+
return applyOne(rangeOp, index, read, write, spansForFile, locateSymbol);
|
|
530
|
+
}
|
|
531
|
+
return { index, op: op.op, file: "", status: "unsupported", reason: "unknown operation" };
|
|
532
|
+
}
|
|
533
|
+
function checkAssumption(a, readFile2, locateSymbol) {
|
|
534
|
+
switch (a.kind) {
|
|
535
|
+
case "file-hash": {
|
|
536
|
+
if (!a.file || !a.expectedHash) return { ok: false, reason: "file-hash assumption needs file and expectedHash" };
|
|
537
|
+
const content = readFile2(a.file);
|
|
538
|
+
if (content === null) return { ok: false, reason: `assumption file-hash: ${a.file} missing` };
|
|
539
|
+
if (hashString(content) !== a.expectedHash) return { ok: false, reason: `assumption file-hash failed for ${a.file}` };
|
|
540
|
+
return { ok: true };
|
|
541
|
+
}
|
|
542
|
+
case "text-present": {
|
|
543
|
+
if (!a.file || !a.detail) return { ok: false, reason: "text-present assumption needs file and detail" };
|
|
544
|
+
const content = readFile2(a.file);
|
|
545
|
+
if (content === null || !content.includes(a.detail)) {
|
|
546
|
+
return { ok: false, reason: `assumption text-present failed for ${a.file}` };
|
|
547
|
+
}
|
|
548
|
+
return { ok: true };
|
|
549
|
+
}
|
|
550
|
+
case "symbol-exists": {
|
|
551
|
+
if (!a.symbolId) return { ok: false, reason: "symbol-exists assumption needs symbolId" };
|
|
552
|
+
if (!locateSymbol(a.symbolId)) return { ok: false, reason: `assumption symbol-exists failed: ${a.symbolId}` };
|
|
553
|
+
return { ok: true };
|
|
554
|
+
}
|
|
555
|
+
case "symbol-span": {
|
|
556
|
+
if (!a.symbolId) return { ok: false, reason: "symbol-span assumption needs symbolId" };
|
|
557
|
+
const loc = locateSymbol(a.symbolId);
|
|
558
|
+
if (!loc) return { ok: false, reason: `assumption symbol-span: ${a.symbolId} missing` };
|
|
559
|
+
if (a.file && normalize3(a.file) !== normalize3(loc.file)) {
|
|
560
|
+
return { ok: false, reason: `assumption symbol-span file mismatch for ${a.symbolId}` };
|
|
561
|
+
}
|
|
562
|
+
return { ok: true };
|
|
563
|
+
}
|
|
564
|
+
case "other":
|
|
565
|
+
return { ok: true };
|
|
566
|
+
default:
|
|
567
|
+
return { ok: false, reason: `unknown assumption kind: ${String(a.kind)}` };
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
function normalize3(file) {
|
|
571
|
+
return file.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
572
|
+
}
|
|
573
|
+
function locatorsFromGraph(nodes) {
|
|
574
|
+
const map = /* @__PURE__ */ new Map();
|
|
575
|
+
for (const n of nodes) {
|
|
576
|
+
map.set(n.id, { id: n.id, file: normalize3(n.file), start: n.span.start, end: n.span.end });
|
|
577
|
+
}
|
|
578
|
+
return (id) => map.get(id) ?? null;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// src/code/secrets.ts
|
|
582
|
+
var SECRET_FILE = new RegExp(
|
|
583
|
+
[
|
|
584
|
+
String.raw`(^|/)\.env(\.[\w.-]+)?$`,
|
|
585
|
+
// .env, .env.local, .env.production
|
|
586
|
+
String.raw`(^|/)\.npmrc$`,
|
|
587
|
+
String.raw`(^|/)\.netrc$`,
|
|
588
|
+
String.raw`(^|/)\.pgpass$`,
|
|
589
|
+
String.raw`(^|/)\.git-credentials$`,
|
|
590
|
+
String.raw`(^|/)id_(rsa|dsa|ecdsa|ed25519)$`,
|
|
591
|
+
String.raw`\.pem$`,
|
|
592
|
+
String.raw`\.key$`,
|
|
593
|
+
String.raw`\.p12$`,
|
|
594
|
+
String.raw`\.pfx$`,
|
|
595
|
+
String.raw`(^|/)credentials(\.json)?$`,
|
|
596
|
+
// aws credentials, gcloud
|
|
597
|
+
String.raw`(^|/)\.aws/credentials$`,
|
|
598
|
+
String.raw`(^|/)secrets?\.(ya?ml|json|toml|env)$`
|
|
599
|
+
].join("|"),
|
|
600
|
+
"i"
|
|
601
|
+
);
|
|
602
|
+
function isSecretPath(file) {
|
|
603
|
+
const normalized = file.replace(/\\/g, "/");
|
|
604
|
+
return SECRET_FILE.test(normalized);
|
|
605
|
+
}
|
|
606
|
+
function secretRefusal(file) {
|
|
607
|
+
return `refusing to read ${file} \u2014 it looks like a secrets/credentials file, and this content would be sent to the model. Reference the values by name instead, or read a non-secret file.`;
|
|
608
|
+
}
|
|
609
|
+
var TOKEN_SHAPES = /\b(sk|pk|rk|xoxb|xoxp|ghp|gho|ghs|ghu|glpat|AKIA|ASIA|AIza|ya29|hf|nvapi|sk-ant|sk-or)[-_][A-Za-z0-9\-_./+]{8,}/g;
|
|
610
|
+
var BEARER = /\b(Authorization|Bearer|token|api[_-]?key)\b\s*[:=]\s*['"]?[A-Za-z0-9\-_./+]{12,}['"]?/gi;
|
|
611
|
+
var ASSIGNMENT = /\b([A-Za-z0-9_]*(?:KEY|TOKEN|SECRET|PASSWORD|PASSWD|PWD|CREDENTIAL|PRIVATE|ACCESS|SESSION)[A-Za-z0-9_]*)\s*([:=])\s*['"]?([^\s'";,]{6,})/gi;
|
|
612
|
+
var URL_AUTH = /\b([a-z][a-z0-9+.-]*:\/\/)([^:/\s]+):([^@/\s]+)@/gi;
|
|
613
|
+
function redactText(text) {
|
|
614
|
+
return text.replace(URL_AUTH, "$1$2:***redacted***@").replace(TOKEN_SHAPES, (_m, prefix) => `${prefix}-***redacted***`).replace(BEARER, (_m, k) => `${k}: ***redacted***`).replace(ASSIGNMENT, (_m, name, op) => `${name}${op}***redacted***`);
|
|
615
|
+
}
|
|
616
|
+
function findSecretShapes(text) {
|
|
617
|
+
const hits = [];
|
|
618
|
+
const scan = (re, kind) => {
|
|
619
|
+
re.lastIndex = 0;
|
|
620
|
+
let m;
|
|
621
|
+
while ((m = re.exec(text)) !== null) {
|
|
622
|
+
hits.push({ kind, index: m.index });
|
|
623
|
+
if (m[0].length === 0) re.lastIndex++;
|
|
624
|
+
}
|
|
625
|
+
};
|
|
626
|
+
scan(TOKEN_SHAPES, "token");
|
|
627
|
+
scan(BEARER, "bearer");
|
|
628
|
+
scan(ASSIGNMENT, "assignment");
|
|
629
|
+
scan(URL_AUTH, "url-auth");
|
|
630
|
+
return hits.sort((a, b) => a.index - b.index || a.kind.localeCompare(b.kind));
|
|
631
|
+
}
|
|
632
|
+
function secretEgressRefusal(text, surface = "network payload") {
|
|
633
|
+
const hits = findSecretShapes(text);
|
|
634
|
+
if (hits.length === 0) return null;
|
|
635
|
+
const kinds = [...new Set(hits.map((h) => h.kind))].sort().join(", ");
|
|
636
|
+
return `refusing to send ${surface} \u2014 detected credential-shaped content (${kinds}). Remove secrets before any network call; path-level secrets files are also blocked.`;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// src/code/safety.ts
|
|
640
|
+
var DANGEROUS = [
|
|
641
|
+
{ pattern: /\brm\s+(-[a-z]*f[a-z]*\s+)?(-[a-z]*r[a-z]*\s+)?(-[a-z]*\s+)*(\/|~|\$HOME|\.)(\s|$)/i, reason: "recursive/forced delete of a root or home path" },
|
|
642
|
+
{ pattern: /\brm\s+-[a-z]*r[a-z]*f|\brm\s+-[a-z]*f[a-z]*r/i, reason: "recursive force delete" },
|
|
643
|
+
{ pattern: /\bmkfs\b|\bmke2fs\b/i, reason: "formats a filesystem" },
|
|
644
|
+
{ pattern: /\bdd\b[^|]*\bof=\/dev\//i, reason: "writes raw to a device" },
|
|
645
|
+
{ pattern: />\s*\/dev\/(sd|nvme|hd|disk)/i, reason: "writes raw to a disk device" },
|
|
646
|
+
{ pattern: /:\s*\(\s*\)\s*\{[^}]*\}\s*;\s*:/, reason: "fork bomb" },
|
|
647
|
+
{ pattern: /\b(curl|wget|fetch)\b[^\n|]*\|\s*(sudo\s+)?(sh|bash|zsh|python|node|ruby|perl)\b/i, reason: "pipes a download straight into a shell/interpreter" },
|
|
648
|
+
{ pattern: /\bgit\s+push\b[^\n]*(--force\b|-f\b)/i, reason: "force-push (rewrites remote history)" },
|
|
649
|
+
{ pattern: /\bgit\s+(reset\s+--hard|clean\s+-[a-z]*f)/i, reason: "destroys uncommitted work" },
|
|
650
|
+
{ pattern: /\b(shutdown|reboot|halt|poweroff)\b/i, reason: "powers off / reboots the machine" },
|
|
651
|
+
{ pattern: /\bchmod\s+-R\s+0*777\s+(\/|~)/i, reason: "world-writable on a root/home path" },
|
|
652
|
+
{ pattern: /\bsudo\b/i, reason: "runs as root (never automatic)" },
|
|
653
|
+
{ pattern: /\beval\b\s+["'`]?\$\(/i, reason: "evaluates dynamically-constructed shell" }
|
|
654
|
+
];
|
|
655
|
+
function dangerousCommand(command, extraDeny = []) {
|
|
656
|
+
const cmd = command.trim();
|
|
657
|
+
for (const { pattern, reason } of DANGEROUS) {
|
|
658
|
+
if (pattern.test(cmd)) return reason;
|
|
659
|
+
}
|
|
660
|
+
for (const raw of extraDeny) {
|
|
661
|
+
if (!raw) continue;
|
|
662
|
+
try {
|
|
663
|
+
if (new RegExp(raw, "i").test(cmd)) return `matches a project denylist rule (${raw})`;
|
|
664
|
+
} catch {
|
|
665
|
+
if (cmd.toLowerCase().includes(raw.toLowerCase())) return `matches a project denylist rule (${raw})`;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
return null;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
// src/code/network-policy.ts
|
|
672
|
+
var NETWORK_POLICY_VERSION = "network-policy@2026.07.0";
|
|
673
|
+
function commandSuggestsNetwork(command) {
|
|
674
|
+
const cmd = command.trim();
|
|
675
|
+
if (!cmd) return false;
|
|
676
|
+
if (/\b(curl|wget|fetch|httpie|aria2c|nc|ncat|netcat|socat|telnet|ftp|sftp|scp|rsync|ssh|nmap|masscan)\b/i.test(
|
|
677
|
+
cmd
|
|
678
|
+
)) {
|
|
679
|
+
return true;
|
|
680
|
+
}
|
|
681
|
+
if (/\b(node|nodejs)\b[^\n]*\b(fetch|https?\.get|http\.request|net\.connect)\b/i.test(cmd)) return true;
|
|
682
|
+
if (/\bpython[23]?\b[^\n]*\b(urllib|requests|http\.client|socket)\b/i.test(cmd)) return true;
|
|
683
|
+
if (/https?:\/\//i.test(cmd)) return true;
|
|
684
|
+
if (/\b(npm|pnpm|yarn|bun)\s+(install|add|update)\b/i.test(cmd) && !/\s--offline\b/i.test(cmd)) {
|
|
685
|
+
if (!/\s(\.\/|\/|\.\.\/)/.test(cmd) && !/\sfile:/i.test(cmd)) return true;
|
|
686
|
+
}
|
|
687
|
+
if (/\bpip[23]?\s+install\b/i.test(cmd) && !/\s--no-index\b/i.test(cmd) && !/\s\.\/|\/|\.\.\//.test(cmd)) {
|
|
688
|
+
return true;
|
|
689
|
+
}
|
|
690
|
+
return false;
|
|
691
|
+
}
|
|
692
|
+
function networkCommandRefusal(command, options = { enforce: true }) {
|
|
693
|
+
if (!options.enforce) return null;
|
|
694
|
+
if (!commandSuggestsNetwork(command)) return null;
|
|
695
|
+
const ver = options.policy?.policyVersion ?? NETWORK_POLICY_VERSION;
|
|
696
|
+
return `network policy ${ver} denies outbound network from agent shell commands (default-deny). Run the command yourself outside the agent, or re-run interactively without --auto if a human should approve it.`;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// src/code/failure-capsule.ts
|
|
700
|
+
var FAILURE_CAPSULE_SCHEMA_VERSION = "failure-capsule/0";
|
|
701
|
+
var FAILURE_CAPSULE_COMPILER_ID = "vg-failure-capsule/0";
|
|
702
|
+
function buildFailureCapsule(options) {
|
|
703
|
+
const maxDiag = options.maxDiagnostic ?? 2500;
|
|
704
|
+
const maxSlices = options.maxSlices ?? 6;
|
|
705
|
+
const failed = pickFailedStep(options);
|
|
706
|
+
const diagnosticExcerpt = redactAndTrim(failed.rawDiagnostic, maxDiag);
|
|
707
|
+
const affectedSymbols = collectAffected(options.capsule, options.changedFiles ?? [], diagnosticExcerpt);
|
|
708
|
+
const sourceSlices = pickSourceSlices(options.capsule, affectedSymbols, maxSlices);
|
|
709
|
+
const likelyCorrections = rankCorrections(failed, diagnosticExcerpt, options.changedFiles ?? []);
|
|
710
|
+
const previousOpsSummary = options.previousOpsSummary ?? (options.changedFiles ?? []).map((f) => `touched ${f}`);
|
|
711
|
+
const capsule = {
|
|
712
|
+
schemaVersion: FAILURE_CAPSULE_SCHEMA_VERSION,
|
|
713
|
+
failedStep: failed.step,
|
|
714
|
+
diagnosticExcerpt,
|
|
715
|
+
affectedSymbols,
|
|
716
|
+
sourceSlices,
|
|
717
|
+
previousOpsSummary,
|
|
718
|
+
graphDeltaNote: options.graphDeltaNote,
|
|
719
|
+
likelyCorrections,
|
|
720
|
+
rendered: "",
|
|
721
|
+
provenance: {
|
|
722
|
+
compiler: FAILURE_CAPSULE_COMPILER_ID,
|
|
723
|
+
modelId: options.modelId ?? null,
|
|
724
|
+
policyVersion: options.policyVersion ?? null
|
|
725
|
+
}
|
|
726
|
+
};
|
|
727
|
+
capsule.rendered = renderFailureCapsule(capsule);
|
|
728
|
+
return capsule;
|
|
729
|
+
}
|
|
730
|
+
function renderFailureCapsule(fc) {
|
|
731
|
+
const lines = [
|
|
732
|
+
"## Failure Capsule",
|
|
733
|
+
`Failed step: ${fc.failedStep.type}${fc.failedStep.command ? ` \`${fc.failedStep.command}\`` : ""}${fc.failedStep.exitCode != null ? ` (exit ${fc.failedStep.exitCode})` : ""}`,
|
|
734
|
+
"",
|
|
735
|
+
"### Diagnostic",
|
|
736
|
+
fc.diagnosticExcerpt || "(no output)"
|
|
737
|
+
];
|
|
738
|
+
if (fc.affectedSymbols.length) {
|
|
739
|
+
lines.push("", "### Affected symbols / files");
|
|
740
|
+
for (const s of fc.affectedSymbols.slice(0, 12)) {
|
|
741
|
+
lines.push(`- ${s.qualifiedName} (${s.file})${s.reason ? ` \u2014 ${s.reason}` : ""}`);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
if (fc.sourceSlices.length) {
|
|
745
|
+
lines.push("", "### Relevant source");
|
|
746
|
+
for (const sl of fc.sourceSlices) {
|
|
747
|
+
lines.push(`\`\`\`${sl.file}:${sl.startLine}-${sl.endLine}`, sl.content, "```");
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
if (fc.previousOpsSummary.length) {
|
|
751
|
+
lines.push("", "### Prior ops", ...fc.previousOpsSummary.map((o) => `- ${o}`));
|
|
752
|
+
}
|
|
753
|
+
if (fc.likelyCorrections.length) {
|
|
754
|
+
lines.push("", "### Likely corrections", ...fc.likelyCorrections.map((c) => `- ${c}`));
|
|
755
|
+
}
|
|
756
|
+
if (fc.graphDeltaNote) {
|
|
757
|
+
lines.push("", `Graph: ${fc.graphDeltaNote}`);
|
|
758
|
+
}
|
|
759
|
+
return lines.join("\n");
|
|
760
|
+
}
|
|
761
|
+
function pickFailedStep(options) {
|
|
762
|
+
if (options.verify && options.verify.exitCode !== 0) {
|
|
763
|
+
return {
|
|
764
|
+
step: {
|
|
765
|
+
type: "verify",
|
|
766
|
+
command: options.verify.command,
|
|
767
|
+
exitCode: options.verify.exitCode
|
|
768
|
+
},
|
|
769
|
+
rawDiagnostic: options.verify.stdout || `exit ${options.verify.exitCode}`
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
const fail = (options.ladderSteps ?? []).find((s) => !s.ok && s.step.kind !== "note");
|
|
773
|
+
if (fail) {
|
|
774
|
+
if (fail.step.kind === "command") {
|
|
775
|
+
return {
|
|
776
|
+
step: { type: "command", command: fail.step.command, detail: fail.message },
|
|
777
|
+
rawDiagnostic: fail.message
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
if (fail.step.kind === "syntax") {
|
|
781
|
+
return {
|
|
782
|
+
step: { type: "syntax", detail: fail.message },
|
|
783
|
+
rawDiagnostic: fail.message
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
return {
|
|
787
|
+
step: { type: "ladder", detail: fail.message },
|
|
788
|
+
rawDiagnostic: fail.message
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
return {
|
|
792
|
+
step: { type: "other", detail: "verification failed" },
|
|
793
|
+
rawDiagnostic: "verification failed (no diagnostic detail)"
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
function collectAffected(capsule, changedFiles, diagnostic) {
|
|
797
|
+
const out = [];
|
|
798
|
+
const seen = /* @__PURE__ */ new Set();
|
|
799
|
+
const push = (qualifiedName, file, reason) => {
|
|
800
|
+
const key = `${qualifiedName}|${file}`;
|
|
801
|
+
if (seen.has(key)) return;
|
|
802
|
+
seen.add(key);
|
|
803
|
+
out.push({ qualifiedName, file, reason });
|
|
804
|
+
};
|
|
805
|
+
for (const f of changedFiles) {
|
|
806
|
+
push(f, f, "modified this session");
|
|
807
|
+
}
|
|
808
|
+
if (capsule) {
|
|
809
|
+
for (const p of capsule.primary) {
|
|
810
|
+
if (changedFiles.includes(p.file) || diagnosticIncludes(diagnostic, p.file) || diagnosticIncludes(diagnostic, p.qualifiedName)) {
|
|
811
|
+
push(p.qualifiedName, p.file, "primary seed");
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
for (const p of capsule.supporting) {
|
|
815
|
+
if (changedFiles.includes(p.file) || diagnosticIncludes(diagnostic, p.file)) {
|
|
816
|
+
push(p.qualifiedName, p.file, "supporting seed");
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
if (!out.length) {
|
|
820
|
+
for (const p of capsule.primary.slice(0, 6)) push(p.qualifiedName, p.file, "primary seed");
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
return out;
|
|
824
|
+
}
|
|
825
|
+
function pickSourceSlices(capsule, affected, maxSlices) {
|
|
826
|
+
if (!capsule?.sourceSlices?.length) return [];
|
|
827
|
+
const files = new Set(affected.map((a) => a.file));
|
|
828
|
+
const preferred = capsule.sourceSlices.filter((s) => files.has(s.file));
|
|
829
|
+
const rest = capsule.sourceSlices.filter((s) => !files.has(s.file));
|
|
830
|
+
const picked = [...preferred, ...rest].slice(0, maxSlices);
|
|
831
|
+
return picked.map((s) => ({
|
|
832
|
+
file: s.file,
|
|
833
|
+
startLine: s.start,
|
|
834
|
+
endLine: s.end,
|
|
835
|
+
content: s.content,
|
|
836
|
+
contentHash: s.contentHash || hashString(s.content)
|
|
837
|
+
}));
|
|
838
|
+
}
|
|
839
|
+
function rankCorrections(failed, diagnostic, changedFiles) {
|
|
840
|
+
const hints = [];
|
|
841
|
+
const d = diagnostic.toLowerCase();
|
|
842
|
+
if (failed.step.type === "syntax") {
|
|
843
|
+
hints.push("Restore missing files or write non-empty content before re-verifying.");
|
|
844
|
+
}
|
|
845
|
+
if (/cannot find module|module not found|enoent/i.test(diagnostic)) {
|
|
846
|
+
hints.push("Check import paths and that new files were created where imports expect them.");
|
|
847
|
+
}
|
|
848
|
+
if (/typeerror|typescript|ts\d{4}/i.test(diagnostic)) {
|
|
849
|
+
hints.push("Fix the reported type error in the touched files; re-run the typecheck command.");
|
|
850
|
+
}
|
|
851
|
+
if (/assert|expect|fail(ed)?/i.test(d) && /test/.test(d)) {
|
|
852
|
+
hints.push("Align the implementation with the failing assertion; avoid changing the test unless the task requires it.");
|
|
853
|
+
}
|
|
854
|
+
if (/syntaxerror|unexpected token|parse error/i.test(diagnostic)) {
|
|
855
|
+
hints.push("Repair syntax around the last edit; re-read the file span before patching again.");
|
|
856
|
+
}
|
|
857
|
+
if (changedFiles.length) {
|
|
858
|
+
hints.push(`Re-read and patch: ${changedFiles.slice(0, 5).join(", ")}.`);
|
|
859
|
+
}
|
|
860
|
+
if (!hints.length) {
|
|
861
|
+
hints.push("Inspect the diagnostic excerpt, re-read the affected files, apply a minimal fix, then verify again.");
|
|
862
|
+
}
|
|
863
|
+
return hints.slice(0, 6);
|
|
864
|
+
}
|
|
865
|
+
function diagnosticIncludes(diagnostic, needle) {
|
|
866
|
+
if (!needle) return false;
|
|
867
|
+
return diagnostic.toLowerCase().includes(needle.toLowerCase());
|
|
868
|
+
}
|
|
869
|
+
function redactAndTrim(raw, max) {
|
|
870
|
+
let s = raw.replace(/(api[_-]?key|token|secret|password)\s*[:=]\s*\S+/gi, "$1=[REDACTED]").replace(/\b(sk|ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{8,}\b/g, "[REDACTED]").replace(/\bBearer\s+[A-Za-z0-9._\-]+\b/gi, "Bearer [REDACTED]");
|
|
871
|
+
if (s.length > max) {
|
|
872
|
+
const lines = s.split("\n");
|
|
873
|
+
const errorish = lines.filter((l) => /error|fail|assert|exception|enoent|cannot/i.test(l));
|
|
874
|
+
const head = (errorish.length ? errorish : lines).slice(0, 40).join("\n");
|
|
875
|
+
s = head.length > max ? head.slice(0, max) + "\n\u2026 (truncated)" : head + (lines.length > 40 ? "\n\u2026 (truncated)" : "");
|
|
876
|
+
}
|
|
877
|
+
return s.trim();
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
// src/code/verify-ladder.ts
|
|
881
|
+
function compileVerificationLadder(plan, options = {}) {
|
|
882
|
+
const steps = [];
|
|
883
|
+
const maxSyntax = options.maxSyntaxFiles ?? 20;
|
|
884
|
+
for (const note of plan.notes) {
|
|
885
|
+
steps.push({ kind: "note", detail: note });
|
|
886
|
+
}
|
|
887
|
+
const syntaxFiles = [...new Set(plan.syntaxFiles.map(normalize4))].filter(Boolean).sort().slice(0, maxSyntax);
|
|
888
|
+
if (syntaxFiles.length) {
|
|
889
|
+
steps.push({
|
|
890
|
+
kind: "syntax",
|
|
891
|
+
files: syntaxFiles,
|
|
892
|
+
detail: `check ${syntaxFiles.length} target file(s) exist after edit`
|
|
893
|
+
});
|
|
894
|
+
}
|
|
895
|
+
const commands = /* @__PURE__ */ new Set();
|
|
896
|
+
for (const t of options.patchTargets ?? []) {
|
|
897
|
+
if (t.kind === "command" || t.kind === "test" || t.kind === "build" || t.kind === "typecheck") {
|
|
898
|
+
if (t.target.trim()) commands.add(t.target.trim());
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
if (options.testCommand?.trim()) commands.add(options.testCommand.trim());
|
|
902
|
+
for (const command of [...commands].sort()) {
|
|
903
|
+
steps.push({ kind: "command", command, detail: `run \`${command}\`` });
|
|
904
|
+
}
|
|
905
|
+
if (plan.suggestedTests.length) {
|
|
906
|
+
steps.push({
|
|
907
|
+
kind: "note",
|
|
908
|
+
detail: `graph-linked tests to prefer: ${plan.suggestedTests.slice(0, 8).join(", ")}`
|
|
909
|
+
});
|
|
910
|
+
}
|
|
911
|
+
return steps;
|
|
912
|
+
}
|
|
913
|
+
function runVerificationLadder(steps, options, failFast = true) {
|
|
914
|
+
const results = [];
|
|
915
|
+
let ranCommands = false;
|
|
916
|
+
let ok = true;
|
|
917
|
+
for (const step of steps) {
|
|
918
|
+
const result = runStep(step, options);
|
|
919
|
+
if (result.step.kind === "command" && options.runCommands !== false) ranCommands = true;
|
|
920
|
+
results.push(result);
|
|
921
|
+
if (!result.ok) {
|
|
922
|
+
ok = false;
|
|
923
|
+
if (failFast && step.kind !== "note") break;
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
return { ok, steps: results, ranCommands };
|
|
927
|
+
}
|
|
928
|
+
function runStep(step, options) {
|
|
929
|
+
if (step.kind === "note") {
|
|
930
|
+
return { step, ok: true, message: step.detail };
|
|
931
|
+
}
|
|
932
|
+
if (step.kind === "syntax") {
|
|
933
|
+
const missing = [];
|
|
934
|
+
const empty = [];
|
|
935
|
+
for (const file of step.files) {
|
|
936
|
+
const content = options.readFile(file);
|
|
937
|
+
if (content === null) missing.push(file);
|
|
938
|
+
else if (content.length === 0) empty.push(file);
|
|
939
|
+
}
|
|
940
|
+
if (missing.length || empty.length) {
|
|
941
|
+
const parts = [];
|
|
942
|
+
if (missing.length) parts.push(`missing: ${missing.join(", ")}`);
|
|
943
|
+
if (empty.length) parts.push(`empty: ${empty.join(", ")}`);
|
|
944
|
+
return { step, ok: false, message: `syntax check failed \u2014 ${parts.join("; ")}` };
|
|
945
|
+
}
|
|
946
|
+
return { step, ok: true, message: `syntax ok \u2014 ${step.files.length} file(s) present` };
|
|
947
|
+
}
|
|
948
|
+
if (options.runCommands === false || !options.run) {
|
|
949
|
+
return { step, ok: true, message: `skipped command (not configured): ${step.command}` };
|
|
950
|
+
}
|
|
951
|
+
const res = options.run(step.command);
|
|
952
|
+
if (res.exitCode === 0) {
|
|
953
|
+
return { step, ok: true, message: `\`${step.command}\` exit 0` };
|
|
954
|
+
}
|
|
955
|
+
const tail = (res.stdout ?? "").slice(0, 2e3);
|
|
956
|
+
return {
|
|
957
|
+
step,
|
|
958
|
+
ok: false,
|
|
959
|
+
message: `\`${step.command}\` exit ${res.exitCode}${tail ? `:
|
|
960
|
+
${tail}` : ""}`
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
function normalize4(file) {
|
|
964
|
+
return file.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
// src/code/inspect-change.ts
|
|
968
|
+
function inspectChange(graph, input = {}) {
|
|
969
|
+
const depth = input.depth ?? 3;
|
|
970
|
+
const maxRoots = input.maxRoots ?? 12;
|
|
971
|
+
const maxAffected = input.maxAffected ?? 40;
|
|
972
|
+
const filesTouched = [...new Set((input.files ?? []).map(normalize5).filter(Boolean))].sort();
|
|
973
|
+
const symbolHints = [...new Set((input.symbols ?? []).map((s) => s.trim()).filter(Boolean))];
|
|
974
|
+
const roots = collectRoots(graph, symbolHints, filesTouched).slice(0, maxRoots);
|
|
975
|
+
const affectedMap = /* @__PURE__ */ new Map();
|
|
976
|
+
let direct = 0;
|
|
977
|
+
let transitive = 0;
|
|
978
|
+
for (const root of roots) {
|
|
979
|
+
const imp = impactOf(graph, root.id, { depth });
|
|
980
|
+
direct += imp.direct;
|
|
981
|
+
transitive += imp.transitive;
|
|
982
|
+
for (const a of imp.affected) {
|
|
983
|
+
const prev = affectedMap.get(a.id);
|
|
984
|
+
if (!prev || a.depth < prev.depth || a.depth === prev.depth && a.confidence > prev.confidence) {
|
|
985
|
+
affectedMap.set(a.id, {
|
|
986
|
+
id: a.id,
|
|
987
|
+
name: a.name,
|
|
988
|
+
kind: a.kind,
|
|
989
|
+
file: a.file,
|
|
990
|
+
line: a.line,
|
|
991
|
+
depth: a.depth,
|
|
992
|
+
confidence: a.confidence,
|
|
993
|
+
viaRoot: root.qualifiedName
|
|
994
|
+
});
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
const affected = [...affectedMap.values()].sort((a, b) => a.depth - b.depth || b.confidence - a.confidence || a.name.localeCompare(b.name)).slice(0, maxAffected);
|
|
999
|
+
const report = {
|
|
1000
|
+
roots,
|
|
1001
|
+
affected,
|
|
1002
|
+
direct,
|
|
1003
|
+
transitive,
|
|
1004
|
+
filesTouched,
|
|
1005
|
+
rendered: ""
|
|
1006
|
+
};
|
|
1007
|
+
report.rendered = renderInspectChange(report);
|
|
1008
|
+
return report;
|
|
1009
|
+
}
|
|
1010
|
+
function collectRoots(graph, symbols, files) {
|
|
1011
|
+
const out = [];
|
|
1012
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1013
|
+
const push = (id) => {
|
|
1014
|
+
if (seen.has(id)) return;
|
|
1015
|
+
const node = graph.nodes.find((n) => n.id === id);
|
|
1016
|
+
if (!node || node.kind === "file" || node.kind === "external") return;
|
|
1017
|
+
seen.add(id);
|
|
1018
|
+
out.push({
|
|
1019
|
+
id: node.id,
|
|
1020
|
+
qualifiedName: node.qualifiedName,
|
|
1021
|
+
kind: node.kind,
|
|
1022
|
+
file: node.file,
|
|
1023
|
+
line: node.span?.start ?? 0
|
|
1024
|
+
});
|
|
1025
|
+
};
|
|
1026
|
+
for (const s of symbols) {
|
|
1027
|
+
if (graph.nodes.some((n) => n.id === s)) {
|
|
1028
|
+
push(s);
|
|
1029
|
+
continue;
|
|
1030
|
+
}
|
|
1031
|
+
const hit = resolveOne(graph, s);
|
|
1032
|
+
if (hit.node) push(hit.node.id);
|
|
1033
|
+
else if (hit.candidates.length === 1 && hit.candidates[0]) push(hit.candidates[0].id);
|
|
1034
|
+
}
|
|
1035
|
+
for (const file of files) {
|
|
1036
|
+
for (const n of graph.nodes) {
|
|
1037
|
+
if (n.kind === "file" || n.kind === "external") continue;
|
|
1038
|
+
if (normalize5(n.file) === file) push(n.id);
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
return out.sort((a, b) => a.file.localeCompare(b.file) || a.qualifiedName.localeCompare(b.qualifiedName));
|
|
1042
|
+
}
|
|
1043
|
+
function renderInspectChange(r) {
|
|
1044
|
+
const lines = ["## Inspect change (blast radius)"];
|
|
1045
|
+
if (r.filesTouched.length) lines.push(`Files: ${r.filesTouched.join(", ")}`);
|
|
1046
|
+
if (!r.roots.length) {
|
|
1047
|
+
lines.push("No graph symbols resolved for this change \u2014 impact unknown.");
|
|
1048
|
+
return lines.join("\n");
|
|
1049
|
+
}
|
|
1050
|
+
lines.push(`Roots (${r.roots.length}):`);
|
|
1051
|
+
for (const root of r.roots) {
|
|
1052
|
+
lines.push(`- ${root.qualifiedName} (${root.kind}) ${root.file}:${root.line}`);
|
|
1053
|
+
}
|
|
1054
|
+
lines.push(`Affected: ${r.affected.length} (direct\u2248${r.direct}, transitive\u2248${r.transitive})`);
|
|
1055
|
+
if (!r.affected.length) {
|
|
1056
|
+
lines.push("Nothing depends on these symbols (safe to change in isolation).");
|
|
1057
|
+
} else {
|
|
1058
|
+
for (const a of r.affected.slice(0, 30)) {
|
|
1059
|
+
lines.push(`- ${a.name} (${a.file}:${a.line}) d=${a.depth} via ${a.viaRoot}`);
|
|
1060
|
+
}
|
|
1061
|
+
if (r.affected.length > 30) lines.push(`\u2026 +${r.affected.length - 30} more`);
|
|
1062
|
+
}
|
|
1063
|
+
return lines.join("\n");
|
|
1064
|
+
}
|
|
1065
|
+
function normalize5(file) {
|
|
1066
|
+
return file.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
// src/code/identifier-enforce.ts
|
|
1070
|
+
function stripNonCodeSpans(text) {
|
|
1071
|
+
return text.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/\/\/[^\n]*/g, " ").replace(/(^|[^:])#[^\n]*/gm, "$1 ").replace(/'(?:\\.|[^'\\])*'/g, " ").replace(/"(?:\\.|[^"\\])*"/g, " ").replace(/`(?:\\.|[^`\\])*`/g, " ");
|
|
1072
|
+
}
|
|
1073
|
+
function enforceIdentifiersInText(text, trie, options = {}) {
|
|
1074
|
+
if (!trie || text == null || text === "") return { ok: true, unknown: [] };
|
|
1075
|
+
const codeOnly = options.codeOnly !== false;
|
|
1076
|
+
const scanned = codeOnly ? stripNonCodeSpans(text) : text;
|
|
1077
|
+
const scan = scanIdentifiersAgainstTrie(scanned, trie);
|
|
1078
|
+
const allow = new Set(options.allow ?? []);
|
|
1079
|
+
const unknown = scan.unknown.filter((id) => !allow.has(id));
|
|
1080
|
+
if (unknown.length === 0) return { ok: true, unknown: [] };
|
|
1081
|
+
return {
|
|
1082
|
+
ok: false,
|
|
1083
|
+
unknown,
|
|
1084
|
+
reason: `blocked: ${unknown.length} identifier(s) not in the code graph: ${unknown.slice(0, 16).join(", ")}${unknown.length > 16 ? "\u2026" : ""}. Use only symbols from inspect_task / the graph, or add them before inventing names.`
|
|
1085
|
+
};
|
|
1086
|
+
}
|
|
1087
|
+
function bodyFromOp(op) {
|
|
1088
|
+
switch (op.op) {
|
|
1089
|
+
case "replace-range":
|
|
1090
|
+
return op.replacement;
|
|
1091
|
+
case "replace-text":
|
|
1092
|
+
return op.replace;
|
|
1093
|
+
case "create-file":
|
|
1094
|
+
return op.content;
|
|
1095
|
+
case "replace-symbol":
|
|
1096
|
+
return op.replacement;
|
|
1097
|
+
case "delete-file":
|
|
1098
|
+
return null;
|
|
1099
|
+
default:
|
|
1100
|
+
return null;
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
function fileFromOp(op) {
|
|
1104
|
+
if ("file" in op && typeof op.file === "string") return op.file;
|
|
1105
|
+
return null;
|
|
1106
|
+
}
|
|
1107
|
+
function enforceIdentifiersInPatch(patch, trie, options = {}) {
|
|
1108
|
+
if (!trie) return { ok: true, unknown: [] };
|
|
1109
|
+
const unknown = /* @__PURE__ */ new Set();
|
|
1110
|
+
for (const op of patch.operations) {
|
|
1111
|
+
const body = bodyFromOp(op);
|
|
1112
|
+
if (!body) continue;
|
|
1113
|
+
const allow = new Set(options.allow ?? []);
|
|
1114
|
+
const file = fileFromOp(op);
|
|
1115
|
+
if (file && options.readFile) {
|
|
1116
|
+
const existing = options.readFile(file);
|
|
1117
|
+
if (existing) {
|
|
1118
|
+
for (const id of extractIdentifiers(existing)) allow.add(id);
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
if (op.op === "replace-text" && typeof op.search === "string") {
|
|
1122
|
+
for (const id of extractIdentifiers(op.search)) allow.add(id);
|
|
1123
|
+
}
|
|
1124
|
+
const r = enforceIdentifiersInText(body, trie, { ...options, allow });
|
|
1125
|
+
for (const u of r.unknown) unknown.add(u);
|
|
1126
|
+
}
|
|
1127
|
+
if (unknown.size === 0) return { ok: true, unknown: [] };
|
|
1128
|
+
const list = [...unknown].sort();
|
|
1129
|
+
return {
|
|
1130
|
+
ok: false,
|
|
1131
|
+
unknown: list,
|
|
1132
|
+
reason: `blocked apply_patch: ${list.length} identifier(s) not in the code graph: ${list.slice(0, 16).join(", ")}${list.length > 16 ? "\u2026" : ""}. Do not invent symbols \u2014 use graph names only.`
|
|
1133
|
+
};
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
// src/code/tools.ts
|
|
1137
|
+
var MAX_OUTPUT = 12e3;
|
|
1138
|
+
var AGENT_TOOLS = [
|
|
1139
|
+
{
|
|
1140
|
+
name: "search_code",
|
|
1141
|
+
description: "Search the codebase by concept or identifier using the deterministic code graph (preferred over reading files blindly). Returns the most relevant symbols with file:line.",
|
|
1142
|
+
parameters: { type: "object", properties: { query: { type: "string", description: 'What to find, e.g. "where auth failures are handled"' } }, required: ["query"] }
|
|
1143
|
+
},
|
|
1144
|
+
{
|
|
1145
|
+
name: "read_file",
|
|
1146
|
+
description: "Read a file (optionally a line range). Use after search_code to see the exact code.",
|
|
1147
|
+
parameters: { type: "object", properties: { path: { type: "string" }, start_line: { type: "number" }, end_line: { type: "number" } }, required: ["path"] }
|
|
1148
|
+
},
|
|
1149
|
+
{
|
|
1150
|
+
name: "list_files",
|
|
1151
|
+
description: "List files known to the code map, optionally filtered by directory prefix and a simple substring/extension pattern.",
|
|
1152
|
+
parameters: { type: "object", properties: { dir: { type: "string" }, pattern: { type: "string" } } }
|
|
1153
|
+
},
|
|
1154
|
+
{
|
|
1155
|
+
name: "graph_impact",
|
|
1156
|
+
description: "What depends on a symbol \u2014 the blast radius of changing it (callers/importers/subtypes). Use before editing shared code.",
|
|
1157
|
+
parameters: { type: "object", properties: { symbol: { type: "string" } }, required: ["symbol"] }
|
|
1158
|
+
},
|
|
1159
|
+
{
|
|
1160
|
+
name: "library_docs",
|
|
1161
|
+
description: "Get version-correct documentation for a dependency this project actually uses (from the installed package), so you use the right API for the installed version. Prefer this over guessing a library's API.",
|
|
1162
|
+
parameters: { type: "object", properties: { name: { type: "string", description: 'package name, e.g. "react" or "zod"' } }, required: ["name"] }
|
|
1163
|
+
},
|
|
1164
|
+
{
|
|
1165
|
+
name: "edit_file",
|
|
1166
|
+
description: "Edit a file by replacing an exact snippet. The SEARCH text must match current file contents (whitespace-flexible). Requires approval.",
|
|
1167
|
+
parameters: { type: "object", properties: { path: { type: "string" }, search: { type: "string" }, replace: { type: "string" } }, required: ["path", "search", "replace"] }
|
|
1168
|
+
},
|
|
1169
|
+
{
|
|
1170
|
+
name: "create_file",
|
|
1171
|
+
description: "Create a new file with the given contents. Requires approval.",
|
|
1172
|
+
parameters: { type: "object", properties: { path: { type: "string" }, content: { type: "string" } }, required: ["path", "content"] }
|
|
1173
|
+
},
|
|
1174
|
+
{
|
|
1175
|
+
name: "delete_file",
|
|
1176
|
+
description: "Delete a file. Requires approval.",
|
|
1177
|
+
parameters: { type: "object", properties: { path: { type: "string" } }, required: ["path"] }
|
|
1178
|
+
},
|
|
1179
|
+
{
|
|
1180
|
+
name: "apply_patch",
|
|
1181
|
+
description: "Apply a validated PatchIR (patch-ir/0) multi-op edit transactionally. Prefer for multi-file changes with assumptions. JSON object with operations[], optional assumptions[]. Requires approval per file change.",
|
|
1182
|
+
parameters: {
|
|
1183
|
+
type: "object",
|
|
1184
|
+
properties: {
|
|
1185
|
+
patch: {
|
|
1186
|
+
type: "object",
|
|
1187
|
+
description: "PatchIR document: { operations: [...], assumptions?: [...] } (schemaVersion optional, defaults to patch-ir/0)"
|
|
1188
|
+
}
|
|
1189
|
+
},
|
|
1190
|
+
required: ["patch"]
|
|
1191
|
+
}
|
|
1192
|
+
},
|
|
1193
|
+
{
|
|
1194
|
+
name: "run_command",
|
|
1195
|
+
description: "Run a shell command (e.g. the test or build command) and read its output. Requires approval.",
|
|
1196
|
+
parameters: { type: "object", properties: { command: { type: "string" } }, required: ["command"] }
|
|
1197
|
+
},
|
|
1198
|
+
{
|
|
1199
|
+
name: "inspect_task",
|
|
1200
|
+
description: "Return (or refresh) the current Task Capsule summary \u2014 primary symbols, source files, and verification plan. Prefer this over re-searching when capsule context is already available.",
|
|
1201
|
+
parameters: { type: "object", properties: {} }
|
|
1202
|
+
},
|
|
1203
|
+
{
|
|
1204
|
+
name: "inspect_change",
|
|
1205
|
+
description: "Blast radius of a proposed change before (or after) editing \u2014 what depends on the symbols/files you will touch. Prefer before multi-file edits. Does not write files.",
|
|
1206
|
+
parameters: {
|
|
1207
|
+
type: "object",
|
|
1208
|
+
properties: {
|
|
1209
|
+
symbols: {
|
|
1210
|
+
type: "array",
|
|
1211
|
+
items: { type: "string" },
|
|
1212
|
+
description: "Symbol ids or qualified names under change"
|
|
1213
|
+
},
|
|
1214
|
+
files: {
|
|
1215
|
+
type: "array",
|
|
1216
|
+
items: { type: "string" },
|
|
1217
|
+
description: "Relative file paths under change (defaults to session dirty/changed files)"
|
|
1218
|
+
},
|
|
1219
|
+
depth: { type: "number", description: "Impact depth (default 3)" }
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
},
|
|
1223
|
+
{
|
|
1224
|
+
name: "verify_change",
|
|
1225
|
+
description: "Run the graph-derived verification ladder (syntax + optional test command). On failure returns a Failure Capsule for focused repair. Read-only regarding files; may run the project test command.",
|
|
1226
|
+
parameters: {
|
|
1227
|
+
type: "object",
|
|
1228
|
+
properties: {
|
|
1229
|
+
command: { type: "string", description: "Optional verify command override (default: project test command from capsule plan)" }
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
},
|
|
1233
|
+
{
|
|
1234
|
+
name: "finish",
|
|
1235
|
+
description: "Finish the task. Call this when the change is complete, with a short summary of what you did.",
|
|
1236
|
+
parameters: { type: "object", properties: { summary: { type: "string" } }, required: ["summary"] }
|
|
1237
|
+
},
|
|
1238
|
+
{
|
|
1239
|
+
name: "abort",
|
|
1240
|
+
description: "Stop the task without further edits. Use when the request is impossible or the user intent is unclear.",
|
|
1241
|
+
parameters: {
|
|
1242
|
+
type: "object",
|
|
1243
|
+
properties: { reason: { type: "string", description: "Why the task is aborted" } },
|
|
1244
|
+
required: ["reason"]
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
];
|
|
1248
|
+
async function executeTool(call, ctx) {
|
|
1249
|
+
const a = call.arguments;
|
|
1250
|
+
try {
|
|
1251
|
+
switch (call.name) {
|
|
1252
|
+
case "search_code":
|
|
1253
|
+
return await search(ctx, str(a.query));
|
|
1254
|
+
case "read_file":
|
|
1255
|
+
return readFile(ctx, str(a.path), num(a.start_line), num(a.end_line));
|
|
1256
|
+
case "list_files":
|
|
1257
|
+
return listFiles(ctx, str(a.dir), str(a.pattern));
|
|
1258
|
+
case "graph_impact":
|
|
1259
|
+
return await graphImpact(ctx, str(a.symbol));
|
|
1260
|
+
case "library_docs":
|
|
1261
|
+
return libraryDocs(ctx, str(a.name));
|
|
1262
|
+
case "edit_file":
|
|
1263
|
+
return editFile(ctx, str(a.path), str(a.search), str(a.replace));
|
|
1264
|
+
case "create_file":
|
|
1265
|
+
return createFile(ctx, str(a.path), str(a.content));
|
|
1266
|
+
case "delete_file":
|
|
1267
|
+
return deleteFile(ctx, str(a.path));
|
|
1268
|
+
case "apply_patch":
|
|
1269
|
+
return applyPatchTool(ctx, a.patch);
|
|
1270
|
+
case "run_command":
|
|
1271
|
+
return runCommand(ctx, str(a.command));
|
|
1272
|
+
case "inspect_task":
|
|
1273
|
+
return inspectTask(ctx);
|
|
1274
|
+
case "inspect_change":
|
|
1275
|
+
return inspectChangeTool(ctx, a);
|
|
1276
|
+
case "verify_change":
|
|
1277
|
+
return verifyChange(ctx, str(a.command) || void 0);
|
|
1278
|
+
case "finish":
|
|
1279
|
+
return { content: "done", mutated: false, finished: true, finalSummary: str(a.summary) || "done" };
|
|
1280
|
+
case "abort":
|
|
1281
|
+
return {
|
|
1282
|
+
content: `aborted: ${str(a.reason) || "no reason given"}`,
|
|
1283
|
+
mutated: false,
|
|
1284
|
+
finished: true,
|
|
1285
|
+
finalSummary: `aborted: ${str(a.reason) || "no reason given"}`
|
|
1286
|
+
};
|
|
1287
|
+
default:
|
|
1288
|
+
return { content: `unknown tool "${call.name}". Available: ${AGENT_TOOLS.map((t) => t.name).join(", ")}`, mutated: false };
|
|
1289
|
+
}
|
|
1290
|
+
} catch (e) {
|
|
1291
|
+
return { content: `tool ${call.name} failed: ${e.message}`, mutated: false };
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
async function search(ctx, query) {
|
|
1295
|
+
if (!query) return { content: "search_code needs a query", mutated: false };
|
|
1296
|
+
const backend = ctx.graphBackend ?? localGraphBackend(ctx.graph);
|
|
1297
|
+
const res = await backend.search(query, { limit: 10 });
|
|
1298
|
+
if (res.matches.length === 0) return { content: `no symbols matched "${query}"`, mutated: false };
|
|
1299
|
+
const lines = res.matches.map(
|
|
1300
|
+
(m) => `- ${m.qualifiedName} (${m.kind}) ${m.file}:${m.line}${m.signature ? ` ${m.signature}` : ""}`
|
|
1301
|
+
);
|
|
1302
|
+
const via = res.source === "vgd" ? " via vgd" : "";
|
|
1303
|
+
return { content: `Matches for "${query}"${via}:
|
|
1304
|
+
${lines.join("\n")}`, mutated: false };
|
|
1305
|
+
}
|
|
1306
|
+
function readFile(ctx, path2, start, end) {
|
|
1307
|
+
if (isSecretPath(path2)) return { content: secretRefusal(path2), mutated: false };
|
|
1308
|
+
const content = ctx.fsImpl.read(path2);
|
|
1309
|
+
if (content === null) return { content: `${path2} not found`, mutated: false };
|
|
1310
|
+
const lines = content.split("\n");
|
|
1311
|
+
const from = start && start > 0 ? start - 1 : 0;
|
|
1312
|
+
const to = end && end > 0 ? end : lines.length;
|
|
1313
|
+
const slice = lines.slice(from, to).join("\n");
|
|
1314
|
+
const shown = truncate(redactText(slice));
|
|
1315
|
+
const header = start || end ? `${path2} (lines ${from + 1}-${Math.min(to, lines.length)} of ${lines.length})` : `${path2} (${lines.length} lines)`;
|
|
1316
|
+
return { content: `${header}:
|
|
1317
|
+
${shown}`, mutated: false };
|
|
1318
|
+
}
|
|
1319
|
+
function listFiles(ctx, dir, pattern) {
|
|
1320
|
+
const prefix = dir ? dir.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/$/, "") : "";
|
|
1321
|
+
const files = [...new Set(ctx.graph.nodes.filter((n) => n.kind === "file").map((n) => n.file))].filter((f) => !isSecretPath(f)).filter((f) => prefix ? f === prefix || f.startsWith(prefix + "/") : true).filter((f) => pattern ? f.includes(pattern) || f.endsWith(pattern) : true).sort();
|
|
1322
|
+
if (files.length === 0) return { content: `no files${dir ? ` under ${dir}` : ""}${pattern ? ` matching "${pattern}"` : ""}`, mutated: false };
|
|
1323
|
+
return { content: `${files.length} file(s):
|
|
1324
|
+
${files.slice(0, 200).join("\n")}`, mutated: false };
|
|
1325
|
+
}
|
|
1326
|
+
function libraryDocs(ctx, name) {
|
|
1327
|
+
if (!name) return { content: "library_docs needs a package name", mutated: false };
|
|
1328
|
+
const entry = resolveLib(loadCatalog(ctx.root), name);
|
|
1329
|
+
if (entry) {
|
|
1330
|
+
const doc = readDoc(ctx.root, entry);
|
|
1331
|
+
if (doc.trim()) return { content: `Docs for ${name}${entry.version && entry.version !== "*" ? ` @ ${entry.version}` : ""} (${entry.source.type}):
|
|
1332
|
+
${truncate(redactText(doc))}`, mutated: false };
|
|
1333
|
+
}
|
|
1334
|
+
const local = localPackageDocs(ctx.root, name);
|
|
1335
|
+
if (local?.docs.trim()) {
|
|
1336
|
+
return { content: `Docs for ${name}${local.version ? ` @ ${local.version}` : ""} (${local.source}):
|
|
1337
|
+
${truncate(redactText(local.docs))}`, mutated: false };
|
|
1338
|
+
}
|
|
1339
|
+
const v = resolveVersion(ctx.root, name);
|
|
1340
|
+
return {
|
|
1341
|
+
content: `no bundled docs for ${name}${v.served ? ` (installed ${v.served})` : " (not found in this project)"}. Read its source under node_modules/${name}, or add curated docs with \`vg lib add\`.`,
|
|
1342
|
+
mutated: false
|
|
1343
|
+
};
|
|
1344
|
+
}
|
|
1345
|
+
async function graphImpact(ctx, symbol) {
|
|
1346
|
+
const backend = ctx.graphBackend ?? localGraphBackend(ctx.graph);
|
|
1347
|
+
const impact = await backend.impact(symbol, { depth: 3 });
|
|
1348
|
+
if (!impact) return { content: `no symbol named "${symbol}" in the map`, mutated: false };
|
|
1349
|
+
if (impact.affected.length === 0) {
|
|
1350
|
+
return {
|
|
1351
|
+
content: `nothing depends on ${impact.root.name} (safe to change in isolation)`,
|
|
1352
|
+
mutated: false
|
|
1353
|
+
};
|
|
1354
|
+
}
|
|
1355
|
+
const lines = impact.affected.slice(0, 20).map((i) => `- ${i.name} (${i.file}:${i.line})`);
|
|
1356
|
+
const via = impact.source === "vgd" ? " via vgd" : "";
|
|
1357
|
+
return {
|
|
1358
|
+
content: `${impact.affected.length} symbol(s) depend on ${impact.root.name}${via}:
|
|
1359
|
+
${lines.join("\n")}`,
|
|
1360
|
+
mutated: false
|
|
1361
|
+
};
|
|
1362
|
+
}
|
|
1363
|
+
async function editFile(ctx, path2, search2, replace) {
|
|
1364
|
+
const before = ctx.fsImpl.read(path2);
|
|
1365
|
+
if (ctx.enforceIdentifiers !== false && ctx.identifierTrie) {
|
|
1366
|
+
const allow = /* @__PURE__ */ new Set([
|
|
1367
|
+
...extractIdentifiers(before ?? ""),
|
|
1368
|
+
...extractIdentifiers(search2)
|
|
1369
|
+
]);
|
|
1370
|
+
const idCheck = enforceIdentifiersInText(replace, ctx.identifierTrie, { allow });
|
|
1371
|
+
if (!idCheck.ok) {
|
|
1372
|
+
return { content: idCheck.reason ?? "blocked: unknown identifiers in edit", mutated: false };
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
const { content: after, outcome } = applyEdit(before, { op: "replace", file: path2, search: search2, replace }, ctx.spans.get(normalize6(path2)) ?? []);
|
|
1376
|
+
if (outcome.status !== "applied") {
|
|
1377
|
+
return { content: `edit not applied (${outcome.status}): ${outcome.reason ?? ""}`, mutated: false };
|
|
1378
|
+
}
|
|
1379
|
+
const diff = unifiedDiff(before, after, path2);
|
|
1380
|
+
if (!await ctx.approve({ kind: "edit", file: path2, diff })) {
|
|
1381
|
+
return { content: `edit to ${path2} was declined by the user`, mutated: false };
|
|
1382
|
+
}
|
|
1383
|
+
ctx.fsImpl.write(path2, after ?? "");
|
|
1384
|
+
return { content: `edited ${path2}`, mutated: true, change: { file: path2, before, after, outcomes: [outcome], diff } };
|
|
1385
|
+
}
|
|
1386
|
+
async function createFile(ctx, path2, content) {
|
|
1387
|
+
if (ctx.enforceIdentifiers !== false && ctx.identifierTrie) {
|
|
1388
|
+
const idCheck = enforceIdentifiersInText(content, ctx.identifierTrie);
|
|
1389
|
+
if (!idCheck.ok) {
|
|
1390
|
+
return { content: idCheck.reason ?? "blocked: unknown identifiers in create", mutated: false };
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
const existing = ctx.fsImpl.read(path2);
|
|
1394
|
+
if (existing !== null) return { content: `${path2} already exists \u2014 use edit_file`, mutated: false };
|
|
1395
|
+
if (!await ctx.approve({ kind: "create", file: path2, bytes: Buffer.byteLength(content) })) {
|
|
1396
|
+
return { content: `creating ${path2} was declined by the user`, mutated: false };
|
|
1397
|
+
}
|
|
1398
|
+
ctx.fsImpl.write(path2, content);
|
|
1399
|
+
return { content: `created ${path2}`, mutated: true, change: { file: path2, before: null, after: content, outcomes: [{ edit: { op: "create", file: path2, content }, status: "applied" }], diff: unifiedDiff(null, content, path2) } };
|
|
1400
|
+
}
|
|
1401
|
+
async function deleteFile(ctx, path2) {
|
|
1402
|
+
const before = ctx.fsImpl.read(path2);
|
|
1403
|
+
if (before === null) return { content: `${path2} does not exist`, mutated: false };
|
|
1404
|
+
if (!await ctx.approve({ kind: "delete", file: path2 })) {
|
|
1405
|
+
return { content: `deleting ${path2} was declined by the user`, mutated: false };
|
|
1406
|
+
}
|
|
1407
|
+
ctx.fsImpl.remove(path2);
|
|
1408
|
+
return { content: `deleted ${path2}`, mutated: true, change: { file: path2, before, after: null, outcomes: [{ edit: { op: "delete", file: path2 }, status: "applied" }], diff: unifiedDiff(before, null, path2) } };
|
|
1409
|
+
}
|
|
1410
|
+
async function applyPatchTool(ctx, raw) {
|
|
1411
|
+
const patch = coercePatchIR(raw);
|
|
1412
|
+
if (!patch) {
|
|
1413
|
+
return { content: "apply_patch needs a patch object with operations[] (patch-ir/0)", mutated: false };
|
|
1414
|
+
}
|
|
1415
|
+
const structural = validatePatchIR(patch);
|
|
1416
|
+
if (!structural.ok) {
|
|
1417
|
+
return { content: `invalid PatchIR: ${structural.errors.join("; ")}`, mutated: false };
|
|
1418
|
+
}
|
|
1419
|
+
if (ctx.enforceIdentifiers !== false && ctx.identifierTrie) {
|
|
1420
|
+
const idCheck = enforceIdentifiersInPatch(patch, ctx.identifierTrie, {
|
|
1421
|
+
readFile: (f) => ctx.fsImpl.read(f)
|
|
1422
|
+
});
|
|
1423
|
+
if (!idCheck.ok) {
|
|
1424
|
+
return { content: idCheck.reason ?? "blocked: unknown identifiers", mutated: false };
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
const locate = locatorsFromGraph(ctx.graph.nodes);
|
|
1428
|
+
const dry = applyPatchIR(patch, {
|
|
1429
|
+
readFile: (f) => ctx.fsImpl.read(f),
|
|
1430
|
+
spansForFile: (f) => ctx.spans.get(normalize6(f)) ?? [],
|
|
1431
|
+
locateSymbol: locate,
|
|
1432
|
+
transactional: true
|
|
1433
|
+
});
|
|
1434
|
+
if (!dry.ok) {
|
|
1435
|
+
const detail = dry.errors.length ? dry.errors.join("; ") : dry.ops.map((o) => `${o.op}@${o.file}:${o.status}`).join("; ");
|
|
1436
|
+
return { content: `patch not applied: ${detail}`, mutated: false };
|
|
1437
|
+
}
|
|
1438
|
+
const planned = [];
|
|
1439
|
+
for (const [file, entry] of [...dry.files.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {
|
|
1440
|
+
if (entry.before === entry.after) continue;
|
|
1441
|
+
if (isSecretPath(file)) return { content: secretRefusal(file), mutated: false };
|
|
1442
|
+
const op = entry.after === null ? "delete" : entry.before === null ? "create" : "edit";
|
|
1443
|
+
planned.push({
|
|
1444
|
+
file,
|
|
1445
|
+
op,
|
|
1446
|
+
before: entry.before,
|
|
1447
|
+
after: entry.after,
|
|
1448
|
+
diff: unifiedDiff(entry.before, entry.after, file)
|
|
1449
|
+
});
|
|
1450
|
+
}
|
|
1451
|
+
if (planned.length === 0) {
|
|
1452
|
+
return { content: "patch applied with no content changes (no-op)", mutated: false };
|
|
1453
|
+
}
|
|
1454
|
+
const files = planned.map((p) => {
|
|
1455
|
+
if (p.op === "delete") return { file: p.file, op: "delete", diff: p.diff };
|
|
1456
|
+
if (p.op === "create") {
|
|
1457
|
+
return { file: p.file, op: "create", bytes: Buffer.byteLength(p.after ?? ""), diff: p.diff };
|
|
1458
|
+
}
|
|
1459
|
+
return { file: p.file, op: "edit", diff: p.diff };
|
|
1460
|
+
});
|
|
1461
|
+
if (!await ctx.approve({ kind: "patch", files })) {
|
|
1462
|
+
return {
|
|
1463
|
+
content: `patch declined \u2014 ${planned.length} file change(s) refused (nothing written)`,
|
|
1464
|
+
mutated: false
|
|
1465
|
+
};
|
|
1466
|
+
}
|
|
1467
|
+
const changes = [];
|
|
1468
|
+
for (const p of planned) {
|
|
1469
|
+
const edit = p.after === null ? { op: "delete", file: p.file } : p.before === null ? { op: "create", file: p.file, content: p.after } : { op: "replace", file: p.file, search: "", replace: p.after };
|
|
1470
|
+
changes.push({
|
|
1471
|
+
file: p.file,
|
|
1472
|
+
before: p.before,
|
|
1473
|
+
after: p.after,
|
|
1474
|
+
outcomes: [{ edit, status: "applied" }],
|
|
1475
|
+
diff: p.diff
|
|
1476
|
+
});
|
|
1477
|
+
if (p.after === null) ctx.fsImpl.remove(p.file);
|
|
1478
|
+
else ctx.fsImpl.write(p.file, p.after);
|
|
1479
|
+
}
|
|
1480
|
+
const summary = changes.map((c) => c.file).join(", ");
|
|
1481
|
+
return {
|
|
1482
|
+
content: `applied patch to ${changes.length} file(s): ${summary}`,
|
|
1483
|
+
mutated: true,
|
|
1484
|
+
change: changes[0]
|
|
1485
|
+
};
|
|
1486
|
+
}
|
|
1487
|
+
function coercePatchIR(raw) {
|
|
1488
|
+
if (!raw || typeof raw !== "object") return null;
|
|
1489
|
+
const o = raw;
|
|
1490
|
+
if (!Array.isArray(o.operations)) return null;
|
|
1491
|
+
return {
|
|
1492
|
+
schemaVersion: PATCH_IR_SCHEMA_VERSION,
|
|
1493
|
+
operations: o.operations,
|
|
1494
|
+
assumptions: Array.isArray(o.assumptions) ? o.assumptions : [],
|
|
1495
|
+
requestedVerification: Array.isArray(o.requestedVerification) ? o.requestedVerification : [],
|
|
1496
|
+
provenance: { format: "structured-json", modelId: null, raw: null }
|
|
1497
|
+
};
|
|
1498
|
+
}
|
|
1499
|
+
async function runCommand(ctx, command) {
|
|
1500
|
+
if (!command) return { content: "run_command needs a command", mutated: false };
|
|
1501
|
+
if (ctx.auto) {
|
|
1502
|
+
const reason = dangerousCommand(command, ctx.denyCommands);
|
|
1503
|
+
if (reason) return { content: `refused to run \`${command}\` autonomously \u2014 ${reason}. Run it yourself if you intend to, or re-run without --auto to approve it interactively.`, mutated: false };
|
|
1504
|
+
}
|
|
1505
|
+
const enforceNet = ctx.enforceNetworkPolicy ?? !!ctx.auto;
|
|
1506
|
+
const netReason = networkCommandRefusal(command, { enforce: enforceNet });
|
|
1507
|
+
if (netReason) {
|
|
1508
|
+
return { content: `refused to run \`${command}\` \u2014 ${netReason}`, mutated: false };
|
|
1509
|
+
}
|
|
1510
|
+
const secretReason = secretEgressRefusal(command, "shell command");
|
|
1511
|
+
if (secretReason) {
|
|
1512
|
+
return { content: `refused to run \`${command}\` \u2014 ${secretReason}`, mutated: false };
|
|
1513
|
+
}
|
|
1514
|
+
if (!await ctx.approve({ kind: "run", command })) {
|
|
1515
|
+
return { content: `running \`${command}\` was declined by the user`, mutated: false };
|
|
1516
|
+
}
|
|
1517
|
+
const res = ctx.run(command);
|
|
1518
|
+
return { content: `$ ${command}
|
|
1519
|
+
exit ${res.exitCode}
|
|
1520
|
+
${truncate(res.stdout)}`, mutated: true };
|
|
1521
|
+
}
|
|
1522
|
+
function inspectTask(ctx) {
|
|
1523
|
+
const capsule = ctx.getTaskCapsule?.() ?? ctx.capsule ?? null;
|
|
1524
|
+
if (!capsule) {
|
|
1525
|
+
return {
|
|
1526
|
+
content: "No Task Capsule for this run (metadata-only context). Re-run with --capsule, or use search_code / read_file to gather evidence.",
|
|
1527
|
+
mutated: false
|
|
1528
|
+
};
|
|
1529
|
+
}
|
|
1530
|
+
const summary = summarizeCapsule(capsule);
|
|
1531
|
+
const lines = [
|
|
1532
|
+
`Task Capsule ${summary.schemaVersion} \xB7 ranking ${summary.rankingVersion} \xB7 ~${summary.tokensEstimate} tokens`,
|
|
1533
|
+
`Instruction: ${summary.instruction}`,
|
|
1534
|
+
`Primary (${summary.primary.length}):`,
|
|
1535
|
+
...summary.primary.map((p) => ` - ${p.qualifiedName} (${p.kind}) ${p.file}`),
|
|
1536
|
+
`Supporting (${summary.supporting.length}):`,
|
|
1537
|
+
...summary.supporting.slice(0, 8).map((p) => ` - ${p.qualifiedName} ${p.file}`),
|
|
1538
|
+
`Source slices: ${summary.sourceSliceCount} across ${summary.sourceFiles.join(", ") || "(none)"}`,
|
|
1539
|
+
`Verification files: ${capsule.verificationPlan.syntaxFiles.slice(0, 8).join(", ") || "(none)"}`
|
|
1540
|
+
];
|
|
1541
|
+
if (capsule.provenance.modelProfileId) lines.push(`Model profile: ${capsule.provenance.modelProfileId}`);
|
|
1542
|
+
if (capsule.provenance.securityTier) lines.push(`Security tier: ${capsule.provenance.securityTier}`);
|
|
1543
|
+
return { content: lines.join("\n"), mutated: false };
|
|
1544
|
+
}
|
|
1545
|
+
function inspectChangeTool(ctx, args) {
|
|
1546
|
+
const symbols = Array.isArray(args.symbols) ? args.symbols.filter((x) => typeof x === "string" && x.trim().length > 0) : [];
|
|
1547
|
+
let files = Array.isArray(args.files) ? args.files.filter((x) => typeof x === "string" && x.trim().length > 0) : [];
|
|
1548
|
+
if (!files.length) {
|
|
1549
|
+
const dirty = ctx.dirtyFiles?.() ?? [];
|
|
1550
|
+
const changed = ctx.changedFiles?.() ?? [];
|
|
1551
|
+
files = [.../* @__PURE__ */ new Set([...dirty, ...changed])];
|
|
1552
|
+
}
|
|
1553
|
+
if (!symbols.length && !files.length) {
|
|
1554
|
+
return {
|
|
1555
|
+
content: 'inspect_change needs symbols[] or files[] (or session dirty/changed files). Example: { "files": ["src/auth.ts"] }',
|
|
1556
|
+
mutated: false
|
|
1557
|
+
};
|
|
1558
|
+
}
|
|
1559
|
+
const depth = typeof args.depth === "number" && args.depth > 0 ? args.depth : 3;
|
|
1560
|
+
const report = inspectChange(ctx.graph, { symbols, files, depth });
|
|
1561
|
+
return { content: report.rendered, mutated: false };
|
|
1562
|
+
}
|
|
1563
|
+
function verifyChange(ctx, commandOverride) {
|
|
1564
|
+
const capsule = ctx.getTaskCapsule?.() ?? ctx.capsule ?? null;
|
|
1565
|
+
if (!capsule) {
|
|
1566
|
+
if (commandOverride) {
|
|
1567
|
+
const res = ctx.run(commandOverride);
|
|
1568
|
+
if (res.exitCode === 0) return { content: `verify ok \u2014 \`${commandOverride}\` exit 0`, mutated: false };
|
|
1569
|
+
const fc2 = buildFailureCapsule({
|
|
1570
|
+
verify: { command: commandOverride, exitCode: res.exitCode, stdout: res.stdout },
|
|
1571
|
+
changedFiles: []
|
|
1572
|
+
});
|
|
1573
|
+
return { content: fc2.rendered, mutated: false };
|
|
1574
|
+
}
|
|
1575
|
+
return { content: "verify_change needs a Task Capsule or an explicit command", mutated: false };
|
|
1576
|
+
}
|
|
1577
|
+
const steps = compileVerificationLadder(capsule.verificationPlan, {
|
|
1578
|
+
testCommand: commandOverride
|
|
1579
|
+
});
|
|
1580
|
+
const ladder = runVerificationLadder(steps, {
|
|
1581
|
+
readFile: (f) => ctx.fsImpl.read(f),
|
|
1582
|
+
run: ctx.run,
|
|
1583
|
+
runCommands: true
|
|
1584
|
+
});
|
|
1585
|
+
if (ladder.ok) {
|
|
1586
|
+
return {
|
|
1587
|
+
content: `verification ladder passed:
|
|
1588
|
+
${ladder.steps.map((s) => `\u2714 ${s.message}`).join("\n")}`,
|
|
1589
|
+
mutated: false
|
|
1590
|
+
};
|
|
1591
|
+
}
|
|
1592
|
+
const fc = buildFailureCapsule({
|
|
1593
|
+
ladderSteps: ladder.steps,
|
|
1594
|
+
capsule,
|
|
1595
|
+
changedFiles: []
|
|
1596
|
+
});
|
|
1597
|
+
return { content: fc.rendered, mutated: false };
|
|
1598
|
+
}
|
|
1599
|
+
function truncate(s) {
|
|
1600
|
+
if (s.length <= MAX_OUTPUT) return s;
|
|
1601
|
+
return s.slice(0, MAX_OUTPUT) + `
|
|
1602
|
+
\u2026 (truncated ${s.length - MAX_OUTPUT} chars)`;
|
|
1603
|
+
}
|
|
1604
|
+
function str(v) {
|
|
1605
|
+
return typeof v === "string" ? v : v === void 0 || v === null ? "" : String(v);
|
|
1606
|
+
}
|
|
1607
|
+
function num(v) {
|
|
1608
|
+
const n = typeof v === "number" ? v : typeof v === "string" ? Number(v) : NaN;
|
|
1609
|
+
return Number.isFinite(n) ? n : void 0;
|
|
1610
|
+
}
|
|
1611
|
+
function normalize6(file) {
|
|
1612
|
+
return file.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
// src/code/overlay.ts
|
|
1616
|
+
var SessionOverlay = class {
|
|
1617
|
+
constructor(base) {
|
|
1618
|
+
this.base = base;
|
|
1619
|
+
}
|
|
1620
|
+
base;
|
|
1621
|
+
dirty = /* @__PURE__ */ new Map();
|
|
1622
|
+
/** Paths with uncommitted writes or deletes (sorted). */
|
|
1623
|
+
dirtyFiles() {
|
|
1624
|
+
return [...this.dirty.keys()].sort();
|
|
1625
|
+
}
|
|
1626
|
+
/** True if the overlay has any uncommitted mutation. */
|
|
1627
|
+
isDirty() {
|
|
1628
|
+
return this.dirty.size > 0;
|
|
1629
|
+
}
|
|
1630
|
+
read(file) {
|
|
1631
|
+
const key = normalize7(file);
|
|
1632
|
+
const entry = this.dirty.get(key);
|
|
1633
|
+
if (entry) {
|
|
1634
|
+
if (entry.kind === "delete") return null;
|
|
1635
|
+
return entry.content;
|
|
1636
|
+
}
|
|
1637
|
+
return this.base.read(file);
|
|
1638
|
+
}
|
|
1639
|
+
write(file, content) {
|
|
1640
|
+
this.dirty.set(normalize7(file), { kind: "write", content });
|
|
1641
|
+
}
|
|
1642
|
+
remove(file) {
|
|
1643
|
+
this.dirty.set(normalize7(file), { kind: "delete" });
|
|
1644
|
+
}
|
|
1645
|
+
appendAudit(line) {
|
|
1646
|
+
this.base.appendAudit(line);
|
|
1647
|
+
}
|
|
1648
|
+
/**
|
|
1649
|
+
* Commit every dirty entry to the base filesystem, then clear the overlay.
|
|
1650
|
+
* Returns the paths flushed (sorted).
|
|
1651
|
+
*/
|
|
1652
|
+
flush() {
|
|
1653
|
+
const files = this.dirtyFiles();
|
|
1654
|
+
for (const file of files) {
|
|
1655
|
+
const entry = this.dirty.get(file);
|
|
1656
|
+
if (entry.kind === "delete") this.base.remove(file);
|
|
1657
|
+
else this.base.write(file, entry.content);
|
|
1658
|
+
}
|
|
1659
|
+
this.dirty.clear();
|
|
1660
|
+
return files;
|
|
1661
|
+
}
|
|
1662
|
+
/** Drop uncommitted mutations without writing the base. */
|
|
1663
|
+
discard() {
|
|
1664
|
+
this.dirty.clear();
|
|
1665
|
+
}
|
|
1666
|
+
/**
|
|
1667
|
+
* Snapshot of dirty entries for tests / diagnostics (path → entry).
|
|
1668
|
+
* Does not include base-only files.
|
|
1669
|
+
*/
|
|
1670
|
+
snapshot() {
|
|
1671
|
+
return new Map(this.dirty);
|
|
1672
|
+
}
|
|
1673
|
+
};
|
|
1674
|
+
function normalize7(file) {
|
|
1675
|
+
return file.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
// src/code/trajectory.ts
|
|
1679
|
+
var TRAJECTORY_SCHEMA_VERSION = "trajectory/0";
|
|
1680
|
+
var DISCOVERY_TOOLS = /* @__PURE__ */ new Set([
|
|
1681
|
+
"search_code",
|
|
1682
|
+
"read_file",
|
|
1683
|
+
"list_files",
|
|
1684
|
+
"graph_impact",
|
|
1685
|
+
"library_docs"
|
|
1686
|
+
]);
|
|
1687
|
+
var MUTATION_TOOLS = /* @__PURE__ */ new Set([
|
|
1688
|
+
"edit_file",
|
|
1689
|
+
"create_file",
|
|
1690
|
+
"delete_file",
|
|
1691
|
+
"apply_patch",
|
|
1692
|
+
"run_command"
|
|
1693
|
+
]);
|
|
1694
|
+
function createTrajectoryCollector() {
|
|
1695
|
+
const toolCalls = [];
|
|
1696
|
+
let promptTokens = 0;
|
|
1697
|
+
let completionTokens = 0;
|
|
1698
|
+
let failureCapsuleBuilt = false;
|
|
1699
|
+
return {
|
|
1700
|
+
recordTool(name, step, mutated) {
|
|
1701
|
+
toolCalls.push({ name, step, mutated });
|
|
1702
|
+
},
|
|
1703
|
+
recordUsage(p, c) {
|
|
1704
|
+
promptTokens += p;
|
|
1705
|
+
completionTokens += c;
|
|
1706
|
+
},
|
|
1707
|
+
markFailureCapsule() {
|
|
1708
|
+
failureCapsuleBuilt = true;
|
|
1709
|
+
},
|
|
1710
|
+
finalize(input) {
|
|
1711
|
+
const discoveryToolCalls = toolCalls.filter((t) => DISCOVERY_TOOLS.has(t.name)).length;
|
|
1712
|
+
const mutationToolCalls = toolCalls.filter((t) => MUTATION_TOOLS.has(t.name)).length;
|
|
1713
|
+
const shellCommands = toolCalls.filter((t) => t.name === "run_command").length;
|
|
1714
|
+
const znsAt1 = computeZnsAt1({
|
|
1715
|
+
solved: input.solved && input.verified,
|
|
1716
|
+
usedCapsule: input.usedCapsule,
|
|
1717
|
+
toolCalls
|
|
1718
|
+
});
|
|
1719
|
+
return {
|
|
1720
|
+
schemaVersion: TRAJECTORY_SCHEMA_VERSION,
|
|
1721
|
+
taskId: input.taskId,
|
|
1722
|
+
arm: input.arm,
|
|
1723
|
+
solved: input.solved,
|
|
1724
|
+
verified: input.verified,
|
|
1725
|
+
znsAt1,
|
|
1726
|
+
steps: input.steps,
|
|
1727
|
+
stopped: input.stopped,
|
|
1728
|
+
inferenceTurns: input.inferenceTurns,
|
|
1729
|
+
toolCalls,
|
|
1730
|
+
discoveryToolCalls,
|
|
1731
|
+
mutationToolCalls,
|
|
1732
|
+
shellCommands,
|
|
1733
|
+
promptTokens,
|
|
1734
|
+
completionTokens,
|
|
1735
|
+
elapsedMs: input.elapsedMs ?? null,
|
|
1736
|
+
filesChanged: input.filesChanged,
|
|
1737
|
+
usedCapsule: input.usedCapsule,
|
|
1738
|
+
failureCapsuleBuilt,
|
|
1739
|
+
provider: input.provider
|
|
1740
|
+
};
|
|
1741
|
+
}
|
|
1742
|
+
};
|
|
1743
|
+
}
|
|
1744
|
+
function computeZnsAt1(input) {
|
|
1745
|
+
if (!input.solved || !input.usedCapsule) return false;
|
|
1746
|
+
return !input.toolCalls.some((t) => DISCOVERY_TOOLS.has(t.name));
|
|
1747
|
+
}
|
|
1748
|
+
function computeFcsReport(records) {
|
|
1749
|
+
const byArm = {};
|
|
1750
|
+
for (const arm of ["capsule", "oracle", "metadata", "other"]) {
|
|
1751
|
+
const armRecs = records.filter((r) => r.arm === arm);
|
|
1752
|
+
if (!armRecs.length && arm === "other") continue;
|
|
1753
|
+
byArm[arm] = summarizeArm(armRecs);
|
|
1754
|
+
}
|
|
1755
|
+
const capsule = records.filter((r) => r.arm === "capsule");
|
|
1756
|
+
const oracle = records.filter((r) => r.arm === "oracle");
|
|
1757
|
+
const oracleSolvedIds = new Set(oracle.filter((r) => r.solved).map((r) => r.taskId));
|
|
1758
|
+
const capsuleByTask = /* @__PURE__ */ new Map();
|
|
1759
|
+
for (const r of capsule) {
|
|
1760
|
+
const prev = capsuleByTask.get(r.taskId);
|
|
1761
|
+
if (!prev || r.solved && !prev.solved) capsuleByTask.set(r.taskId, r);
|
|
1762
|
+
}
|
|
1763
|
+
const pairedIds = [...capsuleByTask.keys()].filter((id) => oracle.some((o) => o.taskId === id));
|
|
1764
|
+
const oracleSolvedPaired = pairedIds.filter((id) => oracleSolvedIds.has(id));
|
|
1765
|
+
const capsuleSolvedAmongOracle = oracleSolvedPaired.filter((id) => capsuleByTask.get(id)?.solved).length;
|
|
1766
|
+
const fcs = oracleSolvedPaired.length > 0 ? round(capsuleSolvedAmongOracle / oracleSolvedPaired.length) : null;
|
|
1767
|
+
const capsuleSolved = capsule.filter((r) => r.solved);
|
|
1768
|
+
const znsAt1Rate = capsuleSolved.length > 0 ? round(capsuleSolved.filter((r) => r.znsAt1).length / capsuleSolved.length) : null;
|
|
1769
|
+
return {
|
|
1770
|
+
schemaVersion: "fcs-report/0",
|
|
1771
|
+
fcs,
|
|
1772
|
+
pairedTasks: pairedIds.length,
|
|
1773
|
+
capsuleSolved: capsuleSolvedAmongOracle,
|
|
1774
|
+
oracleSolved: oracleSolvedPaired.length,
|
|
1775
|
+
znsAt1Rate,
|
|
1776
|
+
capsuleRuns: capsule.length,
|
|
1777
|
+
oracleRuns: oracle.length,
|
|
1778
|
+
metadataRuns: records.filter((r) => r.arm === "metadata").length,
|
|
1779
|
+
byArm,
|
|
1780
|
+
records
|
|
1781
|
+
};
|
|
1782
|
+
}
|
|
1783
|
+
function renderFcsReportMarkdown(report) {
|
|
1784
|
+
const lines = [
|
|
1785
|
+
"# Fusion trajectory / FCS report",
|
|
1786
|
+
"",
|
|
1787
|
+
`| Metric | Value |`,
|
|
1788
|
+
`|---|---:|`,
|
|
1789
|
+
`| FCS (capsule \xF7 oracle solves) | ${report.fcs == null ? "n/a (no oracle arm)" : report.fcs.toFixed(3)} |`,
|
|
1790
|
+
`| ZNS@1 rate (capsule solves) | ${report.znsAt1Rate == null ? "n/a" : (report.znsAt1Rate * 100).toFixed(1) + "%"} |`,
|
|
1791
|
+
`| Paired tasks | ${report.pairedTasks} |`,
|
|
1792
|
+
`| Capsule runs | ${report.capsuleRuns} |`,
|
|
1793
|
+
`| Oracle runs | ${report.oracleRuns} |`,
|
|
1794
|
+
`| Metadata runs | ${report.metadataRuns} |`,
|
|
1795
|
+
"",
|
|
1796
|
+
"## Per arm",
|
|
1797
|
+
"",
|
|
1798
|
+
"| Arm | Runs | Solved | Pass rate | ZNS@1 | Discovery p50 | Steps p50 | Prompt tokens p50 |",
|
|
1799
|
+
"|---|---:|---:|---:|---:|---:|---:|---:|"
|
|
1800
|
+
];
|
|
1801
|
+
for (const [arm, s] of Object.entries(report.byArm)) {
|
|
1802
|
+
if (!s.runs) continue;
|
|
1803
|
+
lines.push(
|
|
1804
|
+
`| ${arm} | ${s.runs} | ${s.solved} | ${(s.passRate * 100).toFixed(1)}% | ${s.znsAt1} | ${fmt(s.medianDiscovery)} | ${fmt(s.medianSteps)} | ${fmt(s.medianPromptTokens)} |`
|
|
1805
|
+
);
|
|
1806
|
+
}
|
|
1807
|
+
lines.push(
|
|
1808
|
+
"",
|
|
1809
|
+
"## Definitions",
|
|
1810
|
+
"",
|
|
1811
|
+
"- **ZNS@1**: solved + verified from a Task Capsule with zero discovery tool calls (`search_code`, `read_file`, `list_files`, `graph_impact`, `library_docs`).",
|
|
1812
|
+
"- **FCS**: among tasks the oracle arm solves, the fraction also solved under the capsule arm (same task ids)."
|
|
1813
|
+
);
|
|
1814
|
+
return lines.join("\n") + "\n";
|
|
1815
|
+
}
|
|
1816
|
+
function summarizeArm(recs) {
|
|
1817
|
+
const solved = recs.filter((r) => r.solved).length;
|
|
1818
|
+
return {
|
|
1819
|
+
runs: recs.length,
|
|
1820
|
+
solved,
|
|
1821
|
+
passRate: recs.length ? round(solved / recs.length) : 0,
|
|
1822
|
+
znsAt1: recs.filter((r) => r.znsAt1).length,
|
|
1823
|
+
medianDiscovery: median(recs.map((r) => r.discoveryToolCalls)),
|
|
1824
|
+
medianSteps: median(recs.map((r) => r.steps)),
|
|
1825
|
+
medianPromptTokens: median(recs.map((r) => r.promptTokens))
|
|
1826
|
+
};
|
|
1827
|
+
}
|
|
1828
|
+
function median(values) {
|
|
1829
|
+
if (!values.length) return null;
|
|
1830
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
1831
|
+
const mid = Math.floor(sorted.length / 2);
|
|
1832
|
+
return sorted.length % 2 ? sorted[mid] : round((sorted[mid - 1] + sorted[mid]) / 2);
|
|
1833
|
+
}
|
|
1834
|
+
function round(n) {
|
|
1835
|
+
return Math.round(n * 1e3) / 1e3;
|
|
1836
|
+
}
|
|
1837
|
+
function fmt(n) {
|
|
1838
|
+
return n == null ? "n/a" : String(n);
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1841
|
+
// src/code/run-provenance.ts
|
|
1842
|
+
var RUN_PROVENANCE_SCHEMA_VERSION = "run-provenance/0";
|
|
1843
|
+
var CONTEXT_POLICY_VERSION = "context-policy@2026.07.0";
|
|
1844
|
+
function mutationOp(before, after) {
|
|
1845
|
+
if (before === null && after !== null) return "create";
|
|
1846
|
+
if (before !== null && after === null) return "delete";
|
|
1847
|
+
return "edit";
|
|
1848
|
+
}
|
|
1849
|
+
function hashMutation(change) {
|
|
1850
|
+
const op = mutationOp(change.before, change.after);
|
|
1851
|
+
return {
|
|
1852
|
+
file: change.file,
|
|
1853
|
+
op,
|
|
1854
|
+
beforeHash: change.before === null ? null : hashString(change.before),
|
|
1855
|
+
afterHash: change.after === null ? null : hashString(change.after),
|
|
1856
|
+
diffHash: change.diff ? hashString(change.diff) : null
|
|
1857
|
+
};
|
|
1858
|
+
}
|
|
1859
|
+
function buildRunProvenance(input = {}) {
|
|
1860
|
+
const mutations = (input.changes ?? []).map(hashMutation).sort((a, b) => a.file.localeCompare(b.file) || a.op.localeCompare(b.op));
|
|
1861
|
+
const verification = input.verification?.length ? input.verification.map((v) => ({
|
|
1862
|
+
kind: v.kind,
|
|
1863
|
+
command: v.command ?? null,
|
|
1864
|
+
passed: !!v.passed,
|
|
1865
|
+
diagnosticHash: v.diagnostic ? hashString(v.diagnostic.slice(0, 4e3)) : null
|
|
1866
|
+
})).sort(
|
|
1867
|
+
(a, b) => a.kind.localeCompare(b.kind) || (a.command ?? "").localeCompare(b.command ?? "") || Number(a.passed) - Number(b.passed)
|
|
1868
|
+
) : void 0;
|
|
1869
|
+
return {
|
|
1870
|
+
schemaVersion: RUN_PROVENANCE_SCHEMA_VERSION,
|
|
1871
|
+
policyVersion: input.policyVersion ?? CONTEXT_POLICY_VERSION,
|
|
1872
|
+
rankingVersion: input.rankingVersion ?? CAPSULE_RANKING_VERSION,
|
|
1873
|
+
modelProfileId: input.modelProfileId ?? null,
|
|
1874
|
+
securityTier: input.securityTier ?? null,
|
|
1875
|
+
graphCorpusHash: input.graphCorpusHash ?? null,
|
|
1876
|
+
mutations,
|
|
1877
|
+
mutationsRootHash: hashString(canonicalize(mutations)),
|
|
1878
|
+
...verification ? {
|
|
1879
|
+
verification,
|
|
1880
|
+
verificationRootHash: hashString(canonicalize(verification))
|
|
1881
|
+
} : { verificationRootHash: null }
|
|
1882
|
+
};
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1885
|
+
// src/code/patch-ir-grammar.ts
|
|
1886
|
+
function patchIrGbnf() {
|
|
1887
|
+
return String.raw`root ::= ws patch
|
|
1888
|
+
ws ::= [ \t\n]*
|
|
1889
|
+
patch ::= "{" ws
|
|
1890
|
+
"\"schemaVersion\"" ws ":" ws "\"patch-ir/0\"" ws "," ws
|
|
1891
|
+
"\"operations\"" ws ":" ws operations ws "," ws
|
|
1892
|
+
"\"assumptions\"" ws ":" ws array-any ws "," ws
|
|
1893
|
+
"\"requestedVerification\"" ws ":" ws array-any ws "," ws
|
|
1894
|
+
"\"provenance\"" ws ":" ws provenance
|
|
1895
|
+
ws "}"
|
|
1896
|
+
operations ::= "[" ws operation (ws "," ws operation)* ws "]"
|
|
1897
|
+
operation ::= replace-range | replace-text | create-file | delete-file | replace-symbol
|
|
1898
|
+
replace-range ::= "{" ws
|
|
1899
|
+
"\"op\"" ws ":" ws "\"replace-range\"" ws "," ws
|
|
1900
|
+
"\"file\"" ws ":" ws string ws "," ws
|
|
1901
|
+
"\"start\"" ws ":" ws number ws "," ws
|
|
1902
|
+
"\"end\"" ws ":" ws number ws "," ws
|
|
1903
|
+
"\"replacement\"" ws ":" ws string
|
|
1904
|
+
(ws "," ws "\"expectedHash\"" ws ":" ws (string | null) )?
|
|
1905
|
+
(ws "," ws "\"anchorSymbol\"" ws ":" ws (string | null) )?
|
|
1906
|
+
ws "}"
|
|
1907
|
+
replace-text ::= "{" ws
|
|
1908
|
+
"\"op\"" ws ":" ws "\"replace-text\"" ws "," ws
|
|
1909
|
+
"\"file\"" ws ":" ws string ws "," ws
|
|
1910
|
+
"\"search\"" ws ":" ws string ws "," ws
|
|
1911
|
+
"\"replace\"" ws ":" ws string
|
|
1912
|
+
(ws "," ws "\"anchorSymbol\"" ws ":" ws (string | null) )?
|
|
1913
|
+
ws "}"
|
|
1914
|
+
create-file ::= "{" ws
|
|
1915
|
+
"\"op\"" ws ":" ws "\"create-file\"" ws "," ws
|
|
1916
|
+
"\"file\"" ws ":" ws string ws "," ws
|
|
1917
|
+
"\"content\"" ws ":" ws string
|
|
1918
|
+
ws "}"
|
|
1919
|
+
delete-file ::= "{" ws
|
|
1920
|
+
"\"op\"" ws ":" ws "\"delete-file\"" ws "," ws
|
|
1921
|
+
"\"file\"" ws ":" ws string
|
|
1922
|
+
ws "}"
|
|
1923
|
+
replace-symbol ::= "{" ws
|
|
1924
|
+
"\"op\"" ws ":" ws "\"replace-symbol\"" ws "," ws
|
|
1925
|
+
"\"symbolId\"" ws ":" ws string ws "," ws
|
|
1926
|
+
"\"replacement\"" ws ":" ws string
|
|
1927
|
+
(ws "," ws "\"file\"" ws ":" ws (string | null) )?
|
|
1928
|
+
(ws "," ws "\"expectedHash\"" ws ":" ws (string | null) )?
|
|
1929
|
+
ws "}"
|
|
1930
|
+
provenance ::= "{" ws
|
|
1931
|
+
"\"format\"" ws ":" ws format
|
|
1932
|
+
(ws "," ws "\"modelId\"" ws ":" ws (string | null) )?
|
|
1933
|
+
(ws "," ws "\"raw\"" ws ":" ws (string | null) )?
|
|
1934
|
+
ws "}"
|
|
1935
|
+
format ::= "\"search-replace\"" | "\"whole-file\"" | "\"structured-json\"" | "\"grammar-constrained\"" | "\"code-edit\""
|
|
1936
|
+
array-any ::= "[" ws (value (ws "," ws value)*)? ws "]"
|
|
1937
|
+
value ::= object | array-any | string | number | "true" | "false" | null
|
|
1938
|
+
object ::= "{" ws (string ws ":" ws value (ws "," ws string ws ":" ws value)*)? ws "}"
|
|
1939
|
+
string ::= "\"" (
|
|
1940
|
+
[^"\\] |
|
|
1941
|
+
"\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
|
|
1942
|
+
)* "\""
|
|
1943
|
+
number ::= "-"? ("0" | [1-9] [0-9]*) ("." [0-9]+)? ([eE] [+-]? [0-9]+)?
|
|
1944
|
+
null ::= "null"
|
|
1945
|
+
`;
|
|
1946
|
+
}
|
|
1947
|
+
function shouldUsePatchIrGrammar(profile) {
|
|
1948
|
+
if (!profile) return false;
|
|
1949
|
+
if (profile.constrainedDecoding) return true;
|
|
1950
|
+
if (profile.toolCalling === "grammar") return true;
|
|
1951
|
+
if (profile.editFormat === "patch_ir_json" && profile.constrainedDecoding !== false) {
|
|
1952
|
+
return !!profile.constrainedDecoding;
|
|
1953
|
+
}
|
|
1954
|
+
return false;
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1957
|
+
// src/code/agent.ts
|
|
1958
|
+
var DEFAULT_MAX_STEPS = 24;
|
|
1959
|
+
var DEFAULT_CONTEXT_BUDGET = 16e3;
|
|
1960
|
+
var KEEP_ROUNDS = 8;
|
|
1961
|
+
var NUDGE_AT = 3;
|
|
1962
|
+
var STOP_AT = 5;
|
|
1963
|
+
async function runAgent(options) {
|
|
1964
|
+
const { root, instruction, providers } = options;
|
|
1965
|
+
let graph = options.graph;
|
|
1966
|
+
const maxSteps = options.maxSteps ?? DEFAULT_MAX_STEPS;
|
|
1967
|
+
const budget = options.budget ?? options.modelProfile?.capsuleBudgetTokens ?? 3e3;
|
|
1968
|
+
const contextBudget = options.contextBudget ?? DEFAULT_CONTEXT_BUDGET;
|
|
1969
|
+
const now = options.now ?? (() => 0);
|
|
1970
|
+
const onEvent = options.onEvent ?? (() => {
|
|
1971
|
+
});
|
|
1972
|
+
const verifyConfig = options.verify && options.modelProfile?.maxRepairRounds != null && options.verify.maxRounds == null ? { ...options.verify, maxRounds: options.modelProfile.maxRepairRounds } : options.verify;
|
|
1973
|
+
const identifierTrie = options.identifierMask === false ? void 0 : buildIdentifierTrieFromGraph(graph);
|
|
1974
|
+
const kvRegistry = new KvBlockRegistry();
|
|
1975
|
+
const draftCandidates = graphDraftCandidates(
|
|
1976
|
+
graph.nodes.flatMap((n) => [n.signature, n.qualifiedName, n.name].filter(Boolean)).filter((s) => s.includes("(") || s.includes("function") || s.includes("class") || s.length > 12),
|
|
1977
|
+
12
|
|
1978
|
+
);
|
|
1979
|
+
const useWorktree = options.worktreeOverlay === true;
|
|
1980
|
+
if (useWorktree) {
|
|
1981
|
+
try {
|
|
1982
|
+
const wt = await materializeWorktreeGraph(graph, root);
|
|
1983
|
+
if (wt.reparsed.length || wt.deleted.length) {
|
|
1984
|
+
graph = wt.graph;
|
|
1985
|
+
onEvent({
|
|
1986
|
+
type: "session-graph",
|
|
1987
|
+
reparsed: wt.reparsed.length,
|
|
1988
|
+
dirty: wt.dirtyFiles.length
|
|
1989
|
+
});
|
|
1990
|
+
}
|
|
1991
|
+
} catch {
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1994
|
+
const useOverlay = options.overlay ?? !!options.capsule;
|
|
1995
|
+
const overlay = useOverlay ? new SessionOverlay(options.fsImpl) : null;
|
|
1996
|
+
const fsImpl = overlay ?? options.fsImpl;
|
|
1997
|
+
const flushOverlay = () => {
|
|
1998
|
+
overlay?.flush();
|
|
1999
|
+
};
|
|
2000
|
+
const advancedMode = options.advancedMode ?? !options.capsule;
|
|
2001
|
+
const discoveryGate = createDiscoveryGateState();
|
|
2002
|
+
const allTools = [...AGENT_TOOLS, ...options.externalTools?.specs ?? []];
|
|
2003
|
+
const built = buildAgentContext(graph, instruction, { ...options, fsImpl, budget });
|
|
2004
|
+
const context = built.context;
|
|
2005
|
+
let capsule = built.capsule;
|
|
2006
|
+
const useLadder = options.verifyLadder ?? !!capsule;
|
|
2007
|
+
const messages = buildAgentMessages(context);
|
|
2008
|
+
if (options.priorSummary) {
|
|
2009
|
+
messages.push({ role: "user", content: options.priorSummary });
|
|
2010
|
+
}
|
|
2011
|
+
if (options.testCommand) {
|
|
2012
|
+
messages.push({ role: "user", content: `When you want to verify a change, the project's test command is: \`${options.testCommand}\`` });
|
|
2013
|
+
}
|
|
2014
|
+
const spans = buildSpanIndex(graph);
|
|
2015
|
+
const changes = [];
|
|
2016
|
+
const repeats = /* @__PURE__ */ new Map();
|
|
2017
|
+
let recordedSearch = false;
|
|
2018
|
+
let commandCount = 0;
|
|
2019
|
+
let verifyRounds = verifyConfig?.maxRounds ?? 2;
|
|
2020
|
+
let lastVerifyPassed = true;
|
|
2021
|
+
let lastVerification = [];
|
|
2022
|
+
let lastFailureCapsule = null;
|
|
2023
|
+
const usage = { promptTokens: 0, completionTokens: 0 };
|
|
2024
|
+
let providerInfo = { id: providers[0]?.id ?? "none", model: providers[0]?.model ?? "", fellBack: false };
|
|
2025
|
+
const trajectory = createTrajectoryCollector();
|
|
2026
|
+
let currentStep = 0;
|
|
2027
|
+
const capsuleSummary = capsule ? summarizeCapsule(capsule) : null;
|
|
2028
|
+
if (capsuleSummary) onEvent({ type: "capsule", summary: capsuleSummary });
|
|
2029
|
+
const runShell = (command) => {
|
|
2030
|
+
if (options.executionEnv) return options.executionEnv.run(command, { cwd: root });
|
|
2031
|
+
return options.run(command);
|
|
2032
|
+
};
|
|
2033
|
+
const ctx = {
|
|
2034
|
+
root,
|
|
2035
|
+
graph,
|
|
2036
|
+
fsImpl,
|
|
2037
|
+
spans,
|
|
2038
|
+
run: runShell,
|
|
2039
|
+
approve: options.approve,
|
|
2040
|
+
auto: options.auto,
|
|
2041
|
+
denyCommands: options.denyCommands,
|
|
2042
|
+
capsule,
|
|
2043
|
+
getTaskCapsule: () => capsule,
|
|
2044
|
+
graphBackend: options.graphBackend,
|
|
2045
|
+
dirtyFiles: () => overlay ? overlay.dirtyFiles() : [],
|
|
2046
|
+
changedFiles: () => changes.map((c) => c.file),
|
|
2047
|
+
// B3: enforce-before-apply against the live graph trie.
|
|
2048
|
+
identifierTrie: identifierTrie ?? null,
|
|
2049
|
+
enforceIdentifiers: options.identifierMask !== false
|
|
2050
|
+
};
|
|
2051
|
+
const refreshSessionGraph = async () => {
|
|
2052
|
+
if (!overlay || !overlay.isDirty()) return;
|
|
2053
|
+
try {
|
|
2054
|
+
const session = await materializeSessionGraph(graph, overlay);
|
|
2055
|
+
ctx.graph = session.graph;
|
|
2056
|
+
ctx.graphBackend = localGraphBackend(session.graph);
|
|
2057
|
+
if (options.identifierMask !== false) {
|
|
2058
|
+
ctx.identifierTrie = buildIdentifierTrieFromGraph(session.graph);
|
|
2059
|
+
}
|
|
2060
|
+
const next = buildSpanIndex(session.graph);
|
|
2061
|
+
ctx.spans.clear();
|
|
2062
|
+
for (const [k, v] of next) ctx.spans.set(k, v);
|
|
2063
|
+
onEvent({ type: "session-graph", reparsed: session.reparsed.length, dirty: session.deleted.length + session.rewritten.length });
|
|
2064
|
+
if (options.capsule && capsule) {
|
|
2065
|
+
try {
|
|
2066
|
+
const nextCapsule = buildTaskCapsule(session.graph, instruction, {
|
|
2067
|
+
budget,
|
|
2068
|
+
files: options.files,
|
|
2069
|
+
readFile: (rel) => fsImpl.read(rel),
|
|
2070
|
+
repositoryId: repositoryIdFromRoot(root),
|
|
2071
|
+
provenance: {
|
|
2072
|
+
modelProfileId: options.modelProfile?.id ?? null,
|
|
2073
|
+
securityTier: options.executionEnv?.tier ?? options.modelProfile?.securityTier ?? null
|
|
2074
|
+
}
|
|
2075
|
+
});
|
|
2076
|
+
const delta = buildCapsuleDelta(capsule, nextCapsule);
|
|
2077
|
+
capsule = nextCapsule;
|
|
2078
|
+
ctx.capsule = nextCapsule;
|
|
2079
|
+
if (!isEmptyCapsuleDelta(delta)) {
|
|
2080
|
+
onEvent({ type: "capsule-delta", delta });
|
|
2081
|
+
messages.push({ role: "user", content: delta.rendered.slice(0, 6e3) });
|
|
2082
|
+
}
|
|
2083
|
+
} catch {
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
2086
|
+
} catch {
|
|
2087
|
+
}
|
|
2088
|
+
};
|
|
2089
|
+
const emitFailure = (fc) => {
|
|
2090
|
+
lastFailureCapsule = fc;
|
|
2091
|
+
trajectory.markFailureCapsule();
|
|
2092
|
+
onEvent({ type: "failure-capsule", capsule: fc });
|
|
2093
|
+
};
|
|
2094
|
+
const finish = (stopped, finalText, steps) => {
|
|
2095
|
+
flushOverlay();
|
|
2096
|
+
const solved = stopped === "finished" && lastVerifyPassed;
|
|
2097
|
+
const traj = trajectory.finalize({
|
|
2098
|
+
taskId: options.attribution?.client ? `${options.attribution.client}:${instruction.slice(0, 40)}` : instruction.slice(0, 48),
|
|
2099
|
+
arm: capsule ? "capsule" : "metadata",
|
|
2100
|
+
solved,
|
|
2101
|
+
verified: lastVerifyPassed,
|
|
2102
|
+
steps,
|
|
2103
|
+
stopped,
|
|
2104
|
+
inferenceTurns: currentStep || steps,
|
|
2105
|
+
filesChanged: changes.length,
|
|
2106
|
+
usedCapsule: !!capsule,
|
|
2107
|
+
provider: { id: providerInfo.id, model: providerInfo.model }
|
|
2108
|
+
});
|
|
2109
|
+
const metrics = {
|
|
2110
|
+
discoveryToolCalls: traj.discoveryToolCalls,
|
|
2111
|
+
mutationToolCalls: traj.mutationToolCalls,
|
|
2112
|
+
shellCommands: traj.shellCommands,
|
|
2113
|
+
znsAt1: traj.znsAt1,
|
|
2114
|
+
usedCapsule: !!capsule,
|
|
2115
|
+
failureCapsuleBuilt: traj.failureCapsuleBuilt,
|
|
2116
|
+
trajectory: traj
|
|
2117
|
+
};
|
|
2118
|
+
onEvent({ type: "metrics", metrics });
|
|
2119
|
+
const result = {
|
|
2120
|
+
finalText,
|
|
2121
|
+
changes,
|
|
2122
|
+
steps,
|
|
2123
|
+
stopped,
|
|
2124
|
+
provider: providerInfo,
|
|
2125
|
+
usage,
|
|
2126
|
+
metrics,
|
|
2127
|
+
failureCapsule: lastFailureCapsule,
|
|
2128
|
+
capsuleSummary: capsule ? summarizeCapsule(capsule) : capsuleSummary,
|
|
2129
|
+
provenance: buildRunProvenance({
|
|
2130
|
+
modelProfileId: options.modelProfile?.id ?? capsule?.provenance.modelProfileId ?? null,
|
|
2131
|
+
securityTier: options.executionEnv?.tier ?? options.modelProfile?.securityTier ?? capsule?.provenance.securityTier ?? null,
|
|
2132
|
+
graphCorpusHash: capsule?.provenance.graphCorpusHash ?? graph.provenance?.corpusHash ?? null,
|
|
2133
|
+
rankingVersion: capsule?.provenance.rankingVersion ?? CAPSULE_RANKING_VERSION,
|
|
2134
|
+
policyVersion: capsule?.provenance.policyVersion ?? CONTEXT_POLICY_VERSION,
|
|
2135
|
+
changes,
|
|
2136
|
+
verification: lastVerification
|
|
2137
|
+
})
|
|
2138
|
+
};
|
|
2139
|
+
if (!options.noAudit) {
|
|
2140
|
+
writeAgentAudit(
|
|
2141
|
+
options.fsImpl,
|
|
2142
|
+
{
|
|
2143
|
+
instruction,
|
|
2144
|
+
providerInfo,
|
|
2145
|
+
changes,
|
|
2146
|
+
commandCount,
|
|
2147
|
+
steps,
|
|
2148
|
+
stopped,
|
|
2149
|
+
provenance: result.provenance
|
|
2150
|
+
},
|
|
2151
|
+
now()
|
|
2152
|
+
);
|
|
2153
|
+
}
|
|
2154
|
+
return result;
|
|
2155
|
+
};
|
|
2156
|
+
const verifyOnFinish = () => {
|
|
2157
|
+
if (changes.length === 0) return "done";
|
|
2158
|
+
if (useLadder && capsule) {
|
|
2159
|
+
const steps = compileVerificationLadder(capsule.verificationPlan, {
|
|
2160
|
+
testCommand: verifyConfig?.command ?? options.testCommand
|
|
2161
|
+
});
|
|
2162
|
+
flushOverlay();
|
|
2163
|
+
const ladder = runVerificationLadder(steps, {
|
|
2164
|
+
readFile: (f) => options.fsImpl.read(f),
|
|
2165
|
+
run: runShell,
|
|
2166
|
+
// Prefer the dedicated verify command path below when both are set, so we
|
|
2167
|
+
// don't double-run the same test command.
|
|
2168
|
+
runCommands: !verifyConfig?.command
|
|
2169
|
+
});
|
|
2170
|
+
const summary = ladder.steps.map((s) => `${s.ok ? "\u2714" : "\u2717"} ${s.message}`).join("\n");
|
|
2171
|
+
onEvent({ type: "ladder", ok: ladder.ok, summary });
|
|
2172
|
+
lastVerification = ladder.steps.map((s) => ({
|
|
2173
|
+
kind: s.step?.kind ?? "ladder",
|
|
2174
|
+
command: s.step && "command" in s.step ? s.step.command ?? null : null,
|
|
2175
|
+
passed: !!s.ok,
|
|
2176
|
+
diagnostic: s.message ?? null
|
|
2177
|
+
}));
|
|
2178
|
+
if (!ladder.ok) {
|
|
2179
|
+
lastVerifyPassed = false;
|
|
2180
|
+
const fc2 = buildFailureCapsule({
|
|
2181
|
+
ladderSteps: ladder.steps,
|
|
2182
|
+
capsule,
|
|
2183
|
+
changedFiles: changes.map((c) => c.file),
|
|
2184
|
+
modelId: providerInfo.model,
|
|
2185
|
+
policyVersion: CONTEXT_POLICY_VERSION
|
|
2186
|
+
});
|
|
2187
|
+
emitFailure(fc2);
|
|
2188
|
+
if (verifyRounds <= 0) return "done";
|
|
2189
|
+
verifyRounds--;
|
|
2190
|
+
messages.push({
|
|
2191
|
+
role: "user",
|
|
2192
|
+
content: `The task isn't finished \u2014 verification ladder failed.
|
|
2193
|
+
|
|
2194
|
+
${fc2.rendered.slice(0, 8e3)}
|
|
2195
|
+
|
|
2196
|
+
Fix the issues, then call finish again.${verifyRounds === 0 ? " (last verification attempt)" : ""}`
|
|
2197
|
+
});
|
|
2198
|
+
return "retry";
|
|
2199
|
+
}
|
|
2200
|
+
}
|
|
2201
|
+
const v = verifyConfig;
|
|
2202
|
+
if (!v || verifyRounds <= 0) {
|
|
2203
|
+
lastVerifyPassed = true;
|
|
2204
|
+
return "done";
|
|
2205
|
+
}
|
|
2206
|
+
flushOverlay();
|
|
2207
|
+
const res = runShell(v.command);
|
|
2208
|
+
onEvent({ type: "verify", command: v.command, passed: res.exitCode === 0 });
|
|
2209
|
+
lastVerification = [
|
|
2210
|
+
...lastVerification,
|
|
2211
|
+
{
|
|
2212
|
+
kind: "command",
|
|
2213
|
+
command: v.command,
|
|
2214
|
+
passed: res.exitCode === 0,
|
|
2215
|
+
diagnostic: res.stdout?.slice(0, 4e3) ?? null
|
|
2216
|
+
}
|
|
2217
|
+
];
|
|
2218
|
+
if (res.exitCode === 0) {
|
|
2219
|
+
lastVerifyPassed = true;
|
|
2220
|
+
return "done";
|
|
2221
|
+
}
|
|
2222
|
+
lastVerifyPassed = false;
|
|
2223
|
+
const fc = buildFailureCapsule({
|
|
2224
|
+
verify: { command: v.command, exitCode: res.exitCode, stdout: res.stdout },
|
|
2225
|
+
capsule,
|
|
2226
|
+
changedFiles: changes.map((c) => c.file),
|
|
2227
|
+
modelId: providerInfo.model,
|
|
2228
|
+
policyVersion: CONTEXT_POLICY_VERSION
|
|
2229
|
+
});
|
|
2230
|
+
emitFailure(fc);
|
|
2231
|
+
verifyRounds--;
|
|
2232
|
+
messages.push({
|
|
2233
|
+
role: "user",
|
|
2234
|
+
content: `The task isn't finished \u2014 \`${v.command}\` failed (exit ${res.exitCode}).
|
|
2235
|
+
|
|
2236
|
+
${fc.rendered.slice(0, 8e3)}
|
|
2237
|
+
|
|
2238
|
+
Fix the failing tests, then call finish again.${verifyRounds === 0 ? " (last verification attempt)" : ""}`
|
|
2239
|
+
});
|
|
2240
|
+
return "retry";
|
|
2241
|
+
};
|
|
2242
|
+
for (let step = 1; step <= maxSteps; step++) {
|
|
2243
|
+
currentStep = step;
|
|
2244
|
+
onEvent({ type: "step", n: step });
|
|
2245
|
+
const compacted = compact(messages, contextBudget, changes);
|
|
2246
|
+
if (compacted.droppedRounds > 0) {
|
|
2247
|
+
messages.length = 0;
|
|
2248
|
+
messages.push(...compacted.messages);
|
|
2249
|
+
onEvent({ type: "compact", droppedRounds: compacted.droppedRounds });
|
|
2250
|
+
}
|
|
2251
|
+
let result;
|
|
2252
|
+
try {
|
|
2253
|
+
if (kvRegistry.size() === 0 && messages.length >= 2) {
|
|
2254
|
+
kvRegistry.commit(
|
|
2255
|
+
messages.slice(0, Math.min(3, messages.length)).map((m, i) => ({
|
|
2256
|
+
kind: i === 0 ? "system" : "context",
|
|
2257
|
+
text: typeof m.content === "string" ? m.content : ""
|
|
2258
|
+
}))
|
|
2259
|
+
);
|
|
2260
|
+
}
|
|
2261
|
+
const promptSegments = messages.map((m, i) => ({
|
|
2262
|
+
kind: m.role === "system" ? "system" : i < 3 ? "context" : m.role === "user" ? "user" : "assistant",
|
|
2263
|
+
text: typeof m.content === "string" ? m.content : ""
|
|
2264
|
+
}));
|
|
2265
|
+
const c = await complete(
|
|
2266
|
+
providers,
|
|
2267
|
+
messages,
|
|
2268
|
+
allTools,
|
|
2269
|
+
onEvent,
|
|
2270
|
+
options.stream,
|
|
2271
|
+
options.modelProfile,
|
|
2272
|
+
identifierTrie,
|
|
2273
|
+
draftCandidates,
|
|
2274
|
+
promptSegments
|
|
2275
|
+
);
|
|
2276
|
+
result = c.result;
|
|
2277
|
+
providerInfo = { id: c.provider.id, model: c.provider.model, fellBack: c.fellBack };
|
|
2278
|
+
} catch (e) {
|
|
2279
|
+
const msg = redactSecrets(e.message);
|
|
2280
|
+
onEvent({ type: "assistant", text: `error: ${msg}` });
|
|
2281
|
+
return finish("error", msg, step);
|
|
2282
|
+
}
|
|
2283
|
+
if (result.usage) {
|
|
2284
|
+
usage.promptTokens += result.usage.promptTokens ?? 0;
|
|
2285
|
+
usage.completionTokens += result.usage.completionTokens ?? 0;
|
|
2286
|
+
trajectory.recordUsage(result.usage.promptTokens ?? 0, result.usage.completionTokens ?? 0);
|
|
2287
|
+
onEvent({ type: "usage", promptTokens: result.usage.promptTokens ?? 0, completionTokens: result.usage.completionTokens ?? 0 });
|
|
2288
|
+
}
|
|
2289
|
+
if (result.text) onEvent({ type: "assistant", text: result.text });
|
|
2290
|
+
const toolCalls = result.toolCalls ?? [];
|
|
2291
|
+
if (toolCalls.length === 0) {
|
|
2292
|
+
messages.push({ role: "assistant", content: result.text });
|
|
2293
|
+
return finish("no-tools", result.text, step);
|
|
2294
|
+
}
|
|
2295
|
+
messages.push({ role: "assistant", content: result.text, toolCalls });
|
|
2296
|
+
for (const call of toolCalls) {
|
|
2297
|
+
onEvent({ type: "tool-call", name: call.name, args: call.arguments });
|
|
2298
|
+
if (call.name === "search_code" && options.attribution?.client && !recordedSearch) {
|
|
2299
|
+
recordedSearch = true;
|
|
2300
|
+
recordSearchSaving(root, context, options.attribution, now());
|
|
2301
|
+
}
|
|
2302
|
+
if (call.name === "run_command") flushOverlay();
|
|
2303
|
+
if (!options.externalTools?.owns(call.name)) {
|
|
2304
|
+
const gate = gateDiscoveryTool(call.name, discoveryGate, { advancedMode });
|
|
2305
|
+
if (!gate.allow) {
|
|
2306
|
+
const blocked = { content: gate.reason};
|
|
2307
|
+
onEvent({ type: "tool-result", name: call.name, content: blocked.content, mutated: false });
|
|
2308
|
+
messages.push({ role: "tool", content: blocked.content, toolCallId: call.id, name: call.name });
|
|
2309
|
+
continue;
|
|
2310
|
+
}
|
|
2311
|
+
if (gate.note) {
|
|
2312
|
+
call.__gateNote = gate.note;
|
|
2313
|
+
}
|
|
2314
|
+
}
|
|
2315
|
+
const toolResult = options.externalTools?.owns(call.name) ? await options.externalTools.execute(call) : await executeTool(call, ctx);
|
|
2316
|
+
if (!options.externalTools?.owns(call.name)) {
|
|
2317
|
+
recordDiscoveryGateOutcome(call.name, discoveryGate, { mutated: toolResult.mutated });
|
|
2318
|
+
}
|
|
2319
|
+
const gateNote = call.__gateNote;
|
|
2320
|
+
if (gateNote) toolResult.content = `${toolResult.content}
|
|
2321
|
+
|
|
2322
|
+
${gateNote}`;
|
|
2323
|
+
if (call.name === "run_command" && toolResult.mutated) commandCount++;
|
|
2324
|
+
if (toolResult.change) {
|
|
2325
|
+
changes.push(toolResult.change);
|
|
2326
|
+
onEvent({ type: "change", change: toolResult.change });
|
|
2327
|
+
}
|
|
2328
|
+
if (toolResult.mutated && !toolResult.finished) {
|
|
2329
|
+
await refreshSessionGraph();
|
|
2330
|
+
}
|
|
2331
|
+
const sig = `${call.name}:${stableArgs(call.arguments)}`;
|
|
2332
|
+
let content = toolResult.content;
|
|
2333
|
+
let stopNoProgress = false;
|
|
2334
|
+
if (toolResult.mutated || toolResult.finished) {
|
|
2335
|
+
repeats.delete(sig);
|
|
2336
|
+
} else {
|
|
2337
|
+
const n = (repeats.get(sig) ?? 0) + 1;
|
|
2338
|
+
repeats.set(sig, n);
|
|
2339
|
+
if (n >= STOP_AT) stopNoProgress = true;
|
|
2340
|
+
else if (n >= NUDGE_AT) content += `
|
|
2341
|
+
|
|
2342
|
+
(note: you have called this exact tool call ${n} times with no change \u2014 try a different approach, read more context, or call finish.)`;
|
|
2343
|
+
}
|
|
2344
|
+
trajectory.recordTool(call.name, step, toolResult.mutated);
|
|
2345
|
+
onEvent({ type: "tool-result", name: call.name, content, mutated: toolResult.mutated });
|
|
2346
|
+
messages.push({ role: "tool", content, toolCallId: call.id, name: call.name });
|
|
2347
|
+
if (toolResult.finished) {
|
|
2348
|
+
if (verifyOnFinish() === "retry") break;
|
|
2349
|
+
return finish("finished", toolResult.finalSummary ?? "done", step);
|
|
2350
|
+
}
|
|
2351
|
+
if (stopNoProgress) return finish("no-progress", `stopped: the model repeated \`${call.name}\` without making progress`, step);
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2354
|
+
return finish("max-steps", "reached the step limit before finishing", maxSteps);
|
|
2355
|
+
}
|
|
2356
|
+
async function complete(providers, messages, tools, onEvent, stream, modelProfile, identifierTrie, draftCandidates, promptSegments) {
|
|
2357
|
+
if (providers.length === 0) throw new Error("no model provider available");
|
|
2358
|
+
const onToken = stream ? (t) => onEvent({ type: "token", text: t }) : void 0;
|
|
2359
|
+
const grammar = shouldUsePatchIrGrammar(modelProfile) ? patchIrGbnf() : void 0;
|
|
2360
|
+
const requireGrammar = !!(grammar && modelProfile?.constrainedDecoding && providers.some((p) => p.id === "llama-cpp"));
|
|
2361
|
+
let lastErr;
|
|
2362
|
+
for (let i = 0; i < providers.length; i++) {
|
|
2363
|
+
try {
|
|
2364
|
+
const result = await providers[i].chat(messages, {
|
|
2365
|
+
temperature: 0,
|
|
2366
|
+
tools,
|
|
2367
|
+
stream,
|
|
2368
|
+
onToken,
|
|
2369
|
+
grammar,
|
|
2370
|
+
// Only require grammar on the llama-cpp provider; HTTP providers ignore it.
|
|
2371
|
+
requireGrammar: requireGrammar && providers[i].id === "llama-cpp",
|
|
2372
|
+
identifierTrie,
|
|
2373
|
+
draftCandidates,
|
|
2374
|
+
promptSegments
|
|
2375
|
+
});
|
|
2376
|
+
return { result, provider: providers[i], fellBack: i > 0 };
|
|
2377
|
+
} catch (e) {
|
|
2378
|
+
lastErr = e;
|
|
2379
|
+
}
|
|
2380
|
+
}
|
|
2381
|
+
throw new Error(lastErr instanceof Error ? redactSecrets(lastErr.message) : String(lastErr));
|
|
2382
|
+
}
|
|
2383
|
+
function compact(messages, budget, changes) {
|
|
2384
|
+
if (estimateTokens(messages) <= budget) return { messages, droppedRounds: 0 };
|
|
2385
|
+
const head = messages.slice(0, 3);
|
|
2386
|
+
const body = messages.slice(3);
|
|
2387
|
+
const rounds = [];
|
|
2388
|
+
for (const m of body) {
|
|
2389
|
+
if (m.role === "assistant" || rounds.length === 0) rounds.push([m]);
|
|
2390
|
+
else rounds[rounds.length - 1].push(m);
|
|
2391
|
+
}
|
|
2392
|
+
if (rounds.length <= KEEP_ROUNDS) return { messages, droppedRounds: 0 };
|
|
2393
|
+
const dropped = rounds.slice(0, rounds.length - KEEP_ROUNDS);
|
|
2394
|
+
const kept = rounds.slice(-KEEP_ROUNDS).flat();
|
|
2395
|
+
const toolCalls = dropped.flat().reduce((n, m) => n + (m.role === "assistant" ? m.toolCalls?.length ?? 0 : 0), 0);
|
|
2396
|
+
const filesChanged = [...new Set(changes.map((c) => c.file))];
|
|
2397
|
+
const note = {
|
|
2398
|
+
role: "user",
|
|
2399
|
+
content: `[earlier steps summarized to save context: ${dropped.length} round(s), ${toolCalls} tool call(s) omitted.${filesChanged.length ? ` Files changed so far: ${filesChanged.join(", ")}.` : ""} Continue the task.]`
|
|
2400
|
+
};
|
|
2401
|
+
return { messages: [...head, note, ...kept], droppedRounds: dropped.length };
|
|
2402
|
+
}
|
|
2403
|
+
function buildAgentContext(graph, instruction, options) {
|
|
2404
|
+
const budget = options.budget ?? 3e3;
|
|
2405
|
+
const files = options.files;
|
|
2406
|
+
if (!options.capsule) {
|
|
2407
|
+
return { context: buildCodeContext(graph, instruction, { budget, files }), capsule: null };
|
|
2408
|
+
}
|
|
2409
|
+
const capsule = buildTaskCapsule(graph, instruction, {
|
|
2410
|
+
budget,
|
|
2411
|
+
files,
|
|
2412
|
+
readFile: (rel) => options.fsImpl.read(rel),
|
|
2413
|
+
repositoryId: repositoryIdFromRoot(options.root),
|
|
2414
|
+
extraPinnedFacts: options.extraPinnedFacts,
|
|
2415
|
+
provenance: {
|
|
2416
|
+
modelProfileId: options.modelProfile?.id ?? null,
|
|
2417
|
+
securityTier: options.executionEnv?.tier ?? options.modelProfile?.securityTier ?? null,
|
|
2418
|
+
policyVersion: CONTEXT_POLICY_VERSION
|
|
2419
|
+
}
|
|
2420
|
+
});
|
|
2421
|
+
return { context: capsuleToCodeContext(capsule), capsule };
|
|
2422
|
+
}
|
|
2423
|
+
function recordSearchSaving(root, context, attribution, ts) {
|
|
2424
|
+
const files = new Set(context.seeds.map((s) => s.node.file).filter(Boolean));
|
|
2425
|
+
recordCliCall(
|
|
2426
|
+
root,
|
|
2427
|
+
{
|
|
2428
|
+
tool: CLI_TOOL_ALIASES.ask,
|
|
2429
|
+
client: attribution.client,
|
|
2430
|
+
provider: attribution.provider,
|
|
2431
|
+
model: attribution.model,
|
|
2432
|
+
outcome: context.seeds.length ? "complete" : "miss",
|
|
2433
|
+
vgTokens: context.tokensEstimate,
|
|
2434
|
+
baselineFiles: files.size
|
|
2435
|
+
},
|
|
2436
|
+
ts
|
|
2437
|
+
);
|
|
2438
|
+
}
|
|
2439
|
+
function writeAgentAudit(fsImpl, rec, ts) {
|
|
2440
|
+
try {
|
|
2441
|
+
fsImpl.appendAudit(
|
|
2442
|
+
JSON.stringify({
|
|
2443
|
+
ts,
|
|
2444
|
+
kind: "agent",
|
|
2445
|
+
instruction: rec.instruction.slice(0, 500),
|
|
2446
|
+
provider: rec.providerInfo.id,
|
|
2447
|
+
model: rec.providerInfo.model,
|
|
2448
|
+
steps: rec.steps,
|
|
2449
|
+
stopped: rec.stopped,
|
|
2450
|
+
commands: rec.commandCount,
|
|
2451
|
+
files: rec.changes.map((c) => ({ file: c.file, statuses: c.outcomes.map((o) => o.status) })),
|
|
2452
|
+
// Content hashes only — never file bodies or secrets.
|
|
2453
|
+
provenance: rec.provenance ? {
|
|
2454
|
+
schemaVersion: rec.provenance.schemaVersion,
|
|
2455
|
+
policyVersion: rec.provenance.policyVersion,
|
|
2456
|
+
rankingVersion: rec.provenance.rankingVersion,
|
|
2457
|
+
modelProfileId: rec.provenance.modelProfileId,
|
|
2458
|
+
securityTier: rec.provenance.securityTier,
|
|
2459
|
+
graphCorpusHash: rec.provenance.graphCorpusHash,
|
|
2460
|
+
mutationsRootHash: rec.provenance.mutationsRootHash,
|
|
2461
|
+
verificationRootHash: rec.provenance.verificationRootHash ?? null,
|
|
2462
|
+
mutationCount: rec.provenance.mutations.length,
|
|
2463
|
+
verificationCount: rec.provenance.verification?.length ?? 0
|
|
2464
|
+
} : void 0
|
|
2465
|
+
})
|
|
2466
|
+
);
|
|
2467
|
+
} catch {
|
|
2468
|
+
}
|
|
2469
|
+
}
|
|
2470
|
+
function stableArgs(args) {
|
|
2471
|
+
const keys = Object.keys(args).sort();
|
|
2472
|
+
return JSON.stringify(keys.map((k) => [k, args[k]]));
|
|
2473
|
+
}
|
|
2474
|
+
function estimateTokens(messages) {
|
|
2475
|
+
let chars = 0;
|
|
2476
|
+
for (const m of messages) {
|
|
2477
|
+
chars += m.content.length;
|
|
2478
|
+
if (m.role === "assistant" && m.toolCalls) chars += JSON.stringify(m.toolCalls).length;
|
|
2479
|
+
}
|
|
2480
|
+
return Math.ceil(chars / 4);
|
|
2481
|
+
}
|
|
2482
|
+
function buildSpanIndex(graph) {
|
|
2483
|
+
const map = /* @__PURE__ */ new Map();
|
|
2484
|
+
for (const n of graph.nodes) {
|
|
2485
|
+
if (n.kind === "file" || n.kind === "external") continue;
|
|
2486
|
+
const file = n.file.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
2487
|
+
const list = map.get(file) ?? [];
|
|
2488
|
+
list.push({ qualifiedName: n.qualifiedName, file, start: n.span.start, end: n.span.end });
|
|
2489
|
+
map.set(file, list);
|
|
2490
|
+
}
|
|
2491
|
+
return map;
|
|
2492
|
+
}
|
|
2493
|
+
|
|
2494
|
+
export { CONTEXT_POLICY_VERSION, computeFcsReport, createTrajectoryCollector, renderFcsReportMarkdown, runAgent };
|
|
2495
|
+
//# sourceMappingURL=chunk-3BVSDMHV.js.map
|
|
2496
|
+
//# sourceMappingURL=chunk-3BVSDMHV.js.map
|