diffsplain 0.8.3 → 0.10.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 +26 -5
- package/dist/assets/{angular-html-e4D_THVy.js → angular-html-2u-u5H-N.js} +1 -1
- package/dist/assets/{angular-ts-Cx3870JF.js → angular-ts-4_HVGlKp.js} +1 -1
- package/dist/assets/{apl-DZuu22Wh.js → apl-icaNWZX3.js} +1 -1
- package/dist/assets/{astro-K-NemFAk.js → astro-HvIOE4vz.js} +1 -1
- package/dist/assets/{blade-ConbVu3U.js → blade-BpKJV8ev.js} +1 -1
- package/dist/assets/{c-DtmaBqln.js → c-BNvtxyRQ.js} +1 -1
- package/dist/assets/{cobol-BMHoPOfB.js → cobol-8js4hfxa.js} +1 -1
- package/dist/assets/{coffee-DfweJ19Y.js → coffee-DXg_piQ9.js} +1 -1
- package/dist/assets/{cpp-CPh_zIOW.js → cpp-BZDFAjyh.js} +1 -1
- package/dist/assets/{crystal-CAEX7C1J.js → crystal-aCF3ira4.js} +1 -1
- package/dist/assets/{css-C8ihGqCA.js → css-DfRtUkhE.js} +1 -1
- package/dist/assets/{edge-DVZPwQ9j.js → edge-vS6_AmQv.js} +1 -1
- package/dist/assets/{elixir-D03TKulP.js → elixir-Ce0nUHSq.js} +1 -1
- package/dist/assets/{elm-JUX-2xVV.js → elm-DmaeyizZ.js} +1 -1
- package/dist/assets/{erb-HbktAFZJ.js → erb-DD6OUyvt.js} +1 -1
- package/dist/assets/{git-rebase-Ccd_ELAP.js → git-rebase-HbJMn7iS.js} +1 -1
- package/dist/assets/{glimmer-js-BcinUfi2.js → glimmer-js-BRuqbNaV.js} +1 -1
- package/dist/assets/{glimmer-ts-DuN2UkN9.js → glimmer-ts-ZBpkdEEA.js} +1 -1
- package/dist/assets/{glsl-BWmCEPsR.js → glsl-CrQ4GScf.js} +1 -1
- package/dist/assets/{graphql-DJGYoBFW.js → graphql-Tn6PBdQY.js} +1 -1
- package/dist/assets/{hack-ZPqBE9w3.js → hack-CoApvrbo.js} +1 -1
- package/dist/assets/{haml-CZDZk6Gw.js → haml-COKq8Xjp.js} +1 -1
- package/dist/assets/{handlebars-d23PDS0n.js → handlebars-CAna2DTA.js} +1 -1
- package/dist/assets/{html-BgzX2DyB.js → html-Ch4xJ7YF.js} +1 -1
- package/dist/assets/{html-derivative-Bq6M-ZFw.js → html-derivative-0ITK-MAI.js} +1 -1
- package/dist/assets/{http-DVhMl_cW.js → http-DiMWQ8Qr.js} +1 -1
- package/dist/assets/{hurl-CbspBAqF.js → hurl-OcXymC0J.js} +1 -1
- package/dist/assets/index-BmcCug2M.js +1574 -0
- package/dist/assets/index-CADUNiTy.css +2 -0
- package/dist/assets/{java--YZbq0z2.js → java-CFb3qN4r.js} +1 -1
- package/dist/assets/{javascript-cvRuifRJ.js → javascript-CRmk2JrF.js} +1 -1
- package/dist/assets/{jinja-BeZmr3LP.js → jinja-Da6lDTEy.js} +1 -1
- package/dist/assets/{jison-qrpGZaxs.js → jison-Bnivzmtu.js} +1 -1
- package/dist/assets/{json-4xUzsNJn.js → json-m8vJP_KK.js} +1 -1
- package/dist/assets/{jsx-BlXrTlSg.js → jsx-C3HX_Sbu.js} +1 -1
- package/dist/assets/{julia-IN3jzmBc.js → julia-Ckg-wSHA.js} +1 -1
- package/dist/assets/{just-C_50nLQH.js → just-DCjIAyvO.js} +1 -1
- package/dist/assets/{latex-Cd-_2IS1.js → latex-CrYSm4pl.js} +1 -1
- package/dist/assets/{liquid-CEJf6Xg5.js → liquid-MQUFSPYU.js} +1 -1
- package/dist/assets/{lua-BlaV_n8i.js → lua-DcuzJJ77.js} +1 -1
- package/dist/assets/{marko-Cb-NPdc_.js → marko-4JNmQaB4.js} +1 -1
- package/dist/assets/{mdc-Dt4ywVAS.js → mdc-C_j8HvRL.js} +1 -1
- package/dist/assets/{nginx-C_6SKViP.js → nginx-ta3Gn1HE.js} +1 -1
- package/dist/assets/{nim-BbkBjoeZ.js → nim-BwWLst9M.js} +1 -1
- package/dist/assets/{perl-CYPU6TVa.js → perl-DfW7Uq5O.js} +1 -1
- package/dist/assets/{php-B6Oqilrc.js → php-DGf5G3Cf.js} +1 -1
- package/dist/assets/{pug-C9Poo2KU.js → pug-CWI-NaYz.js} +1 -1
- package/dist/assets/{qml-DkMURMPv.js → qml-Csblgh7U.js} +1 -1
- package/dist/assets/{r-Bm-Gfi0p.js → r-QGooaGDI.js} +1 -1
- package/dist/assets/{razor-B0LqOok3.js → razor-Dp9ihLYf.js} +1 -1
- package/dist/assets/{regexp-jAbMHzwe.js → regexp-DwUnLcVr.js} +1 -1
- package/dist/assets/{rst-BlWHfg8c.js → rst-BY8GIwDJ.js} +1 -1
- package/dist/assets/{ruby-Bt5lKwPL.js → ruby-VgzI9htT.js} +1 -1
- package/dist/assets/{sas-TuY2-8EF.js → sas-DYLsm-0j.js} +1 -1
- package/dist/assets/{scss-Dh1SsWlK.js → scss-tIFkeUQX.js} +1 -1
- package/dist/assets/{shellscript-CThiwmnp.js → shellscript-CHjuQ4Pl.js} +1 -1
- package/dist/assets/{shellsession-DQ5iRUWd.js → shellsession-Bv0S0hj9.js} +1 -1
- package/dist/assets/{soy-BfM-BrZG.js → soy-D6U1tZod.js} +1 -1
- package/dist/assets/{sql-F617W1uN.js → sql-CGz8kjKv.js} +1 -1
- package/dist/assets/{stata-Ctov3ki2.js → stata-C9ytERUq.js} +1 -1
- package/dist/assets/{surrealql-C-i2bJGB.js → surrealql-DRfRnaYu.js} +1 -1
- package/dist/assets/{svelte-oZT41pTl.js → svelte-wYnk_1sI.js} +1 -1
- package/dist/assets/{templ-CxX9Hf3e.js → templ-DWrGVE0u.js} +1 -1
- package/dist/assets/{tex-GyzJq8GJ.js → tex-CYTzoUJh.js} +1 -1
- package/dist/assets/{ts-tags-BugzKu0x.js → ts-tags-1TIXEGea.js} +1 -1
- package/dist/assets/{tsx-DLqwDEUq.js → tsx-a3BoAoOh.js} +1 -1
- package/dist/assets/{twig-DADiR9Vq.js → twig-e3dvNv6o.js} +1 -1
- package/dist/assets/{typescript-BlDIG_FO.js → typescript-BBhnpnTf.js} +1 -1
- package/dist/assets/{vue-COXmmg2A.js → vue-DzzVbu9q.js} +1 -1
- package/dist/assets/{vue-html-Cy_3KYj1.js → vue-html-quBNv38K.js} +1 -1
- package/dist/assets/{vue-vine-CUqOY8dY.js → vue-vine-CDuR69FE.js} +1 -1
- package/dist/assets/{xml-C9JY7Wf6.js → xml-DMifOLSe.js} +1 -1
- package/dist/assets/{xsl-DBCl8hSu.js → xsl-D8Ef-5HK.js} +1 -1
- package/dist/assets/{yaml-BbMebKkS.js → yaml-5c38gpmA.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +12 -3
- package/scripts/agent-config.mjs +184 -0
- package/scripts/agent-exclusions.mjs +10 -0
- package/scripts/agent-review.mjs +94 -0
- package/scripts/build-diff-data.mjs +211 -49
- package/scripts/cli-args.mjs +63 -9
- package/scripts/coding-agents.mjs +255 -77
- package/scripts/generate-summaries.mjs +872 -426
- package/scripts/local-target.mjs +106 -0
- package/scripts/present.mjs +333 -117
- package/scripts/review-chat-context.mjs +510 -0
- package/scripts/review-chat-controller.mjs +691 -0
- package/scripts/review-chat-provider.mjs +363 -0
- package/scripts/review-chat.mjs +8 -0
- package/scripts/serve-built.mjs +245 -27
- package/scripts/summary-path.mjs +32 -11
- package/dist/assets/index-C2p_1iAk.css +0 -2
- package/dist/assets/index-divlsuKs.js +0 -1568
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
|
|
3
|
+
export function isBaseWorktreeTarget({
|
|
4
|
+
base,
|
|
5
|
+
branch,
|
|
6
|
+
checkout,
|
|
7
|
+
head,
|
|
8
|
+
pullRequest,
|
|
9
|
+
worktree,
|
|
10
|
+
}) {
|
|
11
|
+
return Boolean(base) && [branch, checkout, head, pullRequest, worktree]
|
|
12
|
+
.every((value) => !value);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const checkoutTargetKinds = new Set([
|
|
16
|
+
'checkout',
|
|
17
|
+
'worktree',
|
|
18
|
+
'base-worktree',
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
function localTargetKind({ checkout, worktree }) {
|
|
22
|
+
if (checkout) return 'checkout';
|
|
23
|
+
if (worktree) return 'worktree';
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function remoteTargetKind({ branch, pullRequest }) {
|
|
28
|
+
if (pullRequest) return 'pr';
|
|
29
|
+
if (branch) return 'branch';
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function hasRangeTarget({ base, head, range }) {
|
|
34
|
+
return Boolean(range || base || head);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function gitTargetKind(options) {
|
|
38
|
+
const { base, branch, checkout, head, pullRequest, worktree } = options;
|
|
39
|
+
if (isBaseWorktreeTarget({
|
|
40
|
+
base,
|
|
41
|
+
branch,
|
|
42
|
+
checkout,
|
|
43
|
+
head,
|
|
44
|
+
pullRequest,
|
|
45
|
+
worktree,
|
|
46
|
+
})) return 'base-worktree';
|
|
47
|
+
if (hasRangeTarget(options)) return 'range';
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function explicitTargetKind(options) {
|
|
52
|
+
return localTargetKind(options) ||
|
|
53
|
+
remoteTargetKind(options) ||
|
|
54
|
+
gitTargetKind(options);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function resolvedTargetKind(options) {
|
|
58
|
+
const explicit = explicitTargetKind(options);
|
|
59
|
+
if (explicit) return explicit;
|
|
60
|
+
if (options.snapshotSupplied) return undefined;
|
|
61
|
+
return 'worktree';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function reviewAccessMode({
|
|
65
|
+
repo,
|
|
66
|
+
base,
|
|
67
|
+
branch,
|
|
68
|
+
checkout,
|
|
69
|
+
head,
|
|
70
|
+
noCheckoutAccess = false,
|
|
71
|
+
pullRequest,
|
|
72
|
+
range,
|
|
73
|
+
snapshotSupplied = false,
|
|
74
|
+
worktree,
|
|
75
|
+
}) {
|
|
76
|
+
if (noCheckoutAccess) {
|
|
77
|
+
return { mode: 'snapshot-only', reason: 'disabled' };
|
|
78
|
+
}
|
|
79
|
+
const targetKind = resolvedTargetKind({
|
|
80
|
+
base,
|
|
81
|
+
branch,
|
|
82
|
+
checkout,
|
|
83
|
+
head,
|
|
84
|
+
pullRequest,
|
|
85
|
+
range,
|
|
86
|
+
snapshotSupplied,
|
|
87
|
+
worktree,
|
|
88
|
+
});
|
|
89
|
+
if (checkoutTargetKinds.has(targetKind)) {
|
|
90
|
+
return { mode: 'checkout-read-only', root: repo };
|
|
91
|
+
}
|
|
92
|
+
return { mode: 'snapshot-only', reason: 'target-mismatch' };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function resolveBaseWorktreeCommit(repo, base) {
|
|
96
|
+
const result = spawnSync(
|
|
97
|
+
'git',
|
|
98
|
+
['-C', repo, 'rev-parse', '--verify', `${base}^{commit}`],
|
|
99
|
+
{ encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] },
|
|
100
|
+
);
|
|
101
|
+
const oid = result.status === 0 ? result.stdout.trim() : '';
|
|
102
|
+
if (!oid) {
|
|
103
|
+
throw new Error(`Could not resolve base ref "${base}" to a commit.`);
|
|
104
|
+
}
|
|
105
|
+
return oid;
|
|
106
|
+
}
|
package/scripts/present.mjs
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
mkdirSync,
|
|
8
8
|
mkdtempSync,
|
|
9
9
|
readFileSync,
|
|
10
|
+
renameSync,
|
|
10
11
|
rmSync,
|
|
11
12
|
statSync,
|
|
12
13
|
writeFileSync,
|
|
@@ -16,12 +17,20 @@ import { dirname, join, resolve } from 'node:path';
|
|
|
16
17
|
import { createInterface } from 'node:readline';
|
|
17
18
|
import { fileURLToPath } from 'node:url';
|
|
18
19
|
import { helpText, parseCliArgs } from './cli-args.mjs';
|
|
20
|
+
import { applyAgentConfigOperation } from './agent-config.mjs';
|
|
19
21
|
import {
|
|
22
|
+
agentReadOnlyWarning,
|
|
20
23
|
assertReasoningSupported,
|
|
24
|
+
codingAgentFromSelectionError,
|
|
21
25
|
codingAgentAvailability,
|
|
22
26
|
codingAgentBinary,
|
|
23
27
|
selectCodingAgent,
|
|
24
28
|
} from './coding-agents.mjs';
|
|
29
|
+
import {
|
|
30
|
+
isBaseWorktreeTarget,
|
|
31
|
+
reviewAccessMode,
|
|
32
|
+
resolveBaseWorktreeCommit,
|
|
33
|
+
} from './local-target.mjs';
|
|
25
34
|
import { doctorReport } from './doctor.mjs';
|
|
26
35
|
import { accessTokenDirectory } from './access-token.mjs';
|
|
27
36
|
import { cacheStatus, clearCache, formatCacheStatus, pruneCache } from './cache.mjs';
|
|
@@ -119,6 +128,22 @@ if (cli.version) {
|
|
|
119
128
|
console.log(`diffsplain ${packageJson.version}`);
|
|
120
129
|
process.exit(0);
|
|
121
130
|
}
|
|
131
|
+
if (cli.config) {
|
|
132
|
+
try {
|
|
133
|
+
const result = applyAgentConfigOperation(cli.config);
|
|
134
|
+
if (result.kind === 'show') {
|
|
135
|
+
console.log(result.agent ?? 'No default coding agent is configured.');
|
|
136
|
+
} else if (result.kind === 'set') {
|
|
137
|
+
console.log(`Default coding agent set to "${result.agent}".`);
|
|
138
|
+
} else {
|
|
139
|
+
console.log('Default coding agent unset.');
|
|
140
|
+
}
|
|
141
|
+
process.exit(0);
|
|
142
|
+
} catch (error) {
|
|
143
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
122
147
|
if (cli.doctor) {
|
|
123
148
|
if (cli.doctor.deep) {
|
|
124
149
|
console.error(
|
|
@@ -178,33 +203,90 @@ function emitSupportRecord(code = 1) {
|
|
|
178
203
|
}
|
|
179
204
|
}
|
|
180
205
|
|
|
206
|
+
function targetOption(args, name) {
|
|
207
|
+
const index = args.indexOf(name);
|
|
208
|
+
return index === -1 ? undefined : args[index + 1];
|
|
209
|
+
}
|
|
210
|
+
|
|
181
211
|
const { agentEnabled, browserEnabled, host, port } = cli;
|
|
182
212
|
const feedArgs = [...cli.feedArgs];
|
|
183
213
|
const agentArgs = [...cli.agentArgs];
|
|
214
|
+
const base = targetOption(feedArgs, '--base');
|
|
215
|
+
const head = targetOption(feedArgs, '--head');
|
|
216
|
+
const branch = targetOption(feedArgs, '--branch');
|
|
217
|
+
const checkout = feedArgs.includes('--checkout');
|
|
218
|
+
const reviewRepo = targetOption(feedArgs, '--repo');
|
|
219
|
+
const accessMode = reviewAccessMode({
|
|
220
|
+
repo: reviewRepo,
|
|
221
|
+
base,
|
|
222
|
+
branch,
|
|
223
|
+
checkout,
|
|
224
|
+
head,
|
|
225
|
+
noCheckoutAccess: cli.noCheckoutAccess,
|
|
226
|
+
pullRequest: targetOption(feedArgs, '--pr'),
|
|
227
|
+
worktree: feedArgs.includes('--worktree'),
|
|
228
|
+
});
|
|
229
|
+
if (isBaseWorktreeTarget({
|
|
230
|
+
base,
|
|
231
|
+
branch,
|
|
232
|
+
checkout,
|
|
233
|
+
head,
|
|
234
|
+
pullRequest: targetOption(feedArgs, '--pr'),
|
|
235
|
+
worktree: feedArgs.includes('--worktree'),
|
|
236
|
+
})) {
|
|
237
|
+
try {
|
|
238
|
+
resolveBaseWorktreeCommit(targetOption(feedArgs, '--repo'), base);
|
|
239
|
+
} catch (error) {
|
|
240
|
+
console.error(error.message);
|
|
241
|
+
process.exit(2);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
184
244
|
let selectedAgent;
|
|
245
|
+
let selectedAgentBinary;
|
|
185
246
|
beginSupportRecord();
|
|
186
247
|
if (agentEnabled) {
|
|
187
248
|
const selectionStarted = performance.now();
|
|
188
249
|
try {
|
|
189
250
|
selectedAgent = await selectCodingAgent(
|
|
190
251
|
cli.agent,
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
252
|
+
{
|
|
253
|
+
available: (agent) =>
|
|
254
|
+
codingAgentAvailability(agent, {
|
|
255
|
+
binary: codingAgentBinary(agent, { codexBin: cli.codexBin }),
|
|
256
|
+
}),
|
|
257
|
+
},
|
|
195
258
|
);
|
|
196
259
|
assertReasoningSupported(selectedAgent, cli.reasoning);
|
|
197
|
-
|
|
260
|
+
selectedAgentBinary = codingAgentBinary(selectedAgent, {
|
|
198
261
|
codexBin: cli.codexBin,
|
|
199
262
|
});
|
|
200
|
-
recordSelectedProvider(selectedAgent,
|
|
263
|
+
recordSelectedProvider(selectedAgent, selectedAgentBinary);
|
|
201
264
|
supportRecorder?.addStage(
|
|
202
265
|
'agent',
|
|
203
266
|
performance.now() - selectionStarted,
|
|
204
267
|
);
|
|
205
268
|
agentArgs.push('--agent', selectedAgent);
|
|
269
|
+
if (
|
|
270
|
+
accessMode.mode === 'snapshot-only' &&
|
|
271
|
+
accessMode.reason === 'target-mismatch'
|
|
272
|
+
) {
|
|
273
|
+
console.log(
|
|
274
|
+
'Warning: This target does not map to the live checkout. Agent notes will use the supplied snapshot only.',
|
|
275
|
+
);
|
|
276
|
+
agentArgs.push('--access-warning-emitted');
|
|
277
|
+
}
|
|
278
|
+
const readOnlyWarning = agentReadOnlyWarning(selectedAgent, accessMode);
|
|
279
|
+
if (readOnlyWarning) {
|
|
280
|
+
console.log(readOnlyWarning);
|
|
281
|
+
agentArgs.push('--provider-read-only-warning-emitted');
|
|
282
|
+
}
|
|
206
283
|
} catch (error) {
|
|
207
|
-
recordSelectedProvider(
|
|
284
|
+
recordSelectedProvider(
|
|
285
|
+
codingAgentFromSelectionError(error) ||
|
|
286
|
+
selectedAgent ||
|
|
287
|
+
cli.agent ||
|
|
288
|
+
'unknown',
|
|
289
|
+
);
|
|
208
290
|
supportRecorder?.addStage(
|
|
209
291
|
'agent',
|
|
210
292
|
performance.now() - selectionStarted,
|
|
@@ -217,6 +299,12 @@ if (agentEnabled) {
|
|
|
217
299
|
} else {
|
|
218
300
|
feedArgs.push('--no-summaries');
|
|
219
301
|
}
|
|
302
|
+
if (agentEnabled) {
|
|
303
|
+
agentArgs.push('--access-mode', accessMode.mode);
|
|
304
|
+
if (accessMode.mode === 'snapshot-only') {
|
|
305
|
+
agentArgs.push('--access-reason', accessMode.reason);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
220
308
|
const outputIndex = feedArgs.indexOf('--output');
|
|
221
309
|
let runtimeDirectory;
|
|
222
310
|
|
|
@@ -226,16 +314,27 @@ if (outputIndex === -1) {
|
|
|
226
314
|
feedArgs.push('--output', liveOutput);
|
|
227
315
|
agentArgs.push('--output', liveOutput);
|
|
228
316
|
}
|
|
317
|
+
const outputPath = resolve(
|
|
318
|
+
callerDirectory,
|
|
319
|
+
feedArgs[feedArgs.indexOf('--output') + 1],
|
|
320
|
+
);
|
|
321
|
+
let rawSnapshotPath = outputPath;
|
|
322
|
+
if (agentEnabled) {
|
|
323
|
+
if (!runtimeDirectory) {
|
|
324
|
+
runtimeDirectory = mkdtempSync(join(tmpdir(), 'diffsplain-live-'));
|
|
325
|
+
}
|
|
326
|
+
rawSnapshotPath = resolve(runtimeDirectory, 'raw-diff-data.json');
|
|
327
|
+
const feedOutputIndex = feedArgs.indexOf('--output');
|
|
328
|
+
feedArgs[feedOutputIndex + 1] = rawSnapshotPath;
|
|
329
|
+
feedArgs.push('--exclude-output', outputPath);
|
|
330
|
+
}
|
|
331
|
+
const reviewSnapshotPath = agentEnabled ? rawSnapshotPath : outputPath;
|
|
229
332
|
process.on('exit', () => {
|
|
230
333
|
if (runtimeDirectory) {
|
|
231
334
|
rmSync(runtimeDirectory, { recursive: true, force: true });
|
|
232
335
|
}
|
|
233
336
|
});
|
|
234
337
|
if (!feedArgs.includes('--watch')) feedArgs.push('--watch');
|
|
235
|
-
const outputPath = resolve(
|
|
236
|
-
callerDirectory,
|
|
237
|
-
feedArgs[feedArgs.indexOf('--output') + 1],
|
|
238
|
-
);
|
|
239
338
|
const repoIndex = feedArgs.indexOf('--repo');
|
|
240
339
|
const remoteIndex = feedArgs.indexOf('--remote');
|
|
241
340
|
const projectKey = createHash('sha256')
|
|
@@ -262,7 +361,7 @@ writeFileSync(accessPath, access, { mode: 0o600 });
|
|
|
262
361
|
chmodSync(accessPath, 0o600);
|
|
263
362
|
if (agentEnabled) {
|
|
264
363
|
feedArgs.push('--ignore-summary-watch');
|
|
265
|
-
agentArgs.push('--snapshot',
|
|
364
|
+
agentArgs.push('--snapshot', rawSnapshotPath);
|
|
266
365
|
}
|
|
267
366
|
|
|
268
367
|
const snapshotStarted = performance.now();
|
|
@@ -334,118 +433,185 @@ function scheduleBrowserOpen(match) {
|
|
|
334
433
|
}, 750);
|
|
335
434
|
}
|
|
336
435
|
|
|
436
|
+
function optionalSiteArgument(flag, value) {
|
|
437
|
+
return value ? [flag, value] : [];
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function chatAccessArguments() {
|
|
441
|
+
if (accessMode.mode !== 'checkout-read-only') return [];
|
|
442
|
+
return ['--chat-access-root', accessMode.root];
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function chatAgentArguments() {
|
|
446
|
+
if (!agentEnabled) return [];
|
|
447
|
+
return [
|
|
448
|
+
'--chat-agent',
|
|
449
|
+
selectedAgent,
|
|
450
|
+
'--chat-binary',
|
|
451
|
+
selectedAgentBinary,
|
|
452
|
+
'--chat-access-mode',
|
|
453
|
+
accessMode.mode,
|
|
454
|
+
...chatAccessArguments(),
|
|
455
|
+
...optionalSiteArgument('--chat-model', cli.model),
|
|
456
|
+
...optionalSiteArgument('--chat-reasoning', cli.reasoning),
|
|
457
|
+
];
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function siteArguments() {
|
|
461
|
+
return [
|
|
462
|
+
resolve(root, 'scripts/serve-built.mjs'),
|
|
463
|
+
'--output',
|
|
464
|
+
outputPath,
|
|
465
|
+
'--port',
|
|
466
|
+
String(port),
|
|
467
|
+
'--host',
|
|
468
|
+
host,
|
|
469
|
+
'--project',
|
|
470
|
+
projectKey,
|
|
471
|
+
'--access',
|
|
472
|
+
access,
|
|
473
|
+
...optionalSiteArgument('--previous-access', previousAccess),
|
|
474
|
+
'--chat-snapshot',
|
|
475
|
+
outputPath,
|
|
476
|
+
...chatAgentArguments(),
|
|
477
|
+
...(!cli.portWasPassed ? ['--increment-port'] : []),
|
|
478
|
+
];
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function handleSiteLine(line) {
|
|
482
|
+
if (handleConnectedTab(line)) return;
|
|
483
|
+
console.log(line);
|
|
484
|
+
const match = line.match(/^Diffsplain: (http:\/\/\S+)$/);
|
|
485
|
+
markSiteReady(match);
|
|
486
|
+
scheduleBrowserOpen(match);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function listenForSiteLines(child) {
|
|
490
|
+
if (!child.stdout) return;
|
|
491
|
+
createInterface({ input: child.stdout }).on('line', handleSiteLine);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function siteFailed(code, signal) {
|
|
495
|
+
return Boolean(code || signal);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function recordSiteFailure(code) {
|
|
499
|
+
supportRecorder?.addStage('serve', performance.now() - siteStarted, 'failed');
|
|
500
|
+
emitSupportRecord(code || 1);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function handleSiteExit(code, signal) {
|
|
504
|
+
if (closing) return;
|
|
505
|
+
if (siteFailed(code, signal)) recordSiteFailure(code);
|
|
506
|
+
stop(code || (signal ? 1 : 0));
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function handleSiteError(error) {
|
|
510
|
+
if (closing) return;
|
|
511
|
+
supportRecorder?.addStage('serve', performance.now() - siteStarted, 'failed');
|
|
512
|
+
console.error(`Could not start the local page: ${error.message}`);
|
|
513
|
+
emitSupportRecord();
|
|
514
|
+
stop(1);
|
|
515
|
+
}
|
|
516
|
+
|
|
337
517
|
function startSite() {
|
|
338
518
|
if (closing || site) return;
|
|
339
519
|
siteStarted = performance.now();
|
|
340
|
-
const child = spawn(
|
|
341
|
-
|
|
342
|
-
[
|
|
343
|
-
|
|
344
|
-
'--output',
|
|
345
|
-
outputPath,
|
|
346
|
-
'--port',
|
|
347
|
-
String(port),
|
|
348
|
-
'--host',
|
|
349
|
-
host,
|
|
350
|
-
'--project',
|
|
351
|
-
projectKey,
|
|
352
|
-
'--access',
|
|
353
|
-
access,
|
|
354
|
-
...(previousAccess ? ['--previous-access', previousAccess] : []),
|
|
355
|
-
...(!cli.portWasPassed ? ['--increment-port'] : []),
|
|
356
|
-
],
|
|
357
|
-
{ cwd: root, stdio: ['ignore', 'pipe', 'inherit'] },
|
|
358
|
-
);
|
|
520
|
+
const child = spawn(process.execPath, siteArguments(), {
|
|
521
|
+
cwd: root,
|
|
522
|
+
stdio: ['ignore', 'pipe', 'inherit'],
|
|
523
|
+
});
|
|
359
524
|
site = child;
|
|
525
|
+
listenForSiteLines(child);
|
|
526
|
+
child.on('exit', handleSiteExit);
|
|
527
|
+
child.on('error', handleSiteError);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function savedReviewFingerprint(notes) {
|
|
531
|
+
if (!notes) return undefined;
|
|
532
|
+
if (notes.agentReviewFingerprint) return notes.agentReviewFingerprint;
|
|
533
|
+
return notes.reviewFingerprint;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
function snapshotReviewFingerprint(snapshot) {
|
|
537
|
+
const savedFingerprint = savedReviewFingerprint(snapshot.notes);
|
|
538
|
+
if (savedFingerprint) return savedFingerprint;
|
|
539
|
+
const reviewData = {
|
|
540
|
+
repo: {
|
|
541
|
+
name: snapshot.repo.name,
|
|
542
|
+
base: snapshot.repo.base,
|
|
543
|
+
head: snapshot.repo.head,
|
|
544
|
+
branch: snapshot.repo.branch,
|
|
545
|
+
baseBranch: snapshot.repo.baseBranch,
|
|
546
|
+
remote: snapshot.repo.remote,
|
|
547
|
+
targetKind: snapshot.repo.target?.kind,
|
|
548
|
+
},
|
|
549
|
+
change: {
|
|
550
|
+
title: snapshot.change.title,
|
|
551
|
+
number: snapshot.change.number,
|
|
552
|
+
},
|
|
553
|
+
files: snapshot.files.map((file) => ({
|
|
554
|
+
path: file.path,
|
|
555
|
+
oldPath: file.oldPath,
|
|
556
|
+
status: file.status,
|
|
557
|
+
additions: file.additions,
|
|
558
|
+
deletions: file.deletions,
|
|
559
|
+
isBinary: file.isBinary,
|
|
560
|
+
patch: file.patch,
|
|
561
|
+
})),
|
|
562
|
+
};
|
|
563
|
+
return createHash('sha256')
|
|
564
|
+
.update(JSON.stringify(reviewData))
|
|
565
|
+
.digest('hex');
|
|
566
|
+
}
|
|
360
567
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
});
|
|
370
|
-
}
|
|
568
|
+
function noteMetadataMatches(notes, fingerprint) {
|
|
569
|
+
return [
|
|
570
|
+
notes.complete,
|
|
571
|
+
notes.fresh,
|
|
572
|
+
notes.generatedFor === fingerprint,
|
|
573
|
+
notes.accessMode === accessMode.mode,
|
|
574
|
+
].every(Boolean);
|
|
575
|
+
}
|
|
371
576
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
577
|
+
function normalizedAgentSetting(value) {
|
|
578
|
+
return value ?? null;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
function agentSettingsMatch(notes) {
|
|
582
|
+
return [
|
|
583
|
+
notes.agent === selectedAgent,
|
|
584
|
+
normalizedAgentSetting(notes.model) === normalizedAgentSetting(cli.model),
|
|
585
|
+
normalizedAgentSetting(notes.reasoning) ===
|
|
586
|
+
normalizedAgentSetting(cli.reasoning),
|
|
587
|
+
].every(Boolean);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
function emptyReview(files) {
|
|
591
|
+
return Array.isArray(files) && files.length === 0;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
function hasCurrentAgentNotes(snapshot, fingerprint) {
|
|
595
|
+
const notes = snapshot.notes;
|
|
596
|
+
if (!savedReviewFingerprint(notes)) return false;
|
|
597
|
+
if (!noteMetadataMatches(notes, fingerprint)) return false;
|
|
598
|
+
if (emptyReview(snapshot.files)) return true;
|
|
599
|
+
return agentSettingsMatch(notes);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
function snapshotStateFromSnapshot(snapshot) {
|
|
603
|
+
const fingerprint = snapshotReviewFingerprint(snapshot);
|
|
604
|
+
return {
|
|
605
|
+
fingerprint,
|
|
606
|
+
hasCurrentAgentNotes: hasCurrentAgentNotes(snapshot, fingerprint),
|
|
607
|
+
};
|
|
397
608
|
}
|
|
398
609
|
|
|
399
610
|
function snapshotState() {
|
|
400
611
|
try {
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
const emptyReview =
|
|
405
|
-
Array.isArray(snapshot.files) && snapshot.files.length === 0;
|
|
406
|
-
return {
|
|
407
|
-
fingerprint,
|
|
408
|
-
hasCurrentAgentNotes:
|
|
409
|
-
snapshot.notes.complete &&
|
|
410
|
-
snapshot.notes.fresh &&
|
|
411
|
-
snapshot.notes.generatedFor === fingerprint &&
|
|
412
|
-
(emptyReview ||
|
|
413
|
-
(snapshot.notes.agent === selectedAgent &&
|
|
414
|
-
(snapshot.notes.model || null) === (cli.model || null) &&
|
|
415
|
-
(snapshot.notes.reasoning || null) ===
|
|
416
|
-
(cli.reasoning || null))),
|
|
417
|
-
};
|
|
418
|
-
}
|
|
419
|
-
const reviewData = {
|
|
420
|
-
repo: {
|
|
421
|
-
name: snapshot.repo.name,
|
|
422
|
-
base: snapshot.repo.base,
|
|
423
|
-
head: snapshot.repo.head,
|
|
424
|
-
branch: snapshot.repo.branch,
|
|
425
|
-
baseBranch: snapshot.repo.baseBranch,
|
|
426
|
-
remote: snapshot.repo.remote,
|
|
427
|
-
targetKind: snapshot.repo.target?.kind,
|
|
428
|
-
},
|
|
429
|
-
change: {
|
|
430
|
-
title: snapshot.change.title,
|
|
431
|
-
number: snapshot.change.number,
|
|
432
|
-
},
|
|
433
|
-
files: snapshot.files.map((file) => ({
|
|
434
|
-
path: file.path,
|
|
435
|
-
oldPath: file.oldPath,
|
|
436
|
-
status: file.status,
|
|
437
|
-
additions: file.additions,
|
|
438
|
-
deletions: file.deletions,
|
|
439
|
-
isBinary: file.isBinary,
|
|
440
|
-
patch: file.patch,
|
|
441
|
-
})),
|
|
442
|
-
};
|
|
443
|
-
return {
|
|
444
|
-
fingerprint: createHash('sha256')
|
|
445
|
-
.update(JSON.stringify(reviewData))
|
|
446
|
-
.digest('hex'),
|
|
447
|
-
hasCurrentAgentNotes: false,
|
|
448
|
-
};
|
|
612
|
+
return snapshotStateFromSnapshot(
|
|
613
|
+
JSON.parse(readFileSync(reviewSnapshotPath, 'utf8')),
|
|
614
|
+
);
|
|
449
615
|
} catch {
|
|
450
616
|
return undefined;
|
|
451
617
|
}
|
|
@@ -586,12 +752,16 @@ function scheduleAgent(fingerprint) {
|
|
|
586
752
|
if (agent) {
|
|
587
753
|
if (selectedFingerprint !== agentFingerprint) {
|
|
588
754
|
queuedFingerprint = selectedFingerprint;
|
|
755
|
+
console.log(
|
|
756
|
+
'Review changed while notes were generated. Restarting agent notes...',
|
|
757
|
+
);
|
|
589
758
|
agent.kill('SIGTERM');
|
|
590
759
|
}
|
|
591
760
|
return;
|
|
592
761
|
}
|
|
593
762
|
clearTimeout(agentTimer);
|
|
594
763
|
const delay = agentFingerprint ? 300 : 0;
|
|
764
|
+
console.log('Preparing agent notes...');
|
|
595
765
|
agentTimer = setTimeout(() => runAgent(selectedFingerprint), delay);
|
|
596
766
|
}
|
|
597
767
|
|
|
@@ -599,7 +769,7 @@ function markSnapshotReady() {
|
|
|
599
769
|
if (snapshotReady) return;
|
|
600
770
|
snapshotReady = true;
|
|
601
771
|
try {
|
|
602
|
-
supportRecorder?.addBytes('snapshot', statSync(
|
|
772
|
+
supportRecorder?.addBytes('snapshot', statSync(reviewSnapshotPath).size);
|
|
603
773
|
} catch {}
|
|
604
774
|
supportRecorder?.addStage(
|
|
605
775
|
'snapshot',
|
|
@@ -607,11 +777,55 @@ function markSnapshotReady() {
|
|
|
607
777
|
);
|
|
608
778
|
}
|
|
609
779
|
|
|
780
|
+
function snapshotForPresentation(snapshot) {
|
|
781
|
+
if (snapshotStateFromSnapshot(snapshot).hasCurrentAgentNotes) {
|
|
782
|
+
return snapshot;
|
|
783
|
+
}
|
|
784
|
+
const content = {
|
|
785
|
+
...snapshot,
|
|
786
|
+
notes: {
|
|
787
|
+
...snapshot.notes,
|
|
788
|
+
complete: false,
|
|
789
|
+
status: 'generating',
|
|
790
|
+
},
|
|
791
|
+
};
|
|
792
|
+
delete content.version;
|
|
793
|
+
delete content.generatedAt;
|
|
794
|
+
return {
|
|
795
|
+
version: createHash('sha256')
|
|
796
|
+
.update(JSON.stringify(content))
|
|
797
|
+
.digest('hex')
|
|
798
|
+
.slice(0, 12),
|
|
799
|
+
generatedAt: new Date().toISOString(),
|
|
800
|
+
...content,
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
function seedPresentationSnapshot() {
|
|
805
|
+
const snapshot = snapshotForPresentation(
|
|
806
|
+
JSON.parse(readFileSync(rawSnapshotPath, 'utf8')),
|
|
807
|
+
);
|
|
808
|
+
mkdirSync(dirname(outputPath), { recursive: true });
|
|
809
|
+
const pendingOutput = `${outputPath}.${process.pid}.${randomBytes(8).toString('hex')}.tmp`;
|
|
810
|
+
writeFileSync(pendingOutput, `${JSON.stringify(snapshot, null, 2)}\n`);
|
|
811
|
+
renameSync(pendingOutput, outputPath);
|
|
812
|
+
}
|
|
813
|
+
|
|
610
814
|
function isSnapshotLine(line) {
|
|
611
815
|
return line.startsWith('Wrote ') || line === 'No diff-data changes';
|
|
612
816
|
}
|
|
613
817
|
|
|
614
|
-
function startSnapshotDependents() {
|
|
818
|
+
function startSnapshotDependents({ rawSnapshotWritten = false } = {}) {
|
|
819
|
+
if (agentEnabled && rawSnapshotWritten) {
|
|
820
|
+
try {
|
|
821
|
+
seedPresentationSnapshot();
|
|
822
|
+
} catch (error) {
|
|
823
|
+
console.error(`Could not publish the live snapshot: ${error.message}`);
|
|
824
|
+
emitSupportRecord();
|
|
825
|
+
stop(1);
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
}
|
|
615
829
|
markSnapshotReady();
|
|
616
830
|
startSite();
|
|
617
831
|
if (agentEnabled) scheduleAgent();
|
|
@@ -622,7 +836,9 @@ function shouldPrintFeedLine(line) {
|
|
|
622
836
|
}
|
|
623
837
|
|
|
624
838
|
function handleFeedLine(line) {
|
|
625
|
-
if (isSnapshotLine(line))
|
|
839
|
+
if (isSnapshotLine(line)) {
|
|
840
|
+
startSnapshotDependents({ rawSnapshotWritten: line.startsWith('Wrote ') });
|
|
841
|
+
}
|
|
626
842
|
if (shouldPrintFeedLine(line)) console.log(line);
|
|
627
843
|
}
|
|
628
844
|
|