supercov 0.0.44 → 0.0.46

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 (45) hide show
  1. package/README.md +21 -12
  2. package/docs/agent-loop.md +17 -9
  3. package/docs/assertion-agent.md +146 -0
  4. package/docs/assertion-evidence.md +72 -663
  5. package/docs/assertion-maps.md +236 -0
  6. package/docs/assertions.md +132 -0
  7. package/docs/cli.md +40 -8
  8. package/docs/coverage-model.md +18 -0
  9. package/docs/evidence.md +28 -0
  10. package/docs/performance.md +16 -0
  11. package/docs/supported-suites.md +64 -147
  12. package/package.json +36 -35
  13. package/runtime/javascript/launchSupervisor.mjs +5 -0
  14. package/runtime/javascript/nodeTest.mjs +57 -41
  15. package/runtime/javascript/provenance.mjs +4 -1
  16. package/runtime/javascript/register.mjs +9 -5
  17. package/runtime/javascript/runnerEvidence.mjs +4 -1
  18. package/runtime/javascript/runtime.mjs +62 -36
  19. package/schemas/assertions.schema.json +276 -0
  20. package/analyzers/typescript/README.md +0 -59
  21. package/analyzers/typescript/bin/compiler-identity.mjs +0 -78
  22. package/analyzers/typescript/bin/identity.mjs +0 -71
  23. package/analyzers/typescript/bin/query.mjs +0 -29
  24. package/analyzers/typescript/dist/analyze.js +0 -5273
  25. package/analyzers/typescript/dist/archive.js +0 -337
  26. package/analyzers/typescript/dist/awaited-observations.js +0 -376
  27. package/analyzers/typescript/dist/build-identity.json +0 -1
  28. package/analyzers/typescript/dist/compiler.js +0 -32
  29. package/analyzers/typescript/dist/frontend.js +0 -75
  30. package/analyzers/typescript/dist/mock-counts.js +0 -2517
  31. package/analyzers/typescript/dist/native-frontend.js +0 -271
  32. package/analyzers/typescript/dist/pragmas.js +0 -186
  33. package/analyzers/typescript/dist/types.js +0 -1
  34. package/analyzers/typescript/package.json +0 -27
  35. package/analyzers/typescript/src/analyze.ts +0 -6180
  36. package/analyzers/typescript/src/archive.ts +0 -471
  37. package/analyzers/typescript/src/awaited-observations.ts +0 -561
  38. package/analyzers/typescript/src/compiler.ts +0 -49
  39. package/analyzers/typescript/src/frontend.ts +0 -136
  40. package/analyzers/typescript/src/mock-counts.ts +0 -3219
  41. package/analyzers/typescript/src/native-frontend.ts +0 -315
  42. package/analyzers/typescript/src/pragmas.ts +0 -284
  43. package/analyzers/typescript/src/types.ts +0 -45
  44. package/analyzers/typescript/tsconfig.json +0 -12
  45. package/docs/code-verification.md +0 -4
@@ -36,13 +36,15 @@ export function runnerTestId(identity) {
36
36
  ];
37
37
  // Preserve existing top-level, uniquely registered test IDs. Nested tests
38
38
  // and repeated registrations need more than a shared source/name identity.
39
+ if (identity.role === "setup")
40
+ parts.push("role", "setup");
39
41
  if (identity.parentTestId)
40
42
  parts.push("parent", identity.parentTestId);
41
43
  if (identity.registrationOrdinal)
42
44
  parts.push("registration", identity.registrationOrdinal);
43
45
  // Titles can themselves contain separator text. Domain-separate and encode
44
46
  // the extended identity structurally so it cannot alias a literal title.
45
- const key = identity.parentTestId || identity.registrationOrdinal
47
+ const key = identity.role === "setup" || identity.parentTestId || identity.registrationOrdinal
46
48
  ? JSON.stringify(["registration-v2", ...parts])
47
49
  : parts.join("\0");
48
50
  return `${identity.runner}:${createHash("sha256").update(key).digest("hex").slice(0, 24)}`;
