vouchington-tooling 0.21.0 → 0.22.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 (244) hide show
  1. package/README.md +59 -1
  2. package/dist/agent-blackboard/snapshot-partition-format.mjs +2 -72
  3. package/dist/agent-blackboard/snapshot-partition-guards.d.mts +3 -0
  4. package/dist/agent-blackboard/snapshot-partition-guards.mjs +9 -0
  5. package/dist/agent-blackboard/snapshot-partition-predicates.d.mts +3 -0
  6. package/dist/agent-blackboard/snapshot-partition-predicates.mjs +65 -0
  7. package/dist/api-contract-discovery/header-contract-parser.d.mts +3 -0
  8. package/dist/api-contract-discovery/header-contract-parser.mjs +93 -0
  9. package/dist/api-contract-discovery/header-contract-registry.d.mts +3 -0
  10. package/dist/api-contract-discovery/header-contract-registry.mjs +38 -0
  11. package/dist/api-contract-discovery/header-contract-types.d.mts +19 -0
  12. package/dist/api-contract-discovery/header-contract-types.mjs +1 -0
  13. package/dist/api-contract-discovery/index.d.mts +31 -0
  14. package/dist/api-contract-discovery/index.mjs +26 -0
  15. package/dist/api-contract-discovery/query-contract-extraction.d.mts +3 -0
  16. package/dist/api-contract-discovery/query-contract-extraction.mjs +65 -0
  17. package/dist/api-contract-discovery/query-contract-literals.d.mts +8 -0
  18. package/dist/api-contract-discovery/query-contract-literals.mjs +54 -0
  19. package/dist/api-contract-discovery/query-contract-registry.d.mts +3 -0
  20. package/dist/api-contract-discovery/query-contract-registry.mjs +68 -0
  21. package/dist/api-contract-discovery/query-contract-types.d.mts +8 -0
  22. package/dist/api-contract-discovery/query-contract-types.mjs +1 -0
  23. package/dist/api-contract-discovery/registered-route-catalog.d.mts +10 -0
  24. package/dist/api-contract-discovery/registered-route-catalog.mjs +81 -0
  25. package/dist/api-contract-discovery/registered-route-handler-analysis.d.mts +2 -0
  26. package/dist/api-contract-discovery/registered-route-handler-analysis.mjs +74 -0
  27. package/dist/api-contract-discovery/request-contract-implicit.d.mts +17 -0
  28. package/dist/api-contract-discovery/request-contract-implicit.mjs +73 -0
  29. package/dist/api-contract-discovery/request-contract-lenient.d.mts +28 -0
  30. package/dist/api-contract-discovery/request-contract-lenient.mjs +59 -0
  31. package/dist/api-contract-discovery/request-contract-registry.d.mts +14 -0
  32. package/dist/api-contract-discovery/request-contract-registry.mjs +61 -0
  33. package/dist/api-contract-discovery/request-contract-route-analysis.d.mts +25 -0
  34. package/dist/api-contract-discovery/request-contract-route-analysis.mjs +68 -0
  35. package/dist/api-contract-discovery/request-contract-types.d.mts +12 -0
  36. package/dist/api-contract-discovery/request-contract-types.mjs +1 -0
  37. package/dist/api-contract-discovery/response-contract-ctx-analysis.d.mts +7 -0
  38. package/dist/api-contract-discovery/response-contract-ctx-analysis.mjs +28 -0
  39. package/dist/api-contract-discovery/response-contract-error-branch.d.mts +15 -0
  40. package/dist/api-contract-discovery/response-contract-error-branch.mjs +47 -0
  41. package/dist/api-contract-discovery/response-contract-implicit-sources.test-helpers.d.mts +145 -0
  42. package/dist/api-contract-discovery/response-contract-implicit-sources.test-helpers.mjs +145 -0
  43. package/dist/api-contract-discovery/response-contract-implicit.d.mts +5 -0
  44. package/dist/api-contract-discovery/response-contract-implicit.mjs +110 -0
  45. package/dist/api-contract-discovery/response-contract-lenient.d.mts +23 -0
  46. package/dist/api-contract-discovery/response-contract-lenient.mjs +52 -0
  47. package/dist/api-contract-discovery/response-contract-media.d.mts +3 -0
  48. package/dist/api-contract-discovery/response-contract-media.mjs +46 -0
  49. package/dist/api-contract-discovery/response-contract-registration.d.mts +11 -0
  50. package/dist/api-contract-discovery/response-contract-registration.mjs +61 -0
  51. package/dist/api-contract-discovery/response-contract-registry.d.mts +7 -0
  52. package/dist/api-contract-discovery/response-contract-registry.mjs +76 -0
  53. package/dist/api-contract-discovery/response-contract-route-analysis.d.mts +13 -0
  54. package/dist/api-contract-discovery/response-contract-route-analysis.mjs +144 -0
  55. package/dist/api-contract-discovery/response-contract-status.d.mts +27 -0
  56. package/dist/api-contract-discovery/response-contract-status.mjs +80 -0
  57. package/dist/api-contract-discovery/response-contract-types.d.mts +16 -0
  58. package/dist/api-contract-discovery/response-contract-types.mjs +1 -0
  59. package/dist/api-contract-discovery/test-setup.test-helpers.d.mts +3 -0
  60. package/dist/api-contract-discovery/test-setup.test-helpers.mjs +2 -0
  61. package/dist/ast-grep-examples/companion-parity-compare.d.mts +3 -0
  62. package/dist/ast-grep-examples/companion-parity-compare.mjs +58 -0
  63. package/dist/ast-grep-examples/companion-parity.d.mts +2 -1
  64. package/dist/ast-grep-examples/companion-parity.mjs +2 -58
  65. package/dist/browser-session-runner/attempt.mjs +29 -48
  66. package/dist/browser-session-runner/termination.d.mts +11 -0
  67. package/dist/browser-session-runner/termination.mjs +34 -0
  68. package/dist/cli/parse-direct-options.d.mts +26 -0
  69. package/dist/cli/parse-direct-options.mjs +72 -0
  70. package/dist/cli/parse-forwarded-commands.d.mts +42 -0
  71. package/dist/cli/parse-forwarded-commands.mjs +29 -0
  72. package/dist/cli/parse-options.d.mts +1 -2
  73. package/dist/cli/parse-options.mjs +2 -72
  74. package/dist/cli/parse.mjs +4 -26
  75. package/dist/compiler-build/freshness.d.mts +27 -0
  76. package/dist/compiler-build/freshness.mjs +116 -0
  77. package/dist/compiler-build/index.d.mts +2 -0
  78. package/dist/compiler-build/index.mjs +2 -0
  79. package/dist/compiler-build/settlement.d.mts +10 -0
  80. package/dist/compiler-build/settlement.mjs +14 -0
  81. package/dist/config-inventory/call-arguments.d.mts +1 -0
  82. package/dist/config-inventory/call-arguments.mjs +30 -0
  83. package/dist/config-inventory/collect.d.mts +3 -0
  84. package/dist/config-inventory/collect.mjs +43 -0
  85. package/dist/config-inventory/dynamic-configs.d.mts +2 -0
  86. package/dist/config-inventory/dynamic-configs.mjs +9 -0
  87. package/dist/config-inventory/env-expression-readers.d.mts +3 -0
  88. package/dist/config-inventory/env-expression-readers.mjs +117 -0
  89. package/dist/config-inventory/env-prefix-readers.d.mts +2 -0
  90. package/dist/config-inventory/env-prefix-readers.mjs +14 -0
  91. package/dist/config-inventory/env-reference-matchers.d.mts +3 -0
  92. package/dist/config-inventory/env-reference-matchers.mjs +9 -0
  93. package/dist/config-inventory/env-var-accumulator.d.mts +6 -0
  94. package/dist/config-inventory/env-var-accumulator.mjs +82 -0
  95. package/dist/config-inventory/env-vars.d.mts +4 -0
  96. package/dist/config-inventory/env-vars.mjs +108 -0
  97. package/dist/config-inventory/format.d.mts +2 -0
  98. package/dist/config-inventory/format.mjs +69 -0
  99. package/dist/config-inventory/index.d.mts +3 -0
  100. package/dist/config-inventory/index.mjs +2 -0
  101. package/dist/config-inventory/package-gates.d.mts +2 -0
  102. package/dist/config-inventory/package-gates.mjs +54 -0
  103. package/dist/config-inventory/shared.d.mts +3 -0
  104. package/dist/config-inventory/shared.mjs +7 -0
  105. package/dist/config-inventory/types.d.mts +96 -0
  106. package/dist/config-inventory/types.mjs +1 -0
  107. package/dist/contract-schema/contract-schema-bounded-array.d.mts +15 -0
  108. package/dist/contract-schema/contract-schema-bounded-array.mjs +32 -0
  109. package/dist/contract-schema/contract-schema-type-extractor.d.mts +2 -8
  110. package/dist/contract-schema/contract-schema-type-extractor.mjs +2 -31
  111. package/dist/coverage-transport/control-v2-parse-object.d.mts +12 -0
  112. package/dist/coverage-transport/control-v2-parse-object.mjs +28 -0
  113. package/dist/coverage-transport/control-v2.mjs +2 -27
  114. package/dist/gh-api-shell-quoting/argument-scan.d.mts +7 -0
  115. package/dist/gh-api-shell-quoting/argument-scan.mjs +35 -0
  116. package/dist/gh-api-shell-quoting/logical-line.d.mts +3 -0
  117. package/dist/gh-api-shell-quoting/logical-line.mjs +90 -0
  118. package/dist/gh-api-shell-quoting/scan.d.mts +2 -4
  119. package/dist/gh-api-shell-quoting/scan.mjs +1 -119
  120. package/dist/gh-cli/diff.d.mts +24 -4
  121. package/dist/gh-cli/diff.mjs +111 -4
  122. package/dist/gh-cli/index.d.mts +2 -1
  123. package/dist/gh-cli/index.mjs +1 -1
  124. package/dist/gha-workspace-policy/docker-options.d.mts +4 -0
  125. package/dist/gha-workspace-policy/docker-options.mjs +93 -0
  126. package/dist/gha-workspace-policy/docker-workspace-user.mjs +1 -168
  127. package/dist/gha-workspace-policy/shell-token-modes.d.mts +10 -0
  128. package/dist/gha-workspace-policy/shell-token-modes.mjs +31 -0
  129. package/dist/gha-workspace-policy/shell-tokens.d.mts +1 -0
  130. package/dist/gha-workspace-policy/shell-tokens.mjs +65 -0
  131. package/dist/index.d.mts +2 -1
  132. package/dist/index.mjs +1 -1
  133. package/dist/openapi-document/contract-schema-types.d.mts +1 -1
  134. package/dist/openapi-document/openapi-types.d.mts +1 -1
  135. package/dist/pnpm-install/native-family.d.mts +3 -0
  136. package/dist/pnpm-install/native-family.mjs +31 -0
  137. package/dist/pnpm-install/native-health.d.mts +1 -4
  138. package/dist/pnpm-install/native-health.mjs +2 -31
  139. package/dist/retrospective-facts/local-merge.d.mts +2 -0
  140. package/dist/retrospective-facts/local-merge.mjs +21 -0
  141. package/dist/retrospective-facts/local.mjs +1 -21
  142. package/dist/retrospective-transcript/claude-blocks.d.mts +3 -0
  143. package/dist/retrospective-transcript/claude-blocks.mjs +38 -0
  144. package/dist/retrospective-transcript/claude.mjs +4 -34
  145. package/dist/retrospective-transcript/codex-payload.d.mts +2 -0
  146. package/dist/retrospective-transcript/codex-payload.mjs +51 -0
  147. package/dist/retrospective-transcript/codex-records.d.mts +2 -0
  148. package/dist/retrospective-transcript/codex-records.mjs +95 -0
  149. package/dist/retrospective-transcript/codex.mjs +2 -144
  150. package/dist/retrospective-transcript/index.d.mts +3 -16
  151. package/dist/retrospective-transcript/index.mjs +4 -48
  152. package/dist/retrospective-transcript/resolve-transcript-file.d.mts +19 -0
  153. package/dist/retrospective-transcript/resolve-transcript-file.mjs +50 -0
  154. package/dist/session-friction/command-segments.d.mts +3 -0
  155. package/dist/session-friction/command-segments.mjs +94 -0
  156. package/dist/session-friction/log-file.d.mts +7 -0
  157. package/dist/session-friction/log-file.mjs +83 -0
  158. package/dist/session-friction/log.mjs +3 -83
  159. package/dist/session-friction/normalize.mjs +1 -94
  160. package/dist/sql-scanner/comments.d.mts +1 -0
  161. package/dist/sql-scanner/comments.mjs +101 -0
  162. package/dist/sql-scanner/index.d.mts +1 -1
  163. package/dist/sql-scanner/index.mjs +1 -100
  164. package/dist/sql-scanner/literals.mjs +1 -35
  165. package/dist/sql-scanner/quote-decode.d.mts +2 -0
  166. package/dist/sql-scanner/quote-decode.mjs +35 -0
  167. package/dist/vitest-diagnostics/index.d.mts +7 -0
  168. package/dist/vitest-diagnostics/index.mjs +4 -0
  169. package/dist/vitest-diagnostics/vitest-fork-exit-error-detail.d.mts +7 -0
  170. package/dist/vitest-diagnostics/vitest-fork-exit-error-detail.mjs +26 -0
  171. package/dist/vitest-diagnostics/vitest-fork-exit-records.d.mts +26 -0
  172. package/dist/vitest-diagnostics/vitest-fork-exit-records.mjs +161 -0
  173. package/dist/vitest-diagnostics/vitest-fork-exit-sentinel.d.mts +14 -0
  174. package/dist/vitest-diagnostics/vitest-fork-exit-sentinel.mjs +131 -0
  175. package/dist/vitest-diagnostics/vitest-fork-leak-detection.d.mts +26 -0
  176. package/dist/vitest-diagnostics/vitest-fork-leak-detection.mjs +96 -0
  177. package/dist/vitest-diagnostics/vitest-fork-leak-diagnostics.d.mts +3 -0
  178. package/dist/vitest-diagnostics/vitest-fork-leak-diagnostics.mjs +14 -0
  179. package/dist/vitest-diagnostics/vitest-fork-leak-episode.d.mts +37 -0
  180. package/dist/vitest-diagnostics/vitest-fork-leak-episode.mjs +118 -0
  181. package/dist/vitest-diagnostics/vitest-worker-exit-diagnostics-errors.d.mts +2 -0
  182. package/dist/vitest-diagnostics/vitest-worker-exit-diagnostics-errors.mjs +42 -0
  183. package/dist/vitest-diagnostics/vitest-worker-exit-diagnostics-reporter.d.mts +12 -0
  184. package/dist/vitest-diagnostics/vitest-worker-exit-diagnostics-reporter.mjs +142 -0
  185. package/dist/workflow-policy/concurrency-scope.d.mts +4 -0
  186. package/dist/workflow-policy/concurrency-scope.mjs +66 -0
  187. package/dist/workflow-policy/concurrency.d.mts +10 -0
  188. package/dist/workflow-policy/concurrency.mjs +77 -0
  189. package/dist/workflow-policy/condition-entailment.d.mts +20 -0
  190. package/dist/workflow-policy/condition-entailment.mjs +132 -0
  191. package/dist/workflow-policy/condition-split.d.mts +15 -0
  192. package/dist/workflow-policy/condition-split.mjs +71 -0
  193. package/dist/workflow-policy/graph.d.mts +2 -0
  194. package/dist/workflow-policy/graph.mjs +138 -0
  195. package/dist/workflow-policy/index.d.mts +7 -0
  196. package/dist/workflow-policy/index.mjs +6 -0
  197. package/dist/workflow-policy/permission-mismatches.d.mts +3 -0
  198. package/dist/workflow-policy/permission-mismatches.mjs +52 -0
  199. package/dist/workflow-policy/permissions.d.mts +16 -0
  200. package/dist/workflow-policy/permissions.mjs +83 -0
  201. package/dist/workflow-policy/secret-readiness.d.mts +18 -0
  202. package/dist/workflow-policy/secret-readiness.mjs +163 -0
  203. package/dist/workflow-policy/shell-comments.d.mts +7 -0
  204. package/dist/workflow-policy/shell-comments.mjs +28 -0
  205. package/dist/workflow-policy/types.d.mts +90 -0
  206. package/dist/workflow-policy/types.mjs +1 -0
  207. package/dist/workspace-gates/manifest-version-parser.mjs +1 -52
  208. package/dist/workspace-gates/manifest-version-skips.d.mts +1 -0
  209. package/dist/workspace-gates/manifest-version-skips.mjs +52 -0
  210. package/docs/workflow-and-compiler.md +74 -0
  211. package/package.json +26 -2
  212. package/skills/agent-workflow/SKILL.md +4 -4
  213. package/skills/agent-workflow/references/review-response.md +47 -44
  214. package/skills/backend-vitest-test-authoring/SKILL.md +9 -6
  215. package/skills/blackboard/SKILL.md +8 -8
  216. package/skills/dependabot/SKILL.md +68 -67
  217. package/skills/dotnet-test-authoring/SKILL.md +11 -12
  218. package/skills/git-commit-checklist/SKILL.md +3 -3
  219. package/skills/github-actions-authoring/SKILL.md +40 -38
  220. package/skills/github-actions-checklist/SKILL.md +93 -85
  221. package/skills/github-issue/SKILL.md +82 -78
  222. package/skills/nextjs-vitest-test-authoring/SKILL.md +8 -6
  223. package/skills/npm-publishing/SKILL.md +42 -38
  224. package/skills/organize-github-issues/SKILL.md +18 -22
  225. package/skills/package-json-checklist/SKILL.md +5 -5
  226. package/skills/planning/SKILL.md +3 -3
  227. package/skills/playwright-authoring/SKILL.md +10 -9
  228. package/skills/postgres-node-performance-tuning/SKILL.md +10 -6
  229. package/skills/postgres-partitioning-uuid-v7/SKILL.md +10 -7
  230. package/skills/pr-description/SKILL.md +4 -3
  231. package/skills/retrospective/SKILL.md +14 -15
  232. package/skills/retrospective-distill/SKILL.md +14 -14
  233. package/skills/review-ci-logs/SKILL.md +18 -19
  234. package/skills/review-github-issue-taxonomy/SKILL.md +21 -18
  235. package/skills/revisit-followups/SKILL.md +3 -3
  236. package/skills/stacked-prs/SKILL.md +30 -61
  237. package/skills/static-analysis-checklist/SKILL.md +6 -4
  238. package/skills/storybook-authoring/SKILL.md +9 -9
  239. package/skills/swift-test-authoring/SKILL.md +10 -9
  240. package/skills/test-authoring/SKILL.md +14 -14
  241. package/skills/test-authoring/references/core-practice.md +12 -11
  242. package/skills/test-authoring/references/dependency-boundaries.md +36 -35
  243. package/skills/test-authoring/references/tautological-tests.md +45 -44
  244. package/skills/vitest-test-authoring/SKILL.md +9 -6
