@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.
Files changed (135) hide show
  1. package/build/execution/wrapperConfig.d.ts +4 -0
  2. package/build/execution/wrapperConfig.js +14 -2
  3. package/build/prompts/code-reuse.d.ts +5 -1
  4. package/build/prompts/code-reuse.js +16 -10
  5. package/build/prompts/modularization/ui-test-modularization.js +9 -2
  6. package/build/prompts/pom-aware-code-reuse.d.ts +4 -1
  7. package/build/prompts/pom-aware-code-reuse.js +38 -6
  8. package/build/prompts/reuse-hand-off.d.ts +4 -0
  9. package/build/prompts/reuse-hand-off.js +10 -5
  10. package/build/prompts/shared-helper-policy.d.ts +5 -0
  11. package/build/prompts/shared-helper-policy.js +5 -0
  12. package/build/prompts/test-maintenance/driftAnalysisShared.js +2 -0
  13. package/build/prompts/test-maintenance/uiDriftAnalysisSections.js +2 -1
  14. package/build/prompts/testbot/testbot-prompts.js +33 -16
  15. package/build/recommendation/answers.d.ts +5 -3
  16. package/build/recommendation/answers.js +5 -2
  17. package/build/recommendation/fakeVerifyContext.d.ts +3 -0
  18. package/build/recommendation/fakeVerifyContext.js +16 -0
  19. package/build/recommendation/runVerifiers.js +2 -0
  20. package/build/recommendation/types.d.ts +12 -0
  21. package/build/recommendation/verifierContracts.d.ts +32 -2
  22. package/build/recommendation/verifierContracts.js +45 -4
  23. package/build/recommendation/verifiers/changedLinesClaimed.d.ts +17 -0
  24. package/build/recommendation/verifiers/changedLinesClaimed.js +88 -0
  25. package/build/recommendation/verifiers/coverage.js +143 -4
  26. package/build/recommendation/verifiers/expectedValueSourced.js +8 -0
  27. package/build/recommendation/verifiers/requirementSourced.js +141 -11
  28. package/build/services/TestGenerationService.js +4 -0
  29. package/build/tools/code-refactor/caller-gate.d.ts +105 -0
  30. package/build/tools/code-refactor/caller-gate.js +174 -0
  31. package/build/tools/code-refactor/changed-helper-report.d.ts +62 -0
  32. package/build/tools/code-refactor/changed-helper-report.js +114 -0
  33. package/build/tools/code-refactor/codeReuseTool.js +131 -62
  34. package/build/tools/code-refactor/enhanceAssertionsTool.js +2 -2
  35. package/build/tools/code-refactor/helper-callers.d.ts +65 -0
  36. package/build/tools/code-refactor/helper-callers.js +85 -0
  37. package/build/tools/code-refactor/modularizationTool.js +10 -0
  38. package/build/tools/code-refactor/pom-pass-state.d.ts +51 -0
  39. package/build/tools/code-refactor/pom-pass-state.js +115 -0
  40. package/build/tools/code-refactor/removed-modules.d.ts +33 -0
  41. package/build/tools/code-refactor/removed-modules.js +61 -0
  42. package/build/tools/code-refactor/retrofit-state.d.ts +3 -2
  43. package/build/tools/code-refactor/retrofit-state.js +11 -12
  44. package/build/tools/code-refactor/reuse-outcome.d.ts +33 -1
  45. package/build/tools/code-refactor/reuse-record-store.d.ts +57 -0
  46. package/build/tools/code-refactor/reuse-record-store.js +140 -0
  47. package/build/tools/code-refactor/reuse-state.d.ts +34 -9
  48. package/build/tools/code-refactor/reuse-state.js +140 -135
  49. package/build/tools/code-refactor/utils-verify-gates.js +42 -18
  50. package/build/tools/code-refactor/verify-gates.d.ts +4 -0
  51. package/build/tools/code-refactor/verify-gates.js +8 -1
  52. package/build/tools/generate-tests/generateContractRestTool.js +7 -7
  53. package/build/tools/generate-tests/generateIntegrationRestTool.js +8 -8
  54. package/build/tools/generate-tests/generateUIRestTool.js +3 -3
  55. package/build/tools/submitReportTool.js +121 -30
  56. package/build/tools/test-management/actionsTool.js +7 -0
  57. package/build/tools/test-management/registerTestPlanTool.d.ts +1 -0
  58. package/build/tools/test-management/registerTestPlanTool.js +71 -23
  59. package/build/types/RepositoryAnalysis.d.ts +20 -20
  60. package/build/types/ReuseOutcome.d.ts +123 -0
  61. package/build/types/TestExecution.d.ts +11 -0
  62. package/build/types/TestExecution.js +19 -0
  63. package/build/types/index.d.ts +1 -1
  64. package/build/utils/branchDiff.d.ts +1 -1
  65. package/build/utils/branchDiff.js +1 -1
  66. package/build/utils/changedRuns.d.ts +13 -0
  67. package/build/utils/changedRuns.js +56 -0
  68. package/build/utils/featureFlags.d.ts +8 -10
  69. package/build/utils/featureFlags.js +23 -21
  70. package/build/utils/normalizeSkyrampImports.d.ts +4 -1
  71. package/build/utils/normalizeSkyrampImports.js +24 -17
  72. package/build/utils/pom-scope/ownership.d.ts +19 -0
  73. package/build/utils/pom-scope/ownership.js +31 -0
  74. package/build/utils/pom-scope/pom-files.d.ts +8 -0
  75. package/build/utils/pom-scope/pom-files.js +8 -0
  76. package/build/utils/pom-scope/scoring.js +13 -1
  77. package/build/utils/pom-scope/strip.d.ts +10 -0
  78. package/build/utils/pom-scope/strip.js +9 -0
  79. package/build/utils/pom-verify/verify.js +13 -2
  80. package/build/utils/reuseRouting.d.ts +30 -2
  81. package/build/utils/reuseRouting.js +36 -15
  82. package/build/utils/testDependencyPolicy.js +4 -16
  83. package/build/utils/utils-verify/action-sites.d.ts +2 -2
  84. package/build/utils/utils-verify/allow.d.ts +1 -1
  85. package/build/utils/utils-verify/allow.js +15 -0
  86. package/build/utils/utils-verify/body-reach.d.ts +2 -2
  87. package/build/utils/utils-verify/body-reach.js +4 -1
  88. package/build/utils/utils-verify/call-sites.d.ts +16 -6
  89. package/build/utils/utils-verify/call-sites.js +12 -6
  90. package/build/utils/utils-verify/changed-helpers.d.ts +38 -0
  91. package/build/utils/utils-verify/changed-helpers.js +91 -0
  92. package/build/utils/utils-verify/changed-lines.d.ts +48 -0
  93. package/build/utils/utils-verify/changed-lines.js +106 -0
  94. package/build/utils/utils-verify/head.d.ts +73 -0
  95. package/build/utils/utils-verify/head.js +156 -0
  96. package/build/utils/utils-verify/importers.d.ts +59 -10
  97. package/build/utils/utils-verify/importers.js +121 -31
  98. package/build/utils/utils-verify/in-house.js +26 -31
  99. package/build/utils/utils-verify/index.d.ts +3 -0
  100. package/build/utils/utils-verify/index.js +3 -0
  101. package/build/utils/utils-verify/language-spec.js +10 -2
  102. package/build/utils/utils-verify/locate.d.ts +3 -0
  103. package/build/utils/utils-verify/locate.js +6 -6
  104. package/build/utils/utils-verify/parse.d.ts +13 -1
  105. package/build/utils/utils-verify/parse.js +122 -49
  106. package/build/utils/utils-verify/retrofit-equivalence.d.ts +0 -5
  107. package/build/utils/utils-verify/retrofit-equivalence.js +2 -34
  108. package/build/utils/utils-verify/stage.js +4 -16
  109. package/build/utils/utils-verify/typecheck.d.ts +25 -4
  110. package/build/utils/utils-verify/typecheck.js +95 -13
  111. package/build/utils/utils-verify/verify.d.ts +33 -8
  112. package/build/utils/utils-verify/verify.js +180 -18
  113. package/build/utils/versions.d.ts +3 -3
  114. package/build/utils/versions.js +1 -1
  115. package/node_modules/playwright/lib/dom-analyzer/blueprint.js +1 -0
  116. package/node_modules/playwright/lib/dom-analyzer/dynamicId.js +1 -0
  117. package/node_modules/playwright/lib/dom-analyzer/dynamicId.test.js +6 -0
  118. package/node_modules/playwright/lib/mcp/skyramp/exportTool.js +4 -2
  119. package/node_modules/playwright/lib/mcp/skyramp/snapshotLocators.js +246 -0
  120. package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +378 -237
  121. package/node_modules/playwright/lib/mcp/test/skyRampExport.js +21 -0
  122. package/node_modules/playwright/node_modules/playwright-core/lib/generated/pollingRecorderSource.js +1 -1
  123. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{codeMirrorModule-LNgEKtdV.js → codeMirrorModule-Bou8kKzE.js} +1 -1
  124. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{defaultSettingsView-Bwr1eMKC.js → defaultSettingsView-DTp2-8Si.js} +3 -3
  125. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html +2 -2
  126. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{index.BAkLd5DX.js → index.uiPTFncY.js} +1 -1
  127. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{uiMode.BPopbasy.js → uiMode.GLqsx5cI.js} +1 -1
  128. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.html +2 -2
  129. package/node_modules/playwright/node_modules/playwright-core/package.json +1 -1
  130. package/node_modules/playwright/node_modules/playwright-core/src/generated/pollingRecorderSource.ts +1 -1
  131. package/node_modules/playwright/package.json +1 -1
  132. package/package.json +3 -3
  133. package/plugin/prompts/code-reuse/hand-off.md +13 -1
  134. package/plugin/prompts/plan-tests.md +2 -2
  135. 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
