diffsplain 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -4
- package/dist/assets/{angular-html-m_m91ZYB.js → angular-html-CjYwnGyV.js} +1 -1
- package/dist/assets/{angular-ts-CO5Z--Rb.js → angular-ts-DSPf2G4O.js} +1 -1
- package/dist/assets/{apl-ogYYgLUu.js → apl-B9ytghJx.js} +1 -1
- package/dist/assets/{astro-DAfY3cNR.js → astro-BsGV0yBF.js} +1 -1
- package/dist/assets/{blade-E6RzsLUV.js → blade-Cc2eCYlt.js} +1 -1
- package/dist/assets/{c-kKdxhtHF.js → c-CWKyCnUx.js} +1 -1
- package/dist/assets/{cobol-3oDDyFNf.js → cobol-DkamOI1E.js} +1 -1
- package/dist/assets/{coffee-dsiILuWe.js → coffee-DyaEXePe.js} +1 -1
- package/dist/assets/{cpp-DYyNFq__.js → cpp-16-d2Eyo.js} +1 -1
- package/dist/assets/{crystal-B7dVp9TF.js → crystal-CavPPRhQ.js} +1 -1
- package/dist/assets/{css-DlxJ0aex.js → css-BME_wHJq.js} +1 -1
- package/dist/assets/{edge-BjEVphkC.js → edge-DeVi2hvK.js} +1 -1
- package/dist/assets/{elixir-0xTOtMcM.js → elixir-CCWgH_vc.js} +1 -1
- package/dist/assets/{elm-iQk_Wrxq.js → elm-CZ570J7x.js} +1 -1
- package/dist/assets/{erb-BO74ztiE.js → erb-CDPT5bea.js} +1 -1
- package/dist/assets/{git-rebase-BN667QRY.js → git-rebase-Dq5Z37DJ.js} +1 -1
- package/dist/assets/{glimmer-js-DTGUIHeS.js → glimmer-js-BoPVZK8p.js} +1 -1
- package/dist/assets/{glimmer-ts-BU3oQn2d.js → glimmer-ts-DazEWfg1.js} +1 -1
- package/dist/assets/{glsl-KLiqLJi2.js → glsl-DCUjYCZY.js} +1 -1
- package/dist/assets/{graphql-D_idcsoJ.js → graphql-DkCeSxy7.js} +1 -1
- package/dist/assets/{hack-BXlsgPIr.js → hack-tiY3l-W-.js} +1 -1
- package/dist/assets/{haml-fP0aUg4-.js → haml-DyQZrJg_.js} +1 -1
- package/dist/assets/{handlebars-C1ZEaot-.js → handlebars-Bvu5Rr0t.js} +1 -1
- package/dist/assets/{html-D04qIuup.js → html-C60VFgKW.js} +1 -1
- package/dist/assets/{html-derivative-C6ai-9QL.js → html-derivative-DB69gpnL.js} +1 -1
- package/dist/assets/{http-C7bz3ZvQ.js → http-DmS8hddd.js} +1 -1
- package/dist/assets/{hurl-jL6B6Ahr.js → hurl-Cdp6lmUx.js} +1 -1
- package/dist/assets/{index-BPaJGi-m.js → index-CUiLG0r9.js} +4 -4
- package/dist/assets/{index-VAyBpb3u.css → index-YnLIxcTm.css} +1 -1
- package/dist/assets/{java-BVzvLCON.js → java-CkOnwoVQ.js} +1 -1
- package/dist/assets/{javascript-UDbZHxCI.js → javascript-D2tF_Ylj.js} +1 -1
- package/dist/assets/{jinja-DB-QKo2A.js → jinja-CdWpLcff.js} +1 -1
- package/dist/assets/{jison-B1ZbR2FF.js → jison-BII58uuR.js} +1 -1
- package/dist/assets/{json-DV4WwJzT.js → json-BKlPRLrU.js} +1 -1
- package/dist/assets/{jsx-DS6_VTvW.js → jsx-Ba5UouxN.js} +1 -1
- package/dist/assets/{julia-C4tevET0.js → julia-CwdQF8Ff.js} +1 -1
- package/dist/assets/{just-CWMjefzB.js → just-Be3sr1-h.js} +1 -1
- package/dist/assets/{latex-tyEFf_Af.js → latex-CDF6t93N.js} +1 -1
- package/dist/assets/{liquid-C3d1X1rL.js → liquid-BE3LVHkw.js} +1 -1
- package/dist/assets/{lua-sXvrzLQY.js → lua-D_W8Ux9S.js} +1 -1
- package/dist/assets/{marko-CQweucif.js → marko-BeUfMD-U.js} +1 -1
- package/dist/assets/{mdc-TrxLWF7s.js → mdc-C8tRJlVU.js} +1 -1
- package/dist/assets/{nginx-ChE2nSNJ.js → nginx-CLPimMM6.js} +1 -1
- package/dist/assets/{nim-BY1CTRfx.js → nim-ikQhXKQf.js} +1 -1
- package/dist/assets/{perl-Cvj2PQQH.js → perl-C27cToFa.js} +1 -1
- package/dist/assets/{php-CGmHp8HW.js → php-Btdg-RP3.js} +1 -1
- package/dist/assets/{pug-DM4S0bRr.js → pug-Bk73ERXL.js} +1 -1
- package/dist/assets/{qml-DI89Al9K.js → qml-DhY7FDIl.js} +1 -1
- package/dist/assets/{r-Bs5_4yYh.js → r-CEwua7nj.js} +1 -1
- package/dist/assets/{razor-DimzayhZ.js → razor-sPO7Sh9d.js} +1 -1
- package/dist/assets/{regexp-BLACz0cx.js → regexp-D9DLFGXE.js} +1 -1
- package/dist/assets/{rst-DcWzxQh2.js → rst-YhauCnId.js} +1 -1
- package/dist/assets/{ruby-Dlat5E8d.js → ruby-DpMkRaoz.js} +1 -1
- package/dist/assets/{sas-CCDmMaBY.js → sas-0KdmKGRO.js} +1 -1
- package/dist/assets/{scss-BPiU2nu6.js → scss-CohvHWvq.js} +1 -1
- package/dist/assets/{shellscript-CKinVNrN.js → shellscript-C93Q8fLt.js} +1 -1
- package/dist/assets/{shellsession-Cea8Pu5d.js → shellsession-YCh2AQXu.js} +1 -1
- package/dist/assets/{soy--BRAPY4-.js → soy-CWVYdXUH.js} +1 -1
- package/dist/assets/{sql-DjKiCyWK.js → sql-CJNtax5I.js} +1 -1
- package/dist/assets/{stata-CDvo3J6y.js → stata-C7v41XAM.js} +1 -1
- package/dist/assets/{surrealql-C7b7E5sh.js → surrealql-CRUSOxDf.js} +1 -1
- package/dist/assets/{svelte-DO0tKtiK.js → svelte-Z0harRr1.js} +1 -1
- package/dist/assets/{templ-DhhmR_rv.js → templ-BplDouJX.js} +1 -1
- package/dist/assets/{tex-Cn_V-Gg4.js → tex-MsB6StN5.js} +1 -1
- package/dist/assets/{ts-tags-B6c-FTmA.js → ts-tags-BWx8aq5t.js} +1 -1
- package/dist/assets/{tsx-Cvtt2sJ-.js → tsx-DBT1gmNl.js} +1 -1
- package/dist/assets/{twig-B7ZbCUmB.js → twig-jz67rR3C.js} +1 -1
- package/dist/assets/{typescript-i3C6um2B.js → typescript-CzwGN36B.js} +1 -1
- package/dist/assets/{vue-html-D362KpK3.js → vue-html-Dhrmu7sO.js} +1 -1
- package/dist/assets/{vue-vine-D34cBbwO.js → vue-vine-BfQGXZs6.js} +1 -1
- package/dist/assets/{vue-DyPX0Vum.js → vue-xsrSiVMC.js} +1 -1
- package/dist/assets/{xml-DJEk7u4n.js → xml-W7cP4qeN.js} +1 -1
- package/dist/assets/{xsl-CoTtf90f.js → xsl-BlXf8qm_.js} +1 -1
- package/dist/assets/{yaml-Cv5epQ4-.js → yaml-BtNwxUT9.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +4 -2
- package/scripts/agent-config.mjs +184 -0
- package/scripts/agent-usage.mjs +141 -0
- package/scripts/build-diff-data.mjs +32 -12
- package/scripts/cli-args.mjs +36 -2
- package/scripts/coding-agents.mjs +265 -13
- package/scripts/generate-summaries.mjs +72 -13
- package/scripts/present.mjs +156 -58
- package/scripts/review-chat-context.mjs +6 -4
- package/scripts/review-chat-controller.mjs +142 -17
- package/scripts/review-chat-provider.mjs +40 -7
- package/scripts/serve-built.mjs +139 -7
|
@@ -15,12 +15,22 @@ import { fileURLToPath } from 'node:url';
|
|
|
15
15
|
import {
|
|
16
16
|
agentCommand,
|
|
17
17
|
agentReadOnlyWarning,
|
|
18
|
+
assertFastCompatible,
|
|
18
19
|
assertReasoningSupported,
|
|
20
|
+
codingAgentFromSelectionError,
|
|
19
21
|
codingAgentAvailability,
|
|
20
22
|
codingAgentBinary,
|
|
21
23
|
parseAgentResponse,
|
|
24
|
+
parseAgentUsage,
|
|
22
25
|
selectCodingAgent,
|
|
23
26
|
} from './coding-agents.mjs';
|
|
27
|
+
import {
|
|
28
|
+
emptyUsageAccumulator,
|
|
29
|
+
formatReviewUsage,
|
|
30
|
+
recordUsage,
|
|
31
|
+
reviewUsage,
|
|
32
|
+
usageSummary,
|
|
33
|
+
} from './agent-usage.mjs';
|
|
24
34
|
import {
|
|
25
35
|
isBaseWorktreeTarget,
|
|
26
36
|
reviewAccessMode,
|
|
@@ -75,6 +85,7 @@ const valueFlags = new Set([
|
|
|
75
85
|
const booleanFlags = new Set([
|
|
76
86
|
'--checkout',
|
|
77
87
|
'--force',
|
|
88
|
+
'--fast',
|
|
78
89
|
'--support-record',
|
|
79
90
|
'--worktree',
|
|
80
91
|
'--no-checkout-access',
|
|
@@ -135,10 +146,11 @@ Options:
|
|
|
135
146
|
--cache-dir PATH Bare cache for fetched Git objects
|
|
136
147
|
--agent NAME Use codex, claude, copilot, cursor, or opencode
|
|
137
148
|
Cursor needs version 2026.08.11 or newer
|
|
138
|
-
|
|
149
|
+
Overrides the configured default for this run
|
|
139
150
|
--codex-bin FILE Codex CLI path (default: codex)
|
|
140
151
|
--model NAME Model passed to the coding agent
|
|
141
152
|
--reasoning LEVEL Agent reasoning effort when supported
|
|
153
|
+
--fast Enable provider Fast mode for every agent call
|
|
142
154
|
--batch-size COUNT Maximum files per agent pass (default: 12)
|
|
143
155
|
--jobs COUNT Agent passes to run at once (default: 3)
|
|
144
156
|
--support-record Print a safe record if this run fails
|
|
@@ -148,8 +160,9 @@ Options:
|
|
|
148
160
|
--no-checkout-access
|
|
149
161
|
Limit notes to the supplied snapshot
|
|
150
162
|
|
|
151
|
-
Without --agent, an interactive terminal is required.
|
|
152
|
-
|
|
163
|
+
Without --agent or a configured default, an interactive terminal is required.
|
|
164
|
+
Use diffsplain config agent NAME to set a default. Use diffsplain --no-agent
|
|
165
|
+
for a plain review.`);
|
|
153
166
|
process.exit(0);
|
|
154
167
|
}
|
|
155
168
|
|
|
@@ -176,6 +189,7 @@ let selectedAgent;
|
|
|
176
189
|
let agentBinary;
|
|
177
190
|
const model = option('--model');
|
|
178
191
|
const reasoning = option('--reasoning');
|
|
192
|
+
const fast = rawArgs.includes('--fast');
|
|
179
193
|
const batchSizeValue = option('--batch-size') || '12';
|
|
180
194
|
const reasoningLevels = new Set([
|
|
181
195
|
'minimal',
|
|
@@ -375,6 +389,18 @@ const summariesPath = summaryPath({
|
|
|
375
389
|
const ownershipPath = `${summariesPath}.lock`;
|
|
376
390
|
let ownership;
|
|
377
391
|
let ownershipHeartbeat;
|
|
392
|
+
let agentUsage = emptyUsageAccumulator();
|
|
393
|
+
|
|
394
|
+
function currentAgentUsage() {
|
|
395
|
+
return usageSummary(agentUsage);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function recordAgentUsage(usage) {
|
|
399
|
+
agentUsage = recordUsage(agentUsage, usage);
|
|
400
|
+
const emptyChatUsage = usageSummary(emptyUsageAccumulator());
|
|
401
|
+
console.log(formatReviewUsage(reviewUsage(currentAgentUsage(), emptyChatUsage)));
|
|
402
|
+
}
|
|
403
|
+
|
|
378
404
|
function acquireOwnership() {
|
|
379
405
|
ownership = acquireLease(ownershipPath);
|
|
380
406
|
ownershipHeartbeat = setInterval(() => {
|
|
@@ -1037,8 +1063,13 @@ function publishSnapshot(
|
|
|
1037
1063
|
...(state.errors.length ? { errors: state.errors } : {}),
|
|
1038
1064
|
...(model ? { model } : {}),
|
|
1039
1065
|
...(reasoning ? { reasoning } : {}),
|
|
1066
|
+
fast,
|
|
1040
1067
|
accessMode: accessMode.mode,
|
|
1041
1068
|
},
|
|
1069
|
+
usage: reviewUsage(
|
|
1070
|
+
currentAgentUsage(),
|
|
1071
|
+
usageSummary(emptyUsageAccumulator()),
|
|
1072
|
+
),
|
|
1042
1073
|
};
|
|
1043
1074
|
delete content.version;
|
|
1044
1075
|
delete content.generatedAt;
|
|
@@ -1063,6 +1094,7 @@ function summariesForSnapshot(summaries, snapshot) {
|
|
|
1063
1094
|
meta: {
|
|
1064
1095
|
...summaries.meta,
|
|
1065
1096
|
reviewFingerprint: snapshot.notes.reviewFingerprint,
|
|
1097
|
+
usage: currentAgentUsage(),
|
|
1066
1098
|
},
|
|
1067
1099
|
};
|
|
1068
1100
|
}
|
|
@@ -1206,14 +1238,20 @@ async function selectAgentForNotes() {
|
|
|
1206
1238
|
signal: selectionAbortController.signal,
|
|
1207
1239
|
},
|
|
1208
1240
|
);
|
|
1209
|
-
assertReasoningSupported(selectedAgent, reasoning);
|
|
1210
1241
|
agentBinary = codingAgentBinary(selectedAgent, { codexBin });
|
|
1242
|
+
assertReasoningSupported(selectedAgent, reasoning);
|
|
1243
|
+
assertFastCompatible(selectedAgent, agentBinary, fast);
|
|
1211
1244
|
supportRecorder?.setProvider(
|
|
1212
1245
|
selectedAgent,
|
|
1213
1246
|
safeCommandVersion(selectedAgent, agentBinary),
|
|
1214
1247
|
);
|
|
1215
1248
|
} catch (error) {
|
|
1216
|
-
supportRecorder?.setProvider(
|
|
1249
|
+
supportRecorder?.setProvider(
|
|
1250
|
+
codingAgentFromSelectionError(error) ||
|
|
1251
|
+
selectedAgent ||
|
|
1252
|
+
requestedAgent ||
|
|
1253
|
+
'unknown',
|
|
1254
|
+
);
|
|
1217
1255
|
if (error instanceof Error) error.exitCode = 2;
|
|
1218
1256
|
throw error;
|
|
1219
1257
|
}
|
|
@@ -1258,9 +1296,12 @@ function runAgent(invocation, input, { timeoutMs } = {}) {
|
|
|
1258
1296
|
const timeout = timeoutMs
|
|
1259
1297
|
? setTimeout(() => {
|
|
1260
1298
|
child.kill('SIGTERM');
|
|
1261
|
-
|
|
1262
|
-
|
|
1299
|
+
const error = new Error(`${selectedAgent} timed out`);
|
|
1300
|
+
error.providerUsage = parseAgentUsage(
|
|
1301
|
+
selectedAgent,
|
|
1302
|
+
Buffer.concat(stdout).toString('utf8'),
|
|
1263
1303
|
);
|
|
1304
|
+
rejectPromise(error);
|
|
1264
1305
|
}, timeoutMs)
|
|
1265
1306
|
: undefined;
|
|
1266
1307
|
timeout?.unref();
|
|
@@ -1309,14 +1350,18 @@ function runAgent(invocation, input, { timeoutMs } = {}) {
|
|
|
1309
1350
|
.filter((line) => line.trim() && line.length < 600)
|
|
1310
1351
|
.slice(-8)
|
|
1311
1352
|
.join('\n');
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
`${selectedAgent} exited with status ${status ?? signal}${detail ? `\n${detail}` : ''}`,
|
|
1315
|
-
),
|
|
1353
|
+
const error = new Error(
|
|
1354
|
+
`${selectedAgent} exited with status ${status ?? signal}${detail ? `\n${detail}` : ''}`,
|
|
1316
1355
|
);
|
|
1356
|
+
error.providerUsage = parseAgentUsage(selectedAgent, stdoutText);
|
|
1357
|
+
rejectPromise(error);
|
|
1317
1358
|
return;
|
|
1318
1359
|
}
|
|
1319
|
-
resolvePromise({
|
|
1360
|
+
resolvePromise({
|
|
1361
|
+
stdout: stdoutText,
|
|
1362
|
+
stderr: stderrText,
|
|
1363
|
+
providerUsage: parseAgentUsage(selectedAgent, stdoutText),
|
|
1364
|
+
});
|
|
1320
1365
|
});
|
|
1321
1366
|
if (invocation.input === 'stdin') child.stdin.end(input);
|
|
1322
1367
|
else child.stdin.end();
|
|
@@ -1326,8 +1371,16 @@ function runAgent(invocation, input, { timeoutMs } = {}) {
|
|
|
1326
1371
|
async function requestAgent(invocation, input, normalize, snapshot) {
|
|
1327
1372
|
supportRecorder?.addBytes('agentInput', Buffer.byteLength(input));
|
|
1328
1373
|
return recordAsyncStage('agent', async () => {
|
|
1329
|
-
|
|
1374
|
+
let result;
|
|
1375
|
+
try {
|
|
1376
|
+
result = await runAgent(invocation, input);
|
|
1377
|
+
} catch (error) {
|
|
1378
|
+
assertAgentReviewFresh(snapshot);
|
|
1379
|
+
recordAgentUsage(error?.providerUsage);
|
|
1380
|
+
throw error;
|
|
1381
|
+
}
|
|
1330
1382
|
assertAgentReviewFresh(snapshot);
|
|
1383
|
+
recordAgentUsage(result.providerUsage);
|
|
1331
1384
|
if (result.stderr.trim()) {
|
|
1332
1385
|
console.error(
|
|
1333
1386
|
`${selectedAgent} wrote diagnostic output:\n${result.stderr.trim()}`,
|
|
@@ -1560,6 +1613,7 @@ try {
|
|
|
1560
1613
|
generatedAt: new Date().toISOString(),
|
|
1561
1614
|
...(model ? { model } : {}),
|
|
1562
1615
|
...(reasoning ? { reasoning } : {}),
|
|
1616
|
+
fast,
|
|
1563
1617
|
accessMode: accessMode.mode,
|
|
1564
1618
|
},
|
|
1565
1619
|
};
|
|
@@ -1606,6 +1660,7 @@ try {
|
|
|
1606
1660
|
generatedAt: new Date().toISOString(),
|
|
1607
1661
|
...(model ? { model } : {}),
|
|
1608
1662
|
...(reasoning ? { reasoning } : {}),
|
|
1663
|
+
fast,
|
|
1609
1664
|
accessMode: accessMode.mode,
|
|
1610
1665
|
},
|
|
1611
1666
|
};
|
|
@@ -1687,6 +1742,7 @@ try {
|
|
|
1687
1742
|
: 'complete',
|
|
1688
1743
|
...(model ? { model } : {}),
|
|
1689
1744
|
...(reasoning ? { reasoning } : {}),
|
|
1745
|
+
fast,
|
|
1690
1746
|
accessMode: accessMode.mode,
|
|
1691
1747
|
},
|
|
1692
1748
|
},
|
|
@@ -1742,6 +1798,7 @@ try {
|
|
|
1742
1798
|
binary: agentBinary,
|
|
1743
1799
|
model,
|
|
1744
1800
|
reasoning,
|
|
1801
|
+
fast,
|
|
1745
1802
|
prompt: promptFor(batchPaths, { accessMode, includeChange: false }),
|
|
1746
1803
|
schema: outputSchema(batchPaths, { includeChange: false }),
|
|
1747
1804
|
schemaPath,
|
|
@@ -1868,6 +1925,7 @@ try {
|
|
|
1868
1925
|
binary: agentBinary,
|
|
1869
1926
|
model,
|
|
1870
1927
|
reasoning,
|
|
1928
|
+
fast,
|
|
1871
1929
|
prompt: promptFor([], { accessMode }),
|
|
1872
1930
|
schema,
|
|
1873
1931
|
schemaPath,
|
|
@@ -1938,6 +1996,7 @@ try {
|
|
|
1938
1996
|
} catch (error) {
|
|
1939
1997
|
if (!(error instanceof ReviewChangedError)) throw error;
|
|
1940
1998
|
reviewChanged = true;
|
|
1999
|
+
agentUsage = emptyUsageAccumulator();
|
|
1941
2000
|
console.log('The review changed while notes were generated. Rebuilding the snapshot.');
|
|
1942
2001
|
}
|
|
1943
2002
|
} while (reviewChanged && !interrupted);
|
package/scripts/present.mjs
CHANGED
|
@@ -17,9 +17,12 @@ import { dirname, join, resolve } from 'node:path';
|
|
|
17
17
|
import { createInterface } from 'node:readline';
|
|
18
18
|
import { fileURLToPath } from 'node:url';
|
|
19
19
|
import { helpText, parseCliArgs } from './cli-args.mjs';
|
|
20
|
+
import { applyAgentConfigOperation } from './agent-config.mjs';
|
|
20
21
|
import {
|
|
21
22
|
agentReadOnlyWarning,
|
|
23
|
+
assertFastCompatible,
|
|
22
24
|
assertReasoningSupported,
|
|
25
|
+
codingAgentFromSelectionError,
|
|
23
26
|
codingAgentAvailability,
|
|
24
27
|
codingAgentBinary,
|
|
25
28
|
selectCodingAgent,
|
|
@@ -31,6 +34,11 @@ import {
|
|
|
31
34
|
} from './local-target.mjs';
|
|
32
35
|
import { doctorReport } from './doctor.mjs';
|
|
33
36
|
import { accessTokenDirectory } from './access-token.mjs';
|
|
37
|
+
import {
|
|
38
|
+
emptyUsageAccumulator,
|
|
39
|
+
reviewUsage,
|
|
40
|
+
usageSummary,
|
|
41
|
+
} from './agent-usage.mjs';
|
|
34
42
|
import { cacheStatus, clearCache, formatCacheStatus, pruneCache } from './cache.mjs';
|
|
35
43
|
import {
|
|
36
44
|
agentFallbackRecordNeeded,
|
|
@@ -126,6 +134,22 @@ if (cli.version) {
|
|
|
126
134
|
console.log(`diffsplain ${packageJson.version}`);
|
|
127
135
|
process.exit(0);
|
|
128
136
|
}
|
|
137
|
+
if (cli.config) {
|
|
138
|
+
try {
|
|
139
|
+
const result = applyAgentConfigOperation(cli.config);
|
|
140
|
+
if (result.kind === 'show') {
|
|
141
|
+
console.log(result.agent ?? 'No default coding agent is configured.');
|
|
142
|
+
} else if (result.kind === 'set') {
|
|
143
|
+
console.log(`Default coding agent set to "${result.agent}".`);
|
|
144
|
+
} else {
|
|
145
|
+
console.log('Default coding agent unset.');
|
|
146
|
+
}
|
|
147
|
+
process.exit(0);
|
|
148
|
+
} catch (error) {
|
|
149
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
150
|
+
process.exit(1);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
129
153
|
if (cli.doctor) {
|
|
130
154
|
if (cli.doctor.deep) {
|
|
131
155
|
console.error(
|
|
@@ -238,10 +262,11 @@ if (agentEnabled) {
|
|
|
238
262
|
}),
|
|
239
263
|
},
|
|
240
264
|
);
|
|
241
|
-
assertReasoningSupported(selectedAgent, cli.reasoning);
|
|
242
265
|
selectedAgentBinary = codingAgentBinary(selectedAgent, {
|
|
243
266
|
codexBin: cli.codexBin,
|
|
244
267
|
});
|
|
268
|
+
assertReasoningSupported(selectedAgent, cli.reasoning);
|
|
269
|
+
assertFastCompatible(selectedAgent, selectedAgentBinary, cli.fast);
|
|
245
270
|
recordSelectedProvider(selectedAgent, selectedAgentBinary);
|
|
246
271
|
supportRecorder?.addStage(
|
|
247
272
|
'agent',
|
|
@@ -263,7 +288,12 @@ if (agentEnabled) {
|
|
|
263
288
|
agentArgs.push('--provider-read-only-warning-emitted');
|
|
264
289
|
}
|
|
265
290
|
} catch (error) {
|
|
266
|
-
recordSelectedProvider(
|
|
291
|
+
recordSelectedProvider(
|
|
292
|
+
codingAgentFromSelectionError(error) ||
|
|
293
|
+
selectedAgent ||
|
|
294
|
+
cli.agent ||
|
|
295
|
+
'unknown',
|
|
296
|
+
);
|
|
267
297
|
supportRecorder?.addStage(
|
|
268
298
|
'agent',
|
|
269
299
|
performance.now() - selectionStarted,
|
|
@@ -338,9 +368,7 @@ writeFileSync(accessPath, access, { mode: 0o600 });
|
|
|
338
368
|
chmodSync(accessPath, 0o600);
|
|
339
369
|
if (agentEnabled) {
|
|
340
370
|
feedArgs.push('--ignore-summary-watch');
|
|
341
|
-
|
|
342
|
-
agentArgs.push('--snapshot', rawSnapshotPath);
|
|
343
|
-
}
|
|
371
|
+
agentArgs.push('--snapshot', rawSnapshotPath);
|
|
344
372
|
}
|
|
345
373
|
|
|
346
374
|
const snapshotStarted = performance.now();
|
|
@@ -433,6 +461,7 @@ function chatAgentArguments() {
|
|
|
433
461
|
...chatAccessArguments(),
|
|
434
462
|
...optionalSiteArgument('--chat-model', cli.model),
|
|
435
463
|
...optionalSiteArgument('--chat-reasoning', cli.reasoning),
|
|
464
|
+
...(cli.fast ? ['--chat-fast'] : []),
|
|
436
465
|
];
|
|
437
466
|
}
|
|
438
467
|
|
|
@@ -506,59 +535,91 @@ function startSite() {
|
|
|
506
535
|
child.on('error', handleSiteError);
|
|
507
536
|
}
|
|
508
537
|
|
|
538
|
+
function savedReviewFingerprint(notes) {
|
|
539
|
+
if (!notes) return undefined;
|
|
540
|
+
if (notes.agentReviewFingerprint) return notes.agentReviewFingerprint;
|
|
541
|
+
return notes.reviewFingerprint;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
function snapshotReviewFingerprint(snapshot) {
|
|
545
|
+
const savedFingerprint = savedReviewFingerprint(snapshot.notes);
|
|
546
|
+
if (savedFingerprint) return savedFingerprint;
|
|
547
|
+
const reviewData = {
|
|
548
|
+
repo: {
|
|
549
|
+
name: snapshot.repo.name,
|
|
550
|
+
base: snapshot.repo.base,
|
|
551
|
+
head: snapshot.repo.head,
|
|
552
|
+
branch: snapshot.repo.branch,
|
|
553
|
+
baseBranch: snapshot.repo.baseBranch,
|
|
554
|
+
remote: snapshot.repo.remote,
|
|
555
|
+
targetKind: snapshot.repo.target?.kind,
|
|
556
|
+
},
|
|
557
|
+
change: {
|
|
558
|
+
title: snapshot.change.title,
|
|
559
|
+
number: snapshot.change.number,
|
|
560
|
+
},
|
|
561
|
+
files: snapshot.files.map((file) => ({
|
|
562
|
+
path: file.path,
|
|
563
|
+
oldPath: file.oldPath,
|
|
564
|
+
status: file.status,
|
|
565
|
+
additions: file.additions,
|
|
566
|
+
deletions: file.deletions,
|
|
567
|
+
isBinary: file.isBinary,
|
|
568
|
+
patch: file.patch,
|
|
569
|
+
})),
|
|
570
|
+
};
|
|
571
|
+
return createHash('sha256')
|
|
572
|
+
.update(JSON.stringify(reviewData))
|
|
573
|
+
.digest('hex');
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function noteMetadataMatches(notes, fingerprint) {
|
|
577
|
+
return [
|
|
578
|
+
notes.complete,
|
|
579
|
+
notes.fresh,
|
|
580
|
+
notes.generatedFor === fingerprint,
|
|
581
|
+
notes.accessMode === accessMode.mode,
|
|
582
|
+
].every(Boolean);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
function normalizedAgentSetting(value) {
|
|
586
|
+
return value ?? null;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function agentSettingsMatch(notes) {
|
|
590
|
+
return [
|
|
591
|
+
notes.agent === selectedAgent,
|
|
592
|
+
normalizedAgentSetting(notes.model) === normalizedAgentSetting(cli.model),
|
|
593
|
+
normalizedAgentSetting(notes.reasoning) ===
|
|
594
|
+
normalizedAgentSetting(cli.reasoning),
|
|
595
|
+
].every(Boolean);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
function emptyReview(files) {
|
|
599
|
+
return Array.isArray(files) && files.length === 0;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
function hasCurrentAgentNotes(snapshot, fingerprint) {
|
|
603
|
+
const notes = snapshot.notes;
|
|
604
|
+
if (!savedReviewFingerprint(notes)) return false;
|
|
605
|
+
if (!noteMetadataMatches(notes, fingerprint)) return false;
|
|
606
|
+
if (emptyReview(snapshot.files)) return true;
|
|
607
|
+
return agentSettingsMatch(notes);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
function snapshotStateFromSnapshot(snapshot) {
|
|
611
|
+
const fingerprint = snapshotReviewFingerprint(snapshot);
|
|
612
|
+
return {
|
|
613
|
+
fingerprint,
|
|
614
|
+
hasCurrentAgentNotes: hasCurrentAgentNotes(snapshot, fingerprint),
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
|
|
509
618
|
function snapshotState() {
|
|
510
619
|
try {
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
snapshot.notes.agentReviewFingerprint ||
|
|
515
|
-
snapshot.notes.reviewFingerprint;
|
|
516
|
-
const emptyReview =
|
|
517
|
-
Array.isArray(snapshot.files) && snapshot.files.length === 0;
|
|
518
|
-
return {
|
|
519
|
-
fingerprint,
|
|
520
|
-
hasCurrentAgentNotes:
|
|
521
|
-
snapshot.notes.complete &&
|
|
522
|
-
snapshot.notes.fresh &&
|
|
523
|
-
snapshot.notes.generatedFor === fingerprint &&
|
|
524
|
-
snapshot.notes.accessMode === accessMode.mode &&
|
|
525
|
-
(emptyReview ||
|
|
526
|
-
(snapshot.notes.agent === selectedAgent &&
|
|
527
|
-
(snapshot.notes.model || null) === (cli.model || null) &&
|
|
528
|
-
(snapshot.notes.reasoning || null) ===
|
|
529
|
-
(cli.reasoning || null))),
|
|
530
|
-
};
|
|
531
|
-
}
|
|
532
|
-
const reviewData = {
|
|
533
|
-
repo: {
|
|
534
|
-
name: snapshot.repo.name,
|
|
535
|
-
base: snapshot.repo.base,
|
|
536
|
-
head: snapshot.repo.head,
|
|
537
|
-
branch: snapshot.repo.branch,
|
|
538
|
-
baseBranch: snapshot.repo.baseBranch,
|
|
539
|
-
remote: snapshot.repo.remote,
|
|
540
|
-
targetKind: snapshot.repo.target?.kind,
|
|
541
|
-
},
|
|
542
|
-
change: {
|
|
543
|
-
title: snapshot.change.title,
|
|
544
|
-
number: snapshot.change.number,
|
|
545
|
-
},
|
|
546
|
-
files: snapshot.files.map((file) => ({
|
|
547
|
-
path: file.path,
|
|
548
|
-
oldPath: file.oldPath,
|
|
549
|
-
status: file.status,
|
|
550
|
-
additions: file.additions,
|
|
551
|
-
deletions: file.deletions,
|
|
552
|
-
isBinary: file.isBinary,
|
|
553
|
-
patch: file.patch,
|
|
554
|
-
})),
|
|
555
|
-
};
|
|
556
|
-
return {
|
|
557
|
-
fingerprint: createHash('sha256')
|
|
558
|
-
.update(JSON.stringify(reviewData))
|
|
559
|
-
.digest('hex'),
|
|
560
|
-
hasCurrentAgentNotes: false,
|
|
561
|
-
};
|
|
620
|
+
return snapshotStateFromSnapshot(
|
|
621
|
+
JSON.parse(readFileSync(reviewSnapshotPath, 'utf8')),
|
|
622
|
+
);
|
|
562
623
|
} catch {
|
|
563
624
|
return undefined;
|
|
564
625
|
}
|
|
@@ -699,12 +760,16 @@ function scheduleAgent(fingerprint) {
|
|
|
699
760
|
if (agent) {
|
|
700
761
|
if (selectedFingerprint !== agentFingerprint) {
|
|
701
762
|
queuedFingerprint = selectedFingerprint;
|
|
763
|
+
console.log(
|
|
764
|
+
'Review changed while notes were generated. Restarting agent notes...',
|
|
765
|
+
);
|
|
702
766
|
agent.kill('SIGTERM');
|
|
703
767
|
}
|
|
704
768
|
return;
|
|
705
769
|
}
|
|
706
770
|
clearTimeout(agentTimer);
|
|
707
771
|
const delay = agentFingerprint ? 300 : 0;
|
|
772
|
+
console.log('Preparing agent notes...');
|
|
708
773
|
agentTimer = setTimeout(() => runAgent(selectedFingerprint), delay);
|
|
709
774
|
}
|
|
710
775
|
|
|
@@ -720,11 +785,44 @@ function markSnapshotReady() {
|
|
|
720
785
|
);
|
|
721
786
|
}
|
|
722
787
|
|
|
788
|
+
function snapshotForPresentation(snapshot) {
|
|
789
|
+
const zeroUsage = usageSummary(emptyUsageAccumulator());
|
|
790
|
+
const hasCurrentNotes = snapshotStateFromSnapshot(snapshot)
|
|
791
|
+
.hasCurrentAgentNotes;
|
|
792
|
+
const current = {
|
|
793
|
+
...snapshot,
|
|
794
|
+
usage: reviewUsage(zeroUsage, zeroUsage),
|
|
795
|
+
};
|
|
796
|
+
if (hasCurrentNotes && snapshot.notes?.fast === cli.fast) return current;
|
|
797
|
+
const content = {
|
|
798
|
+
...current,
|
|
799
|
+
notes: {
|
|
800
|
+
...snapshot.notes,
|
|
801
|
+
fast: cli.fast,
|
|
802
|
+
...(hasCurrentNotes
|
|
803
|
+
? {}
|
|
804
|
+
: { complete: false, status: 'generating' }),
|
|
805
|
+
},
|
|
806
|
+
};
|
|
807
|
+
delete content.version;
|
|
808
|
+
delete content.generatedAt;
|
|
809
|
+
return {
|
|
810
|
+
version: createHash('sha256')
|
|
811
|
+
.update(JSON.stringify(content))
|
|
812
|
+
.digest('hex')
|
|
813
|
+
.slice(0, 12),
|
|
814
|
+
generatedAt: new Date().toISOString(),
|
|
815
|
+
...content,
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
|
|
723
819
|
function seedPresentationSnapshot() {
|
|
724
|
-
const
|
|
820
|
+
const snapshot = snapshotForPresentation(
|
|
821
|
+
JSON.parse(readFileSync(rawSnapshotPath, 'utf8')),
|
|
822
|
+
);
|
|
725
823
|
mkdirSync(dirname(outputPath), { recursive: true });
|
|
726
824
|
const pendingOutput = `${outputPath}.${process.pid}.${randomBytes(8).toString('hex')}.tmp`;
|
|
727
|
-
writeFileSync(pendingOutput,
|
|
825
|
+
writeFileSync(pendingOutput, `${JSON.stringify(snapshot, null, 2)}\n`);
|
|
728
826
|
renameSync(pendingOutput, outputPath);
|
|
729
827
|
}
|
|
730
828
|
|
|
@@ -307,8 +307,10 @@ export function makeInput({ kind, context, messages, question, accessMode }) {
|
|
|
307
307
|
};
|
|
308
308
|
}
|
|
309
309
|
|
|
310
|
-
function providerResult(provider, input) {
|
|
311
|
-
return typeof provider === 'function'
|
|
310
|
+
function providerResult(provider, input, context) {
|
|
311
|
+
return typeof provider === 'function'
|
|
312
|
+
? provider(input, context)
|
|
313
|
+
: provider.run(input, context);
|
|
312
314
|
}
|
|
313
315
|
|
|
314
316
|
function isExecution(value) {
|
|
@@ -324,8 +326,8 @@ function executionPromise(value) {
|
|
|
324
326
|
return isExecution(value) ? value.promise : value;
|
|
325
327
|
}
|
|
326
328
|
|
|
327
|
-
export function providerExecution(provider, input) {
|
|
328
|
-
const result = providerResult(provider, input);
|
|
329
|
+
export function providerExecution(provider, input, context) {
|
|
330
|
+
const result = providerResult(provider, input, context);
|
|
329
331
|
return {
|
|
330
332
|
promise: Promise.resolve(executionPromise(result)),
|
|
331
333
|
cancel: cancellationFor(result),
|