package/README.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  Libraries and the `vouchington` CLI.
4
4
 
5
+ The [workflow and compiler primitives](docs/workflow-and-compiler.md) provide consumer supplied
6
+ GitHub Actions policy checks and compiler host freshness with bounded build settlement.
7
+
8
+ [`config-inventory`](../../docs/config-inventory.md) collects environment, dynamic-configuration,
9
+ and package-manager evidence using caller-owned file roles and policies.
10
+
5
11
  ```bash
6
12
  npm install vouchington-tooling
7
13
  # optional, only if you import vouchington-tooling/sql-ast
@@ -9,6 +15,8 @@ npm install @libpg-query/parser
9
15
  # optional, only if you import vouchington-tooling/contract-schema
10
16
  # (classic compiler API; typescript@7's package root is version-only)
11
17
  npm install @typescript/typescript6
18
+ # optional, only if you use the Vitest reporter export
19
+ npm install vitest
12
20
  # optional, only for vouchington-tooling/agent-blackboard and agent-blackboard CLI commands
13
21
  npm install agent-blackboard@^0.6.0
14
22
  ```
@@ -227,6 +235,7 @@ import {
227
235
  validateFixtureContracts,
228
236
  writeGeneratedFiles,
229
237
  } from 'vouchington-tooling/api-fixtures'
