carrick 0.3.105 → 0.3.107

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 (40) hide show
  1. package/dist/hook/refresh.js +8 -1
  2. package/dist/hook/refresh.js.map +1 -1
  3. package/package.json +6 -6
  4. package/plugin/.claude-plugin/plugin.json +1 -1
  5. package/sidecar/dist/src/capture/anchors.d.ts +22 -2
  6. package/sidecar/dist/src/capture/anchors.js +178 -34
  7. package/sidecar/dist/src/capture/api.d.ts +42 -1
  8. package/sidecar/dist/src/capture/check-classify.d.ts +9 -1
  9. package/sidecar/dist/src/capture/check-classify.js +15 -0
  10. package/sidecar/dist/src/capture/check-poison.js +4 -14
  11. package/sidecar/dist/src/capture/check.js +13 -1
  12. package/sidecar/dist/src/capture/index.js +55 -29
  13. package/sidecar/dist/src/capture/installed-package.d.ts +2 -0
  14. package/sidecar/dist/src/capture/installed-package.js +2 -1
  15. package/sidecar/dist/src/capture/outside-root.d.ts +19 -0
  16. package/sidecar/dist/src/capture/outside-root.js +39 -2
  17. package/sidecar/dist/src/capture/self-check.js +12 -13
  18. package/sidecar/dist/src/capture/service-config.d.ts +2 -0
  19. package/sidecar/dist/src/capture/service-config.js +1 -1
  20. package/sidecar/dist/src/capture/specifiers.d.ts +14 -0
  21. package/sidecar/dist/src/capture/specifiers.js +25 -0
  22. package/sidecar/dist/src/failure-path.d.ts +34 -3
  23. package/sidecar/dist/src/failure-path.js +59 -35
  24. package/sidecar/dist/src/function-line-index.d.ts +30 -0
  25. package/sidecar/dist/src/function-line-index.js +162 -0
  26. package/sidecar/dist/src/index.d.ts +5 -0
  27. package/sidecar/dist/src/index.js +29 -5
  28. package/sidecar/dist/src/line-index.d.ts +9 -0
  29. package/sidecar/dist/src/line-index.js +26 -0
  30. package/sidecar/dist/src/printed-names.d.ts +43 -0
  31. package/sidecar/dist/src/printed-names.js +186 -0
  32. package/sidecar/dist/src/progress.d.ts +22 -0
  33. package/sidecar/dist/src/progress.js +31 -0
  34. package/sidecar/dist/src/retype.js +58 -128
  35. package/sidecar/dist/src/type-inferrer.d.ts +124 -13
  36. package/sidecar/dist/src/type-inferrer.js +531 -146
  37. package/sidecar/dist/src/type-structural-expander.js +10 -1
  38. package/sidecar/dist/src/types.d.ts +37 -6
  39. package/sidecar/dist/src/validators.d.ts +76 -0
  40. package/sidecar/dist/src/validators.js +8 -0
@@ -132,7 +132,14 @@ export function refreshInBackground(root, status, options = {}) {
132
132
  return null;
133
133
  if (!claimRefresh(marker, now, cooldown))
134
134
  return null;
135
- (options.start ?? startDetached)(binary(env), ["refresh", "--workspace", root], env);
135
+ // Nobody started this refresh and nobody is reading it, so it does not keep
136
+ // the machine awake: that hold is for a scan a person is waiting on, and it
137
+ // comes with a line saying so that this one has nowhere to print
138
+ // (carrick#1889).
139
+ (options.start ?? startDetached)(binary(env), ["refresh", "--workspace", root], {
140
+ ...env,
141
+ CARRICK_NO_KEEP_AWAKE: "1",
142
+ });
136
143
  return "Carrick: a repo here is connected with no hosted index yet, so a refresh is running in the background. Its rows arrive in the next session.";
137
144
  }
138
145
  //# sourceMappingURL=refresh.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"refresh.js","sourceRoot":"","sources":["../../src/hook/refresh.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"refresh.js","sourceRoot":"","sources":["../../src/hook/refresh.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "carrick",
3
- "version": "0.3.105",
3
+ "version": "0.3.107",
4
4
  "description": "Maps your entire TypeScript codebase across services and repositories, giving AI agents full context on existing types, routes, and function behaviours over MCP before they write duplicate or breaking code.",
5
5
  "keywords": [
6
6
  "typescript",
@@ -58,11 +58,11 @@
58
58
  "zod": "^3.23.0"
59
59
  },
