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,40 @@
1
+ import * as v from 'valibot';
2
+ export declare const StateDagActionSchema: v.VariantSchema<"type", [v.ObjectSchema<{
3
+ readonly selector: v.StringSchema<undefined>;
4
+ readonly type: v.LiteralSchema<"click", undefined>;
5
+ readonly waitMs: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>, undefined>;
6
+ }, undefined>, v.ObjectSchema<{
7
+ readonly ms: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
8
+ readonly type: v.LiteralSchema<"wait", undefined>;
9
+ }, undefined>], undefined>;
10
+ export type StateDagAction = v.InferOutput<typeof StateDagActionSchema>;
11
+ export declare const StateDagDocumentSchema: v.ObjectSchema<{
12
+ readonly states: v.RecordSchema<v.StringSchema<undefined>, v.ObjectSchema<{
13
+ readonly actions: v.OptionalSchema<v.ArraySchema<v.VariantSchema<"type", [v.ObjectSchema<{
14
+ readonly selector: v.StringSchema<undefined>;
15
+ readonly type: v.LiteralSchema<"click", undefined>;
16
+ readonly waitMs: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>, undefined>;
17
+ }, undefined>, v.ObjectSchema<{
18
+ readonly ms: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
19
+ readonly type: v.LiteralSchema<"wait", undefined>;
20
+ }, undefined>], undefined>, undefined>, undefined>;
21
+ readonly frameStability: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 1, undefined>]>, undefined>;
22
+ readonly preconditions: v.OptionalSchema<v.ArraySchema<v.StringSchema<undefined>, undefined>, undefined>;
23
+ }, undefined>, undefined>;
24
+ readonly createdAt: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
25
+ readonly schemaName: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
26
+ readonly schemaVersion: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
27
+ readonly ushmanVersion: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
28
+ }, undefined>;
29
+ export type StateDagDocument = v.InferOutput<typeof StateDagDocumentSchema>;
30
+ export declare const V3WorkspaceIdDocumentSchema: v.ObjectSchema<{
31
+ readonly donorOriginUrl: v.OptionalSchema<v.NullableSchema<v.StringSchema<undefined>, undefined>, undefined>;
32
+ readonly packageHash: v.StringSchema<undefined>;
33
+ readonly shibukLobbyId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
34
+ readonly createdAt: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
35
+ readonly schemaName: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
36
+ readonly schemaVersion: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
37
+ readonly ushmanVersion: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
38
+ }, undefined>;
39
+ export type V3WorkspaceIdDocument = v.InferOutput<typeof V3WorkspaceIdDocumentSchema>;
40
+ //# sourceMappingURL=schema-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema-types.d.ts","sourceRoot":"","sources":["../src/schema-types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,SAAS,CAAC;AAoB7B,eAAO,MAAM,oBAAoB;;;;;;;0BAA2E,CAAC;AAC7G,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAQxE,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;aAGjC,CAAC;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAE5E,eAAO,MAAM,2BAA2B;;;;;;;;aAKtC,CAAC;AACH,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,2BAA2B,CAAC,CAAC"}
@@ -0,0 +1,32 @@
1
+ import * as v from 'valibot';
2
+ const VersionedFields = {
3
+ createdAt: v.optional(v.string()),
4
+ schemaName: v.optional(v.string()),
5
+ schemaVersion: v.optional(v.string()),
6
+ ushmanVersion: v.optional(v.string()),
7
+ };
8
+ const StateDagClickActionSchema = v.object({
9
+ selector: v.string(),
10
+ type: v.literal('click'),
11
+ waitMs: v.optional(v.pipe(v.number(), v.integer(), v.minValue(0))),
12
+ });
13
+ const StateDagWaitActionSchema = v.object({
14
+ ms: v.pipe(v.number(), v.integer(), v.minValue(0)),
15
+ type: v.literal('wait'),
16
+ });
17
+ export const StateDagActionSchema = v.variant('type', [StateDagClickActionSchema, StateDagWaitActionSchema]);
18
+ const StateDagNodeSchema = v.object({
19
+ actions: v.optional(v.array(StateDagActionSchema)),
20
+ frameStability: v.optional(v.pipe(v.number(), v.integer(), v.minValue(1))),
21
+ preconditions: v.optional(v.array(v.string())),
22
+ });
23
+ export const StateDagDocumentSchema = v.object({
24
+ ...VersionedFields,
25
+ states: v.record(v.string(), StateDagNodeSchema),
26
+ });
27
+ export const V3WorkspaceIdDocumentSchema = v.object({
28
+ ...VersionedFields,
29
+ donorOriginUrl: v.optional(v.nullable(v.string())),
30
+ packageHash: v.string(),
31
+ shibukLobbyId: v.optional(v.string()),
32
+ });
@@ -0,0 +1,31 @@
1
+ export declare const SEED_FINGERPRINT_SCHEMA_VERSION = "shibuk-seed-fingerprints/v1";
2
+ export declare const SEED_FINGERPRINT_SIDECAR_RELATIVE_PATH: string;
3
+ export declare const PARITY_BASELINE_RELATIVE_DIR: string;
4
+ export declare const hashSeedScaffoldContent: (value: string) => string;
5
+ export declare const toPosixPath: (value: string) => string;
6
+ export declare const toWorkspaceRelativePath: ({ filePath, workspaceDir, }: {
7
+ readonly filePath: string;
8
+ readonly workspaceDir: string;
9
+ }) => string;
10
+ /**
11
+ * Load the seed scaffold fingerprint sidecar. Missing files are represented as an empty map
12
+ * so callers can decide whether the workspace should be treated as unmanaged or re-seeded.
13
+ */
14
+ export declare const loadSeedFingerprintSidecar: (workspaceDir: string) => Promise<{
15
+ exists: boolean;
16
+ filePath: string;
17
+ scaffolds: Record<string, string>;
18
+ }>;
19
+ /**
20
+ * Merge updated scaffold fingerprints, prune entries whose files no longer exist, and avoid
21
+ * rewriting the sidecar when the normalized fingerprint map is unchanged.
22
+ */
23
+ export declare const updateSeedFingerprintSidecar: ({ entries, workspaceDir, }: {
24
+ readonly entries: Record<string, string>;
25
+ readonly workspaceDir: string;
26
+ }) => Promise<string>;
27
+ export declare const listFilesRecursive: ({ dir, suffix, }: {
28
+ readonly dir: string;
29
+ readonly suffix: string;
30
+ }) => Promise<string[]>;
31
+ //# sourceMappingURL=seed-scaffolds.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seed-scaffolds.d.ts","sourceRoot":"","sources":["../src/seed-scaffolds.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,+BAA+B,gCAAgC,CAAC;AAC7E,eAAO,MAAM,sCAAsC,QAAgD,CAAC;AACpG,eAAO,MAAM,4BAA4B,QAAkC,CAAC;AAO5E,eAAO,MAAM,uBAAuB,GAAI,OAAO,MAAM,WAIpD,CAAC;AAEF,eAAO,MAAM,WAAW,GAAI,OAAO,MAAM,WAAoC,CAAC;AAE9E,eAAO,MAAM,uBAAuB,GAAI,6BAGrC;IACC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CACjC,WAAuD,CAAC;AA4BzD;;;GAGG;AACH,eAAO,MAAM,0BAA0B,GAAU,cAAc,MAAM;;;eAOxC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;EASlD,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,4BAA4B,GAAU,4BAGhD;IACC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CACjC,oBAwBA,CAAC;AAWF,eAAO,MAAM,kBAAkB,GAAU,kBAGtC;IACC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CAC3B,KAAG,OAAO,CAAC,MAAM,EAAE,CAgBnB,CAAC"}
@@ -0,0 +1,96 @@
1
+ import { mkdir, readdir } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { atomicWriteJson } from "./workspace-paths.js";
4
+ export const SEED_FINGERPRINT_SCHEMA_VERSION = 'shibuk-seed-fingerprints/v1';
5
+ export const SEED_FINGERPRINT_SIDECAR_RELATIVE_PATH = path.join('tests', '.seed-fingerprints.json');
6
+ export const PARITY_BASELINE_RELATIVE_DIR = path.join('parity', 'baseline');
7
+ export const hashSeedScaffoldContent = (value) => {
8
+ const hasher = new Bun.CryptoHasher('sha256');
9
+ hasher.update(value);
10
+ return hasher.digest('hex');
11
+ };
12
+ export const toPosixPath = (value) => value.split(path.sep).join('/');
13
+ export const toWorkspaceRelativePath = ({ filePath, workspaceDir, }) => toPosixPath(path.relative(workspaceDir, filePath));
14
+ const seedFingerprintSidecarPath = (workspaceDir) => path.join(workspaceDir, SEED_FINGERPRINT_SIDECAR_RELATIVE_PATH);
15
+ const isRecord = (value) => typeof value === 'object' && value !== null;
16
+ const assertSeedFingerprintSidecar = (value) => {
17
+ if (!isRecord(value) || value.schemaVersion !== SEED_FINGERPRINT_SCHEMA_VERSION || !isRecord(value.scaffolds)) {
18
+ throw new Error(`tests/.seed-fingerprints.json must be ${SEED_FINGERPRINT_SCHEMA_VERSION}. Re-run seed scaffolding or restore the sidecar.`);
19
+ }
20
+ const scaffolds = {};
21
+ for (const [key, entry] of Object.entries(value.scaffolds)) {
22
+ if (typeof entry !== 'string') {
23
+ throw new Error(`tests/.seed-fingerprints.json has a non-string fingerprint for "${key}".`);
24
+ }
25
+ scaffolds[key] = entry;
26
+ }
27
+ return {
28
+ scaffolds,
29
+ schemaVersion: SEED_FINGERPRINT_SCHEMA_VERSION,
30
+ };
31
+ };
32
+ /**
33
+ * Load the seed scaffold fingerprint sidecar. Missing files are represented as an empty map
34
+ * so callers can decide whether the workspace should be treated as unmanaged or re-seeded.
35
+ */
36
+ export const loadSeedFingerprintSidecar = async (workspaceDir) => {
37
+ const filePath = seedFingerprintSidecarPath(workspaceDir);
38
+ const file = Bun.file(filePath);
39
+ if (!(await file.exists())) {
40
+ return {
41
+ exists: false,
42
+ filePath,
43
+ scaffolds: {},
44
+ };
45
+ }
46
+ return {
47
+ exists: true,
48
+ filePath,
49
+ scaffolds: assertSeedFingerprintSidecar(await file.json()).scaffolds,
50
+ };
51
+ };
52
+ /**
53
+ * Merge updated scaffold fingerprints, prune entries whose files no longer exist, and avoid
54
+ * rewriting the sidecar when the normalized fingerprint map is unchanged.
55
+ */
56
+ export const updateSeedFingerprintSidecar = async ({ entries, workspaceDir, }) => {
57
+ const current = await loadSeedFingerprintSidecar(workspaceDir);
58
+ const nextScaffolds = Object.fromEntries((await Promise.all(Object.entries({
59
+ ...current.scaffolds,
60
+ ...entries,
61
+ }).map(async ([relativePath, fingerprint]) => {
62
+ const absolutePath = path.join(workspaceDir, relativePath);
63
+ return (await Bun.file(absolutePath).exists()) ? [relativePath, fingerprint] : null;
64
+ }))).filter((entry) => entry !== null));
65
+ if (stableFingerprintMapsEqual(current.scaffolds, nextScaffolds)) {
66
+ return current.filePath;
67
+ }
68
+ await mkdir(path.dirname(current.filePath), { recursive: true });
69
+ await atomicWriteJson(current.filePath, {
70
+ scaffolds: nextScaffolds,
71
+ schemaVersion: SEED_FINGERPRINT_SCHEMA_VERSION,
72
+ });
73
+ return current.filePath;
74
+ };
75
+ const stableFingerprintMapsEqual = (left, right) => {
76
+ const leftKeys = Object.keys(left).sort((first, second) => first.localeCompare(second));
77
+ const rightKeys = Object.keys(right).sort((first, second) => first.localeCompare(second));
78
+ if (leftKeys.length !== rightKeys.length) {
79
+ return false;
80
+ }
81
+ return leftKeys.every((key, index) => key === rightKeys[index] && left[key] === right[key]);
82
+ };
83
+ export const listFilesRecursive = async ({ dir, suffix, }) => {
84
+ const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);
85
+ const files = await Promise.all(entries.map(async (entry) => {
86
+ const absolutePath = path.join(dir, entry.name);
87
+ if (entry.isDirectory()) {
88
+ return listFilesRecursive({
89
+ dir: absolutePath,
90
+ suffix,
91
+ });
92
+ }
93
+ return entry.isFile() && absolutePath.endsWith(suffix) ? [absolutePath] : [];
94
+ }));
95
+ return files.flat().sort((left, right) => left.localeCompare(right));
96
+ };
@@ -0,0 +1,36 @@
1
+ import { type NodePath } from '@babel/traverse';
2
+ import * as t from '@babel/types';
3
+ export type BundleBindingKind = 'class' | 'function' | 'import' | 'variable';
4
+ export type BundleBinding = {
5
+ readonly kind: BundleBindingKind;
6
+ readonly name: string;
7
+ readonly path: NodePath<t.Statement | t.ModuleDeclaration>;
8
+ readonly sortableStart: number;
9
+ readonly statement: t.Statement | t.ModuleDeclaration;
10
+ };
11
+ export type BundleAst = {
12
+ readonly ast: t.File;
13
+ readonly bindings: ReadonlyMap<string, BundleBinding>;
14
+ readonly exportedNames: ReadonlySet<string>;
15
+ readonly programPath: NodePath<t.Program>;
16
+ readonly source: string;
17
+ readonly sourcePath: string;
18
+ };
19
+ export declare const parseBundleAst: ({ source, sourcePath, }: {
20
+ readonly source: string;
21
+ readonly sourcePath: string;
22
+ }) => BundleAst;
23
+ export declare const sanitizeSymbolName: (value: string) => string;
24
+ export declare const relativeImportPath: ({ fromFile, toFile }: {
25
+ readonly fromFile: string;
26
+ readonly toFile: string;
27
+ }) => string;
28
+ export declare const stableJsonStringify: (value: unknown, indent?: number) => string;
29
+ export declare const buildStandaloneSymbolModule: ({ bindingName, bundle, outputFilePath, }: {
30
+ readonly bindingName: string;
31
+ readonly bundle: BundleAst;
32
+ readonly outputFilePath?: string;
33
+ }) => string;
34
+ export declare const parseJsonLines: <T>(text: string) => T[];
35
+ export declare const toJsonLines: (rows: readonly unknown[]) => string;
36
+ //# sourceMappingURL=shared.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../src/shared.ts"],"names":[],"mappings":"AAGA,OAAiB,EAAE,KAAK,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,KAAK,CAAC,MAAM,cAAc,CAAC;AAIlC,MAAM,MAAM,iBAAiB,GAAG,OAAO,GAAG,UAAU,GAAG,QAAQ,GAAG,UAAU,CAAC;AAE7E,MAAM,MAAM,aAAa,GAAG;IACxB,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,iBAAiB,CAAC,CAAC;IAC3D,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,SAAS,EAAE,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,iBAAiB,CAAC;CACzD,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACpB,QAAQ,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC;IACrB,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IACtD,QAAQ,CAAC,aAAa,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC5C,QAAQ,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;IAC1C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC/B,CAAC;AAwLF,eAAO,MAAM,cAAc,GAAI,yBAG5B;IACC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC/B,KAAG,SAyCH,CAAC;AA0MF,eAAO,MAAM,kBAAkB,GAAI,OAAO,MAAM,WAAqC,CAAC;AAEtF,eAAO,MAAM,kBAAkB,GAAI,sBAAsB;IAAE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,WAG9G,CAAC;AAgBF,eAAO,MAAM,mBAAmB,GAAI,OAAO,OAAO,EAAE,SAAS,MAAM,WACP,CAAC;AAE7D,eAAO,MAAM,2BAA2B,GAAI,0CAIzC;IACC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAC3B,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CACpC,WA4CA,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,CAAC,EAAE,MAAM,MAAM,KAAG,CAAC,EAaxC,CAAC;AAEX,eAAO,MAAM,WAAW,GAAI,MAAM,SAAS,OAAO,EAAE,WAA2D,CAAC"}
package/dist/shared.js ADDED
@@ -0,0 +1,390 @@
1
+ import path from 'node:path';
2
+ import { fileURLToPath, pathToFileURL } from 'node:url';
3
+ import generate from '@babel/generator';
4
+ import traverse, {} from '@babel/traverse';
5
+ import * as t from '@babel/types';
6
+ import { parseModuleAst } from "./babel-config.js";
7
+ import { getAnonymousDefaultExportBindingName, isAnonymousDefaultExportCallableStatement } from "./default-export.js";
8
+ const MAX_STATEMENT_DEPENDENCIES = 2_048;
9
+ const unwrapStatement = (statement) => {
10
+ if (t.isExportNamedDeclaration(statement) || t.isExportDefaultDeclaration(statement)) {
11
+ return statement.declaration ?? statement;
12
+ }
13
+ return statement;
14
+ };
15
+ const addNamedBinding = ({ bindings, kind, name, path: statementPath, statement, }) => {
16
+ if (bindings.has(name)) {
17
+ return;
18
+ }
19
+ bindings.set(name, {
20
+ kind,
21
+ name,
22
+ path: statementPath,
23
+ sortableStart: statement.start ?? 0,
24
+ statement,
25
+ });
26
+ };
27
+ const collectNamedExportSpecifierNames = (statement) => {
28
+ const names = [];
29
+ for (const specifier of statement.specifiers) {
30
+ if (t.isExportSpecifier(specifier) && t.isIdentifier(specifier.exported)) {
31
+ names.push(specifier.exported.name);
32
+ }
33
+ }
34
+ return names;
35
+ };
36
+ const collectDeclarationExportNames = (declaration) => {
37
+ if ((t.isFunctionDeclaration(declaration) || t.isClassDeclaration(declaration)) && declaration.id) {
38
+ return [declaration.id.name];
39
+ }
40
+ if (!t.isVariableDeclaration(declaration)) {
41
+ return [];
42
+ }
43
+ return declaration.declarations
44
+ .map((declarator) => (t.isIdentifier(declarator.id) ? declarator.id.name : null))
45
+ .filter((name) => name !== null);
46
+ };
47
+ const collectExportedNames = ({ anonymousDefaultExportBindingName, exportedNames, statement, }) => {
48
+ if (t.isExportNamedDeclaration(statement)) {
49
+ for (const name of collectNamedExportSpecifierNames(statement)) {
50
+ exportedNames.add(name);
51
+ }
52
+ if (statement.declaration) {
53
+ for (const name of collectDeclarationExportNames(statement.declaration)) {
54
+ exportedNames.add(name);
55
+ }
56
+ }
57
+ return;
58
+ }
59
+ if (!t.isExportDefaultDeclaration(statement)) {
60
+ return;
61
+ }
62
+ if ((t.isFunctionDeclaration(statement.declaration) || t.isClassDeclaration(statement.declaration)) &&
63
+ statement.declaration.id) {
64
+ exportedNames.add(statement.declaration.id.name);
65
+ return;
66
+ }
67
+ if (anonymousDefaultExportBindingName && isAnonymousDefaultExportCallableStatement(statement)) {
68
+ exportedNames.add(anonymousDefaultExportBindingName);
69
+ }
70
+ };
71
+ const collectBindingsFromVariableDeclaration = ({ bindings, path: statementPath, statement, variableDeclaration, }) => {
72
+ for (const declarator of variableDeclaration.declarations) {
73
+ if (!t.isIdentifier(declarator.id)) {
74
+ continue;
75
+ }
76
+ addNamedBinding({
77
+ bindings,
78
+ kind: 'variable',
79
+ name: declarator.id.name,
80
+ path: statementPath,
81
+ statement,
82
+ });
83
+ }
84
+ };
85
+ const collectBindingsFromStatement = ({ anonymousDefaultExportBindingName, bindings, path: statementPath, statement, }) => {
86
+ if (anonymousDefaultExportBindingName && isAnonymousDefaultExportCallableStatement(statement)) {
87
+ addNamedBinding({
88
+ bindings,
89
+ kind: 'function',
90
+ name: anonymousDefaultExportBindingName,
91
+ path: statementPath,
92
+ statement,
93
+ });
94
+ return;
95
+ }
96
+ const unwrapped = unwrapStatement(statement);
97
+ if (t.isFunctionDeclaration(unwrapped) && unwrapped.id) {
98
+ addNamedBinding({
99
+ bindings,
100
+ kind: 'function',
101
+ name: unwrapped.id.name,
102
+ path: statementPath,
103
+ statement,
104
+ });
105
+ return;
106
+ }
107
+ if (t.isClassDeclaration(unwrapped) && unwrapped.id) {
108
+ addNamedBinding({
109
+ bindings,
110
+ kind: 'class',
111
+ name: unwrapped.id.name,
112
+ path: statementPath,
113
+ statement,
114
+ });
115
+ return;
116
+ }
117
+ if (t.isImportDeclaration(unwrapped)) {
118
+ for (const specifier of unwrapped.specifiers) {
119
+ addNamedBinding({
120
+ bindings,
121
+ kind: 'import',
122
+ name: specifier.local.name,
123
+ path: statementPath,
124
+ statement,
125
+ });
126
+ }
127
+ return;
128
+ }
129
+ if (t.isVariableDeclaration(unwrapped)) {
130
+ collectBindingsFromVariableDeclaration({
131
+ bindings,
132
+ path: statementPath,
133
+ statement,
134
+ variableDeclaration: unwrapped,
135
+ });
136
+ }
137
+ };
138
+ export const parseBundleAst = ({ source, sourcePath, }) => {
139
+ const ast = parseModuleAst({
140
+ source,
141
+ sourcePath,
142
+ });
143
+ const bindings = new Map();
144
+ const exportedNames = new Set();
145
+ const anonymousDefaultExportBindingName = getAnonymousDefaultExportBindingName(ast.program.body);
146
+ let programPath = null;
147
+ traverse(ast, {
148
+ Program(path) {
149
+ programPath = path;
150
+ for (const statementPath of path.get('body')) {
151
+ collectExportedNames({
152
+ anonymousDefaultExportBindingName,
153
+ exportedNames,
154
+ statement: statementPath.node,
155
+ });
156
+ collectBindingsFromStatement({
157
+ anonymousDefaultExportBindingName,
158
+ bindings,
159
+ path: statementPath,
160
+ statement: statementPath.node,
161
+ });
162
+ }
163
+ },
164
+ });
165
+ if (!programPath) {
166
+ throw new Error(`Failed to parse ${sourcePath}: missing Program path.`);
167
+ }
168
+ return {
169
+ ast,
170
+ bindings,
171
+ exportedNames,
172
+ programPath,
173
+ source,
174
+ sourcePath,
175
+ };
176
+ };
177
+ const isLocalReference = (identifierPath) => {
178
+ if (!identifierPath.isReferencedIdentifier()) {
179
+ return false;
180
+ }
181
+ const parent = identifierPath.parentPath;
182
+ if (parent.isObjectProperty({ computed: false }) && parent.node.key === identifierPath.node) {
183
+ return false;
184
+ }
185
+ if (parent.isClassMethod() || parent.isClassProperty() || parent.isObjectMethod()) {
186
+ return false;
187
+ }
188
+ return true;
189
+ };
190
+ const collectStatementDependencies = ({ bundle, rootName, seenNames, }) => {
191
+ if (seenNames.has(rootName)) {
192
+ return;
193
+ }
194
+ if (seenNames.size >= MAX_STATEMENT_DEPENDENCIES) {
195
+ throw new Error(`Exceeded standalone dependency limit (${MAX_STATEMENT_DEPENDENCIES}) while extracting "${rootName}" from ${bundle.sourcePath}.`);
196
+ }
197
+ seenNames.add(rootName);
198
+ const binding = bundle.bindings.get(rootName);
199
+ if (!binding) {
200
+ return;
201
+ }
202
+ binding.path.traverse({
203
+ Identifier(identifierPath) {
204
+ if (!isLocalReference(identifierPath)) {
205
+ return;
206
+ }
207
+ const identifierName = identifierPath.node.name;
208
+ if (identifierName === rootName) {
209
+ return;
210
+ }
211
+ const target = bundle.bindings.get(identifierName);
212
+ if (!target) {
213
+ return;
214
+ }
215
+ const resolvedBinding = identifierPath.scope.getBinding(identifierName);
216
+ if (!resolvedBinding || resolvedBinding.scope !== bundle.programPath.scope) {
217
+ return;
218
+ }
219
+ collectStatementDependencies({
220
+ bundle,
221
+ rootName: identifierName,
222
+ seenNames,
223
+ });
224
+ },
225
+ });
226
+ };
227
+ const rewriteImportSpecifier = ({ outputFilePath, sourcePath, specifier, }) => {
228
+ if (!outputFilePath) {
229
+ return specifier;
230
+ }
231
+ let absoluteSource;
232
+ if (specifier.startsWith('.')) {
233
+ absoluteSource = path.resolve(path.dirname(sourcePath), specifier);
234
+ }
235
+ else {
236
+ try {
237
+ const resolvedUrl = import.meta.resolve(specifier, pathToFileURL(sourcePath).href);
238
+ absoluteSource = resolvedUrl.startsWith('file:') ? fileURLToPath(resolvedUrl) : specifier;
239
+ }
240
+ catch {
241
+ return specifier;
242
+ }
243
+ }
244
+ return relativeImportPath({
245
+ fromFile: outputFilePath,
246
+ toFile: absoluteSource,
247
+ });
248
+ };
249
+ const normalizeWrappedExportDeclaration = ({ bindingName, outputFilePath, sourcePath, statement, }) => {
250
+ if (!statement.declaration || !t.isDeclaration(statement.declaration)) {
251
+ return null;
252
+ }
253
+ return normalizeEmittedStatement({
254
+ bindingName,
255
+ outputFilePath,
256
+ sourcePath,
257
+ statement: statement.declaration,
258
+ });
259
+ };
260
+ const materializeDefaultExportDeclaration = ({ bindingName, declaration, }) => {
261
+ if (t.isFunctionDeclaration(declaration) || t.isClassDeclaration(declaration)) {
262
+ if (declaration.id) {
263
+ return t.cloneNode(declaration, true);
264
+ }
265
+ if (!bindingName) {
266
+ return null;
267
+ }
268
+ const namedDeclaration = t.cloneNode(declaration, true);
269
+ namedDeclaration.id = t.identifier(bindingName);
270
+ return namedDeclaration;
271
+ }
272
+ if (t.isFunctionExpression(declaration) ||
273
+ t.isArrowFunctionExpression(declaration) ||
274
+ t.isClassExpression(declaration)) {
275
+ if (!bindingName) {
276
+ return null;
277
+ }
278
+ return t.variableDeclaration('const', [
279
+ t.variableDeclarator(t.identifier(bindingName), t.cloneNode(declaration, true)),
280
+ ]);
281
+ }
282
+ return null;
283
+ };
284
+ const normalizeEmittedStatement = ({ bindingName, outputFilePath, sourcePath, statement, }) => {
285
+ const unwrapped = unwrapStatement(statement);
286
+ if (t.isExportNamedDeclaration(statement)) {
287
+ return normalizeWrappedExportDeclaration({
288
+ bindingName,
289
+ outputFilePath,
290
+ sourcePath,
291
+ statement,
292
+ });
293
+ }
294
+ if (t.isExportDefaultDeclaration(statement)) {
295
+ const materialized = materializeDefaultExportDeclaration({
296
+ bindingName,
297
+ declaration: statement.declaration,
298
+ });
299
+ if (materialized) {
300
+ return materialized;
301
+ }
302
+ return normalizeWrappedExportDeclaration({
303
+ bindingName,
304
+ outputFilePath,
305
+ sourcePath,
306
+ statement,
307
+ });
308
+ }
309
+ if (t.isImportDeclaration(unwrapped)) {
310
+ const nextImport = t.cloneNode(unwrapped, true);
311
+ nextImport.source = t.stringLiteral(rewriteImportSpecifier({
312
+ outputFilePath,
313
+ sourcePath,
314
+ specifier: nextImport.source.value,
315
+ }));
316
+ return nextImport;
317
+ }
318
+ return t.cloneNode(statement, true);
319
+ };
320
+ export const sanitizeSymbolName = (value) => value.replace(/[^\w.-]+/gu, '_');
321
+ export const relativeImportPath = ({ fromFile, toFile }) => {
322
+ const rel = path.relative(path.dirname(fromFile), toFile).split(path.sep).join('/');
323
+ return rel.startsWith('.') ? rel : `./${rel}`;
324
+ };
325
+ const sortStableJsonValue = (value) => {
326
+ if (Array.isArray(value)) {
327
+ return value.map((entry) => sortStableJsonValue(entry));
328
+ }
329
+ if (value && typeof value === 'object') {
330
+ return Object.fromEntries(Object.entries(value)
331
+ .sort(([left], [right]) => left.localeCompare(right))
332
+ .map(([key, entry]) => [key, sortStableJsonValue(entry)]));
333
+ }
334
+ return value;
335
+ };
336
+ export const stableJsonStringify = (value, indent) => JSON.stringify(sortStableJsonValue(value), null, indent);
337
+ export const buildStandaloneSymbolModule = ({ bindingName, bundle, outputFilePath, }) => {
338
+ if (!bundle.bindings.has(bindingName)) {
339
+ throw new Error(`Unable to extract symbol "${bindingName}" from ${bundle.sourcePath}.`);
340
+ }
341
+ const requiredNames = new Set();
342
+ collectStatementDependencies({
343
+ bundle,
344
+ rootName: bindingName,
345
+ seenNames: requiredNames,
346
+ });
347
+ const statements = [];
348
+ const seenStatements = new Set();
349
+ for (const entry of [...requiredNames]
350
+ .map((name) => bundle.bindings.get(name))
351
+ .filter((binding) => binding !== undefined)
352
+ .sort((left, right) => left.sortableStart - right.sortableStart)) {
353
+ if (seenStatements.has(entry.statement)) {
354
+ continue;
355
+ }
356
+ seenStatements.add(entry.statement);
357
+ const normalized = normalizeEmittedStatement({
358
+ bindingName: entry.name,
359
+ outputFilePath,
360
+ sourcePath: bundle.sourcePath,
361
+ statement: entry.statement,
362
+ });
363
+ if (normalized) {
364
+ statements.push(normalized);
365
+ }
366
+ }
367
+ const program = t.program([
368
+ ...statements,
369
+ t.exportNamedDeclaration(null, [t.exportSpecifier(t.identifier(bindingName), t.identifier(bindingName))], null),
370
+ ]);
371
+ return generate(program, {
372
+ comments: true,
373
+ compact: false,
374
+ concise: false,
375
+ retainLines: false,
376
+ }).code;
377
+ };
378
+ export const parseJsonLines = (text) => text
379
+ .split(/\r?\n/u)
380
+ .map((line) => line.trim())
381
+ .filter(Boolean)
382
+ .map((line, index) => {
383
+ try {
384
+ return JSON.parse(line);
385
+ }
386
+ catch (error) {
387
+ throw new Error(`Invalid JSONL at line ${index + 1}: ${error instanceof Error ? error.message : String(error)}`);
388
+ }
389
+ });
390
+ export const toJsonLines = (rows) => rows.map((row) => stableJsonStringify(row)).join('\n');
@@ -0,0 +1,5 @@
1
+ import type { StateDagAction, StateDagDocument } from './schema-types.ts';
2
+ export declare const resolveStateDagOrder: (document: StateDagDocument, stateId: string) => string[];
3
+ export declare const collectStateDagActions: (document: StateDagDocument, stateId: string) => StateDagAction[];
4
+ export type { StateDagDocument };
5
+ //# sourceMappingURL=state-dag.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state-dag.d.ts","sourceRoot":"","sources":["../src/state-dag.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAE1E,eAAO,MAAM,oBAAoB,GAAI,UAAU,gBAAgB,EAAE,SAAS,MAAM,KAAG,MAAM,EA2BxF,CAAC;AAEF,eAAO,MAAM,sBAAsB,GAAI,UAAU,gBAAgB,EAAE,SAAS,MAAM,KAAG,cAAc,EACJ,CAAC;AAEhG,YAAY,EAAE,gBAAgB,EAAE,CAAC"}
@@ -0,0 +1,27 @@
1
+ export const resolveStateDagOrder = (document, stateId) => {
2
+ const visiting = new Set();
3
+ const visited = new Set();
4
+ const order = [];
5
+ const visit = (id) => {
6
+ if (visited.has(id)) {
7
+ return;
8
+ }
9
+ if (visiting.has(id)) {
10
+ throw new Error(`state-dag cycle detected at "${id}"`);
11
+ }
12
+ const node = document.states[id];
13
+ if (!node) {
14
+ throw new Error(`state "${id}" is not defined in state-dag.json`);
15
+ }
16
+ visiting.add(id);
17
+ for (const dep of node.preconditions ?? []) {
18
+ visit(dep);
19
+ }
20
+ visiting.delete(id);
21
+ visited.add(id);
22
+ order.push(id);
23
+ };
24
+ visit(stateId);
25
+ return order;
26
+ };
27
+ export const collectStateDagActions = (document, stateId) => resolveStateDagOrder(document, stateId).flatMap((id) => document.states[id]?.actions ?? []);