@skyramp/mcp 0.4.3 → 0.4.4-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 (168) hide show
  1. package/build/index.js +239 -210
  2. package/build/playwright/PlaywrightTraceService.d.ts +1 -1
  3. package/build/playwright/blueprintDigestStore.d.ts +1 -1
  4. package/build/playwright/registerPlaywrightTools.d.ts +1 -1
  5. package/build/playwright/traceRecordingPrompt.d.ts +1 -1
  6. package/build/playwright/traceRecordingPrompt.js +4 -3
  7. package/build/prompts/local-dev/local-dev-prompts.d.ts +1 -1
  8. package/build/prompts/local-dev/local-dev-prompts.js +2 -2
  9. package/build/prompts/orderAssertionRule.d.ts +9 -0
  10. package/build/prompts/orderAssertionRule.js +9 -0
  11. package/build/prompts/startTraceCollectionPrompts.d.ts +1 -1
  12. package/build/prompts/startTraceCollectionPrompts.js +2 -3
  13. package/build/prompts/test-maintenance/driftAnalysisShared.js +2 -0
  14. package/build/prompts/test-recommendation/registerRecommendTestsPrompt.d.ts +1 -1
  15. package/build/prompts/test-recommendation/registerRecommendTestsPrompt.js +2 -2
  16. package/build/prompts/testbot/testbot-prompts.d.ts +1 -1
  17. package/build/prompts/testbot/testbot-prompts.js +9 -9
  18. package/build/recommendation/answers.d.ts +3 -3
  19. package/build/recommendation/answers.js +3 -2
  20. package/build/recommendation/verifierContracts.d.ts +4 -4
  21. package/build/recommendation/verifierContracts.js +6 -5
  22. package/build/recommendation/verifiers/coverage.js +25 -25
  23. package/build/recommendation/verifiers/deliveredMatchesPlan.js +3 -3
  24. package/build/resources/analysisResources.d.ts +1 -1
  25. package/build/resources/analysisResources.js +1 -1
  26. package/build/resources/progressResource.d.ts +1 -1
  27. package/build/resources/sutSetupResource.d.ts +1 -1
  28. package/build/resources/sutSetupResource.js +1 -1
  29. package/build/resources/testbotResource.d.ts +1 -1
  30. package/build/resources/testbotResource.js +1 -1
  31. package/build/services/AnalyticsService.d.ts +1 -1
  32. package/build/services/FleetExecutionService.d.ts +130 -0
  33. package/build/services/FleetExecutionService.js +391 -0
  34. package/build/services/ModularizationService.d.ts +1 -1
  35. package/build/services/TestExecutionService.d.ts +7 -0
  36. package/build/services/TestExecutionService.js +21 -0
  37. package/build/services/TestGenerationService.d.ts +1 -1
  38. package/build/tool-phases.js +2 -0
  39. package/build/tools/auth/loginTool.d.ts +1 -1
  40. package/build/tools/auth/loginTool.js +2 -2
  41. package/build/tools/auth/logoutTool.d.ts +1 -1
  42. package/build/tools/auth/logoutTool.js +2 -2
  43. package/build/tools/code-refactor/alignAssertionsTool.d.ts +1 -1
  44. package/build/tools/code-refactor/alignAssertionsTool.js +1 -1
  45. package/build/tools/code-refactor/assertionSkillTools.d.ts +1 -1
  46. package/build/tools/code-refactor/assertionSkillTools.js +2 -2
  47. package/build/tools/code-refactor/codeReuseTool.d.ts +1 -1
  48. package/build/tools/code-refactor/codeReuseTool.js +2 -2
  49. package/build/tools/code-refactor/enhanceAssertionsTool.d.ts +1 -1
  50. package/build/tools/code-refactor/enhanceAssertionsTool.js +1 -1
  51. package/build/tools/code-refactor/modularizationTool.d.ts +1 -1
  52. package/build/tools/code-refactor/modularizationTool.js +2 -2
  53. package/build/tools/enrichTestWithMocksTool.d.ts +1 -1
  54. package/build/tools/enrichTestWithMocksTool.js +2 -2
  55. package/build/tools/executeFleetTool.d.ts +28 -0
  56. package/build/tools/executeFleetTool.js +107 -0
  57. package/build/tools/executeSkyrampTestTool.d.ts +8 -5
  58. package/build/tools/executeSkyrampTestTool.js +13 -5
  59. package/build/tools/fixErrorTool.d.ts +1 -1
  60. package/build/tools/fixErrorTool.js +1 -1
  61. package/build/tools/generate-tests/batchMockGenerationTool.d.ts +6 -57
  62. package/build/tools/generate-tests/batchMockGenerationTool.js +3 -3
  63. package/build/tools/generate-tests/generateBatchScenarioRestTool.d.ts +9 -45
  64. package/build/tools/generate-tests/generateBatchScenarioRestTool.js +29 -11
  65. package/build/tools/generate-tests/generateContractRestTool.d.ts +1 -2
  66. package/build/tools/generate-tests/generateContractRestTool.js +1 -1
  67. package/build/tools/generate-tests/generateE2ERestTool.d.ts +2 -2
  68. package/build/tools/generate-tests/generateE2ERestTool.js +10 -2
  69. package/build/tools/generate-tests/generateFuzzRestTool.d.ts +1 -1
  70. package/build/tools/generate-tests/generateFuzzRestTool.js +1 -1
  71. package/build/tools/generate-tests/generateIntegrationRestTool.d.ts +1 -2
  72. package/build/tools/generate-tests/generateIntegrationRestTool.js +1 -1
  73. package/build/tools/generate-tests/generateLoadRestTool.d.ts +1 -2
  74. package/build/tools/generate-tests/generateLoadRestTool.js +2 -1
  75. package/build/tools/generate-tests/generateMockRestTool.d.ts +1 -2
  76. package/build/tools/generate-tests/generateMockRestTool.js +3 -3
  77. package/build/tools/generate-tests/generateSmokeRestTool.d.ts +1 -1
  78. package/build/tools/generate-tests/generateSmokeRestTool.js +1 -1
  79. package/build/tools/generate-tests/generateUILoadRestTool.d.ts +64 -0
  80. package/build/tools/generate-tests/generateUILoadRestTool.js +512 -0
  81. package/build/tools/generate-tests/generateUIRestTool.d.ts +8 -7
  82. package/build/tools/generate-tests/generateUIRestTool.js +10 -2
  83. package/build/tools/generate-tests/loadTestSchema.d.ts +15 -15
  84. package/build/tools/generate-tests/trace-reuse-guard.d.ts +1 -1
  85. package/build/tools/generateEnrichedIntegrationTestTool.d.ts +1 -2
  86. package/build/tools/generateEnrichedIntegrationTestTool.js +1 -1
  87. package/build/tools/localDevWorkerComposeTool.d.ts +1 -1
  88. package/build/tools/localDevWorkerComposeTool.js +2 -2
  89. package/build/tools/one-click/oneClickTool.d.ts +1 -1
  90. package/build/tools/one-click/oneClickTool.js +1 -1
  91. package/build/tools/preflightMockCheckTool.d.ts +1 -1
  92. package/build/tools/preflightMockCheckTool.js +3 -3
  93. package/build/tools/queryProxyMocksTool.d.ts +1 -1
  94. package/build/tools/queryProxyMocksTool.js +3 -3
  95. package/build/tools/submitReportTool.d.ts +111 -414
  96. package/build/tools/submitReportTool.js +88 -38
  97. package/build/tools/test-management/actionsTool.d.ts +1 -1
  98. package/build/tools/test-management/actionsTool.js +7 -7
  99. package/build/tools/test-management/analyzeChangesTool.d.ts +22 -118
  100. package/build/tools/test-management/analyzeChangesTool.js +4 -4
  101. package/build/tools/test-management/analyzeTestHealthTool.d.ts +1 -1
  102. package/build/tools/test-management/analyzeTestHealthTool.js +4 -4
  103. package/build/tools/test-management/registerTestPlanTool.d.ts +107 -733
  104. package/build/tools/test-management/registerTestPlanTool.js +12 -12
  105. package/build/tools/test-management/resolveScreenTool.d.ts +1 -1
  106. package/build/tools/test-management/resolveScreenTool.js +6 -2
  107. package/build/tools/trace/startTraceCollectionTool.d.ts +1 -1
  108. package/build/tools/trace/startTraceCollectionTool.js +4 -4
  109. package/build/tools/trace/stopTraceCollectionTool.d.ts +1 -1
  110. package/build/tools/trace/stopTraceCollectionTool.js +2 -2
  111. package/build/tools/verifyTestDependenciesTool.d.ts +1 -1
  112. package/build/tools/verifyTestDependenciesTool.js +1 -1
  113. package/build/tools/workspace/initScanWorkspaceTool.d.ts +1 -1
  114. package/build/tools/workspace/initScanWorkspaceTool.js +1 -1
  115. package/build/tools/workspace/initializeWorkspaceTool.d.ts +1 -1
  116. package/build/tools/workspace/initializeWorkspaceTool.js +1 -1
  117. package/build/types/RepositoryAnalysis.d.ts +164 -1170
  118. package/build/types/RepositoryAnalysis.js +12 -10
  119. package/build/types/StepMethod.d.ts +24 -1
  120. package/build/types/StepMethod.js +1 -1
  121. package/build/types/TestTypes.d.ts +41 -133
  122. package/build/types/TestTypes.js +25 -3
  123. package/build/types/TestbotReport.d.ts +4 -1
  124. package/build/utils/assertion-verify/metrics.js +12 -0
  125. package/build/utils/docker.d.ts +29 -7
  126. package/build/utils/docker.js +83 -27
  127. package/build/utils/findUp.d.ts +20 -0
  128. package/build/utils/findUp.js +59 -0
  129. package/build/utils/initAgent.d.ts +3 -3
  130. package/build/utils/initAgent.js +58 -26
  131. package/build/utils/language-helper.js +1 -1
  132. package/build/utils/progress.d.ts +3 -4
  133. package/build/utils/progress.js +4 -4
  134. package/build/utils/repoSdkVersion.d.ts +16 -0
  135. package/build/utils/repoSdkVersion.js +88 -0
  136. package/build/utils/testDependencyPolicy.d.ts +20 -0
  137. package/build/utils/testDependencyPolicy.js +64 -4
  138. package/build/utils/utils-verify/typecheck.js +3 -18
  139. package/build/utils/utils.d.ts +1 -1
  140. package/build/utils/versions.d.ts +3 -3
  141. package/build/utils/versions.js +1 -1
  142. package/build/workspace/workspace.d.ts +117 -538
  143. package/build/workspace/workspace.js +2 -2
  144. package/node_modules/playwright/lib/mcp/skyramp/assertOrder.js +66 -0
  145. package/node_modules/playwright/lib/mcp/skyramp/assertTool.js +12 -3
  146. package/node_modules/playwright/lib/mcp/skyramp/common/liveReadError.js +40 -0
  147. package/node_modules/playwright/lib/mcp/skyramp/loadTraceTool.js +21 -0
  148. package/node_modules/playwright/lib/mcp/skyramp/skyRampImport.js +2 -1
  149. package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +96 -16
  150. package/node_modules/playwright/lib/mcp/test/skyRampExport.js +10 -0
  151. package/node_modules/playwright/node_modules/playwright-core/lib/server/codegen/skyramp/jsonlReader.js +1 -0
  152. package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/recorderRunner.js +22 -0
  153. package/node_modules/playwright/node_modules/playwright-core/lib/utils/isomorphic/elementOrder.js +57 -0
  154. package/node_modules/playwright/node_modules/playwright-core/lib/utils.js +2 -0
  155. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{index.uiPTFncY.js → index.0MJW0uG7.js} +1 -1
  156. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html +1 -1
  157. package/node_modules/playwright/node_modules/playwright-core/package.json +1 -1
  158. package/node_modules/playwright/node_modules/playwright-core/src/server/codegen/skyramp/jsonlReader.ts +1 -1
  159. package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/recorderRunner.ts +26 -1
  160. package/node_modules/playwright/node_modules/playwright-core/src/utils/isomorphic/elementOrder.ts +83 -0
  161. package/node_modules/playwright/node_modules/playwright-core/src/utils.ts +1 -0
  162. package/node_modules/playwright/package.json +1 -1
  163. package/package.json +9 -6
  164. package/plugin/prompts/plan-tests.md +9 -17
  165. package/plugin/skills/enhance-assertions/reference/shared-rules.md +1 -1
  166. package/plugin/skills/enhance-assertions/reference/ui.md +2 -2
  167. package/plugin/skills/fix-test-import-errors/SKILL.md +1 -1
  168. package/plugin/skills/repair-staged-files/SKILL.md +17 -0
