@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.
- package/build/execution/wrapperConfig.d.ts +4 -0
- package/build/execution/wrapperConfig.js +14 -2
- package/build/prompts/code-reuse.d.ts +5 -1
- package/build/prompts/code-reuse.js +16 -10
- package/build/prompts/modularization/ui-test-modularization.js +9 -2
- package/build/prompts/pom-aware-code-reuse.d.ts +4 -1
- package/build/prompts/pom-aware-code-reuse.js +38 -6
- package/build/prompts/reuse-hand-off.d.ts +4 -0
- package/build/prompts/reuse-hand-off.js +10 -5
- package/build/prompts/shared-helper-policy.d.ts +5 -0
- package/build/prompts/shared-helper-policy.js +5 -0
- package/build/prompts/test-maintenance/driftAnalysisShared.js +2 -0
- package/build/prompts/test-maintenance/uiDriftAnalysisSections.js +2 -1
- package/build/prompts/testbot/testbot-prompts.js +33 -16
- package/build/recommendation/answers.d.ts +5 -3
- package/build/recommendation/answers.js +5 -2
- package/build/recommendation/fakeVerifyContext.d.ts +3 -0
- package/build/recommendation/fakeVerifyContext.js +16 -0
- package/build/recommendation/runVerifiers.js +2 -0
- package/build/recommendation/types.d.ts +12 -0
- package/build/recommendation/verifierContracts.d.ts +32 -2
- package/build/recommendation/verifierContracts.js +45 -4
- package/build/recommendation/verifiers/changedLinesClaimed.d.ts +17 -0
- package/build/recommendation/verifiers/changedLinesClaimed.js +88 -0
- package/build/recommendation/verifiers/coverage.js +143 -4
- package/build/recommendation/verifiers/expectedValueSourced.js +8 -0
- package/build/recommendation/verifiers/requirementSourced.js +141 -11
- package/build/services/TestGenerationService.js +4 -0
- package/build/tools/code-refactor/caller-gate.d.ts +105 -0
- package/build/tools/code-refactor/caller-gate.js +174 -0
- package/build/tools/code-refactor/changed-helper-report.d.ts +62 -0
- package/build/tools/code-refactor/changed-helper-report.js +114 -0
- package/build/tools/code-refactor/codeReuseTool.js +131 -62
- package/build/tools/code-refactor/enhanceAssertionsTool.js +2 -2
- package/build/tools/code-refactor/helper-callers.d.ts +65 -0
- package/build/tools/code-refactor/helper-callers.js +85 -0
- package/build/tools/code-refactor/modularizationTool.js +10 -0
- package/build/tools/code-refactor/pom-pass-state.d.ts +51 -0
- package/build/tools/code-refactor/pom-pass-state.js +115 -0
- package/build/tools/code-refactor/removed-modules.d.ts +33 -0
- package/build/tools/code-refactor/removed-modules.js +61 -0
- package/build/tools/code-refactor/retrofit-state.d.ts +3 -2
- package/build/tools/code-refactor/retrofit-state.js +11 -12
- package/build/tools/code-refactor/reuse-outcome.d.ts +33 -1
- package/build/tools/code-refactor/reuse-record-store.d.ts +57 -0
- package/build/tools/code-refactor/reuse-record-store.js +140 -0
- package/build/tools/code-refactor/reuse-state.d.ts +34 -9
- package/build/tools/code-refactor/reuse-state.js +140 -135
- package/build/tools/code-refactor/utils-verify-gates.js +42 -18
- package/build/tools/code-refactor/verify-gates.d.ts +4 -0
- package/build/tools/code-refactor/verify-gates.js +8 -1
- package/build/tools/generate-tests/generateContractRestTool.js +7 -7
- package/build/tools/generate-tests/generateIntegrationRestTool.js +8 -8
- package/build/tools/generate-tests/generateUIRestTool.js +3 -3
- package/build/tools/submitReportTool.js +121 -30
- package/build/tools/test-management/actionsTool.js +7 -0
- package/build/tools/test-management/registerTestPlanTool.d.ts +1 -0
- package/build/tools/test-management/registerTestPlanTool.js +71 -23
- package/build/types/RepositoryAnalysis.d.ts +20 -20
- package/build/types/ReuseOutcome.d.ts +123 -0
- package/build/types/TestExecution.d.ts +11 -0
- package/build/types/TestExecution.js +19 -0
- package/build/types/index.d.ts +1 -1
- package/build/utils/branchDiff.d.ts +1 -1
- package/build/utils/branchDiff.js +1 -1
- package/build/utils/changedRuns.d.ts +13 -0
- package/build/utils/changedRuns.js +56 -0
- package/build/utils/featureFlags.d.ts +8 -10
- package/build/utils/featureFlags.js +23 -21
- package/build/utils/normalizeSkyrampImports.d.ts +4 -1
- package/build/utils/normalizeSkyrampImports.js +24 -17
- package/build/utils/pom-scope/ownership.d.ts +19 -0
- package/build/utils/pom-scope/ownership.js +31 -0
- package/build/utils/pom-scope/pom-files.d.ts +8 -0
- package/build/utils/pom-scope/pom-files.js +8 -0
- package/build/utils/pom-scope/scoring.js +13 -1
- package/build/utils/pom-scope/strip.d.ts +10 -0
- package/build/utils/pom-scope/strip.js +9 -0
- package/build/utils/pom-verify/verify.js +13 -2
- package/build/utils/reuseRouting.d.ts +30 -2
- package/build/utils/reuseRouting.js +36 -15
- package/build/utils/testDependencyPolicy.js +4 -16
- package/build/utils/utils-verify/action-sites.d.ts +2 -2
- package/build/utils/utils-verify/allow.d.ts +1 -1
- package/build/utils/utils-verify/allow.js +15 -0
- package/build/utils/utils-verify/body-reach.d.ts +2 -2
- package/build/utils/utils-verify/body-reach.js +4 -1
- package/build/utils/utils-verify/call-sites.d.ts +16 -6
- package/build/utils/utils-verify/call-sites.js +12 -6
- package/build/utils/utils-verify/changed-helpers.d.ts +38 -0
- package/build/utils/utils-verify/changed-helpers.js +91 -0
- package/build/utils/utils-verify/changed-lines.d.ts +48 -0
- package/build/utils/utils-verify/changed-lines.js +106 -0
- package/build/utils/utils-verify/head.d.ts +73 -0
- package/build/utils/utils-verify/head.js +156 -0
- package/build/utils/utils-verify/importers.d.ts +59 -10
- package/build/utils/utils-verify/importers.js +121 -31
- package/build/utils/utils-verify/in-house.js +26 -31
- package/build/utils/utils-verify/index.d.ts +3 -0
- package/build/utils/utils-verify/index.js +3 -0
- package/build/utils/utils-verify/language-spec.js +10 -2
- package/build/utils/utils-verify/locate.d.ts +3 -0
- package/build/utils/utils-verify/locate.js +6 -6
- package/build/utils/utils-verify/parse.d.ts +13 -1
- package/build/utils/utils-verify/parse.js +122 -49
- package/build/utils/utils-verify/retrofit-equivalence.d.ts +0 -5
- package/build/utils/utils-verify/retrofit-equivalence.js +2 -34
- package/build/utils/utils-verify/stage.js +4 -16
- package/build/utils/utils-verify/typecheck.d.ts +25 -4
- package/build/utils/utils-verify/typecheck.js +95 -13
- package/build/utils/utils-verify/verify.d.ts +33 -8
- package/build/utils/utils-verify/verify.js +180 -18
- package/build/utils/versions.d.ts +3 -3
- package/build/utils/versions.js +1 -1
- package/node_modules/playwright/lib/dom-analyzer/blueprint.js +1 -0
- package/node_modules/playwright/lib/dom-analyzer/dynamicId.js +1 -0
- package/node_modules/playwright/lib/dom-analyzer/dynamicId.test.js +6 -0
- package/node_modules/playwright/lib/mcp/skyramp/exportTool.js +4 -2
- package/node_modules/playwright/lib/mcp/skyramp/snapshotLocators.js +246 -0
- package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +378 -237
- package/node_modules/playwright/lib/mcp/test/skyRampExport.js +21 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/generated/pollingRecorderSource.js +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{codeMirrorModule-LNgEKtdV.js → codeMirrorModule-Bou8kKzE.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{defaultSettingsView-Bwr1eMKC.js → defaultSettingsView-DTp2-8Si.js} +3 -3
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html +2 -2
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{index.BAkLd5DX.js → index.uiPTFncY.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{uiMode.BPopbasy.js → uiMode.GLqsx5cI.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.html +2 -2
- package/node_modules/playwright/node_modules/playwright-core/package.json +1 -1
- package/node_modules/playwright/node_modules/playwright-core/src/generated/pollingRecorderSource.ts +1 -1
- package/node_modules/playwright/package.json +1 -1
- package/package.json +3 -3
- package/plugin/prompts/code-reuse/hand-off.md +13 -1
- package/plugin/prompts/plan-tests.md +2 -2
- 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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
54
|
-
*
|
|
55
|
-
*
|
|
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?:
|
|
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
|
|
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
|
-
//
|
|
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
|
-
:
|
|
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
|
|
285
|
-
*
|
|
286
|
-
*
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
*
|
|
176
|
-
* ADVISORY
|
|
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;
|