diffsplain 0.8.2 → 0.9.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.
Files changed (93) hide show
  1. package/README.md +10 -5
  2. package/dist/assets/{angular-html-CphzFC_v.js → angular-html-m_m91ZYB.js} +1 -1
  3. package/dist/assets/{angular-ts-Am0-mbFw.js → angular-ts-CO5Z--Rb.js} +1 -1
  4. package/dist/assets/{apl-Cs2rPKDN.js → apl-ogYYgLUu.js} +1 -1
  5. package/dist/assets/{astro-CxKUUn9h.js → astro-DAfY3cNR.js} +1 -1
  6. package/dist/assets/{blade-DqeCa6v5.js → blade-E6RzsLUV.js} +1 -1
  7. package/dist/assets/{c-DdI6lknk.js → c-kKdxhtHF.js} +1 -1
  8. package/dist/assets/{cobol-COuAGE2X.js → cobol-3oDDyFNf.js} +1 -1
  9. package/dist/assets/{coffee-BFlYT3Lp.js → coffee-dsiILuWe.js} +1 -1
  10. package/dist/assets/{cpp-nywLU0Np.js → cpp-DYyNFq__.js} +1 -1
  11. package/dist/assets/{crystal-BWp4dz1s.js → crystal-B7dVp9TF.js} +1 -1
  12. package/dist/assets/{css-CRb9tUpU.js → css-DlxJ0aex.js} +1 -1
  13. package/dist/assets/{edge-rUYSbJy4.js → edge-BjEVphkC.js} +1 -1
  14. package/dist/assets/{elixir-DOq9ybIH.js → elixir-0xTOtMcM.js} +1 -1
  15. package/dist/assets/{elm-C8e38E5-.js → elm-iQk_Wrxq.js} +1 -1
  16. package/dist/assets/{erb-BR9Q37Et.js → erb-BO74ztiE.js} +1 -1
  17. package/dist/assets/{git-rebase-Coo2LUtB.js → git-rebase-BN667QRY.js} +1 -1
  18. package/dist/assets/{glimmer-js-C7hX9jg6.js → glimmer-js-DTGUIHeS.js} +1 -1
  19. package/dist/assets/{glimmer-ts-CZLT0F0k.js → glimmer-ts-BU3oQn2d.js} +1 -1
  20. package/dist/assets/{glsl-BDZRv6e-.js → glsl-KLiqLJi2.js} +1 -1
  21. package/dist/assets/{graphql-DyzFc3y3.js → graphql-D_idcsoJ.js} +1 -1
  22. package/dist/assets/{hack-CWnO7WsF.js → hack-BXlsgPIr.js} +1 -1
  23. package/dist/assets/{haml-CvDj-ghO.js → haml-fP0aUg4-.js} +1 -1
  24. package/dist/assets/{handlebars-BnU_YcVA.js → handlebars-C1ZEaot-.js} +1 -1
  25. package/dist/assets/{html-CjB2TKKa.js → html-D04qIuup.js} +1 -1
  26. package/dist/assets/{html-derivative-CYQqEXS6.js → html-derivative-C6ai-9QL.js} +1 -1
  27. package/dist/assets/{http-NNQbnqIx.js → http-C7bz3ZvQ.js} +1 -1
  28. package/dist/assets/{hurl-DHaAA1BS.js → hurl-jL6B6Ahr.js} +1 -1
  29. package/dist/assets/index-BPaJGi-m.js +1574 -0
  30. package/dist/assets/index-VAyBpb3u.css +2 -0
  31. package/dist/assets/{java-D1J5V2CD.js → java-BVzvLCON.js} +1 -1
  32. package/dist/assets/{javascript-C3pX-HsE.js → javascript-UDbZHxCI.js} +1 -1
  33. package/dist/assets/{jinja-BY9CIY8g.js → jinja-DB-QKo2A.js} +1 -1
  34. package/dist/assets/{jison-Cr0H89gS.js → jison-B1ZbR2FF.js} +1 -1
  35. package/dist/assets/{json-CA3kKNfq.js → json-DV4WwJzT.js} +1 -1
  36. package/dist/assets/{jsx-BuMkR-Lj.js → jsx-DS6_VTvW.js} +1 -1
  37. package/dist/assets/{julia-C2sLwg3f.js → julia-C4tevET0.js} +1 -1
  38. package/dist/assets/{just-pXpkLBXv.js → just-CWMjefzB.js} +1 -1
  39. package/dist/assets/{latex-7v3EPOdF.js → latex-tyEFf_Af.js} +1 -1
  40. package/dist/assets/{liquid-CJZq0Z0t.js → liquid-C3d1X1rL.js} +1 -1
  41. package/dist/assets/{lua-BimBzvTH.js → lua-sXvrzLQY.js} +1 -1
  42. package/dist/assets/{marko-CCZHVnQN.js → marko-CQweucif.js} +1 -1
  43. package/dist/assets/{mdc-SLECNw2A.js → mdc-TrxLWF7s.js} +1 -1
  44. package/dist/assets/{nginx-DUwtUge2.js → nginx-ChE2nSNJ.js} +1 -1
  45. package/dist/assets/{nim-HD8o_Omp.js → nim-BY1CTRfx.js} +1 -1
  46. package/dist/assets/{perl-BGMeIkNk.js → perl-Cvj2PQQH.js} +1 -1
  47. package/dist/assets/{php-BL5xuY-v.js → php-CGmHp8HW.js} +1 -1
  48. package/dist/assets/{pug-C8G1U_u8.js → pug-DM4S0bRr.js} +1 -1
  49. package/dist/assets/{qml-BGVSyw1x.js → qml-DI89Al9K.js} +1 -1
  50. package/dist/assets/{r-BDnRcclC.js → r-Bs5_4yYh.js} +1 -1
  51. package/dist/assets/{razor-I8tbEIVJ.js → razor-DimzayhZ.js} +1 -1
  52. package/dist/assets/{regexp-D0yakB9Q.js → regexp-BLACz0cx.js} +1 -1
  53. package/dist/assets/{rst-DbTCn07T.js → rst-DcWzxQh2.js} +1 -1
  54. package/dist/assets/{ruby-BwgPFIcO.js → ruby-Dlat5E8d.js} +1 -1
  55. package/dist/assets/{sas-dN4ePfXU.js → sas-CCDmMaBY.js} +1 -1
  56. package/dist/assets/{scss-T7M1A4iM.js → scss-BPiU2nu6.js} +1 -1
  57. package/dist/assets/{shellscript-HFjBHS7_.js → shellscript-CKinVNrN.js} +1 -1
  58. package/dist/assets/{shellsession-DCAGskrx.js → shellsession-Cea8Pu5d.js} +1 -1
  59. package/dist/assets/{soy-BgT96TF9.js → soy--BRAPY4-.js} +1 -1
  60. package/dist/assets/{sql-DCIQeRzx.js → sql-DjKiCyWK.js} +1 -1
  61. package/dist/assets/{stata-Dn0tad0_.js → stata-CDvo3J6y.js} +1 -1
  62. package/dist/assets/{surrealql-CCgFEQfW.js → surrealql-C7b7E5sh.js} +1 -1
  63. package/dist/assets/{svelte-ITXimG8u.js → svelte-DO0tKtiK.js} +1 -1
  64. package/dist/assets/{templ-DJVZZqPw.js → templ-DhhmR_rv.js} +1 -1
  65. package/dist/assets/{tex-Bn4N-OtH.js → tex-Cn_V-Gg4.js} +1 -1
  66. package/dist/assets/{ts-tags-CxQ4PoC4.js → ts-tags-B6c-FTmA.js} +1 -1
  67. package/dist/assets/{tsx-CxwgcjkC.js → tsx-Cvtt2sJ-.js} +1 -1
  68. package/dist/assets/{twig-CtbyTyMH.js → twig-B7ZbCUmB.js} +1 -1
  69. package/dist/assets/{typescript-d6ru12L8.js → typescript-i3C6um2B.js} +1 -1
  70. package/dist/assets/{vue-RvqAsYDm.js → vue-DyPX0Vum.js} +1 -1
  71. package/dist/assets/{vue-html-BkHDVENe.js → vue-html-D362KpK3.js} +1 -1
  72. package/dist/assets/{vue-vine-DvS3Mlkn.js → vue-vine-D34cBbwO.js} +1 -1
  73. package/dist/assets/{xml-lzTq5A-V.js → xml-DJEk7u4n.js} +1 -1
  74. package/dist/assets/{xsl-BL0KXNWI.js → xsl-CoTtf90f.js} +1 -1
  75. package/dist/assets/{yaml-CF4XlBJS.js → yaml-Cv5epQ4-.js} +1 -1
  76. package/dist/index.html +2 -2
  77. package/package.json +11 -3
  78. package/scripts/agent-exclusions.mjs +10 -0
  79. package/scripts/agent-review.mjs +94 -0
  80. package/scripts/build-diff-data.mjs +235 -50
  81. package/scripts/cli-args.mjs +36 -9
  82. package/scripts/coding-agents.mjs +206 -79
  83. package/scripts/generate-summaries.mjs +864 -425
  84. package/scripts/local-target.mjs +106 -0
  85. package/scripts/present.mjs +205 -72
  86. package/scripts/review-chat-context.mjs +510 -0
  87. package/scripts/review-chat-controller.mjs +568 -0
  88. package/scripts/review-chat-provider.mjs +352 -0
  89. package/scripts/review-chat.mjs +8 -0
  90. package/scripts/serve-built.mjs +206 -24
  91. package/scripts/summary-path.mjs +32 -11
  92. package/dist/assets/index-C2p_1iAk.css +0 -2
  93. package/dist/assets/index-f9ekN94b.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
