vouchington-tooling 0.20.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 (257) hide show
  1. package/README.md +73 -10
  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/dependency-license-policy/audit-directory.d.mts +28 -0
  115. package/dist/dependency-license-policy/audit-directory.mjs +115 -0
  116. package/dist/dependency-license-policy/audit-signals.d.mts +12 -0
  117. package/dist/dependency-license-policy/audit-signals.mjs +44 -0
  118. package/dist/dependency-license-policy/audit-store.d.mts +12 -0
  119. package/dist/dependency-license-policy/audit-store.mjs +32 -0
  120. package/dist/dependency-license-policy/collect.d.mts +3 -1
  121. package/dist/dependency-license-policy/collect.mjs +16 -21
  122. package/dist/dependency-license-policy/execute-pnpm.d.mts +10 -0
  123. package/dist/dependency-license-policy/execute-pnpm.mjs +83 -0
  124. package/dist/dependency-license-policy/types.d.mts +11 -9
  125. package/dist/dependency-license-policy/workspace.d.mts +3 -1
  126. package/dist/dependency-license-policy/workspace.mjs +12 -5
  127. package/dist/gh-api-shell-quoting/argument-scan.d.mts +7 -0
  128. package/dist/gh-api-shell-quoting/argument-scan.mjs +35 -0
  129. package/dist/gh-api-shell-quoting/logical-line.d.mts +3 -0
  130. package/dist/gh-api-shell-quoting/logical-line.mjs +90 -0
  131. package/dist/gh-api-shell-quoting/scan.d.mts +2 -4
  132. package/dist/gh-api-shell-quoting/scan.mjs +1 -119
  133. package/dist/gh-cli/diff.d.mts +24 -4
  134. package/dist/gh-cli/diff.mjs +111 -4
  135. package/dist/gh-cli/index.d.mts +2 -1
  136. package/dist/gh-cli/index.mjs +1 -1
  137. package/dist/gha-workspace-policy/docker-options.d.mts +4 -0
  138. package/dist/gha-workspace-policy/docker-options.mjs +93 -0
  139. package/dist/gha-workspace-policy/docker-workspace-user.mjs +1 -168
  140. package/dist/gha-workspace-policy/shell-token-modes.d.mts +10 -0
  141. package/dist/gha-workspace-policy/shell-token-modes.mjs +31 -0
  142. package/dist/gha-workspace-policy/shell-tokens.d.mts +1 -0
  143. package/dist/gha-workspace-policy/shell-tokens.mjs +65 -0
  144. package/dist/index.d.mts +2 -1
  145. package/dist/index.mjs +1 -1
  146. package/dist/openapi-document/contract-schema-types.d.mts +1 -1
  147. package/dist/openapi-document/openapi-types.d.mts +1 -1
  148. package/dist/pnpm-install/native-family.d.mts +3 -0
  149. package/dist/pnpm-install/native-family.mjs +31 -0
  150. package/dist/pnpm-install/native-health.d.mts +1 -4
  151. package/dist/pnpm-install/native-health.mjs +2 -31
  152. package/dist/retrospective-facts/local-merge.d.mts +2 -0
  153. package/dist/retrospective-facts/local-merge.mjs +21 -0
  154. package/dist/retrospective-facts/local.mjs +1 -21
  155. package/dist/retrospective-transcript/claude-blocks.d.mts +3 -0
  156. package/dist/retrospective-transcript/claude-blocks.mjs +38 -0
  157. package/dist/retrospective-transcript/claude.mjs +4 -34
  158. package/dist/retrospective-transcript/codex-payload.d.mts +2 -0
  159. package/dist/retrospective-transcript/codex-payload.mjs +51 -0
  160. package/dist/retrospective-transcript/codex-records.d.mts +2 -0
  161. package/dist/retrospective-transcript/codex-records.mjs +95 -0
  162. package/dist/retrospective-transcript/codex.mjs +2 -144
  163. package/dist/retrospective-transcript/index.d.mts +3 -16
  164. package/dist/retrospective-transcript/index.mjs +4 -48
  165. package/dist/retrospective-transcript/resolve-transcript-file.d.mts +19 -0
  166. package/dist/retrospective-transcript/resolve-transcript-file.mjs +50 -0
  167. package/dist/session-friction/command-segments.d.mts +3 -0
  168. package/dist/session-friction/command-segments.mjs +94 -0
  169. package/dist/session-friction/log-file.d.mts +7 -0
  170. package/dist/session-friction/log-file.mjs +83 -0
  171. package/dist/session-friction/log.mjs +3 -83
  172. package/dist/session-friction/normalize.mjs +1 -94
  173. package/dist/sql-scanner/comments.d.mts +1 -0
  174. package/dist/sql-scanner/comments.mjs +101 -0
  175. package/dist/sql-scanner/index.d.mts +1 -1
  176. package/dist/sql-scanner/index.mjs +1 -100
  177. package/dist/sql-scanner/literals.mjs +1 -35
  178. package/dist/sql-scanner/quote-decode.d.mts +2 -0
  179. package/dist/sql-scanner/quote-decode.mjs +35 -0
  180. package/dist/vitest-diagnostics/index.d.mts +7 -0
  181. package/dist/vitest-diagnostics/index.mjs +4 -0
  182. package/dist/vitest-diagnostics/vitest-fork-exit-error-detail.d.mts +7 -0
  183. package/dist/vitest-diagnostics/vitest-fork-exit-error-detail.mjs +26 -0
  184. package/dist/vitest-diagnostics/vitest-fork-exit-records.d.mts +26 -0
  185. package/dist/vitest-diagnostics/vitest-fork-exit-records.mjs +161 -0
  186. package/dist/vitest-diagnostics/vitest-fork-exit-sentinel.d.mts +14 -0
  187. package/dist/vitest-diagnostics/vitest-fork-exit-sentinel.mjs +131 -0
  188. package/dist/vitest-diagnostics/vitest-fork-leak-detection.d.mts +26 -0
  189. package/dist/vitest-diagnostics/vitest-fork-leak-detection.mjs +96 -0
  190. package/dist/vitest-diagnostics/vitest-fork-leak-diagnostics.d.mts +3 -0
  191. package/dist/vitest-diagnostics/vitest-fork-leak-diagnostics.mjs +14 -0
  192. package/dist/vitest-diagnostics/vitest-fork-leak-episode.d.mts +37 -0
  193. package/dist/vitest-diagnostics/vitest-fork-leak-episode.mjs +118 -0
  194. package/dist/vitest-diagnostics/vitest-worker-exit-diagnostics-errors.d.mts +2 -0
  195. package/dist/vitest-diagnostics/vitest-worker-exit-diagnostics-errors.mjs +42 -0
  196. package/dist/vitest-diagnostics/vitest-worker-exit-diagnostics-reporter.d.mts +12 -0
  197. package/dist/vitest-diagnostics/vitest-worker-exit-diagnostics-reporter.mjs +142 -0
  198. package/dist/workflow-policy/concurrency-scope.d.mts +4 -0
  199. package/dist/workflow-policy/concurrency-scope.mjs +66 -0
  200. package/dist/workflow-policy/concurrency.d.mts +10 -0
  201. package/dist/workflow-policy/concurrency.mjs +77 -0
  202. package/dist/workflow-policy/condition-entailment.d.mts +20 -0
  203. package/dist/workflow-policy/condition-entailment.mjs +132 -0
  204. package/dist/workflow-policy/condition-split.d.mts +15 -0
  205. package/dist/workflow-policy/condition-split.mjs +71 -0
  206. package/dist/workflow-policy/graph.d.mts +2 -0
  207. package/dist/workflow-policy/graph.mjs +138 -0
  208. package/dist/workflow-policy/index.d.mts +7 -0
  209. package/dist/workflow-policy/index.mjs +6 -0
  210. package/dist/workflow-policy/permission-mismatches.d.mts +3 -0
  211. package/dist/workflow-policy/permission-mismatches.mjs +52 -0
  212. package/dist/workflow-policy/permissions.d.mts +16 -0
  213. package/dist/workflow-policy/permissions.mjs +83 -0
  214. package/dist/workflow-policy/secret-readiness.d.mts +18 -0
  215. package/dist/workflow-policy/secret-readiness.mjs +163 -0
  216. package/dist/workflow-policy/shell-comments.d.mts +7 -0
  217. package/dist/workflow-policy/shell-comments.mjs +28 -0
  218. package/dist/workflow-policy/types.d.mts +90 -0
  219. package/dist/workflow-policy/types.mjs +1 -0
  220. package/dist/workspace-gates/manifest-version-parser.mjs +1 -52
  221. package/dist/workspace-gates/manifest-version-skips.d.mts +1 -0
  222. package/dist/workspace-gates/manifest-version-skips.mjs +52 -0
  223. package/docs/workflow-and-compiler.md +74 -0
  224. package/package.json +26 -2
  225. package/skills/agent-workflow/SKILL.md +4 -4
  226. package/skills/agent-workflow/references/review-response.md +47 -44
  227. package/skills/backend-vitest-test-authoring/SKILL.md +9 -6
  228. package/skills/blackboard/SKILL.md +8 -8
  229. package/skills/dependabot/SKILL.md +68 -67
  230. package/skills/dotnet-test-authoring/SKILL.md +11 -12
  231. package/skills/git-commit-checklist/SKILL.md +3 -3
  232. package/skills/github-actions-authoring/SKILL.md +40 -38
  233. package/skills/github-actions-checklist/SKILL.md +93 -85
  234. package/skills/github-issue/SKILL.md +82 -78
  235. package/skills/nextjs-vitest-test-authoring/SKILL.md +8 -6
  236. package/skills/npm-publishing/SKILL.md +42 -38
  237. package/skills/organize-github-issues/SKILL.md +18 -22
  238. package/skills/package-json-checklist/SKILL.md +5 -5
  239. package/skills/planning/SKILL.md +3 -3
  240. package/skills/playwright-authoring/SKILL.md +10 -9
  241. package/skills/postgres-node-performance-tuning/SKILL.md +10 -6
  242. package/skills/postgres-partitioning-uuid-v7/SKILL.md +10 -7
  243. package/skills/pr-description/SKILL.md +4 -3
  244. package/skills/retrospective/SKILL.md +14 -15
  245. package/skills/retrospective-distill/SKILL.md +14 -14
  246. package/skills/review-ci-logs/SKILL.md +18 -19
  247. package/skills/review-github-issue-taxonomy/SKILL.md +21 -18
  248. package/skills/revisit-followups/SKILL.md +3 -3
  249. package/skills/stacked-prs/SKILL.md +30 -61
  250. package/skills/static-analysis-checklist/SKILL.md +6 -4
  251. package/skills/storybook-authoring/SKILL.md +9 -9
  252. package/skills/swift-test-authoring/SKILL.md +10 -9
  253. package/skills/test-authoring/SKILL.md +14 -14
  254. package/skills/test-authoring/references/core-practice.md +12 -11
  255. package/skills/test-authoring/references/dependency-boundaries.md +36 -35
  256. package/skills/test-authoring/references/tautological-tests.md +45 -44
  257. 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
