carrick 0.3.53

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 (114) hide show
  1. package/LICENSE.md +99 -0
  2. package/README.md +79 -0
  3. package/bin/carrick.mjs +168 -0
  4. package/dist/channel.d.ts +16 -0
  5. package/dist/channel.js +37 -0
  6. package/dist/channel.js.map +1 -0
  7. package/dist/cli.d.ts +29 -0
  8. package/dist/cli.js +60 -0
  9. package/dist/cli.js.map +1 -0
  10. package/dist/contract.d.ts +154 -0
  11. package/dist/contract.js +141 -0
  12. package/dist/contract.js.map +1 -0
  13. package/dist/diagnostics.d.ts +54 -0
  14. package/dist/diagnostics.js +199 -0
  15. package/dist/diagnostics.js.map +1 -0
  16. package/dist/hook/post-edit.d.ts +13 -0
  17. package/dist/hook/post-edit.js +76 -0
  18. package/dist/hook/post-edit.js.map +1 -0
  19. package/dist/hook/session-start.d.ts +2 -0
  20. package/dist/hook/session-start.js +44 -0
  21. package/dist/hook/session-start.js.map +1 -0
  22. package/dist/init/identity.d.ts +20 -0
  23. package/dist/init/identity.js +60 -0
  24. package/dist/init/identity.js.map +1 -0
  25. package/dist/init/repos.d.ts +19 -0
  26. package/dist/init/repos.js +46 -0
  27. package/dist/init/repos.js.map +1 -0
  28. package/dist/init/run.d.ts +11 -0
  29. package/dist/init/run.js +228 -0
  30. package/dist/init/run.js.map +1 -0
  31. package/dist/init/settings.d.ts +50 -0
  32. package/dist/init/settings.js +138 -0
  33. package/dist/init/settings.js.map +1 -0
  34. package/dist/log.d.ts +2 -0
  35. package/dist/log.js +35 -0
  36. package/dist/log.js.map +1 -0
  37. package/dist/native.d.ts +48 -0
  38. package/dist/native.js +127 -0
  39. package/dist/native.js.map +1 -0
  40. package/dist/render.d.ts +65 -0
  41. package/dist/render.js +334 -0
  42. package/dist/render.js.map +1 -0
  43. package/dist/root.d.ts +30 -0
  44. package/dist/root.js +82 -0
  45. package/dist/root.js.map +1 -0
  46. package/dist/server.d.ts +2 -0
  47. package/dist/server.js +255 -0
  48. package/dist/server.js.map +1 -0
  49. package/dist/templates.d.ts +18 -0
  50. package/dist/templates.js +66 -0
  51. package/dist/templates.js.map +1 -0
  52. package/package.json +72 -0
  53. package/plugin/.claude-plugin/plugin.json +6 -0
  54. package/plugin/.lsp.json +14 -0
  55. package/plugin/hooks/hooks.json +27 -0
  56. package/sidecar/dist/src/bundler.d.ts +141 -0
  57. package/sidecar/dist/src/bundler.js +680 -0
  58. package/sidecar/dist/src/capture/anchors.d.ts +61 -0
  59. package/sidecar/dist/src/capture/anchors.js +1132 -0
  60. package/sidecar/dist/src/capture/api.d.ts +378 -0
  61. package/sidecar/dist/src/capture/api.js +10 -0
  62. package/sidecar/dist/src/capture/augmentations.d.ts +20 -0
  63. package/sidecar/dist/src/capture/augmentations.js +60 -0
  64. package/sidecar/dist/src/capture/check-classify.d.ts +58 -0
  65. package/sidecar/dist/src/capture/check-classify.js +189 -0
  66. package/sidecar/dist/src/capture/check-deep.d.ts +32 -0
  67. package/sidecar/dist/src/capture/check-deep.js +91 -0
  68. package/sidecar/dist/src/capture/check-poison.d.ts +40 -0
  69. package/sidecar/dist/src/capture/check-poison.js +155 -0
  70. package/sidecar/dist/src/capture/check-probe.d.ts +70 -0
  71. package/sidecar/dist/src/capture/check-probe.js +147 -0
  72. package/sidecar/dist/src/capture/check-scrub.d.ts +31 -0
  73. package/sidecar/dist/src/capture/check-scrub.js +71 -0
  74. package/sidecar/dist/src/capture/check-workspace.d.ts +52 -0
  75. package/sidecar/dist/src/capture/check-workspace.js +218 -0
  76. package/sidecar/dist/src/capture/check.d.ts +39 -0
  77. package/sidecar/dist/src/capture/check.js +450 -0
  78. package/sidecar/dist/src/capture/deep-walk.d.ts +62 -0
  79. package/sidecar/dist/src/capture/deep-walk.js +243 -0
  80. package/sidecar/dist/src/capture/index.d.ts +36 -0
  81. package/sidecar/dist/src/capture/index.js +477 -0
  82. package/sidecar/dist/src/capture/lockfile.d.ts +48 -0
  83. package/sidecar/dist/src/capture/lockfile.js +490 -0
  84. package/sidecar/dist/src/capture/machinery.d.ts +59 -0
  85. package/sidecar/dist/src/capture/machinery.js +160 -0
  86. package/sidecar/dist/src/capture/node-builder.d.ts +37 -0
  87. package/sidecar/dist/src/capture/node-builder.js +123 -0
  88. package/sidecar/dist/src/capture/paths-rewrite.d.ts +34 -0
  89. package/sidecar/dist/src/capture/paths-rewrite.js +100 -0
  90. package/sidecar/dist/src/capture/self-check.d.ts +38 -0
  91. package/sidecar/dist/src/capture/self-check.js +317 -0
  92. package/sidecar/dist/src/capture/specifiers.d.ts +39 -0
  93. package/sidecar/dist/src/capture/specifiers.js +56 -0
  94. package/sidecar/dist/src/definition-resolver.d.ts +57 -0
  95. package/sidecar/dist/src/definition-resolver.js +153 -0
  96. package/sidecar/dist/src/index.d.ts +14 -0
  97. package/sidecar/dist/src/index.js +564 -0
  98. package/sidecar/dist/src/monorepo-builder.d.ts +129 -0
  99. package/sidecar/dist/src/monorepo-builder.js +584 -0
  100. package/sidecar/dist/src/project-loader.d.ts +130 -0
  101. package/sidecar/dist/src/project-loader.js +399 -0
  102. package/sidecar/dist/src/type-inferrer.d.ts +940 -0
  103. package/sidecar/dist/src/type-inferrer.js +3540 -0
  104. package/sidecar/dist/src/type-structural-expander.d.ts +61 -0
  105. package/sidecar/dist/src/type-structural-expander.js +283 -0
  106. package/sidecar/dist/src/type-text-canonicalizer.d.ts +40 -0
  107. package/sidecar/dist/src/type-text-canonicalizer.js +297 -0
  108. package/sidecar/dist/src/types.d.ts +669 -0
  109. package/sidecar/dist/src/types.js +5 -0
  110. package/sidecar/dist/src/validators.d.ts +2214 -0
  111. package/sidecar/dist/src/validators.js +336 -0
  112. package/sidecar/package.json +6 -0
  113. package/templates/carrick.json +7 -0
  114. package/templates/carrick.yml +45 -0
