@vibgrate/cli 2026.721.3 → 2026.722.2
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 +185 -0
- package/README.md +2 -1
- package/dist/baseline-BD3A7EXD.js +7 -0
- package/dist/{baseline-37G4TINM.js.map → baseline-BD3A7EXD.js.map} +1 -1
- package/dist/chunk-2O5YZVZV.js +417 -0
- package/dist/chunk-2O5YZVZV.js.map +1 -0
- package/dist/{chunk-ELUFCQDR.js → chunk-3MXNNBLI.js} +240 -2519
- package/dist/chunk-3MXNNBLI.js.map +1 -0
- package/dist/{chunk-JLRF5TFA.js → chunk-5BDHQ7DD.js} +42 -71
- package/dist/chunk-5BDHQ7DD.js.map +1 -0
- package/dist/chunk-6CXTPC74.js +234 -0
- package/dist/chunk-6CXTPC74.js.map +1 -0
- package/dist/chunk-CRAMOEBE.js +675 -0
- package/dist/chunk-CRAMOEBE.js.map +1 -0
- package/dist/chunk-CS37OBE3.js +861 -0
- package/dist/chunk-CS37OBE3.js.map +1 -0
- package/dist/chunk-CT5BS56U.js +23 -0
- package/dist/chunk-CT5BS56U.js.map +1 -0
- package/dist/chunk-GGJZA3Q6.js +961 -0
- package/dist/chunk-GGJZA3Q6.js.map +1 -0
- package/dist/chunk-I7GY7MVN.js +833 -0
- package/dist/chunk-I7GY7MVN.js.map +1 -0
- package/dist/chunk-JBXNQCGE.js +484 -0
- package/dist/chunk-JBXNQCGE.js.map +1 -0
- package/dist/chunk-JFGYT6BI.js +32 -0
- package/dist/chunk-JFGYT6BI.js.map +1 -0
- package/dist/chunk-JYS4OBN2.js +148 -0
- package/dist/chunk-JYS4OBN2.js.map +1 -0
- package/dist/chunk-K3SLGULW.js +50 -0
- package/dist/chunk-K3SLGULW.js.map +1 -0
- package/dist/chunk-LEPTUB4H.js +108 -0
- package/dist/chunk-LEPTUB4H.js.map +1 -0
- package/dist/{chunk-5PLKADD6.js → chunk-NONYJLOJ.js} +4 -3
- package/dist/chunk-NONYJLOJ.js.map +1 -0
- package/dist/{chunk-IKJBELUV.js → chunk-PY3DNX5H.js} +4 -29
- package/dist/chunk-PY3DNX5H.js.map +1 -0
- package/dist/chunk-WNIIKCNF.js +972 -0
- package/dist/chunk-WNIIKCNF.js.map +1 -0
- package/dist/cli.js +2512 -1224
- package/dist/cli.js.map +1 -1
- package/dist/fs-KCABDURV.js +3 -0
- package/dist/fs-KCABDURV.js.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +8 -3
- package/dist/index.js.map +1 -1
- package/dist/interactive-A4DISSIW.js +681 -0
- package/dist/interactive-A4DISSIW.js.map +1 -0
- package/dist/mcp-tools-I6PME3AV.js +3 -0
- package/dist/mcp-tools-I6PME3AV.js.map +1 -0
- package/dist/parse-worker.js +2 -1
- package/dist/parse-worker.js.map +1 -1
- package/dist/session-POJUDFPH.js +6 -0
- package/dist/session-POJUDFPH.js.map +1 -0
- package/dist/session-store-W5RZZHA3.js +3 -0
- package/dist/session-store-W5RZZHA3.js.map +1 -0
- package/dist/stream-json-Q2JGWPHB.js +65 -0
- package/dist/stream-json-Q2JGWPHB.js.map +1 -0
- package/dist/ui-HOVOPWMX.js +4 -0
- package/dist/ui-HOVOPWMX.js.map +1 -0
- package/package.json +1 -1
- package/dist/baseline-37G4TINM.js +0 -6
- package/dist/chunk-5PLKADD6.js.map +0 -1
- package/dist/chunk-ELUFCQDR.js.map +0 -1
- package/dist/chunk-IKJBELUV.js.map +0 -1
- package/dist/chunk-JLRF5TFA.js.map +0 -1
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { buildCodeContext, buildMessages, parseEdits, applyEdits, unifiedDiff, redactSecrets } from './chunk-CS37OBE3.js';
|
|
2
|
+
import { recordCliCall, CLI_TOOL_ALIASES } from './chunk-WNIIKCNF.js';
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
|
|
6
|
+
async function runCodeSession(options) {
|
|
7
|
+
const {
|
|
8
|
+
graph,
|
|
9
|
+
root,
|
|
10
|
+
instruction,
|
|
11
|
+
providers,
|
|
12
|
+
apply = false,
|
|
13
|
+
consent = false,
|
|
14
|
+
files,
|
|
15
|
+
budget,
|
|
16
|
+
chatOptions,
|
|
17
|
+
now = () => 0,
|
|
18
|
+
onPhase = () => {
|
|
19
|
+
}
|
|
20
|
+
} = options;
|
|
21
|
+
const fsImpl = options.fsImpl ?? nodeCodeFs(root);
|
|
22
|
+
const correlationId = options.correlationId ?? shortId();
|
|
23
|
+
const phasesRun = [];
|
|
24
|
+
const phase = (p, detail) => {
|
|
25
|
+
phasesRun.push(p);
|
|
26
|
+
onPhase(p, detail);
|
|
27
|
+
};
|
|
28
|
+
phase("inspect", "building graph-grounded context");
|
|
29
|
+
const context = buildCodeContext(graph, instruction, { budget, files });
|
|
30
|
+
if (options.attribution?.client) {
|
|
31
|
+
const seedFiles = new Set(context.seeds.map((s) => s.node.file).filter(Boolean));
|
|
32
|
+
recordCliCall(
|
|
33
|
+
root,
|
|
34
|
+
{
|
|
35
|
+
tool: CLI_TOOL_ALIASES.ask,
|
|
36
|
+
client: options.attribution.client,
|
|
37
|
+
provider: options.attribution.provider,
|
|
38
|
+
model: options.attribution.model,
|
|
39
|
+
outcome: context.seeds.length ? "complete" : "miss",
|
|
40
|
+
vgTokens: context.tokensEstimate,
|
|
41
|
+
baselineFiles: seedFiles.size
|
|
42
|
+
},
|
|
43
|
+
now()
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
phase("assess", "asking the routed model for a minimal edit");
|
|
47
|
+
const messages = buildMessages(context);
|
|
48
|
+
const { result, provider, fellBack } = await complete(providers, messages, chatOptions, correlationId);
|
|
49
|
+
const edits = parseEdits(result.text);
|
|
50
|
+
const spans = spanIndex(graph);
|
|
51
|
+
const applied = applyEdits(
|
|
52
|
+
edits,
|
|
53
|
+
(file) => fsImpl.read(file),
|
|
54
|
+
(file) => spans.get(normalize(file)) ?? []
|
|
55
|
+
);
|
|
56
|
+
const changes = [];
|
|
57
|
+
for (const [file, entry] of [...applied.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {
|
|
58
|
+
changes.push({
|
|
59
|
+
file,
|
|
60
|
+
before: entry.before,
|
|
61
|
+
after: entry.after,
|
|
62
|
+
outcomes: entry.outcomes,
|
|
63
|
+
diff: unifiedDiff(entry.before, entry.after, file)
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
phase("dry-run", `${changes.length} file(s) would change`);
|
|
67
|
+
const anyApplicable = changes.some((c) => c.diff !== "" && c.outcomes.some((o) => o.status === "applied"));
|
|
68
|
+
const base = {
|
|
69
|
+
instruction,
|
|
70
|
+
provider: { id: provider.id, model: provider.model, local: provider.local, fellBack },
|
|
71
|
+
phasesRun,
|
|
72
|
+
changes,
|
|
73
|
+
applied: false,
|
|
74
|
+
verification: { ran: false, ok: false, detail: "dry-run \u2014 no changes written" },
|
|
75
|
+
correlationId
|
|
76
|
+
};
|
|
77
|
+
if (!apply) {
|
|
78
|
+
if (!options.noAudit) writeAudit(fsImpl, base, now());
|
|
79
|
+
return base;
|
|
80
|
+
}
|
|
81
|
+
if (!consent) {
|
|
82
|
+
const stopped = { ...base, verification: { ran: false, ok: false, detail: "apply requested without consent \u2014 re-run with --yes to write these changes" } };
|
|
83
|
+
if (!options.noAudit) writeAudit(fsImpl, stopped, now());
|
|
84
|
+
return stopped;
|
|
85
|
+
}
|
|
86
|
+
if (!anyApplicable) {
|
|
87
|
+
const nothing = { ...base, verification: { ran: false, ok: false, detail: "nothing to write \u2014 no edit applied cleanly" } };
|
|
88
|
+
if (!options.noAudit) writeAudit(fsImpl, nothing, now());
|
|
89
|
+
return nothing;
|
|
90
|
+
}
|
|
91
|
+
phase("approve", "consent given (--yes)");
|
|
92
|
+
phase("execute", "writing approved changes");
|
|
93
|
+
for (const change of changes) {
|
|
94
|
+
if (change.diff === "") continue;
|
|
95
|
+
if (!change.outcomes.some((o) => o.status === "applied")) continue;
|
|
96
|
+
if (change.after === null) fsImpl.remove(change.file);
|
|
97
|
+
else fsImpl.write(change.file, change.after);
|
|
98
|
+
}
|
|
99
|
+
phase("verify", "confirming on-disk content matches the approved change");
|
|
100
|
+
const verification = verifyWrites(fsImpl, changes);
|
|
101
|
+
phase("log", "recording an audit entry");
|
|
102
|
+
const final = { ...base, applied: true, verification };
|
|
103
|
+
if (!options.noAudit) writeAudit(fsImpl, final, now());
|
|
104
|
+
return final;
|
|
105
|
+
}
|
|
106
|
+
function commitChanges(result, fsImpl, now = () => 0) {
|
|
107
|
+
const anyApplicable = result.changes.some((c) => c.diff !== "" && c.outcomes.some((o) => o.status === "applied"));
|
|
108
|
+
if (!anyApplicable) {
|
|
109
|
+
return { ...result, applied: false, verification: { ran: false, ok: false, detail: "nothing to write \u2014 no edit applied cleanly" } };
|
|
110
|
+
}
|
|
111
|
+
for (const change of result.changes) {
|
|
112
|
+
if (change.diff === "" || !change.outcomes.some((o) => o.status === "applied")) continue;
|
|
113
|
+
if (change.after === null) fsImpl.remove(change.file);
|
|
114
|
+
else fsImpl.write(change.file, change.after);
|
|
115
|
+
}
|
|
116
|
+
const verification = verifyWrites(fsImpl, result.changes);
|
|
117
|
+
const final = { ...result, applied: true, verification, phasesRun: [...result.phasesRun, "approve", "execute", "verify", "log"] };
|
|
118
|
+
writeAudit(fsImpl, final, now());
|
|
119
|
+
return final;
|
|
120
|
+
}
|
|
121
|
+
function undoChanges(changes, fsImpl) {
|
|
122
|
+
const restored = [];
|
|
123
|
+
for (const ch of changes) {
|
|
124
|
+
if (!ch.outcomes.some((o) => o.status === "applied")) continue;
|
|
125
|
+
if (ch.before === null) fsImpl.remove(ch.file);
|
|
126
|
+
else fsImpl.write(ch.file, ch.before);
|
|
127
|
+
restored.push(ch.file);
|
|
128
|
+
}
|
|
129
|
+
return restored;
|
|
130
|
+
}
|
|
131
|
+
async function complete(providers, messages, chatOptions, correlationId) {
|
|
132
|
+
if (providers.length === 0) throw new Error(`no model provider available (ref ${correlationId})`);
|
|
133
|
+
let lastErr;
|
|
134
|
+
for (let i = 0; i < providers.length; i++) {
|
|
135
|
+
const provider = providers[i];
|
|
136
|
+
try {
|
|
137
|
+
const result = await provider.chat(messages, { temperature: 0, ...chatOptions });
|
|
138
|
+
return { result, provider, fellBack: i > 0 };
|
|
139
|
+
} catch (e) {
|
|
140
|
+
lastErr = e;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
const message = lastErr instanceof Error ? redactSecrets(lastErr.message) : String(lastErr);
|
|
144
|
+
throw new Error(`${message} (ref ${correlationId})`);
|
|
145
|
+
}
|
|
146
|
+
function verifyWrites(fsImpl, changes) {
|
|
147
|
+
let checked = 0;
|
|
148
|
+
const mismatches = [];
|
|
149
|
+
for (const change of changes) {
|
|
150
|
+
if (change.diff === "" || !change.outcomes.some((o) => o.status === "applied")) continue;
|
|
151
|
+
checked++;
|
|
152
|
+
const onDisk = fsImpl.read(change.file);
|
|
153
|
+
if (onDisk !== change.after) mismatches.push(change.file);
|
|
154
|
+
}
|
|
155
|
+
if (mismatches.length) return { ran: true, ok: false, detail: `on-disk content differs for: ${mismatches.join(", ")}` };
|
|
156
|
+
return { ran: true, ok: true, detail: `wrote and verified ${checked} file(s)` };
|
|
157
|
+
}
|
|
158
|
+
function writeAudit(fsImpl, r, ts) {
|
|
159
|
+
const record = {
|
|
160
|
+
ts,
|
|
161
|
+
correlationId: r.correlationId,
|
|
162
|
+
instruction: r.instruction.slice(0, 500),
|
|
163
|
+
provider: r.provider.id,
|
|
164
|
+
model: r.provider.model,
|
|
165
|
+
local: r.provider.local,
|
|
166
|
+
applied: r.applied,
|
|
167
|
+
files: r.changes.map((c) => ({ file: c.file, statuses: c.outcomes.map((o) => o.status) })),
|
|
168
|
+
verification: r.verification,
|
|
169
|
+
phases: r.phasesRun
|
|
170
|
+
};
|
|
171
|
+
try {
|
|
172
|
+
fsImpl.appendAudit(JSON.stringify(record));
|
|
173
|
+
} catch {
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
function spanIndex(graph) {
|
|
177
|
+
const map = /* @__PURE__ */ new Map();
|
|
178
|
+
for (const n of graph.nodes) {
|
|
179
|
+
if (n.kind === "file" || n.kind === "external") continue;
|
|
180
|
+
const file = normalize(n.file);
|
|
181
|
+
const list = map.get(file) ?? [];
|
|
182
|
+
list.push({ qualifiedName: n.qualifiedName, file, start: n.span.start, end: n.span.end });
|
|
183
|
+
map.set(file, list);
|
|
184
|
+
}
|
|
185
|
+
return map;
|
|
186
|
+
}
|
|
187
|
+
function normalize(file) {
|
|
188
|
+
return file.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
189
|
+
}
|
|
190
|
+
function nodeCodeFs(root) {
|
|
191
|
+
const resolve2 = (file) => {
|
|
192
|
+
const abs = path.resolve(root, file);
|
|
193
|
+
const rel = path.relative(root, abs);
|
|
194
|
+
if (rel.startsWith("..") || path.isAbsolute(rel)) {
|
|
195
|
+
throw new Error(`edit path escapes the project root: ${file} \u2014 refusing to touch files outside ${root}`);
|
|
196
|
+
}
|
|
197
|
+
return abs;
|
|
198
|
+
};
|
|
199
|
+
return {
|
|
200
|
+
read(file) {
|
|
201
|
+
try {
|
|
202
|
+
return fs.readFileSync(resolve2(file), "utf8");
|
|
203
|
+
} catch {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
write(file, content) {
|
|
208
|
+
const abs = resolve2(file);
|
|
209
|
+
fs.mkdirSync(path.dirname(abs), { recursive: true });
|
|
210
|
+
fs.writeFileSync(abs, content);
|
|
211
|
+
},
|
|
212
|
+
remove(file) {
|
|
213
|
+
try {
|
|
214
|
+
fs.rmSync(resolve2(file));
|
|
215
|
+
} catch {
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
appendAudit(line) {
|
|
219
|
+
const dir = path.join(root, ".vibgrate");
|
|
220
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
221
|
+
fs.appendFileSync(path.join(dir, "code-audit.jsonl"), line + "\n");
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
function shortId() {
|
|
226
|
+
let s = "";
|
|
227
|
+
const alphabet = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
228
|
+
for (let i = 0; i < 8; i++) s += alphabet[Math.floor(Math.random() * alphabet.length)];
|
|
229
|
+
return s;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export { commitChanges, nodeCodeFs, runCodeSession, undoChanges };
|
|
233
|
+
//# sourceMappingURL=chunk-6CXTPC74.js.map
|
|
234
|
+
//# sourceMappingURL=chunk-6CXTPC74.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/code/session.ts"],"names":["resolve"],"mappings":";;;;;AA4EA,eAAsB,eAAe,OAAA,EAAwD;AAC3F,EAAA,MAAM;AAAA,IACJ,KAAA;AAAA,IACA,IAAA;AAAA,IACA,WAAA;AAAA,IACA,SAAA;AAAA,IACA,KAAA,GAAQ,KAAA;AAAA,IACR,OAAA,GAAU,KAAA;AAAA,IACV,KAAA;AAAA,IACA,MAAA;AAAA,IACA,WAAA;AAAA,IACA,MAAM,MAAM,CAAA;AAAA,IACZ,UAAU,MAAM;AAAA,IAAC;AAAA,GACnB,GAAI,OAAA;AACJ,EAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,MAAA,IAAU,UAAA,CAAW,IAAI,CAAA;AAChD,EAAA,MAAM,aAAA,GAAgB,OAAA,CAAQ,aAAA,IAAiB,OAAA,EAAQ;AACvD,EAAA,MAAM,YAA8B,EAAC;AACrC,EAAA,MAAM,KAAA,GAAQ,CAAC,CAAA,EAAmB,MAAA,KAAyB;AACzD,IAAA,SAAA,CAAU,KAAK,CAAC,CAAA;AAChB,IAAA,OAAA,CAAQ,GAAG,MAAM,CAAA;AAAA,EACnB,CAAA;AAGA,EAAA,KAAA,CAAM,WAAW,iCAAiC,CAAA;AAClD,EAAA,MAAM,UAAU,gBAAA,CAAiB,KAAA,EAAO,aAAa,EAAE,MAAA,EAAQ,OAAO,CAAA;AAKtE,EAAA,IAAI,OAAA,CAAQ,aAAa,MAAA,EAAQ;AAC/B,IAAA,MAAM,SAAA,GAAY,IAAI,GAAA,CAAI,OAAA,CAAQ,MAAM,GAAA,CAAI,CAAC,CAAA,KAAM,CAAA,CAAE,IAAA,CAAK,IAAI,CAAA,CAAE,MAAA,CAAO,OAAO,CAAC,CAAA;AAC/E,IAAA,aAAA;AAAA,MACE,IAAA;AAAA,MACA;AAAA,QACE,MAAM,gBAAA,CAAiB,GAAA;AAAA,QACvB,MAAA,EAAQ,QAAQ,WAAA,CAAY,MAAA;AAAA,QAC5B,QAAA,EAAU,QAAQ,WAAA,CAAY,QAAA;AAAA,QAC9B,KAAA,EAAO,QAAQ,WAAA,CAAY,KAAA;AAAA,QAC3B,OAAA,EAAS,OAAA,CAAQ,KAAA,CAAM,MAAA,GAAS,UAAA,GAAa,MAAA;AAAA,QAC7C,UAAU,OAAA,CAAQ,cAAA;AAAA,QAClB,eAAe,SAAA,CAAU;AAAA,OAC3B;AAAA,MACA,GAAA;AAAI,KACN;AAAA,EACF;AAGA,EAAA,KAAA,CAAM,UAAU,4CAA4C,CAAA;AAC5D,EAAA,MAAM,QAAA,GAAW,cAAc,OAAO,CAAA;AACtC,EAAA,MAAM,EAAE,MAAA,EAAQ,QAAA,EAAU,QAAA,EAAS,GAAI,MAAM,QAAA,CAAS,SAAA,EAAW,QAAA,EAAU,WAAA,EAAa,aAAa,CAAA;AAErG,EAAA,MAAM,KAAA,GAAQ,UAAA,CAAW,MAAA,CAAO,IAAI,CAAA;AACpC,EAAA,MAAM,KAAA,GAAQ,UAAU,KAAK,CAAA;AAC7B,EAAA,MAAM,OAAA,GAAU,UAAA;AAAA,IACd,KAAA;AAAA,IACA,CAAC,IAAA,KAAS,MAAA,CAAO,IAAA,CAAK,IAAI,CAAA;AAAA,IAC1B,CAAC,SAAS,KAAA,CAAM,GAAA,CAAI,UAAU,IAAI,CAAC,KAAK;AAAC,GAC3C;AAEA,EAAA,MAAM,UAAwB,EAAC;AAC/B,EAAA,KAAA,MAAW,CAAC,MAAM,KAAK,CAAA,IAAK,CAAC,GAAG,OAAA,CAAQ,OAAA,EAAS,CAAA,CAAE,IAAA,CAAK,CAAC,CAAA,EAAG,CAAA,KAAM,EAAE,CAAC,CAAA,CAAE,cAAc,CAAA,CAAE,CAAC,CAAC,CAAC,CAAA,EAAG;AAC3F,IAAA,OAAA,CAAQ,IAAA,CAAK;AAAA,MACX,IAAA;AAAA,MACA,QAAQ,KAAA,CAAM,MAAA;AAAA,MACd,OAAO,KAAA,CAAM,KAAA;AAAA,MACb,UAAU,KAAA,CAAM,QAAA;AAAA,MAChB,MAAM,WAAA,CAAY,KAAA,CAAM,MAAA,EAAQ,KAAA,CAAM,OAAO,IAAI;AAAA,KAClD,CAAA;AAAA,EACH;AAGA,EAAA,KAAA,CAAM,SAAA,EAAW,CAAA,EAAG,OAAA,CAAQ,MAAM,CAAA,qBAAA,CAAuB,CAAA;AAEzD,EAAA,MAAM,gBAAgB,OAAA,CAAQ,IAAA,CAAK,CAAC,CAAA,KAAM,EAAE,IAAA,KAAS,EAAA,IAAM,CAAA,CAAE,QAAA,CAAS,KAAK,CAAC,CAAA,KAAM,CAAA,CAAE,MAAA,KAAW,SAAS,CAAC,CAAA;AACzG,EAAA,MAAM,IAAA,GAA0B;AAAA,IAC9B,WAAA;AAAA,IACA,QAAA,EAAU,EAAE,EAAA,EAAI,QAAA,CAAS,EAAA,EAAI,KAAA,EAAO,QAAA,CAAS,KAAA,EAAO,KAAA,EAAO,QAAA,CAAS,KAAA,EAAO,QAAA,EAAS;AAAA,IACpF,SAAA;AAAA,IACA,OAAA;AAAA,IACA,OAAA,EAAS,KAAA;AAAA,IACT,cAAc,EAAE,GAAA,EAAK,OAAO,EAAA,EAAI,KAAA,EAAO,QAAQ,mCAAA,EAA+B;AAAA,IAC9E;AAAA,GACF;AAMA,EAAA,IAAI,CAAC,KAAA,EAAO;AACV,IAAA,IAAI,CAAC,OAAA,CAAQ,OAAA,aAAoB,MAAA,EAAQ,IAAA,EAAM,KAAK,CAAA;AACpD,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,IAAI,CAAC,OAAA,EAAS;AACZ,IAAA,MAAM,OAAA,GAAU,EAAE,GAAG,IAAA,EAAM,YAAA,EAAc,EAAE,GAAA,EAAK,KAAA,EAAO,EAAA,EAAI,KAAA,EAAO,MAAA,EAAQ,iFAAA,EAA6E,EAAE;AACzJ,IAAA,IAAI,CAAC,OAAA,CAAQ,OAAA,aAAoB,MAAA,EAAQ,OAAA,EAAS,KAAK,CAAA;AACvD,IAAA,OAAO,OAAA;AAAA,EACT;AACA,EAAA,IAAI,CAAC,aAAA,EAAe;AAClB,IAAA,MAAM,OAAA,GAAU,EAAE,GAAG,IAAA,EAAM,YAAA,EAAc,EAAE,GAAA,EAAK,KAAA,EAAO,EAAA,EAAI,KAAA,EAAO,MAAA,EAAQ,iDAAA,EAA6C,EAAE;AACzH,IAAA,IAAI,CAAC,OAAA,CAAQ,OAAA,aAAoB,MAAA,EAAQ,OAAA,EAAS,KAAK,CAAA;AACvD,IAAA,OAAO,OAAA;AAAA,EACT;AACA,EAAA,KAAA,CAAM,WAAW,uBAAuB,CAAA;AAGxC,EAAA,KAAA,CAAM,WAAW,0BAA0B,CAAA;AAC3C,EAAA,KAAA,MAAW,UAAU,OAAA,EAAS;AAC5B,IAAA,IAAI,MAAA,CAAO,SAAS,EAAA,EAAI;AACxB,IAAA,IAAI,CAAC,OAAO,QAAA,CAAS,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,MAAA,KAAW,SAAS,CAAA,EAAG;AAC1D,IAAA,IAAI,OAAO,KAAA,KAAU,IAAA,EAAM,MAAA,CAAO,MAAA,CAAO,OAAO,IAAI,CAAA;AAAA,SAC/C,MAAA,CAAO,KAAA,CAAM,MAAA,CAAO,IAAA,EAAM,OAAO,KAAK,CAAA;AAAA,EAC7C;AAGA,EAAA,KAAA,CAAM,UAAU,wDAAwD,CAAA;AACxE,EAAA,MAAM,YAAA,GAAe,YAAA,CAAa,MAAA,EAAQ,OAAO,CAAA;AAGjD,EAAA,KAAA,CAAM,OAAO,0BAA0B,CAAA;AACvC,EAAA,MAAM,QAA2B,EAAE,GAAG,IAAA,EAAM,OAAA,EAAS,MAAM,YAAA,EAAa;AACxE,EAAA,IAAI,CAAC,OAAA,CAAQ,OAAA,aAAoB,MAAA,EAAQ,KAAA,EAAO,KAAK,CAAA;AACrD,EAAA,OAAO,KAAA;AACT;AASO,SAAS,aAAA,CAAc,MAAA,EAA2B,MAAA,EAAgB,GAAA,GAAoB,MAAM,CAAA,EAAsB;AACvH,EAAA,MAAM,gBAAgB,MAAA,CAAO,OAAA,CAAQ,IAAA,CAAK,CAAC,MAAM,CAAA,CAAE,IAAA,KAAS,EAAA,IAAM,CAAA,CAAE,SAAS,IAAA,CAAK,CAAC,MAAM,CAAA,CAAE,MAAA,KAAW,SAAS,CAAC,CAAA;AAChH,EAAA,IAAI,CAAC,aAAA,EAAe;AAClB,IAAA,OAAO,EAAE,GAAG,MAAA,EAAQ,OAAA,EAAS,KAAA,EAAO,YAAA,EAAc,EAAE,GAAA,EAAK,KAAA,EAAO,EAAA,EAAI,KAAA,EAAO,MAAA,EAAQ,mDAA6C,EAAE;AAAA,EACpI;AACA,EAAA,KAAA,MAAW,MAAA,IAAU,OAAO,OAAA,EAAS;AACnC,IAAA,IAAI,MAAA,CAAO,IAAA,KAAS,EAAA,IAAM,CAAC,MAAA,CAAO,QAAA,CAAS,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,MAAA,KAAW,SAAS,CAAA,EAAG;AAChF,IAAA,IAAI,OAAO,KAAA,KAAU,IAAA,EAAM,MAAA,CAAO,MAAA,CAAO,OAAO,IAAI,CAAA;AAAA,SAC/C,MAAA,CAAO,KAAA,CAAM,MAAA,CAAO,IAAA,EAAM,OAAO,KAAK,CAAA;AAAA,EAC7C;AACA,EAAA,MAAM,YAAA,GAAe,YAAA,CAAa,MAAA,EAAQ,MAAA,CAAO,OAAO,CAAA;AACxD,EAAA,MAAM,QAA2B,EAAE,GAAG,MAAA,EAAQ,OAAA,EAAS,MAAM,YAAA,EAAc,SAAA,EAAW,CAAC,GAAG,OAAO,SAAA,EAAW,SAAA,EAAW,SAAA,EAAW,QAAA,EAAU,KAAK,CAAA,EAAE;AACnJ,EAAA,UAAA,CAAW,MAAA,EAAQ,KAAA,EAAO,GAAA,EAAK,CAAA;AAC/B,EAAA,OAAO,KAAA;AACT;AAQO,SAAS,WAAA,CAAY,SAAuB,MAAA,EAA0B;AAC3E,EAAA,MAAM,WAAqB,EAAC;AAC5B,EAAA,KAAA,MAAW,MAAM,OAAA,EAAS;AACxB,IAAA,IAAI,CAAC,GAAG,QAAA,CAAS,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,MAAA,KAAW,SAAS,CAAA,EAAG;AACtD,IAAA,IAAI,GAAG,MAAA,KAAW,IAAA,EAAM,MAAA,CAAO,MAAA,CAAO,GAAG,IAAI,CAAA;AAAA,SACxC,MAAA,CAAO,KAAA,CAAM,EAAA,CAAG,IAAA,EAAM,GAAG,MAAM,CAAA;AACpC,IAAA,QAAA,CAAS,IAAA,CAAK,GAAG,IAAI,CAAA;AAAA,EACvB;AACA,EAAA,OAAO,QAAA;AACT;AAGA,eAAe,QAAA,CACb,SAAA,EACA,QAAA,EACA,WAAA,EACA,aAAA,EAC4E;AAC5E,EAAA,IAAI,SAAA,CAAU,WAAW,CAAA,EAAG,MAAM,IAAI,KAAA,CAAM,CAAA,iCAAA,EAAoC,aAAa,CAAA,CAAA,CAAG,CAAA;AAChG,EAAA,IAAI,OAAA;AACJ,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,SAAA,CAAU,QAAQ,CAAA,EAAA,EAAK;AACzC,IAAA,MAAM,QAAA,GAAW,UAAU,CAAC,CAAA;AAC5B,IAAA,IAAI;AACF,MAAA,MAAM,MAAA,GAAS,MAAM,QAAA,CAAS,IAAA,CAAK,QAAA,EAAU,EAAE,WAAA,EAAa,CAAA,EAAG,GAAG,WAAA,EAAa,CAAA;AAC/E,MAAA,OAAO,EAAE,MAAA,EAAQ,QAAA,EAAU,QAAA,EAAU,IAAI,CAAA,EAAE;AAAA,IAC7C,SAAS,CAAA,EAAG;AACV,MAAA,OAAA,GAAU,CAAA;AAAA,IACZ;AAAA,EACF;AACA,EAAA,MAAM,OAAA,GAAU,mBAAmB,KAAA,GAAQ,aAAA,CAAc,QAAQ,OAAO,CAAA,GAAI,OAAO,OAAO,CAAA;AAC1F,EAAA,MAAM,IAAI,KAAA,CAAM,CAAA,EAAG,OAAO,CAAA,MAAA,EAAS,aAAa,CAAA,CAAA,CAAG,CAAA;AACrD;AAGA,SAAS,YAAA,CAAa,QAAgB,OAAA,EAAsE;AAC1G,EAAA,IAAI,OAAA,GAAU,CAAA;AACd,EAAA,MAAM,aAAuB,EAAC;AAC9B,EAAA,KAAA,MAAW,UAAU,OAAA,EAAS;AAC5B,IAAA,IAAI,MAAA,CAAO,IAAA,KAAS,EAAA,IAAM,CAAC,MAAA,CAAO,QAAA,CAAS,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,MAAA,KAAW,SAAS,CAAA,EAAG;AAChF,IAAA,OAAA,EAAA;AACA,IAAA,MAAM,MAAA,GAAS,MAAA,CAAO,IAAA,CAAK,MAAA,CAAO,IAAI,CAAA;AACtC,IAAA,IAAI,WAAW,MAAA,CAAO,KAAA,EAAO,UAAA,CAAW,IAAA,CAAK,OAAO,IAAI,CAAA;AAAA,EAC1D;AACA,EAAA,IAAI,UAAA,CAAW,MAAA,EAAQ,OAAO,EAAE,KAAK,IAAA,EAAM,EAAA,EAAI,KAAA,EAAO,MAAA,EAAQ,CAAA,6BAAA,EAAgC,UAAA,CAAW,IAAA,CAAK,IAAI,CAAC,CAAA,CAAA,EAAG;AACtH,EAAA,OAAO,EAAE,KAAK,IAAA,EAAM,EAAA,EAAI,MAAM,MAAA,EAAQ,CAAA,mBAAA,EAAsB,OAAO,CAAA,QAAA,CAAA,EAAW;AAChF;AAGA,SAAS,UAAA,CAAW,MAAA,EAAgB,CAAA,EAAsB,EAAA,EAAkB;AAC1E,EAAA,MAAM,MAAA,GAAS;AAAA,IACb,EAAA;AAAA,IACA,eAAe,CAAA,CAAE,aAAA;AAAA,IACjB,WAAA,EAAa,CAAA,CAAE,WAAA,CAAY,KAAA,CAAM,GAAG,GAAG,CAAA;AAAA,IACvC,QAAA,EAAU,EAAE,QAAA,CAAS,EAAA;AAAA,IACrB,KAAA,EAAO,EAAE,QAAA,CAAS,KAAA;AAAA,IAClB,KAAA,EAAO,EAAE,QAAA,CAAS,KAAA;AAAA,IAClB,SAAS,CAAA,CAAE,OAAA;AAAA,IACX,OAAO,CAAA,CAAE,OAAA,CAAQ,IAAI,CAAC,CAAA,MAAO,EAAE,IAAA,EAAM,CAAA,CAAE,MAAM,QAAA,EAAU,CAAA,CAAE,SAAS,GAAA,CAAI,CAAC,MAAM,CAAA,CAAE,MAAM,GAAE,CAAE,CAAA;AAAA,IACzF,cAAc,CAAA,CAAE,YAAA;AAAA,IAChB,QAAQ,CAAA,CAAE;AAAA,GACZ;AACA,EAAA,IAAI;AACF,IAAA,MAAA,CAAO,WAAA,CAAY,IAAA,CAAK,SAAA,CAAU,MAAM,CAAC,CAAA;AAAA,EAC3C,CAAA,CAAA,MAAQ;AAAA,EAER;AACF;AAGA,SAAS,UAAU,KAAA,EAA2C;AAC5D,EAAA,MAAM,GAAA,uBAAU,GAAA,EAA0B;AAC1C,EAAA,KAAA,MAAW,CAAA,IAAK,MAAM,KAAA,EAAO;AAC3B,IAAA,IAAI,CAAA,CAAE,IAAA,KAAS,MAAA,IAAU,CAAA,CAAE,SAAS,UAAA,EAAY;AAChD,IAAA,MAAM,IAAA,GAAO,SAAA,CAAU,CAAA,CAAE,IAAI,CAAA;AAC7B,IAAA,MAAM,IAAA,GAAO,GAAA,CAAI,GAAA,CAAI,IAAI,KAAK,EAAC;AAC/B,IAAA,IAAA,CAAK,IAAA,CAAK,EAAE,aAAA,EAAe,CAAA,CAAE,eAAe,IAAA,EAAM,KAAA,EAAO,CAAA,CAAE,IAAA,CAAK,KAAA,EAAO,GAAA,EAAK,CAAA,CAAE,IAAA,CAAK,KAAK,CAAA;AACxF,IAAA,GAAA,CAAI,GAAA,CAAI,MAAM,IAAI,CAAA;AAAA,EACpB;AACA,EAAA,OAAO,GAAA;AACT;AAEA,SAAS,UAAU,IAAA,EAAsB;AACvC,EAAA,OAAO,KAAK,OAAA,CAAQ,KAAA,EAAO,GAAG,CAAA,CAAE,OAAA,CAAQ,SAAS,EAAE,CAAA;AACrD;AAGO,SAAS,WAAW,IAAA,EAAsB;AAC/C,EAAA,MAAMA,QAAAA,GAAU,CAAC,IAAA,KAAyB;AACxC,IAAA,MAAM,GAAA,GAAW,IAAA,CAAA,OAAA,CAAQ,IAAA,EAAM,IAAI,CAAA;AACnC,IAAA,MAAM,GAAA,GAAW,IAAA,CAAA,QAAA,CAAS,IAAA,EAAM,GAAG,CAAA;AACnC,IAAA,IAAI,IAAI,UAAA,CAAW,IAAI,CAAA,IAAU,IAAA,CAAA,UAAA,CAAW,GAAG,CAAA,EAAG;AAChD,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,oCAAA,EAAuC,IAAI,CAAA,wCAAA,EAAsC,IAAI,CAAA,CAAE,CAAA;AAAA,IACzG;AACA,IAAA,OAAO,GAAA;AAAA,EACT,CAAA;AACA,EAAA,OAAO;AAAA,IACL,KAAK,IAAA,EAAM;AACT,MAAA,IAAI;AACF,QAAA,OAAU,EAAA,CAAA,YAAA,CAAaA,QAAAA,CAAQ,IAAI,CAAA,EAAG,MAAM,CAAA;AAAA,MAC9C,CAAA,CAAA,MAAQ;AACN,QAAA,OAAO,IAAA;AAAA,MACT;AAAA,IACF,CAAA;AAAA,IACA,KAAA,CAAM,MAAM,OAAA,EAAS;AACnB,MAAA,MAAM,GAAA,GAAMA,SAAQ,IAAI,CAAA;AACxB,MAAG,aAAe,IAAA,CAAA,OAAA,CAAQ,GAAG,GAAG,EAAE,SAAA,EAAW,MAAM,CAAA;AACnD,MAAG,EAAA,CAAA,aAAA,CAAc,KAAK,OAAO,CAAA;AAAA,IAC/B,CAAA;AAAA,IACA,OAAO,IAAA,EAAM;AACX,MAAA,IAAI;AACF,QAAG,EAAA,CAAA,MAAA,CAAOA,QAAAA,CAAQ,IAAI,CAAC,CAAA;AAAA,MACzB,CAAA,CAAA,MAAQ;AAAA,MAER;AAAA,IACF,CAAA;AAAA,IACA,YAAY,IAAA,EAAM;AAChB,MAAA,MAAM,GAAA,GAAW,IAAA,CAAA,IAAA,CAAK,IAAA,EAAM,WAAW,CAAA;AACvC,MAAG,EAAA,CAAA,SAAA,CAAU,GAAA,EAAK,EAAE,SAAA,EAAW,MAAM,CAAA;AACrC,MAAG,kBAAoB,IAAA,CAAA,IAAA,CAAK,GAAA,EAAK,kBAAkB,CAAA,EAAG,OAAO,IAAI,CAAA;AAAA,IACnE;AAAA,GACF;AACF;AAGA,SAAS,OAAA,GAAkB;AACzB,EAAA,IAAI,CAAA,GAAI,EAAA;AACR,EAAA,MAAM,QAAA,GAAW,sCAAA;AACjB,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,CAAA,EAAG,KAAK,CAAA,IAAK,QAAA,CAAS,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,MAAA,EAAO,GAAI,QAAA,CAAS,MAAM,CAAC,CAAA;AACrF,EAAA,OAAO,CAAA;AACT","file":"chunk-6CXTPC74.js","sourcesContent":["/**\n * The `vg code` governance session (VG-CLI-CODE §7).\n *\n * This is the load-bearing rule the whole platform is built on, applied to code\n * edits: a state-altering change goes **through** the lifecycle, never around it\n * (GUARDRAILS §5). One run walks inspect → assess → dry-run → approve → execute\n * → verify → log:\n *\n * inspect build the graph-grounded context for the instruction\n * assess ask the routed model for a terse edit; apply it deterministically\n * (in memory); compute the diff and the blast radius\n * dry-run the DEFAULT terminal state — propose the change, write nothing\n * approve writing requires explicit consent; without it we stop at dry-run\n * execute write the approved changes to disk\n * verify confirm the files on disk match what was approved (+ optional cmd)\n * log append an immutable, secret-free audit record with a correlation id\n *\n * Everything is deterministic except the single model call, which is isolated\n * behind the provider list (tried in order, falling back on transport failure).\n * The filesystem and clock are injectable so the whole session is testable\n * offline with no writes escaping a temp dir.\n */\n\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { buildCodeContext } from './context.js';\nimport { recordCliCall, CLI_TOOL_ALIASES } from '../engine/savings.js';\nimport { parseEdits, applyEdits, type SymbolSpan } from './apply.js';\nimport { unifiedDiff } from './diff.js';\nimport { buildMessages } from './prompt.js';\nimport { redactSecrets } from './providers.js';\nimport type { ChatOptions, CodeSessionResult, FileChange, LifecyclePhase, Provider, ProviderResult } from './types.js';\nimport type { VgGraph } from '../schema.js';\n\n/** Minimal filesystem seam so the session is testable without touching real disk. */\nexport interface CodeFs {\n read(file: string): string | null;\n write(file: string, content: string): void;\n remove(file: string): void;\n appendAudit(line: string): void;\n}\n\nexport interface RunSessionOptions {\n graph: VgGraph;\n root: string;\n instruction: string;\n /** Providers to try in order (from the router); the first that answers wins. */\n providers: Provider[];\n /** Write to disk? Default false — dry-run is the safe default (GUARDRAILS §3.2). */\n apply?: boolean;\n /** Explicit consent for the write (`--yes`); without it, a requested apply stops at dry-run. */\n consent?: boolean;\n /** Restrict the edit surface to these files. */\n files?: string[];\n /** Context token budget. */\n budget?: number;\n chatOptions?: ChatOptions;\n /** Injectable filesystem (defaults to a real, root-scoped fs). */\n fsImpl?: CodeFs;\n /** Injectable clock (ms) for the audit timestamp. */\n now?: () => number;\n /** Injectable correlation id (defaults to a random short id). */\n correlationId?: string;\n /** Progress callback per phase. */\n onPhase?: (phase: LifecyclePhase, detail: string) => void;\n /** Skip appending to the audit log (used by tests/dry programmatic calls). */\n noAudit?: boolean;\n /**\n * Who is making the graph-backed call, for per-model savings auditing. When\n * `client` is set, the context-assembly step (a `query_graph`-equivalent) is\n * recorded to the local savings ledger tagged with the provider + model, so\n * `vg savings` can break usage down per model. VG Code always sets this.\n */\n attribution?: { client: string; provider?: string; model?: string };\n}\n\nexport async function runCodeSession(options: RunSessionOptions): Promise<CodeSessionResult> {\n const {\n graph,\n root,\n instruction,\n providers,\n apply = false,\n consent = false,\n files,\n budget,\n chatOptions,\n now = () => 0,\n onPhase = () => {},\n } = options;\n const fsImpl = options.fsImpl ?? nodeCodeFs(root);\n const correlationId = options.correlationId ?? shortId();\n const phasesRun: LifecyclePhase[] = [];\n const phase = (p: LifecyclePhase, detail: string): void => {\n phasesRun.push(p);\n onPhase(p, detail);\n };\n\n // ── inspect ──────────────────────────────────────────────────────────────\n phase('inspect', 'building graph-grounded context');\n const context = buildCodeContext(graph, instruction, { budget, files });\n\n // Per-model savings: the context build is VG Code's `query_graph`-equivalent.\n // Record it (counts only — never code or the instruction) tagged with the\n // model, so `vg savings` audits usage per model. Local-only, best-effort.\n if (options.attribution?.client) {\n const seedFiles = new Set(context.seeds.map((s) => s.node.file).filter(Boolean));\n recordCliCall(\n root,\n {\n tool: CLI_TOOL_ALIASES.ask,\n client: options.attribution.client,\n provider: options.attribution.provider,\n model: options.attribution.model,\n outcome: context.seeds.length ? 'complete' : 'miss',\n vgTokens: context.tokensEstimate,\n baselineFiles: seedFiles.size,\n },\n now(),\n );\n }\n\n // ── assess ──────────────────────────────────────────────────────────────\n phase('assess', 'asking the routed model for a minimal edit');\n const messages = buildMessages(context);\n const { result, provider, fellBack } = await complete(providers, messages, chatOptions, correlationId);\n\n const edits = parseEdits(result.text);\n const spans = spanIndex(graph);\n const applied = applyEdits(\n edits,\n (file) => fsImpl.read(file),\n (file) => spans.get(normalize(file)) ?? [],\n );\n\n const changes: FileChange[] = [];\n for (const [file, entry] of [...applied.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {\n changes.push({\n file,\n before: entry.before,\n after: entry.after,\n outcomes: entry.outcomes,\n diff: unifiedDiff(entry.before, entry.after, file),\n });\n }\n\n // ── dry-run (always produced) ─────────────────────────────────────────────\n phase('dry-run', `${changes.length} file(s) would change`);\n\n const anyApplicable = changes.some((c) => c.diff !== '' && c.outcomes.some((o) => o.status === 'applied'));\n const base: CodeSessionResult = {\n instruction,\n provider: { id: provider.id, model: provider.model, local: provider.local, fellBack },\n phasesRun,\n changes,\n applied: false,\n verification: { ran: false, ok: false, detail: 'dry-run — no changes written' },\n correlationId,\n };\n\n // ── approve ───────────────────────────────────────────────────────────────\n // Writing is opt-in AND consent-gated. A requested apply without consent is\n // not an error — it degrades to a dry-run so the caller can review, then\n // re-run with --yes. This is the \"never destructive-by-default\" guardrail.\n if (!apply) {\n if (!options.noAudit) writeAudit(fsImpl, base, now());\n return base;\n }\n if (!consent) {\n const stopped = { ...base, verification: { ran: false, ok: false, detail: 'apply requested without consent — re-run with --yes to write these changes' } };\n if (!options.noAudit) writeAudit(fsImpl, stopped, now());\n return stopped;\n }\n if (!anyApplicable) {\n const nothing = { ...base, verification: { ran: false, ok: false, detail: 'nothing to write — no edit applied cleanly' } };\n if (!options.noAudit) writeAudit(fsImpl, nothing, now());\n return nothing;\n }\n phase('approve', 'consent given (--yes)');\n\n // ── execute ───────────────────────────────────────────────────────────────\n phase('execute', 'writing approved changes');\n for (const change of changes) {\n if (change.diff === '') continue; // no-op file\n if (!change.outcomes.some((o) => o.status === 'applied')) continue; // nothing applied cleanly here\n if (change.after === null) fsImpl.remove(change.file);\n else fsImpl.write(change.file, change.after);\n }\n\n // ── verify ────────────────────────────────────────────────────────────────\n phase('verify', 'confirming on-disk content matches the approved change');\n const verification = verifyWrites(fsImpl, changes);\n\n // ── log ───────────────────────────────────────────────────────────────────\n phase('log', 'recording an audit entry');\n const final: CodeSessionResult = { ...base, applied: true, verification };\n if (!options.noAudit) writeAudit(fsImpl, final, now());\n return final;\n}\n\n/**\n * Commit an already-computed dry-run result: write the cleanly-applied changes,\n * verify them on disk, and append the audit record — the \"approve → execute →\n * verify → log\" tail of the lifecycle, run against a proposal the caller has\n * already shown and had approved (the interactive REPL uses this so it never\n * calls the model twice or writes something different from what was reviewed).\n */\nexport function commitChanges(result: CodeSessionResult, fsImpl: CodeFs, now: () => number = () => 0): CodeSessionResult {\n const anyApplicable = result.changes.some((c) => c.diff !== '' && c.outcomes.some((o) => o.status === 'applied'));\n if (!anyApplicable) {\n return { ...result, applied: false, verification: { ran: false, ok: false, detail: 'nothing to write — no edit applied cleanly' } };\n }\n for (const change of result.changes) {\n if (change.diff === '' || !change.outcomes.some((o) => o.status === 'applied')) continue;\n if (change.after === null) fsImpl.remove(change.file);\n else fsImpl.write(change.file, change.after);\n }\n const verification = verifyWrites(fsImpl, result.changes);\n const final: CodeSessionResult = { ...result, applied: true, verification, phasesRun: [...result.phasesRun, 'approve', 'execute', 'verify', 'log'] };\n writeAudit(fsImpl, final, now());\n return final;\n}\n\n/**\n * Undo a set of applied changes by restoring each file's prior content — the\n * `/undo` command. A created file (before === null) is removed; an edited or\n * deleted file is restored to its `before`. Only cleanly-applied changes are\n * reverted. Returns the files it restored.\n */\nexport function undoChanges(changes: FileChange[], fsImpl: CodeFs): string[] {\n const restored: string[] = [];\n for (const ch of changes) {\n if (!ch.outcomes.some((o) => o.status === 'applied')) continue;\n if (ch.before === null) fsImpl.remove(ch.file);\n else fsImpl.write(ch.file, ch.before);\n restored.push(ch.file);\n }\n return restored;\n}\n\n/** Try each provider in order; fall back on transport failure. Throws the last actionable error. */\nasync function complete(\n providers: Provider[],\n messages: ReturnType<typeof buildMessages>,\n chatOptions: ChatOptions | undefined,\n correlationId: string,\n): Promise<{ result: ProviderResult; provider: Provider; fellBack: boolean }> {\n if (providers.length === 0) throw new Error(`no model provider available (ref ${correlationId})`);\n let lastErr: unknown;\n for (let i = 0; i < providers.length; i++) {\n const provider = providers[i];\n try {\n const result = await provider.chat(messages, { temperature: 0, ...chatOptions });\n return { result, provider, fellBack: i > 0 };\n } catch (e) {\n lastErr = e;\n }\n }\n const message = lastErr instanceof Error ? redactSecrets(lastErr.message) : String(lastErr);\n throw new Error(`${message} (ref ${correlationId})`);\n}\n\n/** Confirm each written file's on-disk content equals what we approved. */\nfunction verifyWrites(fsImpl: CodeFs, changes: FileChange[]): { ran: boolean; ok: boolean; detail: string } {\n let checked = 0;\n const mismatches: string[] = [];\n for (const change of changes) {\n if (change.diff === '' || !change.outcomes.some((o) => o.status === 'applied')) continue;\n checked++;\n const onDisk = fsImpl.read(change.file);\n if (onDisk !== change.after) mismatches.push(change.file);\n }\n if (mismatches.length) return { ran: true, ok: false, detail: `on-disk content differs for: ${mismatches.join(', ')}` };\n return { ran: true, ok: true, detail: `wrote and verified ${checked} file(s)` };\n}\n\n/** An append-only, secret-free audit record (GUARDRAILS §2.6 / §5). No file contents, no keys. */\nfunction writeAudit(fsImpl: CodeFs, r: CodeSessionResult, ts: number): void {\n const record = {\n ts,\n correlationId: r.correlationId,\n instruction: r.instruction.slice(0, 500),\n provider: r.provider.id,\n model: r.provider.model,\n local: r.provider.local,\n applied: r.applied,\n files: r.changes.map((c) => ({ file: c.file, statuses: c.outcomes.map((o) => o.status) })),\n verification: r.verification,\n phases: r.phasesRun,\n };\n try {\n fsImpl.appendAudit(JSON.stringify(record));\n } catch {\n /* audit is best-effort; never fail the run on a logging problem */\n }\n}\n\n/** Build a per-file map of symbol spans for graph-scoped edit disambiguation. */\nfunction spanIndex(graph: VgGraph): Map<string, SymbolSpan[]> {\n const map = new Map<string, SymbolSpan[]>();\n for (const n of graph.nodes) {\n if (n.kind === 'file' || n.kind === 'external') continue;\n const file = normalize(n.file);\n const list = map.get(file) ?? [];\n list.push({ qualifiedName: n.qualifiedName, file, start: n.span.start, end: n.span.end });\n map.set(file, list);\n }\n return map;\n}\n\nfunction normalize(file: string): string {\n return file.replace(/\\\\/g, '/').replace(/^\\.\\//, '');\n}\n\n/** Default filesystem impl, scoped to `root`. Refuses paths that escape the root. */\nexport function nodeCodeFs(root: string): CodeFs {\n const resolve = (file: string): string => {\n const abs = path.resolve(root, file);\n const rel = path.relative(root, abs);\n if (rel.startsWith('..') || path.isAbsolute(rel)) {\n throw new Error(`edit path escapes the project root: ${file} — refusing to touch files outside ${root}`);\n }\n return abs;\n };\n return {\n read(file) {\n try {\n return fs.readFileSync(resolve(file), 'utf8');\n } catch {\n return null;\n }\n },\n write(file, content) {\n const abs = resolve(file);\n fs.mkdirSync(path.dirname(abs), { recursive: true });\n fs.writeFileSync(abs, content);\n },\n remove(file) {\n try {\n fs.rmSync(resolve(file));\n } catch {\n /* already gone */\n }\n },\n appendAudit(line) {\n const dir = path.join(root, '.vibgrate');\n fs.mkdirSync(dir, { recursive: true });\n fs.appendFileSync(path.join(dir, 'code-audit.jsonl'), line + '\\n');\n },\n };\n}\n\n/** Short, URL-safe correlation id. Not a graph artifact, so runtime randomness is fine here. */\nfunction shortId(): string {\n let s = '';\n const alphabet = 'abcdefghijklmnopqrstuvwxyz0123456789';\n for (let i = 0; i < 8; i++) s += alphabet[Math.floor(Math.random() * alphabet.length)];\n return s;\n}\n"]}
|