diffsplain 0.10.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.
Files changed (87) hide show
  1. package/README.md +1 -0
  2. package/dist/assets/{angular-html-2u-u5H-N.js → angular-html-CjYwnGyV.js} +1 -1
  3. package/dist/assets/{angular-ts-4_HVGlKp.js → angular-ts-DSPf2G4O.js} +1 -1
  4. package/dist/assets/{apl-icaNWZX3.js → apl-B9ytghJx.js} +1 -1
  5. package/dist/assets/{astro-HvIOE4vz.js → astro-BsGV0yBF.js} +1 -1
  6. package/dist/assets/{blade-BpKJV8ev.js → blade-Cc2eCYlt.js} +1 -1
  7. package/dist/assets/{c-BNvtxyRQ.js → c-CWKyCnUx.js} +1 -1
  8. package/dist/assets/{cobol-8js4hfxa.js → cobol-DkamOI1E.js} +1 -1
  9. package/dist/assets/{coffee-DXg_piQ9.js → coffee-DyaEXePe.js} +1 -1
  10. package/dist/assets/{cpp-BZDFAjyh.js → cpp-16-d2Eyo.js} +1 -1
  11. package/dist/assets/{crystal-aCF3ira4.js → crystal-CavPPRhQ.js} +1 -1
  12. package/dist/assets/{css-DfRtUkhE.js → css-BME_wHJq.js} +1 -1
  13. package/dist/assets/{edge-vS6_AmQv.js → edge-DeVi2hvK.js} +1 -1
  14. package/dist/assets/{elixir-Ce0nUHSq.js → elixir-CCWgH_vc.js} +1 -1
  15. package/dist/assets/{elm-DmaeyizZ.js → elm-CZ570J7x.js} +1 -1
  16. package/dist/assets/{erb-DD6OUyvt.js → erb-CDPT5bea.js} +1 -1
  17. package/dist/assets/{git-rebase-HbJMn7iS.js → git-rebase-Dq5Z37DJ.js} +1 -1
  18. package/dist/assets/{glimmer-js-BRuqbNaV.js → glimmer-js-BoPVZK8p.js} +1 -1
  19. package/dist/assets/{glimmer-ts-ZBpkdEEA.js → glimmer-ts-DazEWfg1.js} +1 -1
  20. package/dist/assets/{glsl-CrQ4GScf.js → glsl-DCUjYCZY.js} +1 -1
  21. package/dist/assets/{graphql-Tn6PBdQY.js → graphql-DkCeSxy7.js} +1 -1
  22. package/dist/assets/{hack-CoApvrbo.js → hack-tiY3l-W-.js} +1 -1
  23. package/dist/assets/{haml-COKq8Xjp.js → haml-DyQZrJg_.js} +1 -1
  24. package/dist/assets/{handlebars-CAna2DTA.js → handlebars-Bvu5Rr0t.js} +1 -1
  25. package/dist/assets/{html-Ch4xJ7YF.js → html-C60VFgKW.js} +1 -1
  26. package/dist/assets/{html-derivative-0ITK-MAI.js → html-derivative-DB69gpnL.js} +1 -1
  27. package/dist/assets/{http-DiMWQ8Qr.js → http-DmS8hddd.js} +1 -1
  28. package/dist/assets/{hurl-OcXymC0J.js → hurl-Cdp6lmUx.js} +1 -1
  29. package/dist/assets/{index-BmcCug2M.js → index-CUiLG0r9.js} +4 -4
  30. package/dist/assets/{index-CADUNiTy.css → index-YnLIxcTm.css} +1 -1
  31. package/dist/assets/{java-CFb3qN4r.js → java-CkOnwoVQ.js} +1 -1
  32. package/dist/assets/{javascript-CRmk2JrF.js → javascript-D2tF_Ylj.js} +1 -1
  33. package/dist/assets/{jinja-Da6lDTEy.js → jinja-CdWpLcff.js} +1 -1
  34. package/dist/assets/{jison-Bnivzmtu.js → jison-BII58uuR.js} +1 -1
  35. package/dist/assets/{json-m8vJP_KK.js → json-BKlPRLrU.js} +1 -1
  36. package/dist/assets/{jsx-C3HX_Sbu.js → jsx-Ba5UouxN.js} +1 -1
  37. package/dist/assets/{julia-Ckg-wSHA.js → julia-CwdQF8Ff.js} +1 -1
  38. package/dist/assets/{just-DCjIAyvO.js → just-Be3sr1-h.js} +1 -1
  39. package/dist/assets/{latex-CrYSm4pl.js → latex-CDF6t93N.js} +1 -1
  40. package/dist/assets/{liquid-MQUFSPYU.js → liquid-BE3LVHkw.js} +1 -1
  41. package/dist/assets/{lua-DcuzJJ77.js → lua-D_W8Ux9S.js} +1 -1
  42. package/dist/assets/{marko-4JNmQaB4.js → marko-BeUfMD-U.js} +1 -1
  43. package/dist/assets/{mdc-C_j8HvRL.js → mdc-C8tRJlVU.js} +1 -1
  44. package/dist/assets/{nginx-ta3Gn1HE.js → nginx-CLPimMM6.js} +1 -1
  45. package/dist/assets/{nim-BwWLst9M.js → nim-ikQhXKQf.js} +1 -1
  46. package/dist/assets/{perl-DfW7Uq5O.js → perl-C27cToFa.js} +1 -1
  47. package/dist/assets/{php-DGf5G3Cf.js → php-Btdg-RP3.js} +1 -1
  48. package/dist/assets/{pug-CWI-NaYz.js → pug-Bk73ERXL.js} +1 -1
  49. package/dist/assets/{qml-Csblgh7U.js → qml-DhY7FDIl.js} +1 -1
  50. package/dist/assets/{r-QGooaGDI.js → r-CEwua7nj.js} +1 -1
  51. package/dist/assets/{razor-Dp9ihLYf.js → razor-sPO7Sh9d.js} +1 -1
  52. package/dist/assets/{regexp-DwUnLcVr.js → regexp-D9DLFGXE.js} +1 -1
  53. package/dist/assets/{rst-BY8GIwDJ.js → rst-YhauCnId.js} +1 -1
  54. package/dist/assets/{ruby-VgzI9htT.js → ruby-DpMkRaoz.js} +1 -1
  55. package/dist/assets/{sas-DYLsm-0j.js → sas-0KdmKGRO.js} +1 -1
  56. package/dist/assets/{scss-tIFkeUQX.js → scss-CohvHWvq.js} +1 -1
  57. package/dist/assets/{shellscript-CHjuQ4Pl.js → shellscript-C93Q8fLt.js} +1 -1
  58. package/dist/assets/{shellsession-Bv0S0hj9.js → shellsession-YCh2AQXu.js} +1 -1
  59. package/dist/assets/{soy-D6U1tZod.js → soy-CWVYdXUH.js} +1 -1
  60. package/dist/assets/{sql-CGz8kjKv.js → sql-CJNtax5I.js} +1 -1
  61. package/dist/assets/{stata-C9ytERUq.js → stata-C7v41XAM.js} +1 -1
  62. package/dist/assets/{surrealql-DRfRnaYu.js → surrealql-CRUSOxDf.js} +1 -1
  63. package/dist/assets/{svelte-wYnk_1sI.js → svelte-Z0harRr1.js} +1 -1
  64. package/dist/assets/{templ-DWrGVE0u.js → templ-BplDouJX.js} +1 -1
  65. package/dist/assets/{tex-CYTzoUJh.js → tex-MsB6StN5.js} +1 -1
  66. package/dist/assets/{ts-tags-1TIXEGea.js → ts-tags-BWx8aq5t.js} +1 -1
  67. package/dist/assets/{tsx-a3BoAoOh.js → tsx-DBT1gmNl.js} +1 -1
  68. package/dist/assets/{twig-e3dvNv6o.js → twig-jz67rR3C.js} +1 -1
  69. package/dist/assets/{typescript-BBhnpnTf.js → typescript-CzwGN36B.js} +1 -1
  70. package/dist/assets/{vue-html-quBNv38K.js → vue-html-Dhrmu7sO.js} +1 -1
  71. package/dist/assets/{vue-vine-CDuR69FE.js → vue-vine-BfQGXZs6.js} +1 -1
  72. package/dist/assets/{vue-DzzVbu9q.js → vue-xsrSiVMC.js} +1 -1
  73. package/dist/assets/{xml-DMifOLSe.js → xml-W7cP4qeN.js} +1 -1
  74. package/dist/assets/{xsl-D8Ef-5HK.js → xsl-BlXf8qm_.js} +1 -1
  75. package/dist/assets/{yaml-5c38gpmA.js → yaml-BtNwxUT9.js} +1 -1
  76. package/dist/index.html +2 -2
  77. package/package.json +3 -2
  78. package/scripts/agent-usage.mjs +141 -0
  79. package/scripts/build-diff-data.mjs +32 -12
  80. package/scripts/cli-args.mjs +7 -0
  81. package/scripts/coding-agents.mjs +208 -7
  82. package/scripts/generate-summaries.mjs +61 -9
  83. package/scripts/present.mjs +22 -7
  84. package/scripts/review-chat-context.mjs +6 -4
  85. package/scripts/review-chat-controller.mjs +5 -3
  86. package/scripts/review-chat-provider.mjs +26 -4
  87. package/scripts/serve-built.mjs +100 -4