238
+ import { discoverAppRouteCtxContractsV1 } from 'vouchington-tooling/api-contract-discovery'
230
239
  import { decide, deriveRetryAttempt } from 'vouchington-tooling/transient-retry'
231
240
  import { parseCsvRows, streamCsvRows } from 'vouchington-tooling/csv'
232
241
  import {
@@ -268,20 +277,40 @@ import { normalizeSwiftSource } from 'vouchington-tooling/swift-semantic-equal'
268
277
  import { parseUniqueSwiftBinaryTargetChecksum } from 'vouchington-tooling/swift-source-offset'
269
278
  import { validateResolvedPinDelta } from 'vouchington-tooling/swift-resolved-pin-delta'
270
279
  import {
280
+ createForkLeakDetector,
281
+ createVitestWorkerExitDiagnosticsReporter,
271
282
  formatDiagnosticReportSummaries,
272
283
  readDiagnosticReportSummaries,
284
+ registerForkExitSentinel,
273
285
  } from 'vouchington-tooling/vitest-diagnostics'
274
286
  import { runRetrospectiveTranscript } from 'vouchington-tooling/retrospective-transcript'
275
287
  import { appendJournal, probeBlackboard } from 'vouchington-tooling/agent-blackboard'
276
288
  import { buildSessionFrictionReport, recordFriction } from 'vouchington-tooling/session-friction'
277
289
  import { GITHUB_BODY_MAX_CHARACTERS, validateGitHubBodyLength } from 'vouchington-tooling'
278
- import { createPullRequest, getDiffAgainstBase, runGh, runGit } from 'vouchington-tooling/gh-cli'
290
+ import { createPullRequest, processDiffCommand, runGh, runGit } from 'vouchington-tooling/gh-cli'
279
291
  import {
280
292
  shellScriptViolations,
281
293
  workflowYamlViolations,
282
294
  } from 'vouchington-tooling/gh-api-shell-quoting'
283
295
  ```
284
296
 
297
+ `processDiffCommand` runs a caller-supplied `git diff` or `gh pr diff` executable and argv without
298
+ a shell, emitting complete unified-diff file blocks without accumulating the whole patch. It keeps
299
+ at most one file block (which can itself be large), preserves preambles and line content, and drains
300
+ a bounded stderr tail for typed command failures. Blocks are provisional until the returned promise
301
+ resolves: callers must discard accumulated results if the command later exits nonzero or is signaled.
302
+
303
+ ```ts
304
+ let changedFileCount = 0
305
+ await processDiffCommand({ executable: 'git', args: ['diff', 'origin/main...HEAD'] }, async () => {
306
+ changedFileCount += 1
307
+ })
308
+ // Use changedFileCount only after processDiffCommand resolves.
309
+ ```
310
+
311
+ This breaking minor removes `getDiffAgainstBase`; migrate callers to `processDiffCommand` and pass
312
+ the command they need to run.
313
+
285
314
  `checkSccComplexity` keeps its single repository-wide scan when `scopes` is omitted. Consumers
286
315
  that need separately ratcheted areas may provide named scopes with positional `includePaths`; SCC
287
316
  runs once per scope and reports the scope in each diagnostic. Parse a consumer-owned JSON baseline
@@ -383,6 +412,35 @@ token-source router remains deprecated for one release line.
383
412
  filenames, tolerates partial files, returns only a bounded field allowlist, and never emits raw
384
413
  native frame symbols. Both structured reads and text rendering have hard report-count limits.
385
414
 
415
+ `api-contract-discovery` is the version-one adapter for `app.route('/path').get/post/...` and
416
+ `ctx` response conventions. Pass a classic TypeScript `program` and its route `sourceFiles` to
417
+ `discoverAppRouteCtxContractsV1`. The result contains registered routes and response, request,
418
+ query, and header contracts. The caller owns program construction, route-file selection, and
419
+ generation. Pass `options.formatAliases` and `options.boundedArrayAlias` for application type
420
+ aliases; `options.onRouteError` enables lenient response and request extraction. The adapter
421
+ does not cache programs or depend on the caller's repository layout. OpenAPI document assembly
422
+ remains a separate `openapi-document` call.
423
+
424
+ Version one recognizes literal-key `apiResponse`, `apiNoContent`, `apiOpenApiRawResponse`,
425
+ `apiRequest`, `apiRequestContract`, `apiNoRequestBody`, `apiQuery`, and `apiHeaders` markers inside
426
+ `app.route(...).get/post/put/patch/delete(...)` handlers. It also inspects unmarked `ctx.json`,
427
+ `ctx.pipeline`, `ctx.response.empty/buffer/xml`, `ctx.request.json/buffer`, `parseJsonBody`,
428
+ `ctx.setStatus`, and static `Content-Type` setters. The input source files and TypeScript checker
429
+ are the source representation; the adapter does not parse or discover a repository on its own.
430
+
431
+ Fork diagnostics use a caller-selected `recordDirectory` in both the worker setup file and the
432
+ main-process reporter. Call `registerForkExitSentinel({ recordDirectory })` once per fork, then
433
+ update `setCurrentForkExitModule` and `setCurrentForkExitProject` around tests. Construct
434
+ `createVitestWorkerExitDiagnosticsReporter({ recordDirectory })` in the Vitest reporter list
435
+ before workers start. The reporter clears stale records at construction, preserves them across
436
+ watch reruns, and prints an attribution summary only for abnormal worker exits. The worker writes
437
+ a synchronous stderr line and one JSONL file per PID; a start record without an exit record
438
+ identifies a fork killed before its handler could run. `createForkLeakDetector` accepts tracked
439
+ resource names and warmup, threshold, and sustained-growth settings; the caller supplies each
440
+ checkpoint's resource counts. Use a caller-owned run directory: the reporter clears only regular
441
+ numeric PID `.jsonl` files there at construction and leaves the directory and other files alone.
442
+ Application metrics and CI artifact handling stay with the caller.
443
+
386
444
  `browser-session-runner` supervises caller-created browser-test processes. Callers supply command
387
445
  construction, line classification, retry/outcome policy, and budgets; the library owns process-group
388
446
  termination, output line buffering, shared deadlines, progress watchdogs, diagnostics, and parent signals.
@@ -1,76 +1,6 @@
1
1
  import { assertSessionId } from './session-id.mjs';
2
- function isObject(value) {
3
- return typeof value === 'object' && value !== null && !Array.isArray(value);
4
- }
5
- function isTimestamp(value) {
6
- return typeof value === 'string' && Number.isFinite(Date.parse(value));
7
- }
8
- function isCount(value) {
9
- return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;
10
- }
11
- function isSelection(value) {
12
- if (!isObject(value) || value.archived !== false)
13
- return false;
14
- if (value.agent !== undefined && typeof value.agent !== 'string')
15
- return false;
16
- if (value.version !== undefined && typeof value.version !== 'string')
17
- return false;
18
- if (value.parentSessionId !== undefined &&
19
- value.parentSessionId !== null &&
20
- typeof value.parentSessionId !== 'string')
21
- return false;
22
- if (value.data !== undefined && !isObject(value.data))
23
- return false;
24
- if (value.dataArrayContains !== undefined &&
25
- (!isObject(value.dataArrayContains) ||
26
- Object.keys(value.dataArrayContains).length === 0 ||
27
- Object.entries(value.dataArrayContains).some(([key, member]) => key.length === 0 || typeof member !== 'string' || member.length === 0)))
28
- return false;
29
- return (value.inactiveForHours === undefined ||
30
- (typeof value.inactiveForHours === 'number' &&
31
- Number.isFinite(value.inactiveForHours) &&
32
- value.inactiveForHours > 0));
33
- }
34
- function isManifest(value) {
35
- if (!isObject(value) || value.schemaVersion !== 1 || value.status !== 'complete')
36
- return false;
37
- if (!isTimestamp(value.createdAt) ||
38
- !isTimestamp(value.completedAt) ||
39
- !isSelection(value.selection))
40
- return false;
41
- if (!isObject(value.counts) ||
42
- !isCount(value.counts.sessions) ||
43
- !isCount(value.counts.entries) ||
44
- !isCount(value.counts.records))
45
- return false;
46
- return (isObject(value.ordering) &&
47
- value.ordering.sessions === 'createdAt ascending' &&
48
- value.ordering.entries === 'createdAt ascending within session' &&
49
- value.consistency === 'best-effort');
50
- }
51
- function isSession(value) {
52
- if (isObject(value) &&
53
- typeof value.id === 'string' &&
54
- value.id.length > 0 &&
55
- (value.parentSessionId === null || typeof value.parentSessionId === 'string') &&
56
- typeof value.agent === 'string' &&
57
- typeof value.version === 'string' &&
58
- isTimestamp(value.createdAt) &&
59
- (value.lastEntryAt === null || isTimestamp(value.lastEntryAt)) &&
60
- value.archivedAt === null &&
61
- isObject(value.data)) {
62
- try {
63
- assertSessionId(value.id);
64
- if (value.parentSessionId !== null)
65
- assertSessionId(value.parentSessionId, 'parent session id');
66
- return true;
67
- }
68
- catch {
69
- return false;
70
- }
71
- }
72
- return false;
73
- }
2
+ import { isManifest, isSession } from './snapshot-partition-predicates.mjs';
3
+ import { isObject, isTimestamp } from './snapshot-partition-guards.mjs';
74
4
  function isEntry(value) {
75
5
  if (isObject(value) &&
76
6
  typeof value.sessionId === 'string' &&
@@ -0,0 +1,3 @@
1
+ export declare function isObject(value: unknown): value is Record<string, unknown>;
2
+ export declare function isTimestamp(value: unknown): value is string;
3
+ export declare function isCount(value: unknown): value is number;
@@ -0,0 +1,9 @@
1
+ export function isObject(value) {
2
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
3
+ }
4
+ export function isTimestamp(value) {
5
+ return typeof value === 'string' && Number.isFinite(Date.parse(value));
6
+ }
7
+ export function isCount(value) {
8
+ return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;
9
+ }
@@ -0,0 +1,3 @@
1
+ import type { SnapshotManifest } from './snapshot-types.mts';
2
+ export declare function isManifest(value: unknown): value is SnapshotManifest;
3
+ export declare function isSession(value: unknown): value is Record<string, unknown>;
@@ -0,0 +1,65 @@
1
+ import { assertSessionId } from './session-id.mjs';
2
+ import { isCount, isObject, isTimestamp } from './snapshot-partition-guards.mjs';
3
+ function isSelection(value) {
4
+ if (!isObject(value) || value.archived !== false)
5
+ return false;
6
+ if (value.agent !== undefined && typeof value.agent !== 'string')
7
+ return false;
8
+ if (value.version !== undefined && typeof value.version !== 'string')
9
+ return false;
10
+ if (value.parentSessionId !== undefined &&
11
+ value.parentSessionId !== null &&
12
+ typeof value.parentSessionId !== 'string')
13
+ return false;
14
+ if (value.data !== undefined && !isObject(value.data))
15
+ return false;
16
+ if (value.dataArrayContains !== undefined &&
17
+ (!isObject(value.dataArrayContains) ||
18
+ Object.keys(value.dataArrayContains).length === 0 ||
19
+ Object.entries(value.dataArrayContains).some(([key, member]) => key.length === 0 || typeof member !== 'string' || member.length === 0)))
20
+ return false;
21
+ return (value.inactiveForHours === undefined ||
22
+ (typeof value.inactiveForHours === 'number' &&
23
+ Number.isFinite(value.inactiveForHours) &&
24
+ value.inactiveForHours > 0));
25
+ }
26
+ export function isManifest(value) {
27
+ if (!isObject(value) || value.schemaVersion !== 1 || value.status !== 'complete')
28
+ return false;
29
+ if (!isTimestamp(value.createdAt) ||
30
+ !isTimestamp(value.completedAt) ||
31
+ !isSelection(value.selection))
32
+ return false;
33
+ if (!isObject(value.counts) ||
34
+ !isCount(value.counts.sessions) ||
35
+ !isCount(value.counts.entries) ||
36
+ !isCount(value.counts.records))
37
+ return false;
38
+ return (isObject(value.ordering) &&
39
+ value.ordering.sessions === 'createdAt ascending' &&
40
+ value.ordering.entries === 'createdAt ascending within session' &&
41
+ value.consistency === 'best-effort');
42
+ }
43
+ export function isSession(value) {
44
+ if (isObject(value) &&
45
+ typeof value.id === 'string' &&
46
+ value.id.length > 0 &&
47
+ (value.parentSessionId === null || typeof value.parentSessionId === 'string') &&
48
+ typeof value.agent === 'string' &&
49
+ typeof value.version === 'string' &&
50
+ isTimestamp(value.createdAt) &&
51
+ (value.lastEntryAt === null || isTimestamp(value.lastEntryAt)) &&
52
+ value.archivedAt === null &&
53
+ isObject(value.data)) {
54
+ try {
55
+ assertSessionId(value.id);
56
+ if (value.parentSessionId !== null)
57
+ assertSessionId(value.parentSessionId, 'parent session id');
58
+ return true;
59
+ }
60
+ catch {
61
+ return false;
62
+ }
63
+ }
64
+ return false;
65
+ }
@@ -0,0 +1,3 @@
1
+ import ts from '../contract-schema/typescript-api.mts';
2
+ import type { HeaderContract } from './header-contract-types.mts';
3
+ export declare function parseHeaderContract(sourceFile: ts.SourceFile, node: ts.ObjectLiteralExpression): HeaderContract;
@@ -0,0 +1,93 @@
1
+ import ts from '../contract-schema/typescript-api.mjs';
2
+ import { contractError } from './response-contract-registration.mjs';
3
+ export function parseHeaderContract(sourceFile, node) {
4
+ const request = propertyObject(sourceFile, node, 'request');
5
+ const responses = propertyObject(sourceFile, node, 'responses');
6
+ return {
7
+ requestHeaders: request ? parseHeaders(sourceFile, request) : {},
8
+ responseHeaders: responses ? parseResponses(sourceFile, responses) : {},
9
+ };
10
+ }
11
+ function parseResponses(sourceFile, node) {
12
+ const result = {};
13
+ for (const property of node.properties) {
14
+ if (!ts.isPropertyAssignment(property) ||
15
+ !ts.isNumericLiteral(property.name) ||
16
+ !ts.isObjectLiteralExpression(property.initializer))
17
+ throw contractError(sourceFile, property, 'apiHeaders response status must be a numeric object property');
18
+ const status = Number(property.name.text);
19
+ const errors = property.initializer.properties.find((item) => ts.isPropertyAssignment(item) && item.name.getText(sourceFile) === 'errors');
20
+ const headers = propertyObject(sourceFile, property.initializer, 'headers');
21
+ const description = stringProperty(property.initializer, 'description');
22
+ result[status] = {
23
+ ...(description ? { description } : {}),
24
+ ...(errors ? { errors: parseErrors(sourceFile, property.initializer) } : {}),
25
+ headers: headers ? parseHeaders(sourceFile, headers) : {},
26
+ };
27
+ }
28
+ return result;
29
+ }
30
+ function parseErrors(sourceFile, node) {
31
+ const property = findProperty(node, 'errors', sourceFile);
32
+ if (!property || !ts.isArrayLiteralExpression(property.initializer))
33
+ throw contractError(sourceFile, node, 'apiHeaders errors must be an array');
34
+ return property.initializer.elements.map((element) => {
35
+ if (!ts.isObjectLiteralExpression(element))
36
+ throw contractError(sourceFile, element, 'apiHeaders errors entries must be objects');
37
+ return {
38
+ code: requiredString(sourceFile, element, 'code'),
39
+ message: requiredString(sourceFile, element, 'message'),
40
+ };
41
+ });
42
+ }
43
+ function parseHeaders(sourceFile, node) {
44
+ const result = Object.create(null);
45
+ for (const property of node.properties) {
46
+ if (!ts.isPropertyAssignment(property) ||
47
+ !ts.isStringLiteral(property.name) ||
48
+ !ts.isObjectLiteralExpression(property.initializer))
49
+ throw contractError(sourceFile, property, 'apiHeaders header must be a string-keyed object property');
50
+ const description = stringProperty(property.initializer, 'description');
51
+ const format = stringProperty(property.initializer, 'format');
52
+ result[property.name.text] = {
53
+ type: requiredString(sourceFile, property.initializer, 'type'),
54
+ required: booleanProperty(property.initializer, 'required') ?? false,
55
+ ...(format ? { format: format } : {}),
56
+ ...(description ? { description } : {}),
57
+ };
58
+ }
59
+ return result;
60
+ }
61
+ function propertyObject(sourceFile, node, name) {
62
+ const property = findProperty(node, name, sourceFile);
63
+ if (!property)
64
+ return undefined;
65
+ if (!ts.isObjectLiteralExpression(property.initializer))
66
+ throw contractError(sourceFile, property, `apiHeaders ${name} must be an object`);
67
+ return property.initializer;
68
+ }
69
+ function stringProperty(node, name) {
70
+ const property = findProperty(node, name);
71
+ return property && ts.isStringLiteral(property.initializer)
72
+ ? property.initializer.text
73
+ : undefined;
74
+ }
75
+ function requiredString(sourceFile, node, name) {
76
+ const value = stringProperty(node, name);
77
+ if (!value)
78
+ throw contractError(sourceFile, node, `apiHeaders requires string ${name}`);
79
+ return value;
80
+ }
81
+ function booleanProperty(node, name) {
82
+ const property = findProperty(node, name);
83
+ if (!property)
84
+ return undefined;
85
+ if (property.initializer.kind === ts.SyntaxKind.TrueKeyword)
86
+ return true;
87
+ if (property.initializer.kind === ts.SyntaxKind.FalseKeyword)
88
+ return false;
89
+ return undefined;
90
+ }
91
+ function findProperty(node, name, sourceFile) {
92
+ return node.properties.find((item) => ts.isPropertyAssignment(item) && item.name.getText(sourceFile) === name);
93
+ }
@@ -0,0 +1,3 @@
1
+ import ts from '../contract-schema/typescript-api.mts';
2
+ import type { HeaderContractRegistry } from './header-contract-types.mts';
3
+ export declare function discoverApiHeaderContracts(program: ts.Program, sourceFiles: readonly ts.SourceFile[], knownResponseRoutes: ReadonlySet<string>): HeaderContractRegistry;
@@ -0,0 +1,38 @@
1
+ import ts from '../contract-schema/typescript-api.mjs';
2
+ import { contractError } from './response-contract-registration.mjs';
3
+ import { parseHeaderContract } from './header-contract-parser.mjs';
4
+ import { collectHandlerBindings, enclosingRouteBinding, visit, } from './response-contract-route-analysis.mjs';
5
+ export function discoverApiHeaderContracts(program, sourceFiles, knownResponseRoutes) {
6
+ const checker = program.getTypeChecker();
7
+ const bindings = collectHandlerBindings(sourceFiles, checker);
8
+ const contracts = new Map();
9
+ const knownOperations = new Set([...knownResponseRoutes].map((key) => key.split('#')[0]));
10
+ for (const sourceFile of sourceFiles) {
11
+ visit(sourceFile, (node) => {
12
+ if (!ts.isCallExpression(node) || !isMarker(node.expression))
13
+ return;
14
+ const [keyNode, contractNode] = node.arguments;
15
+ if (!keyNode ||
16
+ !ts.isStringLiteral(keyNode) ||
17
+ !contractNode ||
18
+ !ts.isObjectLiteralExpression(contractNode)) {
19
+ throw contractError(sourceFile, node, 'apiHeaders requires a literal operation key and object contract');
20
+ }
21
+ const binding = enclosingRouteBinding(node, checker, bindings);
22
+ if (!binding)
23
+ throw contractError(sourceFile, node, 'apiHeaders must be inside an app.route handler');
24
+ const key = `${binding.method}:${binding.routeTemplate}`;
25
+ if (keyNode.text !== key)
26
+ throw contractError(sourceFile, keyNode, `Header operation key "${keyNode.text}" does not match enclosing route ${key}`);
27
+ if (!knownOperations.has(key))
28
+ throw contractError(sourceFile, keyNode, `apiHeaders references unknown response route ${key}`);
29
+ if (contracts.has(key))
30
+ throw contractError(sourceFile, keyNode, `Duplicate apiHeaders marker for ${key}`);
31
+ contracts.set(key, parseHeaderContract(sourceFile, contractNode));
32
+ });
33
+ }
34
+ return Object.fromEntries([...contracts.entries()].toSorted(([a], [b]) => a.localeCompare(b)));
35
+ }
36
+ function isMarker(expression) {
37
+ return ts.isIdentifier(expression) && expression.text === 'apiHeaders';
38
+ }
@@ -0,0 +1,19 @@
1
+ export type HeaderContract = {
2
+ requestHeaders: Readonly<Record<string, HeaderDescriptor>>;
3
+ responseHeaders: Readonly<Record<number, ResponseHeaderDescriptor>>;
4
+ };
5
+ export type HeaderDescriptor = {
6
+ description?: string;
7
+ format?: 'uuid';
8
+ required: boolean;
9
+ type: 'integer' | 'string';
10
+ };
11
+ export type ResponseHeaderDescriptor = {
12
+ description?: string;
13
+ errors?: readonly {
14
+ code: string;
15
+ message: string;
16
+ }[];
17
+ headers: Readonly<Record<string, HeaderDescriptor>>;
18
+ };
19
+ export type HeaderContractRegistry = Readonly<Record<string, HeaderContract>>;
@@ -0,0 +1,31 @@
1
+ import type ts from '../contract-schema/typescript-api.mts';
2
+ import type { DiscoverApiResponseContractsOptions } from './response-contract-lenient.mts';
3
+ export declare const APP_ROUTE_CTX_ADAPTER_VERSION: 1;
4
+ export type AppRouteCtxDiscoveryInput = {
5
+ program: ts.Program;
6
+ sourceFiles: readonly ts.SourceFile[];
7
+ requestedKeys?: ReadonlySet<string>;
8
+ options?: DiscoverApiResponseContractsOptions;
9
+ };
10
+ /** Discovers the version-one `app.route(...).method(handler)` and `ctx` convention. */
11
+ export declare function discoverAppRouteCtxContractsV1(input: AppRouteCtxDiscoveryInput): {
12
+ adapterVersion: 1;
13
+ routes: import("./registered-route-catalog.mts").RegisteredRoute[];
14
+ responses: Record<string, import("./response-contract-types.mts").BackendResponseContract>;
15
+ requests: Record<string, import("./request-contract-types.mts").BackendRequestContract>;
16
+ queries: Readonly<Record<string, import("./query-contract-types.mts").BackendQueryContract>>;
17
+ headers: Readonly<Record<string, import("./header-contract-types.mts").HeaderContract>>;
18
+ };
19
+ export { discoverApiResponseContracts } from './response-contract-registry.mts';
20
+ export { discoverApiRequestContracts } from './request-contract-registry.mts';
21
+ export { discoverApiQueryContracts } from './query-contract-registry.mts';
22
+ export { discoverApiHeaderContracts } from './header-contract-registry.mts';
23
+ export { discoverRegisteredRoutes, routeShape } from './registered-route-catalog.mts';
24
+ export { responseStatusCodesForContract } from './response-contract-status.mts';
25
+ export type { BackendResponseContract } from './response-contract-types.mts';
26
+ export type { BackendRequestContract } from './request-contract-types.mts';
27
+ export type { BackendQueryContract, BackendQueryContractRegistry } from './query-contract-types.mts';
28
+ export type { HeaderContract, HeaderContractRegistry } from './header-contract-types.mts';
29
+ export type { RegisteredRoute } from './registered-route-catalog.mts';
30
+ export type { RouteDiscoveryError, DiscoverApiResponseContractsOptions, } from './response-contract-lenient.mts';
31
+ export type { DiscoverApiRequestContractsOptions } from './request-contract-lenient.mts';
@@ -0,0 +1,26 @@
1
+ import { discoverApiHeaderContracts } from './header-contract-registry.mjs';
2
+ import { discoverApiQueryContracts } from './query-contract-registry.mjs';
3
+ import { discoverRegisteredRoutes } from './registered-route-catalog.mjs';
4
+ import { discoverApiRequestContracts } from './request-contract-registry.mjs';
5
+ import { discoverApiResponseContracts } from './response-contract-registry.mjs';
6
+ export const APP_ROUTE_CTX_ADAPTER_VERSION = 1;
7
+ /** Discovers the version-one `app.route(...).method(handler)` and `ctx` convention. */
8
+ export function discoverAppRouteCtxContractsV1(input) {
9
+ const { program, sourceFiles, requestedKeys, options } = input;
10
+ const responses = discoverApiResponseContracts(program, sourceFiles, requestedKeys, options);
11
+ const knownResponseRoutes = new Set(Object.keys(responses));
12
+ return {
13
+ adapterVersion: APP_ROUTE_CTX_ADAPTER_VERSION,
14
+ routes: discoverRegisteredRoutes(program, sourceFiles),
15
+ responses,
16
+ requests: discoverApiRequestContracts(program, sourceFiles, requestedKeys, options),
17
+ queries: discoverApiQueryContracts(program, sourceFiles, knownResponseRoutes),
18
+ headers: discoverApiHeaderContracts(program, sourceFiles, knownResponseRoutes),
19
+ };
20
+ }
21
+ export { discoverApiResponseContracts } from './response-contract-registry.mjs';
22
+ export { discoverApiRequestContracts } from './request-contract-registry.mjs';
23
+ export { discoverApiQueryContracts } from './query-contract-registry.mjs';
24
+ export { discoverApiHeaderContracts } from './header-contract-registry.mjs';
25
+ export { discoverRegisteredRoutes, routeShape } from './registered-route-catalog.mjs';
26
+ export { responseStatusCodesForContract } from './response-contract-status.mjs';
@@ -0,0 +1,3 @@
1
+ import type { QueryParameterContract } from './query-contract-types.mts';
2
+ import ts from '../contract-schema/typescript-api.mts';
3
+ export declare function extractQueryParameterDescriptor(type: ts.Type, checker: ts.TypeChecker, sourceFile: ts.SourceFile, node: ts.Node, parameterName: string): QueryParameterContract;
@@ -0,0 +1,65 @@
1
+ import ts from '../contract-schema/typescript-api.mjs';
2
+ import { contractError } from './response-contract-registration.mjs';
3
+ import { optionalNumberLiteral, optionalStringLiteral, requiredBooleanLiteral, requiredNumberLiteral, requiredPropertyType, requiredStringLiteral, stringTuple, } from './query-contract-literals.mjs';
4
+ export function extractQueryParameterDescriptor(type, checker, sourceFile, node, parameterName) {
5
+ const fail = (detail) => {
6
+ throw contractError(sourceFile, node, `Malformed query parameter "${parameterName}": ${detail}`);
7
+ };
8
+ const kind = requiredStringLiteral(type, 'kind', checker, fail);
9
+ const description = optionalStringLiteral(type, 'description', checker, node, fail);
10
+ const options = description === undefined ? {} : { description };
11
+ if (kind === 'string') {
12
+ const format = optionalStringLiteral(type, 'format', checker, node, fail);
13
+ if (format === 'uuid')
14
+ return { kind, format, ...options };
15
+ if (format === 'uri')
16
+ return { kind, format, ...options };
17
+ if (format !== undefined)
18
+ fail('unsupported format');
19
+ return { kind, ...options };
20
+ }
21
+ if (kind === 'uuid-or-uri' ||
22
+ kind === 'boolean' ||
23
+ kind === 'nullable-boolean' ||
24
+ kind === 'number') {
25
+ return { kind, ...options };
26
+ }
27
+ if (kind === 'integer') {
28
+ const minimum = requiredNumberLiteral(type, 'minimum', checker, fail);
29
+ const maximum = requiredNumberLiteral(type, 'maximum', checker, fail);
30
+ const defaultValue = optionalNumberLiteral(type, 'default', checker, node, fail);
31
+ return {
32
+ kind,
33
+ minimum,
34
+ maximum,
35
+ ...(defaultValue === undefined ? {} : { default: defaultValue }),
36
+ ...options,
37
+ };
38
+ }
39
+ if (kind === 'enum') {
40
+ const values = stringTuple(type, 'values', checker, fail);
41
+ const defaultValue = optionalStringLiteral(type, 'default', checker, node, fail);
42
+ if (defaultValue !== undefined && !values.includes(defaultValue)) {
43
+ fail('default must be one of values');
44
+ }
45
+ return {
46
+ kind,
47
+ values,
48
+ ...(defaultValue === undefined ? {} : { default: defaultValue }),
49
+ ...options,
50
+ };
51
+ }
52
+ if (kind === 'csv-array') {
53
+ if (requiredStringLiteral(type, 'style', checker, fail) !== 'form')
54
+ fail('style must be form');
55
+ if (requiredBooleanLiteral(type, 'explode', checker, fail) !== false)
56
+ fail('explode must be false');
57
+ const itemType = requiredPropertyType(type, 'items', checker, fail);
58
+ const items = extractQueryParameterDescriptor(itemType, checker, sourceFile, node, `${parameterName}[]`);
59
+ if (items.kind === 'string' || items.kind === 'enum') {
60
+ return { kind, items, style: 'form', explode: false, ...options };
61
+ }
62
+ return fail('array items must be string or enum');
63
+ }
64
+ return fail(`unsupported kind "${kind}"`);
65
+ }
@@ -0,0 +1,8 @@
1
+ import ts from '../contract-schema/typescript-api.mts';
2
+ export declare function requiredPropertyType(type: ts.Type, name: string, checker: ts.TypeChecker, fail: (detail: string) => never): ts.Type;
3
+ export declare function requiredStringLiteral(type: ts.Type, name: string, checker: ts.TypeChecker, fail: (detail: string) => never): string;
4
+ export declare function requiredNumberLiteral(type: ts.Type, name: string, checker: ts.TypeChecker, fail: (detail: string) => never): number;
5
+ export declare function requiredBooleanLiteral(type: ts.Type, name: string, checker: ts.TypeChecker, fail: (detail: string) => never): boolean;
6
+ export declare function optionalStringLiteral(type: ts.Type, name: string, checker: ts.TypeChecker, node: ts.Node, fail: (detail: string) => never): string | undefined;
7
+ export declare function optionalNumberLiteral(type: ts.Type, name: string, checker: ts.TypeChecker, node: ts.Node, fail: (detail: string) => never): number | undefined;
8
+ export declare function stringTuple(type: ts.Type, name: string, checker: ts.TypeChecker, fail: (detail: string) => never): readonly string[];
@@ -0,0 +1,54 @@
1
+ import ts from '../contract-schema/typescript-api.mjs';
2
+ export function requiredPropertyType(type, name, checker, fail) {
3
+ const property = type.getProperty(name);
4
+ if (!property || property.flags & ts.SymbolFlags.Optional)
5
+ return fail(`requires literal ${name}`);
6
+ const location = property.valueDeclaration ?? property.declarations?.[0];
7
+ if (!location)
8
+ return fail(`requires declared ${name}`);
9
+ return checker.getTypeOfSymbolAtLocation(property, location);
10
+ }
11
+ export function requiredStringLiteral(type, name, checker, fail) {
12
+ const value = requiredPropertyType(type, name, checker, fail);
13
+ return value.isStringLiteral() ? value.value : fail(`requires literal ${name}`);
14
+ }
15
+ export function requiredNumberLiteral(type, name, checker, fail) {
16
+ const value = requiredPropertyType(type, name, checker, fail);
17
+ return value.isNumberLiteral() ? value.value : fail(`requires literal ${name}`);
18
+ }
19
+ export function requiredBooleanLiteral(type, name, checker, fail) {
20
+ const value = requiredPropertyType(type, name, checker, fail);
21
+ if (value.flags & ts.TypeFlags.BooleanLiteral)
22
+ return checker.typeToString(value) === 'true';
23
+ return fail(`requires literal ${name}`);
24
+ }
25
+ export function optionalStringLiteral(type, name, checker, node, fail) {
26
+ const property = type.getProperty(name);
27
+ if (!property)
28
+ return undefined;
29
+ if (property.flags & ts.SymbolFlags.Optional)
30
+ return fail(`${name} must be literal when present`);
31
+ const location = property.valueDeclaration ?? property.declarations?.[0] ?? node;
32
+ const value = checker.getTypeOfSymbolAtLocation(property, location);
33
+ return value.isStringLiteral() ? value.value : fail(`requires literal ${name}`);
34
+ }
35
+ export function optionalNumberLiteral(type, name, checker, node, fail) {
36
+ const property = type.getProperty(name);
37
+ if (!property)
38
+ return undefined;
39
+ if (property.flags & ts.SymbolFlags.Optional)
40
+ return fail(`${name} must be literal when present`);
41
+ const location = property.valueDeclaration ?? property.declarations?.[0] ?? node;
42
+ const value = checker.getTypeOfSymbolAtLocation(property, location);
43
+ return value.isNumberLiteral() ? value.value : fail(`requires literal ${name}`);
44
+ }
45
+ export function stringTuple(type, name, checker, fail) {
46
+ const value = requiredPropertyType(type, name, checker, fail);
47
+ if (!checker.isTupleType(value))
48
+ return fail(`${name} must be a literal tuple`);
49
+ const values = checker.getTypeArguments(value);
50
+ if (values.length === 0 || values.some((item) => !item.isStringLiteral())) {
51
+ return fail(`${name} must contain string literals`);
52
+ }
53
+ return values.map((item) => item.value);
54
+ }
@@ -0,0 +1,3 @@
1
+ import ts from '../contract-schema/typescript-api.mts';
2
+ import type { BackendQueryContractRegistry } from './query-contract-types.mts';
3
+ export declare function discoverApiQueryContracts(program: ts.Program, sourceFiles: readonly ts.SourceFile[], knownResponseRoutes: ReadonlySet<string>): BackendQueryContractRegistry;