+ }
@@ -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,
@@ -17,11 +18,17 @@ import { createInterface } from 'node:readline';
17
18
  import { fileURLToPath } from 'node:url';
18
19
  import { helpText, parseCliArgs } from './cli-args.mjs';
19
20
  import {
21
+ agentReadOnlyWarning,
20
22
  assertReasoningSupported,
21
23
  codingAgentAvailability,
22
24
  codingAgentBinary,
23
25
  selectCodingAgent,
24
26
  } from './coding-agents.mjs';
27
+ import {
28
+ isBaseWorktreeTarget,
29
+ reviewAccessMode,
30
+ resolveBaseWorktreeCommit,
31
+ } from './local-target.mjs';
25
32
  import { doctorReport } from './doctor.mjs';
26
33
  import { accessTokenDirectory } from './access-token.mjs';
27
34
  import { cacheStatus, clearCache, formatCacheStatus, pruneCache } from './cache.mjs';
@@ -178,31 +185,83 @@ function emitSupportRecord(code = 1) {
178
185
  }
179
186
  }
180
187
 
188
+ function targetOption(args, name) {
189
+ const index = args.indexOf(name);
190
+ return index === -1 ? undefined : args[index + 1];
191
+ }
192
+
181
193
  const { agentEnabled, browserEnabled, host, port } = cli;