@@ -326,19 +355,24 @@ policy out of this package.
326
355
  dependency declared by a non-fixture package manifest. Assert dependency membership or placement,
327
356
  or derive a configuration or documentation package spec from that manifest instead.
328
357
 
329
- `dependency-license-policy` keeps legal policy in the consumer. `collectPnpmLicenseReport` creates
330
- an isolated, script-free temporary workspace and store, expands pnpm's supported architectures to
331
- every `os`, `cpu`, and `libc` selector represented in the lockfile, drops every `engines` constraint
332
- from the audit copy of the lockfile, and validates the JSON report. Dropping `engines` keeps
333
- `pnpm fetch` from skipping optional packages that exclude the running Node.js; pnpm 12's `fetch`
334
- ignores `force`, so their licenses would otherwise report as Unknown.
358
+ `dependency-license-policy` keeps legal policy in the consumer. `collectPnpmLicenseReport` returns
359
+ a promise. It creates an isolated, script-free temporary workspace, expands pnpm's supported
360
+ architectures to every `os`, `cpu`, and `libc` selector represented in the lockfile, drops every
361
+ `engines` constraint from the audit copy of the lockfile, and validates the JSON report. Dropping
362
+ `engines` keeps `pnpm fetch` from skipping optional packages that exclude the running Node.js;
363
+ pnpm 12's `fetch` ignores `force`, so their licenses would otherwise report as Unknown.
364
+ Packages are fetched into a dedicated owner-only store under the pnpm cache
365
+ (`dependency-license-audit-store`) so a later audit reuses content-addressed packages instead of
366
+ downloading every platform again, without writing those packages into the developer store.
335
367
  Pass explicit denied SPDX IDs and prefixes, exact aliases, and justified allowlist scopes to