@@ -0,0 +1,378 @@
1
+ /**
2
+ * Wire contract for the v2 capture bundle ("tsc as the serializer").
3
+ *
4
+ * This file IS the seam (design doc: "seam, not split"): everything outside
5
+ * src/sidecar/src/capture/ may import types from this file and the
6
+ * `captureStub` entry point from ./index.js, and nothing else. Modules inside
7
+ * capture/ import only node builtins, `typescript`, and each other. The
8
+ * stdio `capture_v2` action is the only surface the Rust client sees.
9
+ */
10
+ /**
11
+ * How the anchor was produced upstream (design-doc amendment 2). Recorded so
12
+ * the fidelity metric separates anchor-recall loss from serialization loss.
13
+ * (Named anchor_origin because `provenance` is taken by the op-level
14
+ * producer-provenance fields in src/eval_output.rs.)
15
+ */
16
+ export type AnchorOrigin = 'llm-symbol' | 'deterministic-infer' | 'anchor-backfill';
17
+ /**
18
+ * Serialization tier of a captured alias (design doc, Capture step 5):
19
+ * - emitted: compiler declaration emit of an addressable symbol (best)
20
+ * - node_builder: SymbolTracker-verified node-builder print of an anonymous
21
+ * inferred type
22
+ * - structural_fallback: the legacy hand-text tier. Two shapes share it:
23
+ * (a) literal anchors — the WP3 wiring of v1 inference/inline text into
24
+ * the surface (self-checked like any other alias, so decay is still
25
+ * caught), and (b) demotions from the capture-native paths (guard
26
+ * failure, locator failure, inaccessible symbols), which emit `unknown`
27
+ * with a recorded `capture_failure_reason`. Keeping both at one tier is
28
+ * what makes the remaining legacy-text dependence measurable and
29
+ * ratchetable.
30
+ */
31
+ export type SerializationTier = 'emitted' | 'node_builder' | 'structural_fallback';
32
+ /** Explicit exported symbol: `export type A = import('./m').Sym;` */
33
+ export interface SymbolAnchorRequest {
34
+ kind: 'symbol';
35
+ /** Manifest alias, e.g. Endpoint_abc123_Response */
36
+ alias: string;
37
+ /** Exported symbol name in the producer repo */
38
+ symbol_name: string;
39
+ /** Declaring module, repo-root-relative, e.g. src/types/stock.ts */
40
+ source_file: string;
41
+ anchor_origin: AnchorOrigin;
42
+ /**
43
+ * Wrap the captured symbol in this many TS array levels (#248/#306): an
44
+ * anchor is the ELEMENT symbol by contract (`User[]` -> `User`), so the
45
+ * use-site's array-ness rides here and the surface alias becomes
46
+ * `import('./m').Sym[]`. Omitted/0 captures the symbol as-is.
47
+ */
48
+ array_depth?: number;
49
+ }
50
+ /**
51
+ * Inline literal type text with no addressable symbol (the v1 inline-alias
52
+ * path): the surface entry gets `export type A = <type_text>;`. A bare
53
+ * identifier that names a sibling symbol anchor's symbol resolves through
54
+ * that anchor's module specifier so it does not dangle in the entry file;
55
+ * any other text is emitted verbatim and the self-check owns the verdict.
56
+ */
57
+ export interface LiteralAnchorRequest {
58
+ kind: 'literal';
59
+ alias: string;
60
+ /** Verbatim TS type text (a bare symbol name or an inline object type). */
61
+ type_text: string;
62
+ anchor_origin: AnchorOrigin;
63
+ }
64
+ /**
65
+ * Addressable handler: `export type A = Awaited<ReturnType<typeof
66
+ * import('./m').fn>>;` -- guarded (design doc, Capture step 1): the symbol
67
+ * must be exported, must not be an overload set (ReturnType silently resolves
68
+ * the last overload), and must not be generic (type params erase). Guard
69
+ * failures demote to structural_fallback with the reason recorded.
70
+ */
71
+ export interface HandlerReturnAnchorRequest {
72
+ kind: 'handler_return';
73
+ alias: string;
74
+ symbol_name: string;
75
+ source_file: string;
76
+ anchor_origin: AnchorOrigin;
77
+ }
78
+ /**
79
+ * Anonymous inferred type at a source location (no addressable symbol). The
80
+ * node is located by byte span when given, else by expression text on/after
81
+ * a line, else by line. Its type is printed into the surface entry via the
82
+ * compiler node builder with a real SymbolTracker (see node-builder.ts).
83
+ */
84
+ export interface InferAnchorRequest {
85
+ kind: 'infer';
86
+ alias: string;
87
+ source_file: string;
88
+ anchor_origin: AnchorOrigin;
89
+ /** Byte span of the target node (TS source positions). */
90
+ span_start?: number;
91
+ span_end?: number;
92
+ /** 1-based line the target starts on (locator fallback + disambiguation). */
93
+ line_number?: number;
94
+ /** Exact source text of the target expression (locator fallback). */
95
+ expression_text?: string;
96
+ /**
97
+ * carrick#498: the anchor targets a handler PARAMETER, not an expression.
98
+ * Carries the upstream `function_param` locator (a parameter name, a whole
99
+ * destructured binding pattern, or one binding element inside it), so the
100
+ * capture resolves the payload the handler RECEIVES. Without it a line-only
101
+ * subscriber anchor resolves the enclosing registration CALL and captures
102
+ * that call's return type (`void`, a subscription handle) as the contract.
103
+ * When present the parameter resolution is authoritative: a failure demotes
104
+ * rather than falling back to the expression locator.
105
+ */
106
+ param_name?: string;
107
+ /**
108
+ * Transport unwrapping applied to the located type before printing
109
+ * (design doc, Capture step 6: machinery unwrapping stays at capture time).
110
+ * Default 'awaited': Promise / thenable layers are unwrapped.
111
+ */
112
+ unwrap?: 'awaited' | 'none';
113
+ }
114
+ export type CaptureAnchorRequest = SymbolAnchorRequest | HandlerReturnAnchorRequest | InferAnchorRequest | LiteralAnchorRequest;
115
+ export type SelfCheckOutcome = 'ok' | 'allowlisted_external' | 'decayed_internal';
116
+ /**
117
+ * Why a captured or inferred type carries `any`/`unknown` at a given position
118
+ * (carrick#376).
119
+ *
120
+ * A bare `any` in an endpoint's printed type answers nothing. Each of these is
121
+ * a cause the layer that produced the type actually KNOWS, recorded at the
122
+ * decision point rather than reconstructed later. When no cause is known the
123
+ * honest value is `not_recorded` — never a guess.
124
+ *
125
+ * - `declared`: the captured declaration states `any`/`unknown` at this
126
+ * position. Whatever put it there (an author annotation, or an emitter that
127
+ * printed an unresolved value as `any`), it is baked into the emitted text
128
+ * and no install re-resolves it.
129
+ * - `budget_exhausted`: the subtree was too deep or wide to finish inside the
130
+ * capture walk's budget, so it is reported unverified rather than clean.
131
+ * - `no_payload_evidence`: a handler returned a call whose callee has no
132
+ * resolvable declaration, and nothing in the handler states what the callee
133
+ * is — no returned sibling call hands it a body plus a status, and the
134
+ * argument carries no `satisfies`/`as` annotation. Reading its argument
135
+ * anyway would publish a query or a parameter bag as the endpoint's
136
+ * contract.
137
+ * - `machinery_envelope`: the return resolved to transport (a
138
+ * Response/Request-shaped envelope) and no payload was recoverable inside
139
+ * it or from the handler's returned arguments.
140
+ * - `not_recorded`: the position carries a top type and this layer has no
141
+ * cause for it.
142
+ */
143
+ export type TypeProvenanceReason = 'declared' | 'budget_exhausted' | 'no_payload_evidence' | 'machinery_envelope' | 'not_recorded';
144
+ /**
145
+ * One `any`/`unknown` finding inside a captured or inferred type, with its
146
+ * position and its cause. Sorted by `path` wherever a list is emitted, so the
147
+ * output is byte-stable across runs (`scan-twice.sh`).
148
+ */
149
+ export interface TypeProvenance {
150
+ /**
151
+ * Member path of the finding: `''` for the type's own root, otherwise the
152
+ * same notation the capture self-check walk uses — `sub`, `items<0>.meta`,
153
+ * `[index]`, `()` for a callable return.
154
+ */
155
+ path: string;
156
+ /** What sits at `path`. `budget_exhausted` means the walk stopped there. */
157
+ kind: 'any' | 'unknown' | 'budget_exhausted';
158
+ reason: TypeProvenanceReason;
159
+ /**
160
+ * One scrubbed sentence a reader can act on. Never an absolute path, never a
161
+ * scan internal — the same bar the check phase's `diagnostic` meets.
162
+ */
163
+ detail?: string;
164
+ }
165
+ export interface CaptureAliasRecord {
166
+ alias: string;
167
+ anchor_kind: CaptureAnchorRequest['kind'];
168
+ symbol_name?: string;
169
+ /** Repo-root-relative declaring module; `<inline>` for literal anchors. */
170
+ source_file: string;
171
+ anchor_origin: AnchorOrigin;
172
+ serialization: SerializationTier;
173
+ self_check: SelfCheckOutcome;
174
+ /** Human-readable reason when self_check is not 'ok'. */
175
+ self_check_detail?: string;
176
+ /**
177
+ * Recorded when the alias never reached a usable tier (guard failure,
178
+ * locator failure, inaccessible symbols during node-builder printing).
179
+ * Present exactly for demoted anchors; a successful literal anchor sits
180
+ * at the structural_fallback tier WITHOUT a failure reason.
181
+ */
182
+ capture_failure_reason?: string;
183
+ /** True when the alias resolved to any/unknown/never during self-check.
184
+ * With self_check === 'allowlisted_external' this is expected on a bare
185
+ * checkout and is NOT a decay; the probe gates own the final verdict. */
186
+ top_type_at_self_check: boolean;
187
+ /**
188
+ * Every disqualifier the self-check found at DEPTH (member / element / index
189
+ * signature / type argument / callable return) with no failing
190
+ * pinned-external explanation: an author-baked `any`/`unknown`, or
191
+ * `budget_exhausted` — a subtree too deep/wide to finish within the walk's
192
+ * budget (failed closed, not silently clean).
193
+ *
194
+ * The FIRST entry is the one the check phase pre-gates on: its whole-type
195
+ * probe gates cannot see member-level decay, and `any` at any depth lets an
196
+ * arbitrary counterparty read compatible. `any` routes to
197
+ * `gate_caught_baked_any`; `unknown` and `budget_exhausted` route to
198
+ * `unverifiable`. The rest of the list exists so a reader of the published
199
+ * type can be told which fields are `any` and why (carrick#376) instead of
200
+ * being handed a shrug.
201
+ *
202
+ * Sorted by `path`; absent (not empty) when the walk found nothing.
203
+ */
204
+ any_provenance?: TypeProvenance[];
205
+ }
206
+ /** Aggregate fidelity metric, emitted per capture (one service). */
207
+ export interface CaptureFidelity {
208
+ total_aliases: number;
209
+ by_serialization: Record<SerializationTier, number>;
210
+ by_self_check: Record<SelfCheckOutcome, number>;
211
+ by_anchor_origin: Record<AnchorOrigin, number>;
212
+ /** Aliases whose capture is usable at check time (self_check ok or
213
+ * allowlisted_external) over total. */
214
+ usable_rate: number;
215
+ }
216
+ export interface CaptureStubResult {
217
+ success: boolean;
218
+ stub_dir: string;
219
+ package_name: string;
220
+ /** Stub-relative paths of the emitted declaration tree. */
221
+ emitted_files: string[];
222
+ /** Exact-version pins for external packages referenced by the tree. */
223
+ pinned_dependencies: Record<string, string>;
224
+ /** External specifiers referenced by the tree but absent from the lockfile. */
225
+ unpinned_externals: string[];
226
+ aliases: CaptureAliasRecord[];
227
+ fidelity: CaptureFidelity;
228
+ /** Tree-relative paths of files included because they declare global or
229
+ * module augmentations (design doc, Capture step 4). */
230
+ augmentation_files: string[];
231
+ /** Number of emitted specifiers rewritten by the post-emit pass
232
+ * (tsconfig-paths mappings and absolute internal import types). */
233
+ specifier_rewrites: number;
234
+ /** True when the source repo had no node_modules at capture time. */
235
+ bare_checkout: boolean;
236
+ ts_version: string;
237
+ errors: string[];
238
+ }
239
+ export interface CaptureStubOptions {
240
+ repoRoot: string;
241
+ serviceName: string;
242
+ anchors: CaptureAnchorRequest[];
243
+ /** Directory the stub package is written into (created if missing). */
244
+ outDir: string;
245
+ tsconfigPath?: string;
246
+ }
247
+ /** Wire protocol of a matched pair (drives the direction table). */
248
+ export type ProbeProtocol = 'http' | 'graphql' | 'socket' | 'pubsub';
249
+ /**
250
+ * Type kind of a matched pair. `request`/`response` disambiguate HTTP body
251
+ * direction (the confirmed inversion the direction table fixes); socket/pubsub
252
+ * pairs are `both`.
253
+ */
254
+ export type ProbeTypeKind = 'request' | 'response' | 'both';
255
+ /** One capture stub package to assemble into the check workspace. */
256
+ export interface CheckStubInput {
257
+ /** Service name (used for scrub labels + pair endpoints). */
258
+ service_name: string;
259
+ /** Absolute path to the capture stub dir (package.json + types/ tree). */
260
+ stub_dir: string;
261
+ }
262
+ /** One side of a matched pair: a service + the surface alias to probe. */
263
+ export interface CheckPairEndpoint {
264
+ service_name: string;
265
+ alias: string;
266
+ }
267
+ /**
268
+ * One matched pair to verify. The direction table maps (protocol, type_kind)
269
+ * to which endpoint is the `sent` value and which is the `expected` binding,
270
+ * so callers pass semantic producer/consumer roles and never a raw direction.
271
+ * (WP3 in Rust feeds protocol + type_kind; the table stays here, one place.)
272
+ */
273
+ export interface CheckPairSpec {
274
+ /** Stable caller key echoed back on the verdict; the pair_id is derived from it. */
275
+ pair_key: string;
276
+ protocol: ProbeProtocol;
277
+ type_kind: ProbeTypeKind;
278
+ producer: CheckPairEndpoint;
279
+ consumer: CheckPairEndpoint;
280
+ }
281
+ /**
282
+ * Four-bucket classifier output (pinned decision 7):
283
+ * - compatible: no diagnostics; the value-level assignment holds.
284
+ * - incompatible: an assignment-class diagnostic (TS2322/2741/...) — real
285
+ * compiler text is the report.
286
+ * - unverifiable: a side decayed to unknown/never, a surface export is
287
+ * missing/renamed, or a stub tree carries its own diagnostics (poison).
288
+ * - gate_caught_baked_any: a side resolved to `any` (the IsAny probe gate
289
+ * fired) — the backstop that stops a baked-any reading as compatible.
290
+ */
291
+ export type VerdictBucket = 'compatible' | 'incompatible' | 'unverifiable' | 'gate_caught_baked_any';
292
+ export interface CheckVerdict {
293
+ /** Deterministic FNV-1a hash of the pair (never a temp path). */
294
+ pair_id: string;
295
+ /** Caller key, echoed for the WP3 verdict join. */
296
+ pair_key: string;
297
+ bucket: VerdictBucket;
298
+ /**
299
+ * For gate/import buckets: which side and which gate fired, e.g.
300
+ * `producer:any`, `consumer:unknown`, `import:producer`. Absent for
301
+ * compatible.
302
+ */
303
+ gate?: string;
304
+ /** User-facing message: scrubbed real TS text, or a synthesized reason.
305
+ * Never contains absolute paths or scan internals. Absent for compatible. */
306
+ diagnostic?: string;
307
+ /** TS diagnostic codes attributed to this pair's probe, sorted. */
308
+ codes: number[];
309
+ /**
310
+ * Whether this verdict is a FACT about two known types (carrick#707, R1d).
311
+ *
312
+ * `bucket` alone does not say that. `compatible` is emitted whenever the
313
+ * probe raised no assignment diagnostic, and a pair can clear the whole-type
314
+ * gates while a member three levels down is `any` — which every counterparty
315
+ * shape satisfies, so "no diagnostic" there means "nothing was compared".
316
+ * A reader that treats such a verdict as evidence is reading a gap as a
317
+ * guarantee.
318
+ *
319
+ * `true` only when the bucket is `compatible` or `incompatible` AND a deep
320
+ * walk over BOTH sides of the probe, run in the assembled workspace with the
321
+ * pinned externals installed, found no `any`/`unknown`/`never` at any depth.
322
+ * Every other outcome — a gate, a missing import, poison, a pre-verdict, a
323
+ * deep finding — is `false` with `unresolved_reason` set.
324
+ *
325
+ * Deliberately independent of `bucket`: the bucket keeps its existing
326
+ * meaning and no verdict changes because of this field.
327
+ */
328
+ resolved: boolean;
329
+ /** Why `resolved` is false. Absent exactly when `resolved` is true. */
330
+ unresolved_reason?: string;
331
+ }
332
+ /**
333
+ * A service degraded SERVICE-WIDE: install failure, or a stub-tree diagnostic
334
+ * that could not be attributed to any alias's import closure (#438). Poison
335
+ * contained to specific aliases does NOT appear here — those pairs carry their
336
+ * own `poison:*` verdicts while the service's clean pairs verify normally. So
337
+ * absence from this list is not a "fully verified" signal; read per-pair
338
+ * verdicts for that.
339
+ */
340
+ export interface DegradedService {
341
+ service_name: string;
342
+ reason: string;
343
+ }
344
+ export interface CheckResult {
345
+ success: boolean;
346
+ /** Scratch workspace directory (kept unless caller cleans it). */
347
+ workspace_dir: string;
348
+ /** `pnpm` when isolation held; `unavailable` when the vendored pnpm is
349
+ * missing (soundness over availability — pinned design, Check step 2). */
350
+ isolation: 'pnpm' | 'unavailable';
351
+ install_ok: boolean;
352
+ /** Scrubbed install-failure summary when install_ok is false. */
353
+ install_error?: string;
354
+ ts_version: string;
355
+ /** Verdicts, sorted by pair_id for byte-stable output. */
356
+ verdicts: CheckVerdict[];
357
+ degraded_services: DegradedService[];
358
+ errors: string[];
359
+ }
360
+ export interface CheckOptions {
361
+ stubs: CheckStubInput[];
362
+ pairs: CheckPairSpec[];
363
+ /** Parent dir for the scratch workspace (default: os.tmpdir()). */
364
+ workspaceRoot?: string;
365
+ /** Absolute path to the vendored pnpm binary. Defaults to the first
366
+ * `node_modules/.bin/pnpm` at or above this bundle — the sidecar's own in a
367
+ * checkout, the install root's in an npm install. */
368
+ pnpmPath?: string;
369
+ /** Absolute path to the tsc CLI. Defaults to the nearest
370
+ * `node_modules/.bin/tsc` at or above this bundle. Tests inject a stand-in
371
+ * to pin the abnormal-termination path. */
372
+ tscPath?: string;
373
+ /** Delete the scratch workspace before returning (default true). Tests that
374
+ * inspect the assembled tree pass false. */
375
+ cleanup?: boolean;
376
+ }
377
+ /** Progress phases emitted over the async install protocol. */
378
+ export type CheckProgressPhase = 'assembling' | 'installing' | 'checking';
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Wire contract for the v2 capture bundle ("tsc as the serializer").
3
+ *
4
+ * This file IS the seam (design doc: "seam, not split"): everything outside
5
+ * src/sidecar/src/capture/ may import types from this file and the
6
+ * `captureStub` entry point from ./index.js, and nothing else. Modules inside
7
+ * capture/ import only node builtins, `typescript`, and each other. The
8
+ * stdio `capture_v2` action is the only surface the Rust client sees.
9
+ */
10
+ export {};
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Global / module-augmentation detection (design doc, Capture step 4).
3
+ *
4
+ * An entry-rooted program drops `declare global` and `declare module "x"`
5
+ * augmentation files outside the entry's import graph, and because emit
6
+ * proceeds despite errors the tree would ship with dangling global
7
+ * references. Capture therefore scans the tsconfig's full file list and adds
8
+ * every augmentation-declaring file as an extra emit root.
9
+ *
10
+ * This deliberately over-approximates "reachable from the closure's symbols":
11
+ * including an unrelated augmentation costs tree bytes, excluding a needed
12
+ * one silently corrupts the closure. Cheap syntactic prefilter first, real
13
+ * parse only on candidates.
14
+ */
15
+ /**
16
+ * Absolute paths of files in `fileNames` that declare global or module
17
+ * augmentations. Files that fail to read/parse are skipped (they will fail
18
+ * loudly elsewhere if they matter).
19
+ */
20
+ export declare function findAugmentationFiles(fileNames: readonly string[]): string[];
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Global / module-augmentation detection (design doc, Capture step 4).
3
+ *
4
+ * An entry-rooted program drops `declare global` and `declare module "x"`
5
+ * augmentation files outside the entry's import graph, and because emit
6
+ * proceeds despite errors the tree would ship with dangling global
7
+ * references. Capture therefore scans the tsconfig's full file list and adds
8
+ * every augmentation-declaring file as an extra emit root.
9
+ *
10
+ * This deliberately over-approximates "reachable from the closure's symbols":
11
+ * including an unrelated augmentation costs tree bytes, excluding a needed
12
+ * one silently corrupts the closure. Cheap syntactic prefilter first, real
13
+ * parse only on candidates.
14
+ */
15
+ import ts from 'typescript';
16
+ import * as fs from 'node:fs';
17
+ const PREFILTER = /declare\s+(?:global|module)\b/;
18
+ function declaresAugmentation(sourceFile) {
19
+ // Augmentations only have their augmentation semantics inside a module;
20
+ // in a script file `declare module "x"` is an ambient module declaration,
21
+ // which the closure treatment still wants shipped when present.
22
+ for (const stmt of sourceFile.statements) {
23
+ if (!ts.isModuleDeclaration(stmt))
24
+ continue;
25
+ if (stmt.name.kind === ts.SyntaxKind.Identifier) {
26
+ // `declare global` parses as a ModuleDeclaration with the
27
+ // GlobalAugmentation flag and an Identifier name.
28
+ if ((stmt.flags & ts.NodeFlags.GlobalAugmentation) !== 0)
29
+ return true;
30
+ }
31
+ else if (ts.isStringLiteral(stmt.name)) {
32
+ return true;
33
+ }
34
+ }
35
+ return false;
36
+ }
37
+ /**
38
+ * Absolute paths of files in `fileNames` that declare global or module
39
+ * augmentations. Files that fail to read/parse are skipped (they will fail
40
+ * loudly elsewhere if they matter).
41
+ */
42
+ export function findAugmentationFiles(fileNames) {
43
+ const found = [];
44
+ for (const fileName of fileNames) {
45
+ let text;
46
+ try {
47
+ text = fs.readFileSync(fileName, 'utf8');
48
+ }
49
+ catch {
50
+ continue;
51
+ }
52
+ if (!PREFILTER.test(text))
53
+ continue;
54
+ const sourceFile = ts.createSourceFile(fileName, text, ts.ScriptTarget.Latest,
55
+ /* setParentNodes */ false);
56
+ if (declaresAugmentation(sourceFile))
57
+ found.push(fileName);
58
+ }
59
+ return found;
60
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Diagnostic parsing + four-bucket classification for the v2 check phase.
3
+ *
4
+ * The judge is the vendored `tsc` CLI with `--pretty false`, run from the
5
+ * workspace root so file locations print workspace-relative (no temp path in
6
+ * the location prefix). This module turns that text into per-pair verdicts,
7
+ * classifying by diagnostic code + file + line (never by line position alone):
8
+ *
9
+ * poison (stub-file diagnostic) -> unverifiable [highest precedence]
10
+ * surface import error (probe lines 1-2)-> unverifiable
11
+ * IsAny gate fired (TS2344) -> gate_caught_baked_any
12
+ * IsUnknown/IsNever gate fired (TS2344) -> unverifiable
13
+ * assignment-class error -> incompatible
14
+ * no diagnostics -> compatible [lowest precedence]
15
+ *
16
+ * Gate precedence over the assignment line is load-bearing: an `unknown` side
17
+ * produces BOTH a gate TS2344 and an assignment TS2322, and reading the latter
18
+ * would mislabel an unverifiable pair as incompatible.
19
+ *
20
+ * Seam: node builtins + this bundle only.
21
+ */
22
+ import type { CheckVerdict } from './api.js';
23
+ import type { ProbePlan } from './check-probe.js';
24
+ import { type ScrubContext } from './check-scrub.js';
25
+ import type { PairDeepFindings } from './check-deep.js';
26
+ export interface RawDiagnostic {
27
+ /** Workspace-relative, forward-slash file path (empty for global errors). */
28
+ file: string;
29
+ line: number;
30
+ col: number;
31
+ code: number;
32
+ /** Primary text plus any indented elaboration lines, joined with '\n'. */
33
+ message: string;
34
+ }
35
+ /** Parse `tsc --pretty false` output into structured diagnostics. */
36
+ export declare function parseTscOutput(stdout: string): RawDiagnostic[];
37
+ export interface ClassifyInput {
38
+ plan: ProbePlan;
39
+ /** Diagnostics attributed to this pair's probe file. */
40
+ probeDiags: RawDiagnostic[];
41
+ /**
42
+ * Returns a reason string when THIS alias of the service is poisoned (#438
43
+ * part 2: poison is contained to the aliases whose closure includes the
44
+ * poisoned file, not the whole service).
45
+ */
46
+ poisonReason: (serviceName: string, alias: string) => string | undefined;
47
+ scrubCtx: ScrubContext;
48
+ /**
49
+ * Deep any/unknown findings for this pair's two sides, walked in the
50
+ * assembled workspace after the pinned externals installed (carrick#707,
51
+ * R1d). `undefined` when the walk could not run or could not resolve the
52
+ * aliases -- absence of findings is not evidence of cleanliness, so the
53
+ * verdict is then not a fact either.
54
+ */
55
+ deepFindings?: PairDeepFindings;
56
+ }
57
+ /** Classify one pair into exactly one bucket, honouring the precedence order. */
58
+ export declare function classifyPair(input: ClassifyInput): CheckVerdict;