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.
Files changed (68) hide show
  1. package/README.md +31 -11
  2. package/bin/carrick.mjs +89 -0
  3. package/dist/auth/credentials.d.ts +9 -0
  4. package/dist/auth/credentials.js +13 -2
  5. package/dist/auth/credentials.js.map +1 -1
  6. package/dist/auth/read.d.ts +4 -4
  7. package/dist/global-install.d.ts +183 -0
  8. package/dist/global-install.js +393 -0
  9. package/dist/global-install.js.map +1 -0
  10. package/dist/hook/post-edit.js +10 -0
  11. package/dist/hook/post-edit.js.map +1 -1
  12. package/dist/hook/session-start.js +34 -0
  13. package/dist/hook/session-start.js.map +1 -1
  14. package/dist/hook/stop.js +18 -4
  15. package/dist/hook/stop.js.map +1 -1
  16. package/dist/hook/user-prompt.js +16 -4
  17. package/dist/hook/user-prompt.js.map +1 -1
  18. package/dist/init/doctor.d.ts +61 -0
  19. package/dist/init/doctor.js +160 -1
  20. package/dist/init/doctor.js.map +1 -1
  21. package/dist/init/hosted.d.ts +30 -5
  22. package/dist/init/hosted.js +75 -9
  23. package/dist/init/hosted.js.map +1 -1
  24. package/dist/init/mcp.d.ts +45 -30
  25. package/dist/init/mcp.js +47 -88
  26. package/dist/init/mcp.js.map +1 -1
  27. package/dist/init/outdated.d.ts +47 -0
  28. package/dist/init/outdated.js +104 -0
  29. package/dist/init/outdated.js.map +1 -1
  30. package/dist/init/output.d.ts +43 -6
  31. package/dist/init/output.js +90 -18
  32. package/dist/init/output.js.map +1 -1
  33. package/dist/init/projects.d.ts +2 -2
  34. package/dist/init/run.d.ts +89 -25
  35. package/dist/init/run.js +292 -58
  36. package/dist/init/run.js.map +1 -1
  37. package/dist/render.d.ts +16 -0
  38. package/dist/render.js +57 -6
  39. package/dist/render.js.map +1 -1
  40. package/dist/scan.d.ts +26 -0
  41. package/dist/scan.js +92 -0
  42. package/dist/scan.js.map +1 -1
  43. package/dist/update-check.d.ts +1 -0
  44. package/dist/update-check.js +25 -0
  45. package/dist/update-check.js.map +1 -0
  46. package/dist/update.d.ts +128 -0
  47. package/dist/update.js +398 -0
  48. package/dist/update.js.map +1 -0
  49. package/package.json +8 -7
  50. package/sidecar/dist/src/capture/anchors.js +69 -8
  51. package/sidecar/dist/src/capture/api.d.ts +27 -2
  52. package/sidecar/dist/src/capture/check-classify.js +31 -2
  53. package/sidecar/dist/src/capture/check-fields.d.ts +29 -0
  54. package/sidecar/dist/src/capture/check-fields.js +58 -6
  55. package/sidecar/dist/src/capture/check.js +5 -0
  56. package/sidecar/dist/src/capture/deep-walk.js +4 -1
  57. package/sidecar/dist/src/capture/index.js +6 -1
  58. package/sidecar/dist/src/capture/node-builder.d.ts +28 -1
  59. package/sidecar/dist/src/capture/node-builder.js +89 -3
  60. package/sidecar/dist/src/capture/self-check.d.ts +6 -1
  61. package/sidecar/dist/src/capture/self-check.js +65 -11
  62. package/sidecar/dist/src/capture/unresolved.d.ts +7 -0
  63. package/sidecar/dist/src/capture/unresolved.js +1 -1
  64. package/sidecar/dist/src/type-inferrer.d.ts +118 -4
  65. package/sidecar/dist/src/type-inferrer.js +433 -15
  66. package/sidecar/dist/src/validators.d.ts +40 -40
  67. package/sidecar/dist/src/validators.js +6 -1
  68. 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
- // Failed module specifiers, split external-pinned vs internal, per FILE.
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 failuresFor = (fileName) => {
79
- let entry = failuresByFile.get(fileName);
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 = { externalPinned: new Set(), internal: new Set() };
82
- failuresByFile.set(fileName, entry);
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 bucket = failuresFor(path.resolve(d.file.fileName));
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
- for (const file of closure) {
199
- const failures = ctx.failuresByFile.get(file);
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 send `node` is an argument of, looking through the wrappers that do
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 the parent call is not
212
- * a send or `node` is its callee.
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 sendReceivingArgument;
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;