ushman-characterize 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.
Files changed (99) hide show
  1. package/AGENTS.md +110 -0
  2. package/CHANGELOG.md +41 -0
  3. package/LICENSE.md +21 -0
  4. package/README.md +193 -0
  5. package/bin/ushman-characterize +19 -0
  6. package/dist/babel-config.d.ts +7 -0
  7. package/dist/babel-config.d.ts.map +1 -0
  8. package/dist/babel-config.js +17 -0
  9. package/dist/capture-server.d.ts +31 -0
  10. package/dist/capture-server.d.ts.map +1 -0
  11. package/dist/capture-server.js +199 -0
  12. package/dist/capture.d.ts +97 -0
  13. package/dist/capture.d.ts.map +1 -0
  14. package/dist/capture.js +620 -0
  15. package/dist/cli/logger.d.ts +7 -0
  16. package/dist/cli/logger.d.ts.map +1 -0
  17. package/dist/cli/logger.js +14 -0
  18. package/dist/cli/parse-flags.d.ts +8 -0
  19. package/dist/cli/parse-flags.d.ts.map +1 -0
  20. package/dist/cli/parse-flags.js +60 -0
  21. package/dist/cli.d.ts +39 -0
  22. package/dist/cli.d.ts.map +1 -0
  23. package/dist/cli.js +439 -0
  24. package/dist/constants.d.ts +20 -0
  25. package/dist/constants.d.ts.map +1 -0
  26. package/dist/constants.js +19 -0
  27. package/dist/dedupe-contract.d.ts +26 -0
  28. package/dist/dedupe-contract.d.ts.map +1 -0
  29. package/dist/dedupe-contract.js +12 -0
  30. package/dist/default-export.d.ts +6 -0
  31. package/dist/default-export.d.ts.map +1 -0
  32. package/dist/default-export.js +52 -0
  33. package/dist/format-contract.d.ts +25 -0
  34. package/dist/format-contract.d.ts.map +1 -0
  35. package/dist/format-contract.js +96 -0
  36. package/dist/function-utils.d.ts +6 -0
  37. package/dist/function-utils.d.ts.map +1 -0
  38. package/dist/function-utils.js +22 -0
  39. package/dist/generate-replay.d.ts +18 -0
  40. package/dist/generate-replay.d.ts.map +1 -0
  41. package/dist/generate-replay.js +158 -0
  42. package/dist/index.d.ts +13 -0
  43. package/dist/index.d.ts.map +1 -0
  44. package/dist/index.js +11 -0
  45. package/dist/instrument.d.ts +39 -0
  46. package/dist/instrument.d.ts.map +1 -0
  47. package/dist/instrument.js +605 -0
  48. package/dist/ledger.d.ts +19 -0
  49. package/dist/ledger.d.ts.map +1 -0
  50. package/dist/ledger.js +50 -0
  51. package/dist/puppeteer-harness.d.ts +74 -0
  52. package/dist/puppeteer-harness.d.ts.map +1 -0
  53. package/dist/puppeteer-harness.js +248 -0
  54. package/dist/purity-classifier.d.ts +28 -0
  55. package/dist/purity-classifier.d.ts.map +1 -0
  56. package/dist/purity-classifier.js +363 -0
  57. package/dist/rebind.d.ts +26 -0
  58. package/dist/rebind.d.ts.map +1 -0
  59. package/dist/rebind.js +356 -0
  60. package/dist/replay-report.d.ts +18 -0
  61. package/dist/replay-report.d.ts.map +1 -0
  62. package/dist/replay-report.js +12 -0
  63. package/dist/scene.d.ts +24 -0
  64. package/dist/scene.d.ts.map +1 -0
  65. package/dist/scene.js +235 -0
  66. package/dist/schema-types.d.ts +40 -0
  67. package/dist/schema-types.d.ts.map +1 -0
  68. package/dist/schema-types.js +32 -0
  69. package/dist/seed-scaffolds.d.ts +31 -0
  70. package/dist/seed-scaffolds.d.ts.map +1 -0
  71. package/dist/seed-scaffolds.js +96 -0
  72. package/dist/shared.d.ts +36 -0
  73. package/dist/shared.d.ts.map +1 -0
  74. package/dist/shared.js +390 -0
  75. package/dist/state-dag.d.ts +5 -0
  76. package/dist/state-dag.d.ts.map +1 -0
  77. package/dist/state-dag.js +27 -0
  78. package/dist/stub-pure.d.ts +57 -0
  79. package/dist/stub-pure.d.ts.map +1 -0
  80. package/dist/stub-pure.js +987 -0
  81. package/dist/time.d.ts +3 -0
  82. package/dist/time.d.ts.map +1 -0
  83. package/dist/time.js +10 -0
  84. package/dist/trace-format.d.ts +24 -0
  85. package/dist/trace-format.d.ts.map +1 -0
  86. package/dist/trace-format.js +213 -0
  87. package/dist/trace-serializer.d.ts +94 -0
  88. package/dist/trace-serializer.d.ts.map +1 -0
  89. package/dist/trace-serializer.js +607 -0
  90. package/dist/tracer-runtime.d.ts +25 -0
  91. package/dist/tracer-runtime.d.ts.map +1 -0
  92. package/dist/tracer-runtime.js +291 -0
  93. package/dist/types.d.ts +13 -0
  94. package/dist/types.d.ts.map +1 -0
  95. package/dist/types.js +0 -0
  96. package/dist/workspace-paths.d.ts +64 -0
  97. package/dist/workspace-paths.d.ts.map +1 -0
  98. package/dist/workspace-paths.js +288 -0
  99. package/package.json +86 -0
