@linzumi/cli 1.0.91 → 1.0.93
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 +432 -432
- package/package.json +1 -1
- package/scripts/build-differential-entry.mjs +160 -0
- package/scripts/differentialMutations.mjs +113 -0
package/package.json
CHANGED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
// Spec: kandan/plans/2026-07-10-compute-nodes-rescript-commander-zero-ts-program.md (M2.5 + M2.6)
|
|
2
|
+
// Relationship: Builds the TS-impl differential entry
|
|
3
|
+
// (src/differentialCommanderEntry.ts) into a single runnable bundle at
|
|
4
|
+
// .differential/impl-ts.mjs, mirroring the published dist/index.js build
|
|
5
|
+
// configuration (same platform/target/format/externals/banner) so the
|
|
6
|
+
// differential harness exercises the SAME bundling reality the shipped CLI
|
|
7
|
+
// has - this is the M3 packaging seam's dist/impl-ts precursor: the M3
|
|
8
|
+
// chooser design ships dist/impl-ts/ + dist/impl-res/ side by side, and the
|
|
9
|
+
// M2.5 differential runner's impl parameter selects between them (impl-res
|
|
10
|
+
// fails honestly until M3 cluster 1 lands).
|
|
11
|
+
//
|
|
12
|
+
// M2.6 mutation mode (`--mutation <name>`): applies ONE seeded behavior
|
|
13
|
+
// delta from scripts/differentialMutations.mjs as a build-time source patch
|
|
14
|
+
// and emits .differential/impl-ts.mutant-<name>.mjs instead. The harness's
|
|
15
|
+
// anti-false-green mutation check builds each mutant and asserts the
|
|
16
|
+
// differential suite goes RED on it. Every patch anchor must match the
|
|
17
|
+
// current source EXACTLY ONCE - zero or multiple matches fail the build
|
|
18
|
+
// loudly, so a commander refactor can never silently no-op a mutation.
|
|
19
|
+
//
|
|
20
|
+
// Unminified deliberately: the bundle is a local test artifact
|
|
21
|
+
// (.differential/ is gitignored, never published), and readable output makes
|
|
22
|
+
// differential findings debuggable.
|
|
23
|
+
import { copyFile, mkdir, readFile } from 'node:fs/promises';
|
|
24
|
+
import { dirname, join } from 'node:path';
|
|
25
|
+
import { fileURLToPath } from 'node:url';
|
|
26
|
+
import { build } from 'esbuild';
|
|
27
|
+
import { differentialMutations } from './differentialMutations.mjs';
|
|
28
|
+
|
|
29
|
+
const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
30
|
+
|
|
31
|
+
function parseArgs(argv) {
|
|
32
|
+
if (argv.length === 0) {
|
|
33
|
+
return { mutation: undefined };
|
|
34
|
+
}
|
|
35
|
+
if (argv[0] === '--mutation' && argv.length === 2) {
|
|
36
|
+
const mutation = differentialMutations.find(
|
|
37
|
+
(entry) => entry.name === argv[1]
|
|
38
|
+
);
|
|
39
|
+
if (mutation === undefined) {
|
|
40
|
+
const known = differentialMutations.map((entry) => entry.name);
|
|
41
|
+
throw new Error(
|
|
42
|
+
`unknown differential mutation ${JSON.stringify(argv[1])}; known mutations: ${known.join(', ')}`
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
return { mutation };
|
|
46
|
+
}
|
|
47
|
+
throw new Error(
|
|
48
|
+
'usage: node scripts/build-differential-entry.mjs [--mutation <name>]'
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const { mutation } = parseArgs(process.argv.slice(2));
|
|
53
|
+
|
|
54
|
+
// Count non-overlapping occurrences of `needle` in `haystack`. An empty
|
|
55
|
+
// needle is a registry authoring error, refused loudly (indexOf('') matches
|
|
56
|
+
// at every position, which would both loop forever here and make the
|
|
57
|
+
// exactly-once rule meaningless); the harness registry conformance pins the
|
|
58
|
+
// same rule at rest (Greptile finding on #3115).
|
|
59
|
+
function occurrences(haystack, needle) {
|
|
60
|
+
if (needle.length === 0) {
|
|
61
|
+
throw new Error('mutation patch `find` must be a non-empty string');
|
|
62
|
+
}
|
|
63
|
+
let count = 0;
|
|
64
|
+
let from = 0;
|
|
65
|
+
for (;;) {
|
|
66
|
+
const at = haystack.indexOf(needle, from);
|
|
67
|
+
if (at === -1) {
|
|
68
|
+
return count;
|
|
69
|
+
}
|
|
70
|
+
count += 1;
|
|
71
|
+
from = at + needle.length;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// esbuild onLoad plugin: apply the mutation's patches to their target
|
|
76
|
+
// sources, enforcing the exactly-once anchor rule per patch. Also asserts
|
|
77
|
+
// every patch was actually loaded by the bundle (a patched-but-unbundled
|
|
78
|
+
// file would be a silent no-op mutation).
|
|
79
|
+
function mutationPlugin(activeMutation) {
|
|
80
|
+
const patchesByFile = new Map();
|
|
81
|
+
for (const patch of activeMutation.patches) {
|
|
82
|
+
const absolute = join(packageRoot, patch.file);
|
|
83
|
+
const existing = patchesByFile.get(absolute) ?? [];
|
|
84
|
+
existing.push(patch);
|
|
85
|
+
patchesByFile.set(absolute, existing);
|
|
86
|
+
}
|
|
87
|
+
const applied = new Set();
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
plugin: {
|
|
91
|
+
name: 'differential-mutation',
|
|
92
|
+
setup(pluginBuild) {
|
|
93
|
+
pluginBuild.onLoad({ filter: /\.ts$/ }, async (args) => {
|
|
94
|
+
const patches = patchesByFile.get(args.path);
|
|
95
|
+
if (patches === undefined) {
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
let contents = await readFile(args.path, 'utf8');
|
|
99
|
+
for (const patch of patches) {
|
|
100
|
+
const found = occurrences(contents, patch.find);
|
|
101
|
+
if (found !== 1) {
|
|
102
|
+
throw new Error(
|
|
103
|
+
`mutation ${activeMutation.name}: anchor in ${patch.file} matched ${found} times (must match exactly once); the commander source moved - re-anchor the patch in scripts/differentialMutations.mjs`
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
contents = contents.replace(patch.find, patch.replace);
|
|
107
|
+
applied.add(`${patch.file}:${patch.find.slice(0, 40)}`);
|
|
108
|
+
}
|
|
109
|
+
return { contents, loader: 'ts' };
|
|
110
|
+
});
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
assertAllApplied() {
|
|
114
|
+
const expected = activeMutation.patches.length;
|
|
115
|
+
if (applied.size !== expected) {
|
|
116
|
+
throw new Error(
|
|
117
|
+
`mutation ${activeMutation.name}: only ${applied.size} of ${expected} patches were loaded into the bundle - a patched file is not part of the differential entry`
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Renamed banner import: unlike the minified dist build, this bundle keeps
|
|
125
|
+
// source-level identifiers, and the commander sources themselves import
|
|
126
|
+
// createRequire - the published banner's bare name would collide.
|
|
127
|
+
const banner = {
|
|
128
|
+
js: "import { createRequire as __differentialCreateRequire } from 'node:module';\nconst require = __differentialCreateRequire(import.meta.url);",
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const outfile =
|
|
132
|
+
mutation === undefined
|
|
133
|
+
? join(packageRoot, '.differential/impl-ts.mjs')
|
|
134
|
+
: join(packageRoot, `.differential/impl-ts.mutant-${mutation.name}.mjs`);
|
|
135
|
+
|
|
136
|
+
const mutationHooks = mutation === undefined ? undefined : mutationPlugin(mutation);
|
|
137
|
+
|
|
138
|
+
await build({
|
|
139
|
+
entryPoints: [join(packageRoot, 'src/differentialCommanderEntry.ts')],
|
|
140
|
+
bundle: true,
|
|
141
|
+
platform: 'node',
|
|
142
|
+
target: 'node20',
|
|
143
|
+
format: 'esm',
|
|
144
|
+
outfile,
|
|
145
|
+
minify: false,
|
|
146
|
+
sourcemap: false,
|
|
147
|
+
legalComments: 'none',
|
|
148
|
+
external: ['ws', 'undici', 'blessed', '@anthropic-ai/claude-agent-sdk'],
|
|
149
|
+
banner,
|
|
150
|
+
plugins: mutationHooks === undefined ? [] : [mutationHooks.plugin],
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
mutationHooks?.assertAllApplied();
|
|
154
|
+
|
|
155
|
+
// The runtime resolves assets/ relative to the bundle (same as dist/).
|
|
156
|
+
await mkdir(join(packageRoot, '.differential/assets'), { recursive: true });
|
|
157
|
+
await copyFile(
|
|
158
|
+
join(packageRoot, 'src/assets/linzumi-logo.svg'),
|
|
159
|
+
join(packageRoot, '.differential/assets/linzumi-logo.svg')
|
|
160
|
+
);
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// Spec: kandan/plans/2026-07-10-compute-nodes-rescript-commander-zero-ts-program.md (M2.6)
|
|
2
|
+
// Relationship: The maintained seeded-mutation set for the differential
|
|
3
|
+
// harness's anti-false-green mutation check. Each entry is a deliberate
|
|
4
|
+
// behavior delta patched into the TS commander AT BUILD TIME
|
|
5
|
+
// (scripts/build-differential-entry.mjs --mutation <name>); the harness's
|
|
6
|
+
// MutationCheckConformance then asserts the differential suite goes RED on
|
|
7
|
+
// the mutant (transcript diff for the shape/ordering classes, a named
|
|
8
|
+
// ported-invariant violation for the invariant class). A seeded delta the
|
|
9
|
+
// suite stays green on means the gate is lying - that conformance leg fails.
|
|
10
|
+
//
|
|
11
|
+
// This registry is the SINGLE source (derive, don't duplicate): the build
|
|
12
|
+
// script applies `patches`, and the harness conformance imports this exact
|
|
13
|
+
// module to drive and pin the set (closed classes, one mutation per class).
|
|
14
|
+
//
|
|
15
|
+
// MAINTENANCE RULES:
|
|
16
|
+
// - The set stays small (one mutation per class below) - every entry is a
|
|
17
|
+
// permanent CI cost, so each must earn its place by proving a DISTINCT
|
|
18
|
+
// sensitivity class of the differential canonical transcript.
|
|
19
|
+
// - `find` strings must match the current source EXACTLY ONCE; the build
|
|
20
|
+
// fails loudly on zero or multiple matches, so a refactor can never
|
|
21
|
+
// silently no-op a mutation (the anchor rot the exactly-once rule
|
|
22
|
+
// exists for).
|
|
23
|
+
// - Mutations must never remove an event a differential scenario AWAITS
|
|
24
|
+
// (the choreography synchronizes on milestones; deleting one turns a
|
|
25
|
+
// fast red into a slow timeout red).
|
|
26
|
+
// - No current-version literals (the commander-harness package rule).
|
|
27
|
+
//
|
|
28
|
+
// Classes (the M2.6 task's closed vocabulary):
|
|
29
|
+
// - transcript-shape: an event the commander silently stops emitting must
|
|
30
|
+
// change the canonical transcript bytes.
|
|
31
|
+
// - invariant: a behavior delta violating a ported ordering invariant
|
|
32
|
+
// must surface as that invariant's named violation.
|
|
33
|
+
// - ordering: a pure reorder of the commander's own send order (same
|
|
34
|
+
// event multiset) must change the canonical transcript bytes.
|
|
35
|
+
|
|
36
|
+
export const differentialMutations = [
|
|
37
|
+
{
|
|
38
|
+
name: 'message-state-processing-drop',
|
|
39
|
+
class: 'transcript-shape',
|
|
40
|
+
scenario: 'harness-session-rebuild',
|
|
41
|
+
proves:
|
|
42
|
+
'a silently dropped message-state publish (the msg-state ladder silent-drop class: the user-visible "processing / starting turn" paint never reaches the server) changes the canonical transcript - the differential diff can see a suppressed notification type',
|
|
43
|
+
patches: [
|
|
44
|
+
{
|
|
45
|
+
file: 'src/channelSession.ts',
|
|
46
|
+
find: ` await publishQueuedMessageState(
|
|
47
|
+
args,
|
|
48
|
+
state,
|
|
49
|
+
next,
|
|
50
|
+
{
|
|
51
|
+
status: 'processing',
|
|
52
|
+
reason: 'starting turn',
|
|
53
|
+
},
|
|
54
|
+
'sent'
|
|
55
|
+
);`,
|
|
56
|
+
replace: ` // [seeded differential mutation: message-state-processing-drop] the
|
|
57
|
+
// starting-turn processing paint is silently dropped.`,
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: 'join-roster-drop',
|
|
63
|
+
class: 'invariant',
|
|
64
|
+
expectInvariant: 'I319',
|
|
65
|
+
scenario: 'full-join-surfaces',
|
|
66
|
+
proves:
|
|
67
|
+
'a commander that silently drops the join-reply roster (the Commander-boots-into-empty-onboarding regression class the compute-nodes program exists to kill) under-delivers the display page and goes red as an I319 violation, not just a byte diff',
|
|
68
|
+
patches: [
|
|
69
|
+
{
|
|
70
|
+
file: 'src/runner.ts',
|
|
71
|
+
find: ` seedDashboardRosterFromReply(joinResponse, 'join');`,
|
|
72
|
+
replace: ` // [seeded differential mutation: join-roster-drop] the join-reply
|
|
73
|
+
// roster seed is silently dropped.`,
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
name: 'handshake-initialized-reorder',
|
|
79
|
+
class: 'ordering',
|
|
80
|
+
scenario: 'harness-clean-turn',
|
|
81
|
+
proves:
|
|
82
|
+
'a pure reorder of the commander\'s own send order (the codex app-server handshake: `initialized` notified BEFORE the `initialize` request instead of after its result) changes the canonical transcript even though the event multiset is identical - per-party send order is preserved evidence, not normalized away',
|
|
83
|
+
patches: [
|
|
84
|
+
{
|
|
85
|
+
file: 'src/codexAppServer.ts',
|
|
86
|
+
// The early frame below is byte-for-byte the wire shape of the
|
|
87
|
+
// pristine `initialized` literal in codexAppServer.ts, so this
|
|
88
|
+
// mutation stays a PURE reorder. Drift teeth (Greptile finding on
|
|
89
|
+
// #3115): if the real notification ever gains fields, the early
|
|
90
|
+
// frame's content diverges and MutationCheckConformance's
|
|
91
|
+
// ordering-class assertion (equal sorted-line multiset) goes red
|
|
92
|
+
// until this patch is re-synced - the payload cannot silently rot.
|
|
93
|
+
find: ` const response = await sendRequest(websocket, pending, {
|
|
94
|
+
jsonrpc: '2.0',
|
|
95
|
+
id: 'initialize',
|
|
96
|
+
method: 'initialize',`,
|
|
97
|
+
replace: ` // [seeded differential mutation: handshake-initialized-reorder] the
|
|
98
|
+
// initialized notification jumps AHEAD of the initialize request.
|
|
99
|
+
websocket.send(JSON.stringify({ jsonrpc: '2.0', method: 'initialized' }));
|
|
100
|
+
const response = await sendRequest(websocket, pending, {
|
|
101
|
+
jsonrpc: '2.0',
|
|
102
|
+
id: 'initialize',
|
|
103
|
+
method: 'initialize',`,
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
file: 'src/codexAppServer.ts',
|
|
107
|
+
find: ` websocket.send(JSON.stringify(initialized));`,
|
|
108
|
+
replace: ` // [seeded differential mutation: handshake-initialized-reorder] the
|
|
109
|
+
// post-result send is suppressed (it already went out early above).`,
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
},
|
|
113
|
+
];
|