@skyramp/mcp 0.4.2 → 0.4.3-rc.2

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 (135) hide show
  1. package/build/execution/wrapperConfig.d.ts +4 -0
  2. package/build/execution/wrapperConfig.js +14 -2
  3. package/build/prompts/code-reuse.d.ts +5 -1
  4. package/build/prompts/code-reuse.js +16 -10
  5. package/build/prompts/modularization/ui-test-modularization.js +9 -2
  6. package/build/prompts/pom-aware-code-reuse.d.ts +4 -1
  7. package/build/prompts/pom-aware-code-reuse.js +38 -6
  8. package/build/prompts/reuse-hand-off.d.ts +4 -0
  9. package/build/prompts/reuse-hand-off.js +10 -5
  10. package/build/prompts/shared-helper-policy.d.ts +5 -0
  11. package/build/prompts/shared-helper-policy.js +5 -0
  12. package/build/prompts/test-maintenance/driftAnalysisShared.js +2 -0
  13. package/build/prompts/test-maintenance/uiDriftAnalysisSections.js +2 -1
  14. package/build/prompts/testbot/testbot-prompts.js +33 -16
  15. package/build/recommendation/answers.d.ts +5 -3
  16. package/build/recommendation/answers.js +5 -2
  17. package/build/recommendation/fakeVerifyContext.d.ts +3 -0
  18. package/build/recommendation/fakeVerifyContext.js +16 -0
  19. package/build/recommendation/runVerifiers.js +2 -0
  20. package/build/recommendation/types.d.ts +12 -0
  21. package/build/recommendation/verifierContracts.d.ts +32 -2
  22. package/build/recommendation/verifierContracts.js +45 -4
  23. package/build/recommendation/verifiers/changedLinesClaimed.d.ts +17 -0
  24. package/build/recommendation/verifiers/changedLinesClaimed.js +88 -0
  25. package/build/recommendation/verifiers/coverage.js +143 -4
  26. package/build/recommendation/verifiers/expectedValueSourced.js +8 -0
  27. package/build/recommendation/verifiers/requirementSourced.js +141 -11
  28. package/build/services/TestGenerationService.js +4 -0
  29. package/build/tools/code-refactor/caller-gate.d.ts +105 -0
  30. package/build/tools/code-refactor/caller-gate.js +174 -0
  31. package/build/tools/code-refactor/changed-helper-report.d.ts +62 -0
  32. package/build/tools/code-refactor/changed-helper-report.js +114 -0
  33. package/build/tools/code-refactor/codeReuseTool.js +131 -62
  34. package/build/tools/code-refactor/enhanceAssertionsTool.js +2 -2
  35. package/build/tools/code-refactor/helper-callers.d.ts +65 -0
  36. package/build/tools/code-refactor/helper-callers.js +85 -0
  37. package/build/tools/code-refactor/modularizationTool.js +10 -0
  38. package/build/tools/code-refactor/pom-pass-state.d.ts +51 -0
  39. package/build/tools/code-refactor/pom-pass-state.js +115 -0
  40. package/build/tools/code-refactor/removed-modules.d.ts +33 -0
  41. package/build/tools/code-refactor/removed-modules.js +61 -0
  42. package/build/tools/code-refactor/retrofit-state.d.ts +3 -2
  43. package/build/tools/code-refactor/retrofit-state.js +11 -12
  44. package/build/tools/code-refactor/reuse-outcome.d.ts +33 -1
  45. package/build/tools/code-refactor/reuse-record-store.d.ts +57 -0
  46. package/build/tools/code-refactor/reuse-record-store.js +140 -0
  47. package/build/tools/code-refactor/reuse-state.d.ts +34 -9
  48. package/build/tools/code-refactor/reuse-state.js +140 -135
  49. package/build/tools/code-refactor/utils-verify-gates.js +42 -18
  50. package/build/tools/code-refactor/verify-gates.d.ts +4 -0
  51. package/build/tools/code-refactor/verify-gates.js +8 -1
  52. package/build/tools/generate-tests/generateContractRestTool.js +7 -7
  53. package/build/tools/generate-tests/generateIntegrationRestTool.js +8 -8
  54. package/build/tools/generate-tests/generateUIRestTool.js +3 -3
  55. package/build/tools/submitReportTool.js +121 -30
  56. package/build/tools/test-management/actionsTool.js +7 -0
  57. package/build/tools/test-management/registerTestPlanTool.d.ts +1 -0
  58. package/build/tools/test-management/registerTestPlanTool.js +71 -23
  59. package/build/types/RepositoryAnalysis.d.ts +20 -20
  60. package/build/types/ReuseOutcome.d.ts +123 -0
  61. package/build/types/TestExecution.d.ts +11 -0
  62. package/build/types/TestExecution.js +19 -0
  63. package/build/types/index.d.ts +1 -1
  64. package/build/utils/branchDiff.d.ts +1 -1
  65. package/build/utils/branchDiff.js +1 -1
  66. package/build/utils/changedRuns.d.ts +13 -0
  67. package/build/utils/changedRuns.js +56 -0
  68. package/build/utils/featureFlags.d.ts +8 -10
  69. package/build/utils/featureFlags.js +23 -21
  70. package/build/utils/normalizeSkyrampImports.d.ts +4 -1
  71. package/build/utils/normalizeSkyrampImports.js +24 -17
  72. package/build/utils/pom-scope/ownership.d.ts +19 -0
  73. package/build/utils/pom-scope/ownership.js +31 -0
  74. package/build/utils/pom-scope/pom-files.d.ts +8 -0
  75. package/build/utils/pom-scope/pom-files.js +8 -0
  76. package/build/utils/pom-scope/scoring.js +13 -1
  77. package/build/utils/pom-scope/strip.d.ts +10 -0
  78. package/build/utils/pom-scope/strip.js +9 -0
  79. package/build/utils/pom-verify/verify.js +13 -2
  80. package/build/utils/reuseRouting.d.ts +30 -2
  81. package/build/utils/reuseRouting.js +36 -15
  82. package/build/utils/testDependencyPolicy.js +4 -16
  83. package/build/utils/utils-verify/action-sites.d.ts +2 -2
  84. package/build/utils/utils-verify/allow.d.ts +1 -1
  85. package/build/utils/utils-verify/allow.js +15 -0
  86. package/build/utils/utils-verify/body-reach.d.ts +2 -2
  87. package/build/utils/utils-verify/body-reach.js +4 -1
  88. package/build/utils/utils-verify/call-sites.d.ts +16 -6
  89. package/build/utils/utils-verify/call-sites.js +12 -6
  90. package/build/utils/utils-verify/changed-helpers.d.ts +38 -0
  91. package/build/utils/utils-verify/changed-helpers.js +91 -0
  92. package/build/utils/utils-verify/changed-lines.d.ts +48 -0
  93. package/build/utils/utils-verify/changed-lines.js +106 -0
  94. package/build/utils/utils-verify/head.d.ts +73 -0
  95. package/build/utils/utils-verify/head.js +156 -0
  96. package/build/utils/utils-verify/importers.d.ts +59 -10
  97. package/build/utils/utils-verify/importers.js +121 -31
  98. package/build/utils/utils-verify/in-house.js +26 -31
  99. package/build/utils/utils-verify/index.d.ts +3 -0
  100. package/build/utils/utils-verify/index.js +3 -0
  101. package/build/utils/utils-verify/language-spec.js +10 -2
  102. package/build/utils/utils-verify/locate.d.ts +3 -0
  103. package/build/utils/utils-verify/locate.js +6 -6
  104. package/build/utils/utils-verify/parse.d.ts +13 -1
  105. package/build/utils/utils-verify/parse.js +122 -49
  106. package/build/utils/utils-verify/retrofit-equivalence.d.ts +0 -5
  107. package/build/utils/utils-verify/retrofit-equivalence.js +2 -34
  108. package/build/utils/utils-verify/stage.js +4 -16
  109. package/build/utils/utils-verify/typecheck.d.ts +25 -4
  110. package/build/utils/utils-verify/typecheck.js +95 -13
  111. package/build/utils/utils-verify/verify.d.ts +33 -8
  112. package/build/utils/utils-verify/verify.js +180 -18
  113. package/build/utils/versions.d.ts +3 -3
  114. package/build/utils/versions.js +1 -1
  115. package/node_modules/playwright/lib/dom-analyzer/blueprint.js +1 -0
  116. package/node_modules/playwright/lib/dom-analyzer/dynamicId.js +1 -0
  117. package/node_modules/playwright/lib/dom-analyzer/dynamicId.test.js +6 -0
  118. package/node_modules/playwright/lib/mcp/skyramp/exportTool.js +4 -2
  119. package/node_modules/playwright/lib/mcp/skyramp/snapshotLocators.js +246 -0
  120. package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +378 -237
  121. package/node_modules/playwright/lib/mcp/test/skyRampExport.js +21 -0
  122. package/node_modules/playwright/node_modules/playwright-core/lib/generated/pollingRecorderSource.js +1 -1
  123. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{codeMirrorModule-LNgEKtdV.js → codeMirrorModule-Bou8kKzE.js} +1 -1
  124. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{defaultSettingsView-Bwr1eMKC.js → defaultSettingsView-DTp2-8Si.js} +3 -3
  125. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html +2 -2
  126. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{index.BAkLd5DX.js → index.uiPTFncY.js} +1 -1
  127. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{uiMode.BPopbasy.js → uiMode.GLqsx5cI.js} +1 -1
  128. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.html +2 -2
  129. package/node_modules/playwright/node_modules/playwright-core/package.json +1 -1
  130. package/node_modules/playwright/node_modules/playwright-core/src/generated/pollingRecorderSource.ts +1 -1
  131. package/node_modules/playwright/package.json +1 -1
  132. package/package.json +3 -3
  133. package/plugin/prompts/code-reuse/hand-off.md +13 -1
  134. package/plugin/prompts/plan-tests.md +2 -2
  135. package/plugin/skills/enhance-assertions/reference/shared-rules.md +1 -0
