@skyramp/mcp 0.4.2 → 0.4.3-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) 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/fakeVerifyContext.d.ts +3 -0
  16. package/build/recommendation/fakeVerifyContext.js +16 -0
  17. package/build/recommendation/runVerifiers.js +2 -0
  18. package/build/recommendation/types.d.ts +12 -0
  19. package/build/recommendation/verifierContracts.d.ts +24 -2
  20. package/build/recommendation/verifierContracts.js +32 -4
  21. package/build/recommendation/verifiers/changedLinesClaimed.d.ts +17 -0
  22. package/build/recommendation/verifiers/changedLinesClaimed.js +88 -0
  23. package/build/recommendation/verifiers/expectedValueSourced.js +8 -0
  24. package/build/recommendation/verifiers/requirementSourced.js +141 -11
  25. package/build/services/TestGenerationService.js +4 -0
  26. package/build/tools/code-refactor/caller-gate.d.ts +105 -0
  27. package/build/tools/code-refactor/caller-gate.js +174 -0
  28. package/build/tools/code-refactor/changed-helper-report.d.ts +62 -0
  29. package/build/tools/code-refactor/changed-helper-report.js +114 -0
  30. package/build/tools/code-refactor/codeReuseTool.js +131 -62
  31. package/build/tools/code-refactor/enhanceAssertionsTool.js +2 -2
  32. package/build/tools/code-refactor/helper-callers.d.ts +65 -0
  33. package/build/tools/code-refactor/helper-callers.js +85 -0
  34. package/build/tools/code-refactor/modularizationTool.js +10 -0
  35. package/build/tools/code-refactor/pom-pass-state.d.ts +51 -0
  36. package/build/tools/code-refactor/pom-pass-state.js +115 -0
  37. package/build/tools/code-refactor/retrofit-state.d.ts +3 -2
  38. package/build/tools/code-refactor/retrofit-state.js +11 -12
  39. package/build/tools/code-refactor/reuse-outcome.d.ts +6 -0
  40. package/build/tools/code-refactor/reuse-record-store.d.ts +52 -0
  41. package/build/tools/code-refactor/reuse-record-store.js +126 -0
  42. package/build/tools/code-refactor/reuse-state.d.ts +18 -6
  43. package/build/tools/code-refactor/reuse-state.js +78 -131
  44. package/build/tools/code-refactor/utils-verify-gates.js +3 -3
  45. package/build/tools/code-refactor/verify-gates.d.ts +4 -0
  46. package/build/tools/code-refactor/verify-gates.js +8 -1
  47. package/build/tools/generate-tests/generateContractRestTool.js +7 -7
  48. package/build/tools/generate-tests/generateIntegrationRestTool.js +8 -8
  49. package/build/tools/generate-tests/generateUIRestTool.js +3 -3
  50. package/build/tools/submitReportTool.js +91 -29
  51. package/build/tools/test-management/actionsTool.js +7 -0
  52. package/build/tools/test-management/registerTestPlanTool.d.ts +1 -0
  53. package/build/tools/test-management/registerTestPlanTool.js +70 -22
  54. package/build/types/RepositoryAnalysis.d.ts +20 -20
  55. package/build/types/ReuseOutcome.d.ts +96 -0
  56. package/build/types/TestExecution.d.ts +11 -0
  57. package/build/types/TestExecution.js +19 -0
  58. package/build/types/index.d.ts +1 -1
  59. package/build/utils/branchDiff.d.ts +1 -1
  60. package/build/utils/branchDiff.js +1 -1
  61. package/build/utils/changedRuns.d.ts +13 -0
  62. package/build/utils/changedRuns.js +56 -0
  63. package/build/utils/featureFlags.d.ts +8 -10
  64. package/build/utils/featureFlags.js +23 -21
  65. package/build/utils/normalizeSkyrampImports.d.ts +4 -1
  66. package/build/utils/normalizeSkyrampImports.js +24 -17
  67. package/build/utils/pom-scope/ownership.d.ts +19 -0
  68. package/build/utils/pom-scope/ownership.js +31 -0
  69. package/build/utils/pom-scope/pom-files.d.ts +8 -0
  70. package/build/utils/pom-scope/pom-files.js +8 -0
  71. package/build/utils/pom-scope/scoring.js +13 -1
  72. package/build/utils/pom-scope/strip.d.ts +10 -0
  73. package/build/utils/pom-scope/strip.js +9 -0
  74. package/build/utils/pom-verify/verify.js +13 -2
  75. package/build/utils/reuseRouting.d.ts +30 -2
  76. package/build/utils/reuseRouting.js +36 -15
  77. package/build/utils/testDependencyPolicy.js +4 -16
  78. package/build/utils/utils-verify/action-sites.d.ts +2 -2
  79. package/build/utils/utils-verify/body-reach.d.ts +2 -2
  80. package/build/utils/utils-verify/body-reach.js +4 -1
  81. package/build/utils/utils-verify/call-sites.d.ts +16 -6
  82. package/build/utils/utils-verify/call-sites.js +12 -6
  83. package/build/utils/utils-verify/changed-helpers.d.ts +38 -0
  84. package/build/utils/utils-verify/changed-helpers.js +91 -0
  85. package/build/utils/utils-verify/head.d.ts +48 -0
  86. package/build/utils/utils-verify/head.js +115 -0
  87. package/build/utils/utils-verify/importers.d.ts +59 -10
  88. package/build/utils/utils-verify/importers.js +121 -31
  89. package/build/utils/utils-verify/in-house.js +26 -31
  90. package/build/utils/utils-verify/index.d.ts +3 -0
  91. package/build/utils/utils-verify/index.js +3 -0
  92. package/build/utils/utils-verify/language-spec.js +10 -2
  93. package/build/utils/utils-verify/locate.d.ts +3 -0
  94. package/build/utils/utils-verify/locate.js +6 -6
  95. package/build/utils/utils-verify/parse.d.ts +13 -1
  96. package/build/utils/utils-verify/parse.js +122 -49
  97. package/build/utils/utils-verify/retrofit-equivalence.d.ts +0 -5
  98. package/build/utils/utils-verify/retrofit-equivalence.js +2 -34
  99. package/build/utils/utils-verify/stage.js +4 -16
  100. package/build/utils/utils-verify/verify.d.ts +9 -3
  101. package/build/utils/utils-verify/verify.js +26 -12
  102. package/node_modules/playwright/lib/dom-analyzer/blueprint.js +1 -0
  103. package/node_modules/playwright/lib/dom-analyzer/dynamicId.js +1 -0
  104. package/node_modules/playwright/lib/dom-analyzer/dynamicId.test.js +6 -0
  105. package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +303 -150
  106. package/node_modules/playwright/node_modules/playwright-core/lib/generated/pollingRecorderSource.js +1 -1
  107. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{codeMirrorModule-LNgEKtdV.js → codeMirrorModule-Bou8kKzE.js} +1 -1
  108. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{defaultSettingsView-Bwr1eMKC.js → defaultSettingsView-DTp2-8Si.js} +3 -3
  109. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{index.BAkLd5DX.js → index.ZYuEFHsi.js} +1 -1
  110. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html +2 -2
  111. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{uiMode.BPopbasy.js → uiMode.GLqsx5cI.js} +1 -1
  112. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.html +2 -2
  113. package/node_modules/playwright/node_modules/playwright-core/package.json +1 -1
  114. package/node_modules/playwright/node_modules/playwright-core/src/generated/pollingRecorderSource.ts +1 -1
  115. package/node_modules/playwright/package.json +1 -1
  116. package/package.json +2 -2
  117. package/plugin/prompts/code-reuse/hand-off.md +13 -1
  118. package/plugin/prompts/plan-tests.md +2 -2
  119. package/plugin/skills/enhance-assertions/reference/shared-rules.md +1 -0