@@ -0,0 +1,59 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import { logger } from "./logger.js";
4
+ /** Deeper than any real checkout; a bound so a walk can never run away. */
5
+ const MAX_LEVELS = 64;
6
+ /**
7
+ * The first `<dir>/<candidate>` that exists, checking `from` and then each
8
+ * ancestor, or undefined.
9
+ *
10
+ * With `stopAtRepoRoot`, only the directories up to the nearest one holding
11
+ * `.git` (a directory or a file, so worktrees count) are searched, and a `from`
12
+ * outside any checkout searches nothing: a parent directory is not the
13
+ * repository's to answer for. The walk never passes the filesystem root or
14
+ * MAX_LEVELS.
15
+ */
16
+ export function findUp(from, candidates, { stopAtRepoRoot = false } = {}) {
17
+ const dirs = ancestors(from);
18
+ if (stopAtRepoRoot) {
19
+ const root = dirs.findIndex((dir) => fs.existsSync(path.join(dir, ".git")));
20
+ if (root === -1)
21
+ return undefined;
22
+ dirs.length = root + 1;
23
+ }
24
+ for (const dir of dirs) {
25
+ for (const candidate of candidates) {
26
+ const found = path.join(dir, candidate);
27
+ if (fs.existsSync(found))
28
+ return found;
29
+ }
30
+ }
31
+ return undefined;
32
+ }
33
+ /** `from` and each ancestor, nearest first, up to the filesystem root. */
34
+ function ancestors(from) {
35
+ const dirs = [path.resolve(from)];
36
+ while (dirs.length < MAX_LEVELS) {
37
+ const parent = path.dirname(dirs[dirs.length - 1]);
38
+ if (parent === dirs[dirs.length - 1])
39
+ break;
40
+ dirs.push(parent);
41
+ }
42
+ return dirs;
43
+ }
44
+ /** The package's manifest as Node would find it from `from`: the nearest
45
+ * `node_modules/<pkg>/package.json` walking UP from there. Node's own lookup
46
+ * never reads NODE_PATH for this, and neither does this one. A manifest that
47
+ * does not parse is logged and treated as absent. */
48
+ export function installedManifest(pkg, from, options) {
49
+ const manifest = findUp(from, [path.join("node_modules", pkg, "package.json")], options);
50
+ if (!manifest)
51
+ return undefined;
52
+ try {
53
+ return JSON.parse(fs.readFileSync(manifest, "utf8"));
54
+ }
55
+ catch (error) {
56
+ logger.warning(`Could not read ${manifest}`, { error: String(error) });
57
+ return undefined;
58
+ }
59
+ }
@@ -16,9 +16,9 @@ export declare function ensureSkyrampMdInstalled(): Promise<void>;
16
16
  * Previously both images were always pulled regardless.
