@linzumi/cli 1.0.116 → 1.0.117
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 +1 -1
- package/dist/index.js +359 -359
- package/package.json +1 -1
- package/scripts/build-onboarding-demo-evidence.mjs +186 -0
- package/scripts/build-onboarding-discovery-parity-oracle.mjs +380 -0
- package/scripts/build-onboarding-flow-parity-oracle.mjs +562 -0
- package/scripts/build-onboarding-gateway-parity-oracle.mjs +313 -0
- package/scripts/build-onboarding-support-parity-oracle.mjs +484 -0
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
// Spec: kandan/plans/2026-07-10-compute-nodes-rescript-commander-zero-ts-program.md
|
|
2
|
+
// (M3 cluster 13, onboarding - the SUPPORT slice: the commander-side
|
|
3
|
+
// starter-task suggestion engine and the hello-demo project materializer).
|
|
4
|
+
// Relationship: Builds the TS-side PARITY ORACLE for the onboarding
|
|
5
|
+
// support modules of the ReScript commander port, the sibling of
|
|
6
|
+
// build-worker-entry-parity-oracle.mjs (cluster 12). The oracle bundles
|
|
7
|
+
// the REAL signupTaskSuggestions.ts (suggestSignupTasksWithCodex /
|
|
8
|
+
// signupTaskSuggestionProcessForTest + the tagged oracle exports of the
|
|
9
|
+
// model/prompt/schema/parse internals), signupTaskSuggestionsProgress.ts
|
|
10
|
+
// (progressLabelForCodexEvent / createTaskSuggestionProgressParser), and
|
|
11
|
+
// helloLinzumiProject.ts (createHelloLinzumiProject + the default
|
|
12
|
+
// constants) behind the cluster 1/2 one-shot BATCH driver (a JSON array
|
|
13
|
+
// of cases on stdin, a JSON array of results on stdout).
|
|
14
|
+
//
|
|
15
|
+
// Determinism + normalization: fs cases run in realpath'd scratch dirs (or
|
|
16
|
+
// a caller-provided externalRoot for the hello byte-tree law, so both
|
|
17
|
+
// impls can build at the SAME root and compare without normalization);
|
|
18
|
+
// paths normalize through the "$DIR" placeholder and the random
|
|
19
|
+
// linzumi-signup-codex-tasks-* scratch roots through the "$TASKS"
|
|
20
|
+
// placeholder. The suggestion flow's codex binary is a case-scripted
|
|
21
|
+
// executable planted from the case tree, so both impls drive identical
|
|
22
|
+
// bytes.
|
|
23
|
+
//
|
|
24
|
+
// helloLinzumiProject.ts reads its logo asset relative to the bundled
|
|
25
|
+
// module, so the build copies src/assets/linzumi-logo.svg next to the
|
|
26
|
+
// bundle (.differential/assets/).
|
|
27
|
+
//
|
|
28
|
+
// SECRETS: the oracle only ever sees the conformance suite's synthetic
|
|
29
|
+
// trees and synthetic credentials. The output is a local test artifact
|
|
30
|
+
// (.differential/ is gitignored, never published) and stays unminified
|
|
31
|
+
// for debuggability.
|
|
32
|
+
import { copyFile, mkdir } from 'node:fs/promises';
|
|
33
|
+
import { dirname, join } from 'node:path';
|
|
34
|
+
import { fileURLToPath } from 'node:url';
|
|
35
|
+
import { build } from 'esbuild';
|
|
36
|
+
|
|
37
|
+
const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
38
|
+
|
|
39
|
+
const driverSource = `
|
|
40
|
+
import {
|
|
41
|
+
chmodSync,
|
|
42
|
+
existsSync,
|
|
43
|
+
mkdirSync,
|
|
44
|
+
mkdtempSync,
|
|
45
|
+
readdirSync,
|
|
46
|
+
readFileSync,
|
|
47
|
+
realpathSync,
|
|
48
|
+
rmSync,
|
|
49
|
+
statSync,
|
|
50
|
+
writeFileSync,
|
|
51
|
+
} from 'node:fs';
|
|
52
|
+
import { tmpdir } from 'node:os';
|
|
53
|
+
import { dirname as pathDirname, join, relative } from 'node:path';
|
|
54
|
+
import {
|
|
55
|
+
parseTaskSuggestionResponseForTest,
|
|
56
|
+
resolveTaskSuggestionModelForTest,
|
|
57
|
+
signupTaskSuggestionProcessForTest,
|
|
58
|
+
suggestSignupTasksWithCodex,
|
|
59
|
+
taskSuggestionJsonSchemaForTest,
|
|
60
|
+
taskSuggestionPromptForTest,
|
|
61
|
+
} from './src/signupTaskSuggestions';
|
|
62
|
+
import {
|
|
63
|
+
createTaskSuggestionProgressParser,
|
|
64
|
+
progressLabelForCodexEvent,
|
|
65
|
+
} from './src/signupTaskSuggestionsProgress';
|
|
66
|
+
import {
|
|
67
|
+
createHelloLinzumiProject,
|
|
68
|
+
defaultHelloLinzumiHost,
|
|
69
|
+
defaultHelloLinzumiParentDir,
|
|
70
|
+
defaultHelloLinzumiPort,
|
|
71
|
+
defaultHelloLinzumiProjectDir,
|
|
72
|
+
defaultHelloLinzumiProjectName,
|
|
73
|
+
} from './src/helloLinzumiProject';
|
|
74
|
+
|
|
75
|
+
// --- Placeholder + snapshot plumbing --------------------------------------------
|
|
76
|
+
|
|
77
|
+
const dirPlaceholder = '$DIR';
|
|
78
|
+
|
|
79
|
+
function scratch() {
|
|
80
|
+
// realpath so macOS /var -> /private/var never splits the two impls'
|
|
81
|
+
// placeholder substitution.
|
|
82
|
+
return realpathSync(mkdtempSync(join(tmpdir(), 'linzumi-onboarding-oracle-')));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function substitute(value, from, to) {
|
|
86
|
+
if (typeof value === 'string') {
|
|
87
|
+
return value.split(from).join(to);
|
|
88
|
+
}
|
|
89
|
+
if (Array.isArray(value)) {
|
|
90
|
+
return value.map((entry) => substitute(entry, from, to));
|
|
91
|
+
}
|
|
92
|
+
if (typeof value === 'object' && value !== null) {
|
|
93
|
+
return Object.fromEntries(
|
|
94
|
+
Object.entries(value).map(([key, entry]) => [
|
|
95
|
+
substitute(key, from, to),
|
|
96
|
+
substitute(entry, from, to),
|
|
97
|
+
])
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
return value;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// The suggestion flow's per-attempt scratch roots are random mkdtemp dirs
|
|
104
|
+
// under the OS tmpdir; normalize the WHOLE path through one placeholder.
|
|
105
|
+
function normalizeTasksTemp(value) {
|
|
106
|
+
if (typeof value === 'string') {
|
|
107
|
+
return value.replace(/\\S*linzumi-signup-codex-tasks-[^/"'\\s]+/g, '$TASKS');
|
|
108
|
+
}
|
|
109
|
+
if (Array.isArray(value)) {
|
|
110
|
+
return value.map((entry) => normalizeTasksTemp(entry));
|
|
111
|
+
}
|
|
112
|
+
if (typeof value === 'object' && value !== null) {
|
|
113
|
+
return Object.fromEntries(
|
|
114
|
+
Object.entries(value).map(([key, entry]) => [
|
|
115
|
+
normalizeTasksTemp(key),
|
|
116
|
+
normalizeTasksTemp(entry),
|
|
117
|
+
])
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
return value;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function buildTree(root, files) {
|
|
124
|
+
for (const [relPath, spec] of Object.entries(files ?? {})) {
|
|
125
|
+
const full = join(root, relPath);
|
|
126
|
+
if (relPath.endsWith('/')) {
|
|
127
|
+
mkdirSync(full, { recursive: true });
|
|
128
|
+
} else {
|
|
129
|
+
mkdirSync(pathDirname(full), { recursive: true });
|
|
130
|
+
if (spec !== null && typeof spec === 'object') {
|
|
131
|
+
writeFileSync(full, String(spec.content), 'utf8');
|
|
132
|
+
if (spec.mode !== undefined) {
|
|
133
|
+
chmodSync(full, spec.mode);
|
|
134
|
+
}
|
|
135
|
+
} else {
|
|
136
|
+
writeFileSync(full, String(spec), 'utf8');
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function snapshotFiles(dir) {
|
|
143
|
+
const files = {};
|
|
144
|
+
const walk = (current) => {
|
|
145
|
+
let names;
|
|
146
|
+
try {
|
|
147
|
+
names = readdirSync(current).sort();
|
|
148
|
+
} catch {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
for (const name of names) {
|
|
152
|
+
const full = join(current, name);
|
|
153
|
+
const stats = statSync(full);
|
|
154
|
+
if (stats.isDirectory()) {
|
|
155
|
+
files[relative(dir, full) + '/'] = '<dir>';
|
|
156
|
+
walk(full);
|
|
157
|
+
} else {
|
|
158
|
+
files[relative(dir, full)] = {
|
|
159
|
+
content: readFileSync(full, 'utf8'),
|
|
160
|
+
mode: stats.mode & 0o777,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
walk(dir);
|
|
166
|
+
return files;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Env manipulation is per-case and restored afterwards (the batch runs
|
|
170
|
+
// sequentially in one process).
|
|
171
|
+
function withEnv(overrides, run) {
|
|
172
|
+
const saved = {};
|
|
173
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
174
|
+
saved[key] = process.env[key];
|
|
175
|
+
if (value === null) {
|
|
176
|
+
delete process.env[key];
|
|
177
|
+
} else {
|
|
178
|
+
process.env[key] = value;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
const restore = () => {
|
|
182
|
+
for (const [key, value] of Object.entries(saved)) {
|
|
183
|
+
if (value === undefined) {
|
|
184
|
+
delete process.env[key];
|
|
185
|
+
} else {
|
|
186
|
+
process.env[key] = value;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
return { run, restore };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async function runWithEnv(overrides, run) {
|
|
194
|
+
const { restore } = withEnv(overrides, run);
|
|
195
|
+
try {
|
|
196
|
+
return await run();
|
|
197
|
+
} finally {
|
|
198
|
+
restore();
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// --- Suggestion transforms ---------------------------------------------------------
|
|
203
|
+
|
|
204
|
+
function handleResolveModel(testCase) {
|
|
205
|
+
return runWithEnv(
|
|
206
|
+
{ LINZUMI_SIGNUP_TASK_MODEL: testCase.envModel ?? null },
|
|
207
|
+
() => ({
|
|
208
|
+
value: resolveTaskSuggestionModelForTest(
|
|
209
|
+
testCase.explicitModel ?? undefined,
|
|
210
|
+
testCase.wafer === true
|
|
211
|
+
? {
|
|
212
|
+
config: {
|
|
213
|
+
id: 'linzumi',
|
|
214
|
+
name: 'Linzumi',
|
|
215
|
+
baseUrl: 'https://example.invalid/llm',
|
|
216
|
+
envKey: 'LINZUMI_LLM_PROXY_TOKEN',
|
|
217
|
+
wireApi: 'responses',
|
|
218
|
+
},
|
|
219
|
+
env: {},
|
|
220
|
+
}
|
|
221
|
+
: undefined
|
|
222
|
+
),
|
|
223
|
+
})
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function providerOfCase(spec) {
|
|
228
|
+
if (spec === undefined || spec === null) {
|
|
229
|
+
return undefined;
|
|
230
|
+
}
|
|
231
|
+
return {
|
|
232
|
+
config: {
|
|
233
|
+
id: spec.config.id,
|
|
234
|
+
name: spec.config.name,
|
|
235
|
+
baseUrl: spec.config.baseUrl,
|
|
236
|
+
envKey: spec.config.envKey,
|
|
237
|
+
wireApi: spec.config.wireApi,
|
|
238
|
+
...(spec.config.httpHeaders === undefined ? {} : { httpHeaders: spec.config.httpHeaders }),
|
|
239
|
+
...(spec.config.envHttpHeaders === undefined
|
|
240
|
+
? {}
|
|
241
|
+
: { envHttpHeaders: spec.config.envHttpHeaders }),
|
|
242
|
+
},
|
|
243
|
+
env: spec.env ?? {},
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function handleBuildProcess(testCase) {
|
|
248
|
+
const built = signupTaskSuggestionProcessForTest({
|
|
249
|
+
command: testCase.command,
|
|
250
|
+
model: testCase.model,
|
|
251
|
+
...(testCase.modelProvider === undefined
|
|
252
|
+
? {}
|
|
253
|
+
: { modelProvider: providerOfCase(testCase.modelProvider) }),
|
|
254
|
+
projectPath: testCase.projectPath,
|
|
255
|
+
schemaPath: testCase.schemaPath,
|
|
256
|
+
outputPath: testCase.outputPath,
|
|
257
|
+
prompt: testCase.prompt,
|
|
258
|
+
...(testCase.timeoutMs === undefined ? {} : { timeoutMs: testCase.timeoutMs }),
|
|
259
|
+
codexHome: testCase.codexHome,
|
|
260
|
+
...(testCase.forwardableOpenAiApiKey === undefined
|
|
261
|
+
? {}
|
|
262
|
+
: { forwardableOpenAiApiKey: testCase.forwardableOpenAiApiKey }),
|
|
263
|
+
});
|
|
264
|
+
return {
|
|
265
|
+
value: {
|
|
266
|
+
command: built.command,
|
|
267
|
+
args: [...built.args],
|
|
268
|
+
cwd: built.cwd,
|
|
269
|
+
stdin: built.stdin,
|
|
270
|
+
env: built.env ?? null,
|
|
271
|
+
timeoutMs: built.timeoutMs,
|
|
272
|
+
},
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function handleProgressChunks(testCase) {
|
|
277
|
+
const labels = [];
|
|
278
|
+
const throwOn = new Set(testCase.throwOnLabels ?? []);
|
|
279
|
+
const parser = createTaskSuggestionProgressParser((label) => {
|
|
280
|
+
labels.push(label);
|
|
281
|
+
if (throwOn.has(label)) {
|
|
282
|
+
throw new Error('scripted consumer failure');
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
for (const chunk of testCase.chunks ?? []) {
|
|
286
|
+
parser.push(chunk);
|
|
287
|
+
}
|
|
288
|
+
if (testCase.flush !== false) {
|
|
289
|
+
parser.flush();
|
|
290
|
+
}
|
|
291
|
+
return { labels };
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// --- The suggestion flow -------------------------------------------------------------
|
|
295
|
+
|
|
296
|
+
async function handleSuggestFlow(testCase) {
|
|
297
|
+
const dir = scratch();
|
|
298
|
+
const sub = (value) => substitute(value, dirPlaceholder, dir);
|
|
299
|
+
buildTree(dir, sub(testCase.files ?? {}));
|
|
300
|
+
|
|
301
|
+
const labels = [];
|
|
302
|
+
const onProgress =
|
|
303
|
+
testCase.withProgress === true ? (label) => labels.push(label) : undefined;
|
|
304
|
+
|
|
305
|
+
const outcome = await runWithEnv(
|
|
306
|
+
{
|
|
307
|
+
LINZUMI_CODEX_HOME: join(dir, 'isolated-home'),
|
|
308
|
+
CODEX_HOME: join(dir, 'user-codex'),
|
|
309
|
+
OPENAI_API_KEY: null,
|
|
310
|
+
LINZUMI_SIGNUP_TASK_MODEL: null,
|
|
311
|
+
},
|
|
312
|
+
async () => {
|
|
313
|
+
try {
|
|
314
|
+
const tasks = await suggestSignupTasksWithCodex({
|
|
315
|
+
projectPath: join(dir, testCase.projectRelDir ?? 'project'),
|
|
316
|
+
codexBin: sub(testCase.codexBin),
|
|
317
|
+
...(testCase.model === undefined ? {} : { model: testCase.model }),
|
|
318
|
+
...(testCase.modelProvider === undefined
|
|
319
|
+
? {}
|
|
320
|
+
: { modelProvider: providerOfCase(sub(testCase.modelProvider)) }),
|
|
321
|
+
...(onProgress === undefined ? {} : { onProgress }),
|
|
322
|
+
});
|
|
323
|
+
return { ok: true, tasks };
|
|
324
|
+
} catch (error) {
|
|
325
|
+
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
// Give any straggling stdout events a beat before reading labels (the
|
|
331
|
+
// scripted binary sleeps before exiting, so labels are ordinarily
|
|
332
|
+
// delivered pre-exit in both impls).
|
|
333
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
334
|
+
|
|
335
|
+
const invocationsPath = join(dir, 'invocations.jsonl');
|
|
336
|
+
const invocations = existsSync(invocationsPath)
|
|
337
|
+
? readFileSync(invocationsPath, 'utf8')
|
|
338
|
+
.split('\\n')
|
|
339
|
+
.filter((line) => line !== '')
|
|
340
|
+
.map((line) => JSON.parse(line))
|
|
341
|
+
: [];
|
|
342
|
+
|
|
343
|
+
// The per-attempt scratch roots must be gone after the flow (the TS
|
|
344
|
+
// finally); derive them from the recorded argv.
|
|
345
|
+
const tempRoots = new Set();
|
|
346
|
+
for (const invocation of invocations) {
|
|
347
|
+
const index = invocation.argv.indexOf('--output-schema');
|
|
348
|
+
if (index !== -1) {
|
|
349
|
+
tempRoots.add(pathDirname(invocation.argv[index + 1]));
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
const tempRootsGone = [...tempRoots].every((root) => !existsSync(root));
|
|
353
|
+
|
|
354
|
+
const result = {
|
|
355
|
+
outcome,
|
|
356
|
+
labels,
|
|
357
|
+
invocations,
|
|
358
|
+
isolatedHome: snapshotFiles(join(dir, 'isolated-home')),
|
|
359
|
+
tempRootsGone,
|
|
360
|
+
};
|
|
361
|
+
return normalizeTasksTemp(substitute(result, dir, dirPlaceholder));
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// --- The hello-demo project ----------------------------------------------------------
|
|
365
|
+
|
|
366
|
+
function handleHelloCreate(testCase) {
|
|
367
|
+
const externalRoot = testCase.externalRoot;
|
|
368
|
+
const dir = externalRoot ?? scratch();
|
|
369
|
+
const sub = (value) => substitute(value, dirPlaceholder, dir);
|
|
370
|
+
buildTree(dir, sub(testCase.files ?? {}));
|
|
371
|
+
|
|
372
|
+
let result;
|
|
373
|
+
try {
|
|
374
|
+
const input = sub(testCase.input);
|
|
375
|
+
const project = createHelloLinzumiProject(
|
|
376
|
+
typeof input === 'string'
|
|
377
|
+
? input
|
|
378
|
+
: {
|
|
379
|
+
...(input.rootPath === undefined ? {} : { rootPath: input.rootPath }),
|
|
380
|
+
...(input.parentDir === undefined ? {} : { parentDir: input.parentDir }),
|
|
381
|
+
...(input.name === undefined ? {} : { name: input.name }),
|
|
382
|
+
...(input.port === undefined ? {} : { port: input.port }),
|
|
383
|
+
...(input.host === undefined ? {} : { host: input.host }),
|
|
384
|
+
...(input.reset === undefined ? {} : { reset: input.reset }),
|
|
385
|
+
}
|
|
386
|
+
);
|
|
387
|
+
result = { ok: project };
|
|
388
|
+
} catch (error) {
|
|
389
|
+
result = { threw: error instanceof Error ? error.message : String(error) };
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return substitute(
|
|
393
|
+
{
|
|
394
|
+
result,
|
|
395
|
+
...(externalRoot === undefined ? { tree: snapshotFiles(dir) } : {}),
|
|
396
|
+
},
|
|
397
|
+
dir,
|
|
398
|
+
dirPlaceholder
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// --- Dispatch -----------------------------------------------------------------------
|
|
403
|
+
|
|
404
|
+
async function handle(testCase) {
|
|
405
|
+
switch (testCase.op) {
|
|
406
|
+
case 'resolveModel':
|
|
407
|
+
return handleResolveModel(testCase);
|
|
408
|
+
case 'prompt':
|
|
409
|
+
return { value: taskSuggestionPromptForTest(testCase.previousResponse ?? undefined) };
|
|
410
|
+
case 'schemaFile':
|
|
411
|
+
return {
|
|
412
|
+
value: JSON.stringify(taskSuggestionJsonSchemaForTest(), null, 2) + '\\n',
|
|
413
|
+
};
|
|
414
|
+
case 'parseResponse': {
|
|
415
|
+
const parsed = parseTaskSuggestionResponseForTest(testCase.response);
|
|
416
|
+
return { value: parsed === undefined ? null : parsed };
|
|
417
|
+
}
|
|
418
|
+
case 'buildProcess':
|
|
419
|
+
return handleBuildProcess(testCase);
|
|
420
|
+
case 'progressEvent': {
|
|
421
|
+
const label = progressLabelForCodexEvent(testCase.event);
|
|
422
|
+
return { value: label === undefined ? null : label };
|
|
423
|
+
}
|
|
424
|
+
case 'progressChunks':
|
|
425
|
+
return handleProgressChunks(testCase);
|
|
426
|
+
case 'suggestFlow':
|
|
427
|
+
return handleSuggestFlow(testCase);
|
|
428
|
+
case 'helloCreate':
|
|
429
|
+
return handleHelloCreate(testCase);
|
|
430
|
+
case 'helloDefaults':
|
|
431
|
+
return {
|
|
432
|
+
value: {
|
|
433
|
+
projectDir: defaultHelloLinzumiProjectDir,
|
|
434
|
+
projectName: defaultHelloLinzumiProjectName,
|
|
435
|
+
parentDir: defaultHelloLinzumiParentDir,
|
|
436
|
+
port: defaultHelloLinzumiPort,
|
|
437
|
+
host: defaultHelloLinzumiHost,
|
|
438
|
+
},
|
|
439
|
+
};
|
|
440
|
+
default:
|
|
441
|
+
return { threw: true, message: 'unknown op ' + String(testCase.op) };
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
let stdin = '';
|
|
446
|
+
process.stdin.setEncoding('utf8');
|
|
447
|
+
for await (const chunk of process.stdin) {
|
|
448
|
+
stdin += chunk;
|
|
449
|
+
}
|
|
450
|
+
const cases = JSON.parse(stdin);
|
|
451
|
+
const results = [];
|
|
452
|
+
for (const testCase of cases) {
|
|
453
|
+
results.push(await handle(testCase));
|
|
454
|
+
}
|
|
455
|
+
process.stdout.write(JSON.stringify(results));
|
|
456
|
+
`;
|
|
457
|
+
|
|
458
|
+
await mkdir(join(packageRoot, '.differential'), { recursive: true });
|
|
459
|
+
|
|
460
|
+
await build({
|
|
461
|
+
stdin: {
|
|
462
|
+
contents: driverSource,
|
|
463
|
+
resolveDir: packageRoot,
|
|
464
|
+
sourcefile: 'onboarding-support-parity-driver.ts',
|
|
465
|
+
loader: 'ts',
|
|
466
|
+
},
|
|
467
|
+
bundle: true,
|
|
468
|
+
platform: 'node',
|
|
469
|
+
target: 'node20',
|
|
470
|
+
format: 'esm',
|
|
471
|
+
outfile: join(packageRoot, '.differential/onboarding-support-parity-oracle.mjs'),
|
|
472
|
+
minify: false,
|
|
473
|
+
sourcemap: false,
|
|
474
|
+
legalComments: 'none',
|
|
475
|
+
external: ['ws', 'undici', 'blessed', '@anthropic-ai/claude-agent-sdk'],
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
// helloLinzumiProject.ts reads its logo relative to the executing module;
|
|
479
|
+
// the bundle executes from .differential/, so mirror the asset beside it.
|
|
480
|
+
await mkdir(join(packageRoot, '.differential/assets'), { recursive: true });
|
|
481
|
+
await copyFile(
|
|
482
|
+
join(packageRoot, 'src/assets/linzumi-logo.svg'),
|
|
483
|
+
join(packageRoot, '.differential/assets/linzumi-logo.svg')
|
|
484
|
+
);
|