@@ -0,0 +1,52 @@
1
+ import * as t from '@babel/types';
2
+ const DEFAULT_EXPORT_BINDING_BASENAME = 'defaultExport';
3
+ const addBindingIdentifiers = ({ names, value, }) => {
4
+ if (!value) {
5
+ return;
6
+ }
7
+ for (const name of Object.keys(t.getBindingIdentifiers(value))) {
8
+ names.add(name);
9
+ }
10
+ };
11
+ export const isAnonymousDefaultExportCallableStatement = (statement) => t.isExportDefaultDeclaration(statement) &&
12
+ ((t.isFunctionDeclaration(statement.declaration) && !statement.declaration.id) ||
13
+ t.isFunctionExpression(statement.declaration) ||
14
+ t.isArrowFunctionExpression(statement.declaration));
15
+ export const collectTopLevelBindingNames = (statements) => {
16
+ const names = new Set();
17
+ for (const statement of statements) {
18
+ if (t.isImportDeclaration(statement)) {
19
+ for (const specifier of statement.specifiers) {
20
+ names.add(specifier.local.name);
21
+ }
22
+ continue;
23
+ }
24
+ if (t.isExportNamedDeclaration(statement) || t.isExportDefaultDeclaration(statement)) {
25
+ addBindingIdentifiers({
26
+ names,
27
+ value: statement.declaration,
28
+ });
29
+ continue;
30
+ }
31
+ addBindingIdentifiers({
32
+ names,
33
+ value: statement,
34
+ });
35
+ }
36
+ return names;
37
+ };
38
+ export const resolveAnonymousDefaultExportBindingName = (takenNames) => {
39
+ let candidate = DEFAULT_EXPORT_BINDING_BASENAME;
40
+ let suffix = 2;
41
+ while (takenNames.has(candidate)) {
42
+ candidate = `${DEFAULT_EXPORT_BINDING_BASENAME}_${suffix}`;
43
+ suffix += 1;
44
+ }
45
+ return candidate;
46
+ };
47
+ export const getAnonymousDefaultExportBindingName = (statements) => {
48
+ if (!statements.some((statement) => isAnonymousDefaultExportCallableStatement(statement))) {
49
+ return null;
50
+ }
51
+ return resolveAnonymousDefaultExportBindingName(collectTopLevelBindingNames(statements));
52
+ };
@@ -0,0 +1,25 @@
1
+ type VersionedEnvelope = {
2
+ readonly schemaName: string;
3
+ readonly schemaVersion: string;
4
+ readonly supportVersion: string;
5
+ };
6
+ export type ReplayFixtureDocument<TCase> = VersionedEnvelope & {
7
+ readonly cases: readonly TCase[];
8
+ };
9
+ export declare const stampTraceRecord: <TValue extends Record<string, unknown>>(value: TValue) => TValue & {
10
+ schemaName: string;
11
+ schemaVersion: string;
12
+ supportVersion: string;
13
+ };
14
+ export declare const parseTraceRecord: <TValue extends Record<string, unknown>>({ context, value, }: {
15
+ readonly context: string;
16
+ readonly value: unknown;
17
+ }) => TValue;
18
+ export declare const createReplayFixtureDocument: <TCase>(cases: readonly TCase[]) => ReplayFixtureDocument<TCase>;
19
+ export declare const parseReplayFixtureDocument: <TCase>({ caseValidator, context, value, }: {
20
+ readonly caseValidator: (value: unknown) => value is TCase;
21
+ readonly context: string;
22
+ readonly value: unknown;
23
+ }) => readonly TCase[];
24
+ export {};
25
+ //# sourceMappingURL=format-contract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format-contract.d.ts","sourceRoot":"","sources":["../src/format-contract.ts"],"names":[],"mappings":"AAQA,KAAK,iBAAiB,GAAG;IACrB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CACnC,CAAC;AAEF,MAAM,MAAM,qBAAqB,CAAC,KAAK,IAAI,iBAAiB,GAAG;IAC3D,QAAQ,CAAC,KAAK,EAAE,SAAS,KAAK,EAAE,CAAC;CACpC,CAAC;AAqEF,eAAO,MAAM,gBAAgB,GAAI,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,MAAM;;;;CAKpF,CAAC;AAEH,eAAO,MAAM,gBAAgB,GAAI,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,qBAGtE;IACC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CAC3B,KAkBqB,MACrB,CAAC;AAEF,eAAO,MAAM,2BAA2B,GAAI,KAAK,EAAE,OAAO,SAAS,KAAK,EAAE,KAAG,qBAAqB,CAAC,KAAK,CAKtG,CAAC;AAEH,eAAO,MAAM,0BAA0B,GAAI,KAAK,EAAE,oCAI/C;IACC,QAAQ,CAAC,aAAa,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,KAAK,IAAI,KAAK,CAAC;IAC3D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CAC3B,KAwBmB,SAAS,KAAK,EACjC,CAAC"}
@@ -0,0 +1,96 @@
1
+ import { CHARACTERIZE_SUPPORT_VERSION, REPLAY_FIXTURE_SCHEMA_NAME, REPLAY_FIXTURE_SCHEMA_VERSION, TRACE_RECORD_SCHEMA_NAME, TRACE_RECORD_SCHEMA_VERSION, } from "./constants.js";
2
+ const isRecord = (value) => typeof value === 'object' && value !== null;
3
+ const isNullableString = (value) => value === null || typeof value === 'string';
4
+ const isTraceMemberKind = (value) => value === 'function' || value === 'method';
5
+ const describeObservedVersion = (value) => {
6
+ if (!isRecord(value)) {
7
+ return 'an invalid non-object payload';
8
+ }
9
+ const schemaName = typeof value.schemaName === 'string' ? value.schemaName : 'unknown-schema';
10
+ const schemaVersion = typeof value.schemaVersion === 'string' ? value.schemaVersion : 'unknown-version';
11
+ const supportVersion = typeof value.supportVersion === 'string' ? value.supportVersion : 'unknown-support';
12
+ return `${schemaName}@${schemaVersion} support=${supportVersion}`;
13
+ };
14
+ const assertVersionedEnvelope = ({ context, expected, hint, value, }) => {
15
+ if (!isRecord(value)) {
16
+ throw new Error(`${context} must be an object. ${hint}`);
17
+ }
18
+ if (value.schemaName !== expected.schemaName ||
19
+ value.schemaVersion !== expected.schemaVersion ||
20
+ value.supportVersion !== expected.supportVersion) {
21
+ throw new Error(`${context} is incompatible. Expected ${expected.schemaName}@${expected.schemaVersion} support=${expected.supportVersion}, found ${describeObservedVersion(value)}. ${hint}`);
22
+ }
23
+ };
24
+ const stripEnvelope = (value) => {
25
+ const { schemaName: _schemaName, schemaVersion: _schemaVersion, supportVersion: _supportVersion, ...rest } = value;
26
+ return rest;
27
+ };
28
+ const assertTraceRecordPayload = ({ context, value }) => {
29
+ if (!isRecord(value)) {
30
+ throw new Error(`${context} must be an object. Re-run ushman-characterize capture, then regenerate replay fixtures if needed.`);
31
+ }
32
+ if (typeof value.bindingName !== 'string' ||
33
+ !isNullableString(value.className) ||
34
+ typeof value.count !== 'number' ||
35
+ typeof value.functionName !== 'string' ||
36
+ !isTraceMemberKind(value.memberKind) ||
37
+ !isNullableString(value.methodName) ||
38
+ !Array.isArray(value.sideEffects) ||
39
+ typeof value.state !== 'string') {
40
+ throw new Error(`${context} has an invalid trace payload shape. Re-run ushman-characterize capture, then regenerate replay fixtures if needed.`);
41
+ }
42
+ };
43
+ export const stampTraceRecord = (value) => ({
44
+ ...value,
45
+ schemaName: TRACE_RECORD_SCHEMA_NAME,
46
+ schemaVersion: TRACE_RECORD_SCHEMA_VERSION,
47
+ supportVersion: CHARACTERIZE_SUPPORT_VERSION,
48
+ });
49
+ export const parseTraceRecord = ({ context, value, }) => {
50
+ assertVersionedEnvelope({
51
+ context,
52
+ expected: {
53
+ schemaName: TRACE_RECORD_SCHEMA_NAME,
54
+ schemaVersion: TRACE_RECORD_SCHEMA_VERSION,
55
+ supportVersion: CHARACTERIZE_SUPPORT_VERSION,
56
+ },
57
+ hint: 'Re-run ushman-characterize capture, then regenerate replay fixtures if needed.',
58
+ value,
59
+ });
60
+ const payload = stripEnvelope(value);
61
+ // Envelope validation guards the schema contract; payload validation makes
62
+ // corrupted trace rows fail early with a trace-specific diagnostic.
63
+ assertTraceRecordPayload({
64
+ context,
65
+ value: payload,
66
+ });
67
+ return payload;
68
+ };
69
+ export const createReplayFixtureDocument = (cases) => ({
70
+ cases,
71
+ schemaName: REPLAY_FIXTURE_SCHEMA_NAME,
72
+ schemaVersion: REPLAY_FIXTURE_SCHEMA_VERSION,
73
+ supportVersion: CHARACTERIZE_SUPPORT_VERSION,
74
+ });
75
+ export const parseReplayFixtureDocument = ({ caseValidator, context, value, }) => {
76
+ assertVersionedEnvelope({
77
+ context,
78
+ expected: {
79
+ schemaName: REPLAY_FIXTURE_SCHEMA_NAME,
80
+ schemaVersion: REPLAY_FIXTURE_SCHEMA_VERSION,
81
+ supportVersion: CHARACTERIZE_SUPPORT_VERSION,
82
+ },
83
+ hint: 'Re-run ushman-characterize generate-replay for this workspace.',
84
+ value,
85
+ });
86
+ if (!Array.isArray(value.cases)) {
87
+ throw new Error(`${context} is missing its cases array. Re-run ushman-characterize generate-replay for this workspace.`);
88
+ }
89
+ const cases = value.cases;
90
+ for (const [index, caseValue] of cases.entries()) {
91
+ if (!caseValidator(caseValue)) {
92
+ throw new Error(`${context} has an invalid replay case at index ${index}. Re-run ushman-characterize generate-replay for this workspace.`);
93
+ }
94
+ }
95
+ return cases;
96
+ };
@@ -0,0 +1,6 @@
1
+ import * as t from '@babel/types';
2
+ export declare const isTrivialGetterLike: ({ body, params, }: {
3
+ readonly body: t.BlockStatement;
4
+ readonly params: readonly (t.FunctionParameter | t.TSParameterProperty)[];
5
+ }) => boolean;
6
+ //# sourceMappingURL=function-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"function-utils.d.ts","sourceRoot":"","sources":["../src/function-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,cAAc,CAAC;AAmBlC,eAAO,MAAM,mBAAmB,GAAI,mBAGjC;IACC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,cAAc,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC,iBAAiB,GAAG,CAAC,CAAC,mBAAmB,CAAC,EAAE,CAAC;CAC7E,YAcA,CAAC"}
@@ -0,0 +1,22 @@
1
+ import * as t from '@babel/types';
2
+ const returnsFirstParameter = ({ argument, params, }) => {
3
+ const firstParameter = params[0];
4
+ return t.isIdentifier(argument) && t.isIdentifier(firstParameter) && argument.name === firstParameter.name;
5
+ };
6
+ const returnsThisProperty = (argument) => t.isMemberExpression(argument) &&
7
+ !argument.computed &&
8
+ t.isThisExpression(argument.object) &&
9
+ t.isIdentifier(argument.property);
10
+ export const isTrivialGetterLike = ({ body, params, }) => {
11
+ if (body.body.length !== 1) {
12
+ return false;
13
+ }
14
+ const [statement] = body.body;
15
+ if (!statement || !t.isReturnStatement(statement) || !statement.argument || !t.isExpression(statement.argument)) {
16
+ return false;
17
+ }
18
+ return (returnsFirstParameter({
19
+ argument: statement.argument,
20
+ params,
21
+ }) || returnsThisProperty(statement.argument));
22
+ };
@@ -0,0 +1,18 @@
1
+ type ReplayGenerationFailure = {
2
+ readonly functionName: string;
3
+ readonly reason: string;
4
+ };
5
+ /**
6
+ * Generate Bun replay tests and fixture files from captured state traces.
7
+ */
8
+ export declare const generateReplayCharacterization: ({ bundlePath, maxCases, workspaceRoot, }: {
9
+ readonly bundlePath: string;
10
+ readonly maxCases?: number;
11
+ readonly workspaceRoot: string;
12
+ }) => Promise<{
13
+ skippedFunctions: ReplayGenerationFailure[];
14
+ writtenFixtures: string[];
15
+ writtenTests: string[];
16
+ }>;
17
+ export {};
18
+ //# sourceMappingURL=generate-replay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate-replay.d.ts","sourceRoot":"","sources":["../src/generate-replay.ts"],"names":[],"mappings":"AAiDA,KAAK,uBAAuB,GAAG;IAC3B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CAC3B,CAAC;AAyGF;;GAEG;AACH,eAAO,MAAM,8BAA8B,GAAU,0CAIlD;IACC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAClC;;;;EAuFA,CAAC"}
@@ -0,0 +1,158 @@
1
+ import { mkdir, readdir } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { createReplayFixtureDocument, parseTraceRecord } from "./format-contract.js";
4
+ import { buildStandaloneSymbolModule, parseBundleAst, parseJsonLines, relativeImportPath, sanitizeSymbolName, } from "./shared.js";
5
+ import { canonicalizeTraceValue, ensureWorkspaceTraceHarness, workspaceHarnessPaths } from "./trace-format.js";
6
+ import { assertV4Workspace } from "./workspace-paths.js";
7
+ const readAllTraceFiles = async (workspaceRoot) => {
8
+ const traceDir = workspaceHarnessPaths(workspaceRoot).tracesDir;
9
+ const entries = await readdir(traceDir).catch(() => []);
10
+ const records = [];
11
+ for (const entry of entries) {
12
+ if (!entry.endsWith('.jsonl')) {
13
+ continue;
14
+ }
15
+ const filePath = path.join(traceDir, entry);
16
+ // Keep the zero-based JSONL index so parseTraceRecord can surface a
17
+ // precise line number when a trace row is version-incompatible or malformed.
18
+ for (const [index, record] of parseJsonLines(await Bun.file(filePath).text()).entries()) {
19
+ records.push(parseTraceRecord({
20
+ context: `Trace record ${entry}:${index + 1}`,
21
+ value: record,
22
+ }));
23
+ }
24
+ }
25
+ return records;
26
+ };
27
+ const createReplayFixtures = ({ maxCases, records, }) => records.slice(0, maxCases).map((record, index) => ({
28
+ args: record.args,
29
+ expected: record.expected,
30
+ i: index + 1,
31
+ sideEffects: canonicalizeTraceValue(record.sideEffects),
32
+ state: record.state,
33
+ thisArg: record.thisArg,
34
+ threw: record.threw,
35
+ }));
36
+ const renderReplayTestSource = ({ fixtureName, harnessFile, importName, memberKind, methodName, modulePath, testFile, }) => {
37
+ const harnessImport = relativeImportPath({
38
+ fromFile: testFile,
39
+ toFile: harnessFile,
40
+ });
41
+ const moduleImport = relativeImportPath({
42
+ fromFile: testFile,
43
+ toFile: modulePath,
44
+ });
45
+ const fixtureImportName = fixtureName.replace(/'/gu, "\\'");
46
+ const invokeSource = memberKind === 'method' && methodName
47
+ ? `const instance = replay.hydrate(${importName}, entry.thisArg);
48
+ return replay.callMethod(instance, '${methodName}', { args: entry.args });`
49
+ : `return replay.invoke(${importName}, { args: entry.args, thisArg: entry.thisArg });`;
50
+ return `// AUTO-GENERATED by ushman-characterize generate-replay. Do not hand-edit.
51
+ // generation-version: 1
52
+ import { describe, expect, test } from 'bun:test';
53
+ import { fixture, registerTraceMatchers, replay } from '${harnessImport}';
54
+ import { ${importName} } from '${moduleImport}';
55
+
56
+ registerTraceMatchers();
57
+
58
+ const cases = await fixture('replay', '${fixtureImportName}');
59
+
60
+ describe('${fixtureImportName}', () => {
61
+ for (const entry of cases) {
62
+ test(\`replay #\${entry.i} (state=\${entry.state})\`, async () => {
63
+ const outcome = await replay.capture(async () => {
64
+ ${invokeSource}
65
+ });
66
+
67
+ expect(outcome.sideEffects).toMatchTrace(entry.sideEffects);
68
+
69
+ if (entry.threw !== null) {
70
+ expect(outcome.threw).not.toBeNull();
71
+ expect(outcome.threw).toMatchTrace(entry.threw);
72
+ return;
73
+ }
74
+
75
+ expect(outcome.threw).toBeNull();
76
+ expect(outcome.value).toMatchTrace(entry.expected);
77
+ });
78
+ }
79
+ });
80
+ `;
81
+ };
82
+ const writeStandaloneModule = async ({ bindingName, bundle, modulePath, }) => {
83
+ await Bun.write(modulePath, `${buildStandaloneSymbolModule({
84
+ bindingName,
85
+ bundle,
86
+ outputFilePath: modulePath,
87
+ }).trimEnd()}\n`);
88
+ };
89
+ /**
90
+ * Generate Bun replay tests and fixture files from captured state traces.
91
+ */
92
+ export const generateReplayCharacterization = async ({ bundlePath, maxCases = 10, workspaceRoot, }) => {
93
+ await assertV4Workspace(workspaceRoot);
94
+ const allRecords = await readAllTraceFiles(workspaceRoot);
95
+ const grouped = new Map();
96
+ for (const record of allRecords) {
97
+ const current = grouped.get(record.functionName) ?? [];
98
+ current.push(record);
99
+ grouped.set(record.functionName, current);
100
+ }
101
+ const bundle = parseBundleAst({
102
+ source: await Bun.file(bundlePath).text(),
103
+ sourcePath: bundlePath,
104
+ });
105
+ const harnessPaths = await ensureWorkspaceTraceHarness(workspaceRoot);
106
+ const testsDir = path.join(workspaceRoot, 'tests', 'replay');
107
+ await mkdir(testsDir, { recursive: true });
108
+ await mkdir(harnessPaths.replayFixturesDir, { recursive: true });
109
+ await mkdir(harnessPaths.modulesDir, { recursive: true });
110
+ const writtenFixtures = [];
111
+ const writtenTests = [];
112
+ const skippedFunctions = [];
113
+ for (const [functionName, records] of [...grouped.entries()].sort((left, right) => left[0].localeCompare(right[0]))) {
114
+ const sample = records[0];
115
+ if (!sample) {
116
+ continue;
117
+ }
118
+ const fixtureName = functionName;
119
+ const safeName = sanitizeSymbolName(functionName);
120
+ const fixturePath = path.join(harnessPaths.replayFixturesDir, `${safeName}.json`);
121
+ const modulePath = path.join(harnessPaths.modulesDir, `${safeName}.mjs`);
122
+ const testFile = path.join(testsDir, `${safeName}.test.ts`);
123
+ try {
124
+ await writeStandaloneModule({
125
+ bindingName: sample.bindingName,
126
+ bundle,
127
+ modulePath,
128
+ });
129
+ }
130
+ catch (error) {
131
+ skippedFunctions.push({
132
+ functionName,
133
+ reason: error instanceof Error ? error.message : String(error),
134
+ });
135
+ continue;
136
+ }
137
+ await Bun.write(fixturePath, `${JSON.stringify(createReplayFixtureDocument(createReplayFixtures({
138
+ maxCases,
139
+ records,
140
+ })), null, 2)}\n`);
141
+ writtenFixtures.push(fixturePath);
142
+ await Bun.write(testFile, renderReplayTestSource({
143
+ fixtureName,
144
+ harnessFile: harnessPaths.harnessFile,
145
+ importName: sample.bindingName,
146
+ memberKind: sample.memberKind,
147
+ methodName: sample.methodName,
148
+ modulePath,
149
+ testFile,
150
+ }));
151
+ writtenTests.push(testFile);
152
+ }
153
+ return {
154
+ skippedFunctions,
155
+ writtenFixtures,
156
+ writtenTests,
157
+ };
158
+ };
@@ -0,0 +1,13 @@
1
+ export { type CaptureCharacterizationOptions, type CaptureCharacterizationResult, type CaptureStateResult, captureCharacterization, readCapturedTraceState, } from './capture.ts';
2
+ export { createConsoleLogger, type Logger } from './cli/logger.ts';
3
+ export { runStubPureCommand, runStubStatesCommand } from './cli.ts';
4
+ export { generateReplayCharacterization } from './generate-replay.ts';
5
+ export { instrumentBundle } from './instrument.ts';
6
+ export { classifyPureTopLevelFunctions } from './purity-classifier.ts';
7
+ export { rewriteReplayImports } from './rebind.ts';
8
+ export { createVerifyReport, type VerifyReport } from './replay-report.ts';
9
+ export { canonicalizeSceneTree, type PopulateSmokeScaffoldsResult, populateSmokeScaffolds, scaffoldSceneCharacterizationTests, } from './scene.ts';
10
+ export { type PopulateScaffoldsResult, populateScaffolds, scaffoldPureCharacterizationTests, } from './stub-pure.ts';
11
+ export { canonicalizeTraceValue as canonicalizeTrace } from './trace-format.ts';
12
+ export type { CaptureServerHost, SceneInspectorDriver } from './types.ts';
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,KAAK,8BAA8B,EACnC,KAAK,6BAA6B,EAClC,KAAK,kBAAkB,EACvB,uBAAuB,EACvB,sBAAsB,GACzB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,mBAAmB,EAAE,KAAK,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AACpE,OAAO,EAAE,8BAA8B,EAAE,MAAM,sBAAsB,CAAC;AACtE,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAE,6BAA6B,EAAE,MAAM,wBAAwB,CAAC;AACvE,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,kBAAkB,EAAE,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAC3E,OAAO,EACH,qBAAqB,EACrB,KAAK,4BAA4B,EACjC,sBAAsB,EACtB,kCAAkC,GACrC,MAAM,YAAY,CAAC;AACpB,OAAO,EACH,KAAK,uBAAuB,EAC5B,iBAAiB,EACjB,iCAAiC,GACpC,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,sBAAsB,IAAI,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAChF,YAAY,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,11 @@
1
+ export { captureCharacterization, readCapturedTraceState, } from "./capture.js";
2
+ export { createConsoleLogger } from "./cli/logger.js";
3
+ export { runStubPureCommand, runStubStatesCommand } from "./cli.js";
4
+ export { generateReplayCharacterization } from "./generate-replay.js";
5
+ export { instrumentBundle } from "./instrument.js";
6
+ export { classifyPureTopLevelFunctions } from "./purity-classifier.js";
7
+ export { rewriteReplayImports } from "./rebind.js";
8
+ export { createVerifyReport } from "./replay-report.js";
9
+ export { canonicalizeSceneTree, populateSmokeScaffolds, scaffoldSceneCharacterizationTests, } from "./scene.js";
10
+ export { populateScaffolds, scaffoldPureCharacterizationTests, } from "./stub-pure.js";
11
+ export { canonicalizeTraceValue as canonicalizeTrace } from "./trace-format.js";
@@ -0,0 +1,39 @@
1
+ export type InstrumentSourceMapMode = 'external' | 'inline' | 'off';
2
+ type InstrumentSourceMapPayload = {
3
+ readonly file?: string;
4
+ readonly mappings: string;
5
+ readonly names: readonly string[];
6
+ readonly sourceRoot?: string;
7
+ readonly sources: readonly string[];
8
+ readonly sourcesContent?: readonly (null | string)[];
9
+ readonly version: number;
10
+ };
11
+ type InstrumentBundleSourceResult = {
12
+ readonly code: string;
13
+ readonly instrumentedSymbols: readonly string[];
14
+ readonly map: InstrumentSourceMapPayload | null;
15
+ readonly skippedSymbols: readonly string[];
16
+ };
17
+ /**
18
+ * Instrument a bundle source string with the characterization tracer wrappers.
19
+ */
20
+ export declare const instrumentBundleSource: ({ source, sourceMapFile, sourceMapMode, sourcePath, }: {
21
+ readonly source: string;
22
+ readonly sourceMapFile?: string;
23
+ readonly sourceMapMode?: InstrumentSourceMapMode;
24
+ readonly sourcePath: string;
25
+ }) => InstrumentBundleSourceResult;
26
+ export declare const instrumentBundle: ({ bundlePath, outputPath, sourceMapMode, }: {
27
+ readonly bundlePath: string;
28
+ readonly outputPath: string;
29
+ readonly sourceMapMode?: InstrumentSourceMapMode;
30
+ }) => Promise<{
31
+ mapPath: string | null;
32
+ outputPath: string;
33
+ code: string;
34
+ instrumentedSymbols: readonly string[];
35
+ map: InstrumentSourceMapPayload | null;
36
+ skippedSymbols: readonly string[];
37
+ }>;
38
+ export {};
39
+ //# sourceMappingURL=instrument.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instrument.d.ts","sourceRoot":"","sources":["../src/instrument.ts"],"names":[],"mappings":"AAcA,MAAM,MAAM,uBAAuB,GAAG,UAAU,GAAG,QAAQ,GAAG,KAAK,CAAC;AAEpE,KAAK,0BAA0B,GAAG;IAC9B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,cAAc,CAAC,EAAE,SAAS,CAAC,IAAI,GAAG,MAAM,CAAC,EAAE,CAAC;IACrD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF,KAAK,4BAA4B,GAAG;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,mBAAmB,EAAE,SAAS,MAAM,EAAE,CAAC;IAChD,QAAQ,CAAC,GAAG,EAAE,0BAA0B,GAAG,IAAI,CAAC;IAChD,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;CAC9C,CAAC;AAkvBF;;GAEG;AACH,eAAO,MAAM,sBAAsB,GAAI,uDAKpC;IACC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,aAAa,CAAC,EAAE,uBAAuB,CAAC;IACjD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC/B,KAAG,4BA+GH,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAAU,4CAIpC;IACC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,aAAa,CAAC,EAAE,uBAAuB,CAAC;CACpD;;;UA53BkB,MAAM;yBACS,SAAS,MAAM,EAAE;SACjC,0BAA0B,GAAG,IAAI;oBACtB,SAAS,MAAM,EAAE;EA44B7C,CAAC"}