@linzumi/cli 1.0.275 → 1.0.276

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.
Files changed (41) hide show
  1. package/README.md +10 -9
  2. package/dist/{impl-ts/cloud-supervisor.mjs → cloud-supervisor.mjs} +78 -78
  3. package/dist/hosted-coding-account-worker.js +25 -0
  4. package/dist/index.js +1609 -3
  5. package/dist/mcp-server.mjs +174 -168
  6. package/dist/{impl-res/native → native}/darwin/manifest.json +5 -2
  7. package/dist/remote-codex-harness-worker.js +757 -0
  8. package/dist/tcp-tunnel-cli.mjs +25 -69
  9. package/dist/topics-cli.mjs +137 -13
  10. package/package.json +9 -16
  11. package/scripts/build.mjs +33 -356
  12. package/scripts/bump-cli-version.mjs +35 -9
  13. package/scripts/check-package-size-budget.mjs +13 -61
  14. package/dist/impl-res/cloud-supervisor.mjs +0 -281
  15. package/dist/impl-res/index.js +0 -1546
  16. package/dist/impl-res/mcp-server.mjs +0 -461
  17. package/dist/impl-ts/assets/linzumi-logo.svg +0 -1
  18. package/dist/impl-ts/crossHarnessFailover.mjs +0 -14
  19. package/dist/impl-ts/index.js +0 -2617
  20. package/dist/impl-ts/mcp-server.mjs +0 -455
  21. package/dist/impl-ts/native/darwin/manifest.json +0 -21
  22. package/dist/impl-ts/native/darwin/preview-process-identity +0 -0
  23. package/dist/impl-ts/tcp-tunnel-cli.mjs +0 -93
  24. package/scripts/build-differential-entry-impl-res.mjs +0 -64
  25. package/scripts/build-differential-entry.mjs +0 -180
  26. package/scripts/build-onboarding-demo-evidence.mjs +0 -186
  27. package/scripts/build-tui-demo-evidence.mjs +0 -107
  28. package/scripts/diagnose-claude-probe.ts +0 -198
  29. package/scripts/differentialMutations.mjs +0 -114
  30. package/scripts/f5_live_cross_harness_failover_e2e.mjs +0 -239
  31. package/scripts/golden-chaos-harness.mjs +0 -1420
  32. package/scripts/normalize-agent-replay-fixture.ts +0 -281
  33. package/scripts/promote-gstack-claude-smoke-fixture.ts +0 -891
  34. package/scripts/record-agent-replay-fixtures.ts +0 -610
  35. package/scripts/replay-agent-backend-browser.ts +0 -198
  36. package/scripts/replay-agent-backend.ts +0 -1961
  37. package/scripts/replay-agent-fixture.ts +0 -326
  38. package/scripts/typecheck.mjs +0 -42
  39. /package/dist/{impl-res/assets → assets}/linzumi-logo.svg +0 -0
  40. /package/dist/{impl-res/crossHarnessFailover.mjs → crossHarnessFailover.mjs} +0 -0
  41. /package/dist/{impl-res/native → native}/darwin/preview-process-identity +0 -0
package/scripts/build.mjs CHANGED
@@ -114,375 +114,52 @@ if (
114
114
  )
115
115
  throw new Error('preview_identity_packaged_asset_invalid');
116
116
 
117
+ // Spec: spec/single-commander.md. Required ReScript assets fail the build.
117
118
  const { build } = await import('esbuild');