@@ -0,0 +1,115 @@
1
+ import * as path from "path";
2
+ import { logger } from "../../utils/logger.js";
3
+ import { isPomFirstTarget } from "../../utils/reuseRouting.js";
4
+ import { ReuseVerificationOutcome } from "./reuse-outcome.js";
5
+ import { freshRecord, owns, readHandOff, readReuseRecord, runStateWritable, updateReuseOutcome, } from "./reuse-record-store.js";
6
+ /**
7
+ * The page-object pass as run state, for a target both reuse settings serve
8
+ * (isPomFirstTarget): whether the pass has finished for a spec, the mark that
9
+ * finishes it, the checkpoint that holds modularization until it has, and the
10
+ * merge of the two halves one such spec's report row carries.
11
+ */
12
+ /** Whether a hand-off's chain starts with the page-object pass. Derived from what the
13
+ * hand-off already stores, never recorded beside it: generation writes the hand-off
14
+ * from exactly these three values, and maintenance arms none for such a target. */
15
+ export function pomFirstHandOff(handOff) {
16
+ return isPomFirstTarget(handOff.language, handOff.testType);
17
+ }
18
+ /**
19
+ * With both reuse settings on (isPomFirstTarget), where this spec's page-object pass
20
+ * stands. `undefined` while it is open; `"finished"` once its verify pass PASSED;
21
+ * `"no-layer"` when discovery found no page-object layer to reuse. From then on the
22
+ * reuse tool serves the SkyrampUtils flow for the spec, and the page-object gates
23
+ * have already judged the spec while every step they did not substitute was still raw
24
+ * and inline — the state they were written for.
25
+ *
26
+ * A FAILED verdict keeps the pass open: the page-object report says to fix and
27
+ * re-verify, and `pendingReuseVerification` refuses the tools after it until that
28
+ * happens. A record owned by another spec, or no run state, reads as open; the caller
29
+ * then serves the page-object pass, which is what the spec gets without composition.
30
+ */
31
+ export async function pomPassOutcome(testFile, explicitStateFile) {
32
+ try {
33
+ const record = await readReuseRecord(testFile, explicitStateFile);
34
+ if (!record?.pomPassFinished || !owns(record, testFile))
35
+ return undefined;
36
+ return record.verification === ReuseVerificationOutcome.SkippedNoPom
37
+ ? "no-layer"
38
+ : "finished";
39
+ }
40
+ catch {
41
+ return undefined;
42
+ }
43
+ }
44
+ /** Mark the page-object pass finished for a spec (see `pomPassOutcome`). Written by
45
+ * the reuse tool at the two points that finish it: a PASSED page-object verify, and
46
+ * discovery that found no page-object layer. */
47
+ export async function recordPomPassFinished(testFile, explicitStateFile) {
48
+ await updateReuseOutcome(testFile, (existing) => owns(existing, testFile)
49
+ ? { pomPassFinished: true }
50
+ : { ...freshRecord(), testFilePath: testFile, pomPassFinished: true }, explicitStateFile);
51
+ }
52
+ /**
53
+ * Does this spec owe its page-object pass before `skyramp_modularization`? Only a spec
54
+ * generation handed off with the page-object pass first. Modularizing first would move
55
+ * the raw steps the page-object pass maps into helpers it cannot see, and its gates
56
+ * would then judge a spec with nothing left inline. Fails open without a hand-off, on
57
+ * an unreadable or unwritable state file.
58
+ */
59
+ export async function pendingPomPass(testFile, explicitStateFile) {
60
+ try {
61
+ const handOff = (await readHandOff(testFile, explicitStateFile))?.handOff;
62
+ if (!handOff || !pomFirstHandOff(handOff))
63
+ return undefined;
64
+ if ((await pomPassOutcome(testFile, explicitStateFile)) !== undefined)
65
+ return undefined;
66
+ if (!(await runStateWritable(explicitStateFile)))
67
+ return undefined;
68
+ return (`${path.basename(testFile)} takes the page-object pass BEFORE modularization. Call skyramp_reuse_code with ` +
69
+ `{ testFile: "${testFile}", language: "${handOff.language}", framework: "${handOff.framework}", testType: "${handOff.testType}" }, ` +
70
+ `follow its steps to a PASSED verification (or its "No reusable POM layer detected" answer), then call skyramp_modularization again.`);
71
+ }
72
+ catch (err) {
73
+ logger.warning("Could not check for a pending page-object pass — proceeding", {
74
+ testFile,
75
+ error: String(err),
76
+ });
77
+ return undefined;
78
+ }
79
+ }
80
+ /**
81
+ * One row, both halves. The outcome fields are disjoint by construction (the
82
+ * page-object fields sit at the top level, the shared-helper ones under `helpers` and
83
+ * `inHouse`). The two blocking verdicts become ONE, because the report refuses per
84
+ * spec and one `verify: true` call re-checks both halves (the reuse tool's combined
85
+ * verify). Declines are retired only where the half that owns them was measured: the
86
+ * page-object half measures a `kept inline` decline only when its zero-reuse gate
87
+ * walked the candidates, which it does not at non-zero reuse.
88
+ */
89
+ export function combineRederivations(pom, helper, carriesKeptInline,
90
+ /** The call that re-checks both halves — the shared-helper verify call, which the
91
+ * reuse tool routes to its combined verify for such a spec. */
92
+ combinedVerifyCall) {
93
+ const outcome = pom.outcome || helper.outcome
94
+ ? { ...(pom.outcome ?? {}), ...(helper.outcome ?? {}) }
95
+ : undefined;
96
+ const verdicts = [pom.blocking, helper.blocking].filter((v) => v !== undefined);
97
+ const blocking = verdicts.length === 0
98
+ ? undefined
99
+ : {
100
+ file: verdicts[0].file,
101
+ failures: verdicts.flatMap((v) => v.failures),
102
+ detail: verdicts.map((v) => v.detail).join("\n\n"),
103
+ verifyCall: combinedVerifyCall ?? verdicts[0].verifyCall,
104
+ };
105
+ return {
106
+ outcome,
107
+ ...(blocking ? { blocking } : {}),
108
+ carriedConsumed: [
109
+ ...(pom.carriedConsumed ?? []),
110
+ ...(helper.carriedConsumed ?? []),
111
+ ],
112
+ carriedEvaluated: !!helper.carriedEvaluated &&
113
+ (!!pom.carriedEvaluated || !carriesKeptInline),
114
+ };
115
+ }
@@ -6,8 +6,9 @@ import { type StagingResult } from "../../utils/utils-verify/index.js";
6
6
  * sibling-inline advisory). Staging carries the edit into the output commit, so the