- /** Verdict of one utils verify pass. The verdict alone is stored — counts and the
101
- * file list are re-derived from the delivered files at report time. */
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, access, constants as fsConstants } from "fs/promises";
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, StateManager, } from "../../utils/AnalysisStateManager.js";
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
- ? { utils: { ...existing.utils, verifyError: true } }
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
- const root = detectGitRoot(file);
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, and a path through a symlink
275
- // (macOS /tmp → /private/tmp) would otherwise relativise to `../../…`.
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
- function canon(p) {
285
- try {
286
- return realpathSync(p);
287
- }
288
- catch {
289
- return path.resolve(p);
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 alone is stored — counts and the
293
- * file list are re-derived from the delivered files at report time. */
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 stateFile = resolveRunStatePath(explicitStateFile);
403
- if (!stateFile)
372
+ const read = await readHandOff(testFile, explicitStateFile);
373
+ if (!read?.handOff)
404
374
  return undefined;
405
- const data = await StateManager.fromStatePath(stateFile).readData();
406
- const handOff = data?.reuseHandOffs?.[await realpath(testFile)];
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 manager = StateManager.fromStatePath(stateFile);
514
- const data = await manager.readData();
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
- const e = err;
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 handOff = data?.reuseHandOffs?.[await realpath(testFile)];
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
- function reuseRan(record) {
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
- const { candidatesDetected, verification, flagged, testFilePath, language } = record;
952
- // A SkyrampUtils-path record has nothing for the POM verifier to measure; its
953
- // outcome is re-derived from the utils file instead.
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
- return await rederiveHelperOutcome(record, carriedAllows);
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
- return { outcome: undefined };
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 page-object reuse off"}. Continue.`
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: errors only the repository's tsconfig raises (advised — whether that
384
- * tsconfig is the one their build runs is the customer's call), and the two ways
385
- * the run could fall short — it did not run at all, or an import resolved from
386
- * neither the repository nor the MCP's install, so what that module types went
387
- * unchecked. Both are stated, because a PASSED that says nothing reads as a clean
388
- * compile. Nothing for Python and JavaScript, where `typecheck` is absent. */
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
- const ambient = r.advisories.filter((v) => v.kind === "ambient-type-error");
399
- if (ambient.length > 0) {
400
- const n = ambient.length;
401
- lines.push("", "", `ADVISORY — ${n} name${n === 1 ? "" : "s"} the compiler could not find (does not block):`, ...ambient.map((v) => `- ${show(v.file, r)}:${v.line} · ${v.helper}: ${v.detail}`), "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. Advisory only: continue without re-verifying, and do NOT write a `reuse-verify` marker for it.");
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
- const settings = r.advisories.filter((v) => v.kind === "tsconfig-type-error");
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;