118
-
119
- // Spec: plans/2026-08-17-tcp-tunnel-cli.spec.md. The zero-TS tunnel command
120
- // is a mandatory ReScript build input, not a fail-soft optional rung.
121
- const tcpTunnelCliEntry = join(
122
- packageRoot,
123
- '../linzumi-cli-rescript/src/forwarding/CommanderTcpTunnelCli.res.mjs'
124
- );
125
- await access(tcpTunnelCliEntry);
126
-
127
- // M3 cluster 15: the ReScript package ships no version literal (repo rule);
128
- // the CLI version is esbuild-`define`d into the impl-res bundle as
129
- // process.env.LINZUMI_CLI_VERSION (read via CommanderEntryVersion.res), the
130
- // same source of truth as src/version.ts linzumiCliVersion.
131
- const packageVersion = JSON.parse(
132
- await readFile(join(packageRoot, 'package.json'), 'utf8')
133
- ).version;
134
-
135
- await rm(join(packageRoot, 'dist'), { recursive: true, force: true });
136
- for (const implementation of ['impl-ts', 'impl-res']) {
137
- const destination = join(
138
- packageRoot,
139
- 'dist',
140
- implementation,
141
- 'native/darwin'
142
- );
143
- await mkdir(destination, { recursive: true });
144
- await copyFile(identityAsset, join(destination, 'preview-process-identity'));
145
- await chmod(join(destination, 'preview-process-identity'), 0o755);
146
- await copyFile(identityManifestPath, join(destination, 'manifest.json'));
147
- }
148
-
149
- // Renamed banner import (the build-auth-parity-oracle.mjs convention, made
150
- // load-bearing by prod RCA 2026-07-20): esbuild injects banners VERBATIM -
151
- // outside symbol renaming - so a bare `createRequire` here collides with any
152
- // source module's own top-level `import { createRequire } from 'node:module'`
153
- // (bundledCodexBin.ts) and the bundle dies at load with "Identifier
154
- // 'createRequire' has already been declared". These bundles are currently
155
- // minified (which renames the source-side binding and masks the collision),
156
- // but the identically-bannered UNMINIFIED nix image bundles shipped exactly
157
- // that SyntaxError on 2026-07-11 and crash-looped the hosted @linzumi runner
158
- // for 9 days. Keep the banner symbol collision-proof, never rely on minify.
119
+ const packageVersion = JSON.parse(await readFile(join(packageRoot, 'package.json'), 'utf8')).version;
120
+ const commanderSource = join(packageRoot, '../linzumi-cli-rescript/src');
159
121
  const banner = {
160
122
  js: "import { createRequire as __linzumiBundleCreateRequire } from 'node:module';\nconst require = __linzumiBundleCreateRequire(import.meta.url);",
161
123
  };
