carrick 0.3.83 → 0.3.85
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/README.md +31 -11
- package/bin/carrick.mjs +89 -0
- package/dist/auth/credentials.d.ts +9 -0
- package/dist/auth/credentials.js +13 -2
- package/dist/auth/credentials.js.map +1 -1
- package/dist/auth/read.d.ts +4 -4
- package/dist/global-install.d.ts +183 -0
- package/dist/global-install.js +393 -0
- package/dist/global-install.js.map +1 -0
- package/dist/hook/post-edit.js +10 -0
- package/dist/hook/post-edit.js.map +1 -1
- package/dist/hook/session-start.js +34 -0
- package/dist/hook/session-start.js.map +1 -1
- package/dist/hook/stop.js +18 -4
- package/dist/hook/stop.js.map +1 -1
- package/dist/hook/user-prompt.js +16 -4
- package/dist/hook/user-prompt.js.map +1 -1
- package/dist/init/doctor.d.ts +61 -0
- package/dist/init/doctor.js +160 -1
- package/dist/init/doctor.js.map +1 -1
- package/dist/init/hosted.d.ts +30 -5
- package/dist/init/hosted.js +75 -9
- package/dist/init/hosted.js.map +1 -1
- package/dist/init/mcp.d.ts +45 -30
- package/dist/init/mcp.js +47 -88
- package/dist/init/mcp.js.map +1 -1
- package/dist/init/outdated.d.ts +47 -0
- package/dist/init/outdated.js +104 -0
- package/dist/init/outdated.js.map +1 -1
- package/dist/init/output.d.ts +43 -6
- package/dist/init/output.js +90 -18
- package/dist/init/output.js.map +1 -1
- package/dist/init/projects.d.ts +2 -2
- package/dist/init/run.d.ts +89 -25
- package/dist/init/run.js +292 -58
- package/dist/init/run.js.map +1 -1
- package/dist/render.d.ts +16 -0
- package/dist/render.js +57 -6
- package/dist/render.js.map +1 -1
- package/dist/scan.d.ts +26 -0
- package/dist/scan.js +92 -0
- package/dist/scan.js.map +1 -1
- package/dist/update-check.d.ts +1 -0
- package/dist/update-check.js +25 -0
- package/dist/update-check.js.map +1 -0
- package/dist/update.d.ts +128 -0
- package/dist/update.js +398 -0
- package/dist/update.js.map +1 -0
- package/package.json +8 -7
- package/sidecar/dist/src/capture/anchors.js +69 -8
- package/sidecar/dist/src/capture/api.d.ts +27 -2
- package/sidecar/dist/src/capture/check-classify.js +31 -2
- package/sidecar/dist/src/capture/check-fields.d.ts +29 -0
- package/sidecar/dist/src/capture/check-fields.js +58 -6
- package/sidecar/dist/src/capture/check.js +5 -0
- package/sidecar/dist/src/capture/deep-walk.js +4 -1
- package/sidecar/dist/src/capture/index.js +6 -1
- package/sidecar/dist/src/capture/node-builder.d.ts +28 -1
- package/sidecar/dist/src/capture/node-builder.js +89 -3
- package/sidecar/dist/src/capture/self-check.d.ts +6 -1
- package/sidecar/dist/src/capture/self-check.js +65 -11
- package/sidecar/dist/src/capture/unresolved.d.ts +7 -0
- package/sidecar/dist/src/capture/unresolved.js +1 -1
- package/sidecar/dist/src/type-inferrer.d.ts +118 -4
- package/sidecar/dist/src/type-inferrer.js +433 -15
- package/sidecar/dist/src/validators.d.ts +40 -40
- package/sidecar/dist/src/validators.js +6 -1
- package/templates/skills/carrick-census.md +3 -1
|
@@ -22,7 +22,12 @@
|
|
|
22
22
|
* Attribution is per-alias closure: failed specifiers are blamed on an alias
|
|
23
23
|
* only if they occur in a file reachable from that alias's surface statement
|
|
24
24
|
* (import-type seeds, then BFS over relative imports). The spike's
|
|
25
|
-
* file-granularity shortcut is gone
|
|
25
|
+
* file-granularity shortcut is gone -- including on the SURFACE file itself,
|
|
26
|
+
* which holds every alias, so a file-granular bucket there blamed the whole
|
|
27
|
+
* service for one alias's dangling specifier (cloud#1184). A surface
|
|
28
|
+
* diagnostic is attributed by `export type` statement span, exactly as
|
|
29
|
+
* check-poison.ts contains poison; one that no statement covers keeps the
|
|
30
|
+
* service-wide bucket.
|
|
26
31
|
*/
|
|
27
32
|
import ts from 'typescript';
|
|
28
33
|
import * as fs from 'node:fs';
|
|
@@ -73,13 +78,26 @@ function runSelfCheck(args, treeFiles) {
|
|
|
73
78
|
const program = ts.createProgram(treeFiles, options, args.compilerHost?.(options));
|
|
74
79
|
const checker = program.getTypeChecker();
|
|
75
80
|
const diagnostics = ts.getPreEmitDiagnostics(program);
|
|
76
|
-
|
|
81
|
+
const surfaceAbs = path.resolve(args.surfaceAbsPath);
|
|
82
|
+
const surfaceSource = program.getSourceFile(surfaceAbs);
|
|
83
|
+
// The surface holds EVERY alias, and every alias's closure starts there, so
|
|
84
|
+
// a file-granular failure bucket on it blames the whole service for one
|
|
85
|
+
// alias's dangling specifier (cloud#1184). Attribute by `export type`
|
|
86
|
+
// statement span, exactly as check-poison.ts contains poison.
|
|
87
|
+
const aliasAtSurfacePosition = buildSurfaceSpanIndex(surfaceSource);
|
|
88
|
+
// Failed module specifiers, split external-pinned vs internal, per FILE —
|
|
89
|
+
// except on the surface, where they are per ALIAS.
|
|
77
90
|
const failuresByFile = new Map();
|
|
78
|
-
const
|
|
79
|
-
|
|
91
|
+
const surfaceFailuresByAlias = new Map();
|
|
92
|
+
const emptyFailures = () => ({
|
|
93
|
+
externalPinned: new Set(),
|
|
94
|
+
internal: new Set(),
|
|
95
|
+
});
|
|
96
|
+
const bucketIn = (map, key) => {
|
|
97
|
+
let entry = map.get(key);
|
|
80
98
|
if (!entry) {
|
|
81
|
-
entry =
|
|
82
|
-
|
|
99
|
+
entry = emptyFailures();
|
|
100
|
+
map.set(key, entry);
|
|
83
101
|
}
|
|
84
102
|
return entry;
|
|
85
103
|
};
|
|
@@ -91,7 +109,15 @@ function runSelfCheck(args, treeFiles) {
|
|
|
91
109
|
if (!m)
|
|
92
110
|
continue;
|
|
93
111
|
const spec = m[1];
|
|
94
|
-
const
|
|
112
|
+
const abs = path.resolve(d.file.fileName);
|
|
113
|
+
// A surface diagnostic outside every alias statement (a file-level import,
|
|
114
|
+
// a reference directive) is attributable to no alias and keeps the
|
|
115
|
+
// service-wide file bucket: soundness over precision, the same fallback
|
|
116
|
+
// check-poison.ts makes.
|
|
117
|
+
const owner = abs === surfaceAbs ? aliasAtSurfacePosition(d.start) : undefined;
|
|
118
|
+
const bucket = owner
|
|
119
|
+
? bucketIn(surfaceFailuresByAlias, owner)
|
|
120
|
+
: bucketIn(failuresByFile, abs);
|
|
95
121
|
if (!isRelative(spec) && args.pinned[packageNameOf(spec)]) {
|
|
96
122
|
bucket.externalPinned.add(spec);
|
|
97
123
|
}
|
|
@@ -113,8 +139,6 @@ function runSelfCheck(args, treeFiles) {
|
|
|
113
139
|
}
|
|
114
140
|
adjacency.set(abs, neighbors);
|
|
115
141
|
}
|
|
116
|
-
const surfaceAbs = path.resolve(args.surfaceAbsPath);
|
|
117
|
-
const surfaceSource = program.getSourceFile(surfaceAbs);
|
|
118
142
|
const records = [];
|
|
119
143
|
for (const anchor of args.resolved) {
|
|
120
144
|
// Demotions (failureReason present) never reached the surface with a
|
|
@@ -131,10 +155,35 @@ function runSelfCheck(args, treeFiles) {
|
|
|
131
155
|
surfaceSource,
|
|
132
156
|
adjacency,
|
|
133
157
|
failuresByFile,
|
|
158
|
+
surfaceFailuresByAlias,
|
|
134
159
|
}));
|
|
135
160
|
}
|
|
136
161
|
return records;
|
|
137
162
|
}
|
|
163
|
+
/**
|
|
164
|
+
* Position -> the alias whose `export type` statement span covers it, for
|
|
165
|
+
* diagnostics reported on the surface file. `undefined` when no alias
|
|
166
|
+
* statement covers the position, or when the surface is not in the program.
|
|
167
|
+
*/
|
|
168
|
+
function buildSurfaceSpanIndex(surfaceSource) {
|
|
169
|
+
if (!surfaceSource)
|
|
170
|
+
return () => undefined;
|
|
171
|
+
const spans = [];
|
|
172
|
+
for (const stmt of surfaceSource.statements) {
|
|
173
|
+
if (!ts.isTypeAliasDeclaration(stmt))
|
|
174
|
+
continue;
|
|
175
|
+
spans.push({
|
|
176
|
+
alias: stmt.name.text,
|
|
177
|
+
start: stmt.getStart(surfaceSource),
|
|
178
|
+
end: stmt.getEnd(),
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
return (position) => {
|
|
182
|
+
if (position === undefined)
|
|
183
|
+
return undefined;
|
|
184
|
+
return spans.find((span) => position >= span.start && position <= span.end)?.alias;
|
|
185
|
+
};
|
|
186
|
+
}
|
|
138
187
|
/** An alias that never reached a capture-native tier: the failure reason was
|
|
139
188
|
* recorded at demotion time; the surface line is `unknown` by construction. */
|
|
140
189
|
function demotedRecord(anchor) {
|
|
@@ -195,8 +244,13 @@ function checkedRecord(anchor, ctx) {
|
|
|
195
244
|
let blamedExternal;
|
|
196
245
|
let internalFailure;
|
|
197
246
|
const danglingSpecifiers = new Set();
|
|
198
|
-
|
|
199
|
-
|
|
247
|
+
// This alias's own surface statement, then the closure's files. The surface
|
|
248
|
+
// file bucket now holds only the diagnostics no alias statement covers.
|
|
249
|
+
const closureFailures = [
|
|
250
|
+
ctx.surfaceFailuresByAlias.get(alias),
|
|
251
|
+
...[...closure].map((file) => ctx.failuresByFile.get(file)),
|
|
252
|
+
];
|
|
253
|
+
for (const failures of closureFailures) {
|
|
200
254
|
if (!failures)
|
|
201
255
|
continue;
|
|
202
256
|
if (!blamedExternal)
|
|
@@ -26,3 +26,10 @@ import { type UnresolvedAtAnchor } from './deep-walk.js';
|
|
|
26
26
|
* depth prints `import('./m').Row[]`, whose members sit under `<0>`.
|
|
27
27
|
*/
|
|
28
28
|
export declare function unresolvedAtAnchor(program: ts.Program, sourceFile: ts.SourceFile, type: ts.Type, location: ts.Node, pathPrefix?: string): UnresolvedAtAnchor | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* Module specifiers, as written, that do not resolve from `sourceFile` or from
|
|
31
|
+
* any source module it imports, breadth-first so the nearest come first, with
|
|
32
|
+
* relative specifiers ahead of package names. Installed packages and the
|
|
33
|
+
* default library are not descended into.
|
|
34
|
+
*/
|
|
35
|
+
export declare function unresolvedSpecifiersReachableFrom(program: ts.Program, sourceFile: ts.SourceFile): string[];
|
|
@@ -50,7 +50,7 @@ function prefixPath(prefix, path) {
|
|
|
50
50
|
* relative specifiers ahead of package names. Installed packages and the
|
|
51
51
|
* default library are not descended into.
|
|
52
52
|
*/
|
|
53
|
-
function unresolvedSpecifiersReachableFrom(program, sourceFile) {
|
|
53
|
+
export function unresolvedSpecifiersReachableFrom(program, sourceFile) {
|
|
54
54
|
let cache = reachableCache.get(program);
|
|
55
55
|
if (!cache) {
|
|
56
56
|
cache = new Map();
|
|
@@ -206,17 +206,131 @@ export declare class TypeInferrer {
|
|
|
206
206
|
*/
|
|
207
207
|
private isResponseSend;
|
|
208
208
|
/**
|
|
209
|
-
* The
|
|
209
|
+
* The call `node` is an ARGUMENT of, looking through the wrappers that do
|
|
210
210
|
* not change a payload (parentheses, `as`, `satisfies`, `!`, `await`) and a
|
|
211
|
-
* `JSON.stringify` around the body. `undefined` when
|
|
212
|
-
*
|
|
211
|
+
* `JSON.stringify` around the body. `undefined` when `accept` rejects that
|
|
212
|
+
* call or `node` is its callee.
|
|
213
|
+
*
|
|
214
|
+
* Two readings use it: a value handed to a response send is the payload that
|
|
215
|
+
* send transmits, and a body read handed to a call that states what it
|
|
216
|
+
* returns is a better statement of that body than the read (carrick#1382).
|
|
213
217
|
*/
|
|
214
|
-
private
|
|
218
|
+
private receivingCallOf;
|
|
215
219
|
private inferCallResult;
|
|
216
220
|
private inferVariable;
|
|
217
221
|
private inferExpression;
|
|
218
222
|
private inferRequestBody;
|
|
219
223
|
private resolveCallResultTerminalNode;
|
|
224
|
+
/**
|
|
225
|
+
* True when a member read on the call's result resolves to one of that
|
|
226
|
+
* result's own TYPE ARGUMENTS — the source is unwrapping a generic envelope
|
|
227
|
+
* by hand (`state.data` off a `ResourceState<Envelope>`), and the payload it
|
|
228
|
+
* carries is the instantiation, not the envelope (carrick#1375).
|
|
229
|
+
*
|
|
230
|
+
* The generic is what tells the two apart. A call that answers its payload
|
|
231
|
+
* directly is read member by member too, and its declared result IS the
|
|
232
|
+
* contract; abstaining there would throw away the type the request boundary
|
|
233
|
+
* states, which a replay over a real repo's consumer rows showed on a
|
|
234
|
+
* `{ ok: true } | { ok: false; reason: string }` result read as `sent.ok`.
|
|
235
|
+
*/
|
|
236
|
+
private projectionReadsGenericPayload;
|
|
237
|
+
/**
|
|
238
|
+
* The payload a RESULT CARRIER carries, or `undefined` when `type` is not
|
|
239
|
+
* one or its success side cannot be told from its failure side
|
|
240
|
+
* (carrick#1376).
|
|
241
|
+
*
|
|
242
|
+
* A carrier is recognised by its shape, never by a name: a union of object
|
|
243
|
+
* branches, instantiated with two or more type arguments, at least one of
|
|
244
|
+
* which a branch holds as a member. `Result<T, E>`, `Either<L, R>` and a
|
|
245
|
+
* hand-rolled `{ ok: true; value: T } | { ok: false; error: E }` are all the
|
|
246
|
+
* same shape, and a promise-like around one is peeled first through the
|
|
247
|
+
* language's own await protocol. A single generic object — a resource state,
|
|
248
|
+
* a query result — is NOT a union and is left to carrick#1375, which
|
|
249
|
+
* abstains on it so a sibling site can answer.
|
|
250
|
+
*
|
|
251
|
+
* Which argument is the payload is decided twice over, and never guessed:
|
|
252
|
+
*
|
|
253
|
+
* 1. the platform's error shape. Exactly one argument that is not
|
|
254
|
+
* error-shaped, beside at least one that is, is the success side.
|
|
255
|
+
* 2. what the source reads. Where every argument looks alike — `Pair<A,
|
|
256
|
+
* string>` — a member read of the carrier that resolves to exactly one
|
|
257
|
+
* of the arguments names the side this call site takes.
|
|
258
|
+
*
|
|
259
|
+
* Where neither decides, the carrier keeps its own answer and the limit is
|
|
260
|
+
* logged: a coin flip published as a contract is worse than an envelope a
|
|
261
|
+
* reader can see is an envelope.
|
|
262
|
+
*/
|
|
263
|
+
private resultCarrierPayload;
|
|
264
|
+
/**
|
|
265
|
+
* `Future<T>` -> `T` for a promise-like of the source's own making, read off
|
|
266
|
+
* the await protocol rather than a name: a `then` whose first parameter is a
|
|
267
|
+
* callback, whose own first parameter is the value awaiting it yields.
|
|
268
|
+
* `Promise` and `PromiseLike` are peeled by `unwrapPromiseType` before this.
|
|
269
|
+
*/
|
|
270
|
+
private unwrapThenableType;
|
|
271
|
+
/**
|
|
272
|
+
* The platform's error shape, in full: `name` and `message` strings AND a
|
|
273
|
+
* `stack`, which is what the `Error` interface declares and every subclass
|
|
274
|
+
* of it inherits.
|
|
275
|
+
*
|
|
276
|
+
* `stack` is what makes the test a test. A name and a message alone are a
|
|
277
|
+
* shape a PAYLOAD can have — a contact form declares both — and reading such
|
|
278
|
+
* a payload as the failure side would publish the other argument, which is
|
|
279
|
+
* the concrete-but-wrong answer this whole rule exists to avoid. A union is
|
|
280
|
+
* error-shaped when every member of it is.
|
|
281
|
+
*/
|
|
282
|
+
private isErrorShaped;
|
|
283
|
+
/**
|
|
284
|
+
* The member read that takes `identifier` as its RECEIVER — `query` in
|
|
285
|
+
* `query.data`, `envelope` in `envelope.list[0]` — or `undefined` when the
|
|
286
|
+
* identifier names the value itself.
|
|
287
|
+
*
|
|
288
|
+
* A member CALL is not a projection: `res.text()` yields a body rather than
|
|
289
|
+
* a part of one, and what it returns stays the walk's business. The
|
|
290
|
+
* zero-argument json body read has its own branch and is taken before this
|
|
291
|
+
* is asked.
|
|
292
|
+
*/
|
|
293
|
+
private projectionOnReceiver;
|
|
294
|
+
/**
|
|
295
|
+
* The call that CONSUMES this json body read and states what the body is —
|
|
296
|
+
* `parseEnvelope(await response.json())` — or `undefined` when nothing
|
|
297
|
+
* downstream of the read says more about it than the read itself does
|
|
298
|
+
* (carrick#1382).
|
|
299
|
+
*
|
|
300
|
+
* Three conditions, all shapes of the language rather than names:
|
|
301
|
+
*
|
|
302
|
+
* - the read reaches the call as an ARGUMENT, through the wrappers that do
|
|
303
|
+
* not change a value (`await`, parentheses, `as`, `satisfies`, `!`). A
|
|
304
|
+
* cast with no call around it therefore keeps the read as the terminal,
|
|
305
|
+
* so `(await res.json()) as Entry` is still read off the read itself;
|
|
306
|
+
* - the call's own result is BOUND — declared into a variable, returned, or
|
|
307
|
+
* assigned — so a call the source made for its side effect
|
|
308
|
+
* (`store(await res.json())`) states nothing about the payload;
|
|
309
|
+
* - that result is an OBJECT shape. A validator answering `boolean` or a
|
|
310
|
+
* serialiser answering `string` describes what the caller did with the
|
|
311
|
+
* body, not what the body is, and publishing it would be a
|
|
312
|
+
* concrete-but-wrong contract where the honest `any` of the read is
|
|
313
|
+
* merely unresolved.
|
|
314
|
+
*/
|
|
315
|
+
private statedPayloadAroundBodyRead;
|
|
316
|
+
/**
|
|
317
|
+
* The source keeps this call's result: it initializes a declaration, is
|
|
318
|
+
* returned, is assigned, or is an arrow's expression body. A result that is
|
|
319
|
+
* kept is one the source has a use for; a discarded one is a side effect.
|
|
320
|
+
*/
|
|
321
|
+
private callResultIsBound;
|
|
322
|
+
/**
|
|
323
|
+
* A shape a JSON body can be: an object, an array, or a union of them.
|
|
324
|
+
* Top types, primitives, `void` and callables are not.
|
|
325
|
+
*/
|
|
326
|
+
private isObjectShape;
|
|
327
|
+
/**
|
|
328
|
+
* Every use of a tracked name inside `expr` reads a member out of the
|
|
329
|
+
* tracked value, so the expression's type describes a PART of the payload.
|
|
330
|
+
* False when the expression uses no tracked name at all, so a caller can
|
|
331
|
+
* read it as "this is a projection" rather than "this is not a use".
|
|
332
|
+
*/
|
|
333
|
+
private usesNamesOnlyByProjection;
|
|
220
334
|
private extractBindingFromCall;
|
|
221
335
|
private extractBindingNames;
|
|
222
336
|
private getPrimaryBindingNode;
|