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
package/scripts/serve-built.mjs
CHANGED
|
@@ -6,6 +6,12 @@ import { readFile, stat } from 'node:fs/promises';
|
|
|
6
6
|
import { createServer } from 'node:http';
|
|
7
7
|
import { dirname, extname, resolve, sep } from 'node:path';
|
|
8
8
|
import { fileURLToPath } from 'node:url';
|
|
9
|
+
import {
|
|
10
|
+
ReviewChatError,
|
|
11
|
+
createCodingAgentChatProvider,
|
|
12
|
+
createReviewChat,
|
|
13
|
+
} from './review-chat.mjs';
|
|
14
|
+
import { publicError } from './review-chat-context.mjs';
|
|
9
15
|
|
|
10
16
|
const root = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
11
17
|
const clientRoot = resolve(root, 'dist');
|
|
@@ -27,6 +33,13 @@ const portValue = option('--port', '2299');
|
|
|
27
33
|
const host = option('--host', 'localhost').replace(/^\[|\]$/g, '');
|
|
28
34
|
const access = option('--access', randomBytes(32).toString('base64url'));
|
|
29
35
|
const previousAccess = option('--previous-access', '');
|
|
36
|
+
const chatSnapshot = resolve(option('--chat-snapshot', output));
|
|
37
|
+
const chatAgent = option('--chat-agent');
|
|
38
|
+
const chatBinary = option('--chat-binary', chatAgent);
|
|
39
|
+
const chatModel = option('--chat-model');
|
|
40
|
+
const chatReasoning = option('--chat-reasoning');
|
|
41
|
+
const chatAccessMode = option('--chat-access-mode', 'snapshot-only');
|
|
42
|
+
const chatAccessRoot = option('--chat-access-root');
|
|
30
43
|
if (!/^\d+$/.test(portValue) || Number(portValue) > 65_535) {
|
|
31
44
|
throw new Error('--port must be a number from 0 to 65535');
|
|
32
45
|
}
|
|
@@ -36,6 +49,12 @@ if (!/^[A-Za-z0-9_-]{32,}$/.test(access)) {
|
|
|
36
49
|
if (previousAccess && !/^[A-Za-z0-9_-]{32,}$/.test(previousAccess)) {
|
|
37
50
|
throw new Error('--previous-access must be an unguessable URL-safe value');
|
|
38
51
|
}
|
|
52
|
+
if (!['checkout-read-only', 'snapshot-only'].includes(chatAccessMode)) {
|
|
53
|
+
throw new Error('--chat-access-mode must be checkout-read-only or snapshot-only');
|
|
54
|
+
}
|
|
55
|
+
if (chatAccessMode === 'checkout-read-only' && !chatAccessRoot) {
|
|
56
|
+
throw new Error('--chat-access-root is required for checkout chat access');
|
|
57
|
+
}
|
|
39
58
|
const incrementPort =
|
|
40
59
|
rawArgs.includes('--increment-port') || !rawArgs.includes('--port');
|
|
41
60
|
|
|
@@ -270,6 +289,60 @@ function jsonResponse(value, status = 200) {
|
|
|
270
289
|
});
|
|
271
290
|
}
|
|
272
291
|
|
|
292
|
+
const chatBodyLimitBytes = 64 * 1024;
|
|
293
|
+
|
|
294
|
+
function chatJsonContentType(value) {
|
|
295
|
+
return typeof value === 'string' &&
|
|
296
|
+
/^application\/json(?:\s*;\s*charset=utf-8)?$/i.test(value);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function chatRequestBody(request) {
|
|
300
|
+
if (!chatJsonContentType(request.headers['content-type'])) {
|
|
301
|
+
throw new ReviewChatError('Chat commands need application/json.', 415);
|
|
302
|
+
}
|
|
303
|
+
const contentLength = Number(request.headers['content-length']);
|
|
304
|
+
if (Number.isFinite(contentLength) && contentLength > chatBodyLimitBytes) {
|
|
305
|
+
throw new ReviewChatError('Chat command body is too large.', 413);
|
|
306
|
+
}
|
|
307
|
+
return new Promise((resolvePromise, reject) => {
|
|
308
|
+
const chunks = [];
|
|
309
|
+
let bytes = 0;
|
|
310
|
+
let settled = false;
|
|
311
|
+
const fail = (error) => {
|
|
312
|
+
if (settled) return;
|
|
313
|
+
settled = true;
|
|
314
|
+
request.resume();
|
|
315
|
+
reject(error);
|
|
316
|
+
};
|
|
317
|
+
request.on('data', (chunk) => {
|
|
318
|
+
bytes += chunk.length;
|
|
319
|
+
if (bytes > chatBodyLimitBytes) {
|
|
320
|
+
fail(new ReviewChatError('Chat command body is too large.', 413));
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
chunks.push(chunk);
|
|
324
|
+
});
|
|
325
|
+
request.once('error', fail);
|
|
326
|
+
request.once('end', () => {
|
|
327
|
+
if (settled) return;
|
|
328
|
+
settled = true;
|
|
329
|
+
try {
|
|
330
|
+
const value = JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
331
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
332
|
+
throw new ReviewChatError('Chat commands must be JSON objects.');
|
|
333
|
+
}
|
|
334
|
+
resolvePromise(value);
|
|
335
|
+
} catch (error) {
|
|
336
|
+
reject(
|
|
337
|
+
error instanceof ReviewChatError
|
|
338
|
+
? error
|
|
339
|
+
: new ReviewChatError('Chat command JSON is not valid.'),
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
|
|
273
346
|
async function send(nodeResponse, webResponse) {
|
|
274
347
|
nodeResponse.writeHead(
|
|
275
348
|
webResponse.status,
|
|
@@ -287,6 +360,65 @@ let selectedPort = Number(portValue);
|
|
|
287
360
|
let readyState;
|
|
288
361
|
let closing = false;
|
|
289
362
|
|
|
363
|
+
function broadcastEvent(name, data = {}) {
|
|
364
|
+
const payload = JSON.stringify(data);
|
|
365
|
+
for (const client of eventClients) {
|
|
366
|
+
if (!client.destroyed) client.write(`event: ${name}\ndata: ${payload}\n\n`);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function lifecycleKind(kind) {
|
|
371
|
+
return kind === 'compact' ? 'history compaction' : 'answer';
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function lifecycleStatus(type) {
|
|
375
|
+
return {
|
|
376
|
+
start: 'started',
|
|
377
|
+
progress: 'still running',
|
|
378
|
+
complete: 'completed',
|
|
379
|
+
failure: 'failed',
|
|
380
|
+
timeout: 'timed out',
|
|
381
|
+
cancel: 'cancelled',
|
|
382
|
+
}[type] || type;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function elapsedText(milliseconds) {
|
|
386
|
+
if (milliseconds < 1_000) return `${milliseconds}ms`;
|
|
387
|
+
return `${Math.round(milliseconds / 1_000)}s`;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function logChatLifecycle(event) {
|
|
391
|
+
const elapsed = event.type === 'start' ? '' : ` (${elapsedText(event.elapsedMs)})`;
|
|
392
|
+
const error = event.error ? `: ${event.error}` : '';
|
|
393
|
+
console.log(
|
|
394
|
+
`Diffsplain chat #${event.runId}: ${lifecycleKind(event.kind)} ${lifecycleStatus(event.type)}${elapsed}${error}`,
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function logObservedError(area, error) {
|
|
399
|
+
console.log(`Diffsplain ${area.replaceAll('-', ' ')} error: ${publicError(error)}`);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const chatProviderAccess = chatAccessMode === 'checkout-read-only'
|
|
403
|
+
? { mode: chatAccessMode, root: resolve(chatAccessRoot) }
|
|
404
|
+
: { mode: chatAccessMode, reason: 'target-mismatch' };
|
|
405
|
+
const chatProvider = chatAgent
|
|
406
|
+
? createCodingAgentChatProvider({
|
|
407
|
+
agent: chatAgent,
|
|
408
|
+
binary: chatBinary,
|
|
409
|
+
model: chatModel,
|
|
410
|
+
reasoning: chatReasoning,
|
|
411
|
+
accessMode: chatProviderAccess,
|
|
412
|
+
})
|
|
413
|
+
: undefined;
|
|
414
|
+
const reviewChat = createReviewChat({
|
|
415
|
+
snapshotPath: chatSnapshot,
|
|
416
|
+
provider: chatProvider,
|
|
417
|
+
accessMode: chatProviderAccess,
|
|
418
|
+
onChange: () => broadcastEvent('chat'),
|
|
419
|
+
onLifecycle: logChatLifecycle,
|
|
420
|
+
});
|
|
421
|
+
|
|
290
422
|
function requestAuthorityError(request) {
|
|
291
423
|
const localAddress = request.socket.localAddress || '';
|
|
292
424
|
if (!validHost(request.headers.host, localAddress)) {
|
|
@@ -298,14 +430,22 @@ function requestAuthorityError(request) {
|
|
|
298
430
|
return undefined;
|
|
299
431
|
}
|
|
300
432
|
|
|
433
|
+
function allowedMethods(url) {
|
|
434
|
+
return url.pathname === '/api/chat' ? ['GET', 'POST'] : ['GET', 'HEAD'];
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function methodError(request, url) {
|
|
438
|
+
if (allowedMethods(url).includes(request.method)) return undefined;
|
|
439
|
+
return webResponse('Method not allowed', { status: 405 });
|
|
440
|
+
}
|
|
441
|
+
|
|
301
442
|
function requestContext(request) {
|
|
302
443
|
const url = parseRequestUrl(request);
|
|
303
444
|
if (!url) return { error: clientError() };
|
|
304
445
|
const authorityError = requestAuthorityError(request);
|
|
305
446
|
if (authorityError) return { error: authorityError };
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
}
|
|
447
|
+
const error = methodError(request, url);
|
|
448
|
+
if (error) return { error };
|
|
309
449
|
return { url };
|
|
310
450
|
}
|
|
311
451
|
|
|
@@ -349,20 +489,99 @@ async function routeResponse(url) {
|
|
|
349
489
|
return fetchAsset(url);
|
|
350
490
|
}
|
|
351
491
|
|
|
492
|
+
function chatAccessError(url) {
|
|
493
|
+
if (validAccess(url.searchParams.get('access'))) return undefined;
|
|
494
|
+
return webResponse('Forbidden', { status: 403 });
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function chatCommandError(error) {
|
|
498
|
+
if (error instanceof ReviewChatError) {
|
|
499
|
+
return { status: error.status, message: error.message };
|
|
500
|
+
}
|
|
501
|
+
return { status: 400, message: 'Chat command failed.' };
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
async function sendChatCommand(request, response) {
|
|
505
|
+
try {
|
|
506
|
+
const result = reviewChat.command(await chatRequestBody(request));
|
|
507
|
+
const status = result.accepted ? 202 : 200;
|
|
508
|
+
await send(response, jsonResponse(result.state, status));
|
|
509
|
+
} catch (error) {
|
|
510
|
+
logObservedError('chat-command', error);
|
|
511
|
+
const failure = chatCommandError(error);
|
|
512
|
+
await send(response, jsonResponse({ error: failure.message }, failure.status));
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
async function handleChatRequest(request, response, url) {
|
|
517
|
+
const accessError = chatAccessError(url);
|
|
518
|
+
if (accessError) {
|
|
519
|
+
await send(response, accessError);
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
if (request.method === 'GET') {
|
|
523
|
+
await send(response, jsonResponse(reviewChat.getState()));
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
await sendChatCommand(request, response);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
async function handleEventsRequest(_request, response, url) {
|
|
530
|
+
await sendEvents(response, url);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
function handlerFor(url) {
|
|
534
|
+
return {
|
|
535
|
+
'/events': handleEventsRequest,
|
|
536
|
+
'/api/chat': handleChatRequest,
|
|
537
|
+
}[url.pathname];
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function throwForcedHandlerFailure(url) {
|
|
541
|
+
if (forcedHandlerFailure(url)) throw new Error('forced request handler failure');
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
async function handleRoutedRequest(request, response, url) {
|
|
545
|
+
const handler = handlerFor(url);
|
|
546
|
+
if (handler) {
|
|
547
|
+
await handler(request, response, url);
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
await send(response, await routeResponse(url));
|
|
551
|
+
}
|
|
552
|
+
|
|
352
553
|
async function handleRequest(request, response) {
|
|
353
554
|
const context = requestContext(request);
|
|
354
555
|
if (context.error) {
|
|
355
556
|
await send(response, context.error);
|
|
356
557
|
return;
|
|
357
558
|
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
559
|
+
throwForcedHandlerFailure(context.url);
|
|
560
|
+
await handleRoutedRequest(request, response, context.url);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
function stopWatchers() {
|
|
564
|
+
unwatchFile(output);
|
|
565
|
+
if (chatSnapshot !== output) unwatchFile(chatSnapshot);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
function closeEventClients() {
|
|
569
|
+
for (const client of eventClients) client.end();
|
|
570
|
+
eventClients.clear();
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
function closeServer() {
|
|
574
|
+
if (server.listening) server.close();
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
function close(exitCode = 0) {
|
|
578
|
+
if (closing) return;
|
|
579
|
+
closing = true;
|
|
580
|
+
process.exitCode = exitCode;
|
|
581
|
+
stopWatchers();
|
|
582
|
+
reviewChat.close();
|
|
583
|
+
closeEventClients();
|
|
584
|
+
closeServer();
|
|
366
585
|
}
|
|
367
586
|
|
|
368
587
|
async function containHandlerFailure(response) {
|
|
@@ -381,20 +600,29 @@ async function containHandlerFailure(response) {
|
|
|
381
600
|
}
|
|
382
601
|
|
|
383
602
|
const server = createServer((request, response) => {
|
|
384
|
-
void handleRequest(request, response).catch(() =>
|
|
385
|
-
|
|
386
|
-
|
|
603
|
+
void handleRequest(request, response).catch((error) => {
|
|
604
|
+
logObservedError('request-handler', error);
|
|
605
|
+
return containHandlerFailure(response);
|
|
606
|
+
});
|
|
387
607
|
});
|
|
388
608
|
|
|
389
609
|
watchFile(output, { interval: 100 }, (current, previous) => {
|
|
390
610
|
if (current.mtimeMs === previous.mtimeMs && current.size === previous.size) {
|
|
391
611
|
return;
|
|
392
612
|
}
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
}
|
|
613
|
+
reviewChat.refresh();
|
|
614
|
+
broadcastEvent('update');
|
|
396
615
|
});
|
|
397
616
|
|
|
617
|
+
if (chatSnapshot !== output) {
|
|
618
|
+
watchFile(chatSnapshot, { interval: 100 }, (current, previous) => {
|
|
619
|
+
if (current.mtimeMs === previous.mtimeMs && current.size === previous.size) {
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
reviewChat.refresh();
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
|
|
398
626
|
function urlFor(address, port) {
|
|
399
627
|
const formattedAddress = address.includes(':') ? `[${address}]` : address;
|
|
400
628
|
return `http://${formattedAddress}:${port}`;
|
|
@@ -454,15 +682,5 @@ server.on('error', (error) => {
|
|
|
454
682
|
|
|
455
683
|
listen();
|
|
456
684
|
|
|
457
|
-
function close(exitCode = 0) {
|
|
458
|
-
if (closing) return;
|
|
459
|
-
closing = true;
|
|
460
|
-
process.exitCode = exitCode;
|
|
461
|
-
unwatchFile(output);
|
|
462
|
-
for (const client of eventClients) client.end();
|
|
463
|
-
eventClients.clear();
|
|
464
|
-
if (server.listening) server.close();
|
|
465
|
-
}
|
|
466
|
-
|
|
467
685
|
process.on('SIGINT', () => close());
|
|
468
686
|
process.on('SIGTERM', () => close());
|
package/scripts/summary-path.mjs
CHANGED
|
@@ -20,6 +20,37 @@ export function defaultCacheRoot({
|
|
|
20
20
|
return resolve(platformRoot, 'diffsplain');
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
function pullRequestTarget(pr, remote) {
|
|
24
|
+
const pullRequest =
|
|
25
|
+
pr?.match(/\/pull\/(\d+)(?:\/|$)/)?.[1] || pr || undefined;
|
|
26
|
+
return { kind: 'pr', pullRequest, remote };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function branchTarget(branch, base, remote) {
|
|
30
|
+
return { kind: 'branch', branch, base: base || 'default', remote };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function checkoutTarget(base, remote) {
|
|
34
|
+
return { kind: 'checkout', base: base || 'default', remote };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function localSummaryTarget(base, head) {
|
|
38
|
+
if (!base) return { kind: 'worktree' };
|
|
39
|
+
if (!head) return { kind: 'base-worktree', base };
|
|
40
|
+
return { kind: 'range', base, head };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function summaryTarget({ pr, branch, checkout, base, head, remote }) {
|
|
44
|
+
const candidates = [
|
|
45
|
+
[Boolean(pr), pullRequestTarget(pr, remote)],
|
|
46
|
+
[Boolean(branch), branchTarget(branch, base, remote)],
|
|
47
|
+
[Boolean(checkout), checkoutTarget(base, remote)],
|
|
48
|
+
];
|
|
49
|
+
const selected = candidates.find(([matches]) => matches);
|
|
50
|
+
if (selected) return selected[1];
|
|
51
|
+
return localSummaryTarget(base, head);
|
|
52
|
+
}
|
|
53
|
+
|
|
23
54
|
export function summaryPath({
|
|
24
55
|
callerDirectory,
|
|
25
56
|
repo,
|
|
@@ -33,17 +64,7 @@ export function summaryPath({
|
|
|
33
64
|
remote = 'origin',
|
|
34
65
|
}) {
|
|
35
66
|
if (explicit) return resolve(callerDirectory, explicit);
|
|
36
|
-
const
|
|
37
|
-
pr?.match(/\/pull\/(\d+)(?:\/|$)/)?.[1] || pr || undefined;
|
|
38
|
-
const target = pr
|
|
39
|
-
? { kind: 'pr', pullRequest, remote }
|
|
40
|
-
: branch
|
|
41
|
-
? { kind: 'branch', branch, base: base || 'default', remote }
|
|
42
|
-
: checkout
|
|
43
|
-
? { kind: 'checkout', base: base || 'default', remote }
|
|
44
|
-
: base && head
|
|
45
|
-
? { kind: 'range', base, head }
|
|
46
|
-
: { kind: 'worktree' };
|
|
67
|
+
const target = summaryTarget({ pr, branch, checkout, base, head, remote });
|
|
47
68
|
const key = createHash('sha256')
|
|
48
69
|
.update(JSON.stringify({ repo, target }))
|
|
49
70
|
.digest('hex')
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
/*! tailwindcss v4.2.1 | MIT License | https://tailwindcss.com */
|
|
2
|
-
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-ease:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-orange-50:oklch(98% .016 73.684);--color-orange-100:oklch(95.4% .038 75.164);--color-amber-50:oklch(98.7% .022 95.277);--color-amber-100:oklch(96.2% .059 95.617);--color-yellow-50:oklch(98.7% .026 102.212);--color-stone-50:oklch(98.5% .001 106.423);--color-stone-100:oklch(97% .001 106.424);--color-stone-200:oklch(92.3% .003 48.717);--color-white:#fff;--spacing:.25rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25 / 1.875);--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5 / 2.25);--text-5xl:3rem;--text-5xl--line-height:1;--text-6xl:3.75rem;--text-6xl--line-height:1;--text-7xl:4.5rem;--text-7xl--line-height:1;--text-8xl:6rem;--text-8xl--line-height:1;--text-9xl:8rem;--text-9xl--line-height:1;--font-weight-bold:700;--tracking-widest:.1em;--radius-md:.375rem;--radius-lg:.5rem;--ease-in:cubic-bezier(.4, 0, 1, 1);--ease-out:cubic-bezier(0, 0, .2, 1);--ease-in-out:cubic-bezier(.4, 0, .2, 1);--animate-ping:ping 1s cubic-bezier(0, 0, .2, 1) infinite;--animate-pulse:pulse 2s cubic-bezier(.4, 0, .6, 1) infinite;--animate-bounce:bounce 1s infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.\!visible{visibility:visible!important}.collapse{visibility:collapse}.invisible{visibility:hidden}.visible{visibility:visible}.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.start{inset-inline-start:var(--spacing)}.end{inset-inline-end:var(--spacing)}.isolate{isolation:isolate}.container{width:100%}@media (width>=40rem){.container{max-width:40rem}}@media (width>=48rem){.container{max-width:48rem}}@media (width>=64rem){.container{max-width:64rem}}@media (width>=80rem){.container{max-width:80rem}}@media (width>=96rem){.container{max-width:96rem}}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.inline-grid{display:inline-grid}.table{display:table}.w-24{width:calc(var(--spacing) * 24)}.flex-shrink,.shrink{flex-shrink:1}.grow{flex-grow:1}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.animate-bounce{animation:var(--animate-bounce)}.animate-ping{animation:var(--animate-ping)}.animate-pulse{animation:var(--animate-pulse)}.resize{resize:both}.flex-wrap{flex-wrap:wrap}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.border{border-style:var(--tw-border-style);border-width:1px}.bg-\[\#f5f0e6\],.bg-\[rgb\(245_240_230\)\]{background-color:#f5f0e6}.bg-amber-50{background-color:var(--color-amber-50)}.bg-amber-100{background-color:var(--color-amber-100)}.bg-orange-50{background-color:var(--color-orange-50)}.bg-orange-100{background-color:var(--color-orange-100)}.bg-stone-50{background-color:var(--color-stone-50)}.bg-stone-100{background-color:var(--color-stone-100)}.bg-stone-200{background-color:var(--color-stone-200)}.bg-yellow-50{background-color:var(--color-yellow-50)}.bg-clip-text{-webkit-background-clip:text;background-clip:text}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-6{padding-inline:calc(var(--spacing) * 6)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-3{padding-block:calc(var(--spacing) * 3)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.text-5xl{font-size:var(--text-5xl);line-height:var(--tw-leading,var(--text-5xl--line-height))}.text-6xl{font-size:var(--text-6xl);line-height:var(--tw-leading,var(--text-6xl--line-height))}.text-7xl{font-size:var(--text-7xl);line-height:var(--tw-leading,var(--text-7xl--line-height))}.text-8xl{font-size:var(--text-8xl);line-height:var(--tw-leading,var(--text-8xl--line-height))}.text-9xl{font-size:var(--text-9xl);line-height:var(--tw-leading,var(--text-9xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[…\]{color:…}.text-white{color:var(--color-white)}.capitalize{text-transform:capitalize}.lowercase{text-transform:lowercase}.uppercase{text-transform:uppercase}.italic{font-style:italic}.underline{text-decoration-line:underline}.shadow,.shadow\/elevation{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.blur{--tw-blur:blur(8px);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.grayscale{--tw-grayscale:grayscale(100%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.invert{--tw-invert:invert(100%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.backdrop-filter{-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.ease-in{--tw-ease:var(--ease-in);transition-timing-function:var(--ease-in)}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.ease-out{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}}@font-face{font-display:swap;font-family:Geist;font-style:normal;font-weight:100 900;src:url(./geist-latin-BGnTDqni.woff2)format("woff2")}@font-face{font-display:swap;font-family:Geist Mono;font-style:normal;font-weight:100 900;src:url(./geist-mono-latin-pyAoGB9p.woff2)format("woff2")}:root{--font-geist-sans:"Geist";--font-geist-mono:"Geist Mono";--canvas:#e9e2d5;--paper:#fbf8f0;--paper-deep:#f1ebdf;--ink:#20231f;--muted:#73766d;--quiet:#aaa99f;--line:#d8d1c4;--line-dark:#333831;--green:#2f6c4d;--green-soft:#dce9dc;--red:#a14f45;--red-soft:#f1ddda;--blue:#426b7c;--blue-soft:#dce9ed;--amber:#9c7027;--amber-soft:#eee3c7;--shadow:0 28px 80px #382d1e24}*{box-sizing:border-box}html,body{max-width:100%;min-height:100%;overflow-x:clip}#root{min-width:0;max-width:100%}body{background:var(--canvas);color:var(--ink);font-family:var(--font-geist-sans), Arial, sans-serif;text-rendering:optimizelegibility;margin:0}button,input{color:inherit;font:inherit}button,a{-webkit-tap-highlight-color:transparent}button:focus-visible,a:focus-visible,input:focus-visible{outline:2px solid var(--blue);outline-offset:3px}a{color:inherit}.app-shell{background:radial-gradient(circle at 6% 2%, #fffcf3d1, transparent 24rem), radial-gradient(circle at 94% 88%, #cbd3be85, transparent 26rem), var(--canvas);flex-direction:column;height:100dvh;min-height:700px;padding:0 24px 24px;display:flex;overflow:hidden}.topbar{flex:0 0 82px;grid-template-columns:minmax(230px,.8fr) minmax(300px,1.5fr) minmax(150px,.45fr);align-items:center;gap:32px;width:100%;max-width:1680px;margin:0 auto;display:grid}.brand-block,.change-block,.sync-state{min-width:0}.brand-block{align-items:center;gap:13px;display:flex}.brand-mark{border:1px solid var(--ink);font:700 10px/1 var(--font-geist-mono), monospace;letter-spacing:-.06em;flex:0 0 38px;grid-template-columns:1fr 1fr;height:38px;display:grid}.brand-mark span{justify-content:center;align-items:center;display:flex}.brand-mark span:first-child{background:var(--ink);color:var(--paper)}.brand-name,.brand-tag,.change-block p,.change-meta,.sync-state strong,.sync-state span,.eyebrow,.renamed-from,.summary-pane h2,.summary-lead,.note-section p,.agent-signoff,.loading-shell h1,.loading-shell p{margin:0}.brand-name{letter-spacing:-.02em;font-size:14px;font-weight:720}.brand-tag{color:var(--muted);margin-top:2px;font-size:11px}.change-block{border-left:1px solid #20231f29;padding-left:26px}.change-meta{color:var(--muted);font:600 10px/1.2 var(--font-geist-mono), monospace;letter-spacing:.06em;text-transform:uppercase;align-items:center;gap:8px;display:flex}.change-meta a{-webkit-text-decoration-color:var(--quiet);-webkit-text-decoration-color:var(--quiet);-webkit-text-decoration-color:var(--quiet);text-decoration-color:var(--quiet);text-underline-offset:3px}.meta-separator{color:var(--quiet)}.change-block>p{text-overflow:ellipsis;white-space:nowrap;margin-top:6px;font-size:13px;font-weight:560;overflow:hidden}.sync-state{justify-content:flex-end;align-items:center;gap:10px;display:flex}.sync-state>div{flex-direction:column;min-width:0;display:flex}.sync-state strong{font-size:11px;font-weight:650}.sync-state>div>span{color:var(--muted);margin-top:2px;font-size:10px}.live-dot{background:var(--green);box-sizing:content-box;border:4px solid #2f6c4d24;border-radius:999px;width:7px;height:7px;position:relative}.live-dot:after{border:1px solid var(--green);border-radius:inherit;content:"";animation:2.2s ease-out infinite live-ring;position:absolute;inset:-5px}.sync-state--error .live-dot{background:var(--red);border-color:#a14f4524}.sync-state--error .live-dot:after{border-color:var(--red);animation:none}.sync-state--notes .live-dot{background:var(--blue);border-color:#426b7c24}.sync-state--notes .live-dot:after{border-color:var(--blue)}.sync-state--notes-error .live-dot{background:var(--amber);border-color:#9c702724}.sync-state--notes-error .live-dot:after{border-color:var(--amber);animation:none}@keyframes live-ring{0%{opacity:.7;transform:scale(.65)}75%,to{opacity:0;transform:scale(1.55)}}.reader{flex-direction:column;flex:1;width:100%;max-width:1680px;min-height:0;margin:0 auto;display:flex}.reader-toolbar{background:var(--ink);color:#f8f4ea;border-radius:15px 15px 0 0;flex:0 0 60px;grid-template-columns:60px minmax(0,1fr) auto 60px;align-items:stretch;display:grid;overflow:hidden}.nav-button,.file-picker-trigger,.picker-row,.text-button,.picker-close{cursor:pointer;border:0}.nav-button{color:#f8f4ea;font:400 22px/1 var(--font-geist-mono), monospace;background:0 0;justify-content:center;align-items:center;transition:background .16s,color .16s;display:flex}.nav-button--previous{border-right:1px solid #ffffff1a}.nav-button--next{border-left:1px solid #ffffff1a}.nav-button:hover{color:#fff;background:#ffffff17}.nav-button span{transition:transform .18s}.nav-button--previous:hover span{transform:translate(-3px)}.nav-button--next:hover span{transform:translate(3px)}.file-picker-trigger{text-align:left;background:0 0;align-items:center;gap:13px;min-width:0;padding:0 20px;display:flex}.file-picker-trigger:hover .current-path{color:#fff}.file-count{color:#bcbcb3;font:600 10px/1 var(--font-geist-mono), monospace;letter-spacing:.04em;flex:none}.file-count i{color:#686d65;margin:0 4px;font-style:normal}.status-pin{background:var(--blue);border-radius:999px;flex:0 0 7px;width:7px;height:7px;display:inline-block}.status-pin--added{background:#71b78c}.status-pin--deleted{background:#db8478}.status-pin--renamed{background:#d5aa5c}.status-pin--binary{background:#a391c4}.current-path{color:#e6e2d9;font:520 12px/1.2 var(--font-geist-mono), monospace;text-overflow:ellipsis;white-space:nowrap;transition:color .16s;overflow:hidden}.picker-hint{color:#80847d;align-items:center;gap:3px;margin-left:auto;display:flex}kbd{font:500 9px/1 var(--font-geist-mono), monospace;border:1px solid #50554f;border-radius:4px;padding:3px 4px}.file-stats{font:600 10px/1 var(--font-geist-mono), monospace;align-items:center;gap:12px;padding:0 22px;display:flex}.status-label{color:#c7cbc4;letter-spacing:.04em;text-transform:uppercase;border:1px solid #525751;border-radius:99px;padding:6px 9px}.status-label--added{color:#a8d4b7;border-color:#71b78c73}.status-label--deleted{color:#ebb1a9;border-color:#db847873}.status-label--renamed{color:#e4c486;border-color:#d5aa5c73}.status-label--binary{color:#c7b9df;border-color:#a391c473}.addition{color:#8fcba5}.deletion{color:#e09b91}.page{background:var(--paper);box-shadow:var(--shadow);transform-origin:50% 8%;border:1px solid #20231f29;border-top:0;border-radius:0 0 18px 18px;flex:1;grid-template-columns:minmax(0,1.72fr) minmax(340px,.88fr);min-height:0;display:grid;overflow:hidden}.page--next{animation:.36s cubic-bezier(.22,.78,.28,1) page-next}.page--previous{animation:.36s cubic-bezier(.22,.78,.28,1) page-previous}.page--pick{animation:.3s cubic-bezier(.22,.78,.28,1) page-pick}@keyframes page-next{0%{opacity:0;transform:translate(26px)rotateY(-1deg)}to{opacity:1;transform:translate(0)rotateY(0)}}@keyframes page-previous{0%{opacity:0;transform:translate(-26px)rotateY(1deg)}to{opacity:1;transform:translate(0)rotateY(0)}}@keyframes page-pick{0%{opacity:0;transform:translateY(9px)scale(.997)}to{opacity:1;transform:translateY(0)scale(1)}}.diff-pane,.summary-pane{flex-direction:column;min-width:0;min-height:0;display:flex}.diff-pane{background:var(--paper)}.summary-pane{background:linear-gradient(#ffffff57, #fff0), var(--paper-deep);border-left:1px solid var(--line)}.pane-heading{border-bottom:1px solid var(--line);flex:0 0 84px;justify-content:space-between;align-items:center;gap:20px;padding:0 28px;display:flex}.eyebrow{color:var(--muted);font:650 9px/1.2 var(--font-geist-mono), monospace;letter-spacing:.12em}.pane-heading h1{letter-spacing:-.03em;text-overflow:ellipsis;white-space:nowrap;max-width:52vw;margin:5px 0 0;font-size:19px;font-weight:620;overflow:hidden}.renamed-from{color:var(--muted);font:500 10px/1.4 var(--font-geist-mono), monospace;margin-top:3px}.diff-actions{flex:none;align-items:center;gap:16px;display:flex}.text-button{color:var(--ink);text-underline-offset:4px;background:0 0;padding:7px 0;font-size:10px;font-weight:650;-webkit-text-decoration:underline #aaa99f;text-decoration:underline #aaa99f}.text-button:hover{-webkit-text-decoration-color:var(--ink);-webkit-text-decoration-color:var(--ink);-webkit-text-decoration-color:var(--ink);text-decoration-color:var(--ink)}.diff-scroll{overflow-anchor:none;overscroll-behavior:contain;flex:1;min-height:0;overflow:auto}.diff-scroll-content{min-height:100%}.diff-renderer-shell{min-width:100%}.diff-renderer{--diffs-addition-color-override:var(--green);--diffs-bg:var(--paper);--diffs-bg-context-override:var(--paper);--diffs-bg-hover-override:#20231f0a;--diffs-bg-separator-override:var(--blue-soft);--diffs-deletion-color-override:var(--red);--diffs-fg:var(--ink);--diffs-fg-number-override:var(--quiet);--diffs-font-family:var(--font-geist-mono), monospace;--diffs-font-size:11.5px;--diffs-font-weight:450;--diffs-gap-block:14px;--diffs-light:var(--ink);--diffs-light-bg:var(--paper);--diffs-line-height:21px;--diffs-min-number-column-width:3ch;--diffs-tab-size:2;min-width:100%;display:block}.diff-footer{border-top:1px solid var(--line);color:var(--muted);font:500 9px/1 var(--font-geist-mono), monospace;flex:0 0 38px;justify-content:space-between;align-items:center;padding:0 28px;display:flex}.binary-card{flex-direction:column;align-items:flex-start;max-width:420px;margin:auto;padding:12vh 36px 80px;display:flex}.binary-icon{background:linear-gradient(135deg, #ffffff3d, transparent), var(--ink);color:var(--paper);font:650 11px/1 var(--font-geist-mono), monospace;justify-content:center;align-items:center;width:48px;height:54px;margin-bottom:30px;display:flex;position:relative}.binary-icon:after{border-right:12px solid var(--paper);content:"";border-top:12px solid #0000;position:absolute;top:0;right:0}.binary-card h2{letter-spacing:-.04em;margin:10px 0 14px;font-size:26px;font-weight:590;line-height:1.08}.binary-card>p:last-child{color:var(--muted);margin:0;font-size:13px;line-height:1.6}.summary-scroll{flex:1;min-height:0;padding:31px 34px 44px;overflow:auto}.summary-kicker{color:var(--muted);font:650 9px/1 var(--font-geist-mono), monospace;letter-spacing:.12em;justify-content:space-between;align-items:center;display:flex}.summary-kicker span:first-child{align-items:center;gap:8px;display:flex}.summary-kicker span:first-child:before{background:var(--ink);content:"";width:19px;height:1px}.summary-kicker--pending span:first-child:before{background:var(--blue)}.summary-pane h2{letter-spacing:-.055em;max-width:12em;margin-top:35px;font-size:clamp(26px,2vw,36px);font-weight:570;line-height:1.04}.summary-lead{color:#545851;max-width:40em;margin-top:17px;font-size:14px;line-height:1.55}.note-skeleton{max-width:40em;margin-top:25px}.note-skeleton-group{gap:10px;display:grid}.note-skeleton-group+.note-skeleton-group{border-top:1px solid var(--line);margin-top:30px;padding-top:20px}.note-skeleton-line{background:linear-gradient(100deg,#ddd6ca 20%,#ebe5da 42%,#ddd6ca 64%) 0 0/300% 100%;border-radius:2px;height:11px;animation:1.6s ease-in-out infinite note-skeleton-shimmer;display:block}.note-skeleton-line--label{width:23%;height:8px;margin-bottom:3px}.note-skeleton-line--long{width:94%}.note-skeleton-line--medium{width:72%}.note-skeleton-line--short{width:48%}@keyframes note-skeleton-shimmer{0%{background-position:100% 0}to{background-position:-100% 0}}.note-section{border-top:1px solid var(--line);margin-top:30px;padding-top:19px}.note-section>p:not(.eyebrow){color:#454942;margin-top:10px;font-size:12.5px;line-height:1.62}.note-section--pending>p:not(.eyebrow){color:var(--blue)}.note-section ul{margin:11px 0 0;padding:0;list-style:none}.note-section li{color:#454942;padding:5px 0 5px 18px;font-size:12px;line-height:1.55;position:relative}.note-section li:before{background:var(--ink);content:"";width:8px;height:1px;position:absolute;top:14px;left:0}.note-section--risk{background:#eee5d8;border:1px solid #d6c9b7;border-radius:8px;padding:17px 18px 14px}.note-section--risk .eyebrow{color:#805a22}.note-section--risk li:before{background:var(--amber)}.agent-signoff{border-top:1px solid var(--line);color:#4e524b;flex:0 0 58px;align-items:center;gap:10px;padding:0 34px;font-size:10px;display:flex}.agent-glyph{background:var(--ink);color:var(--paper);border-radius:99px;flex:0 0 25px;justify-content:center;align-items:center;height:25px;font-size:10px;display:flex}.agent-signoff>span:last-child{flex-direction:column;display:flex}.agent-signoff small{color:var(--muted);font:450 8px/1.3 var(--font-geist-mono), monospace;margin-top:2px}.picker-backdrop{z-index:50;background:#181b178c;justify-content:center;align-items:flex-start;padding:min(14vh,130px) 24px 32px;animation:.18s ease-out backdrop-in;display:flex;position:fixed;inset:0}@keyframes backdrop-in{0%{opacity:0}to{opacity:1}}.picker-dialog{background:var(--paper);border:1px solid #ffffff80;border-radius:15px;flex-direction:column;width:100%;max-width:780px;max-height:min(680px,76vh);animation:.24s cubic-bezier(.22,.78,.28,1) dialog-in;display:flex;overflow:hidden;box-shadow:0 40px 100px #12141152}@keyframes dialog-in{0%{opacity:0;transform:translateY(-10px)scale(.985)}to{opacity:1;transform:translateY(0)scale(1)}}.picker-header{justify-content:space-between;align-items:flex-start;padding:25px 27px 20px;display:flex}.picker-header h2{letter-spacing:-.04em;margin:5px 0 0;font-size:22px;font-weight:600}.picker-close{background:var(--paper-deep);border:1px solid var(--line);color:var(--muted);font:550 9px/1 var(--font-geist-mono), monospace;border-radius:5px;min-width:44px;min-height:44px;padding:7px 8px}.picker-close:hover{color:var(--ink);border-color:#aaa79e}.picker-search{border-bottom:1px solid var(--line);border-top:1px solid var(--line);grid-template-columns:26px minmax(0,1fr) auto;align-items:center;margin:0 27px;padding:0 3px;transition:background .16s,border-color .16s;display:grid}.picker-search:focus-within{background:var(--blue-soft);border-color:var(--blue)}.picker-search>span{color:var(--muted);font-size:22px}.picker-search input{font:500 13px/1 var(--font-geist-mono), monospace;background:0 0;border:0;outline:0;min-width:0;height:52px}.picker-search input::placeholder{color:#a6a49b}.picker-search small{color:var(--muted);font:500 9px/1 var(--font-geist-mono), monospace}.picker-list{flex:1;min-height:0;padding:10px 13px 16px;position:relative;overflow:auto}.picker-row{text-align:left;background:0 0;border-radius:7px;grid-template-columns:30px 8px minmax(0,1fr) auto;align-items:center;gap:11px;width:100%;min-height:48px;padding:0 14px;display:grid}.picker-row:hover,.picker-row--active{background:#ece7dc}.picker-row--active{box-shadow:inset 2px 0 var(--ink)}.picker-index{color:var(--quiet);font:500 9px/1 var(--font-geist-mono), monospace}.picker-path{font:520 11px/1.2 var(--font-geist-mono), monospace;text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.picker-change-count{font:600 9px/1 var(--font-geist-mono), monospace;gap:9px;display:flex}.picker-change-count i{color:var(--green);font-style:normal}.picker-change-count b{color:var(--red);font-weight:600}.picker-empty{color:var(--muted);text-align:center;padding:38px 20px;font-size:12px}.loading-shell{background:radial-gradient(circle at 50% 34%, #f9f5eb, transparent 30rem), var(--canvas);text-align:center;flex-direction:column;justify-content:center;align-items:center;height:100dvh;min-height:600px;padding:32px;display:flex}.empty-shell{background:radial-gradient(circle at 50% 48%, #f9f5eb, transparent 30rem), var(--canvas);flex-direction:column;min-height:100dvh;padding:0 28px 28px;display:flex}.empty-topline{border-bottom:1px solid #20231f24;flex:0 0 82px;justify-content:space-between;align-items:center;width:100%;max-width:1480px;margin:0 auto;display:flex}.empty-message{text-align:center;flex-direction:column;flex:1;justify-content:center;align-items:center;padding:80px 24px 140px;display:flex}.empty-message h1{letter-spacing:-.07em;margin:15px 0 0;font-size:clamp(38px,6vw,72px);font-weight:540;line-height:1}.empty-message>p:last-child{color:var(--muted);max-width:420px;margin:20px 0 0;font-size:13px;line-height:1.6}.empty-message code{color:var(--ink);font-family:var(--font-geist-mono), monospace}.loading-mark{background:var(--ink);color:var(--paper);font:650 11px/1 var(--font-geist-mono), monospace;justify-content:center;align-items:center;width:42px;height:42px;margin-bottom:25px;display:flex}.loading-shell h1{letter-spacing:-.05em;margin-top:10px;font-size:36px;font-weight:570}.loading-line{background:#d0c8ba;width:190px;height:2px;margin-top:28px;overflow:hidden}.loading-line span{background:var(--ink);width:42%;height:100%;animation:1.4s ease-in-out infinite loading-slide;display:block}@keyframes loading-slide{0%{transform:translate(-110%)}to{transform:translate(350%)}}.loading-note{color:var(--muted);margin-top:18px;font-size:11px}.loading-note code{font-family:var(--font-geist-mono), monospace}.connection-error{background:var(--red);color:#fff;border-radius:6px;max-width:calc(100vw - 40px);padding:10px 14px;font-size:11px;position:fixed;bottom:20px;left:50%;transform:translate(-50%)}.sr-only{clip:rect(0, 0, 0, 0);clip-path:inset(50%);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}@media (width<=980px){.app-shell{height:auto;min-height:100dvh;padding:0 14px 18px;overflow:visible}.topbar{flex-basis:72px;grid-template-columns:1fr auto;gap:18px}.change-block{display:none}.reader{min-height:calc(100dvh - 90px)}.page{display:block;overflow:visible}.diff-pane{height:min(68vh,720px)}.summary-pane{border-left:0;border-top:1px solid var(--line);min-height:620px}.summary-scroll{overflow:visible}.summary-pane h2{max-width:16em}}@media (width<=680px){.app-shell{padding-left:0;padding-right:0}.topbar{padding:0 16px}.brand-tag,.sync-state>div>span,.picker-hint,.status-label,.diff-footer span:last-child{display:none}.sync-state{gap:7px}.reader-toolbar{border-radius:0;grid-template-columns:48px minmax(0,1fr) auto 48px}.file-picker-trigger{gap:8px;padding:0 10px}.file-count{display:none}.file-stats{gap:8px;padding:0 9px}.page{border-left:0;border-right:0;border-radius:0}.pane-heading{flex-direction:column;justify-content:center;align-items:flex-start;height:auto;min-height:104px;padding:18px 17px}.pane-heading h1{max-width:82vw}.diff-actions{gap:15px}.diff-renderer{--diffs-font-size:10.5px}.diff-footer{padding:0 17px}.summary-scroll{padding:28px 22px 38px}.agent-signoff{padding:0 22px}.picker-backdrop{align-items:flex-end;padding:0}.picker-dialog{border-radius:15px 15px 0 0;max-height:86vh}.picker-header{padding:22px 19px 18px}.picker-search{margin:0 19px}.picker-list{padding-left:7px;padding-right:7px}.picker-row{padding-left:10px;padding-right:10px}}@media (width<=360px){.reader-toolbar{grid-template-columns:48px minmax(0,1fr) 48px}.file-stats{display:none}}@media (prefers-reduced-motion:reduce){*,:before,:after{scroll-behavior:auto!important;transition-duration:.01ms!important;animation-duration:.01ms!important;animation-iteration-count:1!important}}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@keyframes ping{75%,to{opacity:0;transform:scale(2)}}@keyframes pulse{50%{opacity:.5}}@keyframes bounce{0%,to{animation-timing-function:cubic-bezier(.8,0,1,1);transform:translateY(-25%)}50%{animation-timing-function:cubic-bezier(0,0,.2,1);transform:none}}
|