162
-
163
- // M3 cluster 15 (entry/packaging/selector): ONE package ships BOTH commander
164
- // implementations - dist/impl-ts/ (this package's esbuild output, relocated
165
- // from the historical flat dist/index.js) and dist/impl-res/ (the ReScript
166
- // commander bundled from ../linzumi-cli-rescript) - behind the tiny plain-JS
167
- // chooser bundled to dist/index.js. The byte-pinned bin/linzumi.js entry
168
- // (I153) keeps importing ../dist/index.js and calling main(argv), so the bin
169
- // bytes stay EXACTLY as pinned and the chooser lives behind them.
170
- //
171
- // OWNER GATE (2026-07-15): the chooser resolves an absent/unknown
172
- // ~/.linzumi/commanderImpl cache to "ts"; nothing in the package selects
173
- // impl-res by default.
174
- await build({
175
- entryPoints: [join(packageRoot, 'src/commanderImplChooser.mjs')],
176
- bundle: true,
177
- platform: 'node',
178
- target: 'node20',
179
- format: 'esm',
180
- outfile: join(packageRoot, 'dist/index.js'),
181
- minify: true,
182
- sourcemap: false,
183
- legalComments: 'none',
184
- // The impl bundles are runtime dynamic imports the chooser selects between;
185
- // they must never be inlined into the chooser bundle.
186
- external: ['./impl-ts/*', './impl-res/*'],
187
- });
188
-
189
- await build({
190
- entryPoints: [join(packageRoot, 'src/index.ts')],
191
- bundle: true,
192
- platform: 'node',
193
- target: 'node20',
194
- format: 'esm',
195
- outfile: join(packageRoot, 'dist/impl-ts/index.js'),
196
- // Production hardening: minify the published CLI bundle and ship no source
197
- // map. We already bundle to a single impl bundle (no loose src/); minifying
198
- // reduces accidental source/IP leakage in the npm artifact. This is NOT a
199
- // security boundary -- the published package is public by definition, so no
200
- // secret may live in this code and all authz stays server-side.
201
- minify: true,
202
- sourcemap: false,
203
- legalComments: 'none',
204
- external: ['ws', 'undici', 'blessed', '@anthropic-ai/claude-agent-sdk'],
205
- banner,
206
- });
207
-
208
- await build({
209
- entryPoints: [tcpTunnelCliEntry],
210
- bundle: true,
211
- platform: 'node',
212
- target: 'node20',
213
- format: 'esm',
214
- outfile: join(packageRoot, 'dist/tcp-tunnel-cli.mjs'),
215
- minify: true,
216
- sourcemap: false,
217
- legalComments: 'none',
218
- external: ['ws'],
219
- banner,
220
- });
221
-
222
- // The in-guest Linzumi MCP bridge: a self-contained MCP server bundle that the
223
- // VM Codex supervisor injects into a coding job's microVM and runs with a plain
224
- // `node`. Unlike dist/index.js it bundles EVERYTHING (no `external`), so the
225
- // guest needs no extra npm packages - only Node, which provisioning already
226
- // installs. The MCP server path uses only the bundled @modelcontextprotocol/sdk
227
- // and global fetch; it never touches ws/undici/blessed/claude-agent-sdk, so
228
- // bundling with no externals stays lean (Spec:
229
- // plans/2026-06-12-local-vm-codex-execution-phase1.md in-guest MCP bridge).
230
- // Output extension is .mjs (not .js): the bundle is ESM (banner createRequire +
231
- // a node:url built-in import are top-level `import` statements). In the guest it
232
- // lives at /opt/linzumi/ next to the CJS relay.js with no package.json in scope,
233
- // so a `.js` would be treated as CommonJS on Node versions without ESM-syntax
234
- // detection and throw "Cannot use import statement outside a module". A
235
- // `package.json {"type":"module"}` marker would fix mcp-server but BREAK the CJS
236
- // relay.js at the same path, so the unambiguous `.mjs` extension is the correct
237
- // fix.
238
- await build({
239
- entryPoints: [join(packageRoot, 'src/mcpServerEntry.ts')],
240
- bundle: true,
241
- platform: 'node',
242
- target: 'node20',
243
- format: 'esm',
244
- outfile: join(packageRoot, 'dist/mcp-server.mjs'),
245
- minify: true,
246
- sourcemap: false,
247
- legalComments: 'none',
248
- banner,
249
- });
250
-
251
- // M3 cluster 15: the TS impl's guest MCP bridge also ships CO-LOCATED at
252
- // dist/impl-ts/mcp-server.mjs (the 'ts' candidate ladder's impl-specific
253
- // head; the flat dist/mcp-server.mjs above stays as the rollback backstop
254
- // and the guest path contract). Same bytes, copied - never a second build.
255
- await mkdir(join(packageRoot, 'dist/impl-ts'), { recursive: true });
256
- await copyFile(
257
- join(packageRoot, 'dist/tcp-tunnel-cli.mjs'),
258
- join(packageRoot, 'dist/impl-ts/tcp-tunnel-cli.mjs')
259
- );
260
- await copyFile(
261
- join(packageRoot, 'dist/mcp-server.mjs'),
262
- join(packageRoot, 'dist/impl-ts/mcp-server.mjs')
263
- );
264
-
265
- // M3 cluster 14 selector story (compute-nodes program, M3 packaging): the
266
- // ReScript impl's guest MCP bridge, bundled from the compiled
267
- // CommanderMcpEntry artifact into dist/impl-res/mcp-server.mjs - the head
268
- // of the 'rescript' candidate ladder (mcpServerBundleCandidates in
269
- // src/vmSandbox/startVmCodexAppServer.ts / CommanderMcpBundleSelection.res).
270
- // Same no-externals rationale as dist/mcp-server.mjs above: the guest gets
271
- // ONE file and a bare `node`. Like the F5 cross-harness bridge, the
272
- // ReScript artifacts may not be built in this checkout (publish CI builds
273
- // only @linzumi/cli); the selector ladder is fail-soft by design - a
274
- // missing impl-res bundle falls back to the flat TS bundle - so the honest
275
- // posture here is warn-and-skip, never a broken stub. Post-M4.3 this leg
276
- // BECOMES the dist/mcp-server.mjs build and the TS leg above is deleted
277
- // with the TS impl.
278
- try {
279
- await build({
280
- entryPoints: [
281
- join(
282
- packageRoot,
283
- '../linzumi-cli-rescript/src/mcp/CommanderMcpEntry.res.mjs'
284
- ),
285
- ],
286
- bundle: true,
287
- platform: 'node',
288
- target: 'node20',
289
- format: 'esm',
290
- outfile: join(packageRoot, 'dist/impl-res/mcp-server.mjs'),
291
- minify: true,
292
- sourcemap: false,
293
- legalComments: 'none',
294
- banner,
295
- });
296
- } catch (error) {
297
- console.warn(
298
- '[build] impl-res mcp-server bundle could not be built (are the ' +
299
- 'linzumi-cli-rescript artifacts compiled?); the selector ladder falls ' +
300
- `back to the flat dist/mcp-server.mjs: ${
301
- error instanceof Error ? error.message : String(error)
302
- }`
303
- );
304
- }
305
-
306
- // Long-lived topics producer (spec/topics-rules.md :topics/first-producers-v1
307
- // slice a): `linzumi topics post` is ReScript per the zero-TS program
308
- // (CommanderTopicsCli.res); bundle it beside the flat CLI bundle so the
309
- // dispatcher's `new URL('./topics-cli.mjs', import.meta.url)` resolves at
310
- // runtime. Publish CI compiles @linzumi/cli-rescript before pack, so real
311
- // releases always carry it; a checkout without the compiled artifacts gets
312
- // the same warn-and-skip posture as the mcp bundle above (the dispatcher
313
- // then falls back to the package-source candidate, and failing that prints
314
- // an honest "unavailable in this build" refusal).
315
- try {
316
- await build({
317
- entryPoints: [
318
- join(
319
- packageRoot,
320
- '../linzumi-cli-rescript/src/topics/CommanderTopicsCli.res.mjs'
321
- ),
322
- ],
323
- bundle: true,
324
- platform: 'node',
325
- target: 'node20',
326
- format: 'esm',
327
- outfile: join(packageRoot, 'dist/topics-cli.mjs'),
328
- minify: true,
329
- sourcemap: false,
330
- legalComments: 'none',
331
- banner,
332
- });
333
- } catch (error) {
334
- console.warn(
335
- '[build] topics-cli bundle could not be built (are the ' +
336
- 'linzumi-cli-rescript artifacts compiled?); `linzumi topics post` ' +
337
- `falls back to the package-source candidate: ${
338
- error instanceof Error ? error.message : String(error)
339
- }`
340
- );
341
- }
342
-
343
- // The in-guest CLOUD turn driver: a self-contained dist/cloud-supervisor.mjs
344
- // bundle that vmCloudSupervisor injects onto the persistent overlay of a
345
- // workspace-agent smolcloud VM and runs with a bare `node` (the guest's
346
- // node:22-bookworm-slim has node, but no npm packages beyond what provisioning
347
- // installs globally). Unlike dist/index.js it bundles EVERYTHING (no
348
- // `external`) so the guest needs no extra packages. It uses `ws` (bundled) for
349
- // the guest-loopback codex websocket. Same .mjs rationale as mcp-server.mjs:
350
- // the bundle is ESM (banner createRequire + node:* built-in imports) and lives
351
- // next to CJS files with no package.json in scope, so the explicit .mjs makes
352
- // Node treat it as ESM regardless of Node version.
353
- await build({
354
- entryPoints: [join(packageRoot, 'src/cloudSupervisorEntry.ts')],
355
- bundle: true,
356
- platform: 'node',
357
- target: 'node20',
358
- format: 'esm',
359
- outfile: join(packageRoot, 'dist/impl-ts/cloud-supervisor.mjs'),
360
- minify: true,
361
- sourcemap: false,
362
- legalComments: 'none',
363
- banner,
364
- });
365
-
366
- // The in-guest bundles are impl-AGNOSTIC guest artifacts (they run inside the
367
- // VM, not inside either commander), so the impl-res dist carries the same
368
- // bytes: its vmCloudSupervisor twin resolves siblings of ITS bundle exactly
369
- // like the TS one does (resolveDistBundle / dirname(import.meta.url)).
370
- await mkdir(join(packageRoot, 'dist/impl-res'), { recursive: true });
371
- await copyFile(
372
- join(packageRoot, 'dist/impl-ts/cloud-supervisor.mjs'),
373
- join(packageRoot, 'dist/impl-res/cloud-supervisor.mjs')
374
- );
375
-
376
- // F5 cross-harness failover bridge (Workstream F task F5): the runner
377
- // dynamic-imports ./crossHarnessFailover.mjs relative to its own bundle, so
378
- // dist ships a self-contained sibling bundle with the compiled ReScript
379
- // transpiler artifacts (packages/linzumi-cli-rescript, built by `rescript`)
380
- // resolved at build time. When the artifacts are not built in this checkout
381
- // the publish must not silently ship a broken rung: emit an HONEST stub
382
- // whose import succeeds but whose executor refuses loudly - the ladder then
383
- // terminates red-with-receipts exactly as F2 (the flag's rollback posture) -
384
- // and warn in the build log.
385
- try {
124
+ await rm(join(packageRoot, 'dist'), { recursive: true, force: true });
125
+ await mkdir(join(packageRoot, 'dist/native/darwin'), { recursive: true });
126
+ await copyFile(identityAsset, join(packageRoot, 'dist/native/darwin/preview-process-identity'));
127
+ await chmod(join(packageRoot, 'dist/native/darwin/preview-process-identity'), 0o755);
128
+ await copyFile(identityManifestPath, join(packageRoot, 'dist/native/darwin/manifest.json'));
129
+ await mkdir(join(packageRoot, 'dist/assets'), { recursive: true });
130
+ await copyFile(join(packageRoot, 'src/assets/linzumi-logo.svg'), join(packageRoot, 'dist/assets/linzumi-logo.svg'));
131
+
132
+ for (const [source, output, external] of [
133
+ ['entry/CommanderEntryMain.res.mjs', 'index.js', ['ws', 'undici', 'blessed', '@anthropic-ai/claude-agent-sdk']],
134
+ ['mcp/CommanderMcpEntry.res.mjs', 'mcp-server.mjs', []],
135
+ ['worker/CommanderHostedAccountWorker.res.mjs', 'hosted-coding-account-worker.js', []],
136
+ ['topics/CommanderTopicsCli.res.mjs', 'topics-cli.mjs', []],
137
+ ['forwarding/CommanderTcpTunnelCli.res.mjs', 'tcp-tunnel-cli.mjs', ['ws', 'undici']],
138
+ ]) {
386
139
  await build({
387
- entryPoints: [join(packageRoot, 'src/crossHarnessFailover.mjs')],
140
+ entryPoints: [join(commanderSource, source)],
388
141
  bundle: true,
389
142
  platform: 'node',
390
143
  target: 'node20',
391
144
  format: 'esm',
392
- outfile: join(packageRoot, 'dist/impl-ts/crossHarnessFailover.mjs'),
145
+ outfile: join(packageRoot, 'dist', output),
393
146
  minify: true,
394
147
  sourcemap: false,
395
148
  legalComments: 'none',
149
+ external,
396
150
  banner,
151
+ define: {'process.env.LINZUMI_CLI_VERSION': JSON.stringify(packageVersion)},
397
152
  });
398
- } catch (error) {
399
- console.warn(
400
- '[build] cross-harness failover bridge could not be bundled (are the ' +
401
- 'linzumi-cli-rescript artifacts built?); shipping the honest refusal ' +
402
- `stub instead: ${error instanceof Error ? error.message : String(error)}`
403
- );
404
- await mkdir(join(packageRoot, 'dist/impl-ts'), { recursive: true });
405
- await writeFile(
406
- join(packageRoot, 'dist/impl-ts/crossHarnessFailover.mjs'),
407
- 'export async function transpileAndSeedClaudeSession() {\n' +
408
- ' return {\n' +
409
- ' ok: false,\n' +
410
- " failure: 'the cross-harness failover transpiler bridge was not bundled into this build',\n" +
411
- ' };\n' +
412
- '}\n' +
413
- 'export async function seedCanonicalTranscriptIntoClaudeSession() {\n' +
414
- ' return {\n' +
415
- ' ok: false,\n' +
416
- " failure: 'the canonical Claude rehydration bridge was not bundled into this build',\n" +
417
- ' };\n' +
418
- '}\n'
419
- );
420
153
  }
421
- // The F5 bridge is self-contained; the impl-res commander resolves the same
422
- // sibling name next to ITS bundle.
423
- await copyFile(
424
- join(packageRoot, 'dist/impl-ts/crossHarnessFailover.mjs'),
425
- join(packageRoot, 'dist/impl-res/crossHarnessFailover.mjs')
426
- );
427
-
428
- for (const implDir of ['dist/impl-ts', 'dist/impl-res']) {
429
- await mkdir(join(packageRoot, implDir, 'assets'), { recursive: true });
430
- await copyFile(
431
- join(packageRoot, 'src/assets/linzumi-logo.svg'),
432
- join(packageRoot, implDir, 'assets/linzumi-logo.svg')
433
- );
434
- }
435
-
436
- // M3 cluster 15: the ReScript commander itself - dist/impl-res/index.js,
437
- // bundled from the compiled composition-root entry
438
- // (packages/linzumi-cli-rescript/src/entry/CommanderEntryMain.res). Same
439
- // externals/banner posture as the TS impl bundle: the runtime deps stay
440
- // external (they ship in the package's dependency closure), everything else
441
- // is inlined. When the ReScript artifacts are not compiled in this checkout
442
- // the build ships an HONEST refusal stub whose import succeeds but whose
443
- // main() refuses loudly - the chooser's short-lived-exit breaker then falls
444
- // back to TS (never a silent wrong impl). Publish CI compiles the ReScript
445
- // package first and the package-size gate asserts the REAL bundle shipped,
446
- // so the stub can never reach npm.
447
- try {
448
- await build({
449
- entryPoints: [
450
- join(
451
- packageRoot,
452
- '../linzumi-cli-rescript/src/entry/CommanderEntryMain.res.mjs'
453
- ),
454
- ],
455
- bundle: true,
456
- platform: 'node',
457
- target: 'node20',
458
- format: 'esm',
459
- outfile: join(packageRoot, 'dist/impl-res/index.js'),
460
- minify: true,
461
- sourcemap: false,
462
- legalComments: 'none',
463
- external: ['ws', 'undici', 'blessed', '@anthropic-ai/claude-agent-sdk'],
464
- banner,
465
- define: {
466
- 'process.env.LINZUMI_CLI_VERSION': JSON.stringify(packageVersion),
467
- },
468
- });
469
- } catch (error) {
470
- console.warn(
471
- '[build] impl-res commander bundle could not be built (are the ' +
472
- 'linzumi-cli-rescript artifacts compiled?); shipping the honest ' +
473
- `refusal stub instead: ${error instanceof Error ? error.message : String(error)}`
474
- );
475
- await writeFile(
476
- join(packageRoot, 'dist/impl-res/index.js'),
477
- 'export async function main() {\n' +
478
- " throw new Error('the rescript commander implementation was not bundled into this build');\n" +
479
- '}\n'
480
- );
154
+ for (const [source, output] of [
155
+ ['remoteCodexHarnessWorkerEntrypoint.ts', 'remote-codex-harness-worker.js'],
156
+ ['cloudSupervisorEntry.ts', 'cloud-supervisor.mjs'],
157
+ ['crossHarnessFailover.mjs', 'crossHarnessFailover.mjs'],
158
+ ]) {
159
+ await build({entryPoints: [join(packageRoot, 'src', source)], bundle: true,
160
+ platform: 'node', target: 'node20', format: 'esm',
161
+ outfile: join(packageRoot, 'dist', output), minify: true,
162
+ sourcemap: false, legalComments: 'none', banner,
163
+ external: output === 'remote-codex-harness-worker.js' ? ['ws', 'undici', 'blessed', '@anthropic-ai/claude-agent-sdk'] : []});
481
164
  }
482
-
483
- // LOAD-SMOKE TOOTH (prod RCA 2026-08-06): parsing alone did not catch a
484
- // createRequire dependency that was absent from the Nix image. The published
485
- // package has a separate build recipe, so import its fully installed commander
486
- // bundle here as well; any eager MODULE_NOT_FOUND or other load-time failure
487
- // fails the build.
488
- await import(pathToFileURL(join(packageRoot, 'dist/impl-ts/index.js')).href);
165
+ await import(pathToFileURL(join(packageRoot, 'dist/index.js')).href);
@@ -1,3 +1,4 @@
1
+ // Spec: spec/single-commander.md. Retained operational version pins.
1
2
  // Single-command CLI version bump.
2
3
  //
3
4
  // Releasing the CLI:
@@ -5,7 +6,8 @@
5
6
  // this script too, so a REAL version literal here would collide with the
6
7
  // live package version and abort a bump.)
7
8
  //
8
- // 1. pnpm bump-version <new-version> (for example: 1.2.3-beta)
9
+ // 1. pnpm bump-version <new-version> --spec plans/<release-spec>.md
10
+ // (for example: 1.2.3-beta with its approved release spec)
9
11
  // 2. Run the test suite: pnpm vitest run --config vitest.config.mjs
10
12
  // 3. Commit with a "[deploy-commander]" title and merge to main (the
11
13
  // deprecated "[deploy-cli]" marker was retired in the N3.9 window on
@@ -21,8 +23,10 @@
21
23
  // drift past a release.
22
24
  //
23
25
  // Flags:
26
+ // --spec <path> Required standalone release spec under plans/.
24
27
  // --dry-run Plan and verify every rewrite without writing files.
25
28
  // --root <dir> Operate on a different package root (used by tests).
29
+ // Release contract: plans/2026-07-12-linzumi-unscoped-npm-package-spec.md
26
30
  import { readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
27
31
  import { dirname, join, relative } from 'node:path';
28
32
  import { fileURLToPath } from 'node:url';
@@ -33,11 +37,12 @@ function fail(message) {
33
37
  }
34
38
 
35
39
  const usage =
36
- 'usage: node scripts/bump-cli-version.mjs <new-version> [--dry-run] [--root <dir>]';
40
+ 'usage: node scripts/bump-cli-version.mjs <new-version> --spec plans/<release-spec>.md [--dry-run] [--root <dir>]';
37
41
 
38
42
  const rawArgs = process.argv.slice(2);
39
43
  let dryRun = false;
40
44
  let explicitRoot;
45
+ let releaseSpec;
41
46
  const positional = [];
42
47
 
43
48
  for (let index = 0; index < rawArgs.length; index += 1) {
@@ -45,6 +50,13 @@ for (let index = 0; index < rawArgs.length; index += 1) {
45
50
 
46
51
  if (arg === '--dry-run') {
47
52
  dryRun = true;
53
+ } else if (arg === '--spec') {
54
+ releaseSpec = rawArgs[index + 1];
55
+ index += 1;
56
+
57
+ if (releaseSpec === undefined) {
58
+ fail(`--spec requires a standalone release spec under plans/\n${usage}`);
59
+ }
48
60
  } else if (arg === '--root') {
49
61
  explicitRoot = rawArgs[index + 1];
50
62
  index += 1;
@@ -75,6 +87,26 @@ const packageRoot =
75
87
  explicitRoot ?? dirname(dirname(fileURLToPath(import.meta.url)));
76
88
  const aliasPackageRoot = join(dirname(packageRoot), 'linzumi');
77
89
 
90
+ if (
91
+ releaseSpec === undefined ||
92
+ !/^plans\/[A-Za-z0-9][A-Za-z0-9._/-]*\.md$/.test(releaseSpec) ||
93
+ releaseSpec.split('/').some((part) => part === '.' || part === '..')
94
+ ) {
95
+ fail(`--spec requires a standalone release spec under plans/\n${usage}`);
96
+ }
97
+
98
+ try {
99
+ if (
100
+ readFileSync(join(packageRoot, '../..', releaseSpec), 'utf8').trim() === ''
101
+ ) {
102
+ fail(`--spec requires a nonempty standalone release spec: ${releaseSpec}`);
103
+ }
104
+ } catch (error) {
105
+ fail(
106
+ `--spec requires a readable standalone release spec: ${releaseSpec} (${error.message})`
107
+ );
108
+ }
109
+
78
110
  let manifest;
79
111
 
80
112
  try {
@@ -141,12 +173,6 @@ const knownSites = [
141
173
  pattern: `expect(manifest.version).toBe('${oldVersion}');`,
142
174
  replacement: `expect(manifest.version).toBe('${newVersion}');`,
143
175
  },
144
- {
145
- file: 'test/cli.test.ts',
146
- description: '--version output expectation',
147
- pattern: `'linzumi ${oldVersion}'`,
148
- replacement: `'linzumi ${newVersion}'`,
149
- },
150
176
  {
151
177
  file: 'test/runnerBridge.test.ts',
152
178
  description: 'runner join metadata version pin',
@@ -193,7 +219,7 @@ function appendVersionSpecComment(content) {
193
219
  const today = new Date().toISOString().slice(0, 10);
194
220
  const entry = [
195
221
  `- Date: ${today}`,
196
- ' Spec: scripts/bump-cli-version.mjs (automated version bump)',
222
+ ` Spec: ${releaseSpec}`,
197
223
  ` Relationship: Bumps the published CLI version so the`,
198
224
  ' current mainline CLI can be released under a fresh immutable npm',
199
225
  ' version with `[deploy-commander]`.',
@@ -1,25 +1,4 @@
1
- /*
2
- - Date: 2026-07-15
3
- Spec: kandan/plans/2026-07-10-compute-nodes-rescript-commander-zero-ts-program.md
4
- (M3 packaging paragraph: "Package-size budget in publish CI")
5
- Relationship: The M3 cluster 15 publish-CI package-size gate. Runs
6
- `npm pack --dry-run --json`, enforces the dual-impl package budget with the
7
- warn-band shape of test/ciBudget.ts (warn at 85% of the fail limit,
8
- actionable per-file top-offender breakdown - never a bare at-limit
9
- assertion; see the 2026-07-12 "expected 1000 to be less than 1000" staging
10
- red), and asserts the dual-impl packaging PARITY facts: both impl bundles
11
- ship for real (the impl-res honest-refusal stub that dev builds emit when
12
- the ReScript artifacts are not compiled must never reach npm), the chooser
13
- stays tiny, and the guest-bundle ladder files exist.
14
-
15
- Budget rationale (measured 2026-07-15 on this branch): the dual-impl
16
- package measures ~4.96 MB unpacked with the impl-res stub; the real
17
- impl-res commander bundle lands well under 3 MB (it bundles the same code
18
- the ~2.26 MB impl-ts bundle carries, compiled from ReScript). 12 MB
19
- unpacked caps runaway growth at roughly 1.5x the projected dual-impl size
20
- while leaving the dual-impl period real headroom; the 85% warn band
21
- (10.2 MB) makes erosion loud before it reds a publish.
22
- */
1
+ // Spec: spec/single-commander.md. One complete Commander bundle.
23
2
  import { execFileSync } from 'node:child_process';
24
3
  import { dirname } from 'node:path';
25
4
  import { fileURLToPath, pathToFileURL } from 'node:url';
@@ -28,42 +7,22 @@ const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
28
7
 
29
8
  export const UNPACKED_SIZE_LIMIT_BYTES = 12 * 1024 * 1024;
30
9
  export const WARN_RATIO = 0.85;
31
- // The chooser must stay a TINY plain-JS shim (it runs before either impl
32
- // bundle loads); a fat dist/index.js means impl code leaked into it.
33
- export const CHOOSER_SIZE_LIMIT_BYTES = 64 * 1024;
34
- // A real commander bundle is megabytes; the honest-refusal stub is ~125
35
- // bytes. Anything under this floor is not a real implementation.
36
10
  export const REAL_IMPL_BUNDLE_FLOOR_BYTES = 200 * 1024;
37
11
 
38
12
  const requiredFiles = [
39
13
  'bin/linzumi.js',
40
- 'bin/remote-codex-harness-worker.js',
41
14
  'dist/index.js',
42
- 'dist/impl-ts/index.js',
43
- 'dist/impl-res/index.js',
15
+ 'dist/remote-codex-harness-worker.js',
16
+ 'dist/hosted-coding-account-worker.js',
17
+ 'dist/cloud-supervisor.mjs',
18
+ 'dist/crossHarnessFailover.mjs',
44
19
  'dist/mcp-server.mjs',
45
- 'dist/impl-ts/mcp-server.mjs',
46
- 'dist/impl-res/mcp-server.mjs',
47
- 'dist/impl-ts/crossHarnessFailover.mjs',
48
- 'dist/impl-res/crossHarnessFailover.mjs',
49
- 'dist/impl-ts/cloud-supervisor.mjs',
50
- 'dist/impl-res/cloud-supervisor.mjs',
20
+ 'dist/topics-cli.mjs',
21
+ 'dist/tcp-tunnel-cli.mjs',
22
+ 'dist/native/darwin/preview-process-identity',
23
+ 'dist/assets/linzumi-logo.svg',
51
24
  ];
52
-
53
- // Per-file real-bundle floors (Greptile P2): the co-located guest bundles
54
- // have honest-stub fallbacks in the build too (the F5 refusal stub is ~200
55
- // bytes; the real bridge is ~8 KB; the real cloud supervisor is ~93 KB), so
56
- // each gets a floor that distinguishes stub from real - a botched build must
57
- // red the publish, not ship a refusal stub to the fleet.
58
- export const GUEST_BUNDLE_FLOOR_BYTES = {
59
- 'dist/impl-ts/crossHarnessFailover.mjs': 2 * 1024,
60
- 'dist/impl-res/crossHarnessFailover.mjs': 2 * 1024,
61
- 'dist/impl-ts/cloud-supervisor.mjs': 20 * 1024,
62
- 'dist/impl-res/cloud-supervisor.mjs': 20 * 1024,
63
- 'dist/mcp-server.mjs': 100 * 1024,
64
- 'dist/impl-ts/mcp-server.mjs': 100 * 1024,
65
- 'dist/impl-res/mcp-server.mjs': 100 * 1024,
66
- };
25
+ export const GUEST_BUNDLE_FLOOR_BYTES = {'dist/mcp-server.mjs': 100 * 1024, 'dist/crossHarnessFailover.mjs': 1024};
67
26
 
68
27
  function formatMb(bytes) {
69
28
  return `${Math.round((bytes / (1024 * 1024)) * 100) / 100} MB`;
@@ -89,17 +48,10 @@ export function evaluatePackedPackage(packReport) {
89
48
  }
90
49
  }
91
50
 
92
- const chooserSize = files.get('dist/index.js') ?? 0;
93
- if (chooserSize > CHOOSER_SIZE_LIMIT_BYTES) {
94
- failures.push(
95
- `dist/index.js (the chooser) is ${chooserSize} bytes > ${CHOOSER_SIZE_LIMIT_BYTES}: impl code must not leak into the chooser shim`
96
- );
51
+ for (const path of files.keys()) {
52
+ if (/^dist\/impl-/.test(path)) failures.push(`obsolete implementation bundle: ${path}`);
97
53
  }
98
-
99
- for (const implBundle of [
100
- 'dist/impl-ts/index.js',
101
- 'dist/impl-res/index.js',
102
- ]) {
54
+ for (const implBundle of ['dist/index.js']) {
103
55
  const size = files.get(implBundle) ?? 0;
104
56
  if (size > 0 && size < REAL_IMPL_BUNDLE_FLOOR_BYTES) {
105
57
  failures.push(