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,160 @@
1
+ /**
2
+ * Framework-machinery detection for the v2 capture path (carrick#371).
3
+ *
4
+ * A producer response anchor whose resolved type IS or CONTAINS HTTP transport
5
+ * machinery — a fetch/DOM `Response`/`Request`, a Node `http.ServerResponse`, a
6
+ * wrapper envelope `{ response: Response; error?: undefined } | { ...; error }`,
7
+ * or a wrapper function `(req: Request) => Promise<Response>` — must never be
8
+ * emitted as a comparable contract. Comparing it against the consumer's real
9
+ * payload manufactures a false compat mismatch (the bug this module fixes).
10
+ *
11
+ * This is the raw-`ts` seam mirror of `type-inferrer.ts`'s
12
+ * `typeIsOrContainsResponseMachinery` (ts-morph): the capture/ seam forbids
13
+ * importing a module from outside it, so the canonical indicator set and the
14
+ * detection shape are duplicated here in lockstep — the same pattern by which
15
+ * `BUILTIN_ANCHOR_SYMBOLS` mirrors `socket_io.rs`. Detection is STRUCTURAL and
16
+ * framework-agnostic: no framework NAME appears, only the shared HTTP-message
17
+ * member surface, gated by a lib / `node_modules` declaration origin so a user
18
+ * payload that merely shares a member name can never trip it.
19
+ */
20
+ import ts from 'typescript';
21
+ /**
22
+ * Strongly-discriminating member names of HTTP transport machinery. Kept
23
+ * identical to `MACHINERY_MEMBER_INDICATORS` in `type-inferrer.ts`. These are
24
+ * the names no JSON payload carries (`ok`, `redirected`, `bodyUsed`,
25
+ * `writeHead`, ...), so the origin gate + threshold never fire on real data.
26
+ * Exported so a drift-guard test (`machinery-indicator-mirror.test.ts`) asserts
27
+ * it stays equal to the `type-inferrer.ts` copy — nothing else enforces the
28
+ * lockstep, and if the two drift one path silently stops abstaining.
29
+ */
30
+ export const MACHINERY_MEMBER_INDICATORS = new Set([
31
+ // fetch / DOM Response & Request body-consumer surface
32
+ 'ok',
33
+ 'redirected',
34
+ 'bodyUsed',
35
+ 'arrayBuffer',
36
+ 'blob',
37
+ 'formData',
38
+ 'clone',
39
+ 'json',
40
+ 'statusText',
41
+ // Node http ServerResponse / reply-object surface
42
+ 'statusCode',
43
+ 'statusMessage',
44
+ 'setHeader',
45
+ 'getHeader',
46
+ 'removeHeader',
47
+ 'writeHead',
48
+ 'flushHeaders',
49
+ ]);
50
+ /** Machinery needs at least this many indicator members to be recognized. */
51
+ const MACHINERY_INDICATOR_THRESHOLD = 3;
52
+ /**
53
+ * True when `type`, resolved against `node`, IS or CONTAINS framework
54
+ * machinery.
55
+ *
56
+ * DETECTS, exactly:
57
+ * 1. the type itself is machinery (`isFrameworkMachinery`);
58
+ * 2. a union/intersection member is machinery (the envelope union);
59
+ * 3. a DIRECT property whose type is machinery (`{ response: Response }`),
60
+ * ONE level of descent only;
61
+ * 4. a CALL SIGNATURE whose AWAITED return type is machinery — the wrapper
62
+ * function `(req) => Promise<Response>` the Infer fallback resolves to.
63
+ * (Only the call return is awaited; property types below are not.)
64
+ *
65
+ * DELIBERATELY NOT DETECTED — stated so this comment never overstates the
66
+ * guarantee (mirrors the same list in `type-inferrer.ts`). Each is a
67
+ * non-regression (pre-existing verdict unchanged, never a new wrong one), a
68
+ * tracked follow-up:
69
+ * - machinery nested deeper than one property level;
70
+ * - a PROPERTY typed `Promise<Response>` (property types are not awaited /
71
+ * Promise-unwrapped before the check — only call-signature returns are);
72
+ * - an array element type: `Response[]` is not descended to its element;
73
+ * - `interface X extends Response` declared in USER source — the origin gate
74
+ * is lib/`node_modules` only, so a user-declared subtype reads as a real
75
+ * contract.
76
+ *
77
+ * The depth cap + origin gate keep a legitimate payload that merely references a
78
+ * machinery type far inside from over-abstaining.
79
+ */
80
+ export function typeIsOrContainsMachinery(checker, type, node) {
81
+ return isOrContains(checker, type, node, 0);
82
+ }
83
+ function isOrContains(checker, type, node, depth) {
84
+ if (isFrameworkMachinery(checker, type)) {
85
+ return true;
86
+ }
87
+ if (type.isUnion() || type.isIntersection()) {
88
+ return type.types.some((part) => isOrContains(checker, part, node, depth));
89
+ }
90
+ if (depth >= 1) {
91
+ return false;
92
+ }
93
+ // A wrapper function value: descend into its (awaited) return type.
94
+ for (const sig of checker.getSignaturesOfType(type, ts.SignatureKind.Call)) {
95
+ const returnType = sig.getReturnType();
96
+ const awaited = checker.getAwaitedType?.(returnType) ?? returnType;
97
+ if (isOrContains(checker, awaited, node, depth + 1)) {
98
+ return true;
99
+ }
100
+ }
101
+ // Direct properties: the `{ response: Response; error }` envelope shape.
102
+ for (const prop of checker.getPropertiesOfType(type)) {
103
+ const propType = checker.getTypeOfSymbolAtLocation(prop, node);
104
+ if (isOrContains(checker, propType, node, depth + 1)) {
105
+ return true;
106
+ }
107
+ }
108
+ return false;
109
+ }
110
+ /**
111
+ * True when `type` itself is an HTTP-machinery type: it structurally carries at
112
+ * least `MACHINERY_INDICATOR_THRESHOLD` of the indicator members AND its symbol
113
+ * is declared in a lib (`lib.*.d.ts`) or `node_modules` origin.
114
+ */
115
+ function isFrameworkMachinery(checker, type) {
116
+ let hits = 0;
117
+ for (const prop of checker.getPropertiesOfType(type)) {
118
+ if (MACHINERY_MEMBER_INDICATORS.has(prop.getName())) {
119
+ hits += 1;
120
+ if (hits >= MACHINERY_INDICATOR_THRESHOLD)
121
+ break;
122
+ }
123
+ }
124
+ if (hits < MACHINERY_INDICATOR_THRESHOLD) {
125
+ return false;
126
+ }
127
+ return symbolIsLibOrExternalOrigin(checker, type.getSymbol() ?? type.aliasSymbol);
128
+ }
129
+ /**
130
+ * True when the symbol is declared in a TS lib file (`lib.*.d.ts`) or under
131
+ * `node_modules`. Works on a bare checkout: the DOM `Response`/`Request`
132
+ * resolve from the bundled `lib.dom.d.ts` even with no installed dependencies.
133
+ */
134
+ function symbolIsLibOrExternalOrigin(checker, symbol) {
135
+ if (!symbol) {
136
+ return false;
137
+ }
138
+ const isExternalPath = (filePath) => {
139
+ const normalized = filePath.replace(/\\/g, '/');
140
+ return (normalized.includes('/node_modules/') ||
141
+ /\/lib\.[^/]*\.d\.ts$/.test(normalized));
142
+ };
143
+ for (const decl of symbol.getDeclarations() ?? []) {
144
+ if (isExternalPath(decl.getSourceFile().fileName)) {
145
+ return true;
146
+ }
147
+ }
148
+ if (symbol.flags & ts.SymbolFlags.Alias) {
149
+ try {
150
+ const aliased = checker.getAliasedSymbol(symbol);
151
+ if (aliased && aliased !== symbol) {
152
+ return symbolIsLibOrExternalOrigin(checker, aliased);
153
+ }
154
+ }
155
+ catch {
156
+ // Ignore errors when resolving the aliased symbol.
157
+ }
158
+ }
159
+ return false;
160
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * SymbolTracker-backed node-builder printing for anonymous inferred types
3
+ * (design doc, Capture step 5). This is WP1's risk concentration, built
4
+ * failure-visibility-first: any tracked symbol that is not plainly
5
+ * accessible from the destination file demotes the alias to the
6
+ * structural_fallback tier with a recorded reason -- the bundle never ships
7
+ * a silently wrong .d.ts.
8
+ *
9
+ * The four banked corrections from the 2026-07-02 derisk sweep, applied
10
+ * verbatim:
11
+ * 1. enclosingDeclaration anchors in the DESTINATION surface file (the
12
+ * entry's placeholder alias for this anchor), never undefined.
13
+ * 2. Demotion triggers on accessibility !== Accessible -- that includes
14
+ * CannotBeNamed (2), not just NotAccessible (1).
15
+ * 3. Detection drives off the trackSymbol callback; the
16
+ * reportInaccessibleUniqueSymbolError / reportInaccessibleThisError
17
+ * callbacks never fire for these shapes and are implemented only as
18
+ * belt-and-braces recorders.
19
+ * 4. The tracker is passed as the 5th argument of the (internal)
20
+ * typeToTypeNode signature: (type, enclosingDeclaration, flags,
21
+ * internalFlags, tracker).
22
+ */
23
+ import ts from 'typescript';
24
+ export interface NodeBuilderPrintResult {
25
+ /** Printed type text, present only when the print is trusted. */
26
+ text?: string;
27
+ /** Symbols the tracker flagged as not accessible from the destination. */
28
+ inaccessible: string[];
29
+ /** Failure description when text is absent. */
30
+ failure?: string;
31
+ }
32
+ /**
33
+ * Print `type` as a type node anchored at `destination` (a declaration inside
34
+ * the surface entry file). Returns untrusted-failure instead of text whenever
35
+ * any referenced symbol is not plainly accessible from the destination.
36
+ */
37
+ export declare function printTypeForDestination(program: ts.Program, type: ts.Type, destination: ts.Node): NodeBuilderPrintResult;
@@ -0,0 +1,123 @@
1
+ /**
2
+ * SymbolTracker-backed node-builder printing for anonymous inferred types
3
+ * (design doc, Capture step 5). This is WP1's risk concentration, built
4
+ * failure-visibility-first: any tracked symbol that is not plainly
5
+ * accessible from the destination file demotes the alias to the
6
+ * structural_fallback tier with a recorded reason -- the bundle never ships
7
+ * a silently wrong .d.ts.
8
+ *
9
+ * The four banked corrections from the 2026-07-02 derisk sweep, applied
10
+ * verbatim:
11
+ * 1. enclosingDeclaration anchors in the DESTINATION surface file (the
12
+ * entry's placeholder alias for this anchor), never undefined.
13
+ * 2. Demotion triggers on accessibility !== Accessible -- that includes
14
+ * CannotBeNamed (2), not just NotAccessible (1).
15
+ * 3. Detection drives off the trackSymbol callback; the
16
+ * reportInaccessibleUniqueSymbolError / reportInaccessibleThisError
17
+ * callbacks never fire for these shapes and are implemented only as
18
+ * belt-and-braces recorders.
19
+ * 4. The tracker is passed as the 5th argument of the (internal)
20
+ * typeToTypeNode signature: (type, enclosingDeclaration, flags,
21
+ * internalFlags, tracker).
22
+ */
23
+ import ts from 'typescript';
24
+ /**
25
+ * SymbolAccessibility is internal to the compiler (stable since TS 1.x):
26
+ * Accessible = 0, NotAccessible = 1, CannotBeNamed = 2. Mirrored here
27
+ * because the public .d.ts does not export it; correction 2 depends on the
28
+ * distinction between the two failure members.
29
+ */
30
+ const ACCESSIBLE = 0;
31
+ /**
32
+ * The node builder can only print out-of-scope symbols as `import("...")`
33
+ * type references when the tracker carries a moduleResolverHost -- without
34
+ * one it returns no node at all for external-package symbols
35
+ * (probe-verified on TS 5.8). Hand-rolling the host is a losing game (it
36
+ * needs a dozen emit-host internals); TS's own factory builds it from the
37
+ * program, which is exactly the "route through the emitter's machinery, not
38
+ * a bare typeToTypeNode" guidance from the derisk sweep.
39
+ */
40
+ function moduleResolverHostFor(program) {
41
+ const factory = ts.createModuleSpecifierResolutionHost;
42
+ if (!factory)
43
+ return undefined;
44
+ return factory(program, {
45
+ fileExists: ts.sys.fileExists,
46
+ readFile: ts.sys.readFile,
47
+ directoryExists: ts.sys.directoryExists,
48
+ getCurrentDirectory: () => program.getCurrentDirectory(),
49
+ useCaseSensitiveFileNames: () => ts.sys.useCaseSensitiveFileNames,
50
+ });
51
+ }
52
+ /**
53
+ * Print `type` as a type node anchored at `destination` (a declaration inside
54
+ * the surface entry file). Returns untrusted-failure instead of text whenever
55
+ * any referenced symbol is not plainly accessible from the destination.
56
+ */
57
+ export function printTypeForDestination(program, type, destination) {
58
+ const checker = program.getTypeChecker();
59
+ const inaccessible = [];
60
+ const seen = new Set();
61
+ const record = (symbol, meaning) => {
62
+ if (seen.has(symbol))
63
+ return;
64
+ seen.add(symbol);
65
+ const accessibility = checker.isSymbolAccessible(symbol, destination, // correction 1: destination file, never undefined
66
+ meaning ?? ts.SymbolFlags.Type,
67
+ /* shouldComputeAliasesToMakeVisible */ false).accessibility;
68
+ // Correction 2: anything other than Accessible demotes -- CannotBeNamed
69
+ // (2) is a distinct enum member that a `=== NotAccessible` check misses.
70
+ if (accessibility !== ACCESSIBLE) {
71
+ inaccessible.push(symbol.getName());
72
+ }
73
+ };
74
+ // Correction 3: trackSymbol is the callback that actually fires. The
75
+ // report* callbacks are dead for the verified failure shapes (unexported
76
+ // local interfaces, unique-symbol keys, local recursive aliases) but are
77
+ // kept as recorders in case other shapes reach them.
78
+ const tracker = {
79
+ moduleResolverHost: moduleResolverHostFor(program),
80
+ trackSymbol: (symbol, enclosing, meaning) => {
81
+ void enclosing;
82
+ record(symbol, meaning);
83
+ // Returning false tells the builder no error was reported here; the
84
+ // demotion decision is ours, made after the walk completes.
85
+ return false;
86
+ },
87
+ reportInaccessibleThisError: () => {
88
+ inaccessible.push('this');
89
+ },
90
+ reportInaccessibleUniqueSymbolError: () => {
91
+ inaccessible.push('(unique symbol)');
92
+ },
93
+ reportPrivateInBaseOfClassExpression: (propertyName) => {
94
+ inaccessible.push(propertyName);
95
+ },
96
+ };
97
+ let node;
98
+ try {
99
+ node = checker.typeToTypeNode(type, destination, ts.NodeBuilderFlags.NoTruncation |
100
+ ts.NodeBuilderFlags.UseStructuralFallback |
101
+ ts.NodeBuilderFlags.InTypeAlias,
102
+ /* internalFlags */ undefined, tracker // correction 4: 5th argument
103
+ );
104
+ }
105
+ catch (err) {
106
+ return {
107
+ inaccessible,
108
+ failure: `node builder threw: ${err instanceof Error ? err.message : String(err)}`,
109
+ };
110
+ }
111
+ if (!node) {
112
+ return { inaccessible, failure: 'node builder returned no type node' };
113
+ }
114
+ if (inaccessible.length > 0) {
115
+ return {
116
+ inaccessible,
117
+ failure: `symbols not accessible from the surface entry: ${[...new Set(inaccessible)].join(', ')}`,
118
+ };
119
+ }
120
+ const printer = ts.createPrinter({ removeComments: true });
121
+ const text = printer.printNode(ts.EmitHint.Unspecified, node, destination.getSourceFile());
122
+ return { text, inaccessible };
123
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Post-emit specifier rewrite pass (design doc, Capture step 3 -- REQUIRED
3
+ * component). Declaration emit ships tsconfig-`paths`-mapped specifiers
4
+ * verbatim (`@app/models/item` dangles after relocation into the stub), and
5
+ * node-builder-printed import types can carry absolute source paths. Both
6
+ * are mapped onto tree-relative specifiers here, at capture time, in the
7
+ * producer's own resolution context -- `paths` cannot be fixed at check time
8
+ * (program-global, and namespaces collide across stubs).
9
+ *
10
+ * Specifiers that map outside the emitted tree are left untouched: the
11
+ * per-alias self-check classifies them as dangling internals with a recorded
12
+ * reason, which is the honest outcome.
13
+ */
14
+ import ts from 'typescript';
15
+ import { type PathsPattern } from './specifiers.js';
16
+ export interface RewriteArgs {
17
+ /** Absolute path of the stub's types/ directory. */
18
+ typesDir: string;
19
+ /** Tree-relative emitted file paths (as recorded in emitted_files, without
20
+ * the leading "types/"). */
21
+ files: string[];
22
+ /** The producer repo's parsed compiler options. */
23
+ options: ts.CompilerOptions;
24
+ /** Absolute path of the tsconfig the options came from. */
25
+ configPath: string;
26
+ /** Effective rootDir the emit ran with (tree layout mirrors it). */
27
+ entryDir: string;
28
+ }
29
+ export declare function parsePathsPatterns(options: ts.CompilerOptions, configPath: string): PathsPattern[];
30
+ /**
31
+ * Rewrite paths-mapped and absolute-internal specifiers in every emitted
32
+ * file. Returns the number of specifiers rewritten.
33
+ */
34
+ export declare function rewriteEmittedSpecifiers(args: RewriteArgs): number;
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Post-emit specifier rewrite pass (design doc, Capture step 3 -- REQUIRED
3
+ * component). Declaration emit ships tsconfig-`paths`-mapped specifiers
4
+ * verbatim (`@app/models/item` dangles after relocation into the stub), and
5
+ * node-builder-printed import types can carry absolute source paths. Both
6
+ * are mapped onto tree-relative specifiers here, at capture time, in the
7
+ * producer's own resolution context -- `paths` cannot be fixed at check time
8
+ * (program-global, and namespaces collide across stubs).
9
+ *
10
+ * Specifiers that map outside the emitted tree are left untouched: the
11
+ * per-alias self-check classifies them as dangling internals with a recorded
12
+ * reason, which is the honest outcome.
13
+ */
14
+ import * as fs from 'node:fs';
15
+ import * as path from 'node:path';
16
+ import { isRelative, matchPathsPattern, rewriteSpecifiers, } from './specifiers.js';
17
+ export function parsePathsPatterns(options, configPath) {
18
+ const paths = options.paths;
19
+ if (!paths)
20
+ return [];
21
+ // Since TS 4.1 `paths` without `baseUrl` resolves relative to the config
22
+ // file; the parser records that base internally as pathsBasePath.
23
+ const base = path.resolve(options.baseUrl ??
24
+ (options.pathsBasePath || path.dirname(configPath)));
25
+ const patterns = [];
26
+ for (const [pattern, targets] of Object.entries(paths)) {
27
+ const starIdx = pattern.indexOf('*');
28
+ patterns.push({
29
+ pattern,
30
+ prefix: starIdx === -1 ? pattern : pattern.slice(0, starIdx),
31
+ suffix: starIdx === -1 ? undefined : pattern.slice(starIdx + 1),
32
+ targets: targets.map((t) => path.resolve(base, t)),
33
+ });
34
+ }
35
+ return patterns;
36
+ }
37
+ /** Map an absolute source path to its tree-relative emitted file, if any. */
38
+ function treeFileFor(absSource, entryDir, emitted) {
39
+ const noExt = absSource.replace(/\.(d\.ts|ts|tsx|mts|cts)$/, '');
40
+ const rel = path.relative(entryDir, noExt).split(path.sep).join('/');
41
+ if (rel.startsWith('..'))
42
+ return undefined;
43
+ for (const candidate of [`${rel}.d.ts`, `${rel}/index.d.ts`]) {
44
+ if (emitted.has(candidate))
45
+ return candidate;
46
+ }
47
+ return undefined;
48
+ }
49
+ /** Tree-relative .d.ts path -> extensionless specifier from `fromFile`. */
50
+ function relativeSpecifier(fromFile, toFile) {
51
+ const fromDir = path.posix.dirname(fromFile);
52
+ let rel = path.posix.relative(fromDir, toFile.replace(/\.d\.ts$/, ''));
53
+ if (!rel.startsWith('.'))
54
+ rel = `./${rel}`;
55
+ return rel;
56
+ }
57
+ /**
58
+ * Rewrite paths-mapped and absolute-internal specifiers in every emitted
59
+ * file. Returns the number of specifiers rewritten.
60
+ */
61
+ export function rewriteEmittedSpecifiers(args) {
62
+ const patterns = parsePathsPatterns(args.options, args.configPath);
63
+ const emitted = new Set(args.files);
64
+ let total = 0;
65
+ for (const file of args.files) {
66
+ const absFile = path.join(args.typesDir, file);
67
+ const text = fs.readFileSync(absFile, 'utf8');
68
+ const { text: rewritten, rewrites } = rewriteSpecifiers(text, (spec) => {
69
+ // Absolute internal paths (node-builder import types).
70
+ if (spec.startsWith('/')) {
71
+ const target = treeFileFor(spec, args.entryDir, emitted);
72
+ return target ? relativeSpecifier(file, target) : undefined;
73
+ }
74
+ if (isRelative(spec))
75
+ return undefined;
76
+ // tsconfig-paths patterns, first matching target that exists in-tree
77
+ // (mirrors the resolver's declaration-order semantics).
78
+ for (const pattern of patterns) {
79
+ const star = matchPathsPattern(spec, pattern);
80
+ if (star === undefined)
81
+ continue;
82
+ for (const targetTemplate of pattern.targets) {
83
+ const absTarget = targetTemplate.replace('*', star);
84
+ const target = treeFileFor(absTarget, args.entryDir, emitted);
85
+ if (target)
86
+ return relativeSpecifier(file, target);
87
+ }
88
+ // Matched a pattern but no in-tree target: leave it for the
89
+ // self-check to classify (never guess).
90
+ return undefined;
91
+ }
92
+ return undefined;
93
+ });
94
+ if (rewrites > 0) {
95
+ fs.writeFileSync(absFile, rewritten);
96
+ total += rewrites;
97
+ }
98
+ }
99
+ return total;
100
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Capture-time self-check (design doc, Capture step 8, amendment 1), with
3
+ * per-alias closure attribution.
4
+ *
5
+ * The stub tree is typechecked standalone with `skipLibCheck: false` --
6
+ * spike-verified as load-bearing: the tree is entirely .d.ts and skipLibCheck
7
+ * skips declaration files wholesale, making the gate vacuous. When the
8
+ * source repo has node_modules, resolution is pointed at it via a temporary
9
+ * node_modules symlink inside the stub, so externals resolve exactly as they
10
+ * will at check time against installed pins.
11
+ *
12
+ * Classification per alias (three-way, keyed on diagnostics, never on
13
+ * printed type text):
14
+ * - ok: resolves to a concrete type.
15
+ * - allowlisted_external: resolution failed only through external
16
+ * specifiers pinned in the stub's dependencies, on a bare checkout. The
17
+ * alias KEEPS its serialization tier; the check-phase probe gates
18
+ * (any/unknown/never, both sides) are the backstop.
19
+ * - decayed_internal: a dangling internal specifier, an unpinned external,
20
+ * or a top-type resolution with no allowlisted explanation.
21
+ *
22
+ * Attribution is per-alias closure: failed specifiers are blamed on an alias
23
+ * only if they occur in a file reachable from that alias's surface statement
24
+ * (import-type seeds, then BFS over relative imports). The spike's
25
+ * file-granularity shortcut is gone.
26
+ */
27
+ import type { CaptureAliasRecord } from './api.js';
28
+ import type { ResolvedAnchor } from './anchors.js';
29
+ export interface SelfCheckArgs {
30
+ stubDir: string;
31
+ surfaceAbsPath: string;
32
+ resolved: ResolvedAnchor[];
33
+ pinned: Record<string, string>;
34
+ bareCheckout: boolean;
35
+ /** Producer repo root; its node_modules (if any) backs resolution. */
36
+ repoRoot: string;
37
+ }
38
+ export declare function selfCheckStub(args: SelfCheckArgs): CaptureAliasRecord[];