@skyramp/mcp 0.4.4-rc.1 → 0.4.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 +239 -210
- package/build/playwright/PlaywrightTraceService.d.ts +1 -1
- package/build/playwright/blueprintDigestStore.d.ts +1 -1
- package/build/playwright/registerPlaywrightTools.d.ts +1 -1
- package/build/playwright/traceRecordingPrompt.d.ts +1 -1
- package/build/playwright/traceRecordingPrompt.js +2 -2
- package/build/prompts/local-dev/local-dev-prompts.d.ts +1 -1
- package/build/prompts/local-dev/local-dev-prompts.js +2 -2
- package/build/prompts/startTraceCollectionPrompts.d.ts +1 -1
- package/build/prompts/startTraceCollectionPrompts.js +2 -3
- package/build/prompts/test-recommendation/registerRecommendTestsPrompt.d.ts +1 -1
- package/build/prompts/test-recommendation/registerRecommendTestsPrompt.js +2 -2
- package/build/prompts/testbot/testbot-prompts.d.ts +1 -1
- package/build/prompts/testbot/testbot-prompts.js +4 -7
- package/build/recommendation/answers.d.ts +17 -52
- package/build/recommendation/answers.js +29 -99
- package/build/recommendation/pullRequestText.d.ts +0 -2
- package/build/recommendation/pullRequestText.js +0 -16
- package/build/recommendation/registerPlan.d.ts +4 -7
- package/build/recommendation/registerPlan.js +11 -37
- package/build/recommendation/types.d.ts +4 -21
- package/build/recommendation/verifierContracts.d.ts +4 -4
- package/build/recommendation/verifierContracts.js +4 -4
- package/build/recommendation/verifiers/citedPath.d.ts +2 -2
- package/build/recommendation/verifiers/citedPath.js +2 -2
- package/build/recommendation/verifiers/issueTraceability.d.ts +5 -1
- package/build/recommendation/verifiers/issueTraceability.js +35 -4
- package/build/resources/analysisResources.d.ts +1 -1
- package/build/resources/analysisResources.js +1 -1
- package/build/resources/progressResource.d.ts +1 -1
- package/build/resources/sutSetupResource.d.ts +1 -1
- package/build/resources/sutSetupResource.js +1 -1
- package/build/resources/testbotResource.d.ts +1 -1
- package/build/resources/testbotResource.js +1 -1
- package/build/services/AnalyticsService.d.ts +1 -1
- package/build/services/FleetExecutionService.d.ts +130 -0
- package/build/services/FleetExecutionService.js +391 -0
- package/build/services/ModularizationService.d.ts +1 -1
- package/build/services/TestExecutionService.d.ts +7 -0
- package/build/services/TestExecutionService.js +21 -0
- package/build/services/TestGenerationService.d.ts +1 -1
- package/build/tool-phases.js +2 -0
- package/build/tools/auth/loginTool.d.ts +1 -1
- package/build/tools/auth/loginTool.js +2 -2
- package/build/tools/auth/logoutTool.d.ts +1 -1
- package/build/tools/auth/logoutTool.js +2 -2
- package/build/tools/code-refactor/alignAssertionsTool.d.ts +1 -1
- package/build/tools/code-refactor/alignAssertionsTool.js +1 -1
- package/build/tools/code-refactor/assertionSkillTools.d.ts +1 -1
- package/build/tools/code-refactor/assertionSkillTools.js +2 -2
- package/build/tools/code-refactor/codeReuseTool.d.ts +1 -1
- package/build/tools/code-refactor/codeReuseTool.js +2 -2
- package/build/tools/code-refactor/enhanceAssertionsTool.d.ts +1 -1
- package/build/tools/code-refactor/enhanceAssertionsTool.js +1 -1
- package/build/tools/code-refactor/modularizationTool.d.ts +1 -1
- package/build/tools/code-refactor/modularizationTool.js +2 -2
- package/build/tools/enrichTestWithMocksTool.d.ts +1 -1
- package/build/tools/enrichTestWithMocksTool.js +2 -2
- package/build/tools/executeFleetTool.d.ts +28 -0
- package/build/tools/executeFleetTool.js +107 -0
- package/build/tools/executeSkyrampTestTool.d.ts +8 -5
- package/build/tools/executeSkyrampTestTool.js +5 -5
- package/build/tools/fixErrorTool.d.ts +1 -1
- package/build/tools/fixErrorTool.js +1 -1
- package/build/tools/generate-tests/batchMockGenerationTool.d.ts +6 -57
- package/build/tools/generate-tests/batchMockGenerationTool.js +3 -3
- package/build/tools/generate-tests/generateBatchScenarioRestTool.d.ts +9 -45
- package/build/tools/generate-tests/generateBatchScenarioRestTool.js +29 -11
- package/build/tools/generate-tests/generateContractRestTool.d.ts +1 -2
- package/build/tools/generate-tests/generateContractRestTool.js +1 -1
- package/build/tools/generate-tests/generateE2ERestTool.d.ts +1 -2
- package/build/tools/generate-tests/generateE2ERestTool.js +1 -1
- package/build/tools/generate-tests/generateFuzzRestTool.d.ts +1 -1
- package/build/tools/generate-tests/generateFuzzRestTool.js +1 -1
- package/build/tools/generate-tests/generateIntegrationRestTool.d.ts +1 -2
- package/build/tools/generate-tests/generateIntegrationRestTool.js +1 -1
- package/build/tools/generate-tests/generateLoadRestTool.d.ts +1 -2
- package/build/tools/generate-tests/generateLoadRestTool.js +2 -1
- package/build/tools/generate-tests/generateMockRestTool.d.ts +1 -2
- package/build/tools/generate-tests/generateMockRestTool.js +3 -3
- package/build/tools/generate-tests/generateSmokeRestTool.d.ts +1 -1
- package/build/tools/generate-tests/generateSmokeRestTool.js +1 -1
- package/build/tools/generate-tests/generateUILoadRestTool.d.ts +64 -0
- package/build/tools/generate-tests/generateUILoadRestTool.js +512 -0
- package/build/tools/generate-tests/generateUIRestTool.d.ts +6 -7
- package/build/tools/generate-tests/generateUIRestTool.js +1 -1
- package/build/tools/generate-tests/loadTestSchema.d.ts +15 -15
- package/build/tools/generate-tests/trace-reuse-guard.d.ts +1 -1
- package/build/tools/generateEnrichedIntegrationTestTool.d.ts +1 -2
- package/build/tools/generateEnrichedIntegrationTestTool.js +1 -1
- package/build/tools/localDevWorkerComposeTool.d.ts +1 -1
- package/build/tools/localDevWorkerComposeTool.js +2 -2
- package/build/tools/one-click/oneClickTool.d.ts +1 -1
- package/build/tools/one-click/oneClickTool.js +1 -1
- package/build/tools/preflightMockCheckTool.d.ts +1 -1
- package/build/tools/preflightMockCheckTool.js +3 -3
- package/build/tools/queryProxyMocksTool.d.ts +1 -1
- package/build/tools/queryProxyMocksTool.js +3 -3
- package/build/tools/submitReportTool.d.ts +111 -414
- package/build/tools/submitReportTool.js +154 -138
- package/build/tools/test-management/actionsTool.d.ts +1 -1
- package/build/tools/test-management/actionsTool.js +7 -7
- package/build/tools/test-management/analyzeChangesTool.d.ts +22 -118
- package/build/tools/test-management/analyzeChangesTool.js +4 -4
- package/build/tools/test-management/analyzeTestHealthTool.d.ts +1 -1
- package/build/tools/test-management/analyzeTestHealthTool.js +4 -4
- package/build/tools/test-management/registerTestPlanTool.d.ts +111 -743
- package/build/tools/test-management/registerTestPlanTool.js +20 -36
- package/build/tools/test-management/resolveScreenTool.d.ts +1 -1
- package/build/tools/test-management/resolveScreenTool.js +6 -2
- package/build/tools/trace/startTraceCollectionTool.d.ts +1 -1
- package/build/tools/trace/startTraceCollectionTool.js +4 -4
- package/build/tools/trace/stopTraceCollectionTool.d.ts +1 -1
- package/build/tools/trace/stopTraceCollectionTool.js +2 -2
- package/build/tools/verifyTestDependenciesTool.d.ts +1 -1
- package/build/tools/verifyTestDependenciesTool.js +1 -1
- package/build/tools/workspace/initScanWorkspaceTool.d.ts +1 -1
- package/build/tools/workspace/initScanWorkspaceTool.js +1 -1
- package/build/tools/workspace/initializeWorkspaceTool.d.ts +1 -1
- package/build/tools/workspace/initializeWorkspaceTool.js +1 -1
- package/build/types/RepositoryAnalysis.d.ts +164 -1170
- package/build/types/RepositoryAnalysis.js +12 -10
- package/build/types/StepMethod.d.ts +24 -1
- package/build/types/StepMethod.js +1 -1
- package/build/types/TestTypes.d.ts +34 -133
- package/build/types/TestTypes.js +3 -3
- package/build/types/TestbotReport.d.ts +37 -0
- package/build/types/TestbotReport.js +22 -0
- package/build/types/index.d.ts +1 -1
- package/build/types/index.js +1 -1
- package/build/utils/docker.d.ts +29 -7
- package/build/utils/docker.js +83 -27
- package/build/utils/initAgent.d.ts +3 -3
- package/build/utils/initAgent.js +58 -26
- package/build/utils/progress.d.ts +3 -4
- package/build/utils/progress.js +4 -4
- package/build/utils/utils.d.ts +1 -1
- package/build/utils/versions.d.ts +3 -3
- package/build/utils/versions.js +1 -1
- package/build/workspace/workspace.d.ts +117 -538
- package/build/workspace/workspace.js +2 -2
- package/package.json +6 -5
- package/plugin/prompts/testbot-task1.md +1 -1
- package/plugin/skills/repair-staged-files/SKILL.md +17 -0
- package/build/recommendation/blockerProbes.d.ts +0 -13
- package/build/recommendation/blockerProbes.js +0 -91
- package/build/recommendation/blockers.d.ts +0 -65
- package/build/recommendation/blockers.js +0 -135
|
@@ -61,7 +61,7 @@ export const serviceSchema = z
|
|
|
61
61
|
* the auth header). Literal values only — no env-var interpolation.
|
|
62
62
|
* Explicit per-request queryParams always win on a key collision
|
|
63
63
|
* (SKYR-4050). */
|
|
64
|
-
defaultQueryParams: z.record(z.string()).optional(),
|
|
64
|
+
defaultQueryParams: z.record(z.string(), z.string()).optional(),
|
|
65
65
|
/** Path-scoped overrides layered on top of defaultQueryParams
|
|
66
66
|
* (SKYR-4127). For a given request, the single most-specific matching
|
|
67
67
|
* pathPattern wins and its values override the base map key by key;
|
|
@@ -84,7 +84,7 @@ export const serviceSchema = z
|
|
|
84
84
|
.array(z
|
|
85
85
|
.object({
|
|
86
86
|
pathPattern: z.string().min(1),
|
|
87
|
-
values: z.record(z.string()),
|
|
87
|
+
values: z.record(z.string(), z.string()),
|
|
88
88
|
})
|
|
89
89
|
.strict())
|
|
90
90
|
.optional(),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skyramp/mcp",
|
|
3
|
-
"version": "0.4.4
|
|
3
|
+
"version": "0.4.4",
|
|
4
4
|
"main": "build/index.js",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./build/index.js",
|
|
@@ -69,9 +69,10 @@
|
|
|
69
69
|
"url": "https://github.com/skyramp/mcp/issues"
|
|
70
70
|
},
|
|
71
71
|
"dependencies": {
|
|
72
|
-
"@modelcontextprotocol/
|
|
72
|
+
"@modelcontextprotocol/client": "^2.0.0",
|
|
73
|
+
"@modelcontextprotocol/server": "^2.0.0",
|
|
73
74
|
"@playwright/test": "^1.55.0",
|
|
74
|
-
"@skyramp/skyramp": "1.3.
|
|
75
|
+
"@skyramp/skyramp": "1.3.50",
|
|
75
76
|
"dockerode": "^5.0.0",
|
|
76
77
|
"fast-glob": "^3.3.3",
|
|
77
78
|
"js-yaml": "^4.1.1",
|
|
@@ -82,7 +83,7 @@
|
|
|
82
83
|
"smol-toml": "^1.8.0",
|
|
83
84
|
"ts-is-record": "^3.0.5",
|
|
84
85
|
"typescript": "^5.8.3",
|
|
85
|
-
"zod": "^
|
|
86
|
+
"zod": "^4.2.0"
|
|
86
87
|
},
|
|
87
88
|
"devDependencies": {
|
|
88
89
|
"@types/dockerode": "^3.3.39",
|
|
@@ -100,6 +101,6 @@
|
|
|
100
101
|
"ts-jest": "^29.3.4"
|
|
101
102
|
},
|
|
102
103
|
"engines": {
|
|
103
|
-
"node": ">=
|
|
104
|
+
"node": ">=22"
|
|
104
105
|
}
|
|
105
106
|
}
|
|
@@ -65,7 +65,7 @@ Log in once via the credentials in your <ui-credentials> context before you navi
|
|
|
65
65
|
**One state file, per-repo sections.** There is exactly ONE state file for the whole run (created automatically and reused across calls). Each `skyramp_analyze_changes` call writes its repo's section into it, keyed by `repository`. The run declares its primary when it starts, so the root section is the primary's whichever call runs first. When you maintain or act on a related repo's tests, pass that repo's `repository` value on the call, exactly as step {analyzeStep} says to pass `{primaryRepo}` for the primary repo's tests.
|
|
66
66
|
|
|
67
67
|
**Cross-repo test generation ({generateTask} spans all repos).** Draft your planned tests from the analysis of the primary AND every related repo together, and register them as ONE plan. When you generate a test for a related repo's endpoint:
|
|
68
|
-
- **Execute it only if that repo's service is already running and reachable.** The workflow's setup may have started multiple services; before generating an API test for a related repo, confirm its `base_url` (from that repo's workspace/Execution Plan) responds. If the service is unreachable, still GENERATE the test but mark its `testResults` status as `Skipped` with details "service not running in this run" — do NOT count an unreachable service as a failure.
|
|
68
|
+
- **Execute it only if that repo's service is already running and reachable.** The workflow's setup may have started multiple services; before generating an API test for a related repo, confirm its `base_url` (from that repo's workspace/Execution Plan) responds. If the service is unreachable, still GENERATE the test but mark its `testResults` status as `Skipped` with details "service not running in this run" and `failureCause` `{ "kind": "environment", "family": "app_not_started" }` — do NOT count an unreachable service as a failure.
|
|
69
69
|
- **Write the test file into that service's own `testDirectory`** — the one declared for the service in the unified workspace.yml (the related repo's services were registered there in {registerServicesRef}, each with its `repository`). The `testDirectory` is interpreted relative to the **single delivery root** (the configured test repo if set, otherwise the primary repo), so all generated tests are delivered together by the existing single-target delivery. Do NOT invent a per-source-repo subdirectory, and do NOT write into the related repo's own checkout — it is read-only context. (If two repos happen to declare the same `testDirectory`, their files coexist there; the `repository` field on each report item — below — is what attributes ownership, not the path.)
|
|
70
70
|
- **Set the `repository` field** (`owner/repo`) on every such `newTestsCreated` / `testResults` item so the report attributes it to the originating repo (see Report Guidelines).
|
|
71
71
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: repair-staged-files
|
|
3
|
+
description: Get the files a Testbot run created or modified ready to commit under the target repo's own standards — lint, format, typecheck, naming, CI, dependencies, commit message and PR title, credential safety. Use once, after all generation, maintenance, and execution are done, before the report is submitted.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Repair staged files
|
|
7
|
+
|
|
8
|
+
**Goal:** the files this run created or modified are ready to deliver: `git commit` passes the repo's hooks, and the PR passes the repo's own checks. Find the staged and unstaged changed files with git commands.
|
|
9
|
+
|
|
10
|
+
1. **Lint and format.** Find the repo's linter, formatter, type checker, and every commit hook — husky, lint-staged, the pre-commit framework, or a plain script — wherever git runs them from. The files and planned commit message must pass each. Hooks check only staged files; one that checked none of them has not passed. Run them on the files and fix every violation; a formatter's rewrite is the fix. Where a hook fails on warnings (`--max-warnings=0`), fix warnings too. `.editorconfig` and `.gitattributes` apply to every file. A tool applies per file, by that file's language, the repo's own included. Re-run until clean; record the command.
|
|
11
|
+
2. **Conventions.** A new file must match the repo's test naming and directory convention (see existing tests and runner config). Fix a mismatch by renaming or moving the new file, and update every reference to it, the report included. Edit a maintained test in place; never rename or move it.
|
|
12
|
+
3. **CI readiness.** Confirm CI will run the new files; if not, name the change the author must make — a note, not a blocker.
|
|
13
|
+
4. **Dependencies.** Declare every package a test imports in the right manifest, and update its lockfile; both ship with the tests. For an import that does not resolve, use the `fix-test-import-errors` skill.
|
|
14
|
+
5. **Commit and PR text.** Find every rule the repo applies to commit messages and PR titles, wherever it is defined; write both to satisfy all of it, and check them with the repo's own tool where one exists. Only when the repo has none, use "Skyramp Testbot: <concise>". Keep each within 72 characters, and add a body summarizing the tests added and maintained, naming the CODEOWNERS team and covering the PR template.
|
|
15
|
+
- DCO (Developer Certificate of Origin) sign-off is a `Signed-off-by: Skyramp Testbot <test-bot@skyramp.dev>` trailer in the commit message. Add it only when a CI check or the contributing docs require it. It is not a cryptographic signature: do not GPG- or SSH-sign the commit.
|
|
16
|
+
|
|
17
|
+
Re-run any test you edit; its outcome must not change. Never weaken or remove an assertion, and never edit the repo's lint, format, or runner config, disable a rule, or add an ignore comment (`# noqa`, `eslint-disable`) to make a file pass. No real secret in a delivered file, the report, or the PR comment — that path skips every hook. If a finding can't be fixed without breaking these rules, stop and say what blocks the commit.
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { BlockerChecks } from "./blockers.js";
|
|
2
|
-
import { PlanBlocker } from "./types.js";
|
|
3
|
-
/** The three checks, resolved. Every await happens HERE, before the plan verifiers
|
|
4
|
-
* run: they are synchronous, so a probe cannot be reached from inside one. Each
|
|
5
|
-
* distinct URL and branch is probed once however many answers name it.
|
|
6
|
-
*
|
|
7
|
-
* Called by both tools. The plan tool has a `VerifyContext` to hang the result on
|
|
8
|
-
* and the report tool does not, so this returns the checks rather than the context. */
|
|
9
|
-
export declare function probeBlockers(blockers: PlanBlocker[]): Promise<BlockerChecks>;
|
|
10
|
-
/** The blockers an answers list carries. */
|
|
11
|
-
export declare function blockersIn(answers: ReadonlyArray<{
|
|
12
|
-
blocker?: PlanBlocker;
|
|
13
|
-
}>): PlanBlocker[];
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import { probeUrl } from "./blockers.js";
|
|
2
|
-
import { uiCredentialsSupplied } from "./pullRequestText.js";
|
|
3
|
-
import { capturedBlueprintDigests } from "../playwright/blueprintDigestStore.js";
|
|
4
|
-
import { logger } from "../utils/logger.js";
|
|
5
|
-
/** How long a service gets to answer before the run calls it unreachable. Long
|
|
6
|
-
* enough for a container still warming up, short enough that a plan registration
|
|
7
|
-
* with several blockers does not stall the agent. */
|
|
8
|
-
const PROBE_TIMEOUT_MS = 5_000;
|
|
9
|
-
/** Whether the service at `url` answered. ANY status counts — a 500 is a service
|
|
10
|
-
* that is running, and the claim under test is only that the run could not reach
|
|
11
|
-
* it. UNDEFINED is never returned here: a request either gets a response or does
|
|
12
|
-
* not, and `verifyBlocker` decides what an unparseable URL means. */
|
|
13
|
-
async function serviceAnswers(url) {
|
|
14
|
-
for (const method of ["HEAD", "GET"]) {
|
|
15
|
-
try {
|
|
16
|
-
await fetch(url, {
|
|
17
|
-
method,
|
|
18
|
-
signal: AbortSignal.timeout(PROBE_TIMEOUT_MS),
|
|
19
|
-
redirect: "manual",
|
|
20
|
-
});
|
|
21
|
-
return true;
|
|
22
|
-
}
|
|
23
|
-
catch {
|
|
24
|
-
// HEAD is refused by some servers that answer GET perfectly well, so a
|
|
25
|
-
// failure on it is not yet an unreachable service.
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
return false;
|
|
29
|
-
}
|
|
30
|
-
/** A URL's path with no trailing slash, or UNDEFINED. The host is not compared: the
|
|
31
|
-
* agent writes `localhost` where the capture wrote `127.0.0.1`, and the reverse. */
|
|
32
|
-
function pagePath(raw) {
|
|
33
|
-
try {
|
|
34
|
-
const path = new URL(raw).pathname.replace(/\/+$/, "");
|
|
35
|
-
return path || "/";
|
|
36
|
-
}
|
|
37
|
-
catch {
|
|
38
|
-
return undefined;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
/** Whether a capture this run took reached the page at `url` and read elements
|
|
42
|
-
* from it. */
|
|
43
|
-
function pageLoaded(url) {
|
|
44
|
-
const wanted = pagePath(url);
|
|
45
|
-
return capturedBlueprintDigests().some((capture) => capture.elements.length > 0 && pagePath(capture.url) === wanted);
|
|
46
|
-
}
|
|
47
|
-
/** The three checks, resolved. Every await happens HERE, before the plan verifiers
|
|
48
|
-
* run: they are synchronous, so a probe cannot be reached from inside one. Each
|
|
49
|
-
* distinct URL and branch is probed once however many answers name it.
|
|
50
|
-
*
|
|
51
|
-
* Called by both tools. The plan tool has a `VerifyContext` to hang the result on
|
|
52
|
-
* and the report tool does not, so this returns the checks rather than the context. */
|
|
53
|
-
export async function probeBlockers(blockers) {
|
|
54
|
-
const urls = new Set();
|
|
55
|
-
for (const blocker of blockers) {
|
|
56
|
-
const detail = String(blocker?.detail ?? "").trim();
|
|
57
|
-
if (!detail)
|
|
58
|
-
continue;
|
|
59
|
-
if (blocker.kind === "service-unreachable")
|
|
60
|
-
urls.add(detail);
|
|
61
|
-
}
|
|
62
|
-
const serviceResults = new Map();
|
|
63
|
-
await Promise.all([
|
|
64
|
-
...[...urls].map(async (raw) => {
|
|
65
|
-
// The SAME filter `verifyBlocker` applies, and it gates the request. Reading
|
|
66
|
-
// the detail with a bare `new URL` sent the server after whatever the agent
|
|
67
|
-
// wrote — `file:`, `data:`, a link-local address — and made the verdict a
|
|
68
|
-
// reachability oracle for anything the URL could name.
|
|
69
|
-
const url = probeUrl(raw);
|
|
70
|
-
if (!url)
|
|
71
|
-
return;
|
|
72
|
-
serviceResults.set(url, await serviceAnswers(url));
|
|
73
|
-
}),
|
|
74
|
-
]).catch((error) => {
|
|
75
|
-
// A probe that throws leaves its entry unset, and an unset entry reads as
|
|
76
|
-
// "could not tell". A blocker must never close because a check crashed, and it
|
|
77
|
-
// must never be refused for that either.
|
|
78
|
-
logger.warning(`Blocker probe failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
79
|
-
});
|
|
80
|
-
return {
|
|
81
|
-
serviceAnswers: (url) => serviceResults.get(url),
|
|
82
|
-
credentialsSupplied: uiCredentialsSupplied(),
|
|
83
|
-
pageLoaded,
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
/** The blockers an answers list carries. */
|
|
87
|
-
export function blockersIn(answers) {
|
|
88
|
-
return answers
|
|
89
|
-
.map((entry) => entry.blocker)
|
|
90
|
-
.filter((blocker) => blocker !== undefined);
|
|
91
|
-
}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { PlanBlocker } from "./types.js";
|
|
3
|
-
/** What `blocker` is, in the words both tools' schemas give the agent. One string:
|
|
4
|
-
* the plan states the rule before the tests run and the report states it after, and
|
|
5
|
-
* two copies drifted the moment either was edited. */
|
|
6
|
-
export declare const BLOCKER_DESCRIPTION = "What stopped this run from writing the test, as one of two claims the server checks: `service-unreachable` with the URL the run needed, or `credential-missing` with the URL of the page the missing login guards. An objection about a change or a reported bug with no test, or about a value a page and API change accepts or refuses that no UI test names, closes only with one of these, and the server probes it \u2014 a claim it finds untrue leaves the objection open. A reason the test is not worth writing is not a blocker. A step that the browser tools cannot record is not a blocker either: record the steps they can, then edit the test file the generator writes and add the missing step yourself.";
|
|
7
|
-
/** The `kind` values, in the schema's own words. */
|
|
8
|
-
export declare const BLOCKER_KIND_DESCRIPTION = "Which of the two checkable things stopped the test. `service-unreachable`: the run could not reach a service it needed. `credential-missing`: the run holds no login for a page the test has to get past.";
|
|
9
|
-
export declare const BLOCKER_DETAIL_DESCRIPTION = "The fact behind the claim: for `service-unreachable` the URL, spelled as the run tried it \u2014 the server requests it; for `credential-missing` the URL of the page that asked for the login \u2014 the server reads it against the pages this run already loaded, and a page the run loaded with no login needs none.";
|
|
10
|
-
/** The blocker both tools take, as the schema that rejects anything else. An
|
|
11
|
-
* OBJECT, not a sentence: free text let an argument close an untested defect, and
|
|
12
|
-
* each kind here names one fact the server can probe. `.strict()` so an invented
|
|
13
|
-
* kind or an extra field fails the schema rather than reading as a claim nothing
|
|
14
|
-
* could check. */
|
|
15
|
-
export declare const blockerSchema: z.ZodEffects<z.ZodObject<{
|
|
16
|
-
kind: z.ZodEnum<["service-unreachable", "credential-missing"]>;
|
|
17
|
-
detail: z.ZodEffects<z.ZodString, string, string>;
|
|
18
|
-
}, "strict", z.ZodTypeAny, {
|
|
19
|
-
kind: "service-unreachable" | "credential-missing";
|
|
20
|
-
detail: string;
|
|
21
|
-
}, {
|
|
22
|
-
kind: "service-unreachable" | "credential-missing";
|
|
23
|
-
detail: string;
|
|
24
|
-
}>, {
|
|
25
|
-
kind: "service-unreachable" | "credential-missing";
|
|
26
|
-
detail: string;
|
|
27
|
-
}, {
|
|
28
|
-
kind: "service-unreachable" | "credential-missing";
|
|
29
|
-
detail: string;
|
|
30
|
-
}>;
|
|
31
|
-
/** What the server made of a blocker's claim.
|
|
32
|
-
*
|
|
33
|
-
* `refuted` is the one that costs the agent something: the probe found the
|
|
34
|
-
* opposite of what the blocker said, so the answer closes nothing. `unverifiable`
|
|
35
|
-
* is not a soft refusal — it is the server saying it could not look, which is
|
|
36
|
-
* where every close stood before these checks existed. */
|
|
37
|
-
export type BlockerVerdict = "verified" | "refuted" | "unverifiable";
|
|
38
|
-
/** The three probes, INJECTED so this module reaches neither the network nor the
|
|
39
|
-
* filesystem — the same rule `VerifyContext.citedFileExists` follows. Each returns
|
|
40
|
-
* UNDEFINED when it could not tell, which is a verdict of its own and never a
|
|
41
|
-
* silent pass. */
|
|
42
|
-
export interface BlockerChecks {
|
|
43
|
-
/** Whether the service at this URL answered an HTTP request. Any status counts:
|
|
44
|
-
* a 500 is a service that is running. */
|
|
45
|
-
serviceAnswers(url: string): boolean | undefined;
|
|
46
|
-
/** Whether the run was given browser credentials. */
|
|
47
|
-
credentialsSupplied: boolean | undefined;
|
|
48
|
-
/** Whether a page capture this run took reached this URL's page and read its
|
|
49
|
-
* elements. A page that redirected to a login reports the login's URL. */
|
|
50
|
-
pageLoaded(url: string): boolean | undefined;
|
|
51
|
-
}
|
|
52
|
-
/** Whether an answer carries a blocker at all: a kind the schema knows and a detail
|
|
53
|
-
* that says something. Read defensively — the plan comes off disk, so the declared
|
|
54
|
-
* type holds only on the validated path. */
|
|
55
|
-
export declare function hasBlocker(blocker: unknown): blocker is PlanBlocker;
|
|
56
|
-
/** The blocker as one line, for a report field and an objection's evidence. */
|
|
57
|
-
export declare function blockerText(blocker: PlanBlocker): string;
|
|
58
|
-
/** An http(s) URL the probe can be pointed at, or UNDEFINED. A detail that is not
|
|
59
|
-
* one is not a wrong claim — it is a claim nothing can be made of. */
|
|
60
|
-
export declare function probeUrl(detail: string): string | undefined;
|
|
61
|
-
/** What the server made of this blocker. Pure: every fact comes from `checks`. */
|
|
62
|
-
export declare function verifyBlocker(blocker: PlanBlocker, checks: BlockerChecks): BlockerVerdict;
|
|
63
|
-
/** What the server saw, in the words the refusal quotes back. Only ever called on a
|
|
64
|
-
* `refuted` verdict, so each branch states the opposite of the claim. */
|
|
65
|
-
export declare function refutedEvidence(blocker: PlanBlocker): string;
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
/** What `blocker` is, in the words both tools' schemas give the agent. One string:
|
|
3
|
-
* the plan states the rule before the tests run and the report states it after, and
|
|
4
|
-
* two copies drifted the moment either was edited. */
|
|
5
|
-
export const BLOCKER_DESCRIPTION = "What stopped this run from writing the test, as one of two claims the server checks: `service-unreachable` with the URL the run needed, or `credential-missing` with the URL of the page the missing login guards. An objection about a change or a reported bug with no test, or about a value a page and API change accepts or refuses that no UI test names, closes only with one of these, and the server probes it — a claim it finds untrue leaves the objection open. A reason the test is not worth writing is not a blocker. A step that the browser tools cannot record is not a blocker either: record the steps they can, then edit the test file the generator writes and add the missing step yourself.";
|
|
6
|
-
/** The `kind` values, in the schema's own words. */
|
|
7
|
-
export const BLOCKER_KIND_DESCRIPTION = "Which of the two checkable things stopped the test. `service-unreachable`: the run could not reach a service it needed. `credential-missing`: the run holds no login for a page the test has to get past.";
|
|
8
|
-
export const BLOCKER_DETAIL_DESCRIPTION = "The fact behind the claim: for `service-unreachable` the URL, spelled as the run tried it — the server requests it; for `credential-missing` the URL of the page that asked for the login — the server reads it against the pages this run already loaded, and a page the run loaded with no login needs none.";
|
|
9
|
-
/** The blocker both tools take, as the schema that rejects anything else. An
|
|
10
|
-
* OBJECT, not a sentence: free text let an argument close an untested defect, and
|
|
11
|
-
* each kind here names one fact the server can probe. `.strict()` so an invented
|
|
12
|
-
* kind or an extra field fails the schema rather than reading as a claim nothing
|
|
13
|
-
* could check. */
|
|
14
|
-
export const blockerSchema = z
|
|
15
|
-
.object({
|
|
16
|
-
kind: z
|
|
17
|
-
.enum(["service-unreachable", "credential-missing"])
|
|
18
|
-
.describe(BLOCKER_KIND_DESCRIPTION),
|
|
19
|
-
detail: z
|
|
20
|
-
.string()
|
|
21
|
-
.refine((value) => value.trim().length > 0, {
|
|
22
|
-
message: "a blocker's detail must not be blank",
|
|
23
|
-
})
|
|
24
|
-
.describe(BLOCKER_DETAIL_DESCRIPTION),
|
|
25
|
-
})
|
|
26
|
-
.strict()
|
|
27
|
-
// A detail the server cannot make anything of used to be accepted and then called
|
|
28
|
-
// `unverifiable`, which closes — so `service-unreachable: outside this PR` was the
|
|
29
|
-
// free-text escape this schema exists to remove. Each kind states its detail in the
|
|
30
|
-
// shape its check takes, or the plan is refused here. `credential-missing` was a
|
|
31
|
-
// sentence, and it closed an XR02 page objection on a page that needs no login.
|
|
32
|
-
.superRefine((blocker, ctx) => {
|
|
33
|
-
const detail = String(blocker?.detail ?? "").trim();
|
|
34
|
-
if (!detail || probeUrl(detail))
|
|
35
|
-
return;
|
|
36
|
-
ctx.addIssue({
|
|
37
|
-
code: z.ZodIssueCode.custom,
|
|
38
|
-
path: ["detail"],
|
|
39
|
-
message: blocker.kind === "credential-missing"
|
|
40
|
-
? "a `credential-missing` detail is the URL of the page that asked for the login, with an `http://` or `https://` scheme — the server reads it against the pages this run loaded."
|
|
41
|
-
: "a `service-unreachable` detail is the URL the run tried, with an `http://` or `https://` scheme — the server requests it. A cloud metadata address is not requested.",
|
|
42
|
-
});
|
|
43
|
-
});
|
|
44
|
-
/** Whether an answer carries a blocker at all: a kind the schema knows and a detail
|
|
45
|
-
* that says something. Read defensively — the plan comes off disk, so the declared
|
|
46
|
-
* type holds only on the validated path. */
|
|
47
|
-
export function hasBlocker(blocker) {
|
|
48
|
-
const kind = blocker?.kind;
|
|
49
|
-
const detail = blocker?.detail;
|
|
50
|
-
return ((kind === "service-unreachable" || kind === "credential-missing") &&
|
|
51
|
-
typeof detail === "string" &&
|
|
52
|
-
detail.trim().length > 0);
|
|
53
|
-
}
|
|
54
|
-
/** The blocker as one line, for a report field and an objection's evidence. */
|
|
55
|
-
export function blockerText(blocker) {
|
|
56
|
-
return `${blocker.kind}: ${String(blocker.detail ?? "").trim()}`;
|
|
57
|
-
}
|
|
58
|
-
/** The addresses a probe must never be pointed at: the cloud metadata endpoint, in
|
|
59
|
-
* the two spellings it wears. The service under test runs on the runner, so
|
|
60
|
-
* loopback, `host.docker.internal` and the private ranges stay reachable — they
|
|
61
|
-
* are the probe's whole purpose. Only the link-local range, which serves nothing
|
|
62
|
-
* a test needs and serves instance credentials on every major cloud, is closed. */
|
|
63
|
-
function isMetadataAddress(hostname) {
|
|
64
|
-
const host = hostname.replace(/^\[|\]$/g, "").toLowerCase();
|
|
65
|
-
// 169.254.0.0/16, the same range mapped into IPv6 (the URL parser writes it as
|
|
66
|
-
// `::ffff:a9fe:…`), and fe80::/10 written as any of its four leading nibbles.
|
|
67
|
-
if (/^169\.254\./.test(host) ||
|
|
68
|
-
/^::ffff:a9fe:/.test(host) ||
|
|
69
|
-
/^fe[89ab][0-9a-f]:/.test(host))
|
|
70
|
-
return true;
|
|
71
|
-
return host === "metadata" || host === "metadata.google.internal";
|
|
72
|
-
}
|
|
73
|
-
/** An http(s) URL the probe can be pointed at, or UNDEFINED. A detail that is not
|
|
74
|
-
* one is not a wrong claim — it is a claim nothing can be made of. */
|
|
75
|
-
export function probeUrl(detail) {
|
|
76
|
-
try {
|
|
77
|
-
const url = new URL(detail.trim());
|
|
78
|
-
if (url.protocol !== "http:" && url.protocol !== "https:")
|
|
79
|
-
return undefined;
|
|
80
|
-
// `fetch` throws on a URL with a login in it, which would read as unreachable.
|
|
81
|
-
if (url.username || url.password)
|
|
82
|
-
return undefined;
|
|
83
|
-
return isMetadataAddress(url.hostname) ? undefined : url.toString();
|
|
84
|
-
}
|
|
85
|
-
catch {
|
|
86
|
-
return undefined;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
/** What the server made of this blocker. Pure: every fact comes from `checks`. */
|
|
90
|
-
export function verifyBlocker(blocker, checks) {
|
|
91
|
-
const detail = String(blocker?.detail ?? "").trim();
|
|
92
|
-
switch (blocker?.kind) {
|
|
93
|
-
case "service-unreachable": {
|
|
94
|
-
const url = probeUrl(detail);
|
|
95
|
-
if (!url)
|
|
96
|
-
return "unverifiable";
|
|
97
|
-
const answered = checks?.serviceAnswers(url);
|
|
98
|
-
return answered === undefined
|
|
99
|
-
? "unverifiable"
|
|
100
|
-
: answered
|
|
101
|
-
? "refuted"
|
|
102
|
-
: "verified";
|
|
103
|
-
}
|
|
104
|
-
case "credential-missing": {
|
|
105
|
-
// The run records ONE opaque credentials string, so `true` says a login was
|
|
106
|
-
// supplied and nothing about which one — and that login may be what let a
|
|
107
|
-
// capture reach the page. Only `false` states a fact about the claim, and then
|
|
108
|
-
// a capture of the page says the page needed no login.
|
|
109
|
-
const url = probeUrl(detail);
|
|
110
|
-
if (!url || checks?.credentialsSupplied !== false)
|
|
111
|
-
return "unverifiable";
|
|
112
|
-
const loaded = checks?.pageLoaded(url);
|
|
113
|
-
return loaded === undefined
|
|
114
|
-
? "unverifiable"
|
|
115
|
-
: loaded
|
|
116
|
-
? "refuted"
|
|
117
|
-
: "verified";
|
|
118
|
-
}
|
|
119
|
-
default:
|
|
120
|
-
return "unverifiable";
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
/** What the server saw, in the words the refusal quotes back. Only ever called on a
|
|
124
|
-
* `refuted` verdict, so each branch states the opposite of the claim. */
|
|
125
|
-
export function refutedEvidence(blocker) {
|
|
126
|
-
const detail = String(blocker?.detail ?? "").trim();
|
|
127
|
-
switch (blocker?.kind) {
|
|
128
|
-
case "service-unreachable":
|
|
129
|
-
return `${detail} answered an HTTP request, so the service is running`;
|
|
130
|
-
case "credential-missing":
|
|
131
|
-
return `this run loaded ${detail} with no login and read its elements, so the page needs none`;
|
|
132
|
-
default:
|
|
133
|
-
return blockerText(blocker);
|
|
134
|
-
}
|
|
135
|
-
}
|