182
194
  const feedArgs = [...cli.feedArgs];
183
195
  const agentArgs = [...cli.agentArgs];
196
+ const base = targetOption(feedArgs, '--base');
197
+ const head = targetOption(feedArgs, '--head');
198
+ const branch = targetOption(feedArgs, '--branch');
199
+ const checkout = feedArgs.includes('--checkout');
200
+ const reviewRepo = targetOption(feedArgs, '--repo');
201
+ const accessMode = reviewAccessMode({
202
+ repo: reviewRepo,
203
+ base,
204
+ branch,
205
+ checkout,
206
+ head,
207
+ noCheckoutAccess: cli.noCheckoutAccess,
208
+ pullRequest: targetOption(feedArgs, '--pr'),
209
+ worktree: feedArgs.includes('--worktree'),
210
+ });
211
+ if (isBaseWorktreeTarget({
212
+ base,
213
+ branch,
214
+ checkout,
215
+ head,
216
+ pullRequest: targetOption(feedArgs, '--pr'),
217
+ worktree: feedArgs.includes('--worktree'),
218
+ })) {
219
+ try {
220
+ resolveBaseWorktreeCommit(targetOption(feedArgs, '--repo'), base);
221
+ } catch (error) {
222
+ console.error(error.message);
223
+ process.exit(2);
224
+ }
225
+ }
184
226
  let selectedAgent;
227
+ let selectedAgentBinary;
185
228
  beginSupportRecord();
