ushman-equiv 0.4.0
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/AGENTS.md +81 -0
- package/LICENSE.md +21 -0
- package/README.md +201 -0
- package/bin/ushman-equiv +19 -0
- package/dist/analysis-context.d.ts +102 -0
- package/dist/analysis-context.d.ts.map +1 -0
- package/dist/analysis-context.js +708 -0
- package/dist/ast-guards.d.ts +24 -0
- package/dist/ast-guards.d.ts.map +1 -0
- package/dist/ast-guards.js +83 -0
- package/dist/candidate-boot.d.ts +30 -0
- package/dist/candidate-boot.d.ts.map +1 -0
- package/dist/candidate-boot.js +262 -0
- package/dist/canonicalize.d.ts +19 -0
- package/dist/canonicalize.d.ts.map +1 -0
- package/dist/canonicalize.js +525 -0
- package/dist/cli.d.ts +4 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +312 -0
- package/dist/equiv-execution-context.d.ts +25 -0
- package/dist/equiv-execution-context.d.ts.map +1 -0
- package/dist/equiv-execution-context.js +82 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/run.d.ts +8 -0
- package/dist/run.d.ts.map +1 -0
- package/dist/run.js +129 -0
- package/dist/shared.d.ts +9 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.js +47 -0
- package/dist/tier-i-import-graph.d.ts +7 -0
- package/dist/tier-i-import-graph.d.ts.map +1 -0
- package/dist/tier-i-import-graph.js +34 -0
- package/dist/tier-l-child-runtime.d.ts +2 -0
- package/dist/tier-l-child-runtime.d.ts.map +1 -0
- package/dist/tier-l-child-runtime.js +62 -0
- package/dist/tier-l-module-load.d.ts +6 -0
- package/dist/tier-l-module-load.d.ts.map +1 -0
- package/dist/tier-l-module-load.js +139 -0
- package/dist/tier-l-stub-source.d.ts +11 -0
- package/dist/tier-l-stub-source.d.ts.map +1 -0
- package/dist/tier-l-stub-source.js +246 -0
- package/dist/tier-r-replay.d.ts +6 -0
- package/dist/tier-r-replay.d.ts.map +1 -0
- package/dist/tier-r-replay.js +382 -0
- package/dist/tier-s-symbol-diff.d.ts +19 -0
- package/dist/tier-s-symbol-diff.d.ts.map +1 -0
- package/dist/tier-s-symbol-diff.js +156 -0
- package/dist/types.d.ts +91 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +19 -0
- package/dist/workspace.d.ts +63 -0
- package/dist/workspace.d.ts.map +1 -0
- package/dist/workspace.js +459 -0
- package/package.json +64 -0
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
2
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
3
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
4
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
return path;
|
|
8
|
+
};
|
|
9
|
+
import { readdir } from 'node:fs/promises';
|
|
10
|
+
import { basename, extname, join } from 'node:path';
|
|
11
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
12
|
+
import { canonicalize, hydrateCanonicalized } from "./canonicalize.js";
|
|
13
|
+
import { createEquivExecutionContext } from "./equiv-execution-context.js";
|
|
14
|
+
import { compareStrings } from "./shared.js";
|
|
15
|
+
import { assertV4Workspace, chunk, createFilterMatcher, DEFAULT_EQUIV_MODE, DEFAULT_MAX_CONCURRENCY, MAX_MAX_CONCURRENCY, defaultFixturesDir, defaultModulesDir, directoryExists, displayPath, fileExists, readJsonFile, resolveWithinWorkspace, sanitizeSymbolName, sha256Text, toFileImportUrl, } from "./workspace.js";
|
|
16
|
+
const createFallbackModulePath = (modulesDir, fn) => join(modulesDir, `${sanitizeSymbolName(fn)}.mjs`);
|
|
17
|
+
const isReplayFixtureDocument = (value) => Boolean(value) &&
|
|
18
|
+
typeof value === 'object' &&
|
|
19
|
+
typeof value.fn === 'string' &&
|
|
20
|
+
Array.isArray(value.calls);
|
|
21
|
+
const resolveReplayMaxConcurrency = (value) => {
|
|
22
|
+
if (value === undefined) {
|
|
23
|
+
return DEFAULT_MAX_CONCURRENCY;
|
|
24
|
+
}
|
|
25
|
+
if (!Number.isInteger(value) || value <= 0) {
|
|
26
|
+
throw new Error('maxConcurrency must be a positive integer.');
|
|
27
|
+
}
|
|
28
|
+
if (value > MAX_MAX_CONCURRENCY) {
|
|
29
|
+
throw new Error(`maxConcurrency must be less than or equal to ${MAX_MAX_CONCURRENCY}.`);
|
|
30
|
+
}
|
|
31
|
+
return value;
|
|
32
|
+
};
|
|
33
|
+
const standardizeFixture = ({ fixturePath, modulesDir, value, workspaceRoot, }) => {
|
|
34
|
+
if (Array.isArray(value)) {
|
|
35
|
+
const fn = basename(fixturePath, extname(fixturePath));
|
|
36
|
+
return {
|
|
37
|
+
calls: value.map((entry) => ({
|
|
38
|
+
args: Array.isArray(entry.args) ? entry.args : [],
|
|
39
|
+
expected: 'expected' in entry ? entry.expected : undefined,
|
|
40
|
+
thisArg: 'thisArg' in entry ? entry.thisArg : undefined,
|
|
41
|
+
threw: 'threw' in entry ? entry.threw : undefined,
|
|
42
|
+
})),
|
|
43
|
+
fallbackModulePath: createFallbackModulePath(modulesDir, fn),
|
|
44
|
+
fixturePath,
|
|
45
|
+
fn,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
if (!isReplayFixtureDocument(value)) {
|
|
49
|
+
throw new Error(`Invalid replay fixture: ${fixturePath}`);
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
calls: value.calls.map((entry) => ({
|
|
53
|
+
args: Array.isArray(entry.args) ? entry.args : [],
|
|
54
|
+
expected: Object.hasOwn(entry, 'return') ? entry.return : entry.expected,
|
|
55
|
+
thisArg: entry.thisArg,
|
|
56
|
+
threw: entry.threw,
|
|
57
|
+
})),
|
|
58
|
+
fallbackModulePath: typeof value.module === 'string'
|
|
59
|
+
? resolveWithinWorkspace(workspaceRoot, value.module)
|
|
60
|
+
: createFallbackModulePath(modulesDir, value.fn),
|
|
61
|
+
fixturePath,
|
|
62
|
+
fn: value.fn,
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
const loadFixture = async ({ fixturePath, modulesDir, workspaceRoot, }) => {
|
|
66
|
+
const result = await readJsonFile(fixturePath);
|
|
67
|
+
if (result.status !== 'ok') {
|
|
68
|
+
throw new Error(result.status === 'missing' ? `Missing fixture ${fixturePath}` : result.message);
|
|
69
|
+
}
|
|
70
|
+
return standardizeFixture({
|
|
71
|
+
fixturePath,
|
|
72
|
+
modulesDir,
|
|
73
|
+
value: result.value,
|
|
74
|
+
workspaceRoot,
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
const loadExportedFunction = async ({ exportName, modulePath, salt, }) => {
|
|
78
|
+
if (!(await fileExists(modulePath))) {
|
|
79
|
+
throw new Error(`Missing module file: ${modulePath}`);
|
|
80
|
+
}
|
|
81
|
+
let importedModule;
|
|
82
|
+
try {
|
|
83
|
+
importedModule = (await import(__rewriteRelativeImportExtension(toFileImportUrl(modulePath, salt))));
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
throw new Error(`Unable to import ${modulePath} for ${exportName} (${error instanceof Error ? error.message : String(error)})`);
|
|
87
|
+
}
|
|
88
|
+
const exportedValue = importedModule[exportName];
|
|
89
|
+
if (typeof exportedValue !== 'function') {
|
|
90
|
+
throw new Error(`Module does not export a callable ${exportName}.`);
|
|
91
|
+
}
|
|
92
|
+
return exportedValue;
|
|
93
|
+
};
|
|
94
|
+
const resolveReplayTarget = async ({ exportFiles, fixture, workspaceRoot, }) => {
|
|
95
|
+
const failedAttempts = [];
|
|
96
|
+
const attemptedModules = [];
|
|
97
|
+
const seenModules = new Set();
|
|
98
|
+
const candidateModulePath = exportFiles.get(fixture.fn);
|
|
99
|
+
for (const modulePath of [candidateModulePath, fixture.fallbackModulePath].filter((value) => typeof value === 'string' && value.length > 0)) {
|
|
100
|
+
if (seenModules.has(modulePath)) {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
seenModules.add(modulePath);
|
|
104
|
+
attemptedModules.push(modulePath);
|
|
105
|
+
try {
|
|
106
|
+
return {
|
|
107
|
+
findings: [],
|
|
108
|
+
targetFunction: await loadExportedFunction({
|
|
109
|
+
exportName: fixture.fn,
|
|
110
|
+
modulePath,
|
|
111
|
+
salt: `${fixture.fn}-${attemptedModules.length}`,
|
|
112
|
+
}),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
failedAttempts.push({
|
|
117
|
+
evidence: error instanceof Error ? error.message : String(error),
|
|
118
|
+
message: `Failed to load ${fixture.fn} from ${displayPath(workspaceRoot, modulePath)}.`,
|
|
119
|
+
path: displayPath(workspaceRoot, modulePath),
|
|
120
|
+
symbol: fixture.fn,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
failedAttempts.push({
|
|
125
|
+
evidence: attemptedModules.map((modulePath) => displayPath(workspaceRoot, modulePath)),
|
|
126
|
+
message: `Unable to resolve a callable export for ${fixture.fn}.`,
|
|
127
|
+
path: attemptedModules[0]
|
|
128
|
+
? displayPath(workspaceRoot, attemptedModules[0])
|
|
129
|
+
: displayPath(workspaceRoot, fixture.fixturePath),
|
|
130
|
+
symbol: fixture.fn,
|
|
131
|
+
});
|
|
132
|
+
return {
|
|
133
|
+
findings: failedAttempts,
|
|
134
|
+
targetFunction: null,
|
|
135
|
+
};
|
|
136
|
+
};
|
|
137
|
+
const stableEvidenceString = (value) => {
|
|
138
|
+
try {
|
|
139
|
+
const serialized = JSON.stringify(canonicalize(value));
|
|
140
|
+
return serialized === undefined ? 'undefined' : serialized;
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
const serialized = JSON.stringify(value);
|
|
144
|
+
return serialized === undefined ? String(value) : serialized;
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
const createReplayDriftFinding = ({ actual, expected, fixture, message, workspaceRoot, }) => ({
|
|
148
|
+
evidence: {
|
|
149
|
+
actual,
|
|
150
|
+
actualHash: sha256Text(stableEvidenceString(actual)),
|
|
151
|
+
expected,
|
|
152
|
+
expectedHash: sha256Text(stableEvidenceString(expected)),
|
|
153
|
+
},
|
|
154
|
+
message,
|
|
155
|
+
path: displayPath(workspaceRoot, fixture.fixturePath),
|
|
156
|
+
symbol: fixture.fn,
|
|
157
|
+
});
|
|
158
|
+
const runReplayCall = async ({ fixture, index, targetFunction, workspaceRoot, }) => {
|
|
159
|
+
const call = fixture.calls[index];
|
|
160
|
+
const args = call.args.map((entry) => hydrateCanonicalized(entry));
|
|
161
|
+
const thisArg = hydrateCanonicalized(call.thisArg);
|
|
162
|
+
try {
|
|
163
|
+
const value = await Promise.resolve(targetFunction.apply(thisArg, args));
|
|
164
|
+
if (call.threw !== undefined && call.threw !== null) {
|
|
165
|
+
return {
|
|
166
|
+
finding: createReplayDriftFinding({
|
|
167
|
+
actual: canonicalize(value),
|
|
168
|
+
expected: call.threw,
|
|
169
|
+
fixture,
|
|
170
|
+
message: `Replay expected ${fixture.fn} call #${index + 1} to throw, but it returned.`,
|
|
171
|
+
workspaceRoot,
|
|
172
|
+
}),
|
|
173
|
+
kind: 'drift',
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
const actual = canonicalize(value);
|
|
177
|
+
if (isDeepStrictEqual(actual, call.expected)) {
|
|
178
|
+
return { finding: null, kind: 'matched' };
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
finding: createReplayDriftFinding({
|
|
182
|
+
actual,
|
|
183
|
+
expected: call.expected,
|
|
184
|
+
fixture,
|
|
185
|
+
message: `Replay drift for ${fixture.fn} call #${index + 1}.`,
|
|
186
|
+
workspaceRoot,
|
|
187
|
+
}),
|
|
188
|
+
kind: 'drift',
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
if (call.threw !== undefined && call.threw !== null) {
|
|
193
|
+
const actual = canonicalize(error);
|
|
194
|
+
if (isDeepStrictEqual(actual, call.threw)) {
|
|
195
|
+
return { finding: null, kind: 'matched' };
|
|
196
|
+
}
|
|
197
|
+
return {
|
|
198
|
+
finding: createReplayDriftFinding({
|
|
199
|
+
actual,
|
|
200
|
+
expected: call.threw,
|
|
201
|
+
fixture,
|
|
202
|
+
message: `Replay threw a different value for ${fixture.fn} call #${index + 1}.`,
|
|
203
|
+
workspaceRoot,
|
|
204
|
+
}),
|
|
205
|
+
kind: 'drift',
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
return {
|
|
209
|
+
finding: {
|
|
210
|
+
evidence: error instanceof Error ? (error.stack ?? error.message) : String(error),
|
|
211
|
+
message: `Replay threw for ${fixture.fn} call #${index + 1}.`,
|
|
212
|
+
path: displayPath(workspaceRoot, fixture.fixturePath),
|
|
213
|
+
symbol: fixture.fn,
|
|
214
|
+
},
|
|
215
|
+
kind: 'error',
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
const processFixture = async ({ exportFiles, fixture, workspaceRoot, }) => {
|
|
220
|
+
const { findings, targetFunction } = await resolveReplayTarget({
|
|
221
|
+
exportFiles,
|
|
222
|
+
fixture,
|
|
223
|
+
workspaceRoot,
|
|
224
|
+
});
|
|
225
|
+
if (!targetFunction) {
|
|
226
|
+
return { driftCount: 0, errorCount: findings.length, findings, matched: 0 };
|
|
227
|
+
}
|
|
228
|
+
let driftCount = 0;
|
|
229
|
+
let errorCount = findings.length;
|
|
230
|
+
let matched = 0;
|
|
231
|
+
for (const [index] of fixture.calls.entries()) {
|
|
232
|
+
const result = await runReplayCall({
|
|
233
|
+
fixture,
|
|
234
|
+
index,
|
|
235
|
+
targetFunction,
|
|
236
|
+
workspaceRoot,
|
|
237
|
+
});
|
|
238
|
+
if (result.kind === 'matched') {
|
|
239
|
+
matched += 1;
|
|
240
|
+
}
|
|
241
|
+
if (result.kind === 'drift') {
|
|
242
|
+
driftCount += 1;
|
|
243
|
+
}
|
|
244
|
+
if (result.kind === 'error') {
|
|
245
|
+
errorCount += 1;
|
|
246
|
+
}
|
|
247
|
+
if (result.finding) {
|
|
248
|
+
findings.push(result.finding);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return { driftCount, errorCount, findings, matched };
|
|
252
|
+
};
|
|
253
|
+
const loadSelectedFixtures = async ({ fixturesDir, matcher, modulesDir, workspaceRoot, }) => {
|
|
254
|
+
const fixtureFiles = (await readdir(fixturesDir))
|
|
255
|
+
.filter((entry) => entry.endsWith('.json'))
|
|
256
|
+
.map((entry) => join(fixturesDir, entry))
|
|
257
|
+
.sort(compareStrings);
|
|
258
|
+
const results = await Promise.all(fixtureFiles.map(async (fixturePath) => {
|
|
259
|
+
try {
|
|
260
|
+
const fixture = await loadFixture({
|
|
261
|
+
fixturePath,
|
|
262
|
+
modulesDir,
|
|
263
|
+
workspaceRoot,
|
|
264
|
+
});
|
|
265
|
+
return matcher(fixture.fn) ? { fixture } : {};
|
|
266
|
+
}
|
|
267
|
+
catch (error) {
|
|
268
|
+
return {
|
|
269
|
+
detail: {
|
|
270
|
+
evidence: error instanceof Error ? error.message : String(error),
|
|
271
|
+
message: `Unable to load replay fixture ${displayPath(workspaceRoot, fixturePath)}.`,
|
|
272
|
+
path: displayPath(workspaceRoot, fixturePath),
|
|
273
|
+
},
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
}));
|
|
277
|
+
return {
|
|
278
|
+
details: results.flatMap((result) => (result.detail ? [result.detail] : [])),
|
|
279
|
+
fixtures: results.flatMap((result) => (result.fixture ? [result.fixture] : [])),
|
|
280
|
+
};
|
|
281
|
+
};
|
|
282
|
+
const createReplayBootFailure = ({ diagnostics, elapsedMs, mode, }) => ({
|
|
283
|
+
details: [
|
|
284
|
+
{
|
|
285
|
+
evidence: diagnostics,
|
|
286
|
+
message: `Candidate ${mode} boot failed before tier R replay started.`,
|
|
287
|
+
},
|
|
288
|
+
],
|
|
289
|
+
elapsedMs,
|
|
290
|
+
severity: 'red',
|
|
291
|
+
summary: `mode=${mode}, booted=false, fixtures=0, matched=0, drift=0, errors=1`,
|
|
292
|
+
tier: 'R',
|
|
293
|
+
});
|
|
294
|
+
const resolveReplayDirectories = (options) => ({
|
|
295
|
+
fixturesDir: options.fixturesDir
|
|
296
|
+
? resolveWithinWorkspace(options.workspaceRoot, options.fixturesDir)
|
|
297
|
+
: defaultFixturesDir(options.workspaceRoot),
|
|
298
|
+
modulesDir: options.modulesDir
|
|
299
|
+
? resolveWithinWorkspace(options.workspaceRoot, options.modulesDir)
|
|
300
|
+
: defaultModulesDir(options.workspaceRoot),
|
|
301
|
+
});
|
|
302
|
+
const checkReplayWithContext = async (options, context) => {
|
|
303
|
+
const start = Date.now();
|
|
304
|
+
await assertV4Workspace(options.workspaceRoot);
|
|
305
|
+
const mode = options.mode ?? DEFAULT_EQUIV_MODE;
|
|
306
|
+
const bootResult = await context.getCandidateBoot({
|
|
307
|
+
mode,
|
|
308
|
+
workspaceRoot: options.workspaceRoot,
|
|
309
|
+
});
|
|
310
|
+
if (!bootResult.ok) {
|
|
311
|
+
return createReplayBootFailure({
|
|
312
|
+
diagnostics: bootResult.diagnostics,
|
|
313
|
+
elapsedMs: Date.now() - start,
|
|
314
|
+
mode,
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
const { fixturesDir, modulesDir } = resolveReplayDirectories(options);
|
|
318
|
+
const details = [];
|
|
319
|
+
if (!(await directoryExists(fixturesDir))) {
|
|
320
|
+
details.push({
|
|
321
|
+
message: `Replay fixtures directory is missing: ${displayPath(options.workspaceRoot, fixturesDir)}. Generate fixtures under .lab/characterize/replay before relying on tier R.`,
|
|
322
|
+
path: displayPath(options.workspaceRoot, fixturesDir),
|
|
323
|
+
});
|
|
324
|
+
return {
|
|
325
|
+
details,
|
|
326
|
+
elapsedMs: Date.now() - start,
|
|
327
|
+
severity: 'yellow',
|
|
328
|
+
summary: `mode=${mode}, booted=true, fixtures=0, matched=0, drift=0, errors=1`,
|
|
329
|
+
tier: 'R',
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
const matcher = createFilterMatcher(options.filter);
|
|
333
|
+
const loadedFixtures = await loadSelectedFixtures({
|
|
334
|
+
fixturesDir,
|
|
335
|
+
matcher,
|
|
336
|
+
modulesDir,
|
|
337
|
+
workspaceRoot: options.workspaceRoot,
|
|
338
|
+
});
|
|
339
|
+
const fixtures = loadedFixtures.fixtures;
|
|
340
|
+
details.push(...loadedFixtures.details);
|
|
341
|
+
if (fixtures.length === 0) {
|
|
342
|
+
details.push({
|
|
343
|
+
message: `Replay fixtures directory is present but empty after filtering: ${displayPath(options.workspaceRoot, fixturesDir)}`,
|
|
344
|
+
path: displayPath(options.workspaceRoot, fixturesDir),
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
const candidateSymbols = await context.getCandidateSymbols({
|
|
348
|
+
entrypoint: options.entrypoint,
|
|
349
|
+
filter: options.filter,
|
|
350
|
+
srcRoots: options.srcRoots,
|
|
351
|
+
workspaceRoot: options.workspaceRoot,
|
|
352
|
+
});
|
|
353
|
+
details.push(...candidateSymbols.errors);
|
|
354
|
+
details.push(...candidateSymbols.warnings);
|
|
355
|
+
let driftCount = 0;
|
|
356
|
+
let errorCount = loadedFixtures.details.length + candidateSymbols.errors.length;
|
|
357
|
+
let matched = 0;
|
|
358
|
+
const maxConcurrency = resolveReplayMaxConcurrency(options.maxConcurrency);
|
|
359
|
+
for (const fixtureChunk of chunk(fixtures, maxConcurrency)) {
|
|
360
|
+
const results = await Promise.all(fixtureChunk.map((fixture) => processFixture({
|
|
361
|
+
exportFiles: candidateSymbols.exportFiles,
|
|
362
|
+
fixture,
|
|
363
|
+
workspaceRoot: options.workspaceRoot,
|
|
364
|
+
})));
|
|
365
|
+
for (const result of results) {
|
|
366
|
+
driftCount += result.driftCount;
|
|
367
|
+
errorCount += result.errorCount;
|
|
368
|
+
matched += result.matched;
|
|
369
|
+
details.push(...result.findings);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
const severity = driftCount > 0 || errorCount > 0 ? 'red' : fixtures.length === 0 ? 'yellow' : 'green';
|
|
373
|
+
return {
|
|
374
|
+
details: details.length > 0 ? details : undefined,
|
|
375
|
+
elapsedMs: Date.now() - start,
|
|
376
|
+
severity,
|
|
377
|
+
summary: `mode=${mode}, booted=true, fixtures=${fixtures.length}, matched=${matched}, drift=${driftCount}, errors=${errorCount}`,
|
|
378
|
+
tier: 'R',
|
|
379
|
+
};
|
|
380
|
+
};
|
|
381
|
+
export const checkReplay = async (options) => checkReplayWithContext(options, createEquivExecutionContext());
|
|
382
|
+
export { checkReplayWithContext };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type EquivExecutionContext } from './equiv-execution-context.ts';
|
|
2
|
+
import type { EquivCheckResult, SymbolDiffOptions } from './types.ts';
|
|
3
|
+
export declare const emitSymbolBaseline: (options: {
|
|
4
|
+
readonly bundlePath: string;
|
|
5
|
+
readonly outputPath: string;
|
|
6
|
+
} | {
|
|
7
|
+
readonly entrypoint?: string;
|
|
8
|
+
readonly filter?: string;
|
|
9
|
+
readonly outputPath?: string;
|
|
10
|
+
readonly srcRoots?: readonly string[];
|
|
11
|
+
readonly workspaceRoot: string;
|
|
12
|
+
}) => Promise<{
|
|
13
|
+
readonly outputPath: string;
|
|
14
|
+
readonly symbolCount: number;
|
|
15
|
+
}>;
|
|
16
|
+
declare const checkSymbolDiffWithContext: (options: SymbolDiffOptions, context: EquivExecutionContext) => Promise<EquivCheckResult>;
|
|
17
|
+
export declare const checkSymbolDiff: (options: SymbolDiffOptions) => Promise<EquivCheckResult>;
|
|
18
|
+
export { checkSymbolDiffWithContext };
|
|
19
|
+
//# sourceMappingURL=tier-s-symbol-diff.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tier-s-symbol-diff.d.ts","sourceRoot":"","sources":["../src/tier-s-symbol-diff.ts"],"names":[],"mappings":"AACA,OAAO,EAA+B,KAAK,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAEvG,OAAO,KAAK,EACR,gBAAgB,EAIhB,iBAAiB,EACpB,MAAM,YAAY,CAAC;AAoHpB,eAAO,MAAM,kBAAkB,GAC3B,SACM;IACI,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC/B,GACD;IACI,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAClC,KACR,OAAO,CAAC;IAAE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAKvE,CAAC;AAwBF,QAAA,MAAM,0BAA0B,GAC5B,SAAS,iBAAiB,EAC1B,SAAS,qBAAqB,KAC/B,OAAO,CAAC,gBAAgB,CAoE1B,CAAC;AAEF,eAAO,MAAM,eAAe,GAAU,SAAS,iBAAiB,KAAG,OAAO,CAAC,gBAAgB,CACrB,CAAC;AAEvE,OAAO,EAAE,0BAA0B,EAAE,CAAC"}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { scanTopLevelDeclarations } from "./analysis-context.js";
|
|
2
|
+
import { createEquivExecutionContext } from "./equiv-execution-context.js";
|
|
3
|
+
import { compareStrings } from "./shared.js";
|
|
4
|
+
import { EQUIV_BASELINE_SCHEMA_NAME, EQUIV_BASELINE_SCHEMA_VERSION } from "./types.js";
|
|
5
|
+
import { assertV4Workspace, createFilterMatcher, defaultBaselineSymbolsPath, displayPath, parseSourceFile, readJsonFile, resolveWorkspaceInputPath, sha256File, sha256Text, writeTextFileAtomically, } from "./workspace.js";
|
|
6
|
+
const collectTopLevelDeclarations = async (filePath) => {
|
|
7
|
+
const { ast } = await parseSourceFile(filePath);
|
|
8
|
+
return new Map(scanTopLevelDeclarations(ast).declarations);
|
|
9
|
+
};
|
|
10
|
+
const createBaselineDocument = ({ sourceKind, sourcePath, sourceSha256, topLevelDecls, }) => ({
|
|
11
|
+
createdAt: new Date().toISOString().replace(/\.\d+Z$/u, 'Z'),
|
|
12
|
+
schemaName: EQUIV_BASELINE_SCHEMA_NAME,
|
|
13
|
+
schemaVersion: EQUIV_BASELINE_SCHEMA_VERSION,
|
|
14
|
+
source: {
|
|
15
|
+
kind: sourceKind,
|
|
16
|
+
path: sourcePath,
|
|
17
|
+
sha256: sourceSha256,
|
|
18
|
+
},
|
|
19
|
+
topLevelDecls,
|
|
20
|
+
});
|
|
21
|
+
const emitBundleSymbolBaseline = async ({ bundlePath, outputPath, }) => {
|
|
22
|
+
const declarations = await collectTopLevelDeclarations(bundlePath);
|
|
23
|
+
const document = createBaselineDocument({
|
|
24
|
+
sourceKind: 'bundle',
|
|
25
|
+
sourcePath: bundlePath,
|
|
26
|
+
sourceSha256: await sha256File(bundlePath),
|
|
27
|
+
topLevelDecls: [...declarations.entries()]
|
|
28
|
+
.sort(([left], [right]) => compareStrings(left, right))
|
|
29
|
+
.map(([name, kind]) => ({
|
|
30
|
+
kind,
|
|
31
|
+
name,
|
|
32
|
+
})),
|
|
33
|
+
});
|
|
34
|
+
await writeTextFileAtomically(outputPath, `${JSON.stringify(document, null, 2)}\n`);
|
|
35
|
+
return {
|
|
36
|
+
outputPath,
|
|
37
|
+
symbolCount: document.topLevelDecls.length,
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
const formatBaselineEmitFailure = (errors) => [
|
|
41
|
+
`Cannot emit a baseline because ${errors.length} source file${errors.length === 1 ? '' : 's'} failed to parse.`,
|
|
42
|
+
...errors.slice(0, 5).map((error) => (error.path ? `${error.path}: ${error.message}` : error.message)),
|
|
43
|
+
].join('\n');
|
|
44
|
+
const emitWorkspaceSymbolBaseline = async ({ entrypoint, filter, outputPath, srcRoots, workspaceRoot, }) => {
|
|
45
|
+
await assertV4Workspace(workspaceRoot);
|
|
46
|
+
const candidateSymbols = await createEquivExecutionContext().getCandidateSymbols({
|
|
47
|
+
entrypoint,
|
|
48
|
+
filter,
|
|
49
|
+
srcRoots,
|
|
50
|
+
workspaceRoot,
|
|
51
|
+
});
|
|
52
|
+
if (candidateSymbols.errors.length > 0) {
|
|
53
|
+
throw new Error(formatBaselineEmitFailure(candidateSymbols.errors));
|
|
54
|
+
}
|
|
55
|
+
const resolvedOutputPath = outputPath ?? defaultBaselineSymbolsPath(workspaceRoot);
|
|
56
|
+
const sourceFingerprint = sha256Text(candidateSymbols.symbols.map((declaration) => `${declaration.kind}:${declaration.name}`).join('\n'));
|
|
57
|
+
const document = createBaselineDocument({
|
|
58
|
+
sourceKind: 'workspace',
|
|
59
|
+
sourcePath: workspaceRoot,
|
|
60
|
+
sourceSha256: sourceFingerprint,
|
|
61
|
+
topLevelDecls: candidateSymbols.symbols,
|
|
62
|
+
});
|
|
63
|
+
await writeTextFileAtomically(resolvedOutputPath, `${JSON.stringify(document, null, 2)}\n`);
|
|
64
|
+
return {
|
|
65
|
+
outputPath: resolvedOutputPath,
|
|
66
|
+
symbolCount: document.topLevelDecls.length,
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
export const emitSymbolBaseline = async (options) => {
|
|
70
|
+
if ('bundlePath' in options) {
|
|
71
|
+
return emitBundleSymbolBaseline(options);
|
|
72
|
+
}
|
|
73
|
+
return emitWorkspaceSymbolBaseline(options);
|
|
74
|
+
};
|
|
75
|
+
const isLegacySymbolBaselineDocument = (value) => Boolean(value) &&
|
|
76
|
+
typeof value === 'object' &&
|
|
77
|
+
value.schemaName === 'ushman.equiv-baseline';
|
|
78
|
+
const isSymbolBaselineDocument = (value) => value.schemaName === EQUIV_BASELINE_SCHEMA_NAME &&
|
|
79
|
+
value.schemaVersion === EQUIV_BASELINE_SCHEMA_VERSION &&
|
|
80
|
+
value.source !== null &&
|
|
81
|
+
typeof value.source === 'object' &&
|
|
82
|
+
typeof value.source.kind === 'string' &&
|
|
83
|
+
typeof value.source.path === 'string' &&
|
|
84
|
+
typeof value.source.sha256 === 'string' &&
|
|
85
|
+
Array.isArray(value.topLevelDecls) &&
|
|
86
|
+
value.topLevelDecls.every((declaration) => Boolean(declaration) &&
|
|
87
|
+
typeof declaration === 'object' &&
|
|
88
|
+
typeof declaration.name === 'string' &&
|
|
89
|
+
typeof declaration.kind === 'string');
|
|
90
|
+
const checkSymbolDiffWithContext = async (options, context) => {
|
|
91
|
+
const start = Date.now();
|
|
92
|
+
await assertV4Workspace(options.workspaceRoot);
|
|
93
|
+
const baselinePath = options.baselineSymbolsPath
|
|
94
|
+
? resolveWorkspaceInputPath(options.workspaceRoot, options.baselineSymbolsPath)
|
|
95
|
+
: defaultBaselineSymbolsPath(options.workspaceRoot);
|
|
96
|
+
const baselineResult = await readJsonFile(baselinePath);
|
|
97
|
+
const details = [];
|
|
98
|
+
if (baselineResult.status !== 'ok') {
|
|
99
|
+
details.push({
|
|
100
|
+
message: baselineResult.status === 'missing'
|
|
101
|
+
? `Baseline symbols file is missing: ${displayPath(options.workspaceRoot, baselinePath)}`
|
|
102
|
+
: `Invalid baseline symbols file: ${baselineResult.message}`,
|
|
103
|
+
path: displayPath(options.workspaceRoot, baselinePath),
|
|
104
|
+
});
|
|
105
|
+
return {
|
|
106
|
+
details,
|
|
107
|
+
elapsedMs: Date.now() - start,
|
|
108
|
+
severity: 'red',
|
|
109
|
+
summary: 'baseline=0, candidate=0, dropped=0, extras=0',
|
|
110
|
+
tier: 'S',
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
if (!isSymbolBaselineDocument(baselineResult.value)) {
|
|
114
|
+
const legacyHint = isLegacySymbolBaselineDocument(baselineResult.value)
|
|
115
|
+
? ' Legacy v3 baseline files are not supported after the v4 hard cutover; regenerate the baseline from the v4 workspace.'
|
|
116
|
+
: '';
|
|
117
|
+
return {
|
|
118
|
+
details: [
|
|
119
|
+
{
|
|
120
|
+
message: `Baseline symbols file has an invalid schema or declaration shape: ${displayPath(options.workspaceRoot, baselinePath)}.${legacyHint}`,
|
|
121
|
+
path: displayPath(options.workspaceRoot, baselinePath),
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
elapsedMs: Date.now() - start,
|
|
125
|
+
severity: 'red',
|
|
126
|
+
summary: 'baseline=0, candidate=0, dropped=0, extras=0',
|
|
127
|
+
tier: 'S',
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
const matcher = createFilterMatcher(options.filter);
|
|
131
|
+
const baselineNames = baselineResult.value.topLevelDecls
|
|
132
|
+
.map((declaration) => declaration.name)
|
|
133
|
+
.filter((name) => matcher(name));
|
|
134
|
+
const candidateSymbols = await context.getCandidateSymbols(options);
|
|
135
|
+
details.push(...candidateSymbols.errors);
|
|
136
|
+
details.push(...candidateSymbols.warnings);
|
|
137
|
+
const candidateSet = new Set(candidateSymbols.symbolNames);
|
|
138
|
+
const baselineSet = new Set(baselineNames);
|
|
139
|
+
const dropped = [...baselineSet].filter((name) => !candidateSet.has(name)).sort(compareStrings);
|
|
140
|
+
const extras = [...candidateSet].filter((name) => !baselineSet.has(name)).sort(compareStrings);
|
|
141
|
+
for (const name of dropped) {
|
|
142
|
+
details.push({
|
|
143
|
+
message: `Baseline symbol is missing from the candidate: ${name}`,
|
|
144
|
+
symbol: name,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
details: details.length > 0 ? details : undefined,
|
|
149
|
+
elapsedMs: Date.now() - start,
|
|
150
|
+
severity: dropped.length > 0 || candidateSymbols.errors.length > 0 ? 'red' : 'green',
|
|
151
|
+
summary: `baseline=${baselineNames.length}, candidate=${candidateSymbols.symbolNames.length}, dropped=${dropped.length}, extras=${extras.length}`,
|
|
152
|
+
tier: 'S',
|
|
153
|
+
};
|
|
154
|
+
};
|
|
155
|
+
export const checkSymbolDiff = async (options) => checkSymbolDiffWithContext(options, createEquivExecutionContext());
|
|
156
|
+
export { checkSymbolDiffWithContext };
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
export declare const EQUIV_REPORT_VERSION = "1.0.0";
|
|
2
|
+
export declare const EQUIV_RESULT_SCHEMA_NAME = "ushman-equiv-result";
|
|
3
|
+
export declare const EQUIV_RESULT_SCHEMA_VERSION = "ushman-equiv-result/v1";
|
|
4
|
+
export declare const EQUIV_BASELINE_SCHEMA_NAME = "ushman-equiv-baseline";
|
|
5
|
+
export declare const EQUIV_BASELINE_SCHEMA_VERSION = "ushman-equiv-baseline/v1";
|
|
6
|
+
export declare const ALL_TIERS: readonly ["I", "L", "S", "R"];
|
|
7
|
+
export declare const ALL_EQUIV_MODES: readonly ["preview", "dev"];
|
|
8
|
+
export type EquivTier = (typeof ALL_TIERS)[number];
|
|
9
|
+
export type EquivMode = (typeof ALL_EQUIV_MODES)[number];
|
|
10
|
+
export type EquivSeverity = 'green' | 'yellow' | 'red';
|
|
11
|
+
export type EquivFinding = {
|
|
12
|
+
readonly evidence?: unknown;
|
|
13
|
+
readonly message: string;
|
|
14
|
+
readonly path?: string;
|
|
15
|
+
readonly symbol?: string;
|
|
16
|
+
};
|
|
17
|
+
export type EquivCheckResult = {
|
|
18
|
+
readonly details?: readonly EquivFinding[];
|
|
19
|
+
readonly elapsedMs: number;
|
|
20
|
+
readonly severity: EquivSeverity;
|
|
21
|
+
readonly summary: string;
|
|
22
|
+
readonly tier: EquivTier;
|
|
23
|
+
};
|
|
24
|
+
export type EquivReport = {
|
|
25
|
+
readonly checks: readonly EquivCheckResult[];
|
|
26
|
+
readonly createdAt: string;
|
|
27
|
+
readonly mode: EquivMode;
|
|
28
|
+
readonly reportVersion: typeof EQUIV_REPORT_VERSION;
|
|
29
|
+
readonly schemaName: typeof EQUIV_RESULT_SCHEMA_NAME;
|
|
30
|
+
readonly schemaVersion: typeof EQUIV_RESULT_SCHEMA_VERSION;
|
|
31
|
+
readonly tiers: readonly EquivTier[];
|
|
32
|
+
readonly ushmanEquivVersion: string;
|
|
33
|
+
readonly verdict: EquivSeverity;
|
|
34
|
+
readonly workspace: string;
|
|
35
|
+
};
|
|
36
|
+
type CommonTierOptions = {
|
|
37
|
+
readonly entrypoint?: string;
|
|
38
|
+
readonly filter?: string;
|
|
39
|
+
readonly mode?: EquivMode;
|
|
40
|
+
readonly srcRoots?: readonly string[];
|
|
41
|
+
readonly workspaceRoot: string;
|
|
42
|
+
};
|
|
43
|
+
export type EquivOptions = CommonTierOptions & {
|
|
44
|
+
readonly baselineSymbolsPath?: string;
|
|
45
|
+
readonly fixturesDir?: string;
|
|
46
|
+
readonly maxConcurrency?: number;
|
|
47
|
+
readonly modulesDir?: string;
|
|
48
|
+
readonly tiers?: readonly EquivTier[];
|
|
49
|
+
};
|
|
50
|
+
export type ImportGraphOptions = CommonTierOptions;
|
|
51
|
+
export type ModuleLoadOptions = CommonTierOptions;
|
|
52
|
+
export type SymbolBaselineDeclKind = 'class' | 'const' | 'export' | 'function' | 'let' | 'var';
|
|
53
|
+
export type SymbolBaselineDeclaration = {
|
|
54
|
+
readonly kind: SymbolBaselineDeclKind;
|
|
55
|
+
readonly name: string;
|
|
56
|
+
};
|
|
57
|
+
export type SymbolBaselineSource = {
|
|
58
|
+
readonly kind: 'bundle' | 'workspace';
|
|
59
|
+
readonly path: string;
|
|
60
|
+
readonly sha256: string;
|
|
61
|
+
};
|
|
62
|
+
export type SymbolBaselineDocument = {
|
|
63
|
+
readonly createdAt: string;
|
|
64
|
+
readonly schemaName: typeof EQUIV_BASELINE_SCHEMA_NAME;
|
|
65
|
+
readonly schemaVersion: typeof EQUIV_BASELINE_SCHEMA_VERSION;
|
|
66
|
+
readonly source: SymbolBaselineSource;
|
|
67
|
+
readonly topLevelDecls: readonly SymbolBaselineDeclaration[];
|
|
68
|
+
};
|
|
69
|
+
export type SymbolDiffOptions = CommonTierOptions & {
|
|
70
|
+
readonly baselineSymbolsPath?: string;
|
|
71
|
+
};
|
|
72
|
+
export type ReplayCallRecord = {
|
|
73
|
+
readonly args?: readonly unknown[];
|
|
74
|
+
readonly expected?: unknown;
|
|
75
|
+
readonly return?: unknown;
|
|
76
|
+
readonly thisArg?: unknown;
|
|
77
|
+
readonly threw?: unknown;
|
|
78
|
+
};
|
|
79
|
+
export type ReplayFixtureDocument = {
|
|
80
|
+
readonly calls: readonly ReplayCallRecord[];
|
|
81
|
+
readonly fn: string;
|
|
82
|
+
readonly module?: string;
|
|
83
|
+
};
|
|
84
|
+
export type ReplayOptions = CommonTierOptions & {
|
|
85
|
+
readonly fixturesDir?: string;
|
|
86
|
+
readonly maxConcurrency?: number;
|
|
87
|
+
readonly modulesDir?: string;
|
|
88
|
+
};
|
|
89
|
+
export declare const verdictForChecks: (checks: readonly EquivCheckResult[]) => EquivSeverity;
|
|
90
|
+
export {};
|
|
91
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export const EQUIV_REPORT_VERSION = '1.0.0';
|
|
2
|
+
export const EQUIV_RESULT_SCHEMA_NAME = 'ushman-equiv-result';
|
|
3
|
+
export const EQUIV_RESULT_SCHEMA_VERSION = 'ushman-equiv-result/v1';
|
|
4
|
+
export const EQUIV_BASELINE_SCHEMA_NAME = 'ushman-equiv-baseline';
|
|
5
|
+
export const EQUIV_BASELINE_SCHEMA_VERSION = 'ushman-equiv-baseline/v1';
|
|
6
|
+
export const ALL_TIERS = ['I', 'L', 'S', 'R'];
|
|
7
|
+
export const ALL_EQUIV_MODES = ['preview', 'dev'];
|
|
8
|
+
export const verdictForChecks = (checks) => {
|
|
9
|
+
if (checks.length === 0) {
|
|
10
|
+
return 'yellow';
|
|
11
|
+
}
|
|
12
|
+
if (checks.some((check) => check.severity === 'red')) {
|
|
13
|
+
return 'red';
|
|
14
|
+
}
|
|
15
|
+
if (checks.some((check) => check.severity === 'yellow')) {
|
|
16
|
+
return 'yellow';
|
|
17
|
+
}
|
|
18
|
+
return 'green';
|
|
19
|
+
};
|