17
17
  * - Parallel pulls: missing images are pulled concurrently via Promise.all, halving
18
18
  * wall-clock time when both need to be downloaded.
19
- * - Arch-aware: pull platform is detected from process.arch (linux/arm64 on Apple Silicon,
20
- * linux/amd64 otherwise) with an automatic amd64 fallback if no arm64 manifest exists
21
- * (e.g. skyramp/executor is amd64-only; skyramp/worker supports both).
19
+ * - Arch-aware: the worker's pull platform is detected from process.arch (linux/arm64 on
20
+ * Apple Silicon, linux/amd64 otherwise) with an automatic amd64 fallback if no arm64
21
+ * manifest exists. The executor is always pulled as amd64, the platform it runs as.
22
22
  * - No FFI overhead: direct Dockerode calls replace the Go shared-library bridge,
23
23
  * removing the cost of loading the .so and marshalling output through a C string.
24
24
  * - Cleaner error propagation: structured errors from Dockerode replace parsing a
@@ -4,7 +4,8 @@ import path from "path";
4
4
  import { logger } from "./logger.js";
5
5
  import { SKYRAMP_MD_CONTENT } from "./skyrampMdContent.js";
6
6
  import { EXECUTOR_DOCKER_IMAGE, WORKER_DOCKER_IMAGE } from "./versions.js";
