@skyramp/mcp 0.4.3 → 0.4.4-rc.1
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/playwright/traceRecordingPrompt.js +2 -1
- package/build/prompts/orderAssertionRule.d.ts +9 -0
- package/build/prompts/orderAssertionRule.js +9 -0
- package/build/prompts/test-maintenance/driftAnalysisShared.js +2 -0
- package/build/prompts/testbot/testbot-prompts.js +10 -7
- package/build/recommendation/answers.d.ts +54 -19
- package/build/recommendation/answers.js +102 -31
- package/build/recommendation/blockerProbes.d.ts +13 -0
- package/build/recommendation/blockerProbes.js +91 -0
- package/build/recommendation/blockers.d.ts +65 -0
- package/build/recommendation/blockers.js +135 -0
- package/build/recommendation/pullRequestText.d.ts +2 -0
- package/build/recommendation/pullRequestText.js +16 -0
- package/build/recommendation/registerPlan.d.ts +7 -4
- package/build/recommendation/registerPlan.js +37 -11
- package/build/recommendation/types.d.ts +21 -4
- package/build/recommendation/verifierContracts.d.ts +6 -6
- package/build/recommendation/verifierContracts.js +8 -7
- package/build/recommendation/verifiers/citedPath.d.ts +2 -2
- package/build/recommendation/verifiers/citedPath.js +2 -2
- package/build/recommendation/verifiers/coverage.js +25 -25
- package/build/recommendation/verifiers/deliveredMatchesPlan.js +3 -3
- package/build/recommendation/verifiers/issueTraceability.d.ts +1 -5
- package/build/recommendation/verifiers/issueTraceability.js +4 -35
- package/build/tools/executeSkyrampTestTool.js +8 -0
- package/build/tools/generate-tests/generateE2ERestTool.d.ts +1 -0
- package/build/tools/generate-tests/generateE2ERestTool.js +9 -1
- package/build/tools/generate-tests/generateUIRestTool.d.ts +2 -0
- package/build/tools/generate-tests/generateUIRestTool.js +9 -1
- package/build/tools/submitReportTool.d.ts +9 -9
- package/build/tools/submitReportTool.js +178 -26
- package/build/tools/test-management/registerTestPlanTool.d.ts +20 -14
- package/build/tools/test-management/registerTestPlanTool.js +30 -14
- package/build/types/TestTypes.d.ts +7 -0
- package/build/types/TestTypes.js +22 -0
- package/build/types/TestbotReport.d.ts +4 -1
- package/build/utils/assertion-verify/metrics.js +12 -0
- package/build/utils/findUp.d.ts +20 -0
- package/build/utils/findUp.js +59 -0
- package/build/utils/language-helper.js +1 -1
- package/build/utils/repoSdkVersion.d.ts +16 -0
- package/build/utils/repoSdkVersion.js +88 -0
- package/build/utils/testDependencyPolicy.d.ts +20 -0
- package/build/utils/testDependencyPolicy.js +64 -4
- package/build/utils/utils-verify/typecheck.js +3 -18
- package/node_modules/playwright/lib/mcp/skyramp/assertOrder.js +66 -0
- package/node_modules/playwright/lib/mcp/skyramp/assertTool.js +12 -3
- package/node_modules/playwright/lib/mcp/skyramp/common/liveReadError.js +40 -0
- package/node_modules/playwright/lib/mcp/skyramp/loadTraceTool.js +21 -0
- package/node_modules/playwright/lib/mcp/skyramp/skyRampImport.js +2 -1
- package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +96 -16
- package/node_modules/playwright/lib/mcp/test/skyRampExport.js +10 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/server/codegen/skyramp/jsonlReader.js +1 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/recorderRunner.js +22 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/utils/isomorphic/elementOrder.js +57 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/utils.js +2 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{index.uiPTFncY.js → index.0MJW0uG7.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html +1 -1
- package/node_modules/playwright/node_modules/playwright-core/package.json +1 -1
- package/node_modules/playwright/node_modules/playwright-core/src/server/codegen/skyramp/jsonlReader.ts +1 -1
- package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/recorderRunner.ts +26 -1
- package/node_modules/playwright/node_modules/playwright-core/src/utils/isomorphic/elementOrder.ts +83 -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 +4 -2
- package/plugin/prompts/plan-tests.md +9 -17
- package/plugin/skills/enhance-assertions/reference/shared-rules.md +1 -1
- package/plugin/skills/enhance-assertions/reference/ui.md +2 -2
- package/plugin/skills/fix-test-import-errors/SKILL.md +1 -1
|
@@ -5,15 +5,17 @@ import { constants as fsConstants } from "fs";
|
|
|
5
5
|
import * as path from "path";
|
|
6
6
|
import { AnalyticsService } from "../services/AnalyticsService.js";
|
|
7
7
|
import { SCENARIO_CATEGORIES, categoryExpectsRed, } from "../types/TestRecommendation.js";
|
|
8
|
-
import { TESTBOT_SUPPORTED_TEST_TYPES, TestType } from "../types/TestTypes.js";
|
|
8
|
+
import { ProgrammingLanguage, TESTBOT_SUPPORTED_TEST_TYPES, TestType, } from "../types/TestTypes.js";
|
|
9
9
|
import { STEP_METHOD_DESCRIPTION, stepMethodSchema, } from "../types/StepMethod.js";
|
|
10
10
|
import { DriftAction } from "../types/TestAnalysis.js";
|
|
11
|
-
import { TestExecutionStatus } from "../types/TestExecution.js";
|
|
11
|
+
import { isResult, TestExecutionStatus } from "../types/TestExecution.js";
|
|
12
12
|
import { IssueFoundCategory } from "../types/TestbotReport.js";
|
|
13
13
|
import { StateManager, runArtifactDir, getTestsRepoDir, resolveOwnRunStatePath, } from "../utils/AnalysisStateManager.js";
|
|
14
14
|
import { toolError, testFileMatches } from "../utils/utils.js";
|
|
15
15
|
import { isTestbotEnabled, isUtilsReuseEnabled, } from "../utils/featureFlags.js";
|
|
16
|
-
import {
|
|
16
|
+
import { BLOCKER_DESCRIPTION, closingAnswerFor, needsBlocker, REFUSED_ANSWER_PREFIX, refusedAnswerObjections, unknownAnswerObjections, } from "../recommendation/answers.js";
|
|
17
|
+
import { blockersIn, probeBlockers } from "../recommendation/blockerProbes.js";
|
|
18
|
+
import { blockerSchema, blockerText, hasBlocker, refutedEvidence, verifyBlocker, } from "../recommendation/blockers.js";
|
|
17
19
|
import { checkDeliveredMatchesPlan, checkMaintenanceDelivered, } from "../recommendation/verifiers/deliveredMatchesPlan.js";
|
|
18
20
|
import { checkReportedCategoryMatchesPlan, checkRequirementConflictReported, } from "../recommendation/verifiers/reportedCategory.js";
|
|
19
21
|
import { checkDefectsReported, checkIssueTraceability, } from "../recommendation/verifiers/issueTraceability.js";
|
|
@@ -79,6 +81,28 @@ function rowsLeavingCoverage(rows) {
|
|
|
79
81
|
MAINTENANCE_COVERAGE_ACTIONS.has(row.action) &&
|
|
80
82
|
row?.reportOnly !== true);
|
|
81
83
|
}
|
|
84
|
+
/** The VERIFY rows this run owes one run: the test is read, not edited, so its
|
|
85
|
+
* only run is the one after the edits. */
|
|
86
|
+
function verifyRows(rows) {
|
|
87
|
+
return (rows ?? []).filter((row) => row?.action === DriftAction.Verify && row?.reportOnly !== true);
|
|
88
|
+
}
|
|
89
|
+
/** The rows that back a `maintains` claim: an edit this run made, or a VERIFY
|
|
90
|
+
* whose test this server ran after the edits to a result. A VERIFY that never
|
|
91
|
+
* ran, or whose run left only the Unknown reservation, backs nothing. In the
|
|
92
|
+
* plan-only lane nothing runs, so a VERIFY backs the claim without a run. */
|
|
93
|
+
function rowsBackingMaintains(rows, executions, planOnly) {
|
|
94
|
+
return [
|
|
95
|
+
...rowsLeavingCoverage(rows),
|
|
96
|
+
...verifyRows(rows).filter((row) => {
|
|
97
|
+
if (planOnly)
|
|
98
|
+
return true;
|
|
99
|
+
if (typeof row.testFilePath !== "string")
|
|
100
|
+
return false;
|
|
101
|
+
const run = executions?.[canonicalTestPath(row.testFilePath)];
|
|
102
|
+
return (run?.phase === "after" && isResult(run.status));
|
|
103
|
+
}),
|
|
104
|
+
];
|
|
105
|
+
}
|
|
82
106
|
/** Objection ids for an untested declared change, `coverage:change:<id>`. The
|
|
83
107
|
* change table reads the agent's answer back off them. */
|
|
84
108
|
const CHANGE_OBJECTION_PREFIX = "coverage:change:";
|
|
@@ -350,7 +374,7 @@ const issueFoundSchema = z
|
|
|
350
374
|
.optional()
|
|
351
375
|
.describe("The `id` of the plan defect this issue reports, from the `defects` list you registered with skyramp_register_test_plan. Spell it as that list spells it: an id the plan does not declare traces nothing. " +
|
|
352
376
|
"Set it on the `category: bug` entry that reports the defect. A `defectId` on a `lint`, `type` or `config` entry does not report the defect — the report renders those in its own Configuration Errors section. " +
|
|
353
|
-
"CHECKED: every plan defect has a `bug` issue that names it, and a `bug` entry with no `plannedTestId` traces through this defect when a delivered test that expects to fail cites it, when a `maintains` entry you registered cites it
|
|
377
|
+
"CHECKED: every plan defect has a `bug` issue that names it, and a `bug` entry with no `plannedTestId` traces through this defect when a delivered test that expects to fail cites it, or when a `maintains` entry you registered cites it. Leave it out for an issue the plan did not declare."),
|
|
354
378
|
sourceFile: citationString.describe("Path of the application file whose code is missing or wrong, relative to the repository root (e.g. 'src/crud/products.py'). " +
|
|
355
379
|
"REQUIRED when category is 'bug'. " +
|
|
356
380
|
"For code that is MISSING — an unmounted router, an unregistered route, an import never added — name the file where the line should be, not the file that defines what is unmounted. " +
|
|
@@ -1217,6 +1241,9 @@ async function pathExists(file) {
|
|
|
1217
1241
|
* missing timestamp on either side falls back to "a run is a run", which is the
|
|
1218
1242
|
* behaviour before this and never invents an objection. */
|
|
1219
1243
|
function runOfWhatWasWritten(file) {
|
|
1244
|
+
if (file.how === "verified")
|
|
1245
|
+
return (file.recordedPhase === "after" &&
|
|
1246
|
+
file.recordedStatus !== TestExecutionStatus.Unknown);
|
|
1220
1247
|
if (file.how === "updated")
|
|
1221
1248
|
return file.recordedPhase === "after";
|
|
1222
1249
|
if (file.recordedPhase === undefined)
|
|
@@ -1229,7 +1256,8 @@ function runOfWhatWasWritten(file) {
|
|
|
1229
1256
|
return true;
|
|
1230
1257
|
return ran >= wrote;
|
|
1231
1258
|
}
|
|
1232
|
-
/** Every test file this run wrote
|
|
1259
|
+
/** Every test file this run wrote, and every VERIFY file it read, has an
|
|
1260
|
+
* execution this server performed.
|
|
1233
1261
|
*
|
|
1234
1262
|
* Informational, like every check here: it surfaces the gap for a person to read
|
|
1235
1263
|
* and the agent can answer it. The agent is not blocked — a hard ban on running a
|
|
@@ -1257,9 +1285,11 @@ function checkEveryWrittenTestRan(written) {
|
|
|
1257
1285
|
// drove itself is not visible here.
|
|
1258
1286
|
message: file.how === "generated"
|
|
1259
1287
|
? "This run generated this test file, and this server recorded no run of it after it was written."
|
|
1260
|
-
: file.
|
|
1261
|
-
? "This run
|
|
1262
|
-
:
|
|
1288
|
+
: file.how === "verified"
|
|
1289
|
+
? "This run marked this test file VERIFY, and this server recorded no run of it after the edits."
|
|
1290
|
+
: file.recordedPhase === "before"
|
|
1291
|
+
? "This run edited this test file, and the only run of it this server recorded is from before the edit."
|
|
1292
|
+
: "This run edited this test file, and this server recorded no run of it.",
|
|
1263
1293
|
evidence: file.recordedPhase === "before"
|
|
1264
1294
|
? `${file.file} — the only recorded skyramp_execute_test run of this file is the pre-edit baseline`
|
|
1265
1295
|
: file.how === "generated" && file.recordedPhase !== undefined
|
|
@@ -1382,11 +1412,12 @@ export function registerSubmitReportTool(server) {
|
|
|
1382
1412
|
answer: z
|
|
1383
1413
|
.string()
|
|
1384
1414
|
.describe("Why the objection does not apply, or what you decided instead. A blank or whitespace-only answer closes nothing and the objection stays open."),
|
|
1415
|
+
blocker: blockerSchema.optional().describe(BLOCKER_DESCRIPTION),
|
|
1385
1416
|
})
|
|
1386
1417
|
.strict())
|
|
1387
1418
|
.optional()
|
|
1388
1419
|
.default([])
|
|
1389
|
-
.describe("One answer per check that objected to your previous call and that you do not intend to fix. An answered objection is published with your answer beside it and no longer counted open; an unanswered one is published on its own. Answers persist across calls, so an id answered once stays answered. There is no call limit."),
|
|
1420
|
+
.describe("One answer per check that objected to your previous call and that you do not intend to fix. An answered objection is published with your answer beside it and no longer counted open; an unanswered one is published on its own. An objection about a bug with no test is the exception: it closes only when the answer also sets `blocker`, and a sentence on its own leaves it open and published. Answers persist across calls, so an id answered once stays answered — with the blocker it carried. There is no call limit."),
|
|
1390
1421
|
stateFile: z
|
|
1391
1422
|
.string()
|
|
1392
1423
|
.optional()
|
|
@@ -2195,9 +2226,10 @@ export function registerSubmitReportTool(server) {
|
|
|
2195
2226
|
// Every file this run wrote, from the server's own records — one canonical
|
|
2196
2227
|
// absolute path each, which is also the key an execution is recorded under.
|
|
2197
2228
|
// Generated files come from the generators; edited files from the maintenance
|
|
2198
|
-
// triage, narrowed by `rowsLeavingCoverage` to UPDATE and REGENERATE.
|
|
2199
|
-
//
|
|
2200
|
-
//
|
|
2229
|
+
// triage, narrowed by `rowsLeavingCoverage` to UPDATE and REGENERATE. A VERIFY
|
|
2230
|
+
// file was not written but owes the same after-edit run. The rest owe none:
|
|
2231
|
+
// DELETE removed the file, IGNORE never touched one, and a `reportOnly` row
|
|
2232
|
+
// only advised a change this run did not make.
|
|
2201
2233
|
//
|
|
2202
2234
|
// A verdict names the SPEC, never `pomFile`: a POM-backed row edits the page
|
|
2203
2235
|
// object, but the execution is recorded against the spec.
|
|
@@ -2232,6 +2264,23 @@ export function registerSubmitReportTool(server) {
|
|
|
2232
2264
|
filesWritten.set(key, "updated");
|
|
2233
2265
|
}
|
|
2234
2266
|
}
|
|
2267
|
+
// A VERIFY file is not written, but owes the same after-edit run
|
|
2268
|
+
// (SKYR-4523). Primary only: the server turns every edit to a related
|
|
2269
|
+
// repository's test into a VERIFY the agent never chose. A language
|
|
2270
|
+
// skyramp_execute_test does not accept can never record a run.
|
|
2271
|
+
const runnableLanguages = Object.values(ProgrammingLanguage);
|
|
2272
|
+
for (const verdict of verifyRows(stateData.maintenanceVerdicts)) {
|
|
2273
|
+
const raw = verdict.testFilePath?.trim();
|
|
2274
|
+
if (!raw)
|
|
2275
|
+
continue;
|
|
2276
|
+
const key = canonicalTestPath(raw);
|
|
2277
|
+
const row = stateData.existingTests?.find((t) => canonicalTestPath(t.testFile) === key);
|
|
2278
|
+
// An uncatalogued file has no row for its result to land on.
|
|
2279
|
+
if (!row || !runnableLanguages.includes(row.language))
|
|
2280
|
+
continue;
|
|
2281
|
+
if (!filesWritten.has(key) && !deletedPaths.has(key))
|
|
2282
|
+
filesWritten.set(key, "verified");
|
|
2283
|
+
}
|
|
2235
2284
|
// The LATEST run of each file, because a re-run replaces the record. An edited
|
|
2236
2285
|
// file needs a run of the edit, so its pre-edit baseline does not answer for
|
|
2237
2286
|
// it — `phase` is what tells the two apart. A generated file has no baseline,
|
|
@@ -2240,6 +2289,7 @@ export function registerSubmitReportTool(server) {
|
|
|
2240
2289
|
file,
|
|
2241
2290
|
{
|
|
2242
2291
|
phase: String(record?.phase ?? ""),
|
|
2292
|
+
status: String(record?.status ?? ""),
|
|
2243
2293
|
executedAt: String(record?.executedAt ?? ""),
|
|
2244
2294
|
},
|
|
2245
2295
|
])));
|
|
@@ -2256,6 +2306,7 @@ export function registerSubmitReportTool(server) {
|
|
|
2256
2306
|
file,
|
|
2257
2307
|
how,
|
|
2258
2308
|
recordedPhase: run?.phase,
|
|
2309
|
+
recordedStatus: run?.status,
|
|
2259
2310
|
executedAt: run?.executedAt,
|
|
2260
2311
|
generatedAt: generatedAt.get(file),
|
|
2261
2312
|
});
|
|
@@ -2265,6 +2316,7 @@ export function registerSubmitReportTool(server) {
|
|
|
2265
2316
|
const executionCoverage = {
|
|
2266
2317
|
generated: written.filter((f) => f.how === "generated").length,
|
|
2267
2318
|
updated: written.filter((f) => f.how === "updated").length,
|
|
2319
|
+
verified: written.filter((f) => f.how === "verified").length,
|
|
2268
2320
|
executed: written.filter(runOfWhatWasWritten).length,
|
|
2269
2321
|
};
|
|
2270
2322
|
// A skipped test is not an execution. Refuse before writing a report so the
|
|
@@ -2423,10 +2475,11 @@ export function registerSubmitReportTool(server) {
|
|
|
2423
2475
|
}, dedupedNewTests, params.testResults, params.issuesFound ?? [], isPlanOnlyMode(),
|
|
2424
2476
|
// The triage's own verdicts, not this tool's input, so the join is against
|
|
2425
2477
|
// what the run is recorded as having edited — and only the rows that left a
|
|
2426
|
-
// test standing and were actually applied,
|
|
2427
|
-
//
|
|
2428
|
-
//
|
|
2429
|
-
|
|
2478
|
+
// test standing and were actually applied, or a VERIFY whose test ran after
|
|
2479
|
+
// the edits — so an unrun assessment, a deletion or a report-only
|
|
2480
|
+
// recommendation cannot corroborate. The verdicts rather than the
|
|
2481
|
+
// published rows because `reportOnly` lives here.
|
|
2482
|
+
rowsBackingMaintains(stateData.maintenanceVerdicts, stateData.testExecutions, isPlanOnlyMode()));
|
|
2430
2483
|
// Read defensively, like the crash-contained checks above: the plan comes
|
|
2431
2484
|
// off disk, so its declared array type holds only on the validated path.
|
|
2432
2485
|
const stillOpen = Array.isArray(stateData.plan?.openObjections)
|
|
@@ -2460,6 +2513,14 @@ export function registerSubmitReportTool(server) {
|
|
|
2460
2513
|
typeof entry?.answer === "string" &&
|
|
2461
2514
|
entry.answer.trim().length > 0);
|
|
2462
2515
|
const carriedByKey = new Map(carried.map((entry) => [answerKey(entry.objection), entry]));
|
|
2516
|
+
// Probed here for the same reason the plan tool probes before building its
|
|
2517
|
+
// context: the checks below are synchronous. Both this call's blockers and
|
|
2518
|
+
// the carried ones, so a carried close is re-judged rather than trusted.
|
|
2519
|
+
const blockerChecks = await probeBlockers([
|
|
2520
|
+
...blockersIn(acknowledged),
|
|
2521
|
+
...blockersIn(carried),
|
|
2522
|
+
]);
|
|
2523
|
+
const judge = (blocker) => verifyBlocker(blocker, blockerChecks);
|
|
2463
2524
|
// What the LAST call raised. Without it there is nothing to notice a
|
|
2464
2525
|
// fixed objection's absence against.
|
|
2465
2526
|
const previouslyRaised = (Array.isArray(stateData.reportObjections?.raisedObjections)
|
|
@@ -2486,12 +2547,35 @@ export function registerSubmitReportTool(server) {
|
|
|
2486
2547
|
...raised,
|
|
2487
2548
|
...previouslyRaised.map((entry) => entry.objection),
|
|
2488
2549
|
];
|
|
2550
|
+
const raisedKeys = new Set(raised.map(answerKey));
|
|
2489
2551
|
const reportStageObjections = [
|
|
2490
2552
|
...raised,
|
|
2491
2553
|
...unknownAnswerObjections(answerable, acknowledged, {
|
|
2492
2554
|
scope: "this run raised",
|
|
2493
2555
|
suggestion: "Answer only the ids this tool has listed on this run. A plan-time objection is answered through skyramp_register_test_plan's own answers, not here.",
|
|
2494
2556
|
}),
|
|
2557
|
+
// The same refusal plan time raises: a sentence sent for an objection that
|
|
2558
|
+
// takes a blocker leaves that objection open, and this says so rather than
|
|
2559
|
+
// letting the answer pass in silence. Against this call's answers only,
|
|
2560
|
+
// like `registerPlan` — a carried one that closed nothing simply stays
|
|
2561
|
+
// carried, and its objection stays published open.
|
|
2562
|
+
...refusedAnswerObjections(raised, acknowledged, judge, (id) =>
|
|
2563
|
+
// The same rule the loop below applies to a carried answer. Without
|
|
2564
|
+
// it a second, weaker answer for an objection an earlier call already
|
|
2565
|
+
// closed raised `answers:refused:` beside the close this call still
|
|
2566
|
+
// publishes as answered. Matched by id AND message, as `carriedByKey`
|
|
2567
|
+
// is: one id can name a different bug on a later call, and a close
|
|
2568
|
+
// carried for the first must not silence a refusal for the second.
|
|
2569
|
+
carried.some((entry) => {
|
|
2570
|
+
if (entry.objection?.objectionId !== id)
|
|
2571
|
+
return false;
|
|
2572
|
+
if (!raisedKeys.has(answerKey(entry.objection)))
|
|
2573
|
+
return false;
|
|
2574
|
+
const blocker = hasBlocker(entry.blocker)
|
|
2575
|
+
? entry.blocker
|
|
2576
|
+
: undefined;
|
|
2577
|
+
return (!needsBlocker(id) || (!!blocker && judge(blocker) !== "refuted"));
|
|
2578
|
+
})),
|
|
2495
2579
|
];
|
|
2496
2580
|
const raisedNow = new Set(reportStageObjections.map(answerKey));
|
|
2497
2581
|
// A call with no execution result gives these checks nothing to judge, so
|
|
@@ -2521,12 +2605,57 @@ export function registerSubmitReportTool(server) {
|
|
|
2521
2605
|
const answeredReportObjections = [];
|
|
2522
2606
|
const openReportObjections = [];
|
|
2523
2607
|
for (const objection of reportStageObjections) {
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2608
|
+
// `closingAnswerFor` applies the blocker rule to THIS call's answers. A
|
|
2609
|
+
// carried answer was accepted on an earlier call and is re-checked against
|
|
2610
|
+
// the same rule here, because the objection it closed is recomputed every
|
|
2611
|
+
// call. The answer and the blocker come from the ONE entry that closed it:
|
|
2612
|
+
// searching for the blocker apart from the answer published the first
|
|
2613
|
+
// blocker sent, refuted or belonging to no answer at all.
|
|
2614
|
+
const wantsBlocker = needsBlocker(objection.objectionId);
|
|
2615
|
+
const closingNow = closingAnswerFor(objection, acknowledged, judge);
|
|
2616
|
+
const blockerNow = hasBlocker(closingNow?.blocker)
|
|
2617
|
+
? closingNow.blocker
|
|
2618
|
+
: undefined;
|
|
2619
|
+
const carriedEntry = carriedByKey.get(answerKey(objection));
|
|
2620
|
+
const carriedBlocker = hasBlocker(carriedEntry?.blocker)
|
|
2621
|
+
? carriedEntry?.blocker
|
|
2622
|
+
: undefined;
|
|
2623
|
+
const answerNow = closingNow?.answer;
|
|
2624
|
+
// A carried answer meets the same rule as the call that sent it: a
|
|
2625
|
+
// blocker, and one this call's probe does not refute.
|
|
2626
|
+
const carriedCloses = carriedEntry &&
|
|
2627
|
+
(!wantsBlocker ||
|
|
2628
|
+
(!!carriedBlocker && judge(carriedBlocker) !== "refuted"));
|
|
2629
|
+
const answer = answerNow ?? (carriedCloses ? carriedEntry?.answer : undefined);
|
|
2630
|
+
if (!answer) {
|
|
2529
2631
|
openReportObjections.push(objection);
|
|
2632
|
+
// A carried answer that closed this objection on an earlier call and
|
|
2633
|
+
// does not now is the probe changing its mind — the service came back
|
|
2634
|
+
// up. Without this the objection simply reappears with no reason.
|
|
2635
|
+
if (carriedEntry &&
|
|
2636
|
+
carriedBlocker &&
|
|
2637
|
+
judge(carriedBlocker) === "refuted") {
|
|
2638
|
+
openReportObjections.push({
|
|
2639
|
+
objectionId: `${REFUSED_ANSWER_PREFIX}${objection.objectionId}`,
|
|
2640
|
+
verifier: "answers",
|
|
2641
|
+
message: "The server checked the blocker this answer names and found the opposite, so the objection stays open.",
|
|
2642
|
+
evidence: `answered "${objection.objectionId}" on an earlier call with \`${carriedBlocker.kind}\`; ${refutedEvidence(carriedBlocker)}`,
|
|
2643
|
+
suggestion: "Write the test: what you named as the blocker is not in the way. If something else is, send the blocker that names it — the server checks each kind against the run it can see.",
|
|
2644
|
+
});
|
|
2645
|
+
}
|
|
2646
|
+
continue;
|
|
2647
|
+
}
|
|
2648
|
+
// A refuted blocker is not published, as at plan time: on an objection any
|
|
2649
|
+
// answer closes, the sentence closes it and the untrue claim is dropped.
|
|
2650
|
+
const sent = answerNow ? blockerNow : carriedBlocker;
|
|
2651
|
+
const blocker = sent && judge(sent) !== "refuted" ? sent : undefined;
|
|
2652
|
+
answeredReportObjections.push({
|
|
2653
|
+
objection,
|
|
2654
|
+
answer,
|
|
2655
|
+
...(blocker
|
|
2656
|
+
? { blocker, blockerVerified: judge(blocker) === "verified" }
|
|
2657
|
+
: {}),
|
|
2658
|
+
});
|
|
2530
2659
|
}
|
|
2531
2660
|
objections = [
|
|
2532
2661
|
...stillOpen.map((objection) => ({
|
|
@@ -2539,6 +2668,16 @@ export function registerSubmitReportTool(server) {
|
|
|
2539
2668
|
...entry.objection,
|
|
2540
2669
|
stage: "plan",
|
|
2541
2670
|
answer: typeof entry.answer === "string" ? entry.answer : "",
|
|
2671
|
+
// A plan-time close that a blocker carried says so here, exactly as
|
|
2672
|
+
// the post-execution closes below do. Without this a probed close
|
|
2673
|
+
// was published as an ordinary sentence and the report lost the one
|
|
2674
|
+
// fact that distinguishes them.
|
|
2675
|
+
...(hasBlocker(entry.blocker)
|
|
2676
|
+
? {
|
|
2677
|
+
unverifiedBlocker: blockerText(entry.blocker),
|
|
2678
|
+
blockerVerified: !!entry.blockerVerified,
|
|
2679
|
+
}
|
|
2680
|
+
: {}),
|
|
2542
2681
|
})),
|
|
2543
2682
|
...unverified
|
|
2544
2683
|
.filter((entry) => entry?.objection)
|
|
@@ -2546,16 +2685,29 @@ export function registerSubmitReportTool(server) {
|
|
|
2546
2685
|
...entry.objection,
|
|
2547
2686
|
stage: "plan",
|
|
2548
2687
|
answer: typeof entry.answer === "string" ? entry.answer : "",
|
|
2549
|
-
|
|
2688
|
+
// `unverifiedCloses[].blocker` is a `PlanBlocker`; reading it as a
|
|
2689
|
+
// string published an empty one on every plan-time close.
|
|
2690
|
+
...(hasBlocker(entry.blocker)
|
|
2691
|
+
? { unverifiedBlocker: blockerText(entry.blocker) }
|
|
2692
|
+
: {}),
|
|
2550
2693
|
})),
|
|
2551
2694
|
...openReportObjections.map((objection) => ({
|
|
2552
2695
|
...objection,
|
|
2553
2696
|
stage: "post-execution",
|
|
2554
2697
|
})),
|
|
2555
|
-
|
|
2698
|
+
// The blocker is published beside the answer exactly as the plan-time
|
|
2699
|
+
// closes above are, and `blockerVerified` says whether a probe backed it
|
|
2700
|
+
// or the server could only take it as given.
|
|
2701
|
+
...answeredReportObjections.map(({ objection, answer, blocker, blockerVerified }) => ({
|
|
2556
2702
|
...objection,
|
|
2557
2703
|
stage: "post-execution",
|
|
2558
2704
|
answer,
|
|
2705
|
+
...(blocker
|
|
2706
|
+
? {
|
|
2707
|
+
unverifiedBlocker: blockerText(blocker),
|
|
2708
|
+
blockerVerified: !!blockerVerified,
|
|
2709
|
+
}
|
|
2710
|
+
: {}),
|
|
2559
2711
|
})),
|
|
2560
2712
|
...resolvedObjections.map(({ objection, resolvedBy }) => ({
|
|
2561
2713
|
...objection,
|
|
@@ -2593,10 +2745,10 @@ export function registerSubmitReportTool(server) {
|
|
|
2593
2745
|
//
|
|
2594
2746
|
// `testFileMatches` rather than a basename, so a spec under one directory
|
|
2595
2747
|
// cannot lend its coverage to a same-named spec under another; and only the
|
|
2596
|
-
// rows
|
|
2597
|
-
// corroborate a claim that an existing test covers the change.
|
|
2748
|
+
// rows `rowsBackingMaintains` keeps, so DELETE, IGNORE and an unrun VERIFY
|
|
2749
|
+
// cannot corroborate a claim that an existing test covers the change.
|
|
2598
2750
|
const editedPaths = [];
|
|
2599
|
-
for (const row of
|
|
2751
|
+
for (const row of rowsBackingMaintains(stateData.maintenanceVerdicts, stateData.testExecutions, isPlanOnlyMode())) {
|
|
2600
2752
|
for (const full of [
|
|
2601
2753
|
row?.testFilePath,
|
|
2602
2754
|
row?.pomFile,
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
3
|
import { StateManager, UnifiedAnalysisState } from "../../utils/AnalysisStateManager.js";
|
|
4
|
-
import { PlanResult } from "../../recommendation/registerPlan.js";
|
|
5
|
-
import { ObjectionAnswer, PlanChange, PlanDefect, PlanInput, PlanMaintenance, VerifyContext } from "../../recommendation/types.js";
|
|
4
|
+
import { Plan, PlanResult } from "../../recommendation/registerPlan.js";
|
|
5
|
+
import { ObjectionAnswer, PlanBlocker, PlanChange, PlanDefect, PlanInput, PlanMaintenance, VerifyContext } from "../../recommendation/types.js";
|
|
6
6
|
/** The whole run's state file: the primary repository at the root, every related
|
|
7
7
|
* repository under `relatedRepos`. */
|
|
8
8
|
type RunState = Awaited<ReturnType<StateManager<UnifiedAnalysisState>["readFullState"]>>;
|
|
@@ -72,9 +72,9 @@ export declare const declarationFieldsSchema: z.ZodObject<{
|
|
|
72
72
|
widgetType: z.ZodOptional<z.ZodEnum<["native", "custom", "unknown"]>>;
|
|
73
73
|
}, "strip", z.ZodTypeAny, {
|
|
74
74
|
role: string;
|
|
75
|
-
accessibleName: string;
|
|
76
75
|
testId: string | null;
|
|
77
76
|
stableId: string | null;
|
|
77
|
+
accessibleName: string;
|
|
78
78
|
contextText: string[] | null;
|
|
79
79
|
mutability?: "unknown" | "mutable" | "immutable" | undefined;
|
|
80
80
|
widgetType?: "unknown" | "custom" | "native" | undefined;
|
|
@@ -90,9 +90,9 @@ export declare const declarationFieldsSchema: z.ZodObject<{
|
|
|
90
90
|
}, "strict", z.ZodTypeAny, {
|
|
91
91
|
items?: {
|
|
92
92
|
role: string;
|
|
93
|
-
accessibleName: string;
|
|
94
93
|
testId: string | null;
|
|
95
94
|
stableId: string | null;
|
|
95
|
+
accessibleName: string;
|
|
96
96
|
contextText: string[] | null;
|
|
97
97
|
mutability?: "unknown" | "mutable" | "immutable" | undefined;
|
|
98
98
|
widgetType?: "unknown" | "custom" | "native" | undefined;
|
|
@@ -130,9 +130,9 @@ export declare const declarationFieldsSchema: z.ZodObject<{
|
|
|
130
130
|
elements?: {
|
|
131
131
|
items?: {
|
|
132
132
|
role: string;
|
|
133
|
-
accessibleName: string;
|
|
134
133
|
testId: string | null;
|
|
135
134
|
stableId: string | null;
|
|
135
|
+
accessibleName: string;
|
|
136
136
|
contextText: string[] | null;
|
|
137
137
|
mutability?: "unknown" | "mutable" | "immutable" | undefined;
|
|
138
138
|
widgetType?: "unknown" | "custom" | "native" | undefined;
|
|
@@ -328,9 +328,9 @@ declare const registerPlannedTestSchema: z.ZodEffects<z.ZodEffects<z.ZodObject<{
|
|
|
328
328
|
widgetType: z.ZodOptional<z.ZodEnum<["native", "custom", "unknown"]>>;
|
|
329
329
|
}, "strip", z.ZodTypeAny, {
|
|
330
330
|
role: string;
|
|
331
|
-
accessibleName: string;
|
|
332
331
|
testId: string | null;
|
|
333
332
|
stableId: string | null;
|
|
333
|
+
accessibleName: string;
|
|
334
334
|
contextText: string[] | null;
|
|
335
335
|
mutability?: "unknown" | "mutable" | "immutable" | undefined;
|
|
336
336
|
widgetType?: "unknown" | "custom" | "native" | undefined;
|
|
@@ -346,9 +346,9 @@ declare const registerPlannedTestSchema: z.ZodEffects<z.ZodEffects<z.ZodObject<{
|
|
|
346
346
|
}, "strict", z.ZodTypeAny, {
|
|
347
347
|
items?: {
|
|
348
348
|
role: string;
|
|
349
|
-
accessibleName: string;
|
|
350
349
|
testId: string | null;
|
|
351
350
|
stableId: string | null;
|
|
351
|
+
accessibleName: string;
|
|
352
352
|
contextText: string[] | null;
|
|
353
353
|
mutability?: "unknown" | "mutable" | "immutable" | undefined;
|
|
354
354
|
widgetType?: "unknown" | "custom" | "native" | undefined;
|
|
@@ -386,9 +386,9 @@ declare const registerPlannedTestSchema: z.ZodEffects<z.ZodEffects<z.ZodObject<{
|
|
|
386
386
|
elements?: {
|
|
387
387
|
items?: {
|
|
388
388
|
role: string;
|
|
389
|
-
accessibleName: string;
|
|
390
389
|
testId: string | null;
|
|
391
390
|
stableId: string | null;
|
|
391
|
+
accessibleName: string;
|
|
392
392
|
contextText: string[] | null;
|
|
393
393
|
mutability?: "unknown" | "mutable" | "immutable" | undefined;
|
|
394
394
|
widgetType?: "unknown" | "custom" | "native" | undefined;
|
|
@@ -462,9 +462,9 @@ declare const registerPlannedTestSchema: z.ZodEffects<z.ZodEffects<z.ZodObject<{
|
|
|
462
462
|
elements?: {
|
|
463
463
|
items?: {
|
|
464
464
|
role: string;
|
|
465
|
-
accessibleName: string;
|
|
466
465
|
testId: string | null;
|
|
467
466
|
stableId: string | null;
|
|
467
|
+
accessibleName: string;
|
|
468
468
|
contextText: string[] | null;
|
|
469
469
|
mutability?: "unknown" | "mutable" | "immutable" | undefined;
|
|
470
470
|
widgetType?: "unknown" | "custom" | "native" | undefined;
|
|
@@ -555,9 +555,9 @@ declare const registerPlannedTestSchema: z.ZodEffects<z.ZodEffects<z.ZodObject<{
|
|
|
555
555
|
elements?: {
|
|
556
556
|
items?: {
|
|
557
557
|
role: string;
|
|
558
|
-
accessibleName: string;
|
|
559
558
|
testId: string | null;
|
|
560
559
|
stableId: string | null;
|
|
560
|
+
accessibleName: string;
|
|
561
561
|
contextText: string[] | null;
|
|
562
562
|
mutability?: "unknown" | "mutable" | "immutable" | undefined;
|
|
563
563
|
widgetType?: "unknown" | "custom" | "native" | undefined;
|
|
@@ -665,9 +665,9 @@ declare const registerPlannedTestSchema: z.ZodEffects<z.ZodEffects<z.ZodObject<{
|
|
|
665
665
|
elements?: {
|
|
666
666
|
items?: {
|
|
667
667
|
role: string;
|
|
668
|
-
accessibleName: string;
|
|
669
668
|
testId: string | null;
|
|
670
669
|
stableId: string | null;
|
|
670
|
+
accessibleName: string;
|
|
671
671
|
contextText: string[] | null;
|
|
672
672
|
mutability?: "unknown" | "mutable" | "immutable" | undefined;
|
|
673
673
|
widgetType?: "unknown" | "custom" | "native" | undefined;
|
|
@@ -775,9 +775,9 @@ declare const registerPlannedTestSchema: z.ZodEffects<z.ZodEffects<z.ZodObject<{
|
|
|
775
775
|
elements?: {
|
|
776
776
|
items?: {
|
|
777
777
|
role: string;
|
|
778
|
-
accessibleName: string;
|
|
779
778
|
testId: string | null;
|
|
780
779
|
stableId: string | null;
|
|
780
|
+
accessibleName: string;
|
|
781
781
|
contextText: string[] | null;
|
|
782
782
|
mutability?: "unknown" | "mutable" | "immutable" | undefined;
|
|
783
783
|
widgetType?: "unknown" | "custom" | "native" | undefined;
|
|
@@ -842,8 +842,14 @@ export declare function makeSpecFileReader(state: RunState): (relativePath: stri
|
|
|
842
842
|
* `owner/repo` read off the origin remote, so the name is always matchable.
|
|
843
843
|
*/
|
|
844
844
|
export declare function makeScreenRouteResolver(state: RunState): (relativePath: string, repository: string) => string[] | undefined;
|
|
845
|
-
/**
|
|
846
|
-
|
|
845
|
+
/** The blockers this registration is judged on: the ones it sends AND the ones the
|
|
846
|
+
* stored plan carries. A carried close is re-probed on every registration — the
|
|
847
|
+
* service that was down when it was accepted may be up now. */
|
|
848
|
+
export declare function blockersToProbe(registration: PlanInput, previous?: Plan): PlanBlocker[];
|
|
849
|
+
/** Every fact the verifiers check against, gathered once per registration. The
|
|
850
|
+
* blockers are probed HERE rather than inside a verifier: an HTTP request and a
|
|
851
|
+
* `git` call are both asynchronous and every verifier is not. */
|
|
852
|
+
export declare function buildVerifyContext(state: RunState, blockers?: PlanBlocker[]): Promise<VerifyContext>;
|
|
847
853
|
/**
|
|
848
854
|
* What the agent reads back. Every objection carries its id so it can be answered,
|
|
849
855
|
* and the text says the plan is stored either way — a objections list without that
|
|
@@ -12,7 +12,10 @@ import { collectChangedRuns } from "../../utils/changedRuns.js";
|
|
|
12
12
|
import { LOCKFILE, NOT_SOURCE_DIR, } from "../../recommendation/verifiers/endpointGrounded.js";
|
|
13
13
|
import { hasUITestTypeLabel, isHTTPStep, } from "../../recommendation/subjectStep.js";
|
|
14
14
|
import { STEP_METHOD_DESCRIPTION, isUiStepVerb, normalizeStepMethod, stepMethodSchema, } from "../../types/StepMethod.js";
|
|
15
|
-
import { registerPlan } from "../../recommendation/registerPlan.js";
|
|
15
|
+
import { registerPlan, } from "../../recommendation/registerPlan.js";
|
|
16
|
+
import { BLOCKER_DESCRIPTION } from "../../recommendation/answers.js";
|
|
17
|
+
import { probeBlockers, blockersIn, } from "../../recommendation/blockerProbes.js";
|
|
18
|
+
import { blockerSchema, verifyBlocker } from "../../recommendation/blockers.js";
|
|
16
19
|
import { targetElementSchema } from "../submitReportTool.js";
|
|
17
20
|
import { capturedBlueprintDigests } from "../../playwright/blueprintDigestStore.js";
|
|
18
21
|
import { pullRequestText } from "../../recommendation/pullRequestText.js";
|
|
@@ -129,7 +132,7 @@ const plannedTestCitations = citationFields({
|
|
|
129
132
|
changesSubject: "test proves",
|
|
130
133
|
defectsSubject: "test proves",
|
|
131
134
|
changesExtra: " This test proves one behaviour: list the id of every change that the behaviour covers. The server checks that each declared change has a planned test that cites it, or an answer. The server does not check that the test exercises the change. CHECKED on a `requirement_conflict` test: at least one cited change must have been read from the pull request or a requirements file, never only from the diff.",
|
|
132
|
-
defectsExtra: " The server checks that each declared defect has a planned test that cites it and expects to fail
|
|
135
|
+
defectsExtra: " The server checks that each declared defect has a planned test that cites it and expects to fail. The server does not check that the test reaches the defect. CHECKED on a `requirement_conflict` test: it must cite at least one defect.",
|
|
133
136
|
});
|
|
134
137
|
// The fields `declarations` accepts. Named on its own so one list of them can be
|
|
135
138
|
// derived from it — a hand-written second copy had already fallen behind.
|
|
@@ -527,7 +530,7 @@ const registerTestPlanSchema = {
|
|
|
527
530
|
});
|
|
528
531
|
})
|
|
529
532
|
.default([])
|
|
530
|
-
.describe("The defects your code review found in the code that serves each change, whether or not the diff contains the line. Every defect closes one of
|
|
533
|
+
.describe("The defects your code review found in the code that serves each change, whether or not the diff contains the line. Every defect closes one of two ways: a planned test that cites it in `defects` and expects to fail, or a `maintains` entry that cites it because your edit to an existing test covers it. A service that is down does not stop the test: write it, and it runs once the service is back. A pre-existing defect in a file this pull request touches belongs on this list. An empty list says the review found none; the check asks you to confirm that in one line. A requirement conflict is a defect: list it here, and the `requirement_conflict` test cites it."),
|
|
531
534
|
// REQUIRED, not `.default([])`. The MCP SDK wraps this shape in a plain
|
|
532
535
|
// `z.object`, which STRIPS an undeclared top-level key, so with a default
|
|
533
536
|
// `planned test:` for `planned tests:` stored an EMPTY plan and counted the
|
|
@@ -565,16 +568,16 @@ const registerTestPlanSchema = {
|
|
|
565
568
|
repository: repositorySlugField("a maintenance entry", "which holds the test this run edits"),
|
|
566
569
|
file: z
|
|
567
570
|
.string()
|
|
568
|
-
.describe("Repository-relative path of the existing test this run edits. It has to resolve in the repository named above: a change is credited as covered only when it does, and `existingCoverage` objects when it does not."),
|
|
571
|
+
.describe("Repository-relative path of the existing test this run edits, or runs unchanged as a VERIFY. It has to resolve in the repository named above: a change is credited as covered only when it does, and `existingCoverage` objects when it does not."),
|
|
569
572
|
differsBy: z
|
|
570
573
|
.string()
|
|
571
|
-
.describe("What your edit changes in that test, in one sentence."),
|
|
574
|
+
.describe("What your edit changes in that test, in one sentence. For a test you run unchanged, what it asserts that the cited changes or defects reach."),
|
|
572
575
|
changes: maintainedCitations.changes,
|
|
573
576
|
defects: maintainedCitations.defects,
|
|
574
577
|
})
|
|
575
578
|
.strict())
|
|
576
579
|
.optional()
|
|
577
|
-
.describe("Existing tests this run edits instead of planning new ones. Coverage counts the changes these cite, and the defect check counts the defects they cite, so a change or a defect already covered by a spec you are updating needs no planned test — and a duplicate spec beside the one you edited is the mistake. Declare maintenance here even when the plan holds no planned tests at all: a maintenance-only plan is a complete plan."),
|
|
580
|
+
.describe("Existing tests this run edits, or runs unchanged as a VERIFY, instead of planning new ones. A test you run unchanged is checked at report time: the report objects when this server recorded no run of it after the edits. Coverage counts the changes these cite, and the defect check counts the defects they cite, so a change or a defect already covered by a spec you are updating needs no planned test — and a duplicate spec beside the one you edited is the mistake. Declare maintenance here even when the plan holds no planned tests at all: a maintenance-only plan is a complete plan."),
|
|
578
581
|
answers: z
|
|
579
582
|
.array(z
|
|
580
583
|
.object({
|
|
@@ -584,10 +587,7 @@ const registerTestPlanSchema = {
|
|
|
584
587
|
answer: z
|
|
585
588
|
.string()
|
|
586
589
|
.describe("Why the objection does not apply, or what you decided instead."),
|
|
587
|
-
blocker:
|
|
588
|
-
.string()
|
|
589
|
-
.optional()
|
|
590
|
-
.describe("What stopped this run from writing the test: a service that is not running, a paired branch that no longer exists, the one credential the run holds. An objection about a change or a defect with no test, or about a value a page and API change refuses that no UI test names, closes only with this. A reason the change is not worth testing is not a blocker."),
|
|
590
|
+
blocker: blockerSchema.optional().describe(BLOCKER_DESCRIPTION),
|
|
591
591
|
})
|
|
592
592
|
.strict())
|
|
593
593
|
.default([])
|
|
@@ -795,10 +795,25 @@ function recordedPullRequest() {
|
|
|
795
795
|
logger.warning("No pull request text was recorded before the plan was registered; the checks that read it will see an empty pull request.");
|
|
796
796
|
return { title: "", description: "" };
|
|
797
797
|
}
|
|
798
|
-
/**
|
|
799
|
-
|
|
798
|
+
/** The blockers this registration is judged on: the ones it sends AND the ones the
|
|
799
|
+
* stored plan carries. A carried close is re-probed on every registration — the
|
|
800
|
+
* service that was down when it was accepted may be up now. */
|
|
801
|
+
export function blockersToProbe(registration, previous) {
|
|
802
|
+
return [
|
|
803
|
+
...blockersIn(registration.answers),
|
|
804
|
+
...blockersIn(previous?.answeredObjections ?? []),
|
|
805
|
+
...blockersIn(previous?.unverifiedCloses ?? []),
|
|
806
|
+
];
|
|
807
|
+
}
|
|
808
|
+
/** Every fact the verifiers check against, gathered once per registration. The
|
|
809
|
+
* blockers are probed HERE rather than inside a verifier: an HTTP request and a
|
|
810
|
+
* `git` call are both asynchronous and every verifier is not. */
|
|
811
|
+
export async function buildVerifyContext(state, blockers = []) {
|
|
800
812
|
const sections = repoSections(state);
|
|
801
|
-
const listings = await Promise.all(
|
|
813
|
+
const [listings, checks] = await Promise.all([
|
|
814
|
+
Promise.all(sections.map((section) => changedFilesForVerification(section))),
|
|
815
|
+
probeBlockers(blockers),
|
|
816
|
+
]);
|
|
802
817
|
// Per repository, so a planned test is checked against the one it names. Every
|
|
803
818
|
// planned test names one; a single-repository run names that one repository.
|
|
804
819
|
return {
|
|
@@ -821,6 +836,7 @@ export async function buildVerifyContext(state) {
|
|
|
821
836
|
citedFileExists: makeCitedFileCheck(state),
|
|
822
837
|
readSpecFile: makeSpecFileReader(state),
|
|
823
838
|
routesRenderingFile: makeScreenRouteResolver(state),
|
|
839
|
+
blockerVerdict: (blocker) => verifyBlocker(blocker, checks),
|
|
824
840
|
};
|
|
825
841
|
}
|
|
826
842
|
// ── Output ──────────────────────────────────────────────────────────────────
|
|
@@ -930,7 +946,7 @@ export function registerRegisterTestPlanTool(server) {
|
|
|
930
946
|
const registration = buildRegistration(params, registrationNumber);
|
|
931
947
|
// The previous registration's answers are already on the stored plan, which
|
|
932
948
|
// the write below replaces.
|
|
933
|
-
const result = registerPlan(registration, await buildVerifyContext(fullState), fullState.plan);
|
|
949
|
+
const result = registerPlan(registration, await buildVerifyContext(fullState, blockersToProbe(registration, fullState.plan)), fullState.plan);
|
|
934
950
|
try {
|
|
935
951
|
const { metadata: _metadata, relatedRepos: _sections, ...rootData } = fullState;
|
|
936
952
|
await stateManager.appendData({
|
|
@@ -255,6 +255,13 @@ export declare const codeRefactoringSchema: z.ZodObject<{
|
|
|
255
255
|
codeReuse?: boolean | undefined;
|
|
256
256
|
modularizeCode?: boolean | undefined;
|
|
257
257
|
}>;
|
|
258
|
+
/** The `testName` field for a recording-driven generation tool.
|
|
259
|
+
*
|
|
260
|
+
* Single-sourced because the description is prompt text: it is the only thing
|
|
261
|
+
* steering the agent's choice of title, it gets re-tuned against eval results,
|
|
262
|
+
* and two copies would let the UI and E2E tools drift apart silently. Each
|
|
263
|
+
* tool passes the title its own generator falls back to (SKYR-4468). */
|
|
264
|
+
export declare const testNameField: (fallbackTitle: string) => z.ZodOptional<z.ZodString>;
|
|
258
265
|
/** Test types skyramp_enhance_assertions supports — single-sourced so the
|
|
259
266
|
* tool's schema enum and generation-time baseline seeding cannot drift
|
|
260
267
|
* (contract additionally requires provider mode at the seeding site). */
|