7
7
  * report must disclose it — with an execution that ran the edited bytes.
8
8
  *
9
- * Whether an execution counts is answered by TIME, not by a stored hash: the
10
- * execution record already carries `executedAt`, and the file's mtime says when it
9
+ * Whether an execution counts is answered by TIME, not by a stored hash (the rule
10
+ * itself is `freshExecution`, shared with the changed-helper gate): the execution
11
+ * record already carries `executedAt`, and the file's mtime says when it
11
12
  * was last edited — an execution older than the last edit ran bytes that no longer
12
13
  * exist. That also covers a bare editor write with no tool call in between, which a
13
14
  * record-time hash cannot see. (Same identity pattern as VideoRecord's
@@ -6,14 +6,16 @@ import { stageUtilsArtifacts, } from "../../utils/utils-verify/index.js";
6
6
  import { importFormViolations, locateUtilsFiles, retrofitMismatches, utilsSpecForFile, } from "../../utils/utils-verify/index.js";
7
7
  import { realpath } from "../../utils/utils-verify/index.js";
8
8
  import { canonPath, samePath, utilsFileLabel } from "./reuse-state.js";
9
+ import { freshExecution } from "../../types/TestExecution.js";
9
10
  /**
10
11
  * Retrofit tracking (SKYR-4276 A4): a RETROFIT is a pre-existing Skyramp-generated
11
12
  * test the reuse pass edited to import from the shared utils file (STEP 5c / the
12
13
  * sibling-inline advisory). Staging carries the edit into the output commit, so the
13
14
  * report must disclose it — with an execution that ran the edited bytes.
14
15
  *
15
- * Whether an execution counts is answered by TIME, not by a stored hash: the
16
- * execution record already carries `executedAt`, and the file's mtime says when it
16
+ * Whether an execution counts is answered by TIME, not by a stored hash (the rule
17
+ * itself is `freshExecution`, shared with the changed-helper gate): the execution
18
+ * record already carries `executedAt`, and the file's mtime says when it
17
19
  * was last edited — an execution older than the last edit ran bytes that no longer
18
20
  * exist. That also covers a bare editor write with no tool call in between, which a
19
21
  * record-time hash cannot see. (Same identity pattern as VideoRecord's
@@ -70,17 +72,14 @@ export async function standingRetrofits(state, changedFilesAbs) {
70
72
  if (!changed.has(canonPath(file)))
71
73
  continue;
72
74
  const recorded = state.existingTests?.find((t) => samePath(t.testFile, file))?.executionAfter;
73
- let execution = recorded?.status;
74
- if (recorded) {
75
- try {
76
- const mtime = (await stat(file)).mtimeMs;
77
- if (Date.parse(recorded.executedAt) < mtime)
78
- execution = undefined;
79
- }
80
- catch {
81
- /* fail open: an unreadable mtime must not demand a re-run */
82
- }
75
+ let mtime = 0; // fail open: an unreadable mtime must not demand a re-run
76
+ try {
77
+ mtime = (await stat(file)).mtimeMs;
78
+ }
79
+ catch {
80
+ /* see above */
83
81
  }
