@skyramp/mcp 0.3.4 → 0.3.6-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/registerPlaywrightTools.js +92 -30
- package/build/playwright/traceRecordingPrompt.d.ts +6 -0
- package/build/playwright/traceRecordingPrompt.js +6 -2
- package/build/prompts/code-reuse.d.ts +1 -2
- package/build/prompts/code-reuse.js +182 -77
- package/build/prompts/modularization/integration-test-modularization.d.ts +2 -0
- package/build/prompts/modularization/integration-test-modularization.js +83 -41
- package/build/prompts/modularization/render.d.ts +18 -0
- package/build/prompts/modularization/render.js +12 -0
- package/build/prompts/modularization/ui-test-modularization.d.ts +3 -1
- package/build/prompts/modularization/ui-test-modularization.js +89 -47
- package/build/prompts/pom-aware-code-reuse.js +3 -1
- package/build/prompts/shared-helper-policy.d.ts +57 -0
- package/build/prompts/shared-helper-policy.js +135 -0
- package/build/prompts/test-recommendation/diffExecutionPlan.js +62 -56
- package/build/prompts/test-recommendation/fullRepoCatalog.js +19 -8
- package/build/prompts/test-recommendation/recommendationShared.d.ts +28 -6
- package/build/prompts/test-recommendation/recommendationShared.js +90 -16
- package/build/prompts/test-recommendation/registerRecommendTestsPrompt.js +22 -0
- package/build/prompts/test-recommendation/test-recommendation-prompt.d.ts +2 -2
- package/build/prompts/test-recommendation/test-recommendation-prompt.js +3 -3
- package/build/prompts/testbot/testbot-prompts.js +88 -33
- package/build/recommendation/budgeters/shared.js +105 -27
- package/build/recommendation/discriminators.js +13 -2
- package/build/recommendation/planRanker.d.ts +6 -6
- package/build/recommendation/planRanker.js +6 -61
- package/build/services/AnalyticsService.d.ts +7 -0
- package/build/services/AnalyticsService.js +7 -1
- package/build/services/ModularizationService.js +1 -3
- package/build/services/TestDiscoveryService.d.ts +0 -2
- package/build/services/TestDiscoveryService.js +2 -37
- package/build/services/TestGenerationService.d.ts +16 -0
- package/build/services/TestGenerationService.js +86 -10
- package/build/services/containerEnv.js +13 -12
- package/build/tools/code-refactor/codeReuseTool.js +279 -93
- package/build/tools/code-refactor/enhance-state.d.ts +49 -0
- package/build/tools/code-refactor/enhance-state.js +109 -0
- package/build/tools/code-refactor/enhanceAssertionsTool.js +34 -1
- package/build/tools/code-refactor/modularizationTool.js +9 -2
- package/build/tools/code-refactor/reuse-outcome.d.ts +23 -1
- package/build/tools/code-refactor/reuse-outcome.js +14 -4
- package/build/tools/code-refactor/reuse-state.d.ts +127 -5
- package/build/tools/code-refactor/reuse-state.js +628 -16
- package/build/tools/code-refactor/utils-verify-gates.d.ts +26 -0
- package/build/tools/code-refactor/utils-verify-gates.js +100 -0
- package/build/tools/code-refactor/verify-gates.d.ts +2 -1
- package/build/tools/code-refactor/verify-gates.js +90 -25
- package/build/tools/executeSkyrampTestTool.d.ts +19 -0
- package/build/tools/executeSkyrampTestTool.js +158 -8
- package/build/tools/generate-tests/generateBatchScenarioRestTool.js +2 -2
- package/build/tools/generate-tests/generateE2ERestTool.js +16 -0
- package/build/tools/generate-tests/generateUIRestTool.d.ts +1 -0
- package/build/tools/generate-tests/generateUIRestTool.js +22 -0
- package/build/tools/generate-tests/scenarioLint.d.ts +2 -0
- package/build/tools/generate-tests/scenarioLint.js +127 -19
- package/build/tools/generate-tests/trace-reuse-guard.d.ts +20 -0
- package/build/tools/generate-tests/trace-reuse-guard.js +93 -0
- package/build/tools/submitReportTool.d.ts +38 -38
- package/build/tools/submitReportTool.js +487 -104
- package/build/tools/test-management/analyzeChangesTool.d.ts +24 -1
- package/build/tools/test-management/analyzeChangesTool.js +75 -12
- package/build/tools/test-management/analyzeTestHealthTool.js +7 -7
- package/build/tools/test-management/registerTestPlanTool.d.ts +203 -0
- package/build/tools/test-management/registerTestPlanTool.js +149 -23
- package/build/types/Recommendation.d.ts +34 -5
- package/build/types/RepositoryAnalysis.d.ts +133 -114
- package/build/types/RepositoryAnalysis.js +1 -1
- package/build/types/ReuseOutcome.d.ts +102 -6
- package/build/types/ReuseOutcome.js +16 -2
- package/build/types/TestRecommendation.js +21 -3
- package/build/types/TestTypes.js +14 -8
- package/build/types/TestbotReport.d.ts +25 -3
- package/build/types/index.d.ts +2 -2
- package/build/types/index.js +1 -1
- package/build/utils/AnalysisStateManager.d.ts +69 -1
- package/build/utils/AnalysisStateManager.js +69 -5
- package/build/utils/branchDiff.d.ts +10 -0
- package/build/utils/branchDiff.js +28 -0
- package/build/utils/changedRoutes.d.ts +29 -0
- package/build/utils/changedRoutes.js +87 -0
- package/build/utils/featureFlags.d.ts +21 -0
- package/build/utils/featureFlags.js +23 -0
- package/build/utils/frontendIntegration.js +34 -4
- package/build/utils/importerHop.d.ts +2 -8
- package/build/utils/importerHop.js +15 -53
- package/build/utils/pathMatching.d.ts +38 -0
- package/build/utils/pathMatching.js +71 -0
- package/build/utils/pathSignatures.d.ts +22 -0
- package/build/utils/pathSignatures.js +57 -0
- package/build/utils/planMatchKeys.d.ts +16 -3
- package/build/utils/planMatchKeys.js +26 -10
- package/build/utils/pluralization.d.ts +10 -0
- package/build/utils/pluralization.js +18 -0
- package/build/utils/pom-catalog-parse.d.ts +52 -0
- package/build/utils/pom-catalog-parse.js +141 -0
- package/build/utils/pom-scope/selector-extractor.d.ts +12 -0
- package/build/utils/pom-scope/selector-extractor.js +34 -8
- package/build/utils/pom-verify/verify.d.ts +6 -5
- package/build/utils/pom-verify/verify.js +8 -6
- package/build/utils/reportLanguage.d.ts +43 -0
- package/build/utils/reportLanguage.js +125 -0
- package/build/utils/reportVerification.d.ts +74 -4
- package/build/utils/reportVerification.js +259 -3
- package/build/utils/reuseRouting.d.ts +3 -0
- package/build/utils/reuseRouting.js +50 -0
- package/build/utils/routeParsers.d.ts +2 -0
- package/build/utils/routeParsers.js +65 -8
- package/build/utils/scenarioDrafting.d.ts +1 -1
- package/build/utils/scenarioDrafting.js +57 -45
- package/build/utils/subjectEndpoints.d.ts +19 -0
- package/build/utils/subjectEndpoints.js +98 -0
- package/build/utils/testFileClassification.d.ts +11 -0
- package/build/utils/testFileClassification.js +47 -0
- package/build/utils/uiPageEnumerator.d.ts +45 -19
- package/build/utils/uiPageEnumerator.js +95 -51
- package/build/utils/utils-verify/allow.d.ts +16 -0
- package/build/utils/utils-verify/allow.js +68 -0
- package/build/utils/utils-verify/call-sites.d.ts +34 -0
- package/build/utils/utils-verify/call-sites.js +154 -0
- package/build/utils/utils-verify/index.d.ts +7 -0
- package/build/utils/utils-verify/index.js +7 -0
- package/build/utils/utils-verify/language-spec.d.ts +91 -0
- package/build/utils/utils-verify/language-spec.js +210 -0
- package/build/utils/utils-verify/locate.d.ts +39 -0
- package/build/utils/utils-verify/locate.js +199 -0
- package/build/utils/utils-verify/parse.d.ts +34 -0
- package/build/utils/utils-verify/parse.js +177 -0
- package/build/utils/utils-verify/stage.d.ts +24 -0
- package/build/utils/utils-verify/stage.js +107 -0
- package/build/utils/utils-verify/verify.d.ts +63 -0
- package/build/utils/utils-verify/verify.js +168 -0
- package/build/utils/utils.d.ts +3 -1
- package/build/utils/utils.js +3 -1
- package/build/workspace/workspace.d.ts +32 -32
- package/node_modules/playwright/lib/mcp/skyramp/assertTool.js +9 -5
- package/node_modules/playwright/lib/mcp/skyramp/loadTraceTool.js +16 -0
- package/node_modules/playwright/lib/mcp/skyramp/skyRampImport.js +2 -0
- package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +115 -14
- package/node_modules/playwright/lib/mcp/test/skyRampExport.js +13 -1
- package/node_modules/playwright/node_modules/playwright-core/.DS_Store +0 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/server/codegen/skyramp/jsonlReader.js +2 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/htmlReport/index.html +27 -253
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/assets/{codeMirrorModule-DtudTj_v.js → codeMirrorModule-DJMC4zNo.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/assets/index-BW82eAUI.js +196 -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-FNMuBzX1.js → codeMirrorModule-CZfp96qZ.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-gpLo02E0.js +809 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.Bq1r1URj.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.VEfqi1qN.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/server/codegen/skyramp/jsonlReader.ts +1 -1
- package/node_modules/playwright/package.json +1 -1
- package/package.json +2 -2
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/assets/index-BpDwp16L.js +0 -422
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-Co9upU5h.js +0 -1035
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.DXNIQ_dx.js +0 -2
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.CIKB3XSv.js +0 -5
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import * as path from "path";
|
|
2
3
|
import { logger } from "../../utils/logger.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
4
|
+
import { toolError } from "../../utils/utils.js";
|
|
5
|
+
import { getCodeReusePrompt } from "../../prompts/code-reuse.js";
|
|
6
|
+
import { isPomAwareTarget } from "../../utils/reuseRouting.js";
|
|
7
|
+
import { selectScopedPoms, } from "../../utils/pom-scope/index.js";
|
|
5
8
|
import { verifyReuse } from "../../utils/pom-verify/index.js";
|
|
6
|
-
import { infraGateFailure, zeroReuseGateFailure, composeVerifyText } from "./verify-gates.js";
|
|
7
|
-
import { recordCandidates, recordNoPomLayer, recordVerifyOutcome } from "./reuse-state.js";
|
|
8
|
-
import {
|
|
9
|
+
import { infraGateFailure, zeroReuseGateFailure, composeVerifyText, } from "./verify-gates.js";
|
|
10
|
+
import { recordCandidates, recordNoPomLayer, recordVerifyOutcome, recordUtilsReuseIssued, recordUtilsVerifyOutcome, recordUtilsVerifyError, pendingModularization, } from "./reuse-state.js";
|
|
11
|
+
import { formatUtilsVerifyText } from "./utils-verify-gates.js";
|
|
12
|
+
import { stageUtilsArtifacts, utilsSpecFor, verifyUtils, } from "../../utils/utils-verify/index.js";
|
|
13
|
+
import { codeRefactoringSchema, languageSchema, TestType, } from "../../types/TestTypes.js";
|
|
9
14
|
import { SKYRAMP_UTILS_HEADER } from "../../utils/utils.js";
|
|
10
15
|
import { AnalyticsService } from "../../services/AnalyticsService.js";
|
|
11
|
-
import { isPomReuseEnabled } from "../../utils/featureFlags.js";
|
|
16
|
+
import { isPomReuseEnabled, isUtilsReuseEnabled, } from "../../utils/featureFlags.js";
|
|
12
17
|
import { stageGeneratedPaths } from "../../utils/gitStaging.js";
|
|
18
|
+
import { currentRunStateFile } from "../../utils/AnalysisStateManager.js";
|
|
13
19
|
const codeReuseSchema = z.object({
|
|
14
20
|
testFile: z
|
|
15
21
|
.string()
|
|
@@ -24,15 +30,255 @@ const codeReuseSchema = z.object({
|
|
|
24
30
|
verify: z
|
|
25
31
|
.boolean()
|
|
26
32
|
.default(false)
|
|
27
|
-
.describe("Verify a previously
|
|
33
|
+
.describe("Verify a previously refactored test instead of returning the reuse prompt. POM path (browser tests): checks the test's POM calls against source; requires the discovery pass — this tool called for the same testFile without `verify` — to have run first. SkyrampUtils path (every other test type): stages the shared utils file for the output commit and checks its invariants (API helpers: one helper per method+path, status-code-only assertions, method+resource names; browser helpers: actions and structural waits only, intent names). Call it after the reuse edits are written; skyramp_enhance_assertions and skyramp_execute_test refuse until it has passed."),
|
|
34
|
+
testType: z
|
|
35
|
+
.nativeEnum(TestType)
|
|
36
|
+
.optional()
|
|
37
|
+
.describe("The type of the test being refactored (shared TestType vocabulary). The tool routes on it server-side: browser types (ui, e2e) may take the POM path; every other type takes the SkyrampUtils path. Omit when unknown."),
|
|
28
38
|
});
|
|
29
39
|
const TOOL_NAME = "skyramp_reuse_code";
|
|
40
|
+
/**
|
|
41
|
+
* Specs whose discovery pass has run, keyed by `<run state file>:<resolved spec path>`.
|
|
42
|
+
*
|
|
43
|
+
* Deliberately in memory rather than in the run state file. The question it answers
|
|
44
|
+
* — has discovery run for this spec, in this run? — is intra-run and intra-process
|
|
45
|
+
* (one Testbot run is one MCP server process, the same assumption the run anchor in
|
|
46
|
+
* AnalysisStateManager rests on), and nothing outside this file reads it. Persisting
|
|
47
|
+
* it would put a precondition into a shared read-modify-write blob, where the reuse
|
|
48
|
+
* fan-out's concurrent verify calls can drop records.
|
|
49
|
+
*
|
|
50
|
+
* A server restart mid-run empties this, costing one extra discovery pass for a spec
|
|
51
|
+
* already refactored. That is self-correcting: the refusal IS the discovery pass.
|
|
52
|
+
*/
|
|
53
|
+
const discoveredSpecs = new Set();
|
|
54
|
+
/** The key this spec is marked under, or undefined when there is no run to enforce
|
|
55
|
+
* the precondition in — standalone/IDE calls have no run to have recorded a pass in.
|
|
56
|
+
*
|
|
57
|
+
* The run's state file is part of the key rather than merely a gate, so one process
|
|
58
|
+
* that ever serves two runs cannot carry run 1's marks into run 2. A stale mark would
|
|
59
|
+
* stamp a spec that never received the procedure, which is the failure this guard
|
|
60
|
+
* exists to close. */
|
|
61
|
+
function markKeyFor(testFile) {
|
|
62
|
+
const stateFile = currentRunStateFile();
|
|
63
|
+
return stateFile === undefined
|
|
64
|
+
? undefined
|
|
65
|
+
: `${stateFile}:${path.resolve(testFile)}`;
|
|
66
|
+
}
|
|
67
|
+
/** Prefixed to the discovery result when verify mode is called before it.
|
|
68
|
+
*
|
|
69
|
+
* Leads with `VERIFICATION <STATE>` because every other verify-mode response does —
|
|
70
|
+
* PASSED, FAILED, SKIPPED, ERROR — and the prompts tell the agent to read that line
|
|
71
|
+
* ("finish only when it reports PASSED"). A fresh top-level keyword would be a state
|
|
72
|
+
* it was never taught. NOT RUN rather than SKIPPED: SKIPPED already means "nothing to
|
|
73
|
+
* verify, carry on", which is the opposite action to this one.
|
|
74
|
+
*
|
|
75
|
+
* Worded to fit either branch that follows — the refactoring procedure, or the "no
|
|
76
|
+
* reusable POM layer detected" abstention — and to leave the "verify when you are
|
|
77
|
+
* done" instruction to that content, which already carries it in both branches. */
|
|
78
|
+
const DISCOVER_FIRST_NOTICE = `VERIFICATION NOT RUN — this test file has not been through code-reuse discovery. That pass runs now, and its result follows below.
|
|
79
|
+
|
|
80
|
+
Discovery is per test file: it scopes page-object candidates to this file's own selectors, so discovery done for another spec does not cover this one. Verification confirms only that the page-object calls a spec ALREADY contains resolve against their source — it cannot name the calls the spec should have made. That is why verifying first reports a spec that reuses almost nothing as PASSED.
|
|
81
|
+
|
|
82
|
+
Follow the result below for this test file; it states what to do next.`;
|
|
30
83
|
// Only advertised when SKYRAMP_FEATURE_POM_REUSE=1 — with the flag off this tool
|
|
31
84
|
// never takes the POM path, so describing it would promise behavior it won't do.
|
|
32
85
|
const POM_AWARE_MODE_DESCRIPTION = `
|
|
33
86
|
|
|
34
87
|
**POM-AWARE MODE (TypeScript/JavaScript + Playwright):**
|
|
35
88
|
For projects with \`language: "typescript" or "javascript"\` and \`framework: "playwright"\`, this tool first checks whether the project has an existing Page Object Model (POM) library (\`pageobjects/\` directories or \`*.page.ts\` / \`*.page.js\` files). If POMs are found, the test is refactored to use those POM classes and methods — replacing raw inline locators with the existing abstractions. SkyrampUtils consolidation is skipped on this path. If no POMs are found, the tool falls back to the standard SkyrampUtils path. New POM creation is out of scope — existing POMs are reused only. After POM-aware code reuse, do NOT call skyramp_modularization — on this path that supersedes WORKFLOW SUMMARY step 6.`;
|
|
89
|
+
/** Whether a `verify: true` call must be turned into the discovery pass it skipped.
|
|
90
|
+
*
|
|
91
|
+
* The two modes return very different things — discovery returns the whole refactoring
|
|
92
|
+
* procedure, `verify: true` a one-line stamp — and nothing ordered them, so a spec could
|
|
93
|
+
* be stamped having never received the procedure (measured: 2 of 3 specs in one run). The
|
|
94
|
+
* rule existed as prompt prose and was skipped anyway; it is a precondition here instead.
|
|
95
|
+
*
|
|
96
|
+
* `isPomAwareTarget` also carries the feature flag and the browser-test-type check, so a
|
|
97
|
+
* call that could never have reached the POM procedure is never refused for lacking it.
|
|
98
|
+
* Enforced only inside a run — see `markKeyFor`. */
|
|
99
|
+
function mustDiscoverFirst(params, markKey) {
|
|
100
|
+
return (markKey !== undefined &&
|
|
101
|
+
isPomAwareTarget(params.language, params.framework, params.testType) &&
|
|
102
|
+
!discoveredSpecs.has(markKey));
|
|
103
|
+
}
|
|
104
|
+
/** Verify mode for the SkyrampUtils path: stage the shared utils file(s) this spec
|
|
105
|
+
* wrote or imports — located by header, in code, after the edits exist — then check
|
|
106
|
+
* their invariants and record the verdict that clears the utils debt.
|
|
107
|
+
*
|
|
108
|
+
* Staging comes FIRST and is unconditional: a utils file that fails verification is
|
|
109
|
+
* still the file the test imports, and an unstaged one ships a test that cannot run
|
|
110
|
+
* at all (SKYR-4196 Part B run 2). */
|
|
111
|
+
async function runUtilsVerify(params) {
|
|
112
|
+
if (!isUtilsReuseEnabled()) {
|
|
113
|
+
return {
|
|
114
|
+
content: [
|
|
115
|
+
{
|
|
116
|
+
type: "text",
|
|
117
|
+
text: "VERIFICATION SKIPPED — SkyrampUtils code reuse is disabled (SKYRAMP_FEATURE_UTILS_REUSE). Nothing to verify; continue.",
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
if (!utilsSpecFor(params.language)) {
|
|
123
|
+
// Say so rather than record a NoUtilsFile verdict nothing established.
|
|
124
|
+
return {
|
|
125
|
+
content: [
|
|
126
|
+
{
|
|
127
|
+
type: "text",
|
|
128
|
+
text: `VERIFICATION SKIPPED — shared-helper verification does not cover ${params.language}; nothing was staged or checked. Continue.`,
|
|
129
|
+
},
|
|
130
|
+
],
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
const staging = await stageUtilsArtifacts(params.testFile, params.language);
|
|
135
|
+
const r = await verifyUtils({
|
|
136
|
+
testFile: params.testFile,
|
|
137
|
+
language: params.language,
|
|
138
|
+
testType: params.testType,
|
|
139
|
+
});
|
|
140
|
+
// A utils file git refused to stage ships a test importing a module the output
|
|
141
|
+
// commit lacks — the exact failure this gate exists for — so it fails the pass
|
|
142
|
+
// whatever the file's invariants say.
|
|
143
|
+
if (staging.failed.length > 0)
|
|
144
|
+
r.ok = false;
|
|
145
|
+
const recorded = await recordUtilsVerifyOutcome(params.testFile, r, params.language, params.framework, params.testType);
|
|
146
|
+
return {
|
|
147
|
+
content: [
|
|
148
|
+
{
|
|
149
|
+
type: "text",
|
|
150
|
+
text: formatUtilsVerifyText(r, {
|
|
151
|
+
staged: staging.staged,
|
|
152
|
+
unstaged: staging.failed,
|
|
153
|
+
recorded,
|
|
154
|
+
}),
|
|
155
|
+
},
|
|
156
|
+
],
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
catch (err) {
|
|
160
|
+
logger.warning("SkyrampUtils verification threw — recording a verify error so the debt fails open", {
|
|
161
|
+
testFile: params.testFile,
|
|
162
|
+
error: String(err),
|
|
163
|
+
});
|
|
164
|
+
await recordUtilsVerifyError(params.testFile);
|
|
165
|
+
return toolError(`VERIFICATION ERROR — the shared-helper verifier could not run: ${String(err)}. This is not something to retry blindly; ` +
|
|
166
|
+
`it does not block skyramp_enhance_assertions or skyramp_execute_test. If the cause is in your files (unreadable utils file), fix it and verify once more.`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/** Verify mode: check the page-object calls the delivered spec already contains. */
|
|
170
|
+
async function runVerify(params) {
|
|
171
|
+
// Route on the same predicate the prompt path routes on. A target the POM path
|
|
172
|
+
// never took must not reach the POM verifier — it is the sole writer of the POM
|
|
173
|
+
// reuse summary, and a POM-less run would otherwise emit POM numbers — so every
|
|
174
|
+
// non-POM target, including browser tests with the POM flag off, takes the
|
|
175
|
+
// SkyrampUtils verifier, which is what the prompt path handed it.
|
|
176
|
+
if (!isPomAwareTarget(params.language, params.framework, params.testType)) {
|
|
177
|
+
return runUtilsVerify(params);
|
|
178
|
+
}
|
|
179
|
+
try {
|
|
180
|
+
const r = await verifyReuse(params.testFile, params.language);
|
|
181
|
+
const gate = (await infraGateFailure(params, r)) ??
|
|
182
|
+
(await zeroReuseGateFailure(params, r));
|
|
183
|
+
// Record what this pass established before returning the text report:
|
|
184
|
+
// every number in it is already in hand here, so the agent is never
|
|
185
|
+
// asked to read one back out and retype it later.
|
|
186
|
+
await recordVerifyOutcome(params.testFile, r, gate !== undefined, params.language);
|
|
187
|
+
return {
|
|
188
|
+
content: [{ type: "text", text: composeVerifyText(gate, r) }],
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
catch (err) {
|
|
192
|
+
logger.warning("POM verification threw", {
|
|
193
|
+
testFile: params.testFile,
|
|
194
|
+
error: String(err),
|
|
195
|
+
});
|
|
196
|
+
return toolError(`VERIFICATION ERROR (treat as unverified, do not retry more than once): ${String(err)}`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
/** Discovery mode: the POM-aware refactoring procedure, scoped to this one spec.
|
|
200
|
+
*
|
|
201
|
+
* Both the ordinary discovery call and the refused-verify path go through here, so the
|
|
202
|
+
* workflow a refused verify receives cannot drift from the one a first call receives. */
|
|
203
|
+
async function runDiscovery(params, markKey) {
|
|
204
|
+
logger.info("Analyzing code for reuse opportunities", {
|
|
205
|
+
testFile: params.testFile,
|
|
206
|
+
language: params.language,
|
|
207
|
+
framework: params.framework,
|
|
208
|
+
});
|
|
209
|
+
// Stage so testbot includes the generated files in its output commit.
|
|
210
|
+
await stageGeneratedPaths(params.testFile);
|
|
211
|
+
// Deterministic POM pre-scoping (TS/JS+Playwright browser tests only).
|
|
212
|
+
// Never blocks: any error or empty result falls back to the agent-driven STEP 1.
|
|
213
|
+
let scopedPoms;
|
|
214
|
+
const pomAware = isPomAwareTarget(params.language, params.framework, params.testType);
|
|
215
|
+
// The SkyrampUtils path records its debt here, where the instructions are issued:
|
|
216
|
+
// from this point the tools that follow reuse on this spec refuse until a
|
|
217
|
+
// `verify: true` pass has staged and checked the utils file (SKYR-4220).
|
|
218
|
+
// Only for languages the verifier covers: a debt the verify pass cannot pay (Java —
|
|
219
|
+
// utils reuse is disabled for it, and there is no analyzer) would clear as
|
|
220
|
+
// "no shared utils file" without anything being staged or checked.
|
|
221
|
+
// Gated by the same flag that gates the rest of SkyrampUtils reuse: with it off the
|
|
222
|
+
// testbot prompt never asks for reuse and tells the agent a no-op is normal, so a debt
|
|
223
|
+
// recorded here would block enhance/execute on today's default configuration.
|
|
224
|
+
if (!pomAware && isUtilsReuseEnabled() && utilsSpecFor(params.language)) {
|
|
225
|
+
await recordUtilsReuseIssued(params.testFile, params.language, params.framework, params.testType);
|
|
226
|
+
}
|
|
227
|
+
if (pomAware) {
|
|
228
|
+
try {
|
|
229
|
+
const t0 = Date.now();
|
|
230
|
+
const { tier1, tier2, diagnostics } = await selectScopedPoms({
|
|
231
|
+
testFile: params.testFile,
|
|
232
|
+
specContent: params.prompt,
|
|
233
|
+
});
|
|
234
|
+
if (tier1.length + tier2.length > 0) {
|
|
235
|
+
scopedPoms = { tier1, tier2 };
|
|
236
|
+
// Only report candidates found through the conventional POM globs.
|
|
237
|
+
//
|
|
238
|
+
// The selector-grep fallback also matches ordinary in-repo frontend
|
|
239
|
+
// source — app components legitimately contain the spec's test-ids by
|
|
240
|
+
// construction — so in a repo with no page-object layer at all it still
|
|
241
|
+
// yields "candidates". Reporting those would render, on a POM-less repo,
|
|
242
|
+
// "4 candidate POM files detected" next to zero reuse: each number true
|
|
243
|
+
// in isolation, but together they assert reusable page objects existed
|
|
244
|
+
// and were ignored. `zeroReuseGateFailure` already draws this exact line
|
|
245
|
+
// for the same reason; the report has to draw it too.
|
|
246
|
+
//
|
|
247
|
+
// The agent is still handed the grep-discovered files to consider —
|
|
248
|
+
// only the customer-facing count is withheld.
|
|
249
|
+
if (diagnostics.discovery === "globs") {
|
|
250
|
+
await recordCandidates(params.testFile, tier1.length, params.language);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
else if (diagnostics.selectors > 0 &&
|
|
254
|
+
diagnostics.discovery !== "skipped-too-large") {
|
|
255
|
+
// A clean scan ran (glob or selector-grep) and found zero overlap — distinct from
|
|
256
|
+
// "scoping wasn't attempted" (no selectors) or "scan was skipped" (repo too large).
|
|
257
|
+
scopedPoms = { tier1: [], tier2: [], scannedNoOverlap: true };
|
|
258
|
+
await recordNoPomLayer(params.testFile);
|
|
259
|
+
}
|
|
260
|
+
logger.info("POM pre-scoping", {
|
|
261
|
+
...diagnostics,
|
|
262
|
+
tier1: tier1.length,
|
|
263
|
+
tier2: tier2.length,
|
|
264
|
+
ms: Date.now() - t0,
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
catch (err) {
|
|
268
|
+
logger.warning("POM pre-scoping failed — falling back to agent-driven detection", { error: String(err) });
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
const codeReusePrompt = getCodeReusePrompt(params.testFile, params.language, params.framework, scopedPoms, params.testType);
|
|
272
|
+
// Marked on the last line before the return, so the mark cannot be observed until the
|
|
273
|
+
// workflow it stands for has been built. An earlier mark let a verify arriving mid-scope
|
|
274
|
+
// check a spec that had not been handed the procedure yet.
|
|
275
|
+
//
|
|
276
|
+
// Every scoping outcome reaches this line — hit, empty, or thrown — because a discovery
|
|
277
|
+
// pass that could not clear the precondition would be refused forever.
|
|
278
|
+
if (pomAware && markKey !== undefined)
|
|
279
|
+
discoveredSpecs.add(markKey);
|
|
280
|
+
return codeReusePrompt;
|
|
281
|
+
}
|
|
36
282
|
export function registerCodeReuseTool(server) {
|
|
37
283
|
server.registerTool(TOOL_NAME, {
|
|
38
284
|
description: `Analyzes code for reuse opportunities and enforces code reuse principles.
|
|
@@ -42,29 +288,28 @@ export function registerCodeReuseTool(server) {
|
|
|
42
288
|
**CRITICAL RULES - VIOLATION WILL RESULT IN ERROR:**
|
|
43
289
|
1. THIS TOOL MUST BE CALLED IF DURING THE TEST GENERATION THE CODE REUSE FLAG IS SET TO TRUE EXPLICITLY
|
|
44
290
|
2. DO NOT create new helper functions - only find and reuse existing ones
|
|
45
|
-
3. ONLY create a utils file
|
|
291
|
+
3. ONLY create a utils file when the returned steps say so: either ACTUAL helper functions (with signatures) exist in OTHER test files, or the returned steps include a seeding step that moves the CURRENT test's own helpers (created by skyramp_modularization) into the utils file
|
|
46
292
|
4. DO NOT create utils files for repetitive patterns - that's refactoring, not reuse
|
|
47
293
|
5. DO NOT extract patterns from current or other test files - only move existing functions
|
|
48
|
-
6. If other test files are identical/similar with no helper functions, DO NOTHING
|
|
294
|
+
6. If other test files are identical/similar with no helper functions, DO NOTHING with them — unless the returned steps include a seeding step, in which case the CURRENT test's own helpers are still moved
|
|
49
295
|
7. Helper functions must have explicit function definitions, not just patterns
|
|
50
|
-
8. If no existing helper functions found, test file remains unchanged
|
|
296
|
+
8. If no existing helper functions are found AND the returned steps have no seeding step, the test file remains unchanged; with a seeding step, the current test's helpers move to the utils file and are imported back
|
|
51
297
|
|
|
52
298
|
|
|
53
299
|
**WORKFLOW SUMMARY:**
|
|
54
300
|
1. Check existing utils with header ${SKYRAMP_UTILS_HEADER} → reuse if found WITHOUT ANY OTHER CHANGES
|
|
55
301
|
2. Search OTHER test files (not current test file) for local helpers with header ${SKYRAMP_UTILS_HEADER}
|
|
56
302
|
3. **ONLY IF** reusable helpers are found in OTHER test files → move to utils and update imports
|
|
57
|
-
4. **IF NO** existing utils or reusable helpers are found in test files then reuse is complete, without creating any files or changes to the test files
|
|
303
|
+
4. **IF NO** existing utils or reusable helpers are found in test files then reuse is complete, without creating any files or changes to the test files — unless the returned steps include a seeding step for the current test's own helpers
|
|
58
304
|
5. Verify no duplicate code remains and tests work as expected
|
|
59
|
-
6.
|
|
305
|
+
6. The returned steps include a MANDATORY line about skyramp_modularization for this test file — follow it (it states whether modularization follows this pass or must not be run again).
|
|
60
306
|
|
|
61
|
-
**WHEN TO SKIP (DO NOTHING):**
|
|
307
|
+
**WHEN TO SKIP (DO NOTHING) — applies to sibling-file reuse; a seeding step in the returned workflow still runs:**
|
|
62
308
|
- Other test files only have repetitive patterns, no helper functions
|
|
63
309
|
- Other test files are essentially identical to current file
|
|
64
310
|
- No actual function definitions found in other test files
|
|
65
311
|
|
|
66
|
-
|
|
67
|
-
**CRITICAL**: NON TRACE BASED TESTS ARE ALREADY MODULARIZED AND DO NOT NEED CODE REUSE.
|
|
312
|
+
Applicability is decided by the caller: when a generation result was produced with \`codeReuse: true\`, it directs you here — ALWAYS follow through (a no-op result is a normal outcome, never a failure). Multi-step tests (UI, E2E, INTEGRATION, LOAD) are the ones that benefit; simple single-request tests (smoke, contract, fuzz) do not request code reuse.
|
|
68
313
|
The tool will provide step-by-step instructions that MUST be followed exactly.${isPomReuseEnabled() ? POM_AWARE_MODE_DESCRIPTION : ""}`,
|
|
69
314
|
inputSchema: codeReuseSchema.shape,
|
|
70
315
|
_meta: {
|
|
@@ -80,98 +325,37 @@ export function registerCodeReuseTool(server) {
|
|
|
80
325
|
let errorResult;
|
|
81
326
|
try {
|
|
82
327
|
if (params.verify) {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
328
|
+
const markKey = markKeyFor(params.testFile);
|
|
329
|
+
if (mustDiscoverFirst(params, markKey)) {
|
|
330
|
+
logger.info("Verify called before the discovery pass — returning the discovery workflow", { testFile: params.testFile });
|
|
331
|
+
const workflow = await runDiscovery(params, markKey);
|
|
87
332
|
return {
|
|
88
333
|
content: [
|
|
89
334
|
{
|
|
90
335
|
type: "text",
|
|
91
|
-
text:
|
|
92
|
-
},
|
|
93
|
-
],
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
try {
|
|
97
|
-
const r = await verifyReuse(params.testFile, params.language);
|
|
98
|
-
const gate = (await infraGateFailure(params, r)) ?? (await zeroReuseGateFailure(params, r));
|
|
99
|
-
// Record what this pass established before returning the text report:
|
|
100
|
-
// every number in it is already in hand here, so the agent is never
|
|
101
|
-
// asked to read one back out and retype it later.
|
|
102
|
-
await recordVerifyOutcome(params.testFile, r, gate !== undefined, params.language);
|
|
103
|
-
return { content: [{ type: "text", text: composeVerifyText(gate, r) }] };
|
|
104
|
-
}
|
|
105
|
-
catch (err) {
|
|
106
|
-
return {
|
|
107
|
-
content: [
|
|
108
|
-
{
|
|
109
|
-
type: "text",
|
|
110
|
-
text: `VERIFICATION ERROR (treat as unverified, do not retry more than once): ${String(err)}`,
|
|
336
|
+
text: `${DISCOVER_FIRST_NOTICE}\n\n---\n\n${workflow}`,
|
|
111
337
|
},
|
|
112
338
|
],
|
|
113
339
|
};
|
|
114
340
|
}
|
|
341
|
+
return await runVerify(params);
|
|
115
342
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
let scopedPoms;
|
|
126
|
-
if (isPomAwareTarget(params.language, params.framework)) {
|
|
127
|
-
try {
|
|
128
|
-
const t0 = Date.now();
|
|
129
|
-
const { tier1, tier2, diagnostics } = await selectScopedPoms({
|
|
130
|
-
testFile: params.testFile,
|
|
131
|
-
specContent: params.prompt,
|
|
132
|
-
});
|
|
133
|
-
if (tier1.length + tier2.length > 0) {
|
|
134
|
-
scopedPoms = { tier1, tier2 };
|
|
135
|
-
// Only report candidates found through the conventional POM globs.
|
|
136
|
-
//
|
|
137
|
-
// The selector-grep fallback also matches ordinary in-repo frontend
|
|
138
|
-
// source — app components legitimately contain the spec's test-ids by
|
|
139
|
-
// construction — so in a repo with no page-object layer at all it still
|
|
140
|
-
// yields "candidates". Reporting those would render, on a POM-less repo,
|
|
141
|
-
// "4 candidate POM files detected" next to zero reuse: each number true
|
|
142
|
-
// in isolation, but together they assert reusable page objects existed
|
|
143
|
-
// and were ignored. `zeroReuseGateFailure` already draws this exact line
|
|
144
|
-
// for the same reason; the report has to draw it too.
|
|
145
|
-
//
|
|
146
|
-
// The agent is still handed the grep-discovered files to consider —
|
|
147
|
-
// only the customer-facing count is withheld.
|
|
148
|
-
if (diagnostics.discovery === "globs") {
|
|
149
|
-
await recordCandidates(params.testFile, tier1.length);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
else if (diagnostics.selectors > 0 && diagnostics.discovery !== "skipped-too-large") {
|
|
153
|
-
// A clean scan ran (glob or selector-grep) and found zero overlap — distinct from
|
|
154
|
-
// "scoping wasn't attempted" (no selectors) or "scan was skipped" (repo too large).
|
|
155
|
-
scopedPoms = { tier1: [], tier2: [], scannedNoOverlap: true };
|
|
156
|
-
await recordNoPomLayer(params.testFile);
|
|
157
|
-
}
|
|
158
|
-
logger.info("POM pre-scoping", {
|
|
159
|
-
...diagnostics,
|
|
160
|
-
tier1: tier1.length,
|
|
161
|
-
tier2: tier2.length,
|
|
162
|
-
ms: Date.now() - t0,
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
catch (err) {
|
|
166
|
-
logger.warning("POM pre-scoping failed — falling back to agent-driven detection", { error: String(err) });
|
|
343
|
+
// Modularize-first targets owe skyramp_modularization before reuse is issued —
|
|
344
|
+
// the other half of the pair the hand-off names. Only ever owed for a file
|
|
345
|
+
// generation handed off, so NL callers and POM targets are untouched.
|
|
346
|
+
if (isUtilsReuseEnabled() &&
|
|
347
|
+
!isPomAwareTarget(params.language, params.framework, params.testType)) {
|
|
348
|
+
const owed = await pendingModularization(params.testFile);
|
|
349
|
+
if (owed) {
|
|
350
|
+
errorResult = toolError(owed);
|
|
351
|
+
return errorResult;
|
|
167
352
|
}
|
|
168
353
|
}
|
|
169
|
-
const codeReusePrompt = getCodeReusePrompt(params.testFile, params.language, params.framework, scopedPoms);
|
|
170
354
|
return {
|
|
171
355
|
content: [
|
|
172
356
|
{
|
|
173
357
|
type: "text",
|
|
174
|
-
text:
|
|
358
|
+
text: await runDiscovery(params, markKeyFor(params.testFile)),
|
|
175
359
|
},
|
|
176
360
|
],
|
|
177
361
|
};
|
|
@@ -195,7 +379,9 @@ export function registerCodeReuseTool(server) {
|
|
|
195
379
|
testFile: params.testFile,
|
|
196
380
|
language: params.language,
|
|
197
381
|
framework: params.framework,
|
|
198
|
-
}).catch((err) => {
|
|
382
|
+
}).catch((err) => {
|
|
383
|
+
logger.warning("Analytics event failed", { error: String(err) });
|
|
384
|
+
});
|
|
199
385
|
}
|
|
200
386
|
});
|
|
201
387
|
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SKYR-4262 — enforcement for the assertion-enhancement step.
|
|
3
|
+
*
|
|
4
|
+
* `skyramp_enhance_assertions` is prompt-returning: it hands the agent
|
|
5
|
+
* instructions and relies on it to Read + Edit the spec. Nothing verified that
|
|
6
|
+
* the file actually changed, and one production run showed an agent calling the
|
|
7
|
+
* tool for 9 generated specs back-to-back, declaring the assertions adequate
|
|
8
|
+
* without reading a single file, and shipping specs whose named behavior
|
|
9
|
+
* (disabled-state checks) was asserted nowhere.
|
|
10
|
+
*
|
|
11
|
+
* Same enforcement shape as SKYR-4115's pendingReuseVerification: record an
|
|
12
|
+
* obligation when the instructions are handed out, check it at the next tool
|
|
13
|
+
* that touches the same spec, fail open on any state error, and make the
|
|
14
|
+
* clearing action one the agent can always perform:
|
|
15
|
+
* - edit the spec (any content change clears the debt), or
|
|
16
|
+
* - document a deliberate no-change decision with a
|
|
17
|
+
* `// assertions reviewed:` comment in the spec — which is itself an edit,
|
|
18
|
+
* so the hash check is the only gate and the marker is the documentation.
|
|
19
|
+
*/
|
|
20
|
+
/** Snapshot of a spec at the moment enhancement instructions were handed out. */
|
|
21
|
+
export interface EnhanceRecord {
|
|
22
|
+
/** Absolute path, for basename-collision detection in multi-repo runs. */
|
|
23
|
+
testFilePath?: string;
|
|
24
|
+
/** sha256 of the file content at handout time. */
|
|
25
|
+
sha256?: string;
|
|
26
|
+
/** Number of `expect(` occurrences at handout time — for the block message. */
|
|
27
|
+
expectCount?: number;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Record that enhancement instructions were handed out for `testFile`.
|
|
31
|
+
* Best-effort: a missing run state (non-Testbot use) or unreadable file
|
|
32
|
+
* degrades to "no enforcement", never to a failed tool call.
|
|
33
|
+
*/
|
|
34
|
+
export declare function recordEnhanceObligation(testFile: string, explicitStateFile?: string): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Was this spec handed assertion-enhancement instructions it never acted on?
|
|
37
|
+
*
|
|
38
|
+
* Returns an actionable message when the file is byte-identical to its state at
|
|
39
|
+
* handout time, `undefined` otherwise. The debt clears through the file itself:
|
|
40
|
+
* any edit — adding the missing assertions, or documenting a deliberate
|
|
41
|
+
* no-change decision with a `// assertions reviewed: <reason>` comment —
|
|
42
|
+
* changes the hash. Enforcing "the file changed" rather than "the assertions
|
|
43
|
+
* are good" keeps this a checkpoint, not a judge: assertion quality stays where
|
|
44
|
+
* it belongs, in the enhancement instructions and the report.
|
|
45
|
+
*
|
|
46
|
+
* Fails open on every error path: an unreadable state file or spec is not
|
|
47
|
+
* evidence the agent skipped anything.
|
|
48
|
+
*/
|
|
49
|
+
export declare function pendingAssertionEnhancement(testFile: string, explicitStateFile?: string): Promise<string | undefined>;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import * as path from "path";
|
|
2
|
+
import { createHash } from "crypto";
|
|
3
|
+
import { readFile } from "fs/promises";
|
|
4
|
+
import { logger } from "../../utils/logger.js";
|
|
5
|
+
import { resolveRunStatePath, StateManager, } from "../../utils/AnalysisStateManager.js";
|
|
6
|
+
const REVIEW_MARKER = "// assertions reviewed:";
|
|
7
|
+
async function fileSnapshot(testFile) {
|
|
8
|
+
try {
|
|
9
|
+
const content = await readFile(testFile, "utf8");
|
|
10
|
+
return {
|
|
11
|
+
content,
|
|
12
|
+
sha256: createHash("sha256").update(content).digest("hex"),
|
|
13
|
+
expectCount: (content.match(/\bexpect\s*\(/g) ?? []).length,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Record that enhancement instructions were handed out for `testFile`.
|
|
22
|
+
* Best-effort: a missing run state (non-Testbot use) or unreadable file
|
|
23
|
+
* degrades to "no enforcement", never to a failed tool call.
|
|
24
|
+
*/
|
|
25
|
+
export async function recordEnhanceObligation(testFile, explicitStateFile) {
|
|
26
|
+
const stateFile = resolveRunStatePath(explicitStateFile);
|
|
27
|
+
if (!stateFile)
|
|
28
|
+
return;
|
|
29
|
+
const snapshot = await fileSnapshot(testFile);
|
|
30
|
+
if (!snapshot)
|
|
31
|
+
return;
|
|
32
|
+
try {
|
|
33
|
+
const manager = StateManager.fromStatePath(stateFile);
|
|
34
|
+
const data = await manager.readData();
|
|
35
|
+
if (!data)
|
|
36
|
+
return;
|
|
37
|
+
const key = path.basename(testFile);
|
|
38
|
+
await manager.writeData({
|
|
39
|
+
...data,
|
|
40
|
+
enhanceOutcomes: {
|
|
41
|
+
...data.enhanceOutcomes,
|
|
42
|
+
[key]: {
|
|
43
|
+
testFilePath: testFile,
|
|
44
|
+
sha256: snapshot.sha256,
|
|
45
|
+
expectCount: snapshot.expectCount,
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
logger.warning("Could not record the assertion-enhancement obligation — proceeding", {
|
|
52
|
+
testFile,
|
|
53
|
+
error: String(err),
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Was this spec handed assertion-enhancement instructions it never acted on?
|
|
59
|
+
*
|
|
60
|
+
* Returns an actionable message when the file is byte-identical to its state at
|
|
61
|
+
* handout time, `undefined` otherwise. The debt clears through the file itself:
|
|
62
|
+
* any edit — adding the missing assertions, or documenting a deliberate
|
|
63
|
+
* no-change decision with a `// assertions reviewed: <reason>` comment —
|
|
64
|
+
* changes the hash. Enforcing "the file changed" rather than "the assertions
|
|
65
|
+
* are good" keeps this a checkpoint, not a judge: assertion quality stays where
|
|
66
|
+
* it belongs, in the enhancement instructions and the report.
|
|
67
|
+
*
|
|
68
|
+
* Fails open on every error path: an unreadable state file or spec is not
|
|
69
|
+
* evidence the agent skipped anything.
|
|
70
|
+
*/
|
|
71
|
+
export async function pendingAssertionEnhancement(testFile, explicitStateFile) {
|
|
72
|
+
try {
|
|
73
|
+
const stateFile = resolveRunStatePath(explicitStateFile);
|
|
74
|
+
if (!stateFile)
|
|
75
|
+
return undefined;
|
|
76
|
+
const data = await StateManager.fromStatePath(stateFile).readData();
|
|
77
|
+
const record = data?.enhanceOutcomes?.[path.basename(testFile)];
|
|
78
|
+
if (!record?.sha256)
|
|
79
|
+
return undefined;
|
|
80
|
+
// enhanceOutcomes is keyed by basename across the whole run; in a multi-repo
|
|
81
|
+
// run two specs can share one record. Blocking a tool on another spec's debt
|
|
82
|
+
// is worse than missing this one, so a mismatch fails open.
|
|
83
|
+
if (record.testFilePath && record.testFilePath !== testFile)
|
|
84
|
+
return undefined;
|
|
85
|
+
const snapshot = await fileSnapshot(testFile);
|
|
86
|
+
if (!snapshot)
|
|
87
|
+
return undefined;
|
|
88
|
+
if (snapshot.sha256 !== record.sha256)
|
|
89
|
+
return undefined;
|
|
90
|
+
if (snapshot.content.includes(REVIEW_MARKER))
|
|
91
|
+
return undefined;
|
|
92
|
+
const spec = path.basename(testFile);
|
|
93
|
+
const countNote = record.expectCount !== undefined
|
|
94
|
+
? ` It still has the same ${record.expectCount} expect() call${record.expectCount === 1 ? "" : "s"} it was generated with.`
|
|
95
|
+
: "";
|
|
96
|
+
return (`skyramp_enhance_assertions handed out instructions for ${spec} and the file has not changed since.${countNote} ` +
|
|
97
|
+
`Read the file and apply the enhancement instructions — in particular, add expect() assertions for the specific ` +
|
|
98
|
+
`behavior this test is named for (state checks like toBeDisabled/toBeEnabled/toHaveCount, not just replayed text ` +
|
|
99
|
+
`assertions). If after actually reading the file you determine no change is needed, document that in the spec with ` +
|
|
100
|
+
`a \`${REVIEW_MARKER} <what you checked and why it is sufficient>\` comment near the top of the test.`);
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
logger.warning("Could not check for a pending assertion enhancement — proceeding", {
|
|
104
|
+
testFile,
|
|
105
|
+
error: String(err),
|
|
106
|
+
});
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
}
|