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.
- package/LICENSE.md +99 -0
- package/README.md +79 -0
- package/bin/carrick.mjs +168 -0
- package/dist/channel.d.ts +16 -0
- package/dist/channel.js +37 -0
- package/dist/channel.js.map +1 -0
- package/dist/cli.d.ts +29 -0
- package/dist/cli.js +60 -0
- package/dist/cli.js.map +1 -0
- package/dist/contract.d.ts +154 -0
- package/dist/contract.js +141 -0
- package/dist/contract.js.map +1 -0
- package/dist/diagnostics.d.ts +54 -0
- package/dist/diagnostics.js +199 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/hook/post-edit.d.ts +13 -0
- package/dist/hook/post-edit.js +76 -0
- package/dist/hook/post-edit.js.map +1 -0
- package/dist/hook/session-start.d.ts +2 -0
- package/dist/hook/session-start.js +44 -0
- package/dist/hook/session-start.js.map +1 -0
- package/dist/init/identity.d.ts +20 -0
- package/dist/init/identity.js +60 -0
- package/dist/init/identity.js.map +1 -0
- package/dist/init/repos.d.ts +19 -0
- package/dist/init/repos.js +46 -0
- package/dist/init/repos.js.map +1 -0
- package/dist/init/run.d.ts +11 -0
- package/dist/init/run.js +228 -0
- package/dist/init/run.js.map +1 -0
- package/dist/init/settings.d.ts +50 -0
- package/dist/init/settings.js +138 -0
- package/dist/init/settings.js.map +1 -0
- package/dist/log.d.ts +2 -0
- package/dist/log.js +35 -0
- package/dist/log.js.map +1 -0
- package/dist/native.d.ts +48 -0
- package/dist/native.js +127 -0
- package/dist/native.js.map +1 -0
- package/dist/render.d.ts +65 -0
- package/dist/render.js +334 -0
- package/dist/render.js.map +1 -0
- package/dist/root.d.ts +30 -0
- package/dist/root.js +82 -0
- package/dist/root.js.map +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +255 -0
- package/dist/server.js.map +1 -0
- package/dist/templates.d.ts +18 -0
- package/dist/templates.js +66 -0
- package/dist/templates.js.map +1 -0
- package/package.json +72 -0
- package/plugin/.claude-plugin/plugin.json +6 -0
- package/plugin/.lsp.json +14 -0
- package/plugin/hooks/hooks.json +27 -0
- package/sidecar/dist/src/bundler.d.ts +141 -0
- package/sidecar/dist/src/bundler.js +680 -0
- package/sidecar/dist/src/capture/anchors.d.ts +61 -0
- package/sidecar/dist/src/capture/anchors.js +1132 -0
- package/sidecar/dist/src/capture/api.d.ts +378 -0
- package/sidecar/dist/src/capture/api.js +10 -0
- package/sidecar/dist/src/capture/augmentations.d.ts +20 -0
- package/sidecar/dist/src/capture/augmentations.js +60 -0
- package/sidecar/dist/src/capture/check-classify.d.ts +58 -0
- package/sidecar/dist/src/capture/check-classify.js +189 -0
- package/sidecar/dist/src/capture/check-deep.d.ts +32 -0
- package/sidecar/dist/src/capture/check-deep.js +91 -0
- package/sidecar/dist/src/capture/check-poison.d.ts +40 -0
- package/sidecar/dist/src/capture/check-poison.js +155 -0
- package/sidecar/dist/src/capture/check-probe.d.ts +70 -0
- package/sidecar/dist/src/capture/check-probe.js +147 -0
- package/sidecar/dist/src/capture/check-scrub.d.ts +31 -0
- package/sidecar/dist/src/capture/check-scrub.js +71 -0
- package/sidecar/dist/src/capture/check-workspace.d.ts +52 -0
- package/sidecar/dist/src/capture/check-workspace.js +218 -0
- package/sidecar/dist/src/capture/check.d.ts +39 -0
- package/sidecar/dist/src/capture/check.js +450 -0
- package/sidecar/dist/src/capture/deep-walk.d.ts +62 -0
- package/sidecar/dist/src/capture/deep-walk.js +243 -0
- package/sidecar/dist/src/capture/index.d.ts +36 -0
- package/sidecar/dist/src/capture/index.js +477 -0
- package/sidecar/dist/src/capture/lockfile.d.ts +48 -0
- package/sidecar/dist/src/capture/lockfile.js +490 -0
- package/sidecar/dist/src/capture/machinery.d.ts +59 -0
- package/sidecar/dist/src/capture/machinery.js +160 -0
- package/sidecar/dist/src/capture/node-builder.d.ts +37 -0
- package/sidecar/dist/src/capture/node-builder.js +123 -0
- package/sidecar/dist/src/capture/paths-rewrite.d.ts +34 -0
- package/sidecar/dist/src/capture/paths-rewrite.js +100 -0
- package/sidecar/dist/src/capture/self-check.d.ts +38 -0
- package/sidecar/dist/src/capture/self-check.js +317 -0
- package/sidecar/dist/src/capture/specifiers.d.ts +39 -0
- package/sidecar/dist/src/capture/specifiers.js +56 -0
- package/sidecar/dist/src/definition-resolver.d.ts +57 -0
- package/sidecar/dist/src/definition-resolver.js +153 -0
- package/sidecar/dist/src/index.d.ts +14 -0
- package/sidecar/dist/src/index.js +564 -0
- package/sidecar/dist/src/monorepo-builder.d.ts +129 -0
- package/sidecar/dist/src/monorepo-builder.js +584 -0
- package/sidecar/dist/src/project-loader.d.ts +130 -0
- package/sidecar/dist/src/project-loader.js +399 -0
- package/sidecar/dist/src/type-inferrer.d.ts +940 -0
- package/sidecar/dist/src/type-inferrer.js +3540 -0
- package/sidecar/dist/src/type-structural-expander.d.ts +61 -0
- package/sidecar/dist/src/type-structural-expander.js +283 -0
- package/sidecar/dist/src/type-text-canonicalizer.d.ts +40 -0
- package/sidecar/dist/src/type-text-canonicalizer.js +297 -0
- package/sidecar/dist/src/types.d.ts +669 -0
- package/sidecar/dist/src/types.js +5 -0
- package/sidecar/dist/src/validators.d.ts +2214 -0
- package/sidecar/dist/src/validators.js +336 -0
- package/sidecar/package.json +6 -0
- package/templates/carrick.json +7 -0
- 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;
|