@@ -116,6 +118,7 @@ export function writeRunnerEvidence(identity, status, scope, evidenceDirectoryOv
116
118
  title: identity.name.split(" > ").at(-1) ?? identity.name,
117
119
  retry: identity.retry ?? 0,
118
120
  status,
121
+ ...(identity.role ? { role: identity.role } : {}),
119
122
  provenance: inferTestProvenance({
120
123
  runner: identity.runner,
121
124
  file: testFile,
@@ -144,9 +144,6 @@ function createState() {
144
144
  decisions: /* @__PURE__ */ new Map(),
145
145
  hits: /* @__PURE__ */ new Set(),
146
146
  events: [],
147
- // Position of the test-file statement currently executing ("file:line:column"), set by the
148
- // instrumented test module; production hits recorded meanwhile are attributed to it.
149
- testStatement: void 0,
150
147
  // Per value-position logical expression (`a && b`, `a || b`, `a ?? b`): the distinct
151
148
  // (right operand evaluated?, result truthy?) pairs seen, keyed by branch id.
152
149
  logicals: /* @__PURE__ */ new Map(),
@@ -317,7 +314,6 @@ function resetCoverage(testId2) {
317
314
  state.decisions.clear();
318
315
  state.hits.clear();
319
316
  state.events.length = 0;
320
- state.testStatement = void 0;
321
317
  state.logicals.clear();
322
318
  state.eventKeys.clear();
323
319
  state.probeV2ContextEpochs.clear();
@@ -758,8 +754,8 @@ function withNodeAssertionPhase(operation, source, callback) {
758
754
  const existing = context.phaseId && assertionPhaseState(scope).phaseIds.has(context.phaseId);
759
755
  if (existing)
760
756
  return callback();
761
- // The adapter's stack is only a fallback. Avoid formatting it when a lexical
762
- // probe already supplied the active phase's exact original-source identity.
757
+ // A lexical occurrence already identifies its source. Stack fallback is lazy
758
+ // and qualified so transformed coordinates cannot impersonate original ones.
763
759
  if (typeof source === "function")
764
760
  source = source();
765
761
  const bridged = (_a8 = runtimeGlobal.__SUPERCOV_ASSERTION_PHASE_BRIDGE__) == null ? void 0 : _a8.call(runtimeGlobal, operation, source, callback);
@@ -792,15 +788,12 @@ function withNodeAssertionPhase(operation, source, callback) {
792
788
  throw cleanInstrumentationStack(error);
793
789
  }
794
790
  }
795
- // Resolve the target exactly once, before argument evaluation, just like a
796
- // native call reference. The closure carries only source identity and receiver;
797
- // it never captures an assertion phase or mutable current-statement state across
798
- // await. Rejected arguments therefore create no phase, and awaited producer work
799
- // is not retroactively attributed to the assertion invocation.
800
- function bindNodeAssertion(operation, source, receiver, method) {
801
- const target = method === void 0 ? receiver : receiver[method];
802
- const thisArgument = method === void 0 ? void 0 : receiver;
803
- return (...args) => withNodeAssertionPhase(operation, source, () => Reflect.apply(target, thisArgument, args));
791
+ // Callee binding preserves receiver, getter/evaluation order, spreads and the
792
+ // original await/yield placement. Only the matcher call opens the phase.
793
+ function bindNodeAssertionPhase(operation, source, target, property = null) {
794
+ const callback = property === null ? target : target[property];
795
+ const receiver = property === null ? undefined : target;
796
+ return (...args) => withNodeAssertionPhase(operation, source, () => Reflect.apply(callback, receiver, args));
804
797
  }
805
798
  function takeNodeAssertionPhases(scope) {
806
799
  var _a8, _b;
@@ -970,15 +963,56 @@ function requestCoverageContext(value) {
970
963
  const phaseId = typeof rawPhaseId === "string" && rawPhaseId.length > 0 && (!scope || phaseBelongsToAttempt(rawPhaseId, scope.attemptId)) ? rawPhaseId : void 0;
971
964
  return __spreadValues(__spreadValues({}, scope ? { scope } : {}), phaseId ? { phaseId } : {});
972
965
  }
973
- function withRequestPhase(handler) {
966
+ // Keep connection ownership on the emitter, without changing listener
967
+ // identity (removeListener/off and once still see the original callbacks).
968
+ const emitterContextMaps = runtimeGlobal.__SUPERCOV_EMITTER_CONTEXT_MAPS__ ??= new Map();
969
+ const emitterCoverageContexts = emitterContextMaps.get(runtimeInstance) ?? new WeakMap();
970
+ emitterContextMaps.set(runtimeInstance, emitterCoverageContexts);
971
+ const patchedEmitterInstances = runtimeGlobal.__SUPERCOV_EMITTER_PATCHED_INSTANCES__ ??= new Set();
972
+ function installNodeRequestPropagation() {
973
+ if (isBrowser || patchedEmitterInstances.has(runtimeInstance) || typeof process === "undefined") return;
974
+ const EventEmitter = process.getBuiltinModule?.("node:events")?.EventEmitter;
975
+ const Server = process.getBuiltinModule?.("node:http")?.Server;
976
+ const SecureServer = process.getBuiltinModule?.("node:https")?.Server;
977
+ if (!EventEmitter) return;
978
+ const original = EventEmitter.prototype.emit;
979
+ EventEmitter.prototype.emit = function(event, ...args) {
980
+ let context = emitterCoverageContexts.get(this);
981
+ if ((event === "request" || event === "upgrade") &&
982
+ ((Server && this instanceof Server) || (SecureServer && this instanceof SecureServer))) {
983
+ // HTTP headers establish ownership even when Express/SDK listeners and
984
+ // the server process were created by a shared before hook.
985
+ const incoming = requestCoverageContext(args[0]);
986
+ if (incoming) context = incoming.scope ? incoming : context ?? currentRequestContext();
987
+ }
988
+ const invoke = () => Reflect.apply(original, this, [event, ...args]);
989
+ try {
990
+ return context ? withCoverageCarrier({ version: 1, ...context }, invoke) : invoke();
991
+ } finally {
992
+ if (event === "close") emitterCoverageContexts.delete(this);
993
+ }
994
+ };
995
+ patchedEmitterInstances.add(runtimeInstance);
996
+ }
997
+ installNodeRequestPropagation();
998
+
999
+ function withRequestPhase(handler, event) {
974
1000
  if (!serverPhaseStorage)
975
1001
  return handler;
1002
+ const registeredContext = currentRequestContext();
976
1003
  return function coverageRequestPhase(...args) {
977
1004
  var _a8, _b, _c, _d;
978
1005
  const requestContext = args.map((argument) => requestCoverageContext(argument)).find((context2) => context2 !== void 0);
979
- const inheritedContext = requestContext === void 0 ? currentRequestContext() : {};
1006
+ // An untagged request to a test-owned server must not erase that server's
1007
+ // owner. Shared servers have no captured test scope and stay unattributed.
1008
+ const inheritedContext = requestContext?.scope ? {} : registeredContext.scope ? registeredContext : requestContext === void 0 ? currentRequestContext() : {};
980
1009
  const context = __spreadValues(__spreadValues({}, ((_a8 = requestContext == null ? void 0 : requestContext.scope) != null ? _a8 : inheritedContext.scope) ? { scope: (_b = requestContext == null ? void 0 : requestContext.scope) != null ? _b : inheritedContext.scope } : {}), ((_c = requestContext == null ? void 0 : requestContext.phaseId) != null ? _c : inheritedContext.phaseId) ? { phaseId: (_d = requestContext == null ? void 0 : requestContext.phaseId) != null ? _d : inheritedContext.phaseId } : {});
981
- const invoke = () => Reflect.apply(handler, this, args);
1010
+ const invoke = () => {
1011
+ if (event === "connection" && context.scope && args[0] &&
1012
+ typeof args[0] === "object" && typeof args[0].emit === "function")
1013
+ emitterCoverageContexts.set(args[0], context);
1014
+ return Reflect.apply(handler, this, args);
1015
+ };
982
1016
  return requestContext !== void 0 || context.scope || context.phaseId ? serverPhaseStorage.run(context, () => withProbeV2Context(context, invoke)) : invoke();
983
1017
  };
984
1018
  }
@@ -995,31 +1029,25 @@ function recordBrowserEvent(event) {
995
1029
  state.events.push(event);
996
1030
  return true;
997
1031
  }
998
- function testStatement(source) {
999
- state.testStatement = typeof source === "string" && source.length > 0 ? source : void 0;
1000
- }
1001
- function statementFields() {
1002
- return state.testStatement ? { statementId: state.testStatement } : {};
1003
- }
1004
1032
  function coverageHit(id) {
1005
1033
  state.hits.add(id);
1006
1034
  const timestampMs = Date.now();
1007
1035
  const phaseId = currentPhaseId();
1008
1036
  if (isBrowser) {
1009
- if (recordBrowserEvent(__spreadProps(__spreadValues(__spreadValues({
1037
+ if (recordBrowserEvent(__spreadProps(__spreadValues({
1010
1038
  type: "hit",
1011
1039
  id,
1012
1040
  timestampMs
1013
- }, phaseId ? { phaseId } : {}), statementFields()), {
1041
+ }, phaseId ? { phaseId } : {}), {
1014
1042
  environment: "browser"
1015
1043
  })))
1016
1044
  persistBrowser();
1017
1045
  } else {
1018
- appendServer(__spreadValues(__spreadValues({
1046
+ appendServer(__spreadValues({
1019
1047
  type: "hit",
1020
1048
  id,
1021
1049
  timestampMs
1022
- }, phaseId ? { phaseId } : {}), statementFields()));
1050
+ }, phaseId ? { phaseId } : {}));
1023
1051
  }
1024
1052
  }
1025
1053
  function registerProbeV2(definition) {
@@ -1254,22 +1282,22 @@ function mcdcEnd(frame, value) {
1254
1282
  const timestampMs = Date.now();
1255
1283
  const phaseId = currentPhaseId();
1256
1284
  if (isBrowser) {
1257
- if (recordBrowserEvent(__spreadProps(__spreadValues(__spreadValues({
1285
+ if (recordBrowserEvent(__spreadProps(__spreadValues({
1258
1286
  type: "decision",
1259
1287
  id: decision.meta.id,
1260
1288
  vector,
1261
1289
  timestampMs
1262
- }, phaseId ? { phaseId } : {}), statementFields()), {
1290
+ }, phaseId ? { phaseId } : {}), {
1263
1291
  environment: "browser"
1264
1292
  })))
1265
1293
  persistBrowser();
1266
1294
  } else {
1267
- appendServer(__spreadValues(__spreadValues({
1295
+ appendServer(__spreadValues({
1268
1296
  type: "decision",
1269
1297
  meta: decision.meta,
1270
1298
  vector,
1271
1299
  timestampMs
1272
- }, phaseId ? { phaseId } : {}), statementFields()));
1300
+ }, phaseId ? { phaseId } : {}));
1273
1301
  }
1274
1302
  return value;
1275
1303
  }
@@ -1310,13 +1338,12 @@ const directRuntimeApi = {
1310
1338
  selectionEnd,
1311
1339
  selectionRight,
1312
1340
  takeNodeAssertionPhases,
1313
- bindNodeAssertion,
1314
- testStatement,
1315
1341
  tryBegin,
1316
1342
  tryCatch,
1317
1343
  tryEnd,
1318
1344
  withCoverageCarrier,
1319
1345
  withNodeAssertionPhase,
1346
+ bindNodeAssertionPhase,
1320
1347
  withRequestPhase,
1321
1348
  writeExclusiveBackgroundRecord
1322
1349
  };
@@ -1361,13 +1388,12 @@ export {
1361
1388
  selectionEnd,
1362
1389
  selectionRight,
1363
1390
  takeNodeAssertionPhases,
1364
- bindNodeAssertion,
1365
- testStatement,
1366
1391
  tryBegin,
1367
1392
  tryCatch,
1368
1393
  tryEnd,
1369
1394
  withCoverageCarrier,
1370
1395
  withNodeAssertionPhase,
1396
+ bindNodeAssertionPhase,
1371
1397
  withRequestPhase,
1372
1398
  writeExclusiveBackgroundRecord
1373
1399
  };
@@ -0,0 +1,276 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "title": "AssertionMap",
4
+ "type": "object",
5
+ "properties": {
6
+ "schemaVersion": {
7
+ "type": "integer",
8
+ "format": "uint32",
9
+ "minimum": 2,
10
+ "maximum": 2
11
+ },
12
+ "assertions": {
13
+ "type": "array",
14
+ "items": {
15
+ "$ref": "#/$defs/Assertion"
16
+ }
17
+ },
18
+ "changeAssessments": {
19
+ "type": "array",
20
+ "items": {
21
+ "$ref": "#/$defs/ChangeAssessment"
22
+ }
23
+ },
24
+ "retiredAssertions": {
25
+ "type": "array",
26
+ "items": {
27
+ "$ref": "#/$defs/Retired"
28
+ }
29
+ }
30
+ },
31
+ "additionalProperties": false,
32
+ "required": [
33
+ "schemaVersion",
34
+ "assertions"
35
+ ],
36
+ "$defs": {
37
+ "Assertion": {
38
+ "type": "object",
39
+ "properties": {
40
+ "id": {
41
+ "type": "string"
42
+ },
43
+ "at": {
44
+ "$ref": "#/$defs/Anchor"
45
+ },
46
+ "questions": {
47
+ "type": "array",
48
+ "items": {
49
+ "type": "string"
50
+ }
51
+ },
52
+ "observes": {
53
+ "type": "array",
54
+ "items": {
55
+ "type": "string"
56
+ },
57
+ "default": []
58
+ },
59
+ "flows": {
60
+ "type": "array",
61
+ "items": {
62
+ "$ref": "#/$defs/Flow"
63
+ },
64
+ "default": []
65
+ }
66
+ },
67
+ "additionalProperties": false,
68
+ "required": [
69
+ "id",
70
+ "at"
71
+ ]
72
+ },
73
+ "Anchor": {
74
+ "description": "One-based lines and UTF-8 byte columns, for every language. Text is exact.",
75
+ "type": "object",
76
+ "properties": {
77
+ "file": {
78
+ "type": "string"
79
+ },
80
+ "line": {
81
+ "type": "integer",
82
+ "format": "uint",
83
+ "minimum": 0
84
+ },
85
+ "column": {
86
+ "type": "integer",
87
+ "format": "uint",
88
+ "minimum": 0
89
+ },
90
+ "text": {
91
+ "type": "string"
92
+ }
93
+ },
94
+ "additionalProperties": false,
95
+ "required": [
96
+ "file",
97
+ "line",
98
+ "column",
99
+ "text"
100
+ ]
101
+ },
102
+ "Flow": {
103
+ "type": "object",
104
+ "properties": {
105
+ "id": {
106
+ "type": "string"
107
+ },
108
+ "basis": {
109
+ "type": [
110
+ "string",
111
+ "null"
112
+ ],
113
+ "pattern": "^scov2:[0-9a-f]{64}$"
114
+ },
115
+ "explanation": {
116
+ "type": "string"
117
+ },
118
+ "appliesTo": {
119
+ "type": "array",
120
+ "items": {
121
+ "$ref": "#/$defs/TestSelector"
122
+ }
123
+ },
124
+ "nodes": {
125
+ "type": "array",
126
+ "items": {
127
+ "$ref": "#/$defs/Node"
128
+ }
129
+ },
130
+ "edges": {
131
+ "type": "array",
132
+ "items": {
133
+ "$ref": "#/$defs/Edge"
134
+ },
135
+ "default": []
136
+ },
137
+ "countsAsAsserted": {
138
+ "type": "array",
139
+ "items": {
140
+ "type": "string"
141
+ }
142
+ },
143
+ "watch": {
144
+ "type": "array",
145
+ "items": {
146
+ "type": "string"
147
+ }
148
+ },
149
+ "questions": {
150
+ "type": "array",
151
+ "items": {
152
+ "type": "string"
153
+ }
154
+ }
155
+ },
156
+ "additionalProperties": false,
157
+ "required": [
158
+ "id",
159
+ "basis",
160
+ "explanation",
161
+ "appliesTo",
162
+ "nodes",
163
+ "countsAsAsserted",
164
+ "watch"
165
+ ]
166
+ },
167
+ "TestSelector": {
168
+ "type": "object",
169
+ "properties": {
170
+ "file": {
171
+ "type": "string"
172
+ },
173
+ "name": {
174
+ "type": "string"
175
+ }
176
+ },
177
+ "additionalProperties": false,
178
+ "required": [
179
+ "file",
180
+ "name"
181
+ ]
182
+ },
183
+ "Node": {
184
+ "type": "object",
185
+ "properties": {
186
+ "id": {
187
+ "type": "string"
188
+ },
189
+ "at": {
190
+ "$ref": "#/$defs/Anchor"
191
+ },
192
+ "role": {
193
+ "type": "string"
194
+ },
195
+ "meaning": {
196
+ "type": "string"
197
+ }
198
+ },
199
+ "additionalProperties": false,
200
+ "required": [
201
+ "id",
202
+ "at"
203
+ ]
204
+ },
205
+ "Edge": {
206
+ "type": "object",
207
+ "properties": {
208
+ "from": {
209
+ "type": "string"
210
+ },
211
+ "to": {
212
+ "type": "string"
213
+ },
214
+ "kind": {
215
+ "type": "string"
216
+ },
217
+ "basis": {
218
+ "type": "string"
219
+ }
220
+ },
221
+ "additionalProperties": false,
222
+ "required": [
223
+ "from",
224
+ "to",
225
+ "kind"
226
+ ]
227
+ },
228
+ "ChangeAssessment": {
229
+ "type": "object",
230
+ "properties": {
231
+ "id": {
232
+ "type": "string"
233
+ },
234
+ "basis": {
235
+ "type": [
236
+ "string",
237
+ "null"
238
+ ],
239
+ "pattern": "^scov2:[0-9a-f]{64}$"
240
+ },
241
+ "affectedFlows": {
242
+ "type": "array",
243
+ "items": {
244
+ "type": "string"
245
+ }
246
+ },
247
+ "explanation": {
248
+ "type": "string"
249
+ }
250
+ },
251
+ "additionalProperties": false,
252
+ "required": [
253
+ "id",
254
+ "basis",
255
+ "affectedFlows",
256
+ "explanation"
257
+ ]
258
+ },
259
+ "Retired": {
260
+ "type": "object",
261
+ "properties": {
262
+ "assertion": {
263
+ "$ref": "#/$defs/Assertion"
264
+ },
265
+ "reason": {
266
+ "type": "string"
267
+ }
268
+ },
269
+ "additionalProperties": false,
270
+ "required": [
271
+ "assertion",
272
+ "reason"
273
+ ]
274
+ }
275
+ }
276
+ }
@@ -1,59 +0,0 @@
1
- # JS/TS assertion analyzer (internal)
2
-
3
- This private package is the post-run analysis component used by Supercov's
4
- `runs <run> assertions` command. It is bundled in the npm distribution, not
5
- published as a separate CLI or SDK.
6
-
7
- See [Assertion evidence](../../docs/assertion-evidence.md) for public commands,
8
- pragma syntax, report interpretation and supported scope.
9
-
10
- ## Product boundary
11
-
12
- - `bin/query.mjs` is the CLI-to-analyzer JSON transport. It accepts an ordinary
13
- archive representation prepared by Rust, validates identities and returns facts.
14
- - `bin/identity.mjs` checks the analyzer source/build identity; its direct
15
- invocation writes that identity during the build.
16
- - `bin/compiler-identity.mjs` fingerprints the project's compiler implementation.
17
- - `src/archive.ts` validates the archive protocol, source positions and attempt
18
- ownership, then constructs query-local evidence in memory.
19
- - `src/analyze.ts` extracts source-flow facts and exact assertion witnesses.
20
- The Rust engine joins those facts into candidate classifications.
21
- - `src/mock-counts.ts` models bounded synchronous console-mock count histories,
22
- keeping count evidence separate from general value-protection claims.
23
- - `src/awaited-observations.ts` recognizes bounded native child-capture/poll
24
- source patterns for hint attachment, without manufacturing runtime witnesses.
25
- - `src/frontend.ts` and `src/native-frontend.ts` implement the legacy and native
26
- TypeScript compiler boundaries. `src/pragmas.ts` validates optional hints.
27
-
28
- There is no converted-input command, on-disk research input, V8 remapping,
29
- mutation runner or diagnostic-ablation option in the product interface.
30
- Source files ship to support build-identity verification; tests and research
31
- tools do not ship. Assertion candidates remain unverified, not safety proofs.
32
- Only executable JavaScript and its build identity ship from `dist`; there is no
33
- public SDK declaration surface or generated source-map payload.
34
-
35
- ## Build and test
36
-
37
- From this directory:
38
-
39
- ```sh
40
- npm ci --ignore-scripts
41
- npm test
42
- ```
43
-
44
- The build uses pinned TypeScript 5.8.3 explicitly. Project analysis uses the
45
- project's compiler; native 7.0.2 has a separate frontend and identity checks.
46
- Native compiler processes close after both successful and failed queries.
47
-
48
- From the repository root, exercise real Node/Vitest archives and the installed
49
- npm package:
50
-
51
- ```sh
52
- npm run test:asserted-integration
53
- npm run test:asserted-package
54
- ```
55
-
56
- Controlled unit fixtures test parser, flow and witness rules. The ordinary-archive
57
- integration tests execute real suites and explicit behavioral counterexamples.
58
- Compiler-path and identity regressions run across supported CI platforms.
59
- Historical external-cohort research is not part of the default suite or product.
@@ -1,78 +0,0 @@
1
- import { createHash } from "node:crypto";
2
- import { readFileSync, readdirSync } from "node:fs";
3
- import { dirname, resolve } from "node:path";
4
- import { createRequire } from "node:module";
5
- import { pathToFileURL } from "node:url";
6
- import { projectCompilerPath } from "../dist/compiler.js";
7
-
8
- /** Hash the native implementation as well as its JS client; version.cjs alone is not the compiler. */
9
- export function compilerIdentity(projectRoot) {
10
- return compilerIdentityFromEntry(projectCompilerPath(projectRoot));
11
- }
12
-
13
- export function compilerIdentityFromEntry(entry) {
14
- const require = createRequire(pathToFileURL(entry));
15
- const { version } = require(entry);
16
- const entrySha256 = createHash("sha256")
17
- .update(readFileSync(entry))
18
- .digest("hex");
19
- if (version !== "7.0.2")
20
- return { backend: "typescript-legacy", version, sha256: entrySha256 };
21
- const packageRoot = dirname(require.resolve("typescript/package.json"));
22
- const platform = `@typescript/typescript-${process.platform}-${process.arch}`;
23
- let nativeRoot;
24
- try {
25
- nativeRoot = dirname(require.resolve(`${platform}/package.json`));
26
- } catch (cause) {
27
- throw new Error(
28
- `TypeScript 7's native compiler dependency ${platform} is missing. Install the project's optional TypeScript dependencies before recapturing tests.`,
29
- { cause },
30
- );
31
- }
32
- const nativeMetadata = JSON.parse(
33
- readFileSync(resolve(nativeRoot, "package.json"), "utf8"),
34
- );
35
- if (nativeMetadata.name !== platform || nativeMetadata.version !== version)
36
- throw new Error(
37
- `TypeScript native package identity mismatch: expected ${platform}@${version}`,
38
- );
39
- const executable = resolve(
40
- nativeRoot,
41
- "lib",
42
- process.platform === "win32" ? "tsc.exe" : "tsc",
43
- );
44
- const nativeSha256 = createHash("sha256")
45
- .update(readFileSync(executable))
46
- .digest("hex");
47
- const hash = createHash("sha256");
48
- let files = 0;
49
- function walk(root, relative = "") {
50
- for (const file of readdirSync(resolve(root, relative), {
51
- withFileTypes: true,
52
- }).sort((a, b) => a.name.localeCompare(b.name))) {
53
- if (file.name === "node_modules") continue;
54
- const path = relative ? `${relative}/${file.name}` : file.name;
55
- if (file.isDirectory()) walk(root, path);
56
- else if (file.isFile()) {
57
- hash
58
- .update(path)
59
- .update("\0")
60
- .update(readFileSync(resolve(root, path)))
61
- .update("\0");
62
- files++;
63
- } else throw new Error(`Unsupported compiler package entry: ${path}`);
64
- }
65
- }
66
- hash.update("typescript-package\0");
67
- walk(packageRoot);
68
- hash.update("native-package\0");
69
- walk(nativeRoot);
70
- return {
71
- backend: "typescript-native-7",
72
- version,
73
- sha256: hash.digest("hex"),
74
- nativePackage: platform,
75
- nativeSha256,
76
- files,
77
- };
78
- }