@skyramp/mcp 0.3.2 → 0.3.4
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/index.js +47 -2
- package/build/playwright/PlaywrightTraceService.d.ts +8 -0
- package/build/playwright/PlaywrightTraceService.js +1 -0
- package/build/playwright/registerPlaywrightTools.js +42 -1
- package/build/prompts/enhance-assertions/sharedAssertionRules.js +19 -0
- package/build/prompts/pom-aware-code-reuse.js +17 -8
- package/build/prompts/test-maintenance/actionsInstructions.js +2 -2
- package/build/prompts/test-recommendation/analysisOutputPrompt.js +1 -4
- package/build/prompts/test-recommendation/recommendationSections.d.ts +1 -1
- package/build/prompts/test-recommendation/recommendationSections.js +5 -5
- package/build/prompts/test-recommendation/test-recommendation-prompt.js +13 -7
- package/build/prompts/testbot/testbot-prompts.d.ts +2 -12
- package/build/prompts/testbot/testbot-prompts.js +28 -16
- package/build/recommendation/discriminators.d.ts +7 -1
- package/build/recommendation/discriminators.js +16 -3
- package/build/resources/testbotResource.js +20 -4
- package/build/services/ScenarioGenerationService.js +5 -2
- package/build/services/TestExecutionService.d.ts +13 -8
- package/build/services/TestExecutionService.js +73 -26
- package/build/services/TestGenerationService.js +24 -9
- package/build/services/containerEnv.d.ts +12 -1
- package/build/services/containerEnv.js +118 -1
- package/build/tools/executeSkyrampTestTool.d.ts +9 -0
- package/build/tools/executeSkyrampTestTool.js +20 -6
- package/build/tools/execution-video-state.d.ts +21 -0
- package/build/tools/execution-video-state.js +51 -0
- package/build/tools/generate-tests/generateBatchScenarioRestTool.js +31 -11
- package/build/tools/generate-tests/planGuard.d.ts +5 -5
- package/build/tools/generate-tests/planGuard.js +5 -17
- package/build/tools/queryProxyMocksTool.js +0 -1
- package/build/tools/submitReportTool.d.ts +83 -10
- package/build/tools/submitReportTool.js +179 -29
- package/build/tools/test-management/actionsTool.js +52 -41
- package/build/tools/test-management/analyzeChangesTool.d.ts +11 -0
- package/build/tools/test-management/analyzeChangesTool.js +37 -33
- package/build/tools/test-management/analyzeTestHealthTool.js +3 -3
- package/build/tools/test-management/registerTestPlanTool.js +113 -31
- package/build/types/TestAnalysis.d.ts +7 -3
- package/build/types/TestExecution.d.ts +14 -0
- package/build/types/TestTypes.js +3 -2
- package/build/types/TestbotPromptOptions.d.ts +34 -0
- package/build/types/TestbotPromptOptions.js +1 -0
- package/build/types/TestbotReport.d.ts +10 -0
- package/build/types/TestbotReport.js +10 -1
- package/build/types/index.d.ts +2 -0
- package/build/types/index.js +1 -0
- package/build/utils/AnalysisStateManager.d.ts +36 -2
- package/build/utils/AnalysisStateManager.js +34 -13
- package/build/utils/frontendSelectors.js +0 -1
- package/build/utils/gitStaging.d.ts +5 -0
- package/build/utils/gitStaging.js +1 -1
- package/build/utils/pom-catalog.d.ts +23 -0
- package/build/utils/pom-catalog.js +30 -0
- package/build/utils/pom-scope/pom-files.d.ts +14 -0
- package/build/utils/pom-scope/pom-files.js +32 -6
- package/build/utils/pom-scope/testIdDiscovery.d.ts +40 -0
- package/build/utils/pom-scope/testIdDiscovery.js +104 -0
- package/build/utils/reportVerification.d.ts +7 -2
- package/build/utils/reportVerification.js +9 -3
- package/build/utils/scenarioDrafting.js +7 -1
- package/build/utils/skyrampMdContent.d.ts +1 -1
- package/build/utils/skyrampMdContent.js +1 -1
- package/build/utils/urlPath.d.ts +37 -0
- package/build/utils/urlPath.js +55 -0
- package/build/utils/utils.d.ts +45 -0
- package/build/utils/utils.js +50 -0
- package/build/utils/versions.d.ts +3 -3
- package/build/utils/versions.js +1 -1
- package/build/utils/workspaceAuth.d.ts +15 -15
- package/build/utils/workspaceAuth.js +32 -17
- package/build/workspace/queryParamResolution.d.ts +93 -0
- package/build/workspace/queryParamResolution.js +201 -0
- package/build/workspace/workspace.d.ts +104 -0
- package/build/workspace/workspace.js +24 -0
- package/node_modules/playwright/ThirdPartyNotices.txt +319 -266
- package/node_modules/playwright/lib/dom-analyzer/blueprint.js +154 -27
- package/node_modules/playwright/lib/dom-analyzer/crawler.js +2 -2
- package/node_modules/playwright/lib/mcp/browser/tools/pageBlueprint.js +1 -1
- package/node_modules/playwright/lib/mcp/browser/tools/sitemap.js +6 -2
- package/node_modules/playwright/lib/mcp/skyramp/loadTraceTool.js +3 -2
- package/node_modules/playwright/lib/mcp/skyramp/resultCode.js +4 -3
- package/node_modules/playwright/lib/mcp/skyramp/skyRampImport.js +15 -3
- package/node_modules/playwright/lib/mcp/skyramp/specImport.js +781 -0
- package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +14 -3
- package/node_modules/playwright/lib/mcp/test/resultCode.test.js +2 -1
- package/node_modules/playwright/lib/mcp/test/skyRampExport.js +1 -1
- package/node_modules/playwright/lib/mcp/test/skyRampExport.test.js +30 -0
- package/node_modules/playwright/lib/transform/babelBundleImpl.js +200 -199
- package/node_modules/playwright/node_modules/playwright-core/ThirdPartyNotices.txt +3 -3
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/.package-lock.json +3 -3
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/adapter/aws-lambda/handler.js +16 -25
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/adapter/bun/websocket.js +3 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/adapter/lambda-edge/handler.js +20 -4
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/adapter/aws-lambda/handler.js +16 -25
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/adapter/bun/websocket.js +3 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/adapter/lambda-edge/handler.js +20 -4
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/client/client.js +10 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/client/utils.js +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/helper/css/common.js +3 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/helper/css/index.js +9 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/jsx/base.js +8 -14
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/jsx/components.js +41 -21
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/jsx/context.js +131 -5
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/jsx/streaming.js +9 -7
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/middleware/bearer-auth/index.js +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/middleware/cache/index.js +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/middleware/compress/index.js +2 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/middleware/cors/index.js +2 -5
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/middleware/etag/index.js +2 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/middleware/language/language.js +10 -32
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/middleware/method-override/index.js +5 -3
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/middleware/serve-static/index.js +2 -2
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/middleware/timing/timing.js +3 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/router/trie-router/node.js +9 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/utils/body.js +12 -4
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/utils/buffer.js +2 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/utils/ipaddr.js +6 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/validator/validator.js +3 -3
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/client/client.js +10 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/client/utils.js +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/helper/css/common.js +3 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/helper/css/index.js +9 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/jsx/base.js +15 -15
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/jsx/components.js +42 -22
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/jsx/context.js +129 -5
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/jsx/streaming.js +10 -8
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/middleware/bearer-auth/index.js +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/middleware/cache/index.js +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/middleware/compress/index.js +2 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/middleware/cors/index.js +2 -5
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/middleware/etag/index.js +2 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/middleware/language/language.js +10 -32
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/middleware/method-override/index.js +5 -3
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/middleware/serve-static/index.js +2 -2
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/middleware/timing/timing.js +3 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/router/trie-router/node.js +9 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/adapter/aws-lambda/handler.d.ts +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/adapter/lambda-edge/handler.d.ts +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/helper/websocket/index.d.ts +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/jsx/base.d.ts +1 -3
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/jsx/context.d.ts +39 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/middleware/context-storage/index.d.ts +2 -2
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/middleware/language/language.d.ts +18 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/utils/body.d.ts +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/utils/types.d.ts +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/utils/body.js +12 -4
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/utils/buffer.js +2 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/utils/ipaddr.js +6 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/validator/validator.js +3 -3
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/package.json +29 -22
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/package-lock.json +3 -3
- package/node_modules/playwright/node_modules/playwright-core/lib/generated/pollingRecorderSource.js +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/server/codegen/skyramp/jsonlReader.js +5 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/server/codegen/skyramp/specReader.js +781 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/recorderApp.js +25 -6
- package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/skyramp/replayEngine.js +3 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/utils.js +2 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/htmlReport/index.html +253 -27
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/assets/{codeMirrorModule-D0BjbCb7.js → codeMirrorModule-DtudTj_v.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/assets/index-BpDwp16L.js +422 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/index.html +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{codeMirrorModule-Bzd72-bG.js → codeMirrorModule-FNMuBzX1.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-Co9upU5h.js +1035 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.DXNIQ_dx.js +2 -0
- 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/uiMode.CIKB3XSv.js +5 -0
- 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/node_modules/playwright-core/src/server/codegen/skyramp/jsonlReader.ts +4 -0
- package/node_modules/playwright/node_modules/playwright-core/src/server/codegen/skyramp/specReader.ts +1028 -0
- package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/recorderApp.ts +31 -8
- package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/skyramp/replayEngine.ts +1 -0
- package/node_modules/playwright/node_modules/playwright-core/src/utils.ts +1 -0
- package/node_modules/playwright/package.json +1 -1
- package/package.json +10 -6
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/tsconfig.build.tsbuildinfo +0 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/assets/index-lvTRGFx-.js +0 -193
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-DzxTioTK.js +0 -809
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.BGc30U3S.js +0 -2
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.IaDrb29A.js +0 -5
|
@@ -10,6 +10,11 @@ export declare function isInsideDir(child: string, parent: string): boolean;
|
|
|
10
10
|
* is unset or the path is already inside it.
|
|
11
11
|
*/
|
|
12
12
|
export declare function resolveOutputDir(outputDir: string, testsRepoDir: string | undefined): string;
|
|
13
|
+
/**
|
|
14
|
+
* Detect the git repository root for a given file or directory path.
|
|
15
|
+
* Returns undefined if detection fails (not inside a git repo).
|
|
16
|
+
*/
|
|
17
|
+
export declare function detectGitRoot(filePath: string): string | undefined;
|
|
13
18
|
/**
|
|
14
19
|
* Stages a file path an MCP tool just wrote into the git index by
|
|
15
20
|
* running `git add -- <path>`.
|
|
@@ -33,7 +33,7 @@ const execFileAsync = promisify(execFile);
|
|
|
33
33
|
* Detect the git repository root for a given file or directory path.
|
|
34
34
|
* Returns undefined if detection fails (not inside a git repo).
|
|
35
35
|
*/
|
|
36
|
-
function detectGitRoot(filePath) {
|
|
36
|
+
export function detectGitRoot(filePath) {
|
|
37
37
|
if (!path.isAbsolute(filePath))
|
|
38
38
|
return undefined;
|
|
39
39
|
try {
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export declare const POM_CATALOG_FILENAME = "skyramp-pom-catalog.md";
|
|
2
|
+
/**
|
|
3
|
+
* The one canonical path for a test file's `skyramp-pom-catalog.md`:
|
|
4
|
+
* `<repoRoot>/.skyramp/skyramp-pom-catalog.md`.
|
|
5
|
+
*
|
|
6
|
+
* The catalog is cross-run state, and one path per repository is what keeps it
|
|
7
|
+
* usable — two runs writing at different depths fork the cache so neither finds
|
|
8
|
+
* the other's. It sits beside `workspace.yml` rather than in the customer's test
|
|
9
|
+
* directory because it is generated tooling state, not a deliverable, and
|
|
10
|
+
* delivery stages it by name (SKYR-4125).
|
|
11
|
+
*
|
|
12
|
+
* The root is resolved in priority order:
|
|
13
|
+
* 1. `getTestsRepoDir()` — the test-repo clone root set by `skyramp_analyze_changes`
|
|
14
|
+
* when `testsRepoDir` is provided. In cross-repo delivery testbot commits ONLY
|
|
15
|
+
* to this clone, so the catalog must anchor here or it is never staged and the
|
|
16
|
+
* cache dies silently.
|
|
17
|
+
* 2. `detectGitRoot` — the fallback for same-repo runs (where nothing sets the
|
|
18
|
+
* tests-repo dir) and for the NL path, where a developer drives
|
|
19
|
+
* `skyramp_ui_test_generation`/`skyramp_reuse_code` directly without ever
|
|
20
|
+
* calling `skyramp_analyze_changes`.
|
|
21
|
+
* 3. The test file's own directory, for a path in no repository at all.
|
|
22
|
+
*/
|
|
23
|
+
export declare function resolvePomCatalogPath(testFile: string): string;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { getTestsRepoDir } from "./AnalysisStateManager.js";
|
|
3
|
+
import { detectGitRoot } from "./gitStaging.js";
|
|
4
|
+
export const POM_CATALOG_FILENAME = "skyramp-pom-catalog.md";
|
|
5
|
+
/**
|
|
6
|
+
* The one canonical path for a test file's `skyramp-pom-catalog.md`:
|
|
7
|
+
* `<repoRoot>/.skyramp/skyramp-pom-catalog.md`.
|
|
8
|
+
*
|
|
9
|
+
* The catalog is cross-run state, and one path per repository is what keeps it
|
|
10
|
+
* usable — two runs writing at different depths fork the cache so neither finds
|
|
11
|
+
* the other's. It sits beside `workspace.yml` rather than in the customer's test
|
|
12
|
+
* directory because it is generated tooling state, not a deliverable, and
|
|
13
|
+
* delivery stages it by name (SKYR-4125).
|
|
14
|
+
*
|
|
15
|
+
* The root is resolved in priority order:
|
|
16
|
+
* 1. `getTestsRepoDir()` — the test-repo clone root set by `skyramp_analyze_changes`
|
|
17
|
+
* when `testsRepoDir` is provided. In cross-repo delivery testbot commits ONLY
|
|
18
|
+
* to this clone, so the catalog must anchor here or it is never staged and the
|
|
19
|
+
* cache dies silently.
|
|
20
|
+
* 2. `detectGitRoot` — the fallback for same-repo runs (where nothing sets the
|
|
21
|
+
* tests-repo dir) and for the NL path, where a developer drives
|
|
22
|
+
* `skyramp_ui_test_generation`/`skyramp_reuse_code` directly without ever
|
|
23
|
+
* calling `skyramp_analyze_changes`.
|
|
24
|
+
* 3. The test file's own directory, for a path in no repository at all.
|
|
25
|
+
*/
|
|
26
|
+
export function resolvePomCatalogPath(testFile) {
|
|
27
|
+
const abs = path.resolve(testFile);
|
|
28
|
+
const root = getTestsRepoDir() ?? detectGitRoot(abs) ?? path.dirname(abs);
|
|
29
|
+
return path.join(root, ".skyramp", POM_CATALOG_FILENAME);
|
|
30
|
+
}
|
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spec/test basenames. POM discovery excludes them; {@link globSpecFiles}
|
|
3
|
+
* targets them.
|
|
4
|
+
*/
|
|
5
|
+
export declare const SPEC_BASENAME: RegExp;
|
|
1
6
|
export declare function globPomFiles(root: string): Promise<string[]>;
|
|
7
|
+
/**
|
|
8
|
+
* Spec files anywhere under `root`, by basename rather than by directory.
|
|
9
|
+
*
|
|
10
|
+
* Suites are often grouped by feature or customer rather than under `tests/`
|
|
11
|
+
* (e.g. `box/workflow.spec.ts`), so the directory name gives nothing away — but
|
|
12
|
+
* the filename always does. Keeps the cypress/`__tests__` trees that POM
|
|
13
|
+
* discovery skips: here the tests are the subject, not the thing to skip past.
|
|
14
|
+
*/
|
|
15
|
+
export declare function globSpecFiles(root: string): Promise<string[]>;
|
|
2
16
|
/** Fallback discovery for repos whose POM layer doesn't match the conventional glob patterns. */
|
|
3
17
|
export declare function globAllSourceFiles(root: string): Promise<string[]>;
|
|
@@ -16,18 +16,24 @@ const POM_GLOBS = [
|
|
|
16
16
|
"**/{test,tests,e2e,playwright}/**/{lib,utils,helpers,support}/**/*.{ts,js}",
|
|
17
17
|
"**/{testUtils,test-utils,testHelpers,test-helpers}.{ts,js}",
|
|
18
18
|
];
|
|
19
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Spec/test basenames. POM discovery excludes them; {@link globSpecFiles}
|
|
21
|
+
* targets them.
|
|
22
|
+
*/
|
|
23
|
+
export const SPEC_BASENAME = /\.(spec|test|cy)\.(ts|js|tsx|jsx|mjs|cjs)$/;
|
|
20
24
|
const ALL_SOURCE_GLOBS = ["**/*.{ts,js,tsx,jsx,mjs,cjs}"];
|
|
21
|
-
|
|
25
|
+
/** Never source: dependencies, build output, VCS metadata. */
|
|
26
|
+
const BUILD_IGNORE = [
|
|
22
27
|
"**/node_modules/**",
|
|
23
28
|
"**/dist/**",
|
|
24
29
|
"**/build/**",
|
|
25
30
|
"**/coverage/**",
|
|
26
31
|
"**/.git/**",
|
|
27
32
|
"**/vendor/**",
|
|
28
|
-
"**/cypress/**",
|
|
29
|
-
"**/__tests__/**",
|
|
30
33
|
];
|
|
34
|
+
/** Test trees a POM is not expected to live in. Not skipped when the tests themselves are the subject. */
|
|
35
|
+
const TEST_TREE_IGNORE = ["**/cypress/**", "**/__tests__/**"];
|
|
36
|
+
const ALL_SOURCE_IGNORE = [...BUILD_IGNORE, ...TEST_TREE_IGNORE];
|
|
31
37
|
export async function globPomFiles(root) {
|
|
32
38
|
const matches = await fg(POM_GLOBS, {
|
|
33
39
|
cwd: root,
|
|
@@ -37,7 +43,27 @@ export async function globPomFiles(root) {
|
|
|
37
43
|
followSymbolicLinks: false,
|
|
38
44
|
});
|
|
39
45
|
return [...new Set(matches)]
|
|
40
|
-
.filter((f) => !
|
|
46
|
+
.filter((f) => !SPEC_BASENAME.test(path.basename(f)))
|
|
47
|
+
.sort();
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Spec files anywhere under `root`, by basename rather than by directory.
|
|
51
|
+
*
|
|
52
|
+
* Suites are often grouped by feature or customer rather than under `tests/`
|
|
53
|
+
* (e.g. `box/workflow.spec.ts`), so the directory name gives nothing away — but
|
|
54
|
+
* the filename always does. Keeps the cypress/`__tests__` trees that POM
|
|
55
|
+
* discovery skips: here the tests are the subject, not the thing to skip past.
|
|
56
|
+
*/
|
|
57
|
+
export async function globSpecFiles(root) {
|
|
58
|
+
const matches = await fg(ALL_SOURCE_GLOBS, {
|
|
59
|
+
cwd: root,
|
|
60
|
+
absolute: true,
|
|
61
|
+
ignore: BUILD_IGNORE,
|
|
62
|
+
suppressErrors: true,
|
|
63
|
+
followSymbolicLinks: false,
|
|
64
|
+
});
|
|
65
|
+
return [...new Set(matches)]
|
|
66
|
+
.filter((f) => SPEC_BASENAME.test(path.basename(f)))
|
|
41
67
|
.sort();
|
|
42
68
|
}
|
|
43
69
|
/** Fallback discovery for repos whose POM layer doesn't match the conventional glob patterns. */
|
|
@@ -50,6 +76,6 @@ export async function globAllSourceFiles(root) {
|
|
|
50
76
|
followSymbolicLinks: false,
|
|
51
77
|
});
|
|
52
78
|
return [...new Set(matches)]
|
|
53
|
-
.filter((f) => !
|
|
79
|
+
.filter((f) => !SPEC_BASENAME.test(path.basename(f)))
|
|
54
80
|
.sort();
|
|
55
81
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which attribute does this repository use for test ids?
|
|
3
|
+
*
|
|
4
|
+
* The blueprint capture anchors every element's xpath on a test-id attribute
|
|
5
|
+
* when it can find one, and assumes Playwright's `data-testid` otherwise. A repo
|
|
6
|
+
* using anything else gets no anchor and a positional xpath instead — which
|
|
7
|
+
* resolves at capture time and breaks later, so the miss is invisible.
|
|
8
|
+
*
|
|
9
|
+
* Enumerating popular names (`data-test`, `data-qa`, ...) only covers the repos
|
|
10
|
+
* someone thought of. The repository already answers the question itself: its
|
|
11
|
+
* page objects and specs name the attribute every time they select on it. So
|
|
12
|
+
* read the answer out of the selectors rather than guessing at it.
|
|
13
|
+
*/
|
|
14
|
+
export type AttributeCount = {
|
|
15
|
+
attribute: string;
|
|
16
|
+
count: number;
|
|
17
|
+
};
|
|
18
|
+
export type TestIdDiscovery = {
|
|
19
|
+
/** Most-used qualifying attribute, or undefined when nothing qualified. */
|
|
20
|
+
attribute?: string;
|
|
21
|
+
/** Every qualifying attribute with its selector count, most-used first. */
|
|
22
|
+
counts: AttributeCount[];
|
|
23
|
+
/**
|
|
24
|
+
* Most-used attributes overall, qualifying or not.
|
|
25
|
+
*
|
|
26
|
+
* The point of reporting these is the case no name rule can reach: a suite
|
|
27
|
+
* selecting on something arbitrary like `xyz-id` produces no qualifying
|
|
28
|
+
* candidate, and the miss is otherwise invisible. Printed on a miss, this names
|
|
29
|
+
* the attribute an operator would set explicitly.
|
|
30
|
+
*/
|
|
31
|
+
observed: AttributeCount[];
|
|
32
|
+
filesScanned: number;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Report which test-id attribute this repository's page objects and specs use.
|
|
36
|
+
*
|
|
37
|
+
* Never throws: an unreadable tree yields no attribute, which leaves the capture
|
|
38
|
+
* on Playwright's default.
|
|
39
|
+
*/
|
|
40
|
+
export declare function discoverTestIdAttribute(root: string): Promise<TestIdDiscovery>;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which attribute does this repository use for test ids?
|
|
3
|
+
*
|
|
4
|
+
* The blueprint capture anchors every element's xpath on a test-id attribute
|
|
5
|
+
* when it can find one, and assumes Playwright's `data-testid` otherwise. A repo
|
|
6
|
+
* using anything else gets no anchor and a positional xpath instead — which
|
|
7
|
+
* resolves at capture time and breaks later, so the miss is invisible.
|
|
8
|
+
*
|
|
9
|
+
* Enumerating popular names (`data-test`, `data-qa`, ...) only covers the repos
|
|
10
|
+
* someone thought of. The repository already answers the question itself: its
|
|
11
|
+
* page objects and specs name the attribute every time they select on it. So
|
|
12
|
+
* read the answer out of the selectors rather than guessing at it.
|
|
13
|
+
*/
|
|
14
|
+
import { readFile } from "fs/promises";
|
|
15
|
+
import { globPomFiles, globSpecFiles } from "./pom-files.js";
|
|
16
|
+
import { DEFAULT_TESTID_ATTRS } from "./selector-extractor.js";
|
|
17
|
+
/** Bound the read — a monorepo scan must not delay server startup. */
|
|
18
|
+
const MAX_FILES = 400;
|
|
19
|
+
const MAX_BYTES_PER_FILE = 256 * 1024;
|
|
20
|
+
/**
|
|
21
|
+
* Attribute name inside an attribute selector: `[data-test="x"]`, `[qa-id='y']`.
|
|
22
|
+
*
|
|
23
|
+
* Deliberately open-ended, which is why it cannot reuse `selector-extractor`'s
|
|
24
|
+
* pattern: that one is built from a known attribute list to *extract values for
|
|
25
|
+
* attributes we already named*, whereas this has to surface names nobody listed.
|
|
26
|
+
*/
|
|
27
|
+
const ATTR_SELECTOR_RE = /\[\s*([A-Za-z][A-Za-z0-9_:-]*)\s*[~^$*|]?=\s*["']/g;
|
|
28
|
+
/**
|
|
29
|
+
* Tokens by which an attribute name declares itself to be about testing.
|
|
30
|
+
*
|
|
31
|
+
* Matched as whole tokens, never as prefixes. `auto` is deliberately absent —
|
|
32
|
+
* it admitted `data-auto-play` and `data-auto-height`, and `automation` already
|
|
33
|
+
* covers the convention it was there for (`data-automation-id`).
|
|
34
|
+
*/
|
|
35
|
+
const TEST_TOKENS = new Set(["test", "testid", "tests", "qa", "cy", "cypress", "e2e", "automation"]);
|
|
36
|
+
/**
|
|
37
|
+
* Whether a name plausibly denotes a test id.
|
|
38
|
+
*
|
|
39
|
+
* A name heuristic, safe only because of what it is applied to: an attribute the
|
|
40
|
+
* repository's own tests already select on. A name that is both used as a
|
|
41
|
+
* selector and self-describes as test-related is not going to be
|
|
42
|
+
* `data-testimonial`. Applied to raw DOM attributes the same rule would be
|
|
43
|
+
* reckless.
|
|
44
|
+
*
|
|
45
|
+
* Requiring a token is also what keeps the standard attributes out — `type`,
|
|
46
|
+
* `role`, `href`, `aria-label`, `placeholder` and the rest carry no such token,
|
|
47
|
+
* so no exclusion list is needed alongside this. (`autocomplete` tokenises as one
|
|
48
|
+
* word, so it does not match `auto`.)
|
|
49
|
+
*/
|
|
50
|
+
function looksLikeTestId(attr) {
|
|
51
|
+
const lower = attr.toLowerCase();
|
|
52
|
+
if (DEFAULT_TESTID_ATTRS.includes(lower))
|
|
53
|
+
return true;
|
|
54
|
+
const tokens = lower.split(/[^a-z0-9]+/).filter(Boolean);
|
|
55
|
+
if (tokens.some((t) => TEST_TOKENS.has(t)))
|
|
56
|
+
return true;
|
|
57
|
+
// Glued forms like `datatestid`. Anchored at the end on purpose: a
|
|
58
|
+
// `startsWith("test")` catch-all also admits `data-testimonial`, and since
|
|
59
|
+
// candidates are ranked by use count, a false positive used more often than
|
|
60
|
+
// the real attribute becomes the anchor for the whole repo.
|
|
61
|
+
return tokens.some((t) => t.endsWith("testid"));
|
|
62
|
+
}
|
|
63
|
+
const OBSERVED_REPORT_LIMIT = 5;
|
|
64
|
+
/**
|
|
65
|
+
* Report which test-id attribute this repository's page objects and specs use.
|
|
66
|
+
*
|
|
67
|
+
* Never throws: an unreadable tree yields no attribute, which leaves the capture
|
|
68
|
+
* on Playwright's default.
|
|
69
|
+
*/
|
|
70
|
+
export async function discoverTestIdAttribute(root) {
|
|
71
|
+
let files;
|
|
72
|
+
try {
|
|
73
|
+
const [poms, specs] = await Promise.all([globPomFiles(root), globSpecFiles(root)]);
|
|
74
|
+
files = [...new Set([...poms, ...specs])].slice(0, MAX_FILES);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return { counts: [], observed: [], filesScanned: 0 };
|
|
78
|
+
}
|
|
79
|
+
const all = new Map();
|
|
80
|
+
for (const file of files) {
|
|
81
|
+
let content;
|
|
82
|
+
try {
|
|
83
|
+
content = (await readFile(file, "utf8")).slice(0, MAX_BYTES_PER_FILE);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
for (const match of content.matchAll(ATTR_SELECTOR_RE)) {
|
|
89
|
+
const attr = match[1];
|
|
90
|
+
all.set(attr, (all.get(attr) ?? 0) + 1);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// Count first; name as a tiebreak so the result never depends on walk order.
|
|
94
|
+
const ranked = [...all.entries()]
|
|
95
|
+
.map(([attribute, count]) => ({ attribute, count }))
|
|
96
|
+
.sort((a, b) => b.count - a.count || a.attribute.localeCompare(b.attribute));
|
|
97
|
+
const counts = ranked.filter((c) => looksLikeTestId(c.attribute));
|
|
98
|
+
return {
|
|
99
|
+
attribute: counts[0]?.attribute,
|
|
100
|
+
counts,
|
|
101
|
+
observed: ranked.slice(0, OBSERVED_REPORT_LIMIT),
|
|
102
|
+
filesScanned: files.length,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
@@ -26,10 +26,12 @@ export interface UnbackedClaimsInput {
|
|
|
26
26
|
fileName?: string;
|
|
27
27
|
repository?: string;
|
|
28
28
|
}[];
|
|
29
|
-
/** Maintenance verdicts (server-derived from skyramp_actions).
|
|
29
|
+
/** Maintenance verdicts (server-derived from skyramp_actions). `pomFile`, when set, is
|
|
30
|
+
* the file the edit actually lands in — see the UPDATE rule below. */
|
|
30
31
|
verdicts: {
|
|
31
32
|
action: DriftAction;
|
|
32
33
|
testFilePath: string;
|
|
34
|
+
pomFile?: string;
|
|
33
35
|
}[];
|
|
34
36
|
/** owner/repo of the primary repo — used to exempt cross-repo newTest claims
|
|
35
37
|
* whose files live in a different checkout. */
|
|
@@ -46,7 +48,10 @@ export interface UnbackedClaimsInput {
|
|
|
46
48
|
* edit (or drops the claim) rather than hallucinating it into the report.
|
|
47
49
|
*
|
|
48
50
|
* Only UPDATE verdicts are gated: an UPDATE is a targeted in-place edit that
|
|
49
|
-
* reliably implies a working-tree change
|
|
51
|
+
* reliably implies a working-tree change — in `pomFile` when the verdict names one,
|
|
52
|
+
* otherwise in `testFilePath`. For a POM-based test those are different files: the spec
|
|
53
|
+
* is what runs, the page object is what gets patched, so checking only `testFilePath`
|
|
54
|
+
* would reject maintenance that did land (SKYR-4129). Exemptions:
|
|
50
55
|
* - REGENERATE verdicts — on an EXTERNAL (user-authored Playwright/Cypress/RTL)
|
|
51
56
|
* test these are report-only (Skyramp can't auto-regenerate them; the developer
|
|
52
57
|
* acts manually), so no working-tree change is expected; and even an internal
|
|
@@ -50,7 +50,10 @@ export async function listChangedFiles(repoRoot) {
|
|
|
50
50
|
* edit (or drops the claim) rather than hallucinating it into the report.
|
|
51
51
|
*
|
|
52
52
|
* Only UPDATE verdicts are gated: an UPDATE is a targeted in-place edit that
|
|
53
|
-
* reliably implies a working-tree change
|
|
53
|
+
* reliably implies a working-tree change — in `pomFile` when the verdict names one,
|
|
54
|
+
* otherwise in `testFilePath`. For a POM-based test those are different files: the spec
|
|
55
|
+
* is what runs, the page object is what gets patched, so checking only `testFilePath`
|
|
56
|
+
* would reject maintenance that did land (SKYR-4129). Exemptions:
|
|
54
57
|
* - REGENERATE verdicts — on an EXTERNAL (user-authored Playwright/Cypress/RTL)
|
|
55
58
|
* test these are report-only (Skyramp can't auto-regenerate them; the developer
|
|
56
59
|
* acts manually), so no working-tree change is expected; and even an internal
|
|
@@ -96,8 +99,11 @@ export function findUnbackedClaims(input) {
|
|
|
96
99
|
// change, and internal ones may regenerate to identical content.
|
|
97
100
|
if (v.action !== DriftAction.Update)
|
|
98
101
|
continue;
|
|
99
|
-
|
|
100
|
-
|
|
102
|
+
// The edit target, not the spec: for a POM-based test the page object is what
|
|
103
|
+
// gets patched while testFilePath stays the spec (SKYR-4129).
|
|
104
|
+
const editTarget = v.pomFile ?? v.testFilePath;
|
|
105
|
+
if (!isBacked(editTarget)) {
|
|
106
|
+
unbacked.push(`testMaintenance ${v.action} claims an edit to "${path.basename(editTarget)}", but that file has no working-tree change`);
|
|
101
107
|
}
|
|
102
108
|
}
|
|
103
109
|
return unbacked;
|
|
@@ -905,7 +905,13 @@ export function draftResponseShapeScenarios(hopEndpoints, byFile, addedFieldsByF
|
|
|
905
905
|
: `reflects the change made in ${fileName}`;
|
|
906
906
|
const nameSuffix = addedFields.length > 0 ? addedFields[0] : "response-change";
|
|
907
907
|
scenarios.push({
|
|
908
|
-
|
|
908
|
+
// The endpoint path is part of the name because one file can reach
|
|
909
|
+
// several GETs and neither the resource token (from the file) nor the
|
|
910
|
+
// suffix (a field name, or the constant "response-change" when the hunk
|
|
911
|
+
// yields none) varies between them. Two same-named candidates are
|
|
912
|
+
// merged by name in registerTestPlanTool, so a collision here loses a
|
|
913
|
+
// scenario outright rather than showing up as a duplicate.
|
|
914
|
+
scenarioName: `${slugify(resource)}-response-shape-${slugify(nameSuffix)}-${slugify(ep.path)}`,
|
|
909
915
|
description: `Verify GET ${ep.path} response ${fieldsPhrase}.`,
|
|
910
916
|
category: "breaking_change",
|
|
911
917
|
priority: "high",
|
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
* Skill content for skyramp.md — installed at auto-discovery paths: ~/.claude/skills/skyramp/SKILLS.md, ~/.cursor/skills/skyramp/SKILLS.md, ~/.github/skills/skyramp.md
|
|
3
3
|
* Follows the SKILL.md specification: https://agentskills.io/what-are-skills#the-skill-md-file
|
|
4
4
|
*/
|
|
5
|
-
export declare const SKYRAMP_MD_CONTENT = "---\nname: skyramp\ndescription: Generate, execute, and maintain API tests (smoke, contract, fuzz, load, integration, E2E, UI) using Skyramp MCP tools.\n---\n\n# Skyramp\n\n## When to use this skill\nUse this skill whenever the user asks to generate, run, or maintain API tests. Always read `<workspace-root>/.skyramp/workspace.yml` first to get `language`, `framework`, `outputDir`, and `api.baseUrl` \u2014 do not ask the user for values already present.\n\n---\n\n## Tools\n\n### Workspace Setup\n1. **`skyramp_initialize_workspace`** \u2014 Create or update `.skyramp/workspace.yml` in a git repository workspace. Scan the repo for all services before calling. Required before any other Skyramp tool.\n\n### Test Generation\n2. **`skyramp_smoke_test_generation`** \u2014 Verify an endpoint is reachable and returns a valid response.\n3. **`skyramp_contract_test_generation`** \u2014 Validate implementation matches OpenAPI/Swagger schema.\n4. **`skyramp_fuzz_test_generation`** \u2014 Send malformed or boundary inputs to find edge cases and security issues.\n5. **`skyramp_load_test_generation`** \u2014 Test performance under concurrent load. Optional: `loadDuration`, `loadNumThreads`. Accepts `trace` instead of `apiSchema`/`endpointURL`.\n6. **`skyramp_integration_test_generation`** \u2014 Multi-step workflows across one or more services. Supply one of: `apiSchema`+`endpointURL`, `trace`, or `scenarioFile`. Do not combine them.\n7. **`skyramp_e2e_test_generation`** \u2014 Full user journey covering UI and backend. Requires `trace` and `playwrightInput` zip. Do not pass `apiSchema` or `endpointURL`.\n8. **`skyramp_ui_test_generation`** \u2014 UI-only tests from Playwright recordings. Requires `playwrightInput` zip.\n\n### Trace Generation\n9. **`skyramp_start_trace_collection`** \u2014 Start capturing backend traffic. Set `playwright: true` for UI or E2E tests. Use an absolute path for `outputDir`.\n10. **`skyramp_stop_trace_collection`** \u2014 Stop capture and save the trace. Use the same `outputDir` and `playwrightEnabled` values as start.\n11. **`skyramp_scenario_test_generation`** \u2014 Scenario trace generation. Describe a multi-step flow in natural language to produce a scenario file. Pass output to `skyramp_integration_test_generation` via `scenarioFile`.\n\n### Test Execution\n12. **`skyramp_execute_test`** \u2014 Run a single Skyramp-generated test file. Required: `workspacePath`, `language`, `testType`, `testFile`. Optional: `stateFile` (writes execution results back for health analysis). For multiple tests, call sequentially to avoid env var conflicts.\n\n### Test Analysis & Maintenance\n13. **`skyramp_analyze_changes`** \u2014 Unified entry point: scans endpoints, discovers tests, computes diff. Takes `repositoryPath` and `scope`. Returns `stateFile` + recommendations.\n14. **`skyramp_analyze_test_health`** \u2014 Drift and health assessment for existing tests. Takes `stateFile`. Returns LLM prompt for scoring.\n15. **`skyramp_actions`** \u2014 Execute UPDATE / REGENERATE / VERIFY / DELETE actions. Takes `stateFile`. Call after analyze_test_health.\n\n### Code Quality\n16. **`skyramp_fix_errors`** \u2014 Fix compilation or runtime errors in a generated test file.\n17. **`skyramp_modularization`** \u2014 Refactor a test file into reusable modules. Set `isTraceBased: true` for trace-based tests.\n18. **`skyramp_reuse_code`** \u2014 Pull shared helpers from other Skyramp tests. Only when `code_reuse` was `true` at generation time.\n\n### Authentication\n19. **`skyramp_login`** \u2014 Log in to the Skyramp platform.\n20. **`skyramp_logout`** \u2014 Log out from the Skyramp platform.\n\n---\n\n## Prompts\n\nPrefer invoking a prompt over manually chaining tools \u2014 prompts run the full workflow automatically.\n\n- **`skyramp_trace_prompt`** \u2014 Trace collection setup and execution.\n- **`skyramp_test_health_analysis`** \u2014 Full maintenance flow (discover \u2192 drift \u2192 health \u2192 actions).\n- **`skyramp_testbot`** \u2014 PR-scoped recommendations + maintenance + report. Required: `prTitle`, `prDescription`, `
|
|
5
|
+
export declare const SKYRAMP_MD_CONTENT = "---\nname: skyramp\ndescription: Generate, execute, and maintain API tests (smoke, contract, fuzz, load, integration, E2E, UI) using Skyramp MCP tools.\n---\n\n# Skyramp\n\n## When to use this skill\nUse this skill whenever the user asks to generate, run, or maintain API tests. Always read `<workspace-root>/.skyramp/workspace.yml` first to get `language`, `framework`, `outputDir`, and `api.baseUrl` \u2014 do not ask the user for values already present.\n\n---\n\n## Tools\n\n### Workspace Setup\n1. **`skyramp_initialize_workspace`** \u2014 Create or update `.skyramp/workspace.yml` in a git repository workspace. Scan the repo for all services before calling. Required before any other Skyramp tool.\n\n### Test Generation\n2. **`skyramp_smoke_test_generation`** \u2014 Verify an endpoint is reachable and returns a valid response.\n3. **`skyramp_contract_test_generation`** \u2014 Validate implementation matches OpenAPI/Swagger schema.\n4. **`skyramp_fuzz_test_generation`** \u2014 Send malformed or boundary inputs to find edge cases and security issues.\n5. **`skyramp_load_test_generation`** \u2014 Test performance under concurrent load. Optional: `loadDuration`, `loadNumThreads`. Accepts `trace` instead of `apiSchema`/`endpointURL`.\n6. **`skyramp_integration_test_generation`** \u2014 Multi-step workflows across one or more services. Supply one of: `apiSchema`+`endpointURL`, `trace`, or `scenarioFile`. Do not combine them.\n7. **`skyramp_e2e_test_generation`** \u2014 Full user journey covering UI and backend. Requires `trace` and `playwrightInput` zip. Do not pass `apiSchema` or `endpointURL`.\n8. **`skyramp_ui_test_generation`** \u2014 UI-only tests from Playwright recordings. Requires `playwrightInput` zip.\n\n### Trace Generation\n9. **`skyramp_start_trace_collection`** \u2014 Start capturing backend traffic. Set `playwright: true` for UI or E2E tests. Use an absolute path for `outputDir`.\n10. **`skyramp_stop_trace_collection`** \u2014 Stop capture and save the trace. Use the same `outputDir` and `playwrightEnabled` values as start.\n11. **`skyramp_scenario_test_generation`** \u2014 Scenario trace generation. Describe a multi-step flow in natural language to produce a scenario file. Pass output to `skyramp_integration_test_generation` via `scenarioFile`.\n\n### Test Execution\n12. **`skyramp_execute_test`** \u2014 Run a single Skyramp-generated test file. Required: `workspacePath`, `language`, `testType`, `testFile`. Optional: `stateFile` (writes execution results back for health analysis). For multiple tests, call sequentially to avoid env var conflicts.\n\n### Test Analysis & Maintenance\n13. **`skyramp_analyze_changes`** \u2014 Unified entry point: scans endpoints, discovers tests, computes diff. Takes `repositoryPath` and `scope`. Returns `stateFile` + recommendations.\n14. **`skyramp_analyze_test_health`** \u2014 Drift and health assessment for existing tests. Takes `stateFile`. Returns LLM prompt for scoring.\n15. **`skyramp_actions`** \u2014 Execute UPDATE / REGENERATE / VERIFY / DELETE actions. Takes `stateFile`. Call after analyze_test_health.\n\n### Code Quality\n16. **`skyramp_fix_errors`** \u2014 Fix compilation or runtime errors in a generated test file.\n17. **`skyramp_modularization`** \u2014 Refactor a test file into reusable modules. Set `isTraceBased: true` for trace-based tests.\n18. **`skyramp_reuse_code`** \u2014 Pull shared helpers from other Skyramp tests. Only when `code_reuse` was `true` at generation time.\n\n### Authentication\n19. **`skyramp_login`** \u2014 Log in to the Skyramp platform.\n20. **`skyramp_logout`** \u2014 Log out from the Skyramp platform.\n\n---\n\n## Prompts\n\nPrefer invoking a prompt over manually chaining tools \u2014 prompts run the full workflow automatically.\n\n- **`skyramp_trace_prompt`** \u2014 Trace collection setup and execution.\n- **`skyramp_test_health_analysis`** \u2014 Full maintenance flow (discover \u2192 drift \u2192 health \u2192 actions).\n- **`skyramp_testbot`** \u2014 PR-scoped recommendations + maintenance + report. Required: `prTitle`, `prDescription`, `repositoryPath`.\n\n---\n\n## Workflows\n\nUse these when a prompt is not available.\n\n**Generate and run a test**\nRead `.skyramp/workspace.yml` \u2192 Call appropriate generation tool \u2192 `skyramp_execute_test`\n\n**Trace-based test (integration / load / E2E / UI)**\n`skyramp_start_trace_collection` (set `playwright: true` for UI/E2E) \u2192 User exercises the app \u2192 `skyramp_stop_trace_collection` \u2192 Call target generation tool with `trace` (and `playwrightInput` for E2E/UI)\n\n**Scenario \u2192 integration test**\n`skyramp_scenario_test_generation` \u2192 `skyramp_integration_test_generation` with `scenarioFile`\n\n**Recommend tests for a PR**\n`skyramp_analyze_changes` (with `scope: \"branch_diff\"`) \u2192 follow enrichment steps \u2192 `skyramp_recommend_tests` \u2192 Generate recommended tests\n\n**Maintain existing tests**\n`skyramp_analyze_changes` \u2192 `skyramp_analyze_test_health` \u2192 *(optional)* execute tests via `skyramp_execute_test` with `stateFile` param (writes results back) \u2192 `skyramp_actions` (do not skip)\n\n---\n\n## Conventions\n\n- **`endpointURL`** \u2014 Full URL to a specific endpoint, not just the base URL. Build from `api.baseUrl` + path (e.g. `http://localhost:8000/api/v1/users`).\n- **`outputDir`** \u2014 Use absolute paths for both test output and trace collection.\n- **Mutually exclusive inputs** \u2014 `apiSchema`/`endpointURL`, `trace`, and `scenarioFile` are mutually exclusive for integration and load tests. Use exactly one.\n- **Sequential execution** \u2014 Execute tests sequentially (not in parallel) to avoid environment variable conflicts with `SKYRAMP_TEST_BASE_URL`.\n";
|
|
@@ -58,7 +58,7 @@ Prefer invoking a prompt over manually chaining tools — prompts run the full w
|
|
|
58
58
|
|
|
59
59
|
- **\`skyramp_trace_prompt\`** — Trace collection setup and execution.
|
|
60
60
|
- **\`skyramp_test_health_analysis\`** — Full maintenance flow (discover → drift → health → actions).
|
|
61
|
-
- **\`skyramp_testbot\`** — PR-scoped recommendations + maintenance + report. Required: \`prTitle\`, \`prDescription\`, \`
|
|
61
|
+
- **\`skyramp_testbot\`** — PR-scoped recommendations + maintenance + report. Required: \`prTitle\`, \`prDescription\`, \`repositoryPath\`.
|
|
62
62
|
|
|
63
63
|
---
|
|
64
64
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* URL/path derivations shared by the generation tools.
|
|
3
|
+
*
|
|
4
|
+
* Both helpers here existed as private copies before: `pathFromEndpointURL`
|
|
5
|
+
* lived in tools/generate-tests/planGuard.ts, and the base-path derivation was
|
|
6
|
+
* inlined in ScenarioGenerationService and then mirrored a second time by
|
|
7
|
+
* generateBatchScenarioRestTool so it could predict the path the service would
|
|
8
|
+
* emit (SKYR-4127). Two copies of "which path does this request actually have"
|
|
9
|
+
* is exactly the kind of thing that drifts silently — a pattern that stops
|
|
10
|
+
* matching produces a 403, not a test failure — so they live in one place now.
|
|
11
|
+
*
|
|
12
|
+
* Dependency-free on purpose: this sits below both services/ and tools/ so
|
|
13
|
+
* either layer can import it without an inversion.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Extract the URL path from a generation tool's `endpointURL` param.
|
|
17
|
+
*
|
|
18
|
+
* Falls back to treating the value as a bare path when it isn't a parseable
|
|
19
|
+
* absolute URL, and to undefined when it's neither.
|
|
20
|
+
*
|
|
21
|
+
* `new URL()` percent-encodes `{param}` placeholders (`{id}` → `%7Bid%7D`),
|
|
22
|
+
* which breaks two consumers: plan matchKeys are computed from raw scenario
|
|
23
|
+
* paths with braces intact, and workspace queryParamOverrides patterns are
|
|
24
|
+
* authored with braces. Restore them.
|
|
25
|
+
*/
|
|
26
|
+
export declare function pathFromEndpointURL(endpointURL: string | undefined): string | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* The path prefix a service's `baseURL` contributes to every request under it:
|
|
29
|
+
* the URL's pathname with any trailing slash removed. Empty string when
|
|
30
|
+
* `baseURL` is absent or unparseable, and for a host-only URL (`http://host`
|
|
31
|
+
* → pathname `/` → `""`).
|
|
32
|
+
*
|
|
33
|
+
* This is the value ScenarioGenerationService prepends to each step path, so
|
|
34
|
+
* anything that needs to reason about the emitted path — such as matching
|
|
35
|
+
* workspace queryParamOverrides — must derive it the same way.
|
|
36
|
+
*/
|
|
37
|
+
export declare function deriveBasePath(baseURL: string | undefined): string;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* URL/path derivations shared by the generation tools.
|
|
3
|
+
*
|
|
4
|
+
* Both helpers here existed as private copies before: `pathFromEndpointURL`
|
|
5
|
+
* lived in tools/generate-tests/planGuard.ts, and the base-path derivation was
|
|
6
|
+
* inlined in ScenarioGenerationService and then mirrored a second time by
|
|
7
|
+
* generateBatchScenarioRestTool so it could predict the path the service would
|
|
8
|
+
* emit (SKYR-4127). Two copies of "which path does this request actually have"
|
|
9
|
+
* is exactly the kind of thing that drifts silently — a pattern that stops
|
|
10
|
+
* matching produces a 403, not a test failure — so they live in one place now.
|
|
11
|
+
*
|
|
12
|
+
* Dependency-free on purpose: this sits below both services/ and tools/ so
|
|
13
|
+
* either layer can import it without an inversion.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Extract the URL path from a generation tool's `endpointURL` param.
|
|
17
|
+
*
|
|
18
|
+
* Falls back to treating the value as a bare path when it isn't a parseable
|
|
19
|
+
* absolute URL, and to undefined when it's neither.
|
|
20
|
+
*
|
|
21
|
+
* `new URL()` percent-encodes `{param}` placeholders (`{id}` → `%7Bid%7D`),
|
|
22
|
+
* which breaks two consumers: plan matchKeys are computed from raw scenario
|
|
23
|
+
* paths with braces intact, and workspace queryParamOverrides patterns are
|
|
24
|
+
* authored with braces. Restore them.
|
|
25
|
+
*/
|
|
26
|
+
export function pathFromEndpointURL(endpointURL) {
|
|
27
|
+
if (!endpointURL)
|
|
28
|
+
return undefined;
|
|
29
|
+
try {
|
|
30
|
+
return new URL(endpointURL).pathname.replace(/%7B/gi, "{").replace(/%7D/gi, "}");
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return endpointURL.startsWith("/") ? endpointURL : undefined;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The path prefix a service's `baseURL` contributes to every request under it:
|
|
38
|
+
* the URL's pathname with any trailing slash removed. Empty string when
|
|
39
|
+
* `baseURL` is absent or unparseable, and for a host-only URL (`http://host`
|
|
40
|
+
* → pathname `/` → `""`).
|
|
41
|
+
*
|
|
42
|
+
* This is the value ScenarioGenerationService prepends to each step path, so
|
|
43
|
+
* anything that needs to reason about the emitted path — such as matching
|
|
44
|
+
* workspace queryParamOverrides — must derive it the same way.
|
|
45
|
+
*/
|
|
46
|
+
export function deriveBasePath(baseURL) {
|
|
47
|
+
if (!baseURL)
|
|
48
|
+
return "";
|
|
49
|
+
try {
|
|
50
|
+
return new URL(baseURL).pathname.replace(/\/$/, "");
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return "";
|
|
54
|
+
}
|
|
55
|
+
}
|
package/build/utils/utils.d.ts
CHANGED
|
@@ -1,6 +1,51 @@
|
|
|
1
1
|
import { CallToolResult, ReadResourceResult } from "@modelcontextprotocol/sdk/types.js";
|
|
2
2
|
export declare function readDiffFile(diffFilePath: string | undefined): string | undefined;
|
|
3
3
|
export declare function toolError(message: string): CallToolResult;
|
|
4
|
+
/**
|
|
5
|
+
* Build a successful tool result whose payload is delivered on BOTH MCP result
|
|
6
|
+
* channels — `structuredContent` and `content[]` — carrying the identical text.
|
|
7
|
+
* The counterpart to `toolError` for any tool that returns a large text payload.
|
|
8
|
+
*
|
|
9
|
+
* Use this for every non-error return of such a tool, and declare a matching
|
|
10
|
+
* `outputSchema`. Two separate failures make that necessary:
|
|
11
|
+
*
|
|
12
|
+
* 1. A result with no `structuredContent` is persisted by the agent harness as a
|
|
13
|
+
* pretty-printed `.json` spill, which buries the payload inside an escaped
|
|
14
|
+
* JSON string. Coming through `structuredContent` instead lands it as a
|
|
15
|
+
* `.txt`. Measured on eval runs 32227152533 and 32283973875: the extension
|
|
16
|
+
* follows the result shape, with no exceptions either way.
|
|
17
|
+
*
|
|
18
|
+
* The extension is not cosmetic: it changes how the payload TOKENIZES. The
|
|
19
|
+
* reader refuses any spill over 25,000 tokens, and pretty-printed JSON with
|
|
20
|
+
* escaped quotes and newlines costs 2-3x the tokens of the same bytes in raw
|
|
21
|
+
* form. Measured on one payload either way: 141.2KB of `.json` counted 72,318
|
|
22
|
+
* tokens and was refused, while the same result at 142.0KB of `.txt` was read
|
|
23
|
+
* back in full. A second: 54,085 tokens as `.json`, 26,950 as `.txt`.
|
|
24
|
+
*
|
|
25
|
+
* So the shape raises the deliverable size ceiling by roughly 3x, but does not
|
|
26
|
+
* remove it. That second payload still missed the cap by 1,950 tokens, and
|
|
27
|
+
* `skyramp_actions` results of 178-621KB are 45,598-158,997 tokens even as
|
|
28
|
+
* `.txt` — the agent then falls back to slicing the file with `head -c`. The
|
|
29
|
+
* check covers the whole file, so a `limit:` on the read cannot evade it.
|
|
30
|
+
* Payloads that big have to shrink (SKYR-4188 and its sibling); this shape is
|
|
31
|
+
* what lets a shrunk payload actually arrive.
|
|
32
|
+
*
|
|
33
|
+
* 2. Each agent CLI testbot supports reads a different channel and none falls
|
|
34
|
+
* back to the other: Claude Code takes `structuredContent` and drops
|
|
35
|
+
* `content[]`; Cursor takes `content[]` and ignores `structuredContent`;
|
|
36
|
+
* GitHub Copilot CLI surfaces both and de-duplicates them only when the text
|
|
37
|
+
* is the literal JSON serialization of `structuredContent` (MCP spec 5.2.6),
|
|
38
|
+
* otherwise it concatenates. Hence `JSON.stringify` rather than the raw text,
|
|
39
|
+
* which would deliver the payload twice there. Trimming either channel
|
|
40
|
+
* silently starves a client.
|
|
41
|
+
*
|
|
42
|
+
* Declaring the outputSchema also obliges every non-error return to come through
|
|
43
|
+
* here: the SDK rejects a successful result without `structuredContent`
|
|
44
|
+
* ("has an output schema but no structured content was provided"). Error results
|
|
45
|
+
* are exempt — that check returns early on `isError` — so they keep using
|
|
46
|
+
* `toolError`.
|
|
47
|
+
*/
|
|
48
|
+
export declare function dualChannelResult(structuredContent: Record<string, string>): CallToolResult;
|
|
4
49
|
/**
|
|
5
50
|
* Does `candidate` (an LLM- or caller-supplied name/path) identify `fullPath` (a known,
|
|
6
51
|
* absolute test file path)? Exact match first, then a real path-segment boundary match
|
package/build/utils/utils.js
CHANGED
|
@@ -18,6 +18,56 @@ export function toolError(message) {
|
|
|
18
18
|
isError: true,
|
|
19
19
|
};
|
|
20
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Build a successful tool result whose payload is delivered on BOTH MCP result
|
|
23
|
+
* channels — `structuredContent` and `content[]` — carrying the identical text.
|
|
24
|
+
* The counterpart to `toolError` for any tool that returns a large text payload.
|
|
25
|
+
*
|
|
26
|
+
* Use this for every non-error return of such a tool, and declare a matching
|
|
27
|
+
* `outputSchema`. Two separate failures make that necessary:
|
|
28
|
+
*
|
|
29
|
+
* 1. A result with no `structuredContent` is persisted by the agent harness as a
|
|
30
|
+
* pretty-printed `.json` spill, which buries the payload inside an escaped
|
|
31
|
+
* JSON string. Coming through `structuredContent` instead lands it as a
|
|
32
|
+
* `.txt`. Measured on eval runs 32227152533 and 32283973875: the extension
|
|
33
|
+
* follows the result shape, with no exceptions either way.
|
|
34
|
+
*
|
|
35
|
+
* The extension is not cosmetic: it changes how the payload TOKENIZES. The
|
|
36
|
+
* reader refuses any spill over 25,000 tokens, and pretty-printed JSON with
|
|
37
|
+
* escaped quotes and newlines costs 2-3x the tokens of the same bytes in raw
|
|
38
|
+
* form. Measured on one payload either way: 141.2KB of `.json` counted 72,318
|
|
39
|
+
* tokens and was refused, while the same result at 142.0KB of `.txt` was read
|
|
40
|
+
* back in full. A second: 54,085 tokens as `.json`, 26,950 as `.txt`.
|
|
41
|
+
*
|
|
42
|
+
* So the shape raises the deliverable size ceiling by roughly 3x, but does not
|
|
43
|
+
* remove it. That second payload still missed the cap by 1,950 tokens, and
|
|
44
|
+
* `skyramp_actions` results of 178-621KB are 45,598-158,997 tokens even as
|
|
45
|
+
* `.txt` — the agent then falls back to slicing the file with `head -c`. The
|
|
46
|
+
* check covers the whole file, so a `limit:` on the read cannot evade it.
|
|
47
|
+
* Payloads that big have to shrink (SKYR-4188 and its sibling); this shape is
|
|
48
|
+
* what lets a shrunk payload actually arrive.
|
|
49
|
+
*
|
|
50
|
+
* 2. Each agent CLI testbot supports reads a different channel and none falls
|
|
51
|
+
* back to the other: Claude Code takes `structuredContent` and drops
|
|
52
|
+
* `content[]`; Cursor takes `content[]` and ignores `structuredContent`;
|
|
53
|
+
* GitHub Copilot CLI surfaces both and de-duplicates them only when the text
|
|
54
|
+
* is the literal JSON serialization of `structuredContent` (MCP spec 5.2.6),
|
|
55
|
+
* otherwise it concatenates. Hence `JSON.stringify` rather than the raw text,
|
|
56
|
+
* which would deliver the payload twice there. Trimming either channel
|
|
57
|
+
* silently starves a client.
|
|
58
|
+
*
|
|
59
|
+
* Declaring the outputSchema also obliges every non-error return to come through
|
|
60
|
+
* here: the SDK rejects a successful result without `structuredContent`
|
|
61
|
+
* ("has an output schema but no structured content was provided"). Error results
|
|
62
|
+
* are exempt — that check returns early on `isError` — so they keep using
|
|
63
|
+
* `toolError`.
|
|
64
|
+
*/
|
|
65
|
+
export function dualChannelResult(structuredContent) {
|
|
66
|
+
return {
|
|
67
|
+
structuredContent,
|
|
68
|
+
content: [{ type: "text", text: JSON.stringify(structuredContent) }],
|
|
69
|
+
};
|
|
70
|
+
}
|
|
21
71
|
/**
|
|
22
72
|
* Does `candidate` (an LLM- or caller-supplied name/path) identify `fullPath` (a known,
|
|
23
73
|
* absolute test file path)? Exact match first, then a real path-segment boundary match
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const SKYRAMP_IMAGE_VERSION = "v1.3.
|
|
2
|
-
export declare const EXECUTOR_DOCKER_IMAGE = "skyramp/executor:v1.3.
|
|
3
|
-
export declare const WORKER_DOCKER_IMAGE = "skyramp/worker:v1.3.
|
|
1
|
+
export declare const SKYRAMP_IMAGE_VERSION = "v1.3.40";
|
|
2
|
+
export declare const EXECUTOR_DOCKER_IMAGE = "skyramp/executor:v1.3.40";
|
|
3
|
+
export declare const WORKER_DOCKER_IMAGE = "skyramp/worker:v1.3.40";
|
|
4
4
|
export declare const WORKER_CONTROL_PORT = 35142;
|
package/build/utils/versions.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const SKYRAMP_IMAGE_VERSION = "v1.3.
|
|
1
|
+
export const SKYRAMP_IMAGE_VERSION = "v1.3.40";
|
|
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`).
|