336
368
  `evaluatePnpmLicenseReport`. Unknown, malformed, and custom SPDX references fail closed. Allowlist
337
369
  scopes are either intentionally global or an exact package-name set; the library returns structured
338
370
  violations and does not format CI-provider diagnostics.
339
- When present, the repository `.npmrc` is copied into the owner-private temporary directory so pnpm
340
- can authenticate to the same registries; normal cleanup removes the copy, and the caller remains
341
- responsible for terminating the process normally rather than abandoning temporary audit state.
371
+ When present, the repository `.npmrc` is copied into the owner-private temporary workspace so pnpm
372
+ can authenticate to the same registries. The workspace is removed when the audit finishes, when the
373
+ process receives SIGINT, SIGTERM, or SIGHUP, and on the next audit if the previous process died
374
+ first, including SIGKILL. Each workspace records its owner's PID so a later audit can delete
375
+ directories whose owner is gone.
342
376
 
343
377
  `session-friction` is an opt-in capture and reporting library. Callers supply the session id,
344
378
  absolute log directory, host-independent observation, and journal loader; it does not inspect host
@@ -378,6 +412,35 @@ token-source router remains deprecated for one release line.
378
412
  filenames, tolerates partial files, returns only a bounded field allowlist, and never emits raw
379
413
  native frame symbols. Both structured reads and text rendering have hard report-count limits.
380
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
+
381
444
  `browser-session-runner` supervises caller-created browser-test processes. Callers supply command
382
445
  construction, line classification, retry/outcome policy, and budgets; the library owns process-group
383
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[];