@dev.fast/whiteboard 0.2.1-preview.20261006.99 → 0.2.1-preview.20261007.101
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/dist/{account-alias-DOHN1RSH.js → account-alias-DLE9XZhi.js} +1 -1
- package/dist/agent-cli-DdfuiyOF.js +2 -0
- package/dist/{agent-cli-ChOq0ZuO.js → agent-cli-d0YJQNrR.js} +6 -6
- package/dist/{agent-client-CPQo7iTI.js → agent-client-DoaODH6I.js} +2 -2
- package/dist/{authoring-tools-Cr6Kpjsx.js → authoring-tools-Bvre8hN2.js} +2 -2
- package/dist/build-info.json +1 -1
- package/dist/{cli-hVgHcdsv.js → cli-DtolttUF.js} +1 -1
- package/dist/{cli-runner-CMmd6o_m.js → cli-runner-CSEH_mQN.js} +11 -11
- package/dist/cli.js +2 -2
- package/dist/{client-DFoh50W3.js → client-D01H2KOV.js} +2 -2
- package/dist/{desktop-discovery-DBO-PM2V.js → desktop-discovery-DK2HoBy5.js} +1 -1
- package/dist/{headless-host-CVMDNmLU.js → headless-host-YxYKMcI5.js} +3 -2
- package/dist/{local-data-CxLk75rx.js → local-data-Dy8AngNb.js} +37 -84
- package/dist/{mcp-QeF8vkdf.js → mcp-DGKhDPMs.js} +3 -3
- package/dist/{process-error-telemetry-CkiiadgB.js → process-error-telemetry-DN5BpZnc.js} +345 -13
- package/dist/{profile-4ry2f3AM.js → profile-B6jrb2QN.js} +1 -1
- package/dist/profile-D8XSrJYI.js +2 -0
- package/dist/{request-origin-D_4QIdoN.js → request-origin-BB2TENZ5.js} +1 -1
- package/dist/{review-telemetry-DEFnQsQ2.js → review-telemetry-2UlsdE4D.js} +2 -2
- package/dist/server/desktop-host.js +14 -303
- package/dist/sharing/index.d.ts +6 -10
- package/dist/sharing/index.js +1 -1
- package/dist/{src-DnwdaQ2r.js → src-DHiDJQDy.js} +7 -14
- package/dist/{stored-document-migration-DHeMpLMb.js → stored-document-migration-T9Uj5MkN.js} +1 -1
- package/dist/{tool-failure-C8zB73HV.js → tool-failure-Djeeax6q.js} +1 -1
- package/package.json +8 -9
- package/src/review-api/comparison-coverage.ts +6 -11
- package/src/review-api/http.ts +31 -4
- package/src/review-api/store.ts +0 -5
- package/src/server/desktop-server.ts +2 -13
- package/src/server/diffr-config.ts +170 -128
- package/src/server/headless-host.ts +3 -0
- package/src/server/structural-diff.ts +12 -39
- package/src/server/test-fixtures/diffr-ui-overrides.toml +38 -0
- package/dist/agent-cli-DzWfZr6G.js +0 -2
- package/dist/profile-Bh5FIrmf.js +0 -2
- package/src/server/diffr-languages.ts +0 -97
package/src/review-api/store.ts
CHANGED
|
@@ -1237,11 +1237,6 @@ export class ReviewStore {
|
|
|
1237
1237
|
...(applied?.children && { children: applied.children }),
|
|
1238
1238
|
};
|
|
1239
1239
|
|
|
1240
|
-
if (snapshot.staleSources?.length)
|
|
1241
|
-
warnings.push(
|
|
1242
|
-
"Some authored source ranges changed. Update their references before presenting this review.",
|
|
1243
|
-
);
|
|
1244
|
-
|
|
1245
1240
|
if (warnings.length) result.warnings = warnings;
|
|
1246
1241
|
|
|
1247
1242
|
this.commitCommand(
|
|
@@ -57,12 +57,12 @@ import { z } from "zod";
|
|
|
57
57
|
import { aliasInstallationToAccount } from "./account-alias";
|
|
58
58
|
import { CrashReportRequestSchema, reportCrashDump } from "./crash-report";
|
|
59
59
|
import {
|
|
60
|
+
migrateDiffrConfig,
|
|
60
61
|
readDiffrConfig,
|
|
61
62
|
saveDiffrSummarizer,
|
|
62
63
|
setDiffrConfigValue,
|
|
63
64
|
testDiffrSummarizer,
|
|
64
65
|
} from "./diffr-config";
|
|
65
|
-
import { installFullDiffr } from "./diffr-languages.js";
|
|
66
66
|
import {
|
|
67
67
|
GlobalReviewDesktopVerbRelay,
|
|
68
68
|
type ReviewDesktopVerbRelay,
|
|
@@ -397,18 +397,6 @@ export function createGlobalReviewServer(
|
|
|
397
397
|
|
|
398
398
|
return serverJson(200, { ok: true });
|
|
399
399
|
});
|
|
400
|
-
app.post("/diffr-languages/install", async () => {
|
|
401
|
-
try {
|
|
402
|
-
await installFullDiffr();
|
|
403
|
-
invalidateStructuralComparisons();
|
|
404
|
-
|
|
405
|
-
return serverJson(200, { installed: true });
|
|
406
|
-
} catch (error) {
|
|
407
|
-
return serverJson(503, {
|
|
408
|
-
error: error instanceof Error ? error.message : String(error),
|
|
409
|
-
});
|
|
410
|
-
}
|
|
411
|
-
});
|
|
412
400
|
|
|
413
401
|
app.get("/diffr-config", async () =>
|
|
414
402
|
serverJson(200, await readDiffrConfig()),
|
|
@@ -580,6 +568,7 @@ export function createGlobalReviewServer(
|
|
|
580
568
|
return urlForBoundPort();
|
|
581
569
|
},
|
|
582
570
|
listen: async () => {
|
|
571
|
+
await migrateDiffrConfig();
|
|
583
572
|
scratchpadEnabled = await readScratchpadEnabled();
|
|
584
573
|
boundPort = await listen(httpServer, input.port);
|
|
585
574
|
discovery.url = urlForBoundPort();
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
type ReviewDiffrConfig,
|
|
16
16
|
type ReviewDiffrProvider,
|
|
17
17
|
type ReviewDiffrSummarizerInput,
|
|
18
|
+
type StructuralRegion,
|
|
18
19
|
decodeStructuralDiffEvent,
|
|
19
20
|
reviewDiffrSummarizerInputSchema,
|
|
20
21
|
} from "@dev.fast/review-protocol";
|
|
@@ -36,29 +37,91 @@ function serialized<T>(operation: () => Promise<T>): Promise<T> {
|
|
|
36
37
|
return next;
|
|
37
38
|
}
|
|
38
39
|
|
|
40
|
+
type DiffrArguments =
|
|
41
|
+
| { command: "schema" }
|
|
42
|
+
| { command: "show"; reveal: boolean }
|
|
43
|
+
| { command: "set"; patch: JsonObject }
|
|
44
|
+
| { command: "migrate" }
|
|
45
|
+
| { command: "diff"; before: string; after: string };
|
|
46
|
+
|
|
47
|
+
function diffrArgv(args: DiffrArguments): string[] {
|
|
48
|
+
switch (args.command) {
|
|
49
|
+
case "schema":
|
|
50
|
+
return ["config", "schema"];
|
|
51
|
+
case "show":
|
|
52
|
+
return ["config", "show", "--json", ...(args.reveal ? ["--reveal"] : [])];
|
|
53
|
+
case "set":
|
|
54
|
+
return ["config", "set", "--stdin", "--json"];
|
|
55
|
+
case "migrate":
|
|
56
|
+
return ["config", "migrate", "--json"];
|
|
57
|
+
case "diff":
|
|
58
|
+
return [
|
|
59
|
+
"--no-index",
|
|
60
|
+
"--format",
|
|
61
|
+
"ndjson",
|
|
62
|
+
"--",
|
|
63
|
+
args.before,
|
|
64
|
+
args.after,
|
|
65
|
+
];
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
39
69
|
async function diffr(
|
|
40
|
-
args:
|
|
70
|
+
args: DiffrArguments,
|
|
41
71
|
rootPath?: string,
|
|
42
72
|
options: { signal?: AbortSignal; env?: NodeJS.ProcessEnv } = {},
|
|
43
73
|
): Promise<string> {
|
|
74
|
+
const executable = diffrExecutable();
|
|
75
|
+
|
|
44
76
|
try {
|
|
45
|
-
const
|
|
77
|
+
const operation = execFileAsync(executable, diffrArgv(args), {
|
|
46
78
|
cwd: rootPath,
|
|
47
79
|
maxBuffer: 16 * 1024 * 1024,
|
|
48
80
|
signal: options.signal ?? AbortSignal.timeout(30_000),
|
|
49
81
|
env: options.env ?? process.env,
|
|
50
82
|
});
|
|
51
83
|
|
|
84
|
+
if (args.command === "set")
|
|
85
|
+
operation.child.stdin?.end(JSON.stringify(args.patch));
|
|
86
|
+
const { stdout } = await operation;
|
|
87
|
+
|
|
52
88
|
return stdout;
|
|
53
89
|
} catch (error) {
|
|
54
|
-
// SAFETY: execFile
|
|
90
|
+
// SAFETY: execFile adds the exit or spawn code and stdout to its Error.
|
|
55
91
|
// Do not forward its message: it includes command arguments.
|
|
56
|
-
const failure = error as NodeJS.ErrnoException;
|
|
92
|
+
const failure = error as NodeJS.ErrnoException & { stdout?: string };
|
|
57
93
|
|
|
58
94
|
if (failure.code === "ENOENT") throw diffrMissingError();
|
|
59
95
|
|
|
60
96
|
if (failure.name === "AbortError")
|
|
61
97
|
throw new Error("diffr timed out or was cancelled.");
|
|
98
|
+
|
|
99
|
+
if (
|
|
100
|
+
(args.command === "set" || args.command === "migrate") &&
|
|
101
|
+
failure.stdout
|
|
102
|
+
) {
|
|
103
|
+
let details: JsonValue | undefined;
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
details = parseJsonText(failure.stdout);
|
|
107
|
+
} catch {
|
|
108
|
+
/* Do not echo raw output in parse errors. */
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (
|
|
112
|
+
isJsonObject(details) &&
|
|
113
|
+
isJsonObject(details.error) &&
|
|
114
|
+
isStringValue(details.error.message)
|
|
115
|
+
) {
|
|
116
|
+
const { message, repair_prompt: prompt } = details.error;
|
|
117
|
+
throw new Error(
|
|
118
|
+
isStringValue(prompt)
|
|
119
|
+
? `${message}\n\nCopy this as a prompt to your coding agent:\n${prompt}`
|
|
120
|
+
: message,
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
62
125
|
throw new Error(
|
|
63
126
|
"diffr could not complete the operation. Check its configuration and credentials.",
|
|
64
127
|
);
|
|
@@ -70,13 +133,7 @@ async function values(
|
|
|
70
133
|
reveal = false,
|
|
71
134
|
signal?: AbortSignal,
|
|
72
135
|
): Promise<JsonObject> {
|
|
73
|
-
return json(
|
|
74
|
-
await diffr(
|
|
75
|
-
["config", "show", "--json", ...(reveal ? ["--reveal"] : [])],
|
|
76
|
-
rootPath,
|
|
77
|
-
{ signal },
|
|
78
|
-
),
|
|
79
|
-
);
|
|
136
|
+
return json(await diffr({ command: "show", reveal }, rootPath, { signal }));
|
|
80
137
|
}
|
|
81
138
|
|
|
82
139
|
function json(output: string): JsonObject {
|
|
@@ -100,7 +157,7 @@ function valueAt(object: JsonObject, key: string): JsonValue | undefined {
|
|
|
100
157
|
return value;
|
|
101
158
|
}
|
|
102
159
|
|
|
103
|
-
const prefix = "plugins.bundled.summarize";
|
|
160
|
+
const prefix = "plugins.shape.bundled.summarize";
|
|
104
161
|
|
|
105
162
|
function savedProvider(config: JsonObject): string {
|
|
106
163
|
const value = valueAt(config, `${prefix}.provider`);
|
|
@@ -108,20 +165,16 @@ function savedProvider(config: JsonObject): string {
|
|
|
108
165
|
return isStringValue(value) ? value : "";
|
|
109
166
|
}
|
|
110
167
|
|
|
111
|
-
function savedEndpoint(config: JsonObject): string {
|
|
112
|
-
const value = valueAt(config, `${prefix}.endpoint`);
|
|
113
|
-
|
|
114
|
-
return isStringValue(value) ? value : "";
|
|
115
|
-
}
|
|
116
|
-
|
|
117
168
|
/** Whether a saved key belongs to a different destination than the draft's. */
|
|
118
169
|
function movesKey(
|
|
119
170
|
config: JsonObject,
|
|
120
171
|
draft: Pick<ReviewDiffrSummarizerInput, "provider" | "endpoint">,
|
|
121
172
|
): boolean {
|
|
173
|
+
const endpoint = valueAt(config, `${prefix}.endpoint`);
|
|
174
|
+
|
|
122
175
|
return (
|
|
123
176
|
draft.provider !== savedProvider(config) ||
|
|
124
|
-
draft.endpoint !==
|
|
177
|
+
draft.endpoint !== (isStringValue(endpoint) ? endpoint : "")
|
|
125
178
|
);
|
|
126
179
|
}
|
|
127
180
|
|
|
@@ -151,27 +204,35 @@ function strings(value: JsonValue | undefined): string[] {
|
|
|
151
204
|
}
|
|
152
205
|
|
|
153
206
|
/**
|
|
154
|
-
*
|
|
155
|
-
* the
|
|
156
|
-
* diffr cannot describe its schema: the rest of Settings still works.
|
|
207
|
+
* Defaults supplied by diffr's schema for tag hiding and summary controls.
|
|
208
|
+
* If the schema cannot be read, resolved settings still work.
|
|
157
209
|
*/
|
|
158
|
-
async function
|
|
210
|
+
async function configDefaults(
|
|
159
211
|
rootPath?: string,
|
|
160
|
-
): Promise<
|
|
212
|
+
): Promise<
|
|
213
|
+
Pick<ReviewDiffrConfig, "providers" | "defaultPrompt" | "defaultHiddenTags">
|
|
214
|
+
> {
|
|
161
215
|
let schema: JsonObject;
|
|
162
216
|
|
|
163
217
|
try {
|
|
164
|
-
schema = json(await diffr(
|
|
218
|
+
schema = json(await diffr({ command: "schema" }, rootPath));
|
|
165
219
|
} catch {
|
|
166
220
|
return {};
|
|
167
221
|
}
|
|
168
222
|
|
|
169
223
|
const options = valueAt(
|
|
170
224
|
schema,
|
|
171
|
-
"properties.plugins.properties.bundled.properties.summarize.properties",
|
|
225
|
+
"properties.plugins.properties.shape.properties.bundled.properties.summarize.properties",
|
|
172
226
|
);
|
|
173
227
|
|
|
174
|
-
|
|
228
|
+
const defaultHiddenTags = strings(
|
|
229
|
+
valueAt(
|
|
230
|
+
schema,
|
|
231
|
+
"properties.plugins.properties.classify.properties.bundled.properties.hide.default",
|
|
232
|
+
),
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
if (!isJsonObject(options)) return { defaultHiddenTags };
|
|
175
236
|
|
|
176
237
|
const option = (name: string) =>
|
|
177
238
|
isJsonObject(options[name]) ? options[name] : {};
|
|
@@ -200,6 +261,7 @@ async function summaryDefaults(
|
|
|
200
261
|
const prompt = option("system_prompt");
|
|
201
262
|
|
|
202
263
|
return {
|
|
264
|
+
defaultHiddenTags,
|
|
203
265
|
providers,
|
|
204
266
|
defaultPrompt: isStringValue(prompt.default) ? prompt.default : undefined,
|
|
205
267
|
};
|
|
@@ -207,7 +269,7 @@ async function summaryDefaults(
|
|
|
207
269
|
|
|
208
270
|
async function read(rootPath?: string): Promise<ReviewDiffrConfig> {
|
|
209
271
|
const config = await values(rootPath);
|
|
210
|
-
const defaults = await
|
|
272
|
+
const defaults = await configDefaults(rootPath);
|
|
211
273
|
const saved = valueAt(config, `${prefix}.api_key`);
|
|
212
274
|
|
|
213
275
|
const credentialSource =
|
|
@@ -217,16 +279,15 @@ async function read(rootPath?: string): Promise<ReviewDiffrConfig> {
|
|
|
217
279
|
? "environment"
|
|
218
280
|
: "missing";
|
|
219
281
|
|
|
220
|
-
const plugins = config.
|
|
282
|
+
const plugins = valueAt(config, "plugins.shape");
|
|
221
283
|
|
|
222
284
|
if (isJsonObject(plugins)) {
|
|
223
|
-
|
|
224
|
-
|
|
285
|
+
const entries = isJsonObject(plugins.bundled)
|
|
286
|
+
? [...Object.values(plugins), ...Object.values(plugins.bundled)]
|
|
287
|
+
: Object.values(plugins);
|
|
225
288
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
}
|
|
229
|
-
}
|
|
289
|
+
for (const plugin of entries)
|
|
290
|
+
if (isJsonObject(plugin)) delete plugin.api_key;
|
|
230
291
|
}
|
|
231
292
|
|
|
232
293
|
return { values: config, credentialSource, ...defaults };
|
|
@@ -236,35 +297,35 @@ export function readDiffrConfig(rootPath?: string): Promise<ReviewDiffrConfig> {
|
|
|
236
297
|
return serialized(() => read(rootPath));
|
|
237
298
|
}
|
|
238
299
|
|
|
239
|
-
export function
|
|
240
|
-
|
|
300
|
+
export async function migrateDiffrConfig(
|
|
301
|
+
signal?: AbortSignal,
|
|
302
|
+
): Promise<boolean> {
|
|
303
|
+
const result = json(
|
|
304
|
+
await diffr({ command: "migrate" }, undefined, { signal }),
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
if (result.changed === true) invalidateStructuralComparisons();
|
|
308
|
+
|
|
309
|
+
return result.changed === true;
|
|
241
310
|
}
|
|
242
311
|
|
|
243
312
|
async function writeSettings(
|
|
244
|
-
|
|
313
|
+
patch: JsonObject,
|
|
245
314
|
rootPath?: string,
|
|
246
315
|
): Promise<ReviewDiffrConfig> {
|
|
247
316
|
let changed = false;
|
|
248
317
|
let error: string | undefined;
|
|
249
|
-
let current = await values(rootPath);
|
|
250
318
|
|
|
251
319
|
try {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
["config", "set", key, diffrConfigValueText(value)],
|
|
256
|
-
rootPath,
|
|
257
|
-
);
|
|
258
|
-
changed = true;
|
|
259
|
-
// A write can clear others, such as a provider's endpoint and key.
|
|
260
|
-
current = await values(rootPath);
|
|
261
|
-
}
|
|
262
|
-
} catch {
|
|
263
|
-
error = changed
|
|
264
|
-
? "Some settings were saved before diffr rejected a change. Check the current values and try again."
|
|
265
|
-
: "diffr could not save the setting. Check its configuration and the entered value.";
|
|
266
|
-
} finally {
|
|
320
|
+
const result = json(await diffr({ command: "set", patch }, rootPath));
|
|
321
|
+
changed = result.changed === true;
|
|
322
|
+
|
|
267
323
|
if (changed) invalidateStructuralComparisons();
|
|
324
|
+
} catch (cause) {
|
|
325
|
+
error =
|
|
326
|
+
cause instanceof Error
|
|
327
|
+
? cause.message
|
|
328
|
+
: "diffr could not save the settings.";
|
|
268
329
|
}
|
|
269
330
|
|
|
270
331
|
return { ...(await read(rootPath)), changed, error };
|
|
@@ -275,13 +336,16 @@ export function setDiffrConfigValue(
|
|
|
275
336
|
value: JsonValue,
|
|
276
337
|
rootPath?: string,
|
|
277
338
|
): Promise<ReviewDiffrConfig> {
|
|
278
|
-
if (
|
|
279
|
-
!/^[A-Za-z0-9_][A-Za-z0-9_-]*(\.[A-Za-z0-9_][A-Za-z0-9_-]*)*$/.test(key)
|
|
280
|
-
) {
|
|
339
|
+
if (!/^[A-Za-z0-9_][A-Za-z0-9_-]*(\.[^.\r\n\0]+)*$/u.test(key)) {
|
|
281
340
|
throw new Error("Invalid diffr config key.");
|
|
282
341
|
}
|
|
283
342
|
|
|
284
|
-
|
|
343
|
+
const parts = key.split(".");
|
|
344
|
+
let patch: JsonObject = { [parts.pop()!]: value };
|
|
345
|
+
|
|
346
|
+
for (const part of parts.reverse()) patch = { [part]: patch };
|
|
347
|
+
|
|
348
|
+
return serialized(() => writeSettings(patch, rootPath));
|
|
285
349
|
}
|
|
286
350
|
|
|
287
351
|
export function saveDiffrSummarizer(
|
|
@@ -313,40 +377,24 @@ export function saveDiffrSummarizer(
|
|
|
313
377
|
};
|
|
314
378
|
}
|
|
315
379
|
|
|
316
|
-
const
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
entries.push([`${prefix}.provider`, draft.provider]);
|
|
380
|
+
const summarize: JsonObject = {
|
|
381
|
+
enabled: draft.enabled,
|
|
382
|
+
provider: draft.provider,
|
|
383
|
+
endpoint: draft.endpoint,
|
|
384
|
+
model: draft.model,
|
|
385
|
+
tests: draft.tests,
|
|
386
|
+
};
|
|
324
387
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
if (
|
|
328
|
-
draft.endpoint !== savedEndpoint(current.values) ||
|
|
329
|
-
(draft.endpoint && draft.provider !== savedProvider(current.values))
|
|
330
|
-
)
|
|
331
|
-
entries.push([`${prefix}.endpoint`, draft.endpoint]);
|
|
388
|
+
if (draft.apiKey) summarize.api_key = draft.apiKey;
|
|
389
|
+
else if (moving && savedKey) summarize.api_key = "";
|
|
332
390
|
|
|
333
|
-
if (draft.
|
|
391
|
+
if (draft.systemPrompt.trim()) summarize.system_prompt = draft.systemPrompt;
|
|
334
392
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
)
|
|
340
|
-
entries.push([`${prefix}.system_prompt`, draft.systemPrompt]);
|
|
341
|
-
|
|
342
|
-
entries.push(
|
|
343
|
-
[`${prefix}.model`, draft.model],
|
|
344
|
-
[`${prefix}.tests`, draft.tests],
|
|
393
|
+
return writeSettings(
|
|
394
|
+
// oxlint-disable-next-line anti-slop/no-shape-in-symbol-names -- Native diffr config key.
|
|
395
|
+
{ plugins: { shape: { bundled: { summarize } } } },
|
|
396
|
+
rootPath,
|
|
345
397
|
);
|
|
346
|
-
|
|
347
|
-
if (draft.enabled) entries.push([`${prefix}.enabled`, true]);
|
|
348
|
-
|
|
349
|
-
return writeSettings(entries, rootPath);
|
|
350
398
|
});
|
|
351
399
|
}
|
|
352
400
|
|
|
@@ -373,7 +421,7 @@ export async function testDiffrSummarizer(
|
|
|
373
421
|
throw new Error("The summarizer is not available in this configuration.");
|
|
374
422
|
|
|
375
423
|
const { provider, endpoint, model, systemPrompt } = parsed.data;
|
|
376
|
-
const providers = (await
|
|
424
|
+
const providers = (await configDefaults(rootPath)).providers ?? [];
|
|
377
425
|
|
|
378
426
|
const variables =
|
|
379
427
|
providers.find((known) => known.id === provider)?.keyVariables ?? [];
|
|
@@ -410,10 +458,13 @@ export async function testDiffrSummarizer(
|
|
|
410
458
|
await writeFile(
|
|
411
459
|
join(directory, "diffr", "config.toml"),
|
|
412
460
|
stringify({
|
|
413
|
-
version:
|
|
461
|
+
version: 2,
|
|
414
462
|
plugins: {
|
|
415
|
-
|
|
416
|
-
|
|
463
|
+
// oxlint-disable-next-line anti-slop/no-shape-in-symbol-names -- Native diffr v2 config key.
|
|
464
|
+
shape: {
|
|
465
|
+
order: ["bundled.summarize"],
|
|
466
|
+
bundled: { summarize: options },
|
|
467
|
+
},
|
|
417
468
|
},
|
|
418
469
|
}),
|
|
419
470
|
{ mode: 0o600 },
|
|
@@ -425,56 +476,47 @@ export async function testDiffrSummarizer(
|
|
|
425
476
|
await writeFile(before, "");
|
|
426
477
|
await writeFile(after, SUMMARY_SAMPLE);
|
|
427
478
|
|
|
428
|
-
const output = await diffr(
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
directory,
|
|
439
|
-
{
|
|
440
|
-
env: {
|
|
441
|
-
...process.env,
|
|
442
|
-
...Object.fromEntries(
|
|
443
|
-
providers
|
|
444
|
-
.flatMap((known) => known.keyVariables)
|
|
445
|
-
.map((name) => [name, ""]),
|
|
446
|
-
),
|
|
447
|
-
...(apiKey && variables[0] && { [variables[0]]: apiKey }),
|
|
448
|
-
XDG_CONFIG_HOME: directory,
|
|
449
|
-
},
|
|
450
|
-
signal: deadline,
|
|
479
|
+
const output = await diffr({ command: "diff", before, after }, directory, {
|
|
480
|
+
env: {
|
|
481
|
+
...process.env,
|
|
482
|
+
...Object.fromEntries(
|
|
483
|
+
providers
|
|
484
|
+
.flatMap((known) => known.keyVariables)
|
|
485
|
+
.map((name) => [name, ""]),
|
|
486
|
+
),
|
|
487
|
+
...(apiKey && variables[0] && { [variables[0]]: apiKey }),
|
|
488
|
+
XDG_CONFIG_HOME: directory,
|
|
451
489
|
},
|
|
452
|
-
|
|
490
|
+
signal: deadline,
|
|
491
|
+
});
|
|
453
492
|
|
|
454
493
|
try {
|
|
455
494
|
const events = output.trim().split("\n").map(decodeStructuralDiffEvent);
|
|
456
495
|
const complete = events.at(-1);
|
|
457
496
|
|
|
458
|
-
const
|
|
497
|
+
const labels = (region: StructuralRegion): string[] => [
|
|
498
|
+
...(region.visibility?.label ? [region.visibility.label] : []),
|
|
499
|
+
...(region.kind === "fold" ? region.children.flatMap(labels) : []),
|
|
500
|
+
];
|
|
501
|
+
|
|
502
|
+
const summary = events
|
|
459
503
|
.flatMap((event) =>
|
|
460
|
-
event.type === "
|
|
504
|
+
event.type === "file" && event.diff?.type === "text"
|
|
505
|
+
? [event.diff.rhs, event.diff.lhs].flatMap((source) =>
|
|
506
|
+
source ? labels(source.root) : [],
|
|
507
|
+
)
|
|
508
|
+
: [],
|
|
461
509
|
)
|
|
462
|
-
.find((
|
|
510
|
+
.find((label) => label.trim());
|
|
463
511
|
|
|
464
512
|
if (
|
|
465
513
|
complete?.type === "complete" &&
|
|
466
514
|
complete.failed === 0 &&
|
|
467
515
|
!complete.aborted &&
|
|
468
|
-
!events.some(
|
|
469
|
-
|
|
470
|
-
(event.type === "annotations" || event.type === "file") &&
|
|
471
|
-
event.error,
|
|
472
|
-
) &&
|
|
473
|
-
annotation
|
|
516
|
+
!events.some((event) => event.type === "file" && event.error) &&
|
|
517
|
+
summary
|
|
474
518
|
) {
|
|
475
|
-
return apiKey
|
|
476
|
-
? annotation.label.split(apiKey).join("[redacted]")
|
|
477
|
-
: annotation.label;
|
|
519
|
+
return apiKey ? summary.split(apiKey).join("[redacted]") : summary;
|
|
478
520
|
}
|
|
479
521
|
} catch {
|
|
480
522
|
/* Never return provider output or parser causes. */
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
reviewServerDiscoveryPath,
|
|
13
13
|
} from "@review/server-discovery.js";
|
|
14
14
|
|
|
15
|
+
import { migrateDiffrConfig } from "./diffr-config.js";
|
|
15
16
|
import { GlobalReviewDesktopVerbRelay } from "./global-verb-relay.js";
|
|
16
17
|
import { createNodeRequestListener } from "./hono-http.js";
|
|
17
18
|
import {
|
|
@@ -72,6 +73,8 @@ export function withHeadlessServerLock<T>(
|
|
|
72
73
|
async function serve(input: HeadlessServerInput) {
|
|
73
74
|
if (input.signal.aborted) return;
|
|
74
75
|
|
|
76
|
+
await migrateDiffrConfig(input.signal);
|
|
77
|
+
|
|
75
78
|
if (input.telemetry) await drainServerCrashReport(input.telemetry);
|
|
76
79
|
|
|
77
80
|
const local = await openReviewProfile(input.stateDir, {
|
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
-
import { existsSync } from "node:fs";
|
|
3
|
-
import path from "node:path";
|
|
4
2
|
import { createInterface } from "node:readline";
|
|
5
3
|
|
|
4
|
+
import { diffrBinaryPath } from "@dev.fast/diffr";
|
|
6
5
|
import {
|
|
7
6
|
STRUCTURAL_DIFF_WIRE_VERSION,
|
|
8
7
|
type StructuralDiffEvent,
|
|
9
8
|
type StructuralProblem,
|
|
10
9
|
decodeStructuralDiffEvent,
|
|
11
10
|
} from "@dev.fast/review-protocol";
|
|
12
|
-
import { findReviewPackageRoot } from "@review/package-paths";
|
|
13
|
-
|
|
14
|
-
import { installedFullDiffr } from "./diffr-languages.js";
|
|
15
11
|
|
|
16
12
|
export type DiffComparison =
|
|
17
13
|
| { kind: "trees"; base: string; head: string }
|
|
@@ -26,27 +22,17 @@ export interface StructuralDiffRequest {
|
|
|
26
22
|
signal: AbortSignal;
|
|
27
23
|
}
|
|
28
24
|
|
|
29
|
-
export function diffrExecutable(
|
|
30
|
-
|
|
31
|
-
): string {
|
|
32
|
-
if (process.env.REVIEW_DIFFR_BINARY) return process.env.REVIEW_DIFFR_BINARY;
|
|
33
|
-
|
|
34
|
-
const full = installedFullDiffr(packageRoot);
|
|
25
|
+
export function diffrExecutable(): string {
|
|
26
|
+
const executable = diffrBinaryPath();
|
|
35
27
|
|
|
36
|
-
if (
|
|
28
|
+
if (!executable) throw diffrMissingError();
|
|
37
29
|
|
|
38
|
-
|
|
39
|
-
packageRoot,
|
|
40
|
-
"bin",
|
|
41
|
-
process.platform === "win32" ? "diffr.exe" : "diffr",
|
|
42
|
-
);
|
|
43
|
-
|
|
44
|
-
return existsSync(bundled) ? bundled : "diffr";
|
|
30
|
+
return executable;
|
|
45
31
|
}
|
|
46
32
|
|
|
47
33
|
export function diffrMissingError(): Error {
|
|
48
34
|
return new Error(
|
|
49
|
-
|
|
35
|
+
"The bundled diffr executable is missing. Reinstall Whiteboard.",
|
|
50
36
|
);
|
|
51
37
|
}
|
|
52
38
|
|
|
@@ -59,15 +45,10 @@ export async function* structuralDiff(
|
|
|
59
45
|
input: StructuralDiffRequest,
|
|
60
46
|
): AsyncGenerator<StructuralDiffEvent> {
|
|
61
47
|
input.signal.throwIfAborted();
|
|
48
|
+
const executable = diffrExecutable();
|
|
62
49
|
const comparison = input.comparison;
|
|
63
50
|
|
|
64
|
-
const args = [
|
|
65
|
-
"--repo",
|
|
66
|
-
input.repositoryPath,
|
|
67
|
-
"--format",
|
|
68
|
-
"ndjson",
|
|
69
|
-
"--stream-annotations",
|
|
70
|
-
];
|
|
51
|
+
const args = ["--repo", input.repositoryPath, "--format", "ndjson"];
|
|
71
52
|
|
|
72
53
|
switch (comparison.kind) {
|
|
73
54
|
case "worktree":
|
|
@@ -88,11 +69,9 @@ export async function* structuralDiff(
|
|
|
88
69
|
|
|
89
70
|
// The host inherits its own environment and runs from the repository, so
|
|
90
71
|
// diffr reads the user's config and keys exactly as it would from a shell.
|
|
91
|
-
console.info(
|
|
92
|
-
`[Review] structural diff: ${diffrExecutable()} ${args.join(" ")}`,
|
|
93
|
-
);
|
|
72
|
+
console.info(`[Review] structural diff: ${executable} ${args.join(" ")}`);
|
|
94
73
|
|
|
95
|
-
const child = spawn(
|
|
74
|
+
const child = spawn(executable, args, {
|
|
96
75
|
cwd: input.repositoryPath,
|
|
97
76
|
stdio: ["ignore", "pipe", "pipe"],
|
|
98
77
|
signal,
|
|
@@ -120,7 +99,6 @@ export async function* structuralDiff(
|
|
|
120
99
|
let completed = false;
|
|
121
100
|
let aborted: StructuralProblem | undefined;
|
|
122
101
|
let failed = 0;
|
|
123
|
-
let annotationFailed = false;
|
|
124
102
|
const lines = createInterface({ input: child.stdout, crlfDelay: Infinity });
|
|
125
103
|
|
|
126
104
|
try {
|
|
@@ -152,8 +130,6 @@ export async function* structuralDiff(
|
|
|
152
130
|
|
|
153
131
|
failed = event.failed;
|
|
154
132
|
aborted = event.aborted;
|
|
155
|
-
} else if (event.type === "annotations") {
|
|
156
|
-
annotationFailed ||= event.error !== undefined;
|
|
157
133
|
} else if (event.type !== "file") {
|
|
158
134
|
throw new Error(`Unexpected diffr event: ${event.type}`);
|
|
159
135
|
}
|
|
@@ -168,13 +144,10 @@ export async function* structuralDiff(
|
|
|
168
144
|
throw new Error("diffr stream ended before completion.");
|
|
169
145
|
}
|
|
170
146
|
|
|
171
|
-
// diffr exits 2 for file
|
|
147
|
+
// diffr exits 2 for file failures already reported by the stream.
|
|
172
148
|
const code = await exited;
|
|
173
149
|
|
|
174
|
-
if (
|
|
175
|
-
code !== 0 &&
|
|
176
|
-
!(code === 2 && (failed > 0 || annotationFailed || aborted !== undefined))
|
|
177
|
-
)
|
|
150
|
+
if (code !== 0 && !(code === 2 && (failed > 0 || aborted !== undefined)))
|
|
178
151
|
throw exitError(code);
|
|
179
152
|
} finally {
|
|
180
153
|
clearTimeout(idle);
|