@linzumi/cli 1.0.18 → 1.0.20
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 +352 -325
- package/dist/mcp-server.mjs +8 -8
- package/package.json +12 -3
- package/scripts/normalize-agent-replay-fixture.ts +281 -0
- package/scripts/promote-gstack-claude-smoke-fixture.ts +891 -0
- package/scripts/record-agent-replay-fixtures.ts +580 -0
- package/scripts/replay-agent-backend-browser.ts +198 -0
- package/scripts/replay-agent-backend.ts +1782 -0
- package/scripts/replay-agent-fixture.ts +325 -0
- package/scripts/vendor-codex-app-server-protocol.ts +383 -0
|
@@ -0,0 +1,891 @@
|
|
|
1
|
+
/*
|
|
2
|
+
- Date: 2026-06-30
|
|
3
|
+
Spec: plans/2026-06-29-gstack-claude-code-support-requirements.md
|
|
4
|
+
Relationship: One-command candidate promotion path for the first real gstack
|
|
5
|
+
Claude Code smoke fixture. It fails fast on missing local gstack setup, then
|
|
6
|
+
records exact raw provider data, normalizes the fixture, writes a candidate
|
|
7
|
+
manifest, and runs replay proofs that can be repeated locally.
|
|
8
|
+
*/
|
|
9
|
+
import { spawnSync } from 'node:child_process';
|
|
10
|
+
import { copyFile, mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
11
|
+
import { tmpdir } from 'node:os';
|
|
12
|
+
import { dirname, join, resolve } from 'node:path';
|
|
13
|
+
import { fileURLToPath } from 'node:url';
|
|
14
|
+
import { scanAgentReplayInstallFiles } from '../src/agentReplayInstallSafety';
|
|
15
|
+
import { agentReplayManifestPathForFixture } from '../src/agentReplayManifest';
|
|
16
|
+
import { isJsonObject, type JsonObject } from '../src/protocol';
|
|
17
|
+
import { normalizeAgentReplayText } from './normalize-agent-replay-fixture';
|
|
18
|
+
|
|
19
|
+
type PromoteOptions = {
|
|
20
|
+
readonly outDir: string;
|
|
21
|
+
readonly rawDir: string;
|
|
22
|
+
readonly existingRawFixture: string | undefined;
|
|
23
|
+
readonly fixtureDir: string;
|
|
24
|
+
readonly providerReplayOutDir: string;
|
|
25
|
+
readonly backendReplayOutDir: string;
|
|
26
|
+
readonly browserReplayOutDir: string;
|
|
27
|
+
readonly installFixtureDir: string | undefined;
|
|
28
|
+
readonly speed: string | undefined;
|
|
29
|
+
readonly preflightOnly: boolean;
|
|
30
|
+
readonly keepWorkdirs: boolean;
|
|
31
|
+
readonly claudePermissionMode: string | undefined;
|
|
32
|
+
readonly claudeModel: string | undefined;
|
|
33
|
+
readonly gstackSkillDir: string | undefined;
|
|
34
|
+
readonly gstackCli: string | undefined;
|
|
35
|
+
readonly skipBackend: boolean;
|
|
36
|
+
readonly browserProof: boolean;
|
|
37
|
+
readonly localBrowserProof: boolean;
|
|
38
|
+
readonly backendUrl: string | undefined;
|
|
39
|
+
readonly setupFixture: boolean;
|
|
40
|
+
readonly sessionToken: string | undefined;
|
|
41
|
+
readonly workspace: string | undefined;
|
|
42
|
+
readonly channel: string | undefined;
|
|
43
|
+
readonly threadId: string | undefined;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
type CommandResult = {
|
|
47
|
+
readonly status: number;
|
|
48
|
+
readonly stdout: string;
|
|
49
|
+
readonly stderr: string;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
type BackendReplayPromotionResult =
|
|
53
|
+
| { readonly type: 'skipped'; readonly reason: string }
|
|
54
|
+
| {
|
|
55
|
+
readonly type: 'ran';
|
|
56
|
+
readonly proofMode: 'backend' | 'backend-browser';
|
|
57
|
+
readonly result: CommandResult;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
type PromotionStatus =
|
|
61
|
+
| 'blocked'
|
|
62
|
+
| 'preflight-ok'
|
|
63
|
+
| 'provider-ready'
|
|
64
|
+
| 'backend-ready'
|
|
65
|
+
| 'browser-ready'
|
|
66
|
+
| 'failed';
|
|
67
|
+
|
|
68
|
+
const thisFile = fileURLToPath(import.meta.url);
|
|
69
|
+
const packageRoot = resolve(dirname(thisFile), '..');
|
|
70
|
+
const repoRoot = resolve(packageRoot, '..', '..');
|
|
71
|
+
const webRoot = join(repoRoot, 'kandan', 'server_v2', 'web');
|
|
72
|
+
const tsxLoader = join(repoRoot, 'node_modules', 'tsx', 'dist', 'loader.mjs');
|
|
73
|
+
const provider = 'gstack-claude-smoke';
|
|
74
|
+
const featureId = 'gstack-claude-smoke';
|
|
75
|
+
|
|
76
|
+
async function main(): Promise<void> {
|
|
77
|
+
const args = process.argv.slice(2);
|
|
78
|
+
|
|
79
|
+
if (args.includes('--help')) {
|
|
80
|
+
printHelp();
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const options = parseArgs(args);
|
|
85
|
+
|
|
86
|
+
if (options.localBrowserProof && options.backendUrl !== undefined) {
|
|
87
|
+
throw new Error(
|
|
88
|
+
'use either --local-browser-proof or --backend-url, not both'
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
await mkdir(options.outDir, { recursive: true });
|
|
93
|
+
|
|
94
|
+
const preflight =
|
|
95
|
+
options.existingRawFixture === undefined
|
|
96
|
+
? runScript('record-agent-replay-fixtures.ts', [
|
|
97
|
+
'--provider',
|
|
98
|
+
provider,
|
|
99
|
+
'--preflight-only',
|
|
100
|
+
'--preflight-json',
|
|
101
|
+
'--out-dir',
|
|
102
|
+
options.rawDir,
|
|
103
|
+
...(options.gstackSkillDir === undefined
|
|
104
|
+
? []
|
|
105
|
+
: ['--gstack-skill-dir', options.gstackSkillDir]),
|
|
106
|
+
...(options.gstackCli === undefined
|
|
107
|
+
? []
|
|
108
|
+
: ['--gstack-cli', options.gstackCli]),
|
|
109
|
+
])
|
|
110
|
+
: skippedPreflightForExistingRaw(options.existingRawFixture);
|
|
111
|
+
const preflightJson = parseJsonObjectFromCommandOutput(preflight.stdout);
|
|
112
|
+
|
|
113
|
+
if (preflight.status !== 0) {
|
|
114
|
+
const status = 'blocked';
|
|
115
|
+
await emitSummary(options, {
|
|
116
|
+
status,
|
|
117
|
+
reason: 'gstack preflight failed before fixture capture',
|
|
118
|
+
preflight: preflightJson,
|
|
119
|
+
proofPacket: proofPacket({
|
|
120
|
+
options,
|
|
121
|
+
status,
|
|
122
|
+
preflight,
|
|
123
|
+
}),
|
|
124
|
+
commands: { preflight: commandSummary(preflight) },
|
|
125
|
+
});
|
|
126
|
+
process.exitCode = 1;
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (options.preflightOnly && options.existingRawFixture === undefined) {
|
|
131
|
+
const status = 'preflight-ok';
|
|
132
|
+
await emitSummary(options, {
|
|
133
|
+
status,
|
|
134
|
+
preflight: preflightJson,
|
|
135
|
+
proofPacket: proofPacket({
|
|
136
|
+
options,
|
|
137
|
+
status,
|
|
138
|
+
preflight,
|
|
139
|
+
}),
|
|
140
|
+
commands: { preflight: commandSummary(preflight) },
|
|
141
|
+
});
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const record =
|
|
146
|
+
options.existingRawFixture === undefined
|
|
147
|
+
? runScript('record-agent-replay-fixtures.ts', [
|
|
148
|
+
'--provider',
|
|
149
|
+
provider,
|
|
150
|
+
'--exact-raw',
|
|
151
|
+
'--out-dir',
|
|
152
|
+
options.rawDir,
|
|
153
|
+
...(options.keepWorkdirs ? ['--keep-workdirs'] : []),
|
|
154
|
+
...(options.claudePermissionMode === undefined
|
|
155
|
+
? []
|
|
156
|
+
: ['--claude-permission-mode', options.claudePermissionMode]),
|
|
157
|
+
...(options.claudeModel === undefined
|
|
158
|
+
? []
|
|
159
|
+
: ['--claude-model', options.claudeModel]),
|
|
160
|
+
...(options.gstackSkillDir === undefined
|
|
161
|
+
? []
|
|
162
|
+
: ['--gstack-skill-dir', options.gstackSkillDir]),
|
|
163
|
+
...(options.gstackCli === undefined
|
|
164
|
+
? []
|
|
165
|
+
: ['--gstack-cli', options.gstackCli]),
|
|
166
|
+
])
|
|
167
|
+
: existingRawCaptureResult(options.existingRawFixture);
|
|
168
|
+
|
|
169
|
+
if (record.status !== 0) {
|
|
170
|
+
const status = 'failed';
|
|
171
|
+
await emitSummary(options, {
|
|
172
|
+
status,
|
|
173
|
+
reason: 'gstack raw fixture recording failed',
|
|
174
|
+
preflight: preflightJson,
|
|
175
|
+
proofPacket: proofPacket({
|
|
176
|
+
options,
|
|
177
|
+
status,
|
|
178
|
+
preflight,
|
|
179
|
+
record,
|
|
180
|
+
}),
|
|
181
|
+
commands: {
|
|
182
|
+
preflight: commandSummary(preflight),
|
|
183
|
+
record: commandSummary(record),
|
|
184
|
+
},
|
|
185
|
+
});
|
|
186
|
+
process.exitCode = 1;
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const rawFixturePath =
|
|
191
|
+
options.existingRawFixture ?? join(options.rawDir, `${provider}.ndjson`);
|
|
192
|
+
const normalizedFixturePath = join(options.fixtureDir, `${provider}.ndjson`);
|
|
193
|
+
const normalizedText = normalizeAgentReplayText(
|
|
194
|
+
await readFile(rawFixturePath, 'utf8')
|
|
195
|
+
);
|
|
196
|
+
await mkdir(options.fixtureDir, { recursive: true });
|
|
197
|
+
await writeFile(normalizedFixturePath, normalizedText, 'utf8');
|
|
198
|
+
await writeCandidateManifest(
|
|
199
|
+
normalizedFixturePath,
|
|
200
|
+
{
|
|
201
|
+
providerReplay: 'pending',
|
|
202
|
+
backendReplay: 'pending',
|
|
203
|
+
browserAutomation: 'pending',
|
|
204
|
+
oracle: 'pending',
|
|
205
|
+
},
|
|
206
|
+
preflightJson,
|
|
207
|
+
options
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
const providerReplay = runScript('replay-agent-fixture.ts', [
|
|
211
|
+
'--fixture',
|
|
212
|
+
normalizedFixturePath,
|
|
213
|
+
'--coverage',
|
|
214
|
+
'none',
|
|
215
|
+
'--no-html',
|
|
216
|
+
'--out-dir',
|
|
217
|
+
options.providerReplayOutDir,
|
|
218
|
+
]);
|
|
219
|
+
const providerReplayJson = parseJsonObjectFromCommandOutput(
|
|
220
|
+
providerReplay.stdout
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
if (providerReplay.status === 0) {
|
|
224
|
+
await writeCandidateManifest(
|
|
225
|
+
normalizedFixturePath,
|
|
226
|
+
{
|
|
227
|
+
providerReplay: 'covered',
|
|
228
|
+
backendReplay: 'pending',
|
|
229
|
+
browserAutomation: 'pending',
|
|
230
|
+
oracle: 'pending',
|
|
231
|
+
},
|
|
232
|
+
preflightJson,
|
|
233
|
+
options
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const backendReplay = backendReplayCommand(options, normalizedFixturePath);
|
|
238
|
+
const backendReplayJson =
|
|
239
|
+
backendReplay.type === 'skipped' ||
|
|
240
|
+
backendReplay.proofMode === 'backend-browser'
|
|
241
|
+
? undefined
|
|
242
|
+
: parseJsonObjectFromCommandOutput(backendReplay.result.stdout);
|
|
243
|
+
|
|
244
|
+
if (
|
|
245
|
+
providerReplay.status === 0 &&
|
|
246
|
+
backendReplay.type === 'ran' &&
|
|
247
|
+
backendReplay.result.status === 0
|
|
248
|
+
) {
|
|
249
|
+
await writeCandidateManifest(
|
|
250
|
+
normalizedFixturePath,
|
|
251
|
+
{
|
|
252
|
+
providerReplay: 'covered',
|
|
253
|
+
backendReplay: 'covered',
|
|
254
|
+
browserAutomation:
|
|
255
|
+
backendReplay.proofMode === 'backend-browser' ? 'covered' : 'pending',
|
|
256
|
+
oracle: 'covered',
|
|
257
|
+
},
|
|
258
|
+
preflightJson,
|
|
259
|
+
options
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const status = promotionStatus(providerReplay, backendReplay);
|
|
264
|
+
const install = await maybeInstallCandidateFixture({
|
|
265
|
+
options,
|
|
266
|
+
status,
|
|
267
|
+
normalizedFixturePath,
|
|
268
|
+
});
|
|
269
|
+
await emitSummary(options, {
|
|
270
|
+
status,
|
|
271
|
+
rawFixturePath,
|
|
272
|
+
normalizedFixturePath,
|
|
273
|
+
manifestPath: agentReplayManifestPathForFixture(normalizedFixturePath),
|
|
274
|
+
preflight: preflightJson,
|
|
275
|
+
providerReplay: providerReplayJson,
|
|
276
|
+
backendReplay: backendReplayJson,
|
|
277
|
+
install,
|
|
278
|
+
proofPacket: proofPacket({
|
|
279
|
+
options,
|
|
280
|
+
status,
|
|
281
|
+
preflight,
|
|
282
|
+
record,
|
|
283
|
+
rawFixturePath,
|
|
284
|
+
normalizedFixturePath,
|
|
285
|
+
providerReplay,
|
|
286
|
+
backendReplay,
|
|
287
|
+
}),
|
|
288
|
+
commands: {
|
|
289
|
+
preflight: commandSummary(preflight),
|
|
290
|
+
record: commandSummary(record),
|
|
291
|
+
providerReplay: commandSummary(providerReplay),
|
|
292
|
+
...(backendReplay.type === 'skipped'
|
|
293
|
+
? { backendReplay: { status: 'skipped', reason: backendReplay.reason } }
|
|
294
|
+
: { backendReplay: commandSummary(backendReplay.result) }),
|
|
295
|
+
},
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
if (status === 'failed' || install.status === 'blocked') {
|
|
299
|
+
process.exitCode = 1;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
async function maybeInstallCandidateFixture(args: {
|
|
304
|
+
readonly options: PromoteOptions;
|
|
305
|
+
readonly status: PromotionStatus;
|
|
306
|
+
readonly normalizedFixturePath: string;
|
|
307
|
+
}): Promise<JsonObject> {
|
|
308
|
+
const { options, status, normalizedFixturePath } = args;
|
|
309
|
+
|
|
310
|
+
if (options.installFixtureDir === undefined) {
|
|
311
|
+
return { status: 'not-requested' };
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (status !== 'browser-ready') {
|
|
315
|
+
return {
|
|
316
|
+
status: 'blocked',
|
|
317
|
+
reason: '--install-fixture-dir requires a browser-ready candidate',
|
|
318
|
+
targetDir: options.installFixtureDir,
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const sourceManifestPath = agentReplayManifestPathForFixture(
|
|
323
|
+
normalizedFixturePath
|
|
324
|
+
);
|
|
325
|
+
const safetyFindings = await scanAgentReplayInstallFiles([
|
|
326
|
+
normalizedFixturePath,
|
|
327
|
+
sourceManifestPath,
|
|
328
|
+
]);
|
|
329
|
+
|
|
330
|
+
if (safetyFindings.length > 0) {
|
|
331
|
+
return {
|
|
332
|
+
status: 'blocked',
|
|
333
|
+
reason: 'candidate fixture safety scan failed',
|
|
334
|
+
targetDir: options.installFixtureDir,
|
|
335
|
+
findings: safetyFindings,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const targetFixturePath = join(
|
|
340
|
+
options.installFixtureDir,
|
|
341
|
+
`${provider}.ndjson`
|
|
342
|
+
);
|
|
343
|
+
const targetManifestPath =
|
|
344
|
+
agentReplayManifestPathForFixture(targetFixturePath);
|
|
345
|
+
await mkdir(options.installFixtureDir, { recursive: true });
|
|
346
|
+
await copyFile(normalizedFixturePath, targetFixturePath);
|
|
347
|
+
await copyFile(sourceManifestPath, targetManifestPath);
|
|
348
|
+
|
|
349
|
+
return {
|
|
350
|
+
status: 'installed',
|
|
351
|
+
fixturePath: targetFixturePath,
|
|
352
|
+
manifestPath: targetManifestPath,
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function skippedPreflightForExistingRaw(rawFixturePath: string): CommandResult {
|
|
357
|
+
return {
|
|
358
|
+
status: 0,
|
|
359
|
+
stdout: `${JSON.stringify(
|
|
360
|
+
{
|
|
361
|
+
provider,
|
|
362
|
+
status: 'skipped',
|
|
363
|
+
reason: 'using previously captured real raw fixture',
|
|
364
|
+
rawFixturePath,
|
|
365
|
+
},
|
|
366
|
+
null,
|
|
367
|
+
2
|
|
368
|
+
)}
|
|
369
|
+
`,
|
|
370
|
+
stderr: '',
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function existingRawCaptureResult(rawFixturePath: string): CommandResult {
|
|
375
|
+
return {
|
|
376
|
+
status: 0,
|
|
377
|
+
stdout: `using previously captured real raw fixture ${rawFixturePath}
|
|
378
|
+
`,
|
|
379
|
+
stderr: '',
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function backendReplayCommand(
|
|
384
|
+
options: PromoteOptions,
|
|
385
|
+
fixturePath: string
|
|
386
|
+
): BackendReplayPromotionResult {
|
|
387
|
+
if (options.skipBackend) {
|
|
388
|
+
return { type: 'skipped', reason: '--skip-backend was provided' };
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (options.localBrowserProof) {
|
|
392
|
+
return {
|
|
393
|
+
type: 'ran',
|
|
394
|
+
proofMode: 'backend-browser',
|
|
395
|
+
result: runCommand(
|
|
396
|
+
'bun',
|
|
397
|
+
[
|
|
398
|
+
'run',
|
|
399
|
+
'replay-harness:agent-backend-live',
|
|
400
|
+
'--',
|
|
401
|
+
'--fixture',
|
|
402
|
+
fixturePath,
|
|
403
|
+
'--provider',
|
|
404
|
+
'claude-code',
|
|
405
|
+
'--setup-fixture',
|
|
406
|
+
'--oracle-feature',
|
|
407
|
+
featureId,
|
|
408
|
+
'--out-dir',
|
|
409
|
+
options.browserReplayOutDir,
|
|
410
|
+
...(options.speed === undefined ? [] : ['--speed', options.speed]),
|
|
411
|
+
],
|
|
412
|
+
webRoot
|
|
413
|
+
),
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (options.backendUrl === undefined) {
|
|
418
|
+
return {
|
|
419
|
+
type: 'skipped',
|
|
420
|
+
reason:
|
|
421
|
+
'--backend-url or --local-browser-proof was not provided; backend replay was not attempted',
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const backendArgs = [
|
|
426
|
+
'--fixture',
|
|
427
|
+
fixturePath,
|
|
428
|
+
'--provider',
|
|
429
|
+
'claude-code',
|
|
430
|
+
'--backend-url',
|
|
431
|
+
options.backendUrl,
|
|
432
|
+
'--oracle-feature',
|
|
433
|
+
featureId,
|
|
434
|
+
'--out-dir',
|
|
435
|
+
options.browserProof
|
|
436
|
+
? options.browserReplayOutDir
|
|
437
|
+
: options.backendReplayOutDir,
|
|
438
|
+
...(options.speed === undefined ? [] : ['--speed', options.speed]),
|
|
439
|
+
...(options.setupFixture ? ['--setup-fixture'] : []),
|
|
440
|
+
...(options.sessionToken === undefined
|
|
441
|
+
? []
|
|
442
|
+
: ['--session-token', options.sessionToken]),
|
|
443
|
+
...(options.workspace === undefined
|
|
444
|
+
? []
|
|
445
|
+
: ['--workspace', options.workspace]),
|
|
446
|
+
...(options.channel === undefined ? [] : ['--channel', options.channel]),
|
|
447
|
+
...(options.threadId === undefined
|
|
448
|
+
? []
|
|
449
|
+
: ['--thread-id', options.threadId]),
|
|
450
|
+
];
|
|
451
|
+
|
|
452
|
+
return {
|
|
453
|
+
type: 'ran',
|
|
454
|
+
proofMode: options.browserProof ? 'backend-browser' : 'backend',
|
|
455
|
+
result: runScript(
|
|
456
|
+
options.browserProof
|
|
457
|
+
? 'replay-agent-backend-browser.ts'
|
|
458
|
+
: 'replay-agent-backend.ts',
|
|
459
|
+
backendArgs
|
|
460
|
+
),
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function promotionStatus(
|
|
465
|
+
providerReplay: CommandResult,
|
|
466
|
+
backendReplay: BackendReplayPromotionResult
|
|
467
|
+
): PromotionStatus {
|
|
468
|
+
if (providerReplay.status !== 0) {
|
|
469
|
+
return 'failed';
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
switch (backendReplay.type) {
|
|
473
|
+
case 'skipped':
|
|
474
|
+
return 'provider-ready';
|
|
475
|
+
case 'ran':
|
|
476
|
+
if (backendReplay.result.status !== 0) {
|
|
477
|
+
return 'failed';
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
return backendReplay.proofMode === 'backend-browser'
|
|
481
|
+
? 'browser-ready'
|
|
482
|
+
: 'backend-ready';
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function proofPacket(args: {
|
|
487
|
+
readonly options: PromoteOptions;
|
|
488
|
+
readonly status: PromotionStatus;
|
|
489
|
+
readonly preflight: CommandResult;
|
|
490
|
+
readonly record?: CommandResult | undefined;
|
|
491
|
+
readonly rawFixturePath?: string | undefined;
|
|
492
|
+
readonly normalizedFixturePath?: string | undefined;
|
|
493
|
+
readonly providerReplay?: CommandResult | undefined;
|
|
494
|
+
readonly backendReplay?: BackendReplayPromotionResult | undefined;
|
|
495
|
+
}): JsonObject {
|
|
496
|
+
const manifestPath =
|
|
497
|
+
args.normalizedFixturePath === undefined
|
|
498
|
+
? undefined
|
|
499
|
+
: agentReplayManifestPathForFixture(args.normalizedFixturePath);
|
|
500
|
+
const backendSummaryPath =
|
|
501
|
+
args.backendReplay === undefined || args.backendReplay.type === 'skipped'
|
|
502
|
+
? undefined
|
|
503
|
+
: join(
|
|
504
|
+
args.backendReplay.proofMode === 'backend-browser'
|
|
505
|
+
? args.options.browserReplayOutDir
|
|
506
|
+
: args.options.backendReplayOutDir,
|
|
507
|
+
'summary.json'
|
|
508
|
+
);
|
|
509
|
+
|
|
510
|
+
return {
|
|
511
|
+
featureId,
|
|
512
|
+
lane: provider,
|
|
513
|
+
status: args.status,
|
|
514
|
+
artifacts: {
|
|
515
|
+
summaryPath: join(args.options.outDir, 'summary.json'),
|
|
516
|
+
rawDir: args.options.rawDir,
|
|
517
|
+
rawFixturePath: args.rawFixturePath,
|
|
518
|
+
normalizedFixturePath: args.normalizedFixturePath,
|
|
519
|
+
manifestPath,
|
|
520
|
+
providerReplaySummaryPath:
|
|
521
|
+
args.providerReplay === undefined
|
|
522
|
+
? undefined
|
|
523
|
+
: join(args.options.providerReplayOutDir, 'summary.json'),
|
|
524
|
+
backendReplaySummaryPath: backendSummaryPath,
|
|
525
|
+
browserReplaySummaryPath:
|
|
526
|
+
args.backendReplay?.type === 'ran' &&
|
|
527
|
+
args.backendReplay.proofMode === 'backend-browser'
|
|
528
|
+
? join(args.options.browserReplayOutDir, 'summary.json')
|
|
529
|
+
: undefined,
|
|
530
|
+
},
|
|
531
|
+
proof: {
|
|
532
|
+
preflight: proofStatusForCommand(args.preflight, 'blocked'),
|
|
533
|
+
rawCapture: proofStatusForOptionalCommand(args.record),
|
|
534
|
+
normalization:
|
|
535
|
+
args.normalizedFixturePath === undefined ? 'pending' : 'covered',
|
|
536
|
+
manifest: manifestPath === undefined ? 'pending' : 'covered',
|
|
537
|
+
providerReplay: proofStatusForOptionalCommand(args.providerReplay),
|
|
538
|
+
backendReplay: proofStatusForBackendReplay(args.backendReplay),
|
|
539
|
+
oracle: proofStatusForBackendReplay(args.backendReplay),
|
|
540
|
+
frontendStorybook:
|
|
541
|
+
args.normalizedFixturePath === undefined ? 'pending' : 'not-applicable',
|
|
542
|
+
browserAutomation: proofStatusForBrowserReplay(args.backendReplay),
|
|
543
|
+
},
|
|
544
|
+
nextCommands: nextPromotionCommands(args.options, args.status),
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
function proofStatusForOptionalCommand(
|
|
549
|
+
result: CommandResult | undefined
|
|
550
|
+
): string {
|
|
551
|
+
if (result === undefined) {
|
|
552
|
+
return 'pending';
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
return proofStatusForCommand(result, 'failed');
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
function proofStatusForCommand(
|
|
559
|
+
result: CommandResult,
|
|
560
|
+
failedStatus: 'blocked' | 'failed'
|
|
561
|
+
): string {
|
|
562
|
+
return result.status === 0 ? 'covered' : failedStatus;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
function proofStatusForBackendReplay(
|
|
566
|
+
result: BackendReplayPromotionResult | undefined
|
|
567
|
+
): string {
|
|
568
|
+
if (result === undefined || result.type === 'skipped') {
|
|
569
|
+
return 'pending';
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
return result.result.status === 0 ? 'covered' : 'failed';
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
function proofStatusForBrowserReplay(
|
|
576
|
+
result: BackendReplayPromotionResult | undefined
|
|
577
|
+
): string {
|
|
578
|
+
if (result === undefined || result.type === 'skipped') {
|
|
579
|
+
return 'pending';
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
if (result.proofMode !== 'backend-browser') {
|
|
583
|
+
return 'pending';
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
return result.result.status === 0 ? 'covered' : 'failed';
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function promotionCommandBase(options: PromoteOptions): string {
|
|
590
|
+
const optionArgs = [
|
|
591
|
+
'--out-dir',
|
|
592
|
+
options.outDir,
|
|
593
|
+
...(options.existingRawFixture === undefined
|
|
594
|
+
? []
|
|
595
|
+
: ['--existing-raw-fixture', options.existingRawFixture]),
|
|
596
|
+
...(options.claudeModel === undefined
|
|
597
|
+
? []
|
|
598
|
+
: ['--claude-model', options.claudeModel]),
|
|
599
|
+
...(options.claudePermissionMode === undefined
|
|
600
|
+
? []
|
|
601
|
+
: ['--claude-permission-mode', options.claudePermissionMode]),
|
|
602
|
+
...(options.speed === undefined ? [] : ['--speed', options.speed]),
|
|
603
|
+
...(options.gstackSkillDir === undefined
|
|
604
|
+
? []
|
|
605
|
+
: ['--gstack-skill-dir', options.gstackSkillDir]),
|
|
606
|
+
...(options.gstackCli === undefined
|
|
607
|
+
? []
|
|
608
|
+
: ['--gstack-cli', options.gstackCli]),
|
|
609
|
+
...(options.keepWorkdirs ? ['--keep-workdirs'] : []),
|
|
610
|
+
];
|
|
611
|
+
|
|
612
|
+
return `pnpm --filter @linzumi/cli run promote:gstack-claude-smoke-fixture -- ${optionArgs.map(shellArg).join(' ')}`;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
function shellArg(value: string): string {
|
|
616
|
+
if (/^[A-Za-z0-9_./:=@+-]+$/u.test(value)) {
|
|
617
|
+
return value;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
return `'${value.replace(/'/gu, `'"'"'`)}'`;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
function nextPromotionCommands(
|
|
624
|
+
options: PromoteOptions,
|
|
625
|
+
status: PromotionStatus
|
|
626
|
+
): readonly string[] {
|
|
627
|
+
const base = promotionCommandBase(options);
|
|
628
|
+
const preflight = `${base} --preflight-only`;
|
|
629
|
+
const providerOnly = `${base} --skip-backend`;
|
|
630
|
+
const backend =
|
|
631
|
+
options.backendUrl === undefined
|
|
632
|
+
? `${base} --backend-url http://127.0.0.1:4140 --setup-fixture`
|
|
633
|
+
: `${base} --backend-url ${shellArg(options.backendUrl)}${options.setupFixture ? ' --setup-fixture' : ''}`;
|
|
634
|
+
const browser = `${backend} --browser-proof`;
|
|
635
|
+
const localBrowser = `${base} --local-browser-proof`;
|
|
636
|
+
|
|
637
|
+
switch (status) {
|
|
638
|
+
case 'blocked':
|
|
639
|
+
return [preflight, providerOnly];
|
|
640
|
+
case 'preflight-ok':
|
|
641
|
+
return [providerOnly, backend];
|
|
642
|
+
case 'provider-ready':
|
|
643
|
+
return [localBrowser, backend, browser];
|
|
644
|
+
case 'backend-ready':
|
|
645
|
+
return [localBrowser, browser];
|
|
646
|
+
case 'browser-ready':
|
|
647
|
+
return [
|
|
648
|
+
`pnpm --filter @linzumi/cli run replay:agent-fixture -- --fixture ${join(options.fixtureDir, `${provider}.ndjson`)} --coverage none --require-feature ${featureId} --out-dir ${join(options.outDir, 'final-readiness')}`,
|
|
649
|
+
];
|
|
650
|
+
case 'failed':
|
|
651
|
+
return [preflight, providerOnly, backend];
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
async function writeCandidateManifest(
|
|
656
|
+
fixturePath: string,
|
|
657
|
+
proof: {
|
|
658
|
+
readonly providerReplay: 'covered' | 'pending';
|
|
659
|
+
readonly backendReplay: 'covered' | 'pending';
|
|
660
|
+
readonly browserAutomation: 'covered' | 'pending';
|
|
661
|
+
readonly oracle: 'covered' | 'pending';
|
|
662
|
+
},
|
|
663
|
+
preflight: JsonObject,
|
|
664
|
+
options: PromoteOptions
|
|
665
|
+
): Promise<void> {
|
|
666
|
+
const manifestPath = agentReplayManifestPathForFixture(fixturePath);
|
|
667
|
+
const manifest = {
|
|
668
|
+
schemaVersion: 1,
|
|
669
|
+
fixtureId: 'gstack-claude-smoke',
|
|
670
|
+
lane: 'gstack-claude-smoke',
|
|
671
|
+
provider: 'claude-code-sdk',
|
|
672
|
+
fixturePath: 'gstack-claude-smoke.ndjson',
|
|
673
|
+
capture: {
|
|
674
|
+
promptId: 'agent-fixture-playground/gstack-claude-smoke-prompt',
|
|
675
|
+
runtimeProfile: 'gstack-claude-code',
|
|
676
|
+
source: 'real Claude Code SDK capture with user-installed gstack',
|
|
677
|
+
...(options.claudeModel === undefined
|
|
678
|
+
? {}
|
|
679
|
+
: { model: options.claudeModel }),
|
|
680
|
+
...candidateManifestPreflightCapture(preflight),
|
|
681
|
+
},
|
|
682
|
+
features: [
|
|
683
|
+
{
|
|
684
|
+
featureId,
|
|
685
|
+
title: 'Gstack skill discovery and local review smoke',
|
|
686
|
+
autonomyStatus:
|
|
687
|
+
proof.backendReplay === 'covered' &&
|
|
688
|
+
proof.oracle === 'covered' &&
|
|
689
|
+
proof.browserAutomation === 'covered'
|
|
690
|
+
? 'ready-to-implement'
|
|
691
|
+
: 'needs-oracle',
|
|
692
|
+
expectedProof: {
|
|
693
|
+
realFixture: 'covered',
|
|
694
|
+
providerReplay: proof.providerReplay,
|
|
695
|
+
backendReplay: proof.backendReplay,
|
|
696
|
+
frontendStorybook: 'not-applicable',
|
|
697
|
+
browserAutomation: proof.browserAutomation,
|
|
698
|
+
oracle: proof.oracle,
|
|
699
|
+
},
|
|
700
|
+
notes:
|
|
701
|
+
'Candidate manifest generated by promote:gstack-claude-smoke-fixture. The gstack smoke lane uses real backend/browser proof, so frontendStorybook is not applicable for this candidate. Keep pending replay/oracle rows until the matching proof command has passed for this exact fixture.',
|
|
702
|
+
},
|
|
703
|
+
],
|
|
704
|
+
};
|
|
705
|
+
|
|
706
|
+
await mkdir(dirname(manifestPath), { recursive: true });
|
|
707
|
+
await writeFile(
|
|
708
|
+
manifestPath,
|
|
709
|
+
`${JSON.stringify(manifest, null, 2)}\n`,
|
|
710
|
+
'utf8'
|
|
711
|
+
);
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
function candidateManifestPreflightCapture(preflight: JsonObject): JsonObject {
|
|
715
|
+
const setup = preflight.setup;
|
|
716
|
+
|
|
717
|
+
if (!isJsonObject(setup)) {
|
|
718
|
+
return {};
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
return {
|
|
722
|
+
gstackSkillDir:
|
|
723
|
+
typeof setup.skillDir === 'string' ? setup.skillDir : undefined,
|
|
724
|
+
gstackCliPath:
|
|
725
|
+
typeof setup.cliPath === 'string' ? setup.cliPath : undefined,
|
|
726
|
+
checkedSkillDirs: Array.isArray(setup.checkedSkillDirs)
|
|
727
|
+
? setup.checkedSkillDirs
|
|
728
|
+
: undefined,
|
|
729
|
+
checkedCliNames: Array.isArray(setup.checkedCliNames)
|
|
730
|
+
? setup.checkedCliNames
|
|
731
|
+
: undefined,
|
|
732
|
+
includeHookEvents: setup.includeHookEvents,
|
|
733
|
+
preservesClaudeTasks: setup.preservesClaudeTasks,
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
async function emitSummary(
|
|
738
|
+
options: PromoteOptions,
|
|
739
|
+
summary: JsonObject
|
|
740
|
+
): Promise<void> {
|
|
741
|
+
await mkdir(options.outDir, { recursive: true });
|
|
742
|
+
await writeFile(
|
|
743
|
+
join(options.outDir, 'summary.json'),
|
|
744
|
+
`${JSON.stringify(summary, null, 2)}\n`,
|
|
745
|
+
'utf8'
|
|
746
|
+
);
|
|
747
|
+
process.stdout.write(`${JSON.stringify(summary, null, 2)}\n`);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
function runScript(scriptName: string, args: readonly string[]): CommandResult {
|
|
751
|
+
const result = spawnSync(
|
|
752
|
+
process.execPath,
|
|
753
|
+
['--import', tsxLoader, join('scripts', scriptName), ...args],
|
|
754
|
+
{
|
|
755
|
+
cwd: packageRoot,
|
|
756
|
+
env: process.env,
|
|
757
|
+
encoding: 'utf8',
|
|
758
|
+
maxBuffer: 1024 * 1024 * 20,
|
|
759
|
+
}
|
|
760
|
+
);
|
|
761
|
+
|
|
762
|
+
return {
|
|
763
|
+
status: result.status ?? 1,
|
|
764
|
+
stdout: result.stdout,
|
|
765
|
+
stderr: result.stderr,
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
function runCommand(
|
|
770
|
+
command: string,
|
|
771
|
+
args: readonly string[],
|
|
772
|
+
cwd: string
|
|
773
|
+
): CommandResult {
|
|
774
|
+
const result = spawnSync(command, args, {
|
|
775
|
+
cwd,
|
|
776
|
+
env: process.env,
|
|
777
|
+
encoding: 'utf8',
|
|
778
|
+
maxBuffer: 1024 * 1024 * 20,
|
|
779
|
+
});
|
|
780
|
+
|
|
781
|
+
return {
|
|
782
|
+
status: result.status ?? 1,
|
|
783
|
+
stdout: result.stdout,
|
|
784
|
+
stderr: result.stderr,
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
function commandSummary(result: CommandResult): JsonObject {
|
|
789
|
+
return {
|
|
790
|
+
status: result.status,
|
|
791
|
+
stdout: result.stdout.slice(0, 20_000),
|
|
792
|
+
stderr: result.stderr.slice(0, 20_000),
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
function parseJsonObjectFromCommandOutput(output: string): JsonObject {
|
|
797
|
+
const start = output.indexOf('{');
|
|
798
|
+
const end = output.lastIndexOf('}');
|
|
799
|
+
|
|
800
|
+
if (start === -1 || end === -1 || end < start) {
|
|
801
|
+
throw new Error(`command output did not contain a JSON object: ${output}`);
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
const parsed: unknown = JSON.parse(output.slice(start, end + 1));
|
|
805
|
+
|
|
806
|
+
if (!isJsonObject(parsed)) {
|
|
807
|
+
throw new Error('command output JSON was not an object');
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
return parsed;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
function parseArgs(args: readonly string[]): PromoteOptions {
|
|
814
|
+
const outDir = resolve(
|
|
815
|
+
argValue(args, '--out-dir') ??
|
|
816
|
+
join(tmpdir(), `linzumi-gstack-claude-smoke-promotion-${Date.now()}`)
|
|
817
|
+
);
|
|
818
|
+
|
|
819
|
+
return {
|
|
820
|
+
outDir,
|
|
821
|
+
rawDir: resolve(argValue(args, '--raw-dir') ?? join(outDir, 'raw')),
|
|
822
|
+
existingRawFixture:
|
|
823
|
+
argValue(args, '--existing-raw-fixture') === undefined
|
|
824
|
+
? undefined
|
|
825
|
+
: resolve(argValue(args, '--existing-raw-fixture') ?? ''),
|
|
826
|
+
fixtureDir: resolve(
|
|
827
|
+
argValue(args, '--fixture-dir') ?? join(outDir, 'fixtures')
|
|
828
|
+
),
|
|
829
|
+
providerReplayOutDir: resolve(
|
|
830
|
+
argValue(args, '--provider-replay-out-dir') ??
|
|
831
|
+
join(outDir, 'provider-replay')
|
|
832
|
+
),
|
|
833
|
+
backendReplayOutDir: resolve(
|
|
834
|
+
argValue(args, '--backend-replay-out-dir') ??
|
|
835
|
+
join(outDir, 'backend-replay')
|
|
836
|
+
),
|
|
837
|
+
browserReplayOutDir: resolve(
|
|
838
|
+
argValue(args, '--browser-replay-out-dir') ??
|
|
839
|
+
join(outDir, 'backend-browser')
|
|
840
|
+
),
|
|
841
|
+
installFixtureDir:
|
|
842
|
+
argValue(args, '--install-fixture-dir') === undefined
|
|
843
|
+
? undefined
|
|
844
|
+
: resolve(argValue(args, '--install-fixture-dir') ?? ''),
|
|
845
|
+
speed: argValue(args, '--speed'),
|
|
846
|
+
preflightOnly: args.includes('--preflight-only'),
|
|
847
|
+
keepWorkdirs: args.includes('--keep-workdirs'),
|
|
848
|
+
claudePermissionMode: argValue(args, '--claude-permission-mode'),
|
|
849
|
+
claudeModel: argValue(args, '--claude-model'),
|
|
850
|
+
gstackSkillDir: argValue(args, '--gstack-skill-dir'),
|
|
851
|
+
gstackCli: argValue(args, '--gstack-cli'),
|
|
852
|
+
skipBackend: args.includes('--skip-backend'),
|
|
853
|
+
browserProof: args.includes('--browser-proof'),
|
|
854
|
+
localBrowserProof: args.includes('--local-browser-proof'),
|
|
855
|
+
backendUrl: argValue(args, '--backend-url'),
|
|
856
|
+
setupFixture: args.includes('--setup-fixture'),
|
|
857
|
+
sessionToken: argValue(args, '--session-token'),
|
|
858
|
+
workspace: argValue(args, '--workspace'),
|
|
859
|
+
channel: argValue(args, '--channel'),
|
|
860
|
+
threadId: argValue(args, '--thread-id'),
|
|
861
|
+
};
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
function argValue(args: readonly string[], name: string): string | undefined {
|
|
865
|
+
const index = args.indexOf(name);
|
|
866
|
+
|
|
867
|
+
if (index === -1) {
|
|
868
|
+
return undefined;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
const value = args[index + 1];
|
|
872
|
+
|
|
873
|
+
if (value === undefined || value.startsWith('--')) {
|
|
874
|
+
throw new Error(`${name} requires a value`);
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
return value;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
function printHelp(): void {
|
|
881
|
+
process.stdout.write(
|
|
882
|
+
`Usage: pnpm run promote:gstack-claude-smoke-fixture -- [options]\n\nOptions:\n --preflight-only Only check local gstack readiness and write summary.json\n --out-dir <path> Promotion artifact root. Default: /tmp/linzumi-gstack-claude-smoke-promotion-*\n --raw-dir <path> Exact raw capture directory. Default: <out-dir>/raw\n --existing-raw-fixture <path> Reuse a previously captured real raw NDJSON fixture without starting Claude\n --fixture-dir <path> Normalized candidate fixture directory. Default: <out-dir>/fixtures\n --provider-replay-out-dir <path> Provider replay artifact directory. Default: <out-dir>/provider-replay\n --backend-replay-out-dir <path> Backend replay artifact directory. Default: <out-dir>/backend-replay\n --browser-replay-out-dir <path> Backend-browser replay artifact directory. Default: <out-dir>/backend-browser\n --speed immediate|realtime|<n>x|<n>ms\n Forwarded to backend replay proof commands. Default: immediate\n --install-fixture-dir <path> Copy browser-ready candidate fixture and manifest into this directory\n --keep-workdirs Forwarded to record:agent-fixtures\n --claude-permission-mode <value> Forwarded to record:agent-fixtures\n --claude-model <model> Forwarded to record:agent-fixtures for cheap-model fixture rerolls\n --gstack-skill-dir <path> Forwarded to record:agent-fixtures for explicit preflight setup\n --gstack-cli <path> Forwarded to record:agent-fixtures for explicit preflight setup\n --skip-backend Do not attempt backend replay after provider replay\n --browser-proof Run backend replay plus browser proof through replay:agent-backend-browser\n --local-browser-proof Boot an isolated local backend, then run backend plus browser proof\n --backend-url <url> If provided, run replay:agent-backend with --oracle-feature gstack-claude-smoke\n --setup-fixture Forwarded to replay:agent-backend\n --session-token <token> Forwarded to replay:agent-backend\n --workspace <slug> Forwarded to replay:agent-backend\n --channel <slug> Forwarded to replay:agent-backend\n --thread-id <id> Forwarded to replay:agent-backend\n\nExamples:\n pnpm --filter @linzumi/cli run promote:gstack-claude-smoke-fixture -- --preflight-only\n pnpm --filter @linzumi/cli run promote:gstack-claude-smoke-fixture -- --skip-backend --claude-permission-mode bypassPermissions --claude-model claude-haiku-4-5-20251001\n pnpm --filter @linzumi/cli run promote:gstack-claude-smoke-fixture -- --existing-raw-fixture /tmp/gstack-raw/gstack-claude-smoke.ndjson --skip-backend\n pnpm --filter @linzumi/cli run promote:gstack-claude-smoke-fixture -- --backend-url http://127.0.0.1:4140 --setup-fixture\n pnpm --filter @linzumi/cli run promote:gstack-claude-smoke-fixture -- --backend-url http://127.0.0.1:4140 --setup-fixture --browser-proof\n pnpm --filter @linzumi/cli run promote:gstack-claude-smoke-fixture -- --local-browser-proof --speed 2x --install-fixture-dir packages/linzumi-cli/test/fixtures/agent-raw-replay/gstack\n`
|
|
883
|
+
);
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
main().catch((error) => {
|
|
887
|
+
process.stderr.write(
|
|
888
|
+
`${error instanceof Error ? (error.stack ?? error.message) : String(error)}\n`
|
|
889
|
+
);
|
|
890
|
+
process.exitCode = 1;
|
|
891
|
+
});
|