@skyramp/mcp 0.4.2 → 0.4.3-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/execution/wrapperConfig.d.ts +4 -0
- package/build/execution/wrapperConfig.js +14 -2
- package/build/prompts/code-reuse.d.ts +5 -1
- package/build/prompts/code-reuse.js +16 -10
- package/build/prompts/modularization/ui-test-modularization.js +9 -2
- package/build/prompts/pom-aware-code-reuse.d.ts +4 -1
- package/build/prompts/pom-aware-code-reuse.js +38 -6
- package/build/prompts/reuse-hand-off.d.ts +4 -0
- package/build/prompts/reuse-hand-off.js +10 -5
- package/build/prompts/shared-helper-policy.d.ts +5 -0
- package/build/prompts/shared-helper-policy.js +5 -0
- package/build/prompts/test-maintenance/driftAnalysisShared.js +2 -0
- package/build/prompts/test-maintenance/uiDriftAnalysisSections.js +2 -1
- package/build/prompts/testbot/testbot-prompts.js +33 -16
- package/build/recommendation/answers.d.ts +5 -3
- package/build/recommendation/answers.js +5 -2
- package/build/recommendation/fakeVerifyContext.d.ts +3 -0
- package/build/recommendation/fakeVerifyContext.js +16 -0
- package/build/recommendation/runVerifiers.js +2 -0
- package/build/recommendation/types.d.ts +12 -0
- package/build/recommendation/verifierContracts.d.ts +32 -2
- package/build/recommendation/verifierContracts.js +45 -4
- package/build/recommendation/verifiers/changedLinesClaimed.d.ts +17 -0
- package/build/recommendation/verifiers/changedLinesClaimed.js +88 -0
- package/build/recommendation/verifiers/coverage.js +143 -4
- package/build/recommendation/verifiers/expectedValueSourced.js +8 -0
- package/build/recommendation/verifiers/requirementSourced.js +141 -11
- package/build/services/TestGenerationService.js +4 -0
- package/build/tools/code-refactor/caller-gate.d.ts +105 -0
- package/build/tools/code-refactor/caller-gate.js +174 -0
- package/build/tools/code-refactor/changed-helper-report.d.ts +62 -0
- package/build/tools/code-refactor/changed-helper-report.js +114 -0
- package/build/tools/code-refactor/codeReuseTool.js +131 -62
- package/build/tools/code-refactor/enhanceAssertionsTool.js +2 -2
- package/build/tools/code-refactor/helper-callers.d.ts +65 -0
- package/build/tools/code-refactor/helper-callers.js +85 -0
- package/build/tools/code-refactor/modularizationTool.js +10 -0
- package/build/tools/code-refactor/pom-pass-state.d.ts +51 -0
- package/build/tools/code-refactor/pom-pass-state.js +115 -0
- package/build/tools/code-refactor/removed-modules.d.ts +33 -0
- package/build/tools/code-refactor/removed-modules.js +61 -0
- package/build/tools/code-refactor/retrofit-state.d.ts +3 -2
- package/build/tools/code-refactor/retrofit-state.js +11 -12
- package/build/tools/code-refactor/reuse-outcome.d.ts +33 -1
- package/build/tools/code-refactor/reuse-record-store.d.ts +57 -0
- package/build/tools/code-refactor/reuse-record-store.js +140 -0
- package/build/tools/code-refactor/reuse-state.d.ts +34 -9
- package/build/tools/code-refactor/reuse-state.js +140 -135
- package/build/tools/code-refactor/utils-verify-gates.js +42 -18
- package/build/tools/code-refactor/verify-gates.d.ts +4 -0
- package/build/tools/code-refactor/verify-gates.js +8 -1
- package/build/tools/generate-tests/generateContractRestTool.js +7 -7
- package/build/tools/generate-tests/generateIntegrationRestTool.js +8 -8
- package/build/tools/generate-tests/generateUIRestTool.js +3 -3
- package/build/tools/submitReportTool.js +121 -30
- package/build/tools/test-management/actionsTool.js +7 -0
- package/build/tools/test-management/registerTestPlanTool.d.ts +1 -0
- package/build/tools/test-management/registerTestPlanTool.js +71 -23
- package/build/types/RepositoryAnalysis.d.ts +20 -20
- package/build/types/ReuseOutcome.d.ts +123 -0
- package/build/types/TestExecution.d.ts +11 -0
- package/build/types/TestExecution.js +19 -0
- package/build/types/index.d.ts +1 -1
- package/build/utils/branchDiff.d.ts +1 -1
- package/build/utils/branchDiff.js +1 -1
- package/build/utils/changedRuns.d.ts +13 -0
- package/build/utils/changedRuns.js +56 -0
- package/build/utils/featureFlags.d.ts +8 -10
- package/build/utils/featureFlags.js +23 -21
- package/build/utils/normalizeSkyrampImports.d.ts +4 -1
- package/build/utils/normalizeSkyrampImports.js +24 -17
- package/build/utils/pom-scope/ownership.d.ts +19 -0
- package/build/utils/pom-scope/ownership.js +31 -0
- package/build/utils/pom-scope/pom-files.d.ts +8 -0
- package/build/utils/pom-scope/pom-files.js +8 -0
- package/build/utils/pom-scope/scoring.js +13 -1
- package/build/utils/pom-scope/strip.d.ts +10 -0
- package/build/utils/pom-scope/strip.js +9 -0
- package/build/utils/pom-verify/verify.js +13 -2
- package/build/utils/reuseRouting.d.ts +30 -2
- package/build/utils/reuseRouting.js +36 -15
- package/build/utils/testDependencyPolicy.js +4 -16
- package/build/utils/utils-verify/action-sites.d.ts +2 -2
- package/build/utils/utils-verify/allow.d.ts +1 -1
- package/build/utils/utils-verify/allow.js +15 -0
- package/build/utils/utils-verify/body-reach.d.ts +2 -2
- package/build/utils/utils-verify/body-reach.js +4 -1
- package/build/utils/utils-verify/call-sites.d.ts +16 -6
- package/build/utils/utils-verify/call-sites.js +12 -6
- package/build/utils/utils-verify/changed-helpers.d.ts +38 -0
- package/build/utils/utils-verify/changed-helpers.js +91 -0
- package/build/utils/utils-verify/changed-lines.d.ts +48 -0
- package/build/utils/utils-verify/changed-lines.js +106 -0
- package/build/utils/utils-verify/head.d.ts +73 -0
- package/build/utils/utils-verify/head.js +156 -0
- package/build/utils/utils-verify/importers.d.ts +59 -10
- package/build/utils/utils-verify/importers.js +121 -31
- package/build/utils/utils-verify/in-house.js +26 -31
- package/build/utils/utils-verify/index.d.ts +3 -0
- package/build/utils/utils-verify/index.js +3 -0
- package/build/utils/utils-verify/language-spec.js +10 -2
- package/build/utils/utils-verify/locate.d.ts +3 -0
- package/build/utils/utils-verify/locate.js +6 -6
- package/build/utils/utils-verify/parse.d.ts +13 -1
- package/build/utils/utils-verify/parse.js +122 -49
- package/build/utils/utils-verify/retrofit-equivalence.d.ts +0 -5
- package/build/utils/utils-verify/retrofit-equivalence.js +2 -34
- package/build/utils/utils-verify/stage.js +4 -16
- package/build/utils/utils-verify/typecheck.d.ts +25 -4
- package/build/utils/utils-verify/typecheck.js +95 -13
- package/build/utils/utils-verify/verify.d.ts +33 -8
- package/build/utils/utils-verify/verify.js +180 -18
- package/build/utils/versions.d.ts +3 -3
- package/build/utils/versions.js +1 -1
- package/node_modules/playwright/lib/dom-analyzer/blueprint.js +1 -0
- package/node_modules/playwright/lib/dom-analyzer/dynamicId.js +1 -0
- package/node_modules/playwright/lib/dom-analyzer/dynamicId.test.js +6 -0
- package/node_modules/playwright/lib/mcp/skyramp/exportTool.js +4 -2
- package/node_modules/playwright/lib/mcp/skyramp/snapshotLocators.js +246 -0
- package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +378 -237
- package/node_modules/playwright/lib/mcp/test/skyRampExport.js +21 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/generated/pollingRecorderSource.js +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{codeMirrorModule-LNgEKtdV.js → codeMirrorModule-Bou8kKzE.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{defaultSettingsView-Bwr1eMKC.js → defaultSettingsView-DTp2-8Si.js} +3 -3
- 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/{index.BAkLd5DX.js → index.uiPTFncY.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{uiMode.BPopbasy.js → uiMode.GLqsx5cI.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.html +2 -2
- package/node_modules/playwright/node_modules/playwright-core/package.json +1 -1
- package/node_modules/playwright/node_modules/playwright-core/src/generated/pollingRecorderSource.ts +1 -1
- package/node_modules/playwright/package.json +1 -1
- package/package.json +3 -3
- package/plugin/prompts/code-reuse/hand-off.md +13 -1
- package/plugin/prompts/plan-tests.md +2 -2
- package/plugin/skills/enhance-assertions/reference/shared-rules.md +1 -0
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
import { type ReuseHandOff, type ReuseHandOffSource } from "../../utils/AnalysisStateManager.js";
|
|
2
2
|
import { type VerifyResult } from "../../utils/pom-verify/index.js";
|
|
3
|
-
import { type InHouseDeclaration, type ReuseOutcome, type ReuseRecord } from "./reuse-outcome.js";
|
|
3
|
+
import { type InHouseDeclaration, type LocatedModule, type ReuseOutcome, type ReuseRecord } from "./reuse-outcome.js";
|
|
4
4
|
import { type UtilsVerifyResult, type UtilsViolationKind } from "../../utils/utils-verify/index.js";
|
|
5
|
-
import type { ReuseVerificationFailure } from "../../types/ReuseOutcome.js";
|
|
5
|
+
import type { HelperReuseOutcome, ReuseVerificationFailure } from "../../types/ReuseOutcome.js";
|
|
6
|
+
/** The canonical path comparison the records use — declared in the store, re-exported
|
|
7
|
+
* here for the readers that always imported it from this module. */
|
|
8
|
+
export { samePath, canonPath, canonPathEvenIfGone, } from "./reuse-record-store.js";
|
|
9
|
+
/**
|
|
10
|
+
* Persistence for the server-derived POM-reuse outcome, keyed by test-file
|
|
11
|
+
* basename (what `newTestsCreated[].fileName` carries, so
|
|
12
|
+
* `skyramp_submit_report` can match without path normalization).
|
|
13
|
+
*
|
|
14
|
+
* Every write is best-effort. Reuse observability must never be able to fail a
|
|
15
|
+
* refactor that otherwise succeeded, so a missing/unreadable/unwritable state
|
|
16
|
+
* file degrades to "no reuse summary in the report" — the same outcome as running
|
|
17
|
+
* outside a Testbot run, where there is no run state to record into.
|
|
18
|
+
*/
|
|
6
19
|
/**
|
|
7
20
|
* Record the tier-1 candidate count STEP 1 detected, plus the identity needed to
|
|
8
21
|
* re-derive the counts from the delivered spec later.
|
|
@@ -80,11 +93,6 @@ export declare function recordUtilsReuseIssued(testFile: string, language: strin
|
|
|
80
93
|
/** A verify pass that threw. Recorded so the debt FAILS OPEN — a verifier that cannot
|
|
81
94
|
* run must not leave a debt nothing can pay — and cleared by the next successful pass. */
|
|
82
95
|
export declare function recordUtilsVerifyError(testFile: string, explicitStateFile?: string): Promise<void>;
|
|
83
|
-
/** Canonical comparison: a record written through a symlinked path (macOS /var →
|
|
84
|
-
* /private/var) must still be recognised as this spec's when checked by realpath. */
|
|
85
|
-
export declare function samePath(a: string, b: string): boolean;
|
|
86
|
-
/** Exported for retrofit-state: the same canonicalisation the records use. */
|
|
87
|
-
export declare function canonPath(p: string): string;
|
|
88
96
|
/**
|
|
89
97
|
* The label a report row shows for a set of utils files: each file's path relative
|
|
90
98
|
* to its repository root, posix-separated, joined with `, `.
|
|
@@ -97,8 +105,21 @@ export declare function canonPath(p: string): string;
|
|
|
97
105
|
* repository, or for a file the root does not contain, the basename stands in.
|
|
98
106
|
*/
|
|
99
107
|
export declare function utilsFileLabel(files: string[]): string;
|
|
100
|
-
/**
|
|
101
|
-
*
|
|
108
|
+
/** The record's module list after one verify pass: every module the pass located
|
|
109
|
+
* gets a fresh entry — whether HEAD lacked it, and the pass's failures when it
|
|
110
|
+
* failed — and every module the pass did not locate keeps its entry. A pass that
|
|
111
|
+
* locates nothing is the pass after the run DELETED the module, and the record
|
|
112
|
+
* is the only place the module's existence survives. */
|
|
113
|
+
export declare function locatedModules(prior: LocatedModule[] | undefined, r: UtilsVerifyResult): LocatedModule[];
|
|
114
|
+
/** The record's module list after a verify pass that THREW: every module keeps
|
|
115
|
+
* its entry and is stamped with the failed pass, so a module removed after it
|
|
116
|
+
* never reads as removed after a pass that passed. */
|
|
117
|
+
export declare function modulesAfterVerifyError(prior: LocatedModule[] | undefined): LocatedModule[];
|
|
118
|
+
/** Verdict of one utils verify pass. The verdict is stored — counts and the file
|
|
119
|
+
* list are re-derived from the delivered files at report time — and so are the
|
|
120
|
+
* modules the pass located and the failures of a failed pass: a module the run
|
|
121
|
+
* removes afterwards can be re-derived from nothing, and its row must still say
|
|
122
|
+
* it existed and why it went. */
|
|
102
123
|
export declare function recordUtilsVerifyOutcome(testFile: string, r: UtilsVerifyResult, language: string, framework: string, testType: string | undefined, explicitStateFile?: string,
|
|
103
124
|
/** The agent's declarations from this verify call, when it made any. */
|
|
104
125
|
inHouseDeclared?: InHouseDeclaration): Promise<boolean>;
|
|
@@ -233,6 +254,10 @@ export interface ReuseRederivation {
|
|
|
233
254
|
outcome: ReuseOutcome | undefined;
|
|
234
255
|
blocking?: ReuseBlockingVerdict;
|
|
235
256
|
}
|
|
257
|
+
/** The orphan fields of a report row: the helpers nothing imports, or the statement
|
|
258
|
+
* that the walk could not tell. Exactly one of the two, and neither when the walk ran
|
|
259
|
+
* and found none. Exported for its tests — the derivation is the whole behaviour. */
|
|
260
|
+
export declare function orphanFields(r: UtilsVerifyResult): Pick<HelperReuseOutcome, "orphanedHelpers" | "orphanScanIncomplete">;
|
|
236
261
|
/**
|
|
237
262
|
* The reason behind a failed shared-helper verdict, one entry per blocking kind in
|
|
238
263
|
* the order the verifier found them. The verify pass had these in hand and dropped
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
+
import { existsSync } from "fs";
|
|
1
2
|
import * as fsp from "fs/promises";
|
|
2
3
|
import * as path from "path";
|
|
3
|
-
import { readFile, stat
|
|
4
|
-
import { realpathSync } from "fs";
|
|
4
|
+
import { readFile, stat } from "fs/promises";
|
|
5
5
|
import { execFile } from "child_process";
|
|
6
6
|
import { promisify } from "util";
|
|
7
7
|
import { logger } from "../../utils/logger.js";
|
|
8
|
-
import { resolveRunStatePath,
|
|
8
|
+
import { resolveRunStatePath, } from "../../utils/AnalysisStateManager.js";
|
|
9
|
+
import { isAbsentFromHead } from "../../utils/utils-verify/head.js";
|
|
9
10
|
import { verifyReuse, } from "../../utils/pom-verify/index.js";
|
|
10
11
|
import { buildSkipped, findUnparsableKeptInline, flaggedFrom, mergeFlagged, verificationFrom, ReuseVerificationOutcome, HelperVerificationOutcome, } from "./reuse-outcome.js";
|
|
11
12
|
import { verifyUtils, realpath, utilsSpecFor, countAssertions, hasPageErrorGuard, utilsSpecForFile, } from "../../utils/utils-verify/index.js";
|
|
@@ -18,6 +19,11 @@ const execFileAsync = promisify(execFile);
|
|
|
18
19
|
import { resolvePomCatalogPath } from "../../utils/pom-catalog.js";
|
|
19
20
|
import { parsePomCatalog, findMissedReuse, } from "../../utils/pom-catalog-parse.js";
|
|
20
21
|
import { ProgrammingLanguage } from "../../types/TestTypes.js";
|
|
22
|
+
import { canonPath, canonPathEvenIfGone, freshRecord, owns, readHandOff, readReuseRecord, readRunState, runStateWritable, samePath, updateReuseOutcome, } from "./reuse-record-store.js";
|
|
23
|
+
import { combineRederivations, pomFirstHandOff } from "./pom-pass-state.js";
|
|
24
|
+
/** The canonical path comparison the records use — declared in the store, re-exported
|
|
25
|
+
* here for the readers that always imported it from this module. */
|
|
26
|
+
export { samePath, canonPath, canonPathEvenIfGone, } from "./reuse-record-store.js";
|
|
21
27
|
/**
|
|
22
28
|
* Persistence for the server-derived POM-reuse outcome, keyed by test-file
|
|
23
29
|
* basename (what `newTestsCreated[].fileName` carries, so
|
|
@@ -28,52 +34,6 @@ import { ProgrammingLanguage } from "../../types/TestTypes.js";
|
|
|
28
34
|
* file degrades to "no reuse summary in the report" — the same outcome as running
|
|
29
35
|
* outside a Testbot run, where there is no run state to record into.
|
|
30
36
|
*/
|
|
31
|
-
/**
|
|
32
|
-
* Read-modify-write one spec's outcome. `patch` receives the entry recorded so
|
|
33
|
-
* far, so a caller that needs prior state (the verify pass, which accumulates
|
|
34
|
-
* flagged members) uses the same path as one that does not.
|
|
35
|
-
*
|
|
36
|
-
* Fields the patch omits are preserved: the prompt-generation call knows the
|
|
37
|
-
* candidate count, each verify pass knows the counts and verdict.
|
|
38
|
-
*/
|
|
39
|
-
async function updateReuseOutcome(testFile, patch, explicitStateFile) {
|
|
40
|
-
const stateFile = resolveRunStatePath(explicitStateFile);
|
|
41
|
-
if (!stateFile)
|
|
42
|
-
return false;
|
|
43
|
-
try {
|
|
44
|
-
const manager = StateManager.fromStatePath(stateFile);
|
|
45
|
-
const data = await manager.readData();
|
|
46
|
-
if (!data)
|
|
47
|
-
return false;
|
|
48
|
-
const key = path.basename(testFile);
|
|
49
|
-
const existing = data.reuseOutcomes?.[key] ?? {};
|
|
50
|
-
await manager.appendData({
|
|
51
|
-
...data,
|
|
52
|
-
reuseOutcomes: {
|
|
53
|
-
...data.reuseOutcomes,
|
|
54
|
-
[key]: { ...existing, ...patch(existing) },
|
|
55
|
-
},
|
|
56
|
-
});
|
|
57
|
-
return true;
|
|
58
|
-
}
|
|
59
|
-
catch (err) {
|
|
60
|
-
logger.warning("Could not record POM-reuse outcome — report will omit the reuse summary", {
|
|
61
|
-
testFile,
|
|
62
|
-
error: String(err),
|
|
63
|
-
});
|
|
64
|
-
return false;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
/** Read one spec's record, or `undefined` when there is no run state, no record, or the
|
|
68
|
-
* state file cannot be read. Shares `updateReuseOutcome`'s resolve-read-basename plumbing
|
|
69
|
-
* so the two cannot disagree about which key a spec maps to. */
|
|
70
|
-
async function readReuseRecord(testFile, explicitStateFile) {
|
|
71
|
-
const stateFile = resolveRunStatePath(explicitStateFile);
|
|
72
|
-
if (!stateFile)
|
|
73
|
-
return undefined;
|
|
74
|
-
const data = await StateManager.fromStatePath(stateFile).readData();
|
|
75
|
-
return data?.reuseOutcomes?.[path.basename(testFile)];
|
|
76
|
-
}
|
|
77
37
|
/**
|
|
78
38
|
* Record the tier-1 candidate count STEP 1 detected, plus the identity needed to
|
|
79
39
|
* re-derive the counts from the delivered spec later.
|
|
@@ -221,38 +181,21 @@ export async function recordUtilsReuseIssued(testFile, language, framework, test
|
|
|
221
181
|
},
|
|
222
182
|
}), explicitStateFile);
|
|
223
183
|
}
|
|
224
|
-
/** Every field cleared — the patch spread onto a record owned by another spec. */
|
|
225
|
-
function freshRecord() {
|
|
226
|
-
return {
|
|
227
|
-
candidatesDetected: undefined,
|
|
228
|
-
verification: undefined,
|
|
229
|
-
flagged: undefined,
|
|
230
|
-
language: undefined,
|
|
231
|
-
utils: undefined,
|
|
232
|
-
modularized: undefined,
|
|
233
|
-
};
|
|
234
|
-
}
|
|
235
184
|
/** A verify pass that threw. Recorded so the debt FAILS OPEN — a verifier that cannot
|
|
236
185
|
* run must not leave a debt nothing can pay — and cleared by the next successful pass. */
|
|
237
186
|
export async function recordUtilsVerifyError(testFile, explicitStateFile) {
|
|
238
187
|
await updateReuseOutcome(testFile, (existing) => owns(existing, testFile) && existing.utils
|
|
239
|
-
? {
|
|
188
|
+
? {
|
|
189
|
+
utils: {
|
|
190
|
+
...existing.utils,
|
|
191
|
+
verifyError: true,
|
|
192
|
+
...(existing.utils.modules
|
|
193
|
+
? { modules: modulesAfterVerifyError(existing.utils.modules) }
|
|
194
|
+
: {}),
|
|
195
|
+
},
|
|
196
|
+
}
|
|
240
197
|
: {}, explicitStateFile);
|
|
241
198
|
}
|
|
242
|
-
/** Whether `record` describes `testFile` (or nobody yet). Basename-keyed run state
|
|
243
|
-
* means two specs can share one record; only the owner's history carries over. */
|
|
244
|
-
function owns(record, testFile) {
|
|
245
|
-
return !record.testFilePath || samePath(record.testFilePath, testFile);
|
|
246
|
-
}
|
|
247
|
-
/** Canonical comparison: a record written through a symlinked path (macOS /var →
|
|
248
|
-
* /private/var) must still be recognised as this spec's when checked by realpath. */
|
|
249
|
-
export function samePath(a, b) {
|
|
250
|
-
return canon(a) === canon(b);
|
|
251
|
-
}
|
|
252
|
-
/** Exported for retrofit-state: the same canonicalisation the records use. */
|
|
253
|
-
export function canonPath(p) {
|
|
254
|
-
return canon(p);
|
|
255
|
-
}
|
|
256
199
|
/**
|
|
257
200
|
* The label a report row shows for a set of utils files: each file's path relative
|
|
258
201
|
* to its repository root, posix-separated, joined with `, `.
|
|
@@ -268,12 +211,17 @@ export function utilsFileLabel(files) {
|
|
|
268
211
|
return files.map(repoRelativeLabel).join(", ");
|
|
269
212
|
}
|
|
270
213
|
function repoRelativeLabel(file) {
|
|
271
|
-
|
|
214
|
+
// Canonical even when GONE: a module the run REMOVED has no file to stat, and
|
|
215
|
+
// its label must still name where in the repository it was.
|
|
216
|
+
const canonFile = canonPathEvenIfGone(file);
|
|
217
|
+
let probe = path.dirname(canonFile);
|
|
218
|
+
while (!existsSync(probe) && path.dirname(probe) !== probe)
|
|
219
|
+
probe = path.dirname(probe);
|
|
220
|
+
const root = detectGitRoot(probe);
|
|
272
221
|
if (!root)
|
|
273
222
|
return path.basename(file);
|
|
274
|
-
// Both sides canonical: git reports the REAL root
|
|
275
|
-
|
|
276
|
-
const rel = path.relative(canon(root), canon(file));
|
|
223
|
+
// Both sides canonical: git reports the REAL root.
|
|
224
|
+
const rel = path.relative(canonPath(root), canonFile);
|
|
277
225
|
// Outside the root means a leading `..` SEGMENT — not any name starting with two
|
|
278
226
|
// dots (`..generated/SkyrampUtils.py` is a valid in-repository path).
|
|
279
227
|
const outside = rel === ".." || rel.startsWith(`..${path.sep}`);
|
|
@@ -281,16 +229,50 @@ function repoRelativeLabel(file) {
|
|
|
281
229
|
return path.basename(file);
|
|
282
230
|
return rel.split(path.sep).join("/");
|
|
283
231
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
232
|
+
/** The record's module list after one verify pass: every module the pass located
|
|
233
|
+
* gets a fresh entry — whether HEAD lacked it, and the pass's failures when it
|
|
234
|
+
* failed — and every module the pass did not locate keeps its entry. A pass that
|
|
235
|
+
* locates nothing is the pass after the run DELETED the module, and the record
|
|
236
|
+
* is the only place the module's existence survives. */
|
|
237
|
+
export function locatedModules(prior, r) {
|
|
238
|
+
const incumbents = new Set(r.incumbentModules);
|
|
239
|
+
const unknown = new Set(r.headUnknownModules);
|
|
240
|
+
const modules = new Map((prior ?? []).map((m) => [m.file, m]));
|
|
241
|
+
const failures = r.ok ? [] : verificationFailures(r);
|
|
242
|
+
const lastPass = {
|
|
243
|
+
at: new Date().toISOString(),
|
|
244
|
+
failed: !r.ok,
|
|
245
|
+
...(failures.length > 0 ? { failures } : {}),
|
|
246
|
+
};
|
|
247
|
+
for (const file of r.utilsFiles) {
|
|
248
|
+
// Git gave no answer this pass: an earlier pass's answer stands. A transient
|
|
249
|
+
// failure on the last pass before a deletion must not erase the claim.
|
|
250
|
+
const createdByThisRun = unknown.has(file)
|
|
251
|
+
? modules.get(file)?.createdByThisRun
|
|
252
|
+
: !incumbents.has(file);
|
|
253
|
+
modules.set(file, {
|
|
254
|
+
file,
|
|
255
|
+
...(createdByThisRun === undefined ? {} : { createdByThisRun }),
|
|
256
|
+
lastPass,
|
|
257
|
+
});
|
|
290
258
|
}
|
|
259
|
+
return [...modules.values()];
|
|
260
|
+
}
|
|
261
|
+
/** The record's module list after a verify pass that THREW: every module keeps
|
|
262
|
+
* its entry and is stamped with the failed pass, so a module removed after it
|
|
263
|
+
* never reads as removed after a pass that passed. */
|
|
264
|
+
export function modulesAfterVerifyError(prior) {
|
|
265
|
+
const at = new Date().toISOString();
|
|
266
|
+
return (prior ?? []).map((m) => ({
|
|
267
|
+
...m,
|
|
268
|
+
lastPass: { at, failed: true, errored: true },
|
|
269
|
+
}));
|
|
291
270
|
}
|
|
292
|
-
/** Verdict of one utils verify pass. The verdict
|
|
293
|
-
*
|
|
271
|
+
/** Verdict of one utils verify pass. The verdict is stored — counts and the file
|
|
272
|
+
* list are re-derived from the delivered files at report time — and so are the
|
|
273
|
+
* modules the pass located and the failures of a failed pass: a module the run
|
|
274
|
+
* removes afterwards can be re-derived from nothing, and its row must still say
|
|
275
|
+
* it existed and why it went. */
|
|
294
276
|
export async function recordUtilsVerifyOutcome(testFile, r, language, framework, testType, explicitStateFile,
|
|
295
277
|
/** The agent's declarations from this verify call, when it made any. */
|
|
296
278
|
inHouseDeclared) {
|
|
@@ -308,6 +290,7 @@ inHouseDeclared) {
|
|
|
308
290
|
return updateReuseOutcome(testFile, (existing) => {
|
|
309
291
|
const mine = owns(existing, testFile);
|
|
310
292
|
const prior = mine ? existing.utils : undefined;
|
|
293
|
+
const modules = locatedModules(prior?.modules, r);
|
|
311
294
|
return {
|
|
312
295
|
...(mine ? {} : freshRecord()),
|
|
313
296
|
testFilePath: testFile,
|
|
@@ -329,24 +312,11 @@ inHouseDeclared) {
|
|
|
329
312
|
...((inHouseDeclared ?? prior?.inHouseDeclared)
|
|
330
313
|
? { inHouseDeclared: inHouseDeclared ?? prior?.inHouseDeclared }
|
|
331
314
|
: {}),
|
|
315
|
+
...(modules.length > 0 ? { modules } : {}),
|
|
332
316
|
},
|
|
333
317
|
};
|
|
334
318
|
}, explicitStateFile);
|
|
335
319
|
}
|
|
336
|
-
/** Whether the run state file can be written — the precondition for any debt this
|
|
337
|
-
* module holds, since only a write clears it. */
|
|
338
|
-
async function runStateWritable(explicitStateFile) {
|
|
339
|
-
const stateFile = resolveRunStatePath(explicitStateFile);
|
|
340
|
-
if (!stateFile)
|
|
341
|
-
return false;
|
|
342
|
-
try {
|
|
343
|
-
await access(stateFile, fsConstants.W_OK);
|
|
344
|
-
return true;
|
|
345
|
-
}
|
|
346
|
-
catch {
|
|
347
|
-
return false;
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
320
|
/** `skyramp_modularization` was called for this spec. Ownership on the write, like
|
|
351
321
|
* the utils debt: a same-basename spec elsewhere does not inherit the mark. */
|
|
352
322
|
export async function recordModularized(testFile, explicitStateFile) {
|
|
@@ -399,14 +369,11 @@ export async function utilsBaselineFor(testFile, explicitStateFile) {
|
|
|
399
369
|
*/
|
|
400
370
|
export async function pendingModularization(testFile, explicitStateFile) {
|
|
401
371
|
try {
|
|
402
|
-
const
|
|
403
|
-
if (!
|
|
372
|
+
const read = await readHandOff(testFile, explicitStateFile);
|
|
373
|
+
if (!read?.handOff)
|
|
404
374
|
return undefined;
|
|
405
|
-
const data =
|
|
406
|
-
const
|
|
407
|
-
if (!handOff)
|
|
408
|
-
return undefined;
|
|
409
|
-
const record = data?.reuseOutcomes?.[path.basename(testFile)];
|
|
375
|
+
const { data, handOff } = read;
|
|
376
|
+
const record = data.reuseOutcomes?.[path.basename(testFile)];
|
|
410
377
|
if (record && !owns(record, testFile))
|
|
411
378
|
return undefined;
|
|
412
379
|
if (record?.modularized)
|
|
@@ -506,14 +473,11 @@ export async function recordReuseHandOff(files, testType, language, framework, e
|
|
|
506
473
|
// nothing on the reuse side records for it — so no hand-off is owed either.
|
|
507
474
|
if (!utilsSpecFor(language))
|
|
508
475
|
return;
|
|
509
|
-
const stateFile = resolveRunStatePath(explicitStateFile);
|
|
510
|
-
if (!stateFile)
|
|
511
|
-
return;
|
|
512
476
|
try {
|
|
513
|
-
const
|
|
514
|
-
|
|
515
|
-
if (!data)
|
|
477
|
+
const state = await readRunState(explicitStateFile);
|
|
478
|
+
if (!state)
|
|
516
479
|
return;
|
|
480
|
+
const { manager, data } = state;
|
|
517
481
|
// A REGENERATE marks its file as maintenance before the generation call it
|
|
518
482
|
// prescribes records the same file as generation. The rewrite is still the
|
|
519
483
|
// maintenance of a file at HEAD, so the origin is never demoted.
|
|
@@ -567,9 +531,7 @@ async function isNewInRepo(file) {
|
|
|
567
531
|
// Only "absent from HEAD" (or no HEAD yet) means new. Anything else — git missing,
|
|
568
532
|
// a corrupt object store, a permission error — is git not answering, and the
|
|
569
533
|
// documented answer to that is false: never block, never accuse, on a malfunction.
|
|
570
|
-
|
|
571
|
-
const text = `${e.stderr ?? ""} ${String(err)}`;
|
|
572
|
-
if (/does not exist|Not a valid object name|bad revision|exists on disk, but not in/i.test(text))
|
|
534
|
+
if (isAbsentFromHead(err))
|
|
573
535
|
return true;
|
|
574
536
|
logger.warning("Could not determine whether a file is new in the repository — treating it as pre-existing", {
|
|
575
537
|
file,
|
|
@@ -596,17 +558,14 @@ async function isNewInRepo(file) {
|
|
|
596
558
|
*/
|
|
597
559
|
export async function pendingReuseChain(testFile, explicitStateFile, testType) {
|
|
598
560
|
try {
|
|
599
|
-
const stateFile = resolveRunStatePath(explicitStateFile);
|
|
600
|
-
if (!stateFile)
|
|
601
|
-
return undefined;
|
|
602
|
-
const data = await StateManager.fromStatePath(stateFile).readData();
|
|
603
561
|
// Keyed by the GENERATED FILE: generation snapshots its outputDir before and after
|
|
604
562
|
// the codegen call and records exactly the files it wrote, so a test generated
|
|
605
563
|
// without codeReuse into the same directory owes nothing, and neither does a
|
|
606
564
|
// pre-existing test beside it.
|
|
607
|
-
const
|
|
608
|
-
if (!handOff)
|
|
565
|
+
const read = await readHandOff(testFile, explicitStateFile);
|
|
566
|
+
if (!read?.handOff)
|
|
609
567
|
return undefined;
|
|
568
|
+
const { data, handOff } = read;
|
|
610
569
|
// A caller that knows the file's type and disagrees with the hand-off is exempt;
|
|
611
570
|
// an unknown type still owes (the hand-off is authoritative for this file).
|
|
612
571
|
if (testType && testType !== handOff.testType)
|
|
@@ -619,7 +578,7 @@ export async function pendingReuseChain(testFile, explicitStateFile, testType) {
|
|
|
619
578
|
// and execute.
|
|
620
579
|
if (record && !owns(record, testFile))
|
|
621
580
|
return undefined;
|
|
622
|
-
if (record && reuseRan(record))
|
|
581
|
+
if (record && reuseRan(record, pomFirstHandOff(handOff)))
|
|
623
582
|
return undefined;
|
|
624
583
|
const maintained = handOff.source === "maintenance";
|
|
625
584
|
if (!maintained && !(await isNewInRepo(testFile)))
|
|
@@ -674,7 +633,7 @@ handOffs) {
|
|
|
674
633
|
if (record) {
|
|
675
634
|
if (!owns(record, file))
|
|
676
635
|
continue; // foreign basename — fail open for this file
|
|
677
|
-
if (reuseRan(record))
|
|
636
|
+
if (reuseRan(record, pomFirstHandOff(h)))
|
|
678
637
|
return undefined;
|
|
679
638
|
}
|
|
680
639
|
try {
|
|
@@ -693,8 +652,12 @@ handOffs) {
|
|
|
693
652
|
}
|
|
694
653
|
/** Whether a record shows the reuse tool ran for its spec: the utils path issued its
|
|
695
654
|
* instructions, or the POM path recorded detection/verification. A modularization mark
|
|
696
|
-
* alone, or a verify verdict recorded without a prior discovery, is NOT reuse.
|
|
697
|
-
|
|
655
|
+
* alone, or a verify verdict recorded without a prior discovery, is NOT reuse. When the
|
|
656
|
+
* page-object pass ran first (`pomFirst`), its verdict is only the first half of the
|
|
657
|
+
* chain: the pair it owes is paid by the utils path's instructions alone. */
|
|
658
|
+
function reuseRan(record, pomFirst) {
|
|
659
|
+
if (pomFirst)
|
|
660
|
+
return record.utils?.issued === true;
|
|
698
661
|
return (record.utils?.issued === true ||
|
|
699
662
|
record.candidatesDetected !== undefined ||
|
|
700
663
|
record.verification !== undefined);
|
|
@@ -826,6 +789,13 @@ async function rederiveHelperOutcome(record, carriedAllows) {
|
|
|
826
789
|
...(r.inlineCallSites.length > 0
|
|
827
790
|
? { siblingInlineCallSites: r.inlineCallSites.length }
|
|
828
791
|
: {}),
|
|
792
|
+
// Helpers nothing imports — the code a deleted test leaves behind for the
|
|
793
|
+
// customer to maintain. Reported, never removed and never gated: the caller
|
|
794
|
+
// list behind it is a lower bound, and removing a helper that is still called
|
|
795
|
+
// is far worse than leaving a dead one. When the walk could not read every
|
|
796
|
+
// file beside the module it reports NO orphan and says so, rather than let an
|
|
797
|
+
// unreadable directory read as "nothing imports this".
|
|
798
|
+
...orphanFields(r),
|
|
829
799
|
verification: r.ok
|
|
830
800
|
? HelperVerificationOutcome.Passed
|
|
831
801
|
: HelperVerificationOutcome.Failed,
|
|
@@ -836,6 +806,22 @@ async function rederiveHelperOutcome(record, carriedAllows) {
|
|
|
836
806
|
blocking,
|
|
837
807
|
};
|
|
838
808
|
}
|
|
809
|
+
/** The orphan fields of a report row: the helpers nothing imports, or the statement
|
|
810
|
+
* that the walk could not tell. Exactly one of the two, and neither when the walk ran
|
|
811
|
+
* and found none. Exported for its tests — the derivation is the whole behaviour. */
|
|
812
|
+
export function orphanFields(r) {
|
|
813
|
+
if (!r.importersComplete)
|
|
814
|
+
return { orphanScanIncomplete: true };
|
|
815
|
+
const orphans = r.singleImporters.filter((h) => h.importers.length === 0);
|
|
816
|
+
if (orphans.length === 0)
|
|
817
|
+
return {};
|
|
818
|
+
return {
|
|
819
|
+
orphanedHelpers: orphans.map((h) => ({
|
|
820
|
+
utilsFile: utilsFileLabel([h.file]),
|
|
821
|
+
helper: h.helper,
|
|
822
|
+
})),
|
|
823
|
+
};
|
|
824
|
+
}
|
|
839
825
|
/**
|
|
840
826
|
* The reason behind a failed shared-helper verdict, one entry per blocking kind in
|
|
841
827
|
* the order the verifier found them. The verify pass had these in hand and dropped
|
|
@@ -948,18 +934,36 @@ export async function deriveReuseFromDelivered(testFile, language) {
|
|
|
948
934
|
};
|
|
949
935
|
}
|
|
950
936
|
export async function rederiveReuse(record, carriedAllows) {
|
|
951
|
-
|
|
952
|
-
//
|
|
953
|
-
//
|
|
937
|
+
// A SkyrampUtils-path record is re-derived from the utils file. With both reuse
|
|
938
|
+
// settings on, the same spec also went through the page-object pass first, and its
|
|
939
|
+
// row carries both halves: the page-object fields and `helpers`/`inHouse`.
|
|
954
940
|
if (record.utils) {
|
|
941
|
+
let helper;
|
|
955
942
|
try {
|
|
956
|
-
|
|
943
|
+
helper = await rederiveHelperOutcome(record, carriedAllows);
|
|
957
944
|
}
|
|
958
945
|
catch (err) {
|
|
959
|
-
logger.warning("Could not re-derive the shared-helper outcome from the delivered files — omitting it rather than reporting a stale verdict", { testFile: testFilePath, error: String(err) });
|
|
960
|
-
|
|
946
|
+
logger.warning("Could not re-derive the shared-helper outcome from the delivered files — omitting it rather than reporting a stale verdict", { testFile: record.testFilePath, error: String(err) });
|
|
947
|
+
helper = { outcome: undefined };
|
|
961
948
|
}
|
|
949
|
+
if (!record.pomPassFinished)
|
|
950
|
+
return helper;
|
|
951
|
+
return combineRederivations(await rederivePomOutcome(record, carriedAllows, true), helper, (carriedAllows ?? []).some((c) => c.keptInline !== undefined), record.testFilePath
|
|
952
|
+
? utilsVerifyCall(record, record.testFilePath)
|
|
953
|
+
: undefined);
|
|
962
954
|
}
|
|
955
|
+
return rederivePomOutcome(record, carriedAllows);
|
|
956
|
+
}
|
|
957
|
+
/** The page-object half of a row, re-derived from the delivered spec. `composed`
|
|
958
|
+
* says the spec also took the SkyrampUtils flow after this pass: the report-time
|
|
959
|
+
* gates then split candidates by owner exactly as the live ones did. */
|
|
960
|
+
async function rederivePomOutcome(record, carriedAllows, composed = false) {
|
|
961
|
+
const { candidatesDetected, verification, flagged, testFilePath, language } = record;
|
|
962
|
+
// No page-object layer was found: that detection-time fact is the whole half. A
|
|
963
|
+
// composed spec whose SkyrampUtils flow wrote its identity must not be measured
|
|
964
|
+
// into page-object counts it never had.
|
|
965
|
+
if (composed && verification === ReuseVerificationOutcome.SkippedNoPom)
|
|
966
|
+
return { outcome: { candidatesDetected, verification } };
|
|
963
967
|
// No spec identity was ever recorded, so there is nothing to measure — only the
|
|
964
968
|
// detection-time facts, which no spec change can invalidate. Reached by the
|
|
965
969
|
// no-POM-layer path; a run that detected candidates always records the identity
|
|
@@ -971,7 +975,7 @@ export async function rederiveReuse(record, carriedAllows) {
|
|
|
971
975
|
const specContent = await readFile(testFilePath, "utf8");
|
|
972
976
|
const carriedKeptInline = (carriedAllows ?? []).filter((c) => c.keptInline !== undefined);
|
|
973
977
|
const classification = { covered: new Set(), ran: false };
|
|
974
|
-
const blocking = await pomBlockingVerdict(testFilePath, language, r, carriedKeptInline.map((c) => c.keptInline), classification);
|
|
978
|
+
const blocking = await pomBlockingVerdict(testFilePath, language, r, carriedKeptInline.map((c) => c.keptInline), classification, composed);
|
|
975
979
|
// Diagnose the file the report is actually built from. A marker the zero-reuse
|
|
976
980
|
// gate accepts but this cannot parse satisfies the escape hatch while its
|
|
977
981
|
// decline never reaches the report — silent signal loss is the failure class
|
|
@@ -1071,7 +1075,7 @@ async function pomBlockingVerdict(testFile, language, r, carriedKeptInline,
|
|
|
1071
1075
|
/** Filled with what the zero-reuse gate did — see `zeroReuseGateFailure`. `ran`
|
|
1072
1076
|
* stays false on every path that returns before the candidate walk, including the
|
|
1073
1077
|
* unknown language and the infra-gate precedence below. */
|
|
1074
|
-
classification) {
|
|
1078
|
+
classification, composed) {
|
|
1075
1079
|
// The record's language was written from the reuse tool's validated `language`
|
|
1076
1080
|
// param, but the record type keeps it a string: a value outside the enum means a
|
|
1077
1081
|
// check that cannot run, and a check that cannot run does not block.
|
|
@@ -1084,6 +1088,7 @@ classification) {
|
|
|
1084
1088
|
testFile,
|
|
1085
1089
|
language: language,
|
|
1086
1090
|
framework: "playwright",
|
|
1091
|
+
composed,
|
|
1087
1092
|
};
|
|
1088
1093
|
let infra;
|
|
1089
1094
|
let zero;
|
|
@@ -36,7 +36,7 @@ export function formatUtilsVerifyText(r, ctx = {}) {
|
|
|
36
36
|
return ((unchecked.length > 0
|
|
37
37
|
? `VERIFICATION PASSED — no Skyramp utils file; this test imports ${unchecked.length} module${unchecked.length === 1 ? "" : "s"} the pass read and does not check: ${unchecked
|
|
38
38
|
.map((f) => path.basename(f))
|
|
39
|
-
.join(", ")} — the repository's own, tracked without a Skyramp header. The in-house resolve and additive checks did not run: ${r.inHouseChecksSkipped ?? "they run for a UI test with
|
|
39
|
+
.join(", ")} — the repository's own, tracked without a Skyramp header. The in-house resolve and additive checks did not run: ${r.inHouseChecksSkipped ?? "they run for a UI test in TypeScript or JavaScript with utils reuse on"}. Continue.`
|
|
40
40
|
: "VERIFICATION PASSED — no shared utils file is written or imported for this test; nothing to verify.") +
|
|
41
41
|
tail +
|
|
42
42
|
unextractedDuplicatesAdvisory(r));
|
|
@@ -286,11 +286,11 @@ function singleImporterAdvisory(r) {
|
|
|
286
286
|
const single = r.singleImporters;
|
|
287
287
|
if (single.length === 0 || r.helpers === 0)
|
|
288
288
|
return "";
|
|
289
|
-
const dead = single.filter((h) => h.importers === 0);
|
|
289
|
+
const dead = single.filter((h) => h.importers.length === 0);
|
|
290
290
|
const lines = ["", ""];
|
|
291
291
|
if (dead.length > 0)
|
|
292
292
|
lines.push(`ADVISORY — ${dead.length} helper${dead.length === 1 ? " is" : "s are"} imported by NOTHING, not even the test that wrote ${dead.length === 1 ? "it" : "them"} (does not block):`, ...dead.map((h) => `- ${show(h.file, r)}:${h.line} · ${h.helper}`), "A dead helper is always actionable: call it from the test it belongs to, or delete it.");
|
|
293
|
-
const once = single.filter((h) => h.importers === 1);
|
|
293
|
+
const once = single.filter((h) => h.importers.length === 1);
|
|
294
294
|
if (once.length === 0)
|
|
295
295
|
return lines.join("\n");
|
|
296
296
|
lines.push(`ADVISORY — ${once.length} of ${r.helpers} shared helper${r.helpers === 1 ? "" : "s"} ${once.length === 1 ? "is" : "are"} imported by exactly one test (does not block):`, ...once.map((h) => `- ${show(h.file, r)}:${h.line} · ${h.helper}`), "Other delivered tests exist and do not call these. Inline a helper back into its test only if it wraps a step specific to this scenario's narrative that no other test would perform as-is; a request wrapper or a cross-test action (sign-in, open a form, apply a filter) stays shared.");
|
|
@@ -380,12 +380,15 @@ function typingAdvisory(r) {
|
|
|
380
380
|
].join("\n");
|
|
381
381
|
}
|
|
382
382
|
/** What the compiler run over the delivered files has to add beyond its blocking
|
|
383
|
-
* `type-error`s:
|
|
384
|
-
*
|
|
385
|
-
*
|
|
386
|
-
*
|
|
387
|
-
*
|
|
388
|
-
*
|
|
383
|
+
* `type-error`s: the three advisory kinds — a name the compiler could not find
|
|
384
|
+
* (`ambient-type-error`), an error the baseline already had on an unchanged line
|
|
385
|
+
* (`inherited-type-error`), an error only the repository's tsconfig raises
|
|
386
|
+
* (`tsconfig-type-error`; whether that tsconfig is the one their build runs is
|
|
387
|
+
* the customer's call) — and the two ways the run could fall short — it did not
|
|
388
|
+
* run at all, or an import resolved from neither the repository nor the MCP's
|
|
389
|
+
* install, so what that module types went unchecked. All are stated, because a
|
|
390
|
+
* PASSED that says nothing reads as a clean compile. Nothing for Python and
|
|
391
|
+
* JavaScript, where `typecheck` is absent. */
|
|
389
392
|
function typecheckAdvisory(r) {
|
|
390
393
|
const tc = r.typecheck;
|
|
391
394
|
if (!tc)
|
|
@@ -395,20 +398,41 @@ function typecheckAdvisory(r) {
|
|
|
395
398
|
lines.push("", "", `NOTE — the delivered files were NOT typechecked (${tc.reason ?? "unknown cause"}); only the static shape rules ran, so a type error can still reach the customer's own \`tsc\`. Read the signatures yourself: every parameter typed by what the SDK call accepts, the request options one object literal.`);
|
|
396
399
|
else if (tc.unresolved.length > 0)
|
|
397
400
|
lines.push("", "", `NOTE — the typecheck could not resolve ${tc.unresolved.map((m) => `\`${m}\``).join(", ")} from the repository or from the MCP's own install, so whatever those modules type was not checked. If a delivered file imports one of them, confirm the import path and that the package is installed in the repository.`);
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
401
|
+
// Attribution that could not excuse anything, when there was something to
|
|
402
|
+
// excuse: an error the repository already had then blocked as this run's, and
|
|
403
|
+
// the agent must know the block is not a verdict on its own edit.
|
|
404
|
+
const at = r.attribution;
|
|
405
|
+
if (at && !at.ran && tc.errors.length > 0) {
|
|
406
|
+
const unknown = at.headUnknown.map((f) => path.basename(f));
|
|
407
|
+
lines.push("", "", `NOTE — no type error was excused as inherited (${at.reason ?? "unknown cause"}), so an error the repository already had at HEAD blocks above as if this run caused it.${unknown.length > 0
|
|
408
|
+
? ` Git gave no HEAD answer for ${unknown.join(", ")}, so every error in ${unknown.length === 1 ? "it" : "them"} is judged this run's.`
|
|
409
|
+
: ""} Fix what is yours; for an error on a line you did not write, report it in your submission rather than delete or rewrite the module.`);
|
|
402
410
|
}
|
|
411
|
+
const ambient = advisoryBlock(r, "ambient-type-error", (n) => `${n} name${n === 1 ? "" : "s"} the compiler could not find`, "Each is a global (`process`, a test runner's `describe`, `Promise`) whose declarations come from a package the repository installs (`@types/node`, the runner's types) or from its `lib` setting. This run cannot see the repository's installed packages, so it cannot tell whether the name exists there; the repository's own `tsc` decides. Do NOT rewrite the code to dodge this note — never a `globalThis` cast or a hand-written declaration; a `process.env` read is the generated form and stays as it is.");
|
|
412
|
+
if (ambient.length > 0)
|
|
413
|
+
lines.push(...ambient);
|
|
403
414
|
else if (tc.tsconfigNotReaching)
|
|
404
415
|
lines.push("", "", `NOTE — the nearest tsconfig (${tc.tsconfigNotReaching}) does not include the delivered files, and nor does any project it references, so nothing was checked under the repository's own settings. The errors above are the ones that hold under every configuration. If this repository type-checks its tests, add them to that config's \`include\`.`);
|
|
405
|
-
|
|
406
|
-
if (settings.length > 0) {
|
|
407
|
-
const n = settings.length;
|
|
408
|
-
lines.push("", "", `ADVISORY — ${n} error${n === 1 ? "" : "s"} under the repository's own tsconfig (${tc.tsconfig ?? "tsconfig.json"}) (does not block):`, ...settings.map((v) => `- ${show(v.file, r)}:${v.line} · ${v.helper}: ${v.detail}`), "These compile with the strictness flags off and fail only under that tsconfig's settings (`noImplicitAny`, `strictNullChecks`, …). It includes the delivered files, so if the repository's build runs it, these are the errors it will report — fix them the same way as a type-error. Advisory only: continue without re-verifying, and do NOT write a `reuse-verify` marker for it.");
|
|
409
|
-
}
|
|
416
|
+
lines.push(...advisoryBlock(r, "inherited-type-error", (n) => `${n} type error${n === 1 ? "" : "s"} the repository already had at HEAD, on ${n === 1 ? "a line" : "lines"} this run did not change`, "These are not the shared module's fault and not this run's to fix: do NOT delete or rewrite the module, restore inline code, or change the repository's installed packages to clear them. The repository's own `tsc` decides whether each one is real."), ...advisoryBlock(r, "tsconfig-type-error", (n) => `${n} error${n === 1 ? "" : "s"} under the repository's own tsconfig (${tc.tsconfig ?? "tsconfig.json"})`, "These compile with the strictness flags off and fail only under that tsconfig's settings (`noImplicitAny`, `strictNullChecks`, …). It includes the delivered files, so if the repository's build runs it, these are the errors it will report — fix them the same way as a type-error."));
|
|
410
417
|
return lines.join("\n");
|
|
411
418
|
}
|
|
419
|
+
/** One compiler advisory block: the findings of `kind`, or nothing. Every kind
|
|
420
|
+
* renders the same way — a count in the heading, one line per finding, the
|
|
421
|
+
* kind's own explanation, then the standing instruction that an advisory is
|
|
422
|
+
* neither re-verified nor documented with a marker — so the shape is stated
|
|
423
|
+
* once and a kind supplies only its heading and its explanation. */
|
|
424
|
+
function advisoryBlock(r, kind, heading, explanation) {
|
|
425
|
+
const found = r.advisories.filter((v) => v.kind === kind);
|
|
426
|
+
if (found.length === 0)
|
|
427
|
+
return [];
|
|
428
|
+
return [
|
|
429
|
+
"",
|
|
430
|
+
"",
|
|
431
|
+
`ADVISORY — ${heading(found.length)} (does not block):`,
|
|
432
|
+
...found.map((v) => `- ${show(v.file, r)}:${v.line} · ${v.helper}: ${v.detail}`),
|
|
433
|
+
`${explanation} Advisory only: continue without re-verifying, and do NOT write a \`reuse-verify\` marker for it.`,
|
|
434
|
+
];
|
|
435
|
+
}
|
|
412
436
|
/** Browser helpers whose Playwright action sequences agree — one operation under two
|
|
413
437
|
* names, the drift a second run compounds. Rendered beside the other
|
|
414
438
|
* duplicate-detection advisories (inline call sites, un-extracted duplicates), after
|
|
@@ -14,6 +14,10 @@ import { languageSchema } from "../../types/TestTypes.js";
|
|
|
14
14
|
export type VerifyTarget = Pick<z.infer<typeof languageSchema>, "language" | "framework"> & {
|
|
15
15
|
testFile: string;
|
|
16
16
|
testType?: string;
|
|
17
|
+
/** Both reuse settings on for this target (the route's `composed`): the in-house
|
|
18
|
+
* path owns free-function modules, so only a class module is a candidate this
|
|
19
|
+
* pass skipped. Stated by the caller, which resolved the route; never inferred. */
|
|
20
|
+
composed: boolean;
|
|
17
21
|
};
|
|
18
22
|
/** Renders a `VerifyResult` (from `verifyReuse`) as the text report returned to the agent. */
|
|
19
23
|
export declare function formatVerifyReport(r: VerifyResult): string;
|