diffsplain 0.5.0 → 0.7.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 +17 -12
- package/dist/assets/{angular-html-CoQle5-T.js → angular-html-CphzFC_v.js} +1 -1
- package/dist/assets/{angular-ts-BB_A1Lpy.js → angular-ts-Am0-mbFw.js} +1 -1
- package/dist/assets/{apl-B7_4_FNq.js → apl-Cs2rPKDN.js} +1 -1
- package/dist/assets/{astro-CXVo4VE4.js → astro-CxKUUn9h.js} +1 -1
- package/dist/assets/{blade-BVX41dq7.js → blade-DqeCa6v5.js} +1 -1
- package/dist/assets/{c-Dhyi8Nd2.js → c-DdI6lknk.js} +1 -1
- package/dist/assets/{cobol-CU5oLeZj.js → cobol-COuAGE2X.js} +1 -1
- package/dist/assets/{coffee-D_FU6gC_.js → coffee-BFlYT3Lp.js} +1 -1
- package/dist/assets/{cpp-CxoYLSi3.js → cpp-nywLU0Np.js} +1 -1
- package/dist/assets/{crystal-BdgqBN3z.js → crystal-BWp4dz1s.js} +1 -1
- package/dist/assets/{css-wLk1vVwJ.js → css-CRb9tUpU.js} +1 -1
- package/dist/assets/{edge-DMylWoGl.js → edge-rUYSbJy4.js} +1 -1
- package/dist/assets/{elixir-Djmgto_Z.js → elixir-DOq9ybIH.js} +1 -1
- package/dist/assets/{elm-D7B-eBLA.js → elm-C8e38E5-.js} +1 -1
- package/dist/assets/{erb-g1sso1GW.js → erb-BR9Q37Et.js} +1 -1
- package/dist/assets/{git-rebase-DEAhxutZ.js → git-rebase-Coo2LUtB.js} +1 -1
- package/dist/assets/{glimmer-js-CHvGFGMp.js → glimmer-js-C7hX9jg6.js} +1 -1
- package/dist/assets/{glimmer-ts-DvhL705p.js → glimmer-ts-CZLT0F0k.js} +1 -1
- package/dist/assets/{glsl-C8mS_t8I.js → glsl-BDZRv6e-.js} +1 -1
- package/dist/assets/{graphql-DW2pnvqi.js → graphql-DyzFc3y3.js} +1 -1
- package/dist/assets/{hack-DYc1d3he.js → hack-CWnO7WsF.js} +1 -1
- package/dist/assets/{haml-_5OmzH31.js → haml-CvDj-ghO.js} +1 -1
- package/dist/assets/{handlebars-rN-CaXUf.js → handlebars-BnU_YcVA.js} +1 -1
- package/dist/assets/{html-DQnaJUjl.js → html-CjB2TKKa.js} +1 -1
- package/dist/assets/{html-derivative-BovOubry.js → html-derivative-CYQqEXS6.js} +1 -1
- package/dist/assets/{http-BKTgwOYc.js → http-NNQbnqIx.js} +1 -1
- package/dist/assets/{hurl-ChS3_IgD.js → hurl-DHaAA1BS.js} +1 -1
- package/dist/assets/{index-BLtRBc2n.css → index-C2p_1iAk.css} +1 -1
- package/dist/assets/{index-CEglkSs-.js → index-f9ekN94b.js} +11 -11
- package/dist/assets/{java-Dgnyk--z.js → java-D1J5V2CD.js} +1 -1
- package/dist/assets/{javascript-BfnrwkRN.js → javascript-C3pX-HsE.js} +1 -1
- package/dist/assets/{jinja-CD2AIvOi.js → jinja-BY9CIY8g.js} +1 -1
- package/dist/assets/{jison-DmKNw3Pl.js → jison-Cr0H89gS.js} +1 -1
- package/dist/assets/{json-CKB5a8aj.js → json-CA3kKNfq.js} +1 -1
- package/dist/assets/{jsx-jJXrDRBM.js → jsx-BuMkR-Lj.js} +1 -1
- package/dist/assets/{julia-G8vVBW-t.js → julia-C2sLwg3f.js} +1 -1
- package/dist/assets/{just-DMXLrLZL.js → just-pXpkLBXv.js} +1 -1
- package/dist/assets/{latex-DS3xuxld.js → latex-7v3EPOdF.js} +1 -1
- package/dist/assets/{liquid-COQnD_C4.js → liquid-CJZq0Z0t.js} +1 -1
- package/dist/assets/{lua-BwMKwosd.js → lua-BimBzvTH.js} +1 -1
- package/dist/assets/{marko-cQCK_WIk.js → marko-CCZHVnQN.js} +1 -1
- package/dist/assets/{mdc-DQUjY13N.js → mdc-SLECNw2A.js} +1 -1
- package/dist/assets/{nginx-C4Kv3DJi.js → nginx-DUwtUge2.js} +1 -1
- package/dist/assets/{nim-ChzLzuYD.js → nim-HD8o_Omp.js} +1 -1
- package/dist/assets/{perl-z5xUiWAY.js → perl-BGMeIkNk.js} +1 -1
- package/dist/assets/{php-BHqm-Xeh.js → php-BL5xuY-v.js} +1 -1
- package/dist/assets/{pug-C9C_Llft.js → pug-C8G1U_u8.js} +1 -1
- package/dist/assets/{qml-dtpuwXJZ.js → qml-BGVSyw1x.js} +1 -1
- package/dist/assets/{r-BicQMs_c.js → r-BDnRcclC.js} +1 -1
- package/dist/assets/{razor-D5h00YTZ.js → razor-I8tbEIVJ.js} +1 -1
- package/dist/assets/{regexp-CTa8S86v.js → regexp-D0yakB9Q.js} +1 -1
- package/dist/assets/{rst-Bg-b-7HK.js → rst-DbTCn07T.js} +1 -1
- package/dist/assets/{ruby-BiWYm056.js → ruby-BwgPFIcO.js} +1 -1
- package/dist/assets/{sas-bJTdfgUz.js → sas-dN4ePfXU.js} +1 -1
- package/dist/assets/{scss-0qkNyHr1.js → scss-T7M1A4iM.js} +1 -1
- package/dist/assets/{shellscript-3sDkDE9z.js → shellscript-HFjBHS7_.js} +1 -1
- package/dist/assets/{shellsession-D4EBhEmz.js → shellsession-DCAGskrx.js} +1 -1
- package/dist/assets/{soy-DG9hUwLL.js → soy-BgT96TF9.js} +1 -1
- package/dist/assets/{sql-B0UoKHkx.js → sql-DCIQeRzx.js} +1 -1
- package/dist/assets/{stata-CsQbuXQp.js → stata-Dn0tad0_.js} +1 -1
- package/dist/assets/{surrealql-D63VLxos.js → surrealql-CCgFEQfW.js} +1 -1
- package/dist/assets/{svelte-BApdnAma.js → svelte-ITXimG8u.js} +1 -1
- package/dist/assets/{templ-B64pN6Lz.js → templ-DJVZZqPw.js} +1 -1
- package/dist/assets/{tex-DR2UVIPF.js → tex-Bn4N-OtH.js} +1 -1
- package/dist/assets/{ts-tags-IwhomvQv.js → ts-tags-CxQ4PoC4.js} +1 -1
- package/dist/assets/{tsx-DROdxqzS.js → tsx-CxwgcjkC.js} +1 -1
- package/dist/assets/{twig-BtbUqpR4.js → twig-CtbyTyMH.js} +1 -1
- package/dist/assets/{typescript-CHpSjYZL.js → typescript-d6ru12L8.js} +1 -1
- package/dist/assets/{vue-DCvEu_ug.js → vue-RvqAsYDm.js} +1 -1
- package/dist/assets/{vue-html-BgL4Rf-L.js → vue-html-BkHDVENe.js} +1 -1
- package/dist/assets/{vue-vine-CRNdhPTn.js → vue-vine-DvS3Mlkn.js} +1 -1
- package/dist/assets/{xml-CkfPxZuz.js → xml-lzTq5A-V.js} +1 -1
- package/dist/assets/{xsl-CteaoFSV.js → xsl-BL0KXNWI.js} +1 -1
- package/dist/assets/{yaml-CUr3TDRN.js → yaml-CF4XlBJS.js} +1 -1
- package/dist/demo-diff-data.json +6 -1
- package/dist/index.html +2 -2
- package/package.json +50 -23
- package/scripts/access-token.mjs +21 -0
- package/scripts/build-diff-data.mjs +260 -39
- package/scripts/cache.mjs +301 -0
- package/scripts/cli-args.mjs +206 -57
- package/scripts/coding-agents.mjs +279 -162
- package/scripts/dev.mjs +218 -0
- package/scripts/doctor.mjs +265 -46
- package/scripts/generate-summaries.mjs +760 -260
- package/scripts/mock-agent.mjs +165 -0
- package/scripts/present.mjs +394 -85
- package/scripts/presenter-runtime.mjs +148 -0
- package/scripts/serve-built.mjs +343 -71
- package/scripts/summary-path.mjs +24 -7
- package/scripts/support-record.mjs +242 -0
package/scripts/present.mjs
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { spawn
|
|
4
|
-
import { createHash } from 'node:crypto';
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
5
5
|
import {
|
|
6
|
-
|
|
6
|
+
chmodSync,
|
|
7
|
+
mkdirSync,
|
|
7
8
|
mkdtempSync,
|
|
8
9
|
readFileSync,
|
|
9
10
|
rmSync,
|
|
11
|
+
statSync,
|
|
12
|
+
writeFileSync,
|
|
10
13
|
} from 'node:fs';
|
|
11
14
|
import { tmpdir } from 'node:os';
|
|
12
15
|
import { dirname, join, resolve } from 'node:path';
|
|
@@ -14,14 +17,88 @@ import { createInterface } from 'node:readline';
|
|
|
14
17
|
import { fileURLToPath } from 'node:url';
|
|
15
18
|
import { helpText, parseCliArgs } from './cli-args.mjs';
|
|
16
19
|
import {
|
|
20
|
+
assertReasoningSupported,
|
|
17
21
|
codingAgentBinary,
|
|
18
22
|
commandAvailable,
|
|
19
23
|
selectCodingAgent,
|
|
20
24
|
} from './coding-agents.mjs';
|
|
21
25
|
import { doctorReport } from './doctor.mjs';
|
|
26
|
+
import { accessTokenDirectory } from './access-token.mjs';
|
|
27
|
+
import { cacheStatus, clearCache, formatCacheStatus, pruneCache } from './cache.mjs';
|
|
28
|
+
import {
|
|
29
|
+
agentFallbackRecordNeeded,
|
|
30
|
+
agentRunCompleted,
|
|
31
|
+
agentRunFailed,
|
|
32
|
+
agentRunNeeded,
|
|
33
|
+
agentRunSuperseded,
|
|
34
|
+
ensureBuiltAssets,
|
|
35
|
+
failedAgentRunForFingerprint,
|
|
36
|
+
nextAgentFingerprint,
|
|
37
|
+
openBrowser,
|
|
38
|
+
} from './presenter-runtime.mjs';
|
|
39
|
+
import {
|
|
40
|
+
createSupportRecorder,
|
|
41
|
+
formatSupportRecord,
|
|
42
|
+
safeCommandVersion,
|
|
43
|
+
writeSupportRecord,
|
|
44
|
+
} from './support-record.mjs';
|
|
22
45
|
|
|
23
46
|
const root = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
24
47
|
const callerDirectory = process.cwd();
|
|
48
|
+
const cacheArgs = process.argv.slice(2);
|
|
49
|
+
|
|
50
|
+
function cacheUsage() {
|
|
51
|
+
console.error(
|
|
52
|
+
'Use: diffsplain cache [status|prune --age DAYS|prune --size BYTES|clear --yes]',
|
|
53
|
+
);
|
|
54
|
+
return 2;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function printCacheChange(result) {
|
|
58
|
+
console.log(
|
|
59
|
+
`Removed ${result.removed.length} inactive cache entries; kept ${result.retainedActive.length} active.`,
|
|
60
|
+
);
|
|
61
|
+
return 0;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function statusCommand(args) {
|
|
65
|
+
if (args.length !== 1 && args.length !== 2) return cacheUsage();
|
|
66
|
+
console.log(formatCacheStatus(cacheStatus()));
|
|
67
|
+
return 0;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function clearCommand(args) {
|
|
71
|
+
if (args.length !== 3 || args[2] !== '--yes') return cacheUsage();
|
|
72
|
+
return printCacheChange(clearCache());
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function pruneOptions(flag, rawValue) {
|
|
76
|
+
const value = Number(rawValue);
|
|
77
|
+
if (!Number.isFinite(value) || value < 0) return undefined;
|
|
78
|
+
if (flag === '--age') return { maxAgeMs: value * 86_400_000 };
|
|
79
|
+
if (flag === '--size') return { maxBytes: value };
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function pruneCommand(args) {
|
|
84
|
+
if (args.length !== 4) return cacheUsage();
|
|
85
|
+
const options = pruneOptions(args[2], args[3]);
|
|
86
|
+
return options ? printCacheChange(pruneCache(options)) : cacheUsage();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function runCacheCommand(args) {
|
|
90
|
+
const commands = {
|
|
91
|
+
status: statusCommand,
|
|
92
|
+
clear: clearCommand,
|
|
93
|
+
prune: pruneCommand,
|
|
94
|
+
};
|
|
95
|
+
const command = args[1] || 'status';
|
|
96
|
+
return commands[command]?.(args) ?? cacheUsage();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (cacheArgs[0] === 'cache') {
|
|
100
|
+
process.exit(runCacheCommand(cacheArgs));
|
|
101
|
+
}
|
|
25
102
|
let cli;
|
|
26
103
|
try {
|
|
27
104
|
cli = parseCliArgs(process.argv.slice(2), { callerDirectory });
|
|
@@ -43,28 +120,102 @@ if (cli.version) {
|
|
|
43
120
|
process.exit(0);
|
|
44
121
|
}
|
|
45
122
|
if (cli.doctor) {
|
|
46
|
-
|
|
47
|
-
|
|
123
|
+
if (cli.doctor.deep) {
|
|
124
|
+
console.error(
|
|
125
|
+
'Warning: deep checks run local provider commands. They do not send a provider prompt.',
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
const report = await doctorReport({ deep: cli.doctor.deep });
|
|
129
|
+
console.log(cli.doctor.json ? JSON.stringify(report.json, null, 2) : report.text);
|
|
48
130
|
process.exit(report.ready ? 0 : 1);
|
|
49
131
|
}
|
|
50
132
|
|
|
51
|
-
|
|
133
|
+
let supportRecorder;
|
|
134
|
+
let supportRecordEmitted = false;
|
|
135
|
+
|
|
136
|
+
function wantsSupportRecord() {
|
|
137
|
+
return cli.supportRecord || Boolean(cli.supportRecordFile);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function selectedProviderVersion(provider, binary) {
|
|
141
|
+
if (!binary) return null;
|
|
142
|
+
return safeCommandVersion(provider, binary);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function beginSupportRecord() {
|
|
146
|
+
if (supportRecorder) return;
|
|
147
|
+
if (!wantsSupportRecord()) return;
|
|
148
|
+
supportRecorder = createSupportRecorder();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function recordSelectedProvider(provider, binary) {
|
|
152
|
+
supportRecorder?.setProvider(
|
|
153
|
+
provider,
|
|
154
|
+
selectedProviderVersion(provider, binary),
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function deliverSupportRecord(record) {
|
|
159
|
+
if (cli.supportRecordFile) {
|
|
160
|
+
writeSupportRecord(cli.supportRecordFile, record);
|
|
161
|
+
console.error(`Wrote support record to ${cli.supportRecordFile}`);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
process.stderr.write(
|
|
165
|
+
`Diffsplain support record:\n${formatSupportRecord(record)}`,
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function emitSupportRecord(code = 1) {
|
|
170
|
+
if (!supportRecorder) return;
|
|
171
|
+
if (supportRecordEmitted) return;
|
|
172
|
+
supportRecordEmitted = true;
|
|
173
|
+
const record = supportRecorder.failure(code);
|
|
174
|
+
try {
|
|
175
|
+
deliverSupportRecord(record);
|
|
176
|
+
} catch {
|
|
177
|
+
console.error('Could not write the support record.');
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const { agentEnabled, browserEnabled, host, port } = cli;
|
|
52
182
|
const feedArgs = [...cli.feedArgs];
|
|
53
183
|
const agentArgs = [...cli.agentArgs];
|
|
184
|
+
let selectedAgent;
|
|
185
|
+
beginSupportRecord();
|
|
54
186
|
if (agentEnabled) {
|
|
187
|
+
const selectionStarted = performance.now();
|
|
55
188
|
try {
|
|
56
|
-
|
|
189
|
+
selectedAgent = await selectCodingAgent(
|
|
57
190
|
cli.agent,
|
|
58
191
|
(agent) =>
|
|
59
192
|
commandAvailable(
|
|
60
193
|
codingAgentBinary(agent, { codexBin: cli.codexBin }),
|
|
61
194
|
),
|
|
62
195
|
);
|
|
196
|
+
assertReasoningSupported(selectedAgent, cli.reasoning);
|
|
197
|
+
const agentBinary = codingAgentBinary(selectedAgent, {
|
|
198
|
+
codexBin: cli.codexBin,
|
|
199
|
+
});
|
|
200
|
+
recordSelectedProvider(selectedAgent, agentBinary);
|
|
201
|
+
supportRecorder?.addStage(
|
|
202
|
+
'agent',
|
|
203
|
+
performance.now() - selectionStarted,
|
|
204
|
+
);
|
|
63
205
|
agentArgs.push('--agent', selectedAgent);
|
|
64
206
|
} catch (error) {
|
|
207
|
+
recordSelectedProvider(cli.agent || 'unknown');
|
|
208
|
+
supportRecorder?.addStage(
|
|
209
|
+
'agent',
|
|
210
|
+
performance.now() - selectionStarted,
|
|
211
|
+
'failed',
|
|
212
|
+
);
|
|
65
213
|
console.error(error.message);
|
|
214
|
+
emitSupportRecord();
|
|
66
215
|
process.exit(1);
|
|
67
216
|
}
|
|
217
|
+
} else {
|
|
218
|
+
feedArgs.push('--no-summaries');
|
|
68
219
|
}
|
|
69
220
|
const outputIndex = feedArgs.indexOf('--output');
|
|
70
221
|
let runtimeDirectory;
|
|
@@ -95,23 +246,39 @@ const projectKey = createHash('sha256')
|
|
|
95
246
|
)
|
|
96
247
|
.digest('hex')
|
|
97
248
|
.slice(0, 12);
|
|
249
|
+
const accessDirectory = accessTokenDirectory();
|
|
250
|
+
const accessPath = join(accessDirectory, `${projectKey}.token`);
|
|
251
|
+
mkdirSync(accessDirectory, { recursive: true, mode: 0o700 });
|
|
252
|
+
chmodSync(accessDirectory, 0o700);
|
|
253
|
+
let previousAccess;
|
|
254
|
+
try {
|
|
255
|
+
const savedAccess = readFileSync(accessPath, 'utf8').trim();
|
|
256
|
+
if (/^[A-Za-z0-9_-]{43}$/.test(savedAccess)) previousAccess = savedAccess;
|
|
257
|
+
} catch {
|
|
258
|
+
// The first run for a project has no prior tab access value.
|
|
259
|
+
}
|
|
260
|
+
const access = randomBytes(32).toString('base64url');
|
|
261
|
+
writeFileSync(accessPath, access, { mode: 0o600 });
|
|
262
|
+
chmodSync(accessPath, 0o600);
|
|
98
263
|
if (agentEnabled) {
|
|
99
264
|
feedArgs.push('--ignore-summary-watch');
|
|
100
265
|
agentArgs.push('--snapshot', outputPath);
|
|
101
266
|
}
|
|
102
267
|
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
268
|
+
const snapshotStarted = performance.now();
|
|
269
|
+
let snapshotReady = false;
|
|
270
|
+
try {
|
|
271
|
+
ensureBuiltAssets({ root });
|
|
272
|
+
} catch (error) {
|
|
273
|
+
const exitCode = Number.isInteger(error.exitCode) ? error.exitCode : 1;
|
|
274
|
+
supportRecorder?.addStage(
|
|
275
|
+
'snapshot',
|
|
276
|
+
performance.now() - snapshotStarted,
|
|
277
|
+
'failed',
|
|
109
278
|
);
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
if (result.status !== 0) process.exit(result.status || 1);
|
|
279
|
+
console.error(error.message);
|
|
280
|
+
emitSupportRecord(exitCode);
|
|
281
|
+
process.exit(exitCode);
|
|
115
282
|
}
|
|
116
283
|
|
|
117
284
|
const feed = spawn(
|
|
@@ -127,39 +294,49 @@ let site;
|
|
|
127
294
|
let agent;
|
|
128
295
|
let agentTimer;
|
|
129
296
|
let agentFingerprint;
|
|
297
|
+
let completedAgentFingerprint;
|
|
298
|
+
let failedAgentFingerprint;
|
|
130
299
|
let queuedFingerprint;
|
|
131
300
|
let browserOpened = false;
|
|
132
301
|
let browserOpenTimer;
|
|
302
|
+
let siteReady = false;
|
|
303
|
+
let siteStarted;
|
|
133
304
|
|
|
134
|
-
function
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
command = 'open';
|
|
142
|
-
args = [url];
|
|
143
|
-
} else if (process.platform === 'win32') {
|
|
144
|
-
command = 'cmd.exe';
|
|
145
|
-
args = ['/d', '/s', '/c', 'start', '', url];
|
|
146
|
-
} else {
|
|
147
|
-
command = 'xdg-open';
|
|
148
|
-
args = [url];
|
|
305
|
+
function handleConnectedTab(line) {
|
|
306
|
+
if (line !== 'Diffsplain tab: connected') return false;
|
|
307
|
+
if (!browserOpened && browserOpenTimer) {
|
|
308
|
+
clearTimeout(browserOpenTimer);
|
|
309
|
+
browserOpenTimer = undefined;
|
|
310
|
+
browserOpened = true;
|
|
311
|
+
console.log('Reusing the open Diffsplain tab.');
|
|
149
312
|
}
|
|
313
|
+
return true;
|
|
314
|
+
}
|
|
150
315
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
316
|
+
function markSiteReady(match) {
|
|
317
|
+
if (siteReady || !match) return;
|
|
318
|
+
siteReady = true;
|
|
319
|
+
supportRecorder?.addStage(
|
|
320
|
+
'serve',
|
|
321
|
+
performance.now() - siteStarted,
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function scheduleBrowserOpen(match) {
|
|
326
|
+
if (!browserEnabled || browserOpened || browserOpenTimer || !match) return;
|
|
327
|
+
browserOpenTimer = setTimeout(() => {
|
|
328
|
+
browserOpenTimer = undefined;
|
|
329
|
+
browserOpened = true;
|
|
330
|
+
openBrowser(match[1], {
|
|
331
|
+
onError: (error) =>
|
|
332
|
+
console.error(`Could not open the browser: ${error.message}`),
|
|
333
|
+
});
|
|
334
|
+
}, 750);
|
|
159
335
|
}
|
|
160
336
|
|
|
161
337
|
function startSite() {
|
|
162
338
|
if (closing || site) return;
|
|
339
|
+
siteStarted = performance.now();
|
|
163
340
|
const child = spawn(
|
|
164
341
|
process.execPath,
|
|
165
342
|
[
|
|
@@ -168,8 +345,13 @@ function startSite() {
|
|
|
168
345
|
outputPath,
|
|
169
346
|
'--port',
|
|
170
347
|
String(port),
|
|
348
|
+
'--host',
|
|
349
|
+
host,
|
|
171
350
|
'--project',
|
|
172
351
|
projectKey,
|
|
352
|
+
'--access',
|
|
353
|
+
access,
|
|
354
|
+
...(previousAccess ? ['--previous-access', previousAccess] : []),
|
|
173
355
|
...(!cli.portWasPassed ? ['--increment-port'] : []),
|
|
174
356
|
],
|
|
175
357
|
{ cwd: root, stdio: ['ignore', 'pipe', 'inherit'] },
|
|
@@ -179,33 +361,36 @@ function startSite() {
|
|
|
179
361
|
if (child.stdout) {
|
|
180
362
|
const siteLines = createInterface({ input: child.stdout });
|
|
181
363
|
siteLines.on('line', (line) => {
|
|
182
|
-
if (line
|
|
183
|
-
if (!browserOpened && browserOpenTimer) {
|
|
184
|
-
clearTimeout(browserOpenTimer);
|
|
185
|
-
browserOpenTimer = undefined;
|
|
186
|
-
browserOpened = true;
|
|
187
|
-
console.log('Reusing the open Diffsplain tab.');
|
|
188
|
-
}
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
364
|
+
if (handleConnectedTab(line)) return;
|
|
191
365
|
console.log(line);
|
|
192
366
|
const match = line.match(/^Diffsplain: (http:\/\/\S+)$/);
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
browserOpenTimer = undefined;
|
|
196
|
-
browserOpened = true;
|
|
197
|
-
openBrowser(match[1]);
|
|
198
|
-
}, 750);
|
|
199
|
-
}
|
|
367
|
+
markSiteReady(match);
|
|
368
|
+
scheduleBrowserOpen(match);
|
|
200
369
|
});
|
|
201
370
|
}
|
|
202
371
|
|
|
203
372
|
child.on('exit', (code, signal) => {
|
|
204
|
-
if (!closing)
|
|
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
|
+
}
|
|
205
384
|
});
|
|
206
385
|
child.on('error', (error) => {
|
|
207
386
|
if (!closing) {
|
|
387
|
+
supportRecorder?.addStage(
|
|
388
|
+
'serve',
|
|
389
|
+
performance.now() - siteStarted,
|
|
390
|
+
'failed',
|
|
391
|
+
);
|
|
208
392
|
console.error(`Could not start the local page: ${error.message}`);
|
|
393
|
+
emitSupportRecord();
|
|
209
394
|
stop(1);
|
|
210
395
|
}
|
|
211
396
|
});
|
|
@@ -216,12 +401,19 @@ function snapshotState() {
|
|
|
216
401
|
const snapshot = JSON.parse(readFileSync(outputPath, 'utf8'));
|
|
217
402
|
if (snapshot.notes?.reviewFingerprint) {
|
|
218
403
|
const fingerprint = snapshot.notes.reviewFingerprint;
|
|
404
|
+
const emptyReview =
|
|
405
|
+
Array.isArray(snapshot.files) && snapshot.files.length === 0;
|
|
219
406
|
return {
|
|
220
407
|
fingerprint,
|
|
221
408
|
hasCurrentAgentNotes:
|
|
222
409
|
snapshot.notes.complete &&
|
|
223
410
|
snapshot.notes.fresh &&
|
|
224
|
-
snapshot.notes.generatedFor === fingerprint
|
|
411
|
+
snapshot.notes.generatedFor === fingerprint &&
|
|
412
|
+
(emptyReview ||
|
|
413
|
+
(snapshot.notes.agent === selectedAgent &&
|
|
414
|
+
(snapshot.notes.model || null) === (cli.model || null) &&
|
|
415
|
+
(snapshot.notes.reasoning || null) ===
|
|
416
|
+
(cli.reasoning || null))),
|
|
225
417
|
};
|
|
226
418
|
}
|
|
227
419
|
const reviewData = {
|
|
@@ -263,6 +455,56 @@ function snapshotFingerprint() {
|
|
|
263
455
|
return snapshotState()?.fingerprint;
|
|
264
456
|
}
|
|
265
457
|
|
|
458
|
+
function recordAgentFallbackStage(needed, startedAt) {
|
|
459
|
+
if (!needed) return;
|
|
460
|
+
supportRecorder?.addStage(
|
|
461
|
+
'agent',
|
|
462
|
+
performance.now() - startedAt,
|
|
463
|
+
'failed',
|
|
464
|
+
);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function releaseAgentChild(child) {
|
|
468
|
+
if (agent === child) agent = undefined;
|
|
469
|
+
agentFingerprint = undefined;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
function recordAgentRunResult({
|
|
473
|
+
closing,
|
|
474
|
+
code,
|
|
475
|
+
error,
|
|
476
|
+
signal,
|
|
477
|
+
superseded,
|
|
478
|
+
finishedFingerprint,
|
|
479
|
+
}) {
|
|
480
|
+
if (agentRunCompleted({ code, error, signal, superseded })) {
|
|
481
|
+
completedAgentFingerprint = finishedFingerprint;
|
|
482
|
+
}
|
|
483
|
+
if (!agentRunFailed({ closing, code, error, signal, superseded })) return;
|
|
484
|
+
failedAgentFingerprint = finishedFingerprint;
|
|
485
|
+
if (error) console.error(error.message);
|
|
486
|
+
console.error(
|
|
487
|
+
'The coding agent could not write notes. It will retry after the diff changes or Diffsplain restarts.',
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function scheduleNextAgentFingerprint({
|
|
492
|
+
pendingFingerprint,
|
|
493
|
+
finishedFingerprint,
|
|
494
|
+
}) {
|
|
495
|
+
const nextFingerprint = nextAgentFingerprint({
|
|
496
|
+
queuedFingerprint: pendingFingerprint,
|
|
497
|
+
observedFingerprint: snapshotFingerprint(),
|
|
498
|
+
finishedFingerprint,
|
|
499
|
+
});
|
|
500
|
+
queuedFingerprint = undefined;
|
|
501
|
+
if (nextFingerprint) scheduleAgent(nextFingerprint);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function emitAgentFallbackRecord(needed, code) {
|
|
505
|
+
if (needed) emitSupportRecord(code || 1);
|
|
506
|
+
}
|
|
507
|
+
|
|
266
508
|
function runAgent(fingerprint) {
|
|
267
509
|
if (closing || !agentEnabled) return;
|
|
268
510
|
if (agent) {
|
|
@@ -273,6 +515,7 @@ function runAgent(fingerprint) {
|
|
|
273
515
|
return;
|
|
274
516
|
}
|
|
275
517
|
agentFingerprint = fingerprint;
|
|
518
|
+
const agentStarted = performance.now();
|
|
276
519
|
const child = spawn(
|
|
277
520
|
process.execPath,
|
|
278
521
|
[resolve(root, 'scripts/generate-summaries.mjs'), ...agentArgs],
|
|
@@ -284,17 +527,32 @@ function runAgent(fingerprint) {
|
|
|
284
527
|
if (settled) return;
|
|
285
528
|
settled = true;
|
|
286
529
|
const finishedFingerprint = agentFingerprint;
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
530
|
+
const pendingFingerprint = queuedFingerprint;
|
|
531
|
+
const superseded = agentRunSuperseded(
|
|
532
|
+
pendingFingerprint,
|
|
533
|
+
finishedFingerprint,
|
|
534
|
+
);
|
|
535
|
+
const needsFallbackRecord = agentFallbackRecordNeeded({
|
|
536
|
+
closing,
|
|
537
|
+
queuedFingerprint: pendingFingerprint,
|
|
538
|
+
error,
|
|
539
|
+
signal,
|
|
540
|
+
});
|
|
541
|
+
recordAgentFallbackStage(needsFallbackRecord, agentStarted);
|
|
542
|
+
releaseAgentChild(child);
|
|
543
|
+
recordAgentRunResult({
|
|
544
|
+
closing,
|
|
545
|
+
code,
|
|
546
|
+
error,
|
|
547
|
+
signal,
|
|
548
|
+
superseded,
|
|
549
|
+
finishedFingerprint,
|
|
550
|
+
});
|
|
551
|
+
scheduleNextAgentFingerprint({
|
|
552
|
+
pendingFingerprint,
|
|
553
|
+
finishedFingerprint,
|
|
554
|
+
});
|
|
555
|
+
emitAgentFallbackRecord(needsFallbackRecord, code);
|
|
298
556
|
};
|
|
299
557
|
child.on('error', (error) => finish(1, undefined, error));
|
|
300
558
|
child.on('exit', (code, signal) => finish(code, signal));
|
|
@@ -303,16 +561,28 @@ function runAgent(fingerprint) {
|
|
|
303
561
|
function scheduleAgent(fingerprint) {
|
|
304
562
|
const state = snapshotState();
|
|
305
563
|
const selectedFingerprint = fingerprint || state?.fingerprint;
|
|
564
|
+
failedAgentFingerprint = failedAgentRunForFingerprint(
|
|
565
|
+
failedAgentFingerprint,
|
|
566
|
+
selectedFingerprint,
|
|
567
|
+
);
|
|
306
568
|
if (
|
|
307
569
|
!cli.forceSummaryRegeneration &&
|
|
308
570
|
!agentFingerprint &&
|
|
309
571
|
state?.hasCurrentAgentNotes &&
|
|
310
572
|
selectedFingerprint === state.fingerprint
|
|
311
573
|
) {
|
|
312
|
-
|
|
574
|
+
console.log('Reusing current agent notes.');
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
if (
|
|
578
|
+
!agentRunNeeded(selectedFingerprint, {
|
|
579
|
+
activeFingerprint: agentFingerprint,
|
|
580
|
+
completedFingerprint: completedAgentFingerprint,
|
|
581
|
+
failedFingerprint: failedAgentFingerprint,
|
|
582
|
+
})
|
|
583
|
+
) {
|
|
313
584
|
return;
|
|
314
585
|
}
|
|
315
|
-
if (!selectedFingerprint || selectedFingerprint === agentFingerprint) return;
|
|
316
586
|
if (agent) {
|
|
317
587
|
if (selectedFingerprint !== agentFingerprint) {
|
|
318
588
|
queuedFingerprint = selectedFingerprint;
|
|
@@ -325,17 +595,44 @@ function scheduleAgent(fingerprint) {
|
|
|
325
595
|
agentTimer = setTimeout(() => runAgent(selectedFingerprint), delay);
|
|
326
596
|
}
|
|
327
597
|
|
|
598
|
+
function markSnapshotReady() {
|
|
599
|
+
if (snapshotReady) return;
|
|
600
|
+
snapshotReady = true;
|
|
601
|
+
try {
|
|
602
|
+
supportRecorder?.addBytes('snapshot', statSync(outputPath).size);
|
|
603
|
+
} catch {}
|
|
604
|
+
supportRecorder?.addStage(
|
|
605
|
+
'snapshot',
|
|
606
|
+
performance.now() - snapshotStarted,
|
|
607
|
+
);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
function isSnapshotLine(line) {
|
|
611
|
+
return line.startsWith('Wrote ') || line === 'No diff-data changes';
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
function startSnapshotDependents() {
|
|
615
|
+
markSnapshotReady();
|
|
616
|
+
startSite();
|
|
617
|
+
if (agentEnabled) scheduleAgent();
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
function shouldPrintFeedLine(line) {
|
|
621
|
+
return line !== 'No diff-data changes' || !agentEnabled;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
function handleFeedLine(line) {
|
|
625
|
+
if (isSnapshotLine(line)) startSnapshotDependents();
|
|
626
|
+
if (shouldPrintFeedLine(line)) console.log(line);
|
|
627
|
+
}
|
|
628
|
+
|
|
328
629
|
if (feed.stdout) {
|
|
329
630
|
const feedLines = createInterface({ input: feed.stdout });
|
|
330
|
-
feedLines.on('line',
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
if (agentEnabled) scheduleAgent();
|
|
336
|
-
}
|
|
337
|
-
if (line !== 'No diff-data changes' || !agentEnabled) console.log(line);
|
|
338
|
-
});
|
|
631
|
+
feedLines.on('line', handleFeedLine);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
function stopChild(child) {
|
|
635
|
+
if (child && !child.killed) child.kill('SIGTERM');
|
|
339
636
|
}
|
|
340
637
|
|
|
341
638
|
function stop(code = 0) {
|
|
@@ -343,18 +640,30 @@ function stop(code = 0) {
|
|
|
343
640
|
closing = true;
|
|
344
641
|
clearTimeout(browserOpenTimer);
|
|
345
642
|
clearTimeout(agentTimer);
|
|
346
|
-
|
|
347
|
-
if (site && !site.killed) site.kill('SIGTERM');
|
|
348
|
-
if (agent && !agent.killed) agent.kill('SIGTERM');
|
|
643
|
+
[feed, site, agent].forEach(stopChild);
|
|
349
644
|
process.exitCode = code;
|
|
350
645
|
}
|
|
351
646
|
|
|
352
647
|
feed.on('exit', (code, signal) => {
|
|
353
|
-
if (!closing && (code || signal))
|
|
648
|
+
if (!closing && (code || signal)) {
|
|
649
|
+
supportRecorder?.addStage(
|
|
650
|
+
'snapshot',
|
|
651
|
+
performance.now() - snapshotStarted,
|
|
652
|
+
'failed',
|
|
653
|
+
);
|
|
654
|
+
emitSupportRecord(code || 1);
|
|
655
|
+
stop(code || 1);
|
|
656
|
+
}
|
|
354
657
|
});
|
|
355
658
|
feed.on('error', (error) => {
|
|
356
659
|
if (!closing) {
|
|
660
|
+
supportRecorder?.addStage(
|
|
661
|
+
'snapshot',
|
|
662
|
+
performance.now() - snapshotStarted,
|
|
663
|
+
'failed',
|
|
664
|
+
);
|
|
357
665
|
console.error(`Could not start the diff watcher: ${error.message}`);
|
|
666
|
+
emitSupportRecord();
|
|
358
667
|
stop(1);
|
|
359
668
|
}
|
|
360
669
|
});
|