60
60
  "optionalDependencies": {
61
- "@carrick-tools/cli-darwin-arm64": "0.3.105",
62
- "@carrick-tools/cli-darwin-x64": "0.3.105",
63
- "@carrick-tools/cli-linux-arm64": "0.3.105",
64
- "@carrick-tools/cli-linux-x64": "0.3.105",
65
- "@carrick-tools/cli-win32-x64": "0.3.105"
61
+ "@carrick-tools/cli-darwin-arm64": "0.3.107",
62
+ "@carrick-tools/cli-darwin-x64": "0.3.107",
63
+ "@carrick-tools/cli-linux-arm64": "0.3.107",
64
+ "@carrick-tools/cli-linux-x64": "0.3.107",
65
+ "@carrick-tools/cli-win32-x64": "0.3.107"
66
66
  },
67
67
  "devDependencies": {
68
68
  "@types/node": "^24.13.3",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "carrick",
3
3
  "description": "Claude Code plugin for Carrick, which indexes TypeScript codebases across service and repository boundaries. After each edit it adds the file's routes, calls, and cross-service type mismatches to the session, and it registers Carrick's language server. It pairs with the Carrick MCP server, which lets agents search functions by intent rather than name.",
4
- "version": "0.3.105",
4
+ "version": "0.3.107",
5
5
  "author": {
6
6
  "name": "Carrick",
7
7
  "email": "hello@carrick.tools"
@@ -56,8 +56,21 @@ export interface ResolvedAnchor {
56
56
  */
57
57
  namesUnemittedModule?: true;
58
58
  }
59
- /** Repo-root-relative source file -> extensionless specifier from entryDir. */
60
- export declare function entryRelativeSpecifier(entryDir: string, repoRoot: string, sourceFile: string): string;
59
+ /**
60
+ * Repo-root-relative source file -> the specifier the surface entry imports
61
+ * it by: its path from `entryDir`, in the form the entry resolves.
62
+ *
63
+ * With the extension removed (`./a`), where `resolveFromEntry` says the entry
64
+ * resolves that to the module. It does not always (carrick#1911): under
65
+ * `module` `node16`..`nodenext` an entry in a `"type": "module"` package is
66
+ * an ES module, which must name the file the module compiles to, and a `.mts`
67
+ * or `.cts` module has no extensionless form under any setting. There, and
68
+ * with no resolver to ask, the specifier is that output name: `./a.js` for
69
+ * `.ts` and `.tsx`, `./a.mjs` for `.mts`, `./a.cjs` for `.cts`, which every
70
+ * module setting maps back to the source and, in the stub, to its
71
+ * declaration.
72
+ */
73
+ export declare function entryRelativeSpecifier(entryDir: string, repoRoot: string, sourceFile: string, resolveFromEntry?: (specifier: string) => string | undefined): string;
61
74
  /**
62
75
  * Resolve one anchor against the analysis program. `placeholder` is the
63
76
  * anchor's own placeholder alias declaration in the entry file -- the
@@ -73,6 +86,13 @@ export declare function resolveAnchor(program: ts.Program, request: CaptureAncho
73
86
  * these resolves through that module instead of dangling in the entry.
74
87
  */
75
88
  siblingSymbolSpecs?: Map<string, string>;
89
+ /**
90
+ * The file a module specifier resolves to from the surface entry, under
91
+ * the analysis program's own resolution (carrick#1789). A literal's name
92
+ * is imported through a package specifier only when the entry reaches the
93
+ * same module by it.
94
+ */
95
+ resolveFromEntry?: (specifier: string) => string | undefined;
76
96
  }): ResolvedAnchor;