@@ -41,8 +41,3 @@ export interface RetrofitMismatch {
41
41
  * than a refusal the agent cannot act on.
42
42
  */
43
43
  export declare function retrofitMismatches(retrofitFile: string, utilsFiles: string[], spec: UtilsLanguageSpec): Promise<RetrofitMismatch[]>;
44
- /** The file's content at HEAD, or undefined when there is nothing to compare against
45
- * (untracked, no git root, or git unavailable). Never throws. The ONE reader of a
46
- * file's committed baseline on the utils path: the incumbent-signature gate reads
47
- * the utils module through it too. */
48
- export declare function committedBaseline(file: string): Promise<string | undefined>;
@@ -1,14 +1,9 @@
1
- import { execFile } from "child_process";
2
1
  import * as fs from "fs";
3
- import * as path from "path";
4
- import { promisify } from "util";
5
- import { logger } from "../logger.js";
6
- import { detectGitRoot } from "../gitStaging.js";
2
+ import { contentAtHead } from "./head.js";
7
3
  import { blankCommentsAndStrings, parseUtilsFile } from "./parse.js";
8
4
  import { normalizePath } from "./language-spec.js";
9
5
  import { callText, hasSchemaCheck, requestFields, REQUEST_CALL_RE, SCHEMA_SCAN_LINES, shapeKeys, } from "./call-sites.js";
10
6
  import { escapeRegExp } from "../regex.js";
11
- const exec = promisify(execFile);
12
7
  /**
13
8
  * Whether a retrofit changed the request the rewired test sends.
14
9
  *
@@ -41,7 +36,7 @@ const exec = promisify(execFile);
41
36
  * than a refusal the agent cannot act on.
42
37
  */
43
38
  export async function retrofitMismatches(retrofitFile, utilsFiles, spec) {
44
- const baseline = await committedBaseline(retrofitFile);
39
+ const baseline = await contentAtHead(retrofitFile);
45
40
  if (baseline === undefined)
46
41
  return [];
47
42
  let delivered;
@@ -191,30 +186,3 @@ function sameSet(a, b) {
191
186
  return false;
192
187
  return true;
193
188
  }
194
- /** The file's content at HEAD, or undefined when there is nothing to compare against
195
- * (untracked, no git root, or git unavailable). Never throws. The ONE reader of a
196
- * file's committed baseline on the utils path: the incumbent-signature gate reads
197
- * the utils module through it too. */
198
- export async function committedBaseline(file) {
199
- const root = detectGitRoot(file);
200
- if (!root)
201
- return undefined;
202
- const rel = path.relative(root, file);
203
- if (!rel || rel.startsWith(".."))
204
- return undefined;
205
- try {
206
- const { stdout } = await exec("git", ["show", `HEAD:${rel}`], {
207
- cwd: root,
208
- maxBuffer: 10 * 1024 * 1024,
209
- });
210
- return stdout;
211
- }
212
- catch (err) {
213
- // A file added by this run has no HEAD version — the common case, not an error.
214
- logger.debug("No committed baseline for a retrofit; skipping equivalence", {
215
- file,
216
- error: String(err),
217
- });
218
- return undefined;
219
- }
220
- }
@@ -1,15 +1,13 @@
1
- import { execFile } from "child_process";
2
- import { promisify } from "util";
3
1
  import * as fs from "fs";
4
2
  import * as path from "path";
5
3
  import { detectGitRoot, stageGeneratedPaths } from "../gitStaging.js";
6
4
  import { logger } from "../logger.js";
7
5
  import { isTestbotEnabled } from "../featureFlags.js";
8
6
  import { locateUtilsFiles, realpath } from "./locate.js";
7
+ import { changedSinceHead } from "./head.js";
9
8
  import { locateInHouseModules } from "./in-house.js";
10
9
  import { CODEGEN_MARKER_RE } from "./call-sites.js";
11
10
  import { utilsSpecFor, utilsSpecForFile } from "./language-spec.js";
12
- const execFileAsync = promisify(execFile);
13
11
  export async function stageUtilsArtifacts(testFile, language) {
14
12
  const staged = [];
15
13
  const failed = [];
@@ -99,10 +97,7 @@ async function modifiedInHouseModules(testFile, language) {
99
97
  const modules = await locateInHouseModules({ testFile, spec, cwd: root });
100
98
  if (modules.length === 0)
101
99
  return [];
102
- const { stdout } = await execFileAsync("git", ["diff", "-z", "--name-only", "--diff-filter=M", "HEAD"], { cwd: root, encoding: "utf8" });
103
- const modified = new Set();
104
- for (const rel of stdout.split("\0").filter(Boolean))
105
- modified.add(await realpath(path.resolve(root, rel)));
100
+ const modified = new Set(await changedSinceHead(root, { filter: "M" }));
106
101
  return modules.map((m) => m.file).filter((f) => modified.has(f));
107
102
  }
108
103
  catch (err) {
@@ -117,18 +112,12 @@ async function modifiedSiblingsImporting(testFile, utilsFiles, language) {
117
112
  return [];
118
113
  let modified;
119
114
  try {
120
- // `-z`: unquoted, NUL-separated — git C-quotes non-ASCII paths otherwise.
121
- const { stdout } = await execFileAsync("git",
122
115
  // Against HEAD, modified only: a test THIS run created is staged at discovery, so
123
116
  // its later STEP 5b edits show in a plain `git diff` and it would masquerade as a
124
117
  // pre-existing sibling (observed: the report gate then demanded an execution no
125
118
  // record could satisfy, and the agent un-shared the helpers to escape). Files
126
119
  // added since HEAD are excluded; they are staged by their own tool calls.
127
- ["diff", "-z", "--name-only", "--diff-filter=M", "HEAD"], {
128
- cwd: root,
129
- encoding: "utf8",
130
- });
131
- modified = stdout.split("\0").filter(Boolean);
120
+ modified = await changedSinceHead(root, { filter: "M" });
132
121
  }
133
122
  catch (err) {
134
123
  logger.warning("Could not list modified files — edited sibling tests will not be staged", {
@@ -140,8 +129,7 @@ async function modifiedSiblingsImporting(testFile, utilsFiles, language) {
140
129
  const stems = utilsFiles.map((f) => path.basename(f).replace(/\.[^.]+$/, ""));
141
130
  const self = await realpath(testFile);
142
131
  const out = [];
143
- for (const rel of modified) {
144
- const abs = await realpath(path.resolve(root, rel));
132
+ for (const abs of modified) {
145
133
  if (abs === self || utilsFiles.includes(abs))
146
134
  continue;
147
135
  if (!spec.extensions.some((e) => abs.toLowerCase().endsWith(e)))
@@ -44,18 +44,39 @@ export interface UtilsTypecheck {
44
44
  unresolved: string[];
45
45
  }
46
46
  export declare const AMBIENT_DECLARATION_CODES: Set<number>;
47
+ /** The delivered files as the repository held them at HEAD, and the files that
48
+ * did not exist then. With a baseline the check is the permissive pass alone: it
49
+ * exists to say which errors this run inherited, and the settings pass never
50
+ * blocks. */
51
+ export interface TypecheckBaseline {
52
+ contents: ReadonlyMap<string, string>;
53
+ hidden: ReadonlySet<string>;
54
+ }
55
+ export interface TypecheckOptions {
56
+ mcpRoot?: string;
57
+ /** The delivered file whose directory stands for the repository: where global
58
+ * types are looked up from, and where the executor packages' peers resolve
59
+ * from for EVERY file. The spec, when one is delivered — it is the consumer,
60
+ * and in a monorepo its package's Playwright is the one the executor runs,
61
+ * not a hoisted root copy the shared module's directory would find. Defaults
62
+ * to the first file. */
63
+ anchor?: string;
64
+ baseline?: TypecheckBaseline;
65
+ }
47
66
  /**
48
67
  * Typecheck the delivered TypeScript files — the shared module and the spec that
49
68
  * imports it — with the compiler the MCP itself depends on, so the check runs in
50
69
  * every repository whether or not `typescript` is installed there.
51
70
  *
52
71
  * Two passes over the same files. The PERMISSIVE pass has every strictness flag off:
53
- * an error it reports fails under any configuration and is blocking (`errors`). The
54
- * SETTINGS pass runs the repository's own tsconfig, when one reaches the files; what
55
- * it adds is an error only under those settings and is advisory (`settingErrors`).
72
+ * an error it reports fails under any configuration (`errors`) — the verifier
73
+ * blocks on it unless the repository already had it at HEAD, which the same
74
+ * function establishes when called with a `baseline`. The SETTINGS pass runs the
75
+ * repository's own tsconfig, when one reaches the files; what it adds is an error
76
+ * only under those settings and is advisory (`settingErrors`).
56
77
  * The executor transpiles without typechecking, so neither pass has run before this
57
78
  * one, and a delivered file that fails here reaches the customer's own `tsc` first.
58
79
  *
59
80
  * Never throws: a compiler failure is a result that says the check did not run.
60
81
  */
61
- export declare function typecheckDelivered(files: string[], mcpRoot?: string): Promise<UtilsTypecheck>;
82
+ export declare function typecheckDelivered(files: string[], { mcpRoot, anchor, baseline }?: TypecheckOptions): Promise<UtilsTypecheck>;
@@ -1,4 +1,5 @@
1
1
  import * as path from "path";
2
+ import { readFileSync } from "fs";
2
3
  import { fileURLToPath } from "url";
3
4
  let tsModule;
4
5
  /** The compiler, loaded on first use and once per process. Loaded statically it
@@ -41,8 +42,61 @@ const UNRESOLVED_MODULE_CODES = new Set([2307, 2792]);
41
42
  * exit was to rewrite working code. Every other package still resolves from the
42
43
  * repository first, because every other package is the repository's to pin. */
43
44
  const EXECUTOR_PROVIDED_PACKAGES = ["@skyramp/skyramp"];
45
+ /** The PEERS of the executor-provided packages — what their type declarations
46
+ * import and expect the host to install (`@skyramp/skyramp` declares
47
+ * `@playwright/test`, whose `Expect` types its `expect`). Read from each
48
+ * package's own manifest under `mcpRoot`, once per root: the SDK already states
49
+ * the list, and a second copy here would need a test to keep it in step. A peer
50
+ * is resolved from the REPOSITORY first for every file that imports it, the
51
+ * MCP's own copy second — including from inside the MCP's SDK declarations.
52
+ * Otherwise the SDK's copy came from the MCP's install while the spec's own
53
+ * import came from the repository's, and a repository pinned to another
54
+ * Playwright version had two `Locator` identities: every `expect(locator)` in
55
+ * it typed as the base matchers, and four assertion lines that pre-dated the
56
+ * run became blocking `type-error`s against a valid module — which the run then
57
+ * deleted. The customer's `tsc` has one node_modules and never sees the split;
58
+ * the executor runs the repository's Playwright, so the repository's copy is
59
+ * the truth. */
60
+ const peersByRoot = new Map();
61
+ function executorPeerPackages(mcpRoot) {
62
+ let peers = peersByRoot.get(mcpRoot);
63
+ if (peers)
64
+ return peers;
65
+ peers = [];
66
+ for (const pkg of EXECUTOR_PROVIDED_PACKAGES) {
67
+ const manifest = installedManifest(pkg, mcpRoot);
68
+ if (manifest)
69
+ peers.push(...Object.keys(manifest.peerDependencies ?? {}));
70
+ // No manifest reachable from this root: the package resolves from nowhere
71
+ // and its imports are reported as unresolved gaps, so there is no peer to
72
+ // route.
73
+ }
74
+ peersByRoot.set(mcpRoot, peers);
75
+ return peers;
76
+ }
77
+ /** The package's manifest as Node would find it from `from`: the nearest
78
+ * `node_modules/<pkg>` walking UP from there. The MCP's dependencies are not
79
+ * always nested under its own root — `npm install @skyramp/mcp`, testbot's
80
+ * default, hoists them BESIDE `@skyramp/mcp` — and a read at one fixed path
81
+ * found nothing on that layout, left the peer list empty, and brought the two
82
+ * Playwright identities back exactly where customers run. */
83
+ function installedManifest(pkg, from) {
84
+ for (let dir = from;; dir = path.dirname(dir)) {
85
+ try {
86
+ return JSON.parse(readFileSync(path.join(dir, "node_modules", pkg, "package.json"), "utf8"));
87
+ }
88
+ catch {
89
+ if (path.dirname(dir) === dir)
90
+ return undefined;
91
+ }
92
+ }
93
+ }
94
+ const isPackageOrSubpath = (specifier, packages) => packages.some((p) => specifier === p || specifier.startsWith(`${p}/`));
44
95
  function executorProvides(specifier) {
45
- return EXECUTOR_PROVIDED_PACKAGES.some((p) => specifier === p || specifier.startsWith(`${p}/`));
96
+ return isPackageOrSubpath(specifier, EXECUTOR_PROVIDED_PACKAGES);
97
+ }
98
+ function executorPeer(specifier, mcpRoot) {
99
+ return isPackageOrSubpath(specifier, executorPeerPackages(mcpRoot));
46
100
  }
47
101
  export const AMBIENT_DECLARATION_CODES = new Set([
48
102
  2304, 2311, 2318, 2468, 2503, 2552, 2580, 2581, 2582, 2583, 2584, 2591, 2592,
@@ -211,26 +265,51 @@ function findConfigs(ts, files) {
211
265
  * an error, and its diagnostics are dropped (everything typed by it is `any` and
212
266
  * was not checked — the gap is stated instead).
213
267
  */
214
- function runPass(ts, files, options, mcpRoot) {
268
+ function runPass(ts, files, options, mcpRoot, repoDir, baseline) {
215
269
  const own = new Set(files.map((f) => path.normalize(f)));
216
270
  const unresolved = new Set();
217
271
  const host = ts.createCompilerHost(options, true);
272
+ if (baseline) {
273
+ // A baseline file is read from the content given, never from disk; a hidden
274
+ // one does not exist to the compiler, so an import of it is unresolved and
275
+ // typed `any`, as it was before the run wrote it.
276
+ const contentOf = (f) => baseline.contents.get(path.normalize(f));
277
+ const hidden = (f) => baseline.hidden.has(path.normalize(f));
278
+ const { fileExists, readFile, getSourceFile } = host;
279
+ host.fileExists = (f) => hidden(f) ? false : contentOf(f) !== undefined || fileExists(f);
280
+ host.readFile = (f) => hidden(f) ? undefined : (contentOf(f) ?? readFile(f));
281
+ host.getSourceFile = (f, lang, onError, shouldCreate) => {
282
+ if (hidden(f))
283
+ return undefined;
284
+ const content = contentOf(f);
285
+ return content === undefined
286
+ ? getSourceFile(f, lang, onError, shouldCreate)
287
+ : ts.createSourceFile(f, content, lang, true);
288
+ };
289
+ }
218
290
  // Global types (`@types/*` pulled in automatically) are looked up from the current
219
291
  // directory upward. That must be the repository, not wherever the MCP process
220
292
  // happens to run: the MCP's own `@types/jest` made a bare `expect` compile here
221
293
  // that the customer's `tsc` cannot see.
222
- const repoDir = path.dirname(files[0]);
223
294
  host.getCurrentDirectory = () => repoDir;
224
295
  // A containing file that does not exist is fine for resolution: only its
225
296
  // directory is read, and node_modules is looked up from there.
226
297
  const anchor = path.join(mcpRoot, "__skyramp_typecheck_anchor__.ts");
298
+ // The same device for the repository: a peer imported from INSIDE the MCP's
299
+ // install must still resolve from the repository first, and the containing
300
+ // file's directory would find the MCP's copy.
301
+ const repoAnchor = path.join(repoDir, "__skyramp_typecheck_anchor__.ts");
227
302
  host.resolveModuleNameLiterals = (literals, containingFile, _r, opts) => literals.map((literal) => {
228
303
  const delivered = own.has(path.normalize(containingFile));
229
- // The executor's own packages are read from the MCP's install first;
230
- // everything else from the repository first, the MCP second.
304
+ // The executor's own packages are read from the MCP's install first; their
305
+ // peers from the repository first wherever the import sits, so both sides of
306
+ // an SDK type meet ONE copy; everything else from the repository first, the
307
+ // MCP second.
231
308
  const [first, second] = executorProvides(literal.text)
232
309
  ? [anchor, containingFile]
233
- : [containingFile, anchor];
310
+ : executorPeer(literal.text, mcpRoot)
311
+ ? [repoAnchor, anchor]
312
+ : [containingFile, anchor];
234
313
  let resolved = ts.resolveModuleName(literal.text, first, opts, host);
235
314
  if (!resolved.resolvedModule) {
236
315
  const other = ts.resolveModuleName(literal.text, second, opts, host);
@@ -281,15 +360,17 @@ const findingKey = (f) => `${f.file}\0${f.line}\0${f.code}\0${f.message}`;
281
360
  * every repository whether or not `typescript` is installed there.
282
361
  *
283
362
  * Two passes over the same files. The PERMISSIVE pass has every strictness flag off:
284
- * an error it reports fails under any configuration and is blocking (`errors`). The
285
- * SETTINGS pass runs the repository's own tsconfig, when one reaches the files; what
286
- * it adds is an error only under those settings and is advisory (`settingErrors`).
363
+ * an error it reports fails under any configuration (`errors`) — the verifier
364
+ * blocks on it unless the repository already had it at HEAD, which the same
365
+ * function establishes when called with a `baseline`. The SETTINGS pass runs the
366
+ * repository's own tsconfig, when one reaches the files; what it adds is an error
367
+ * only under those settings and is advisory (`settingErrors`).
287
368
  * The executor transpiles without typechecking, so neither pass has run before this
288
369
  * one, and a delivered file that fails here reaches the customer's own `tsc` first.
289
370
  *
290
371
  * Never throws: a compiler failure is a result that says the check did not run.
291
372
  */
292
- export async function typecheckDelivered(files, mcpRoot = mcpPackageRoot()) {
373
+ export async function typecheckDelivered(files, { mcpRoot = mcpPackageRoot(), anchor, baseline } = {}) {
293
374
  const result = {
294
375
  ran: false,
295
376
  errors: [],
@@ -310,15 +391,16 @@ export async function typecheckDelivered(files, mcpRoot = mcpPackageRoot()) {
310
391
  // import is unresolved here, typed `any`, and its unconditional error then
311
392
  // surfaces only in the settings pass — downgraded to an advisory.
312
393
  const base = (reaching ?? nearest)?.parsed.options ?? {};
313
- const floor = runPass(ts, files, permissiveOptions(ts, base), mcpRoot);
394
+ const repoDir = path.dirname(anchor ?? files[0]);
395
+ const floor = runPass(ts, files, permissiveOptions(ts, base), mcpRoot, repoDir, baseline);
314
396
  result.ran = true;
315
397
  result.errors = floor.findings.filter((f) => !AMBIENT_DECLARATION_CODES.has(f.code));
316
398
  result.ambientErrors = floor.findings.filter((f) => AMBIENT_DECLARATION_CODES.has(f.code));
317
399
  result.unresolved = [...floor.unresolved].sort();
318
- if (reaching) {
400
+ if (reaching && !baseline) {
319
401
  result.tsconfig = reaching.path;
320
402
  const seen = new Set([...floor.findings, ...floor.unresolvedFindings].map(findingKey));
321
- const settings = runPass(ts, files, settingsOptions(reaching.parsed.options), mcpRoot);
403
+ const settings = runPass(ts, files, settingsOptions(reaching.parsed.options), mcpRoot, repoDir);
322
404
  // An import only the repository's resolution refuses is its `tsc`'s error,
323
405
  // not a gap: the permissive pass resolved it, so the module exists.
324
406
  result.settingErrors = [
@@ -1,7 +1,7 @@
1
1
  import { type UtilsTypecheck } from "./typecheck.js";
2
2
  import { type InlineCallSite, type UnextractedDuplicate } from "./call-sites.js";
3
3
  import { type UtilsAdvisoryKind, type UtilsViolationKind } from "./allow.js";
4
- import { type SingleImporterHelper } from "./importers.js";
4
+ import { type HelperImporters } from "./importers.js";
5
5
  import { type HelperFamilySpec, type UtilsLanguageSpec } from "./language-spec.js";
6
6
  export interface UtilsViolation {
7
7
  kind: UtilsViolationKind;
@@ -24,6 +24,13 @@ export interface UtilsVerifyResult {
24
24
  ok: boolean;
25
25
  /** Utils files located for this spec (absolute). Empty = nothing was written or imported. */
26
26
  utilsFiles: string[];
27
+ /** Those of `utilsFiles` that HEAD holds — an earlier run's or the customer's. */
28
+ incumbentModules: string[];
29
+ /** Those of `utilsFiles` git could not answer for (outside a repository, or a
30
+ * git failure). Neither an incumbent nor this run's: a caller that states
31
+ * "this run created it" must not say so for these. The rest of `utilsFiles`
32
+ * this run created. */
33
+ headUnknownModules: string[];
27
34
  /** Helpers defined across the utils files. */
28
35
  helpers: number;
29
36
  /** Helper names the delivered spec imports from those files. */
@@ -60,8 +67,14 @@ export interface UtilsVerifyResult {
60
67
  * findUnextractedDuplicates. API family only. */
61
68
  unextractedDuplicates: UnextractedDuplicate[];
62
69
  /** Helpers imported by 0 or 1 delivered test (SKYR-4276 A6). Advisory: a first run
63
- * seeds every helper single-use by design; the count is the trend to watch. */
64
- singleImporters: SingleImporterHelper[];
70
+ * seeds every helper single-use by design; the count is the trend to watch. Empty
71
+ * whenever `importersComplete` is false — see there. */
72
+ singleImporters: HelperImporters[];
73
+ /** The importer walk read the whole directory. When false, `singleImporters` is
74
+ * empty because the walk could not support a finding, NOT because every helper has
75
+ * importers: a file it could not read may be the one importer of a helper. A
76
+ * caller that states "nothing imports this" must consult it. */
77
+ importersComplete: boolean;
65
78
  /** The numbers the baseline assertion check compared — present only when a
66
79
  * baseline was supplied and the spec was readable. The check is a COUNT: it
67
80
  * cannot see one assertion swapped for another. The caller's text says so.
@@ -80,6 +93,13 @@ export interface UtilsVerifyResult {
80
93
  * `tsconfig-type-error` advisories; this carries the rest (the tsconfig read, the
81
94
  * modules it could not resolve). Absent for Python and JavaScript. */
82
95
  typecheck?: UtilsTypecheck;
96
+ /** What the attribution of compiler errors to this run established — with
97
+ * `typecheck`, TypeScript only. `ran` is whether the HEAD compile ran, so an
98
+ * error the repository already had could be excused; when it did not, `reason`
99
+ * says why and every error blocked as this run's. `headUnknown` lists the
100
+ * delivered files git gave no HEAD answer for: each is judged as this run's
101
+ * in full, and the pass says so rather than hide it. */
102
+ attribution?: TypeAttribution;
83
103
  /** Modules the spec imports that this pass READ and does not CHECK: the
84
104
  * repository's own modules (tracked at HEAD, no Skyramp header) on a path with no
85
105
  * in-house checks — integration, today — and a module outside the repository.
@@ -104,6 +124,11 @@ export interface UtilsVerifyResult {
104
124
  * Absent when the spec imports no such module. */
105
125
  inHouse?: InHouseVerification;
106
126
  }
127
+ export interface TypeAttribution {
128
+ ran: boolean;
129
+ reason?: string;
130
+ headUnknown: string[];
131
+ }
107
132
  export interface InHouseVerification {
108
133
  /** Absolute paths of the in-house modules read. */
109
134
  modules: string[];
@@ -171,17 +196,17 @@ export interface VerifyUtilsParams {
171
196
  * returned. See status-once.ts.
172
197
  * - `type-error` (TypeScript): the delivered module or spec fails the compiler with
173
198
  * every strictness flag off — an error under every configuration (a path parameter
174
- * typed `unknown`, request options built as a record and handed to `sendRequest`).
175
- * An error only the repository's own tsconfig raises is the `tsconfig-type-error`
176
- * ADVISORY. See typecheckDelivered.
199
+ * typed `unknown`, request options built as a record and handed to `sendRequest`)
200
+ * — and this run CAUSED it: an error the file already had at HEAD on an
201
+ * unchanged line is the `inherited-type-error` ADVISORY, and an error only the
202
+ * repository's own tsconfig raises is the `tsconfig-type-error` ADVISORY. See
203
+ * typecheckDelivered and inheritedTypeError.
177
204
  *
178
205
  * Analyses the utils files' INTERNAL invariants — unlike the POM verifier, which
179
206
  * checks a spec's cross-file references — so it reads the utils files, not the spec,
180
207
  * except to count what the spec imports.
181
208
  */
182
209
  export declare function verifyUtils(params: VerifyUtilsParams): Promise<UtilsVerifyResult>;
183
- /** The compared counts as one phrase, shared by the assertion-loss detail and the
184
- * PASSED line's note so the two cannot drift. */
185
210
  export declare function describeAssertionCount(c: {
186
211
  baseline: number;
187
212
  delivered: number;