@@ -9,6 +9,7 @@ import { join } from 'node:path';
9
9
  import {
10
10
  agentCommand,
11
11
  parseAgentResponse,
12
+ parseAgentUsage,
12
13
  } from './coding-agents.mjs';
13
14
 
14
15
  const maximumProviderOutputBytes = 1024 * 1024;
@@ -99,8 +100,10 @@ function providerOptions(options) {
99
100
  binary: providerBinary(options, agent),
100
101
  model: options.model,
101
102
  reasoning: options.reasoning,
103
+ fast: options.fast === true,
102
104
  accessMode: options.accessMode,
103
105
  env: providerEnvironment(options),
106
+ onUsage: typeof options.onUsage === 'function' ? options.onUsage : noChange,
104
107
  };
105
108
  }
106
109
 
@@ -124,6 +127,7 @@ function providerInvocation(options, request, paths) {
124
127
  binary: options.binary,
125
128
  model: options.model,
126
129
  reasoning: options.reasoning,
130
+ fast: options.fast,
127
131
  prompt: request.prompt,
128
132
  schema: request.responseSchema,
129
133
  schemaPath: paths.schemaPath,
@@ -145,12 +149,15 @@ function preparedProviderRequest(options, request) {
145
149
  }
146
150
 
147
151
  class ChildExecution {
148
- constructor({ invocation, input, agent, signal, temporary }) {
152
+ constructor({ invocation, input, agent, signal, temporary, onUsage, usageContext }) {
149
153
  this.invocation = invocation;
150
154
  this.input = input;
151
155
  this.agent = agent;
152
156
  this.signal = signal;
153
157
  this.temporary = temporary;
158
+ this.onUsage = onUsage;
159
+ this.usageContext = usageContext;
160
+ this.usageReported = false;
154
161
  this.child = undefined;
155
162
  this.settled = false;
156
163
  this.closed = false;
@@ -277,11 +284,23 @@ class ChildExecution {
277
284
  finishClose() {
278
285
  if (this.closed) return;
279
286
  this.closed = true;
287
+ this.reportUsage();
280
288
  this.clearForceKill();
281
289
  removeTemporary(this.temporary);
282
290
  this.resolveFinished();
283
291
  }
284
292
 
293
+ reportUsage() {
294
+ if (this.usageReported) return;
295
+ this.usageReported = true;
296
+ try {
297
+ this.onUsage({
298
+ ...this.usageContext,
299
+ usage: parseAgentUsage(this.agent, childOutput(this.stdout)),
300
+ });
301
+ } catch {}
302
+ }
303
+
285
304
  resolve(value) {
286
305
  this.settle(this.resolvePromise, value);
287
306
  }
@@ -300,6 +319,7 @@ class ChildExecution {
300
319
 
301
320
  closeChild(status, signal) {
302
321
  this.clearForceKill();
322
+ this.reportUsage();
303
323
  if (!this.settled) {
304
324
  const error = closeError(this, status, signal);
305
325
  if (error) this.reject(error);
@@ -325,7 +345,7 @@ function trackExecution(executions, execution) {
325
345
  return execution;
326
346
  }
327
347
 
328
- function runRequest(options, executions, request) {
348
+ function runRequest(options, executions, request, usageContext = {}) {
329
349
  const prepared = preparedProviderRequest(options, request);
330
350
  const controller = new AbortController();
331
351
  const execution = providerChild({
@@ -334,6 +354,8 @@ function runRequest(options, executions, request) {
334
354
  agent: options.agent,
335
355
  signal: controller.signal,
336
356
  temporary: prepared.temporary,
357
+ onUsage: options.onUsage,
358
+ usageContext,
337
359
  });
338
360
  const tracked = trackExecution(executions, execution);
339
361
  return {
@@ -353,8 +375,8 @@ export function createCodingAgentChatProvider(options = {}) {
353
375
  const configured = providerOptions(options);
354
376
  const executions = new Set();
355
377
  return {
356
- run(request) {
357
- return runRequest(configured, executions, request);
378
+ run(request, context) {
379
+ return runRequest(configured, executions, request, context);
358
380
  },
359
381
  close() {
360
382
  closeExecutions(executions);
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { randomBytes, timingSafeEqual } from 'node:crypto';
4
- import { watchFile, unwatchFile } from 'node:fs';
3
+ import { createHash, randomBytes, timingSafeEqual } from 'node:crypto';
4
+ import { readFileSync, watchFile, unwatchFile } from 'node:fs';
5
5
  import { readFile, stat } from 'node:fs/promises';
6
6
  import { createServer } from 'node:http';
7
7
  import { dirname, extname, resolve, sep } from 'node:path';
@@ -12,6 +12,13 @@ import {
12
12
  createReviewChat,
13
13
  } from './review-chat.mjs';
14
14
  import { publicError } from './review-chat-context.mjs';
15
+ import {
16
+ emptyUsageAccumulator,
17
+ formatReviewUsage,
18
+ recordUsage,
19
+ reviewUsage,
20
+ usageSummary,
21
+ } from './agent-usage.mjs';
15
22
 
16
23
  const root = resolve(dirname(fileURLToPath(import.meta.url)), '..');
17
24
  const clientRoot = resolve(root, 'dist');
@@ -38,6 +45,7 @@ const chatAgent = option('--chat-agent');
38
45
  const chatBinary = option('--chat-binary', chatAgent);
39
46
  const chatModel = option('--chat-model');
40
47
  const chatReasoning = option('--chat-reasoning');
48
+ const chatFast = rawArgs.includes('--chat-fast');
41
49
  const chatAccessMode = option('--chat-access-mode', 'snapshot-only');
42
50
  const chatAccessRoot = option('--chat-access-root');
43
51
  if (!/^\d+$/.test(portValue) || Number(portValue) > 65_535) {
@@ -266,7 +274,7 @@ function matchingAccess(value, expectedValue) {
266
274
 
267
275
  async function fetchAsset(url) {
268
276
  if (url.pathname === '/diff-data.json') {
269
- return fileResponse(output, { live: true });
277
+ return liveSnapshotResponse();
270
278
  }
271
279
  const pathname = decodeURIComponent(url.pathname);
272
280
  const file = resolve(
@@ -279,6 +287,44 @@ async function fetchAsset(url) {
279
287
  return fileResponse(file);
280
288
  }
281
289
 
290
+ async function parsedLiveSnapshot() {
291
+ try {
292
+ return JSON.parse(await readFile(output, 'utf8'));
293
+ } catch {
294
+ return undefined;
295
+ }
296
+ }
297
+
298
+ function liveSnapshotVersion(snapshot, usage) {
299
+ return createHash('sha256')
300
+ .update(JSON.stringify({ version: snapshot.version, usage }))
301
+ .digest('hex')
302
+ .slice(0, 12);
303
+ }
304
+
305
+ function snapshotNoteSummary(snapshot) {
306
+ return snapshot?.usage?.agentNotes ||
307
+ usageSummary(emptyUsageAccumulator());
308
+ }
309
+
310
+ function snapshotResponse(snapshot, usage) {
311
+ return jsonResponse({
312
+ ...snapshot,
313
+ version: liveSnapshotVersion(snapshot, usage),
314
+ usage,
315
+ });
316
+ }
317
+
318
+ async function liveSnapshotResponse() {
319
+ const snapshot = await parsedLiveSnapshot();
320
+ if (!snapshot) return fileResponse(output, { live: true });
321
+ const fingerprint = snapshot?.notes?.reviewFingerprint;
322
+ if (!fingerprint) return fileResponse(output, { live: true });
323
+ const chatSummary = currentChatUsage(fingerprint);
324
+ const usage = reviewUsage(snapshotNoteSummary(snapshot), chatSummary);
325
+ return snapshotResponse(snapshot, usage);
326
+ }
327
+
282
328
  function jsonResponse(value, status = 200) {
283
329
  return webResponse(JSON.stringify(value), {
284
330
  status,
@@ -399,6 +445,53 @@ function logObservedError(area, error) {
399
445
  console.log(`Diffsplain ${area.replaceAll('-', ' ')} error: ${publicError(error)}`);
400
446
  }
401
447
 
448
+ let chatUsageFingerprint;
449
+ let chatUsage = emptyUsageAccumulator();
450
+ let reviewChat;
451
+
452
+ function snapshotUsageState() {
453
+ try {
454
+ const snapshot = JSON.parse(readFileSync(output, 'utf8'));
455
+ return {
456
+ agentNotes: snapshot.usage?.agentNotes,
457
+ fingerprint: snapshot.notes?.reviewFingerprint,
458
+ };
459
+ } catch {
460
+ return {};
461
+ }
462
+ }
463
+
464
+ function currentChatUsage(fingerprint) {
465
+ return fingerprint === chatUsageFingerprint
466
+ ? usageSummary(chatUsage)
467
+ : usageSummary(emptyUsageAccumulator());
468
+ }
469
+
470
+ function resetChatUsage(fingerprint) {
471
+ if (fingerprint === chatUsageFingerprint) return;
472
+ chatUsageFingerprint = fingerprint;
473
+ chatUsage = emptyUsageAccumulator();
474
+ }
475
+
476
+ function recordChatUsage({ reviewFingerprint, usage }) {
477
+ const snapshot = snapshotUsageState();
478
+ if (
479
+ !reviewFingerprint ||
480
+ snapshot.fingerprint !== reviewFingerprint ||
481
+ reviewChat?.getState().fingerprint !== reviewFingerprint
482
+ ) {
483
+ return;
484
+ }
485
+ resetChatUsage(reviewFingerprint);
486
+ chatUsage = recordUsage(chatUsage, usage);
487
+ const totals = reviewUsage(
488
+ snapshot.agentNotes || usageSummary(emptyUsageAccumulator()),
489
+ usageSummary(chatUsage),
490
+ );
491
+ console.log(formatReviewUsage(totals));
492
+ broadcastEvent('update');
493
+ }
494
+
402
495
  const chatProviderAccess = chatAccessMode === 'checkout-read-only'
403
496
  ? { mode: chatAccessMode, root: resolve(chatAccessRoot) }
404
497
  : { mode: chatAccessMode, reason: 'target-mismatch' };
@@ -408,10 +501,12 @@ const chatProvider = chatAgent
408
501
  binary: chatBinary,
409
502
  model: chatModel,
410
503
  reasoning: chatReasoning,
504
+ fast: chatFast,
411
505
  accessMode: chatProviderAccess,
506
+ onUsage: recordChatUsage,
412
507
  })
413
508
  : undefined;
414
- const reviewChat = createReviewChat({
509
+ reviewChat = createReviewChat({
415
510
  snapshotPath: chatSnapshot,
416
511
  provider: chatProvider,
417
512
  accessMode: chatProviderAccess,
@@ -611,6 +706,7 @@ watchFile(output, { interval: 100 }, (current, previous) => {
611
706
  return;
612
707
  }
613
708
  reviewChat.refresh();
709
+ resetChatUsage(reviewChat.getState().fingerprint);
614
710
  broadcastEvent('update');
615
711
  });
616
712