77
97
  /**
78
98
  * Locate the target node: tightest expression covering the byte span when
@@ -8,16 +8,35 @@ import ts from 'typescript';
8
8
  import * as path from 'node:path';
9
9
  import { printTypeForDestination, substituteUndeclaredNames, undeclaredNamesIn, } from './node-builder.js';
10
10
  import { typeIsOrContainsMachinery } from './machinery.js';
11
+ import { installedPackageSpecifier } from './installed-package.js';
12
+ import { realPath } from './service-config.js';
11
13
  import { unresolvedAtAnchor, unresolvedSpecifiersReachableFrom, } from './unresolved.js';
12
- /** Repo-root-relative source file -> extensionless specifier from entryDir. */
13
- export function entryRelativeSpecifier(entryDir, repoRoot, sourceFile) {
14
- const target = path
15
- .join(repoRoot, sourceFile)
16
- .replace(/\.(ts|tsx|mts|cts)$/, '');
17
- let rel = path.relative(entryDir, target).split(path.sep).join('/');
18
- if (!rel.startsWith('.'))
19
- rel = `./${rel}`;
20
- return rel;
14
+ /**
15
+ * Repo-root-relative source file -> the specifier the surface entry imports
16
+ * it by: its path from `entryDir`, in the form the entry resolves.
17
+ *
18
+ * With the extension removed (`./a`), where `resolveFromEntry` says the entry
19
+ * resolves that to the module. It does not always (carrick#1911): under
20
+ * `module` `node16`..`nodenext` an entry in a `"type": "module"` package is
21
+ * an ES module, which must name the file the module compiles to, and a `.mts`
22
+ * or `.cts` module has no extensionless form under any setting. There, and
23
+ * with no resolver to ask, the specifier is that output name: `./a.js` for
24
+ * `.ts` and `.tsx`, `./a.mjs` for `.mts`, `./a.cjs` for `.cts`, which every
25
+ * module setting maps back to the source and, in the stub, to its
26
+ * declaration.
27
+ */
28
+ export function entryRelativeSpecifier(entryDir, repoRoot, sourceFile, resolveFromEntry) {
29
+ const target = path.join(repoRoot, sourceFile);
30
+ const from = (file) => {
31
+ const rel = path.relative(entryDir, file).split(path.sep).join('/');
32
+ return rel.startsWith('.') ? rel : `./${rel}`;
33
+ };
34
+ const bare = from(target.replace(/\.(ts|tsx|mts|cts)$/, ''));
35
+ const resolved = resolveFromEntry?.(bare);
36
+ if (resolved !== undefined && realPath(resolved) === realPath(target))
37
+ return bare;
38
+ const kind = /(?:\.d)?\.([cm]?)[jt]sx?$/.exec(target);
39
+ return kind ? from(`${target.slice(0, kind.index)}.${kind[1]}js`) : bare;
21
40
  }
22
41
  function moduleExport(checker, sourceFile, name) {
23
42
  const moduleSymbol = checker.getSymbolAtLocation(sourceFile);
@@ -56,10 +75,13 @@ export function resolveAnchor(program, request, args) {
56
75
  // sibling symbol anchor imports is resolved by that import.
57
76
  // carrick#1774: the text names types as they read where it was printed,
58
77
  // so a name its source file means by a repo module's export is imported
59
- // from that module (a string-union alias printed bare read `any`).
78
+ // from that module (a string-union alias printed bare read `any`), and
79
+ // (carrick#1789) a name it imports from a package is imported from that
80
+ // package. carrick#1836: a name the source file cannot see is imported
81
+ // from the module the inference recorded it was printed for.
60
82
  const scoped = siblingSpec
61
83
  ? undefined
62
- : qualifyNamesFromSource(text, program, request.source_file, args);
84
+ : qualifyNamesFromSource(text, program, request.source_file, request.printed_names, args);
63
85
  const scopedText = scoped ?? text;
64
86
  // carrick#1377: rewrite what nothing declares to `unknown` in place, so
65
87
  // one member typed by a module the checkout does not have stops taking
@@ -95,7 +117,7 @@ export function resolveAnchor(program, request, args) {
95
117
  if (!sourceFile) {
96
118
  return demote(`source file not in program: ${request.source_file}`);
97
119
  }
98
- const spec = entryRelativeSpecifier(args.entryDir, args.repoRoot, request.source_file);
120
+ const spec = entryRelativeSpecifier(args.entryDir, args.repoRoot, request.source_file, args.resolveFromEntry);
99
121
  if (request.kind === 'symbol') {
100
122
  const exported = moduleExport(checker, sourceFile, request.symbol_name);
101
123
  if (!exported) {
@@ -390,15 +412,25 @@ function substituteUndeclaredNamesInText(text, program, destination) {
390
412
  * member that reads `any`) or a global of the same name (`Notification`).
391
413
  * Each reference whose name the source resolves to a type that a module
392
414
  * inside the repo exports becomes `import('<module>').<export>`, with its
393
- * qualifier and type arguments kept. Anything else is left as written: a
394
- * global, a name only a function body declares, an unexported local, and a
395
- * type declared outside the repo or under `node_modules`, which the stub does
396
- * not ship.
415
+ * qualifier and type arguments kept. A name the source imports from an
416
+ * installed package becomes `import('<package specifier>').<export>`
417
+ * (carrick#1789): the stub ships no `node_modules`, but it pins the packages
418
+ * its surface imports, and the check phase installs them. Anything else is
419
+ * left as written: a global, a name only a function body declares, an
420
+ * unexported local, and a type declared outside the repo that no package
421
+ * import reaches.
422
+ *
423
+ * carrick#1836: a name the source file cannot see at all was printed without
424
+ * its scope (the inferrer's structural printer writes names bare). When the
425
+ * inference recorded one declaration for it (`printed`), the name is imported
426
+ * from that declaration's module, under the same repo rule. A name recorded
427
+ * for two declarations is left as written: the text no longer says which one
428
+ * a position meant.
397
429
  *
398
430
  * Returns the rewritten text, or undefined when nothing was rewritten, so a
399
431
  * text with no such name stays byte-identical.
400
432
  */
401
- function qualifyNamesFromSource(text, program, sourceFileRel, args) {
433
+ function qualifyNamesFromSource(text, program, sourceFileRel, printed, args) {
402
434
  if (!sourceFileRel)
403
435
  return undefined;
404
436
  const source = program.getSourceFile(path.join(args.repoRoot, sourceFileRel));
@@ -415,6 +447,14 @@ function qualifyNamesFromSource(text, program, sourceFileRel, args) {
415
447
  ts.forEachChild(node, collect);
416
448
  };
417
449
  collect(parsed);
450
+ const specOf = (file) => {
451
+ const rel = path.relative(args.repoRoot, file.fileName);
452
+ if (!rel || rel.startsWith('..') || path.isAbsolute(rel))
453
+ return undefined;
454
+ if (rel.split(path.sep).includes('node_modules'))
455
+ return undefined;
456
+ return entryRelativeSpecifier(args.entryDir, args.repoRoot, rel.split(path.sep).join('/'), args.resolveFromEntry);
457
+ };
418
458
  const imports = new Map();
419
459
  const importFor = (name) => {
420
460
  if (imports.has(name))
@@ -423,12 +463,8 @@ function qualifyNamesFromSource(text, program, sourceFileRel, args) {
423
463
  const atSource = checker.resolveName(name, source, meaning, false);
424
464
  const target = atSource && resolveSymbolAliases(checker, atSource);
425
465
  const declaringFile = target?.declarations?.[0]?.getSourceFile();
426
- const rel = declaringFile && path.relative(args.repoRoot, declaringFile.fileName);
427
- if (target &&
428
- declaringFile &&
429
- rel &&
430
- !rel.startsWith('..') &&
431
- !rel.split(path.sep).includes('node_modules')) {
466
+ const spec = declaringFile && specOf(declaringFile);
467
+ if (target && declaringFile && spec) {
432
468
  const moduleSymbol = checker.getSymbolAtLocation(declaringFile);
433
469
  const exported = moduleSymbol
434
470
  ? checker
@@ -436,28 +472,46 @@ function qualifyNamesFromSource(text, program, sourceFileRel, args) {
436
472
  .filter((candidate) => resolveSymbolAliases(checker, candidate) === target)
437
473
  : [];
438
474
  const chosen = exported.find((candidate) => candidate.getName() === name) ?? exported[0];
439
- if (chosen) {
440
- found = {
441
- spec: entryRelativeSpecifier(args.entryDir, args.repoRoot, rel.split(path.sep).join('/')),
442
- exportName: chosen.getName(),
443
- };
444
- }
475
+ if (chosen)
476
+ found = { spec, exportPath: [chosen.getName()] };
445
477
  }
478
+ if (!found && atSource)
479
+ found = packageImportOf(program, atSource, args.resolveFromEntry);
480
+ if (!found && !atSource)
481
+ found = printedImportOf(program, name, printed, specOf);
446
482
  imports.set(name, found);
447
483
  return found;
448
484
  };
449
485
  const leftmost = (name) => ts.isIdentifier(name) ? name : leftmost(name.left);
450
- const renameLeftmost = (name, to) => ts.isIdentifier(name)
451
- ? ts.factory.createIdentifier(to)
452
- : ts.factory.createQualifiedName(renameLeftmost(name.left, to), name.right);
486
+ const entityName = (parts) => parts
487
+ .slice(1)
488
+ .reduce((left, part) => ts.factory.createQualifiedName(left, part), ts.factory.createIdentifier(parts[0]));
489
+ const replaceLeftmost = (name, to) => ts.isIdentifier(name)
490
+ ? entityName(to)
491
+ : ts.factory.createQualifiedName(replaceLeftmost(name.left, to), name.right);
492
+ const dropLeftmost = (name) => ts.isIdentifier(name.left)
493
+ ? name.right
494
+ : ts.factory.createQualifiedName(dropLeftmost(name.left), name.right);
495
+ // What follows `import('<spec>')`: the export path, then the reference's
496
+ // own qualifier. Through a namespace import the namespace's name goes, and
497
+ // the next name must be something the package exports.
498
+ const qualifierFor = (typeName, target) => {
499
+ if ('exportPath' in target)
500
+ return replaceLeftmost(typeName, target.exportPath);
501
+ if (ts.isIdentifier(typeName))
502
+ return undefined;
503
+ const qualifier = dropLeftmost(typeName);
504
+ return target.namespaceExports.has(leftmost(qualifier).text) ? qualifier : undefined;
505
+ };
453
506
  let rewrites = 0;
454
507
  const rewrite = (node) => {
455
508
  if (ts.isTypeReferenceNode(node)) {
456
509
  const name = leftmost(node.typeName).text;
457
510
  const target = typeParameters.has(name) ? undefined : importFor(name);
458
- if (target) {
511
+ const qualifier = target && qualifierFor(node.typeName, target);
512
+ if (target && qualifier) {
459
513
  rewrites += 1;
460
- return ts.factory.createImportTypeNode(ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral(target.spec)), undefined, renameLeftmost(node.typeName, target.exportName), node.typeArguments?.map((argument) => rewrite(argument)), false);
514
+ return ts.factory.createImportTypeNode(ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral(target.spec)), undefined, qualifier, node.typeArguments?.map((argument) => rewrite(argument)), false);
461
515
  }
462
516
  }
463
517
  return ts.visitEachChild(node, rewrite, /* context */ undefined);
@@ -469,6 +523,96 @@ function qualifyNamesFromSource(text, program, sourceFileRel, args) {
469
523
  .createPrinter({ removeComments: true })
470
524
  .printNode(ts.EmitHint.Unspecified, rewritten, parsed.getSourceFile());
471
525
  }
526
+ /**
527
+ * carrick#1789: how the surface entry imports `local`, a name the source file
528
+ * binds with an import from an installed package, or undefined.
529
+ *
530
+ * The entry names the module the source's import resolves to by its file
531
+ * path, as the node builder names a library type it cannot reach by a bare
532
+ * specifier; the post-emit rewrite turns that path into the package's bare
533
+ * specifier and pins the installed version (`installedPackageSpecifier`). A
534
+ * bare specifier in the entry itself would be resolved from the entry: on a
535
+ * Deno service that goes through the graph's virtual `node_modules`, and the
536
+ * emitter then reads the package there, cannot name its types from the
537
+ * service's own modules (TS2742), and skips their declarations.
538
+ *
539
+ * Kept only when the module lies inside an installed package and the bare
540
+ * specifier that package gives resolves from the entry to the same file. So
541
+ * a tsconfig path alias to the repo's own module, a workspace package linked
542
+ * from the repo (no installed copy to pin), and an import of another
543
+ * installed copy than the entry reaches all stay as written.
544
+ */
545
+ function packageImportOf(program, local, resolveFromEntry) {
546
+ if (!resolveFromEntry || !(local.flags & ts.SymbolFlags.Alias))
547
+ return undefined;
548
+ const declaration = local.declarations?.[0];
549
+ let statement;
550
+ let exportName;
551
+ if (declaration && ts.isImportSpecifier(declaration)) {
552
+ // `import { "a-b" as name }` has no qualifier an import type can write.
553
+ const exported = declaration.propertyName ?? declaration.name;
554
+ if (!ts.isIdentifier(exported))
555
+ return undefined;
556
+ statement = declaration.parent.parent.parent;
557
+ exportName = exported.text;
558
+ }
559
+ else if (declaration && ts.isImportClause(declaration)) {
560
+ statement = declaration.parent;
561
+ exportName = 'default';
562
+ }
563
+ else if (declaration && ts.isNamespaceImport(declaration)) {
564
+ statement = declaration.parent.parent;
565
+ }
566
+ else {
567
+ return undefined;
568
+ }
569
+ if (!ts.isImportDeclaration(statement))
570
+ return undefined;
571
+ const checker = program.getTypeChecker();
572
+ const moduleSymbol = checker.getSymbolAtLocation(statement.moduleSpecifier);
573
+ const moduleFile = moduleSymbol?.declarations?.find(ts.isSourceFile);
574
+ if (!moduleSymbol || !moduleFile)
575
+ return undefined;
576
+ const installed = installedPackageSpecifier(moduleFile.fileName, exportName);
577
+ const fromEntry = installed && resolveFromEntry(installed.specifier);
578
+ if (!fromEntry || realPath(fromEntry) !== realPath(moduleFile.fileName))
579
+ return undefined;
580
+ const spec = moduleFile.fileName;
581
+ const exported = new Set(checker.getExportsOfModule(moduleSymbol).map((symbol) => symbol.getName()));
582
+ if (exportName === undefined)
583
+ return { spec, namespaceExports: exported };
584
+ return exported.has(exportName) ? { spec, exportPath: [exportName] } : undefined;
585
+ }
586
+ /**
587
+ * carrick#1836: the import for `name` from the one declaration the inference
588
+ * recorded printing it for, or undefined.
589
+ *
590
+ * Undefined when the name was recorded for no declaration or for several,
591
+ * when the recorded module is not in this program or not inside the repo
592
+ * (`specOf`), and when its export path does not lead to a type there: a record
593
+ * that does not check out names nothing, and the text stays as written.
594
+ */
595
+ function printedImportOf(program, name, printed, specOf) {
596
+ const recorded = (printed ?? []).filter((entry) => entry.name === name);
597
+ const identities = new Set(recorded.map((entry) => `${entry.file}\0${entry.export_path.join('.')}`));
598
+ if (identities.size !== 1)
599
+ return undefined;
600
+ const entry = recorded[0];
601
+ const declaring = program.getSourceFile(entry.file);
602
+ const spec = declaring && specOf(declaring);
603
+ if (!declaring || !spec)
604
+ return undefined;
605
+ const checker = program.getTypeChecker();
606
+ let current = checker.getSymbolAtLocation(declaring);
607
+ for (const part of entry.export_path) {
608
+ const next = current && checker.getExportsOfModule(current).find((s) => s.getName() === part);
609
+ current = next && resolveSymbolAliases(checker, next);
610
+ }
611
+ if (!current || !(current.flags & (ts.SymbolFlags.Type | ts.SymbolFlags.Namespace))) {
612
+ return undefined;
613
+ }
614
+ return { spec, exportPath: entry.export_path };
615
+ }
472
616
  /** The type node of `type __LiteralAnchor = <text>;`, or undefined. */
473
617
  function parseLiteralAnchor(text) {
474
618
  const parsed = ts.createSourceFile('literal-anchor.ts', `type __LiteralAnchor = ${text};`, ts.ScriptTarget.Latest, true);
@@ -71,6 +71,36 @@ export interface LiteralAnchorRequest {
71
71
  * record's `source_file` stays `<inline>`: the answer is still the text.
72
72
  */
73
73
  source_file?: string;
74
+ /**
75
+ * carrick#1836: what the text's bare names meant where they were printed,
76
+ * for the names `source_file` cannot resolve. A name listed once is
77
+ * imported from the module that declares it; a name listed for two
78
+ * declarations is left as written.
79
+ */
80
+ printed_names?: PrintedName[];
81
+ /**
82
+ * carrick#1842: the text is a body the call site reads as raw text. Copied
83
+ * onto the alias's record, where the check phase reads it.
84
+ */
85
+ raw_text_read?: true;
86
+ }
87
+ /**
88
+ * The declaration a bare name in printed type text meant (carrick#1836).
89
+ *
90
+ * The v1 inferrer prints some types with no enclosing declaration, and that
91
+ * print writes every named type by its bare name, whether or not the file the
92
+ * request names can see it. The compiler knew the symbol when it printed the
93
+ * name; this is that symbol, recorded as the module that declares it and the
94
+ * export path that reaches it there (`['Status']`, or `['Billing', 'Kind']`
95
+ * for a namespace member printed as `Kind`).
96
+ */
97
+ export interface PrintedName {
98
+ /** The name as the text prints it: the leftmost part of a reference. */
99
+ name: string;
100
+ /** Absolute path of the module that declares it. */
101
+ file: string;
102
+ /** Export names from that module down to the declaration. */
103
+ export_path: string[];
74
104
  }
75
105
  /**
76
106
  * Addressable handler: `export type A = Awaited<ReturnType<typeof
@@ -152,7 +182,10 @@ export type SelfCheckOutcome = 'ok' | 'allowlisted_external' | 'decayed_internal
152
182
  * contract.
153
183
  * - `machinery_envelope`: the return resolved to transport (a
154
184
  * Response/Request-shaped envelope) and no payload was recoverable inside
155
- * it or from the handler's returned arguments.
185
+ * it or from the handler's returned arguments. On a consumer call result it
186
+ * is a decided abstain: what the call's result carrier holds is transport
187
+ * the service's wrapper rules verify and read no payload out of, such as a
188
+ * request library's own response object (carrick#1841).
156
189
  * - `coerced_input`: a request schema's INPUT is `any`/`unknown` at this
157
190
  * position while its parsed output is concrete, which is what a coercion
158
191
  * declares (carrick#1101). The published type carries the output there, so
@@ -271,6 +304,14 @@ export interface CaptureAliasRecord {
271
304
  * entry's other provenance. Absent when every position resolves.
272
305
  */
273
306
  unresolved_in_tree?: TypeProvenance[];
307
+ /**
308
+ * carrick#1842: the alias is a body its call site reads as raw text, from
309
+ * the literal anchor that published it. The check phase reads a pair with
310
+ * this on either side unverifiable: raw text states no structural contract,
311
+ * whether or not the other side's type assigns to `string`. Absent
312
+ * otherwise, and absent on every record a release before this one stored.
313
+ */
314
+ raw_text_read?: true;
274
315
  }
275
316
  /** Aggregate fidelity metric, emitted per capture (one service). */
276
317
  export interface CaptureFidelity {
@@ -13,6 +13,7 @@
13
13
  * IsVoid gate fired (TS2344) -> unverifiable (no body read)
14
14
  * IsFormBody gate fired (TS2344) -> unverifiable (form-encoded body)
15
15
  * IsByteBody gate fired (TS2344) -> unverifiable (bytes, even agreeing)
16
+ * raw-text read marked on a side -> unverifiable (text, even agreeing)
16
17
  * assignment-class error -> incompatible
17
18
  * no diagnostics -> compatible [lowest precedence]
18
19
  *
@@ -23,7 +24,7 @@
23
24
  * Seam: node builtins + this bundle only.
24
25
  */
25
26
  import type { CheckVerdict } from './api.js';
26
- import { type ProbePlan } from './check-probe.js';
27
+ import { type ProbePlan, type Side } from './check-probe.js';
27
28
  import { type ScrubContext } from './check-scrub.js';
28
29
  import type { PairDeepFindings } from './check-deep.js';
29
30
  import { type PairFieldReport } from './check-fields.js';
@@ -65,6 +66,13 @@ export interface ClassifyInput {
65
66
  * run, in which case the tsc text stands alone.
66
67
  */
67
68
  fieldReport?: PairFieldReport;
69
+ /**
70
+ * The side whose capture record says it reads the body as raw text
71
+ * (carrick#1842), the sent side when both do. Read from the record, not the
72
+ * type: a text read publishes `string`, which no type gate can tell from a
73
+ * JSON body that is a string.
74
+ */
75
+ rawTextSide?: Side;
68
76
  }
69
77
  /** Classify one pair into exactly one bucket, honouring the precedence order. */
70
78
  export declare function classifyPair(input: ClassifyInput): CheckVerdict;
@@ -13,6 +13,7 @@
13
13
  * IsVoid gate fired (TS2344) -> unverifiable (no body read)
14
14
  * IsFormBody gate fired (TS2344) -> unverifiable (form-encoded body)
15
15
  * IsByteBody gate fired (TS2344) -> unverifiable (bytes, even agreeing)
16
+ * raw-text read marked on a side -> unverifiable (text, even agreeing)
16
17
  * assignment-class error -> incompatible
17
18
  * no diagnostics -> compatible [lowest precedence]
18
19
  *
@@ -194,6 +195,20 @@ export function classifyPair(input) {
194
195
  ...notAFact(`the ${side} body is bytes`, side),
195
196
  };
196
197
  }
198
+ // 4d. A body read as raw text (carrick#1842): a `.text()` read, or a request
199
+ // library's text format, takes the body unparsed, so `string` there
200
+ // states no structural contract. Like bytes, the pair is not compared,
201
+ // whether or not the other side's type assigns to `string`.
202
+ if (input.rawTextSide && plan.spec.protocol === 'http') {
203
+ const side = input.rawTextSide;
204
+ return {
205
+ ...base,
206
+ bucket: 'unverifiable',
207
+ gate: `${side}:text`,
208
+ diagnostic: `the ${side} reads the body as raw text, which has no JSON shape to compare with the other side.`,
209
+ ...notAFact(`the ${side} reads the body as raw text`, side),
210
+ };
211
+ }
197
212
  // 5. Assignment-class error on the DECISIVE assignment line -> incompatible.
198
213
  //
199
214
  // On an `http` pair that line is the JSON wire assignment, not the declared
@@ -27,7 +27,7 @@
27
27
  import ts from 'typescript';
28
28
  import * as fs from 'node:fs';
29
29
  import * as path from 'node:path';
30
- import { collectSpecifiers, isRelative } from './specifiers.js';
30
+ import { collectSpecifiers, declarationCandidates, isDeclarationFileName, isRelative, } from './specifiers.js';
31
31
  /** One index per assembled stub, keyed by service name. */
32
32
  export function buildPoisonIndexes(ws) {
33
33
  const indexes = new Map();
@@ -45,14 +45,14 @@ function buildOne(ws, serviceName, packageDir) {
45
45
  return undefined;
46
46
  const keyPrefix = `packages/${packageDir}/types`;
47
47
  const surfaceFile = `${keyPrefix}/surface.d.ts`;
48
- // Collect the whole `.d.ts` tree, keyed as `packages/<dir>/types/<rel>`.
48
+ // Collect the whole declaration tree, keyed as `packages/<dir>/types/<rel>`.
49
49
  const treeAbs = [];
50
50
  const walk = (dir) => {
51
51
  for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
52
52
  const p = path.join(dir, entry.name);
53
53
  if (entry.isDirectory())
54
54
  walk(p);
55
- else if (entry.name.endsWith('.d.ts'))
55
+ else if (isDeclarationFileName(entry.name))
56
56
  treeAbs.push(p);
57
57
  }
58
58
  };
@@ -63,17 +63,7 @@ function buildOne(ws, serviceName, packageDir) {
63
63
  if (!isRelative(spec))
64
64
  return undefined;
65
65
  const base = path.resolve(path.dirname(fromAbs), spec);
66
- const candidates = [
67
- `${base}.d.ts`,
68
- path.join(base, 'index.d.ts'),
69
- base.endsWith('.js') ? `${base.slice(0, -3)}.d.ts` : undefined,
70
- base, // already `.d.ts`
71
- ].filter((c) => c !== undefined);
72
- for (const c of candidates) {
73
- if (treeAbsSet.has(path.resolve(c)))
74
- return c;
75
- }
76
- return undefined;
66
+ return declarationCandidates(base).find((candidate) => treeAbsSet.has(candidate));
77
67
  };
78
68
  // Per-file relative-import adjacency, over the fileKey namespace.
79
69
  const adjacency = new Map();
@@ -158,7 +158,8 @@ export async function runCheck(opts, onProgress) {
158
158
  const spec = opts.pairs.find((p) => p.pair_key === v.pair_key);
159
159
  v.pair_id = fnvOfSpec(spec);
160
160
  }
161
- const { preGated, probing } = preGate(plans, readStubAliasRecords(opts.stubs));
161
+ const aliasRecords = readStubAliasRecords(opts.stubs);
162
+ const { preGated, probing } = preGate(plans, aliasRecords);
162
163
  writeProbes(ws, probing);
163
164
  const errors = [];
164
165
  const degraded = [];
@@ -269,6 +270,7 @@ export async function runCheck(opts, onProgress) {
269
270
  scrubCtx,
270
271
  deepFindings: deepByPair.get(plan.pairId),
271
272
  fieldReport: fieldsByPair.get(plan.pairId),
273
+ rawTextSide: rawTextSideOf(plan, aliasRecords),
272
274
  })),
273
275
  ...preGated,
274
276
  ...unresolved,
@@ -355,6 +357,16 @@ function preGate(plans, aliasRecords) {
355
357
  }
356
358
  return { preGated, probing };
357
359
  }
360
+ /**
361
+ * The side whose capture record marks its body as read raw (carrick#1842):
362
+ * the sent side when both are, as the bytes gate names it.
363
+ */
364
+ function rawTextSideOf(plan, aliasRecords) {
365
+ return [plan.direction.sent, plan.direction.expected].find((side) => {
366
+ const endpoint = plan.spec[side];
367
+ return aliasRecords.get(endpoint.service_name)?.get(endpoint.alias)?.raw_text_read === true;
368
+ });
369
+ }
358
370
  /** First side (producer, then consumer) whose capture recorded a deep decay. */
359
371
  function deepDecayOf(plan, aliasRecords) {
360
372
  for (const side of ['producer', 'consumer']) {