186
229
  if (agentEnabled) {
187
230
  const selectionStarted = performance.now();
188
231
  try {
189
232
  selectedAgent = await selectCodingAgent(
190
233
  cli.agent,
191
- (agent) =>
192
- codingAgentAvailability(agent, {
193
- binary: codingAgentBinary(agent, { codexBin: cli.codexBin }),
194
- }),
234
+ {
235
+ available: (agent) =>
236
+ codingAgentAvailability(agent, {
237
+ binary: codingAgentBinary(agent, { codexBin: cli.codexBin }),
238
+ }),
239
+ },
195
240
  );
196
241
  assertReasoningSupported(selectedAgent, cli.reasoning);
197
- const agentBinary = codingAgentBinary(selectedAgent, {
242
+ selectedAgentBinary = codingAgentBinary(selectedAgent, {
198
243
  codexBin: cli.codexBin,
199
244
  });
200
- recordSelectedProvider(selectedAgent, agentBinary);
245
+ recordSelectedProvider(selectedAgent, selectedAgentBinary);
201
246
  supportRecorder?.addStage(
202
247
  'agent',
203
248
  performance.now() - selectionStarted,
204
249
  );
205
250
  agentArgs.push('--agent', selectedAgent);
251
+ if (
252
+ accessMode.mode === 'snapshot-only' &&
253
+ accessMode.reason === 'target-mismatch'
254
+ ) {
255
+ console.log(
256
+ 'Warning: This target does not map to the live checkout. Agent notes will use the supplied snapshot only.',
257
+ );
258
+ agentArgs.push('--access-warning-emitted');
259
+ }
260
+ const readOnlyWarning = agentReadOnlyWarning(selectedAgent, accessMode);
261
+ if (readOnlyWarning) {
262
+ console.log(readOnlyWarning);
263
+ agentArgs.push('--provider-read-only-warning-emitted');
264
+ }
206
265
  } catch (error) {
207
266
  recordSelectedProvider(cli.agent || 'unknown');
208
267
  supportRecorder?.addStage(
@@ -217,6 +276,12 @@ if (agentEnabled) {
217
276
  } else {
218
277
  feedArgs.push('--no-summaries');
219
278
  }
279
+ if (agentEnabled) {
280
+ agentArgs.push('--access-mode', accessMode.mode);
281
+ if (accessMode.mode === 'snapshot-only') {
282
+ agentArgs.push('--access-reason', accessMode.reason);
283
+ }
284
+ }
220
285
  const outputIndex = feedArgs.indexOf('--output');
221
286
  let runtimeDirectory;
222
287
 
@@ -226,16 +291,27 @@ if (outputIndex === -1) {
226
291
  feedArgs.push('--output', liveOutput);
227
292
  agentArgs.push('--output', liveOutput);
228
293
  }
294
+ const outputPath = resolve(
295
+ callerDirectory,
296
+ feedArgs[feedArgs.indexOf('--output') + 1],
297
+ );
298
+ let rawSnapshotPath = outputPath;
299
+ if (agentEnabled) {
300
+ if (!runtimeDirectory) {
301
+ runtimeDirectory = mkdtempSync(join(tmpdir(), 'diffsplain-live-'));
302
+ }
303
+ rawSnapshotPath = resolve(runtimeDirectory, 'raw-diff-data.json');
304
+ const feedOutputIndex = feedArgs.indexOf('--output');
305
+ feedArgs[feedOutputIndex + 1] = rawSnapshotPath;
306
+ feedArgs.push('--exclude-output', outputPath);
307
+ }
308
+ const reviewSnapshotPath = agentEnabled ? rawSnapshotPath : outputPath;
229
309
  process.on('exit', () => {
230
310
  if (runtimeDirectory) {
231
311
  rmSync(runtimeDirectory, { recursive: true, force: true });
232
312
  }
233
313
  });
234
314
  if (!feedArgs.includes('--watch')) feedArgs.push('--watch');
235
- const outputPath = resolve(
236
- callerDirectory,
237
- feedArgs[feedArgs.indexOf('--output') + 1],
238
- );
239
315
  const repoIndex = feedArgs.indexOf('--repo');
240
316
  const remoteIndex = feedArgs.indexOf('--remote');
241
317
  const projectKey = createHash('sha256')
@@ -262,7 +338,9 @@ writeFileSync(accessPath, access, { mode: 0o600 });
262
338
  chmodSync(accessPath, 0o600);
263
339
  if (agentEnabled) {
264
340
  feedArgs.push('--ignore-summary-watch');
265
- agentArgs.push('--snapshot', outputPath);
341
+ if (accessMode.mode === 'snapshot-only') {
342
+ agentArgs.push('--snapshot', rawSnapshotPath);
343
+ }
266
344
  }
267
345
 
268
346
  const snapshotStarted = performance.now();
@@ -334,73 +412,107 @@ function scheduleBrowserOpen(match) {
334
412
  }, 750);
335
413
  }
336
414
 
415
+ function optionalSiteArgument(flag, value) {
416
+ return value ? [flag, value] : [];
417
+ }
418
+
419
+ function chatAccessArguments() {
420
+ if (accessMode.mode !== 'checkout-read-only') return [];
421
+ return ['--chat-access-root', accessMode.root];
422
+ }
423
+
424
+ function chatAgentArguments() {
425
+ if (!agentEnabled) return [];
426
+ return [
427
+ '--chat-agent',
428
+ selectedAgent,
429
+ '--chat-binary',
430
+ selectedAgentBinary,
431
+ '--chat-access-mode',
432
+ accessMode.mode,
433
+ ...chatAccessArguments(),
434
+ ...optionalSiteArgument('--chat-model', cli.model),
435
+ ...optionalSiteArgument('--chat-reasoning', cli.reasoning),
436
+ ];
437
+ }
438
+
439
+ function siteArguments() {
440
+ return [
441
+ resolve(root, 'scripts/serve-built.mjs'),
442
+ '--output',
443
+ outputPath,
444
+ '--port',
445
+ String(port),
446
+ '--host',
447
+ host,
448
+ '--project',
449
+ projectKey,
450
+ '--access',
451
+ access,
452
+ ...optionalSiteArgument('--previous-access', previousAccess),
453
+ '--chat-snapshot',
454
+ outputPath,
455
+ ...chatAgentArguments(),
456
+ ...(!cli.portWasPassed ? ['--increment-port'] : []),
457
+ ];
458
+ }
459
+
460
+ function handleSiteLine(line) {
461
+ if (handleConnectedTab(line)) return;
462
+ console.log(line);
463
+ const match = line.match(/^Diffsplain: (http:\/\/\S+)$/);
464
+ markSiteReady(match);
465
+ scheduleBrowserOpen(match);
466
+ }
467
+
468
+ function listenForSiteLines(child) {
469
+ if (!child.stdout) return;
470
+ createInterface({ input: child.stdout }).on('line', handleSiteLine);
471
+ }
472
+
473
+ function siteFailed(code, signal) {
474
+ return Boolean(code || signal);
475
+ }
476
+
477
+ function recordSiteFailure(code) {
478
+ supportRecorder?.addStage('serve', performance.now() - siteStarted, 'failed');
479
+ emitSupportRecord(code || 1);
480
+ }
481
+
482
+ function handleSiteExit(code, signal) {
483
+ if (closing) return;
484
+ if (siteFailed(code, signal)) recordSiteFailure(code);
485
+ stop(code || (signal ? 1 : 0));
486
+ }
487
+
488
+ function handleSiteError(error) {
489
+ if (closing) return;
490
+ supportRecorder?.addStage('serve', performance.now() - siteStarted, 'failed');
491
+ console.error(`Could not start the local page: ${error.message}`);
492
+ emitSupportRecord();
493
+ stop(1);
494
+ }
495
+
337
496
  function startSite() {
338
497
  if (closing || site) return;
339
498
  siteStarted = performance.now();
340
- const child = spawn(
341
- process.execPath,
342
- [
343
- resolve(root, 'scripts/serve-built.mjs'),
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
- );
359
- site = child;
360
-
361
- if (child.stdout) {
362
- const siteLines = createInterface({ input: child.stdout });
363
- siteLines.on('line', (line) => {
364
- if (handleConnectedTab(line)) return;
365
- console.log(line);
366
- const match = line.match(/^Diffsplain: (http:\/\/\S+)$/);
367
- markSiteReady(match);
368
- scheduleBrowserOpen(match);
369
- });
370
- }
371
-
372
- child.on('exit', (code, signal) => {
373
- if (!closing) {
374
- if (code || signal) {
375
- supportRecorder?.addStage(
376
- 'serve',
377
- performance.now() - siteStarted,
378
- 'failed',
379
- );
380
- emitSupportRecord(code || 1);
381
- }
382
- stop(code || (signal ? 1 : 0));
383
- }
384
- });
385
- child.on('error', (error) => {
386
- if (!closing) {
387
- supportRecorder?.addStage(
388
- 'serve',
389
- performance.now() - siteStarted,
390
- 'failed',
391
- );
392
- console.error(`Could not start the local page: ${error.message}`);
393
- emitSupportRecord();
394
- stop(1);
395
- }
499
+ const child = spawn(process.execPath, siteArguments(), {
500
+ cwd: root,
501
+ stdio: ['ignore', 'pipe', 'inherit'],
396
502
  });
503
+ site = child;
504
+ listenForSiteLines(child);
505
+ child.on('exit', handleSiteExit);
506
+ child.on('error', handleSiteError);
397
507
  }
398
508
 
399
509
  function snapshotState() {
400
510
  try {
401
- const snapshot = JSON.parse(readFileSync(outputPath, 'utf8'));
511
+ const snapshot = JSON.parse(readFileSync(reviewSnapshotPath, 'utf8'));
402
512
  if (snapshot.notes?.reviewFingerprint) {
403
- const fingerprint = snapshot.notes.reviewFingerprint;
513
+ const fingerprint =
514
+ snapshot.notes.agentReviewFingerprint ||
515
+ snapshot.notes.reviewFingerprint;
404
516
  const emptyReview =
405
517
  Array.isArray(snapshot.files) && snapshot.files.length === 0;
406
518
  return {
@@ -409,6 +521,7 @@ function snapshotState() {
409
521
  snapshot.notes.complete &&
410
522
  snapshot.notes.fresh &&
411
523
  snapshot.notes.generatedFor === fingerprint &&
524
+ snapshot.notes.accessMode === accessMode.mode &&
412
525
  (emptyReview ||
413
526
  (snapshot.notes.agent === selectedAgent &&
414
527
  (snapshot.notes.model || null) === (cli.model || null) &&
@@ -599,7 +712,7 @@ function markSnapshotReady() {
599
712
  if (snapshotReady) return;
600
713
  snapshotReady = true;
601
714
  try {
602
- supportRecorder?.addBytes('snapshot', statSync(outputPath).size);
715
+ supportRecorder?.addBytes('snapshot', statSync(reviewSnapshotPath).size);
603
716
  } catch {}
604
717
  supportRecorder?.addStage(
605
718
  'snapshot',
@@ -607,11 +720,29 @@ function markSnapshotReady() {
607
720
  );
608
721
  }
609
722
 
723
+ function seedPresentationSnapshot() {
724
+ const raw = readFileSync(rawSnapshotPath);
725
+ mkdirSync(dirname(outputPath), { recursive: true });
726
+ const pendingOutput = `${outputPath}.${process.pid}.${randomBytes(8).toString('hex')}.tmp`;
727
+ writeFileSync(pendingOutput, raw);
728
+ renameSync(pendingOutput, outputPath);
729
+ }
730
+
610
731
  function isSnapshotLine(line) {
611
732
  return line.startsWith('Wrote ') || line === 'No diff-data changes';
612
733
  }
613
734
 
614
- function startSnapshotDependents() {
735
+ function startSnapshotDependents({ rawSnapshotWritten = false } = {}) {
736
+ if (agentEnabled && rawSnapshotWritten) {
737
+ try {
738
+ seedPresentationSnapshot();
739
+ } catch (error) {
740
+ console.error(`Could not publish the live snapshot: ${error.message}`);
741
+ emitSupportRecord();
742
+ stop(1);
743
+ return;
744
+ }
745
+ }
615
746
  markSnapshotReady();
616
747
  startSite();
617
748
  if (agentEnabled) scheduleAgent();
@@ -622,7 +753,9 @@ function shouldPrintFeedLine(line) {
622
753
  }
623
754
 
624
755
  function handleFeedLine(line) {
625
- if (isSnapshotLine(line)) startSnapshotDependents();
756
+ if (isSnapshotLine(line)) {
757
+ startSnapshotDependents({ rawSnapshotWritten: line.startsWith('Wrote ') });
758
+ }
626
759
  if (shouldPrintFeedLine(line)) console.log(line);
627
760
  }
628
761