7
- import { dockerImageExistsLocally, pullDockerImage } from "./docker.js";
7
+ import { dockerDaemonOsType, dockerImageExistsLocally, getDockerPlatform, isPlatformUnavailableError, pullDockerImage, } from "./docker.js";
8
+ import { resolveExecutorImage } from "../services/FleetExecutionService.js";
8
9
  // Global skill discovery paths (resolved relative to os.homedir()).
9
10
  const SKILL_INSTALL_PATHS = [
10
11
  {
@@ -83,9 +84,9 @@ export async function ensureSkyrampMdInstalled() {
83
84
  * Previously both images were always pulled regardless.
84
85
  * - Parallel pulls: missing images are pulled concurrently via Promise.all, halving
85
86
  * wall-clock time when both need to be downloaded.
86
- * - Arch-aware: pull platform is detected from process.arch (linux/arm64 on Apple Silicon,
87
- * linux/amd64 otherwise) with an automatic amd64 fallback if no arm64 manifest exists
88
- * (e.g. skyramp/executor is amd64-only; skyramp/worker supports both).
87
+ * - Arch-aware: the worker's pull platform is detected from process.arch (linux/arm64 on
88
+ * Apple Silicon, linux/amd64 otherwise) with an automatic amd64 fallback if no arm64
89
+ * manifest exists. The executor is always pulled as amd64, the platform it runs as.
89
90
  * - No FFI overhead: direct Dockerode calls replace the Go shared-library bridge,
90
91
  * removing the cost of loading the .so and marshalling output through a C string.
91
92
  * - Cleaner error propagation: structured errors from Dockerode replace parsing a
@@ -99,33 +100,64 @@ export async function initCheck() {
99
100
  await ensureSkyrampMdInstalled();
100
101
  logger.info("Skyramp skill files installed/refreshed");
101
102
  }
102
- // Check each image independently and pull only what is missing
103
- const [hasExecutor, hasWorker] = await Promise.all([
104
- dockerImageExistsLocally(EXECUTOR_DOCKER_IMAGE),
105
- dockerImageExistsLocally(WORKER_DOCKER_IMAGE),
106
- ]);
103
+ // Two executors can be in use: skyramp_execute_test always runs the released constant,
104
+ // and skyramp_execute_fleet runs SKYRAMP_EXECUTOR_IMAGE when it is set. Fetch both, or a
105
+ // normal test run pays for the pull the override displaced.
106
+ const fleetExecutor = resolveExecutorImage();
107
+ // Every image is pulled for the daemon's own OS: a released executor is one tag carrying
108
+ // both, and the tag names no platform. Both run paths start the executor as amd64, so it
109
+ // is pulled as amd64 on every host -- an arm64 copy would not be the one the run starts.
110
+ const daemonOs = await dockerDaemonOsType();
111
+ const executorPlatform = daemonOs === "windows" ? "windows/amd64" : "linux/amd64";
112
+ const workerPlatform = daemonOs === "windows" ? "windows/amd64" : getDockerPlatform();
113
+ const executors = [...new Set([EXECUTOR_DOCKER_IMAGE, fleetExecutor])];
114
+ // An executor image without this daemon's OS -- an older release, or a single-OS
115
+ // SKYRAMP_EXECUTOR_IMAGE -- has nothing to pull here. That is a fact about the image,
116
+ // not a broken environment, so its pull is optional.
117
+ const wanted = [
118
+ ...executors.map((image) => ({
119
+ what: "executor",
120
+ image,
121
+ platform: executorPlatform,
122
+ optional: true,
123
+ })),
124
+ {
125
+ what: "worker",
126
+ image: WORKER_DOCKER_IMAGE,
127
+ platform: workerPlatform,
128
+ optional: false,
129
+ },
130
+ ];
131
+ // Presence is checked for all of them at once; only the pulls that follow are conditional.
132
+ const present = await Promise.all(wanted.map((w) => dockerImageExistsLocally(w.image)));
107
133
  const pulls = [];
108
- if (!hasExecutor) {
109
- logger.info("Pulling missing executor image", {
110
- image: EXECUTOR_DOCKER_IMAGE,
111
- platform: "linux/amd64",
112
- });
113
- pulls.push(pullDockerImage(EXECUTOR_DOCKER_IMAGE, "linux/amd64"));
114
- }
115
- if (!hasWorker) {
116
- logger.info("Pulling missing worker image", { image: WORKER_DOCKER_IMAGE });
117
- pulls.push(pullDockerImage(WORKER_DOCKER_IMAGE));
134
+ wanted.forEach(({ what, image, platform, optional }, i) => {
135
+ if (present[i])
136
+ return;
137
+ logger.info(`Pulling missing ${what} image`, { image, platform });
138
+ const pull = pullDockerImage(image, platform);
139
+ // Only an image with nothing for this platform is tolerated, and only when optional.
140
+ // Any other failure -- the network, a rate limit, a full disk -- fails init so it is
141
+ // retried, instead of being reported as a missing variant.
142
+ pulls.push(optional
143
+ ? pull.catch((err) => {
144
+ if (!isPlatformUnavailableError(err))
145
+ throw err;
146
+ logger.warning("Executor image has no variant for this daemon's platform: fleet runs " +
147
+ "need an executor built for it (a release that carries it, or " +
148
+ "SKYRAMP_EXECUTOR_IMAGE)", { image, platform, error: err.message });
149
+ })
150
+ : pull);
151
+ });
152
+ if (pulls.length > 0) {
153
+ await Promise.all(pulls);
154
+ logger.info("Skyramp init completed");
118
155
  }
119
- if (pulls.length === 0) {
156
+ else {
120
157
  logger.info("Skyramp images already present locally, skipping pull", {
121
- executor: EXECUTOR_DOCKER_IMAGE,
122
- worker: WORKER_DOCKER_IMAGE,
158
+ images: wanted.map((w) => w.image),
123
159
  });
124
160
  }
125
- else {
126
- await Promise.all(pulls);
127
- logger.info("Skyramp init completed");
128
- }
129
161
  }
130
162
  let initInFlight = false;
131
163
  let initCompleted = false;
@@ -36,7 +36,7 @@ npx playwright test <test-file>.spec.${extension} --reporter=list
36
36
  * a mock is deployed rather than run as a test — it needs no SDK. */
37
37
  const SDK_DECLARATION = {
38
38
  python: "Declare `skyramp` in the same development manifest.",
39
- javascript: `If @skyramp/skyramp is not already declared, add ${npmSdkSpec} as a devDependency. If it is declared, leave its version alone and report the mismatch if the verifier refuses it.`,
39
+ javascript: `If @skyramp/skyramp is not already declared, add ${npmSdkSpec} as a devDependency. If it is declared, keep its version unless skyramp_execute_test refuses the run for an older SDK.`,
40
40
  java: "Declare `dev.skyramp:skyramp-library` with test scope. An existing manifest resolves the version through its own dependency management; a manifest you created has none, so give a concrete version there.",
41
41
  };
42
42
  /** The SDK line for a language, or nothing when the language has none. */
@@ -1,16 +1,15 @@
1
- import { ServerRequest, ServerNotification } from "@modelcontextprotocol/sdk/types.js";
2
- import { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js";
1
+ import { ServerContext } from "@modelcontextprotocol/server";
3
2
  /**
4
3
  * Reports incremental progress for a long-running tool call. No-ops when the
5
4
  * client did not supply a progress token.
6
5
  */
7
6
  export type ProgressReporter = (progress: number, total: number, message: string) => Promise<void>;
8
7
  /**
9
- * Build a progress reporter bound to a tool handler's `extra`. Sends
8
+ * Build a progress reporter bound to a tool handler's context. Sends
10
9
  * `notifications/progress` only when the client provided a `progressToken`,
11
10
  * otherwise silently no-ops.
12
11
  *
13
12
  * Consolidates the identical closure that previously lived inline in multiple
14
13
  * progress-emitting tools (analyzeChanges, startTraceCollection).
15
14
  */
16
- export declare function makeProgressReporter(extra: RequestHandlerExtra<ServerRequest, ServerNotification>): ProgressReporter;
15
+ export declare function makeProgressReporter(ctx: ServerContext): ProgressReporter;
@@ -1,21 +1,21 @@
1
1
  /**
2
- * Build a progress reporter bound to a tool handler's `extra`. Sends
2
+ * Build a progress reporter bound to a tool handler's context. Sends
3
3
  * `notifications/progress` only when the client provided a `progressToken`,
4
4
  * otherwise silently no-ops.
5
5
  *
6
6
  * Consolidates the identical closure that previously lived inline in multiple
7
7
  * progress-emitting tools (analyzeChanges, startTraceCollection).
8
8
  */
9
- export function makeProgressReporter(extra) {
9
+ export function makeProgressReporter(ctx) {
10
10
  return async (progress, total, message) => {
11
- const progressToken = extra._meta?.progressToken;
11
+ const progressToken = ctx.mcpReq._meta?.progressToken;
12
12
  if (progressToken !== undefined) {
13
13
  try {
14
14
  const notification = {
15
15
  method: "notifications/progress",
16
16
  params: { progressToken, progress, total, message },
17
17
  };
18
- await extra.sendNotification(notification);
18
+ await ctx.mcpReq.notify(notification);
19
19
  }
20
20
  catch {
21
21
  // Progress is best-effort; a disconnected client should not fail the tool.
@@ -0,0 +1,16 @@
1
+ import { SdkEcosystem } from "./testDependencyPolicy.js";
2
+ /**
3
+ * The Skyramp SDK version the repository that owns `testFile` runs, as far as it
4
+ * can be read without its toolchain: the installed npm package, else the exact
5
+ * pin in the nearest manifest. Undefined when neither says. Both lookups stop at
6
+ * the repository root.
7
+ */
8
+ export declare function repoSkyrampSdkVersion(testFile: string, ecosystem: SdkEcosystem): string | undefined;
9
+ /**
10
+ * SKYR-4548: the refusal for a host run whose repository uses an older Skyramp
11
+ * SDK than the one that generated the test, or undefined when the run may go
12
+ * ahead. Such a run fails at import, which reads as a broken test. No readable
13
+ * version means no refusal: a missing SDK is an import error that
14
+ * fix-test-import-errors owns.
15
+ */
16
+ export declare function staleSdkRefusal(testFile: string, language: string, serverVersion?: string): string | undefined;
@@ -0,0 +1,88 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import semver from "semver";
4
+ import { ProgrammingLanguage } from "../types/TestTypes.js";
5
+ import { findUp, installedManifest } from "./findUp.js";
6
+ import { logger } from "./logger.js";
7
+ import { SKYRAMP_SDK_VERSION } from "./skyrampSdkVersion.js";
8
+ import { manifestNames, pinnedSkyrampSdkVersion, SKYRAMP_SDK_PACKAGE, } from "./testDependencyPolicy.js";
9
+ const ECOSYSTEM = {
10
+ [ProgrammingLanguage.TYPESCRIPT]: "javascript",
11
+ [ProgrammingLanguage.JAVASCRIPT]: "javascript",
12
+ [ProgrammingLanguage.PYTHON]: "python",
13
+ [ProgrammingLanguage.JAVA]: "java",
14
+ };
15
+ /** The one edit that moves the repository onto the SDK that generated the
16
+ * test, and that skyramp_verify_test_dependencies accepts. */
17
+ const PIN_EDIT = {
18
+ javascript: (v) => `Declare ${SKYRAMP_SDK_PACKAGE.javascript} under devDependencies only (move it out of dependencies if it is there), at exactly "${SKYRAMP_SDK_PACKAGE.javascript}": "${v}" with no range operator`,
19
+ python: (v) => `Change only the requirement to ${SKYRAMP_SDK_PACKAGE.python}==${v}`,
20
+ java: (v) => `Change only the ${SKYRAMP_SDK_PACKAGE.java} version to ${v}`,
21
+ };
22
+ /**
23
+ * The Skyramp SDK version the repository that owns `testFile` runs, as far as it
24
+ * can be read without its toolchain: the installed npm package, else the exact
25
+ * pin in the nearest manifest. Undefined when neither says. Both lookups stop at
26
+ * the repository root.
27
+ */
28
+ export function repoSkyrampSdkVersion(testFile, ecosystem) {
29
+ const from = path.dirname(path.resolve(testFile));
30
+ if (ecosystem === "javascript") {
31
+ const version = installedManifest(SKYRAMP_SDK_PACKAGE.javascript, from, {
32
+ stopAtRepoRoot: true,
33
+ })?.version;
34
+ if (typeof version === "string")
35
+ return version;
36
+ }
37
+ const names = manifestNames(ecosystem);
38
+ const nearest = findUp(from, names, { stopAtRepoRoot: true });
39
+ if (!nearest)
40
+ return undefined;
41
+ for (const name of names) {
42
+ const manifest = path.join(path.dirname(nearest), name);
43
+ if (!fs.existsSync(manifest))
44
+ continue;
45
+ try {
46
+ const pinned = pinnedSkyrampSdkVersion(name, fs.readFileSync(manifest, "utf8"));
47
+ if (pinned)
48
+ return pinned;
49
+ }
50
+ catch (error) {
51
+ logger.warning(`Could not read the Skyramp SDK version in ${manifest}`, {
52
+ error: String(error),
53
+ });
54
+ }
55
+ }
56
+ return undefined;
57
+ }
58
+ /**
59
+ * SKYR-4548: the refusal for a host run whose repository uses an older Skyramp
60
+ * SDK than the one that generated the test, or undefined when the run may go
61
+ * ahead. Such a run fails at import, which reads as a broken test. No readable
62
+ * version means no refusal: a missing SDK is an import error that
63
+ * fix-test-import-errors owns.
64
+ */
65
+ export function staleSdkRefusal(testFile, language, serverVersion = SKYRAMP_SDK_VERSION) {
66
+ const ecosystem = ECOSYSTEM[language];
67
+ if (!ecosystem)
68
+ return undefined;
69
+ const repoVersion = repoSkyrampSdkVersion(testFile, ecosystem);
70
+ if (!isOlderRelease(repoVersion, serverVersion))
71
+ return undefined;
72
+ return (`Not executed: this repository uses Skyramp SDK ${repoVersion}, but the test was generated by ${serverVersion}. ` +
73
+ `${PIN_EDIT[ecosystem](serverVersion)}, update the lockfile if the repository commits one, reinstall, ` +
74
+ `run skyramp_verify_test_dependencies, and call this tool again. Report the SDK change as an issuesFound entry with category config.`);
75
+ }
76
+ /** True when `repo` is an older release than `server`. Pre-release tags are
77
+ * ignored (1.3.47-rc.1 is 1.3.47), and a version semver cannot read is never
78
+ * older: the gate must not refuse a run on a guess. */
79
+ function isOlderRelease(repo, server) {
80
+ const a = repo ? semver.parse(repo) : null;
81
+ const b = semver.parse(server);
82
+ if (!a || !b) {
83
+ if (repo)
84
+ logger.warning(`Cannot compare Skyramp SDK ${repo} to ${server}`);
85
+ return false;
86
+ }
87
+ return a.compareMain(b) < 0;
88
+ }
@@ -7,3 +7,23 @@ export interface TestDependencyVerification {
7
7
  violations: string[];
8
8
  }
9
9
  export declare function verifyTestDependencyChanges(repositoryPath: string): Promise<TestDependencyVerification>;
10
+ /** The Skyramp SDK's name in each ecosystem. All three are released in lockstep
11
+ * with the npm SDK this server imports, so SKYRAMP_SDK_VERSION is the version
12
+ * that generated a test in any of them. */
13
+ export declare const SKYRAMP_SDK_PACKAGE: {
14
+ readonly javascript: "@skyramp/skyramp";
15
+ readonly python: "skyramp";
16
+ readonly java: "dev.skyramp:skyramp-library";
17
+ };
18
+ export type SdkEcosystem = keyof typeof SKYRAMP_SDK_PACKAGE;
19
+ /** The manifest file names an ecosystem declares its dependencies in. */
20
+ export declare function manifestNames(ecosystem: SdkEcosystem): readonly string[];
21
+ /**
22
+ * The exact Skyramp SDK version a manifest pins where the change verifier lets
23
+ * the agent raise it, or undefined: the SDK is absent, declared as a range,
24
+ * resolved elsewhere (a Maven property or BOM, a Gradle catalog), or pinned in
25
+ * a place the verifier refuses to change (`dependencies`, requirements.txt, a
26
+ * Maven block without test scope, a Gradle `implementation` line). A refusal
27
+ * over such a pin would have no fix the agent may make.
28
+ */
29
+ export declare function pinnedSkyrampSdkVersion(fileName: string, raw: string): string | undefined;
@@ -4,6 +4,7 @@ import * as path from "path";
4
4
  import { parse as parseToml } from "smol-toml";
5
5
  import { promisify } from "util";
6
6
  import fg from "fast-glob";
7
+ import semver from "semver";
7
8
  import { canonicalJson as canonical, isPlainObject as isObject, } from "./canonicalJson.js";
8
9
  import { listChangedFiles } from "./reportVerification.js";
9
10
  import { contentAtHead } from "./utils-verify/head.js";
@@ -278,18 +279,77 @@ function verifyPackageJson(file, beforeRaw, afterRaw, violations) {
278
279
  canonical(beforeDev) !== canonical(afterDev));
279
280
  }
280
281
  /** The SDK the tests were generated by is the one this server imports, so the
281
- * repository must declare exactly that version. Only the npm spelling carries
282
- * a version: the Python and Java manifests declare the SDK without one, so
283
- * neither is checked here.
282
+ * repository must declare exactly that version. Only a changed npm entry is
283
+ * checked here: the guidance declares the Python and Java SDK without a
284
+ * version. A pin a repository already has is read by pinnedSkyrampSdkVersion.
284
285
  *
285
286
  * The specifier must be the bare version. A range is refused, including
286
287
  * `^1.2.3`: it permits 1.3.0, so a clean install elsewhere can resolve an SDK
287
288
  * that did not write these tests, which is the whole thing this prevents. */
288
289
  function declaresServerSdkVersion(name, specifier) {
289
- if (name !== "@skyramp/skyramp")
290
+ if (name !== SKYRAMP_SDK_PACKAGE.javascript)
290
291
  return true;
291
292
  return specifier.trim() === SKYRAMP_SDK_VERSION;
292
293
  }
294
+ /** The Skyramp SDK's name in each ecosystem. All three are released in lockstep
295
+ * with the npm SDK this server imports, so SKYRAMP_SDK_VERSION is the version
296
+ * that generated a test in any of them. */
297
+ export const SKYRAMP_SDK_PACKAGE = {
298
+ javascript: "@skyramp/skyramp",
299
+ python: "skyramp",
300
+ java: "dev.skyramp:skyramp-library",
301
+ };
302
+ /** The manifest file names an ecosystem declares its dependencies in. */
303
+ export function manifestNames(ecosystem) {
304
+ return DEPENDENCY_FILES[ecosystem].manifests;
305
+ }
306
+ /**
307
+ * The exact Skyramp SDK version a manifest pins where the change verifier lets
308
+ * the agent raise it, or undefined: the SDK is absent, declared as a range,
309
+ * resolved elsewhere (a Maven property or BOM, a Gradle catalog), or pinned in
310
+ * a place the verifier refuses to change (`dependencies`, requirements.txt, a
311
+ * Maven block without test scope, a Gradle `implementation` line). A refusal
312
+ * over such a pin would have no fix the agent may make.
313
+ */
314
+ export function pinnedSkyrampSdkVersion(fileName, raw) {
315
+ switch (fileName) {
316
+ case "package.json":
317
+ return exactVersion(stringMap(JSON.parse(raw).devDependencies)[SKYRAMP_SDK_PACKAGE.javascript]);
318
+ case "requirements-dev.txt":
319
+ return pep440Pin(requirementsMap(raw).dependencies[SKYRAMP_SDK_PACKAGE.python]);
320
+ case "pyproject.toml": {
321
+ // Poetry reads a bare version as an exact pin; PEP 735 carries `==`.
322
+ const spec = pythonDevView(raw).dependencies[SKYRAMP_SDK_PACKAGE.python];
323
+ return pep440Pin(spec) ?? exactVersion(spec);
324
+ }
325
+ case "pom.xml": {
326
+ const blocks = mavenView(raw.replace(/<!--[\s\S]*?-->/g, ""))
327
+ .dependencies[SKYRAMP_SDK_PACKAGE.java];
328
+ return blocks?.length === 1 && !mavenDependency(blocks[0]).error
329
+ ? exactVersion(singleXmlTag(blocks[0], "version"))
330
+ : undefined;
331
+ }
332
+ case "build.gradle":
333
+ case "build.gradle.kts": {
334
+ const lines = gradleView(raw).dependencies[SKYRAMP_SDK_PACKAGE.java];
335
+ if (lines?.length !== 1)
336
+ return undefined;
337
+ const [configuration, value] = lines[0].split("\0");
338
+ return configuration === "testImplementation" ||
339
+ configuration === "testRuntimeOnly"
340
+ ? exactVersion(value.split(":")[2])
341
+ : undefined;
342
+ }
343
+ default:
344
+ return undefined;
345
+ }
346
+ }
347
+ function exactVersion(spec) {
348
+ return (spec && semver.valid(spec.trim())) || undefined;
349
+ }
350
+ function pep440Pin(requirement) {
351
+ return exactVersion(/==\s*([^,;\s]+)/.exec(requirement ?? "")?.[1]);
352
+ }
293
353
  function verifyPyproject(file, beforeRaw, afterRaw, violations) {
294
354
  const before = pythonDevView(beforeRaw);
295
355
  const after = pythonDevView(afterRaw);
@@ -1,5 +1,5 @@
1
1
  import * as path from "path";
2
- import { readFileSync } from "fs";
2
+ import { installedManifest } from "../findUp.js";
3
3
  import { fileURLToPath } from "url";
4
4
  let tsModule;
5
5
  /** The compiler, loaded on first use and once per process. Loaded statically it
@@ -64,6 +64,8 @@ function executorPeerPackages(mcpRoot) {
64
64
  return peers;
65
65
  peers = [];
66
66
  for (const pkg of EXECUTOR_PROVIDED_PACKAGES) {
67
+ // Nearest `node_modules/<pkg>` walking UP: `npm install @skyramp/mcp`,
68
+ // testbot's default, hoists the MCP's dependencies BESIDE it, not under it.
67
69
  const manifest = installedManifest(pkg, mcpRoot);
68
70
  if (manifest)
69
71
  peers.push(...Object.keys(manifest.peerDependencies ?? {}));
@@ -74,23 +76,6 @@ function executorPeerPackages(mcpRoot) {
74
76
  peersByRoot.set(mcpRoot, peers);
75
77
  return peers;
76
78
  }
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
79
  const isPackageOrSubpath = (specifier, packages) => packages.some((p) => specifier === p || specifier.startsWith(`${p}/`));
95
80
  function executorProvides(specifier) {
96
81
  return isPackageOrSubpath(specifier, EXECUTOR_PROVIDED_PACKAGES);
@@ -1,4 +1,4 @@
1
- import { CallToolResult, ReadResourceResult } from "@modelcontextprotocol/sdk/types.js";
1
+ import { CallToolResult, ReadResourceResult } from "@modelcontextprotocol/server";
2
2
  export declare function toolError(message: string): CallToolResult;
3
3
  /**
4
4
  * Build a successful tool result whose payload is delivered on BOTH MCP result
@@ -1,4 +1,4 @@
1
- export declare const SKYRAMP_IMAGE_VERSION = "v1.3.47";
2
- export declare const EXECUTOR_DOCKER_IMAGE = "skyramp/executor:v1.3.47";
3
- export declare const WORKER_DOCKER_IMAGE = "skyramp/worker:v1.3.47";
1
+ export declare const SKYRAMP_IMAGE_VERSION = "v1.3.50";
2
+ export declare const EXECUTOR_DOCKER_IMAGE = "skyramp/executor:v1.3.50";
3
+ export declare const WORKER_DOCKER_IMAGE = "skyramp/worker:v1.3.50";
4
4
  export declare const WORKER_CONTROL_PORT = 35142;
@@ -1,4 +1,4 @@
1
- export const SKYRAMP_IMAGE_VERSION = "v1.3.47";
1
+ export const SKYRAMP_IMAGE_VERSION = "v1.3.50";
2
2
  export const EXECUTOR_DOCKER_IMAGE = `skyramp/executor:${SKYRAMP_IMAGE_VERSION}`;
3
3
  export const WORKER_DOCKER_IMAGE = `skyramp/worker:${SKYRAMP_IMAGE_VERSION}`;
4
4
  // Control port the Skyramp worker listens on (SDK `CONTAINER_PORT`).