82
+ const execution = freshExecution(recorded, mtime);
84
83
  out.push({ file, utilsFile: r.utilsFile, testFile: r.testFile, execution });
85
84
  }
86
85
  return uncrossAttributions(out);
@@ -60,6 +60,12 @@ export interface ReuseRecord {
60
60
  * enforceable unit, as with every other debt here). Read by the reuse tool on a
61
61
  * hand-off target: modularize-first flows owe modularization BEFORE reuse. */
62
62
  modularized?: true;
63
+ /** Both reuse settings on (isPomFirstTarget): the page-object pass finished for this
64
+ * spec — its verify pass PASSED, or it found no page-object layer. Sticky: a later
65
+ * page-object verdict (the combined verify of the SkyrampUtils flow re-checks the
66
+ * page-object calls) does not reopen the pass, so routing never flips back to it.
67
+ * Not recoverable from the spec: it is a fact about the run's order. */
68
+ pomPassFinished?: true;
63
69
  /** The spec as it stood when `skyramp_modularization` handed out its instructions —
64
70
  * after the enhancer, before any code moved (SKYR-4276 A7/A8). The verify pass
65
71
  * compares the delivered spec plus the helpers it imports against this. */
@@ -0,0 +1,52 @@
1
+ import { StateManager, type ReuseHandOff, type UnifiedAnalysisState } from "../../utils/AnalysisStateManager.js";
2
+ import type { ReuseRecord } from "./reuse-outcome.js";
3
+ /**
4
+ * The run-state store behind every reuse record and hand-off: one read, one
5
+ * read-modify-write, one ownership rule, one canonical path comparison. The reuse
6
+ * checkpoints (reuse-state.ts) and the page-object-pass state (pom-pass-state.ts)
7
+ * both build on these and nothing else touches the file.
8
+ *
9
+ * Every write is best-effort. Reuse observability must never be able to fail a
10
+ * refactor that otherwise succeeded, so a missing/unreadable/unwritable state
11
+ * file degrades to "no reuse summary in the report" — the same outcome as running
12
+ * outside a Testbot run, where there is no run state to record into.
13
+ */
14
+ /**
15
+ * Read-modify-write one spec's outcome. `patch` receives the entry recorded so
16
+ * far, so a caller that needs prior state (the verify pass, which accumulates
17
+ * flagged members) uses the same path as one that does not.
18
+ *
19
+ * Fields the patch omits are preserved: the prompt-generation call knows the
20
+ * candidate count, each verify pass knows the counts and verdict.
21
+ */
22
+ export declare function updateReuseOutcome(testFile: string, patch: (existing: ReuseRecord) => Partial<ReuseRecord>, explicitStateFile?: string): Promise<boolean>;
23
+ /** The run state as one read — the state manager (for a write-back) and what it
24
+ * holds — or `undefined` when there is no run state to read. */
25
+ export declare function readRunState(explicitStateFile?: string): Promise<{
26
+ manager: StateManager<UnifiedAnalysisState>;
27
+ data: UnifiedAnalysisState;
28
+ } | undefined>;
29
+ /** This spec's hand-off, keyed by realpath, with the run state it was read from —
30
+ * the ONE read every hand-off checkpoint makes. `handOff` is `undefined` when the
31
+ * file was never handed off; the whole result is when there is no run state. */
32
+ export declare function readHandOff(testFile: string, explicitStateFile?: string): Promise<{
33
+ data: UnifiedAnalysisState;
34
+ handOff: ReuseHandOff | undefined;
35
+ } | undefined>;
36
+ /** Read one spec's record, or `undefined` when there is no run state, no record, or the
37
+ * state file cannot be read. Shares `updateReuseOutcome`'s resolve-read-basename plumbing
38
+ * so the two cannot disagree about which key a spec maps to. */
39
+ export declare function readReuseRecord(testFile: string, explicitStateFile?: string): Promise<ReuseRecord | undefined>;
40
+ /** Every field cleared — the patch spread onto a record owned by another spec. */
41
+ export declare function freshRecord(): Partial<ReuseRecord>;
42
+ /** Whether `record` describes `testFile` (or nobody yet). Basename-keyed run state
43
+ * means two specs can share one record; only the owner's history carries over. */
44
+ export declare function owns(record: ReuseRecord, testFile: string): boolean;
45
+ /** Canonical comparison: a record written through a symlinked path (macOS /var →
46
+ * /private/var) must still be recognised as this spec's when checked by realpath. */
47
+ export declare function samePath(a: string, b: string): boolean;
48
+ /** Exported for retrofit-state: the same canonicalisation the records use. */
49
+ export declare function canonPath(p: string): string;
50
+ /** Whether the run state file can be written — the precondition for any debt this
51
+ * module holds, since only a write clears it. */
52
+ export declare function runStateWritable(explicitStateFile?: string): Promise<boolean>;
@@ -0,0 +1,126 @@
1
+ import * as path from "path";
2
+ import { access, constants as fsConstants } from "fs/promises";
3
+ import { realpathSync } from "fs";
4
+ import { logger } from "../../utils/logger.js";
5
+ import { resolveRunStatePath, StateManager, } from "../../utils/AnalysisStateManager.js";
6
+ import { realpath } from "../../utils/utils-verify/index.js";
7
+ /**
8
+ * The run-state store behind every reuse record and hand-off: one read, one
9
+ * read-modify-write, one ownership rule, one canonical path comparison. The reuse
10
+ * checkpoints (reuse-state.ts) and the page-object-pass state (pom-pass-state.ts)
11
+ * both build on these and nothing else touches the file.
12
+ *
13
+ * Every write is best-effort. Reuse observability must never be able to fail a
14
+ * refactor that otherwise succeeded, so a missing/unreadable/unwritable state
15
+ * file degrades to "no reuse summary in the report" — the same outcome as running
16
+ * outside a Testbot run, where there is no run state to record into.
17
+ */
18
+ /**
19
+ * Read-modify-write one spec's outcome. `patch` receives the entry recorded so
20
+ * far, so a caller that needs prior state (the verify pass, which accumulates
21
+ * flagged members) uses the same path as one that does not.
22
+ *
23
+ * Fields the patch omits are preserved: the prompt-generation call knows the
24
+ * candidate count, each verify pass knows the counts and verdict.
25
+ */
26
+ export async function updateReuseOutcome(testFile, patch, explicitStateFile) {
27
+ try {
28
+ const state = await readRunState(explicitStateFile);
29
+ if (!state)
30
+ return false;
31
+ const { manager, data } = state;
32
+ const key = path.basename(testFile);
33
+ const existing = data.reuseOutcomes?.[key] ?? {};
34
+ await manager.appendData({
35
+ ...data,
36
+ reuseOutcomes: {
37
+ ...data.reuseOutcomes,
38
+ [key]: { ...existing, ...patch(existing) },
39
+ },
40
+ });
41
+ return true;
42
+ }
43
+ catch (err) {
44
+ logger.warning("Could not record POM-reuse outcome — report will omit the reuse summary", {
45
+ testFile,
46
+ error: String(err),
47
+ });
48
+ return false;
49
+ }
50
+ }
51
+ /** The run state as one read — the state manager (for a write-back) and what it
52
+ * holds — or `undefined` when there is no run state to read. */
53
+ export async function readRunState(explicitStateFile) {
54
+ const stateFile = resolveRunStatePath(explicitStateFile);
55
+ if (!stateFile)
56
+ return undefined;
57
+ const manager = StateManager.fromStatePath(stateFile);
58
+ const data = await manager.readData();
59
+ return data ? { manager, data } : undefined;
60
+ }
61
+ /** This spec's hand-off, keyed by realpath, with the run state it was read from —
62
+ * the ONE read every hand-off checkpoint makes. `handOff` is `undefined` when the
63
+ * file was never handed off; the whole result is when there is no run state. */
64
+ export async function readHandOff(testFile, explicitStateFile) {
65
+ const state = await readRunState(explicitStateFile);
66
+ if (!state)
67
+ return undefined;
68
+ return {
69
+ data: state.data,
70
+ handOff: state.data.reuseHandOffs?.[await realpath(testFile)],
71
+ };
72
+ }
73
+ /** Read one spec's record, or `undefined` when there is no run state, no record, or the
74
+ * state file cannot be read. Shares `updateReuseOutcome`'s resolve-read-basename plumbing
75
+ * so the two cannot disagree about which key a spec maps to. */
76
+ export async function readReuseRecord(testFile, explicitStateFile) {
77
+ return (await readRunState(explicitStateFile))?.data.reuseOutcomes?.[path.basename(testFile)];
78
+ }
79
+ /** Every field cleared — the patch spread onto a record owned by another spec. */
80
+ export function freshRecord() {
81
+ return {
82
+ candidatesDetected: undefined,
83
+ verification: undefined,
84
+ flagged: undefined,
85
+ language: undefined,
86
+ utils: undefined,
87
+ modularized: undefined,
88
+ pomPassFinished: undefined,
89
+ };
90
+ }
91
+ /** Whether `record` describes `testFile` (or nobody yet). Basename-keyed run state
92
+ * means two specs can share one record; only the owner's history carries over. */
93
+ export function owns(record, testFile) {
94
+ return !record.testFilePath || samePath(record.testFilePath, testFile);
95
+ }
96
+ /** Canonical comparison: a record written through a symlinked path (macOS /var →
97
+ * /private/var) must still be recognised as this spec's when checked by realpath. */
98
+ export function samePath(a, b) {
99
+ return canon(a) === canon(b);
100
+ }
101
+ /** Exported for retrofit-state: the same canonicalisation the records use. */
102
+ export function canonPath(p) {
103
+ return canon(p);
104
+ }
105
+ function canon(p) {
106
+ try {
107
+ return realpathSync(p);
108
+ }
109
+ catch {
110
+ return path.resolve(p);
111
+ }
112
+ }
113
+ /** Whether the run state file can be written — the precondition for any debt this
114
+ * module holds, since only a write clears it. */
115
+ export async function runStateWritable(explicitStateFile) {
116
+ const stateFile = resolveRunStatePath(explicitStateFile);
117
+ if (!stateFile)
118
+ return false;
119
+ try {
120
+ await access(stateFile, fsConstants.W_OK);
121
+ return true;
122
+ }
123
+ catch {
124
+ return false;
125
+ }
126
+ }
@@ -2,7 +2,20 @@ import { type ReuseHandOff, type ReuseHandOffSource } from "../../utils/Analysis
2
2
  import { type VerifyResult } from "../../utils/pom-verify/index.js";
3
3
  import { type InHouseDeclaration, 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 } 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 `, `.
@@ -233,6 +241,10 @@ export interface ReuseRederivation {
233
241
  outcome: ReuseOutcome | undefined;
234
242
  blocking?: ReuseBlockingVerdict;
235
243
  }
244
+ /** The orphan fields of a report row: the helpers nothing imports, or the statement
245
+ * that the walk could not tell. Exactly one of the two, and neither when the walk ran
246
+ * and found none. Exported for its tests — the derivation is the whole behaviour. */
247
+ export declare function orphanFields(r: UtilsVerifyResult): Pick<HelperReuseOutcome, "orphanedHelpers" | "orphanScanIncomplete">;
236
248
  /**
237
249
  * The reason behind a failed shared-helper verdict, one entry per blocking kind in
238
250
  * the order the verifier found them. The verify pass had these in hand and dropped