@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,47 +1,111 @@
|
|
|
1
|
+
import { allChangedFiles, } from "../types.js";
|
|
1
2
|
import { normalizeCategory } from "../../types/TestRecommendation.js";
|
|
2
3
|
import { REQUIREMENT_SOURCED_CONTRACT, fillPlaceholders, } from "../verifierContracts.js";
|
|
3
|
-
import { normalizeCitedPath } from "./citedPath.js";
|
|
4
|
+
import { changedPathSet, normalizeCitedPath } from "./citedPath.js";
|
|
4
5
|
import { appearsIn, searchable } from "../pullRequestText.js";
|
|
5
6
|
/** Verifier 10. A change names where the agent read it, and the server reads the
|
|
6
7
|
* name back.
|
|
7
8
|
*
|
|
8
9
|
* `source` is the record of where a change came from, and the server holds the
|
|
9
10
|
* pull request's own words, so it reads the claim back against them: a `spec:` path
|
|
10
|
-
* has to name a file the checkout holds AND a file the pull request names, and a
|
|
11
|
+
* has to name a file the checkout holds AND a file the pull request names or changes, and a
|
|
11
12
|
* change read from the pull request has to quote words the pull request carries.
|
|
12
13
|
*
|
|
14
|
+
* A requirements file the pull request names or changes, and the description
|
|
15
|
+
* itself, are read for their lists: every top-level item needs a change that names
|
|
16
|
+
* it by number (`spec:<path>#11`, `pr-description#3`), or the plan answers why it
|
|
17
|
+
* needs no test.
|
|
18
|
+
*
|
|
13
19
|
* A conflict is a disagreement between a requirement and the code. A change read
|
|
14
20
|
* off the diff IS the code, so there is no second side for it to disagree with.
|
|
15
21
|
*
|
|
16
22
|
* The plan tool always supplies the pull request: the testbot fetches its prompt
|
|
17
23
|
* from this same server, so the text is in the process. Both fields blank means
|
|
18
24
|
* the run had no title and no description. */
|
|
19
|
-
const { unsourced: UNSOURCED, unreadable: UNREADABLE, fromDiff: FROM_DIFF, noChange: NO_CHANGE, notNamed: NOT_NAMED, emptyPullRequest: EMPTY_PULL_REQUEST, notQuoted: NOT_QUOTED, unquoted: UNQUOTED, } = REQUIREMENT_SOURCED_CONTRACT.objections;
|
|
25
|
+
const { unsourced: UNSOURCED, unreadable: UNREADABLE, fromDiff: FROM_DIFF, noChange: NO_CHANGE, notNamed: NOT_NAMED, emptyPullRequest: EMPTY_PULL_REQUEST, notQuoted: NOT_QUOTED, unquoted: UNQUOTED, itemUnlinked: ITEM_UNLINKED, unnumbered: UNNUMBERED, } = REQUIREMENT_SOURCED_CONTRACT.objections;
|
|
20
26
|
const CONFLICT_CATEGORY = "requirement_conflict";
|
|
27
|
+
const itemsIn = (anchor) => anchor !== undefined && /^\s*\d+(\s*,\s*\d+)*\s*$/.test(anchor)
|
|
28
|
+
? anchor.split(",").map((n) => Number(n.trim()))
|
|
29
|
+
: [];
|
|
21
30
|
function readSource(raw) {
|
|
22
31
|
const none = (kind) => ({
|
|
23
32
|
kind,
|
|
24
33
|
file: "",
|
|
25
34
|
citedAs: "",
|
|
35
|
+
items: [],
|
|
26
36
|
});
|
|
27
37
|
const source = raw.trim();
|
|
28
38
|
if (source.length === 0)
|
|
29
39
|
return none("unknown");
|
|
30
|
-
if (/^
|
|
40
|
+
if (/^pr-title$/i.test(source))
|
|
31
41
|
return none("pullRequest");
|
|
42
|
+
const description = /^pr-description(?:#(.*))?$/i.exec(source);
|
|
43
|
+
if (description)
|
|
44
|
+
return { ...none("pullRequest"), items: itemsIn(description[1]) };
|
|
32
45
|
if (/^diff$/i.test(source))
|
|
33
46
|
return none("diff");
|
|
34
47
|
const named = /^spec:(.*)$/i.exec(source);
|
|
35
48
|
if (!named)
|
|
36
49
|
return none("unknown");
|
|
37
|
-
const
|
|
38
|
-
|
|
50
|
+
const [path, anchor] = named[1].split(/#/);
|
|
51
|
+
const citedAs = path
|
|
52
|
+
.split(/§/)[0]
|
|
39
53
|
.replace(/(?::\d+)+(?:-\d+)?$/, "")
|
|
40
54
|
.trim();
|
|
41
55
|
const file = normalizeCitedPath(citedAs);
|
|
42
56
|
// `spec:` with nothing after it names no file, so there is nothing to check and
|
|
43
57
|
// nothing to trust either.
|
|
44
|
-
return file
|
|
58
|
+
return file
|
|
59
|
+
? { kind: "spec", file, citedAs, items: itemsIn(anchor) }
|
|
60
|
+
: none("unknown");
|
|
61
|
+
}
|
|
62
|
+
const LIST_LINE = /^([ \t]*)(\d+(?=[.)])|[-*+•])[.)]?\s+(.*)$/;
|
|
63
|
+
/** Only a prose file holds a list of requirements. A YAML or JSON file is a list of
|
|
64
|
+
* settings, and each of its lines would read as one. */
|
|
65
|
+
const PROSE_FILE = /(^|\/)[^./]+$|\.(md|markdown|txt|rst|adoc)$/i;
|
|
66
|
+
/** The top-level items of a list: its numbered or bulleted lines at the smallest
|
|
67
|
+
* indent. A nested bullet is not an item of its own. Lines inside a fenced code
|
|
68
|
+
* block or a leading front-matter block are not items, and neither is a line with
|
|
69
|
+
* no letter or digit (a `- - -` rule). A text with no list has no items.
|
|
70
|
+
*
|
|
71
|
+
* An item's number is the one the text writes (`11.`), so a plan can cite it as the
|
|
72
|
+
* reader sees it, and a bulleted list is numbered by position from 1. A text whose
|
|
73
|
+
* numbers repeat (two numbered lists) or that mixes numbered and bulleted items
|
|
74
|
+
* has no numbering a plan can cite, so it gives no items. */
|
|
75
|
+
function topLevelListItems(text) {
|
|
76
|
+
const lines = text
|
|
77
|
+
.replace(/^\uFEFF/, "")
|
|
78
|
+
.replace(/^---\r?\n[\s\S]*?\r?\n---(\r?\n|$)/, "")
|
|
79
|
+
.split(/\r\n?|\n/);
|
|
80
|
+
// The marker that opened the fence, so ``` is closed by ``` and not by ~~~.
|
|
81
|
+
let fence;
|
|
82
|
+
const listed = lines.flatMap((line) => {
|
|
83
|
+
const marker = /^\s*(```|~~~)/.exec(line)?.[1];
|
|
84
|
+
if (marker && (fence === undefined || fence === marker))
|
|
85
|
+
fence = fence === undefined ? marker : undefined;
|
|
86
|
+
const match = fence !== undefined || marker ? null : LIST_LINE.exec(line);
|
|
87
|
+
if (!match || !/[\p{L}\p{N}]/u.test(match[3]))
|
|
88
|
+
return [];
|
|
89
|
+
return [
|
|
90
|
+
{ indent: match[1].length, written: match[2], text: match[3].trim() },
|
|
91
|
+
];
|
|
92
|
+
});
|
|
93
|
+
const top = Math.min(...listed.map((line) => line.indent));
|
|
94
|
+
const items = listed.filter((line) => line.indent === top);
|
|
95
|
+
const written = items.map((item) => Number.parseInt(item.written, 10));
|
|
96
|
+
if (written.every(Number.isInteger)) {
|
|
97
|
+
// Numbers that repeat mean two numbered lists; no single number names an item.
|
|
98
|
+
if (new Set(written).size !== written.length)
|
|
99
|
+
return [];
|
|
100
|
+
return items.map((item, index) => ({
|
|
101
|
+
number: written[index],
|
|
102
|
+
text: item.text,
|
|
103
|
+
}));
|
|
104
|
+
}
|
|
105
|
+
// A mix of numbered and bulleted lines has no numbering a reader can cite.
|
|
106
|
+
if (written.some(Number.isInteger))
|
|
107
|
+
return [];
|
|
108
|
+
return items.map((item, index) => ({ number: index + 1, text: item.text }));
|
|
45
109
|
}
|
|
46
110
|
/** A source that stands for a requirement: the pull request, or a requirements file. */
|
|
47
111
|
const statesARequirement = (source) => source.kind === "pullRequest" || source.kind === "spec";
|
|
@@ -54,6 +118,12 @@ export const requirementSourced = {
|
|
|
54
118
|
const sourceById = new Map();
|
|
55
119
|
const quoteById = new Map();
|
|
56
120
|
const pullRequestText = searchable(ctx.pullRequest);
|
|
121
|
+
const changedPaths = changedPathSet(allChangedFiles(ctx), false);
|
|
122
|
+
// The list items the changes name, per requirements file the pull request
|
|
123
|
+
// names or changes, and for the description.
|
|
124
|
+
const descriptionItems = [];
|
|
125
|
+
let descriptionCited = false;
|
|
126
|
+
const itemsBySpec = new Map();
|
|
57
127
|
for (const change of changes) {
|
|
58
128
|
const id = String(change.id ?? "").trim();
|
|
59
129
|
const changeId = id || "(unnamed)";
|
|
@@ -75,6 +145,9 @@ export const requirementSourced = {
|
|
|
75
145
|
continue;
|
|
76
146
|
}
|
|
77
147
|
if (source.kind === "pullRequest") {
|
|
148
|
+
if (/^pr-description/i.test(written))
|
|
149
|
+
descriptionCited = true;
|
|
150
|
+
descriptionItems.push(...source.items);
|
|
78
151
|
if (pullRequestText.length > 0)
|
|
79
152
|
continue;
|
|
80
153
|
objections.push({
|
|
@@ -103,11 +176,17 @@ export const requirementSourced = {
|
|
|
103
176
|
});
|
|
104
177
|
continue;
|
|
105
178
|
}
|
|
106
|
-
// A requirements file the pull request
|
|
107
|
-
// looking through the repository, which is not reading a
|
|
179
|
+
// A requirements file the pull request neither names nor changes is a file the
|
|
180
|
+
// agent found by looking through the repository, which is not reading a
|
|
181
|
+
// requirement.
|
|
108
182
|
if (appearsIn(pullRequestText, source.file) ||
|
|
109
|
-
appearsIn(pullRequestText, source.citedAs)
|
|
183
|
+
appearsIn(pullRequestText, source.citedAs) ||
|
|
184
|
+
changedPaths.has(source.file)) {
|
|
185
|
+
const cited = itemsBySpec.get(source.file) ?? { written, items: [] };
|
|
186
|
+
cited.items.push(...source.items);
|
|
187
|
+
itemsBySpec.set(source.file, cited);
|
|
110
188
|
continue;
|
|
189
|
+
}
|
|
111
190
|
objections.push({
|
|
112
191
|
objectionId: `requirementSourced:notNamed:${changeId}`,
|
|
113
192
|
verifier: "requirementSourced",
|
|
@@ -115,10 +194,61 @@ export const requirementSourced = {
|
|
|
115
194
|
change: changeId,
|
|
116
195
|
source: written,
|
|
117
196
|
}),
|
|
118
|
-
evidence: `change ${changeId} states source \`${written}\`; neither the title nor the description names ${source.file}`,
|
|
197
|
+
evidence: `change ${changeId} states source \`${written}\`; neither the title nor the description names ${source.file}, and the diff does not change it`,
|
|
119
198
|
suggestion: NOT_NAMED.suggestion,
|
|
120
199
|
});
|
|
121
200
|
}
|
|
201
|
+
// Every item of a list needs a change that names it by number. A list no change
|
|
202
|
+
// numbers at all is one objection, not one per item: the plan is not citing the
|
|
203
|
+
// list by item yet, and one answer covers that.
|
|
204
|
+
const objectToUnlinked = (items, linked, where, source) => {
|
|
205
|
+
if (items.length === 0)
|
|
206
|
+
return;
|
|
207
|
+
// A number the list does not hold names nothing.
|
|
208
|
+
if (!linked.some((n) => items.some((item) => item.number === n))) {
|
|
209
|
+
objections.push({
|
|
210
|
+
objectionId: `requirementSourced:unnumbered:${where}`,
|
|
211
|
+
verifier: "requirementSourced",
|
|
212
|
+
message: fillPlaceholders(UNNUMBERED.message, {
|
|
213
|
+
source,
|
|
214
|
+
count: String(items.length),
|
|
215
|
+
}),
|
|
216
|
+
evidence: `${where} lists ${items.length} item(s); no change names one as \`#<number>\``,
|
|
217
|
+
suggestion: fillPlaceholders(UNNUMBERED.suggestion, { source }),
|
|
218
|
+
});
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
for (const item of items) {
|
|
222
|
+
if (linked.includes(item.number))
|
|
223
|
+
continue;
|
|
224
|
+
objections.push({
|
|
225
|
+
objectionId: `requirementSourced:specItem:${where}#${item.number}`,
|
|
226
|
+
verifier: "requirementSourced",
|
|
227
|
+
message: fillPlaceholders(ITEM_UNLINKED.message, {
|
|
228
|
+
source,
|
|
229
|
+
number: String(item.number),
|
|
230
|
+
item: item.text,
|
|
231
|
+
}),
|
|
232
|
+
evidence: `${where} lists item ${item.number}, "${item.text}"; no change names #${item.number}`,
|
|
233
|
+
suggestion: fillPlaceholders(ITEM_UNLINKED.suggestion, {
|
|
234
|
+
source,
|
|
235
|
+
number: String(item.number),
|
|
236
|
+
}),
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
for (const [file, { written, items }] of itemsBySpec) {
|
|
241
|
+
if (!PROSE_FILE.test(file))
|
|
242
|
+
continue;
|
|
243
|
+
const text = ctx.readSpecFile(file);
|
|
244
|
+
if (text === undefined)
|
|
245
|
+
continue;
|
|
246
|
+
objectToUnlinked(topLevelListItems(text), items, file, `spec:${written.slice(5).split("#")[0]}`);
|
|
247
|
+
}
|
|
248
|
+
// The description's own list is read the same way, when a change cites the
|
|
249
|
+
// description. Its sentences are not read.
|
|
250
|
+
if (descriptionCited)
|
|
251
|
+
objectToUnlinked(topLevelListItems(ctx.pullRequest.description), descriptionItems, "pr-description", "pr-description");
|
|
122
252
|
for (const plannedTest of plannedTests) {
|
|
123
253
|
if (normalizeCategory(plannedTest.scenario.category) !== CONFLICT_CATEGORY)
|
|
124
254
|
continue;
|
|
@@ -14,6 +14,7 @@ import { stageGeneratedPaths, resolveOutputDir } from "../utils/gitStaging.js";
|
|
|
14
14
|
import { isInsideExecutorWorkDir, executorWorkDirRefusal, generationTargets, } from "../utils/executorWorkDir.js";
|
|
15
15
|
import { getTestsRepoDir } from "../utils/AnalysisStateManager.js";
|
|
16
16
|
import { recordReuseHandOff } from "../tools/code-refactor/reuse-state.js";
|
|
17
|
+
import { isPomFirstTarget } from "../utils/reuseRouting.js";
|
|
17
18
|
import { recordAssertionBaseline } from "../tools/code-refactor/assertion-state.js";
|
|
18
19
|
import { persistGeneratedTest } from "../utils/generatedTestRecord.js";
|
|
19
20
|
import { buildModularizeFirstNextSteps, isModularizeFirstHandOffTarget, reuseCodeArgsClause, } from "../prompts/reuse-hand-off.js";
|
|
@@ -166,6 +167,8 @@ export class TestGenerationService {
|
|
|
166
167
|
// and the report can mark it — without ever owing it for a test that was not
|
|
167
168
|
// told to run it. Keyed by the files this call wrote: a test generated into
|
|
168
169
|
// the same directory without codeReuse owes nothing.
|
|
170
|
+
// Both reuse settings on: the page-object pass runs first (isPomFirstTarget).
|
|
171
|
+
const pomFirst = isPomFirstTarget(params.language, reuseTestType);
|
|
169
172
|
await recordReuseHandOff(written, testType, params.language, effectiveFramework);
|
|
170
173
|
// Browser tests: the assertion enhancer anchors new assertions to
|
|
171
174
|
// selectors present in the test file, and after modularize → reuse
|
|
@@ -182,6 +185,7 @@ ${buildModularizeFirstNextSteps({
|
|
|
182
185
|
language: params.language,
|
|
183
186
|
framework: effectiveFramework,
|
|
184
187
|
enhanceFirst,
|
|
188
|
+
pomFirst,
|
|
185
189
|
})}
|
|
186
190
|
`;
|
|
187
191
|
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type { UnifiedAnalysisState } from "../../utils/AnalysisStateManager.js";
|
|
2
|
+
import { type TestExecutionStatus } from "../../types/TestExecution.js";
|
|
3
|
+
import type { HelperChangeKind } from "../../types/ReuseOutcome.js";
|
|
4
|
+
import type { ChangedHelperView } from "./helper-callers.js";
|
|
5
|
+
/** One changed helper as a caller meets it. */
|
|
6
|
+
export interface CalledChange {
|
|
7
|
+
/** Absolute path of the module it lives in — with several modules in scope, a
|
|
8
|
+
* helper NAME alone does not say which one this caller met. */
|
|
9
|
+
utilsFile: string;
|
|
10
|
+
helper: string;
|
|
11
|
+
change: HelperChangeKind;
|
|
12
|
+
}
|
|
13
|
+
/** The execution recorded for one caller of a changed helper. */
|
|
14
|
+
export interface CallerExecution {
|
|
15
|
+
/** Absolute path of the caller. */
|
|
16
|
+
file: string;
|
|
17
|
+
/** Present only when an execution ran the module as it stands. */
|
|
18
|
+
execution?: TestExecutionStatus;
|
|
19
|
+
/** This run delivered or edited the caller, so its own report row carries its
|
|
20
|
+
* execution. Without this, a missing `execution` here reads as "never run" for a
|
|
21
|
+
* file the run did run. */
|
|
22
|
+
deliveredByThisRun?: true;
|
|
23
|
+
}
|
|
24
|
+
/** A caller the run CAN execute that has no result against the changed module.
|
|
25
|
+
*
|
|
26
|
+
* Carries what running it needs, because the gate has it in hand — it reads the
|
|
27
|
+
* inventory entry to decide the file is runnable at all — and a remedy the agent has
|
|
28
|
+
* to go and look values up for is a round spent discovering them. All three are
|
|
29
|
+
* required on an inventory entry (`TestAnalysisResult`), and an owed caller always
|
|
30
|
+
* has one: a caller without an entry is never owed. */
|
|
31
|
+
export interface OwedCaller {
|
|
32
|
+
/** Absolute path of the caller. */
|
|
33
|
+
file: string;
|
|
34
|
+
/** The changed helpers it calls. */
|
|
35
|
+
helpers: CalledChange[];
|
|
36
|
+
language: string;
|
|
37
|
+
framework: string;
|
|
38
|
+
/** Not guessable from the path: SKYR-4435 records a shared module inventoried
|
|
39
|
+
* with `unknown`. */
|
|
40
|
+
testType: string;
|
|
41
|
+
}
|
|
42
|
+
export interface ChangedHelperGateParams {
|
|
43
|
+
views: ChangedHelperView[];
|
|
44
|
+
/** `existingTests` says which callers are inventoried and runnable;
|
|
45
|
+
* `testExecutions` holds the latest run of ANY file — the inventory's
|
|
46
|
+
* `executionAfter` is written for a pre-existing test only, and a caller this
|
|
47
|
+
* run created has its result nowhere else. */
|
|
48
|
+
state: Pick<UnifiedAnalysisState, "existingTests" | "testExecutions">;
|
|
49
|
+
/** Absolute paths of everything this run changed, across every checkout. */
|
|
50
|
+
changedFilesAbs: string[];
|
|
51
|
+
}
|
|
52
|
+
export interface ChangedHelperGateResult {
|
|
53
|
+
/** Per caller of a changed helper, by canonical path, the execution that ran the
|
|
54
|
+
* changed module. */
|
|
55
|
+
executions: Map<string, CallerExecution>;
|
|
56
|
+
/** Callers the run can execute that have no result against the changed module.
|
|
57
|
+
* Empty is the ordinary case. */
|
|
58
|
+
owed: OwedCaller[];
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The report gate over the callers of a changed helper: each one the run CAN execute
|
|
62
|
+
* must have been run on the module as it stands. A caller that ran and FAILED is
|
|
63
|
+
* disclosed with its result and blocks nothing — the reviewer reads the red and
|
|
64
|
+
* decides, and SKYR-4451 adds the classification that decides for them.
|
|
65
|
+
*
|
|
66
|
+
* This is the retrofit gate's rule turned outward. A retrofit is a caller this run
|
|
67
|
+
* EDITED; a caller here is one it did not touch at all, which is why nothing ran it.
|
|
68
|
+
* The identity check differs accordingly (`freshExecution` is the same rule for
|
|
69
|
+
* both): a retrofit's execution must post-date the CALLER's last edit, and this one's
|
|
70
|
+
* must post-date the MODULE's — the caller is unchanged, and the bytes that changed
|
|
71
|
+
* under it are the module's.
|
|
72
|
+
*
|
|
73
|
+
* FAILS OPEN on every doubt, and the doubts are enumerated rather than inferred. It
|
|
74
|
+
* demands an execution only for a caller it has positive evidence about: the caller
|
|
75
|
+
* set is known, the file was inventoried as a Skyramp-generated test this run did not
|
|
76
|
+
* deliver, and the run is able to execute it. A helper whose callers could not be
|
|
77
|
+
* enumerated demands nothing; a false demand on a helper nobody calls is worse than
|
|
78
|
+
* a missing report line.
|
|
79
|
+
*/
|
|
80
|
+
export declare function changedHelperGate(params: ChangedHelperGateParams): Promise<ChangedHelperGateResult>;
|
|
81
|
+
/**
|
|
82
|
+
* The refusal a set of unexecuted callers makes, and the exact call that clears it.
|
|
83
|
+
*
|
|
84
|
+
* GIVES the remedy rather than describing it. A sentence naming only the path and
|
|
85
|
+
* the phase produced a call missing arguments `skyramp_execute_test` requires, so the
|
|
86
|
+
* agent spent a round discovering which and could not always recover. The
|
|
87
|
+
* `unresolved-module` finding is the model: it hands over the literal header line to
|
|
88
|
+
* write, not a description of one. Here every value the server holds is printed per
|
|
89
|
+
* file, and the one it does not — the repository's own command for the file — is
|
|
90
|
+
* covered by the same instruction `skyramp_actions` gives for a before-run
|
|
91
|
+
* (`runTestCall`), so the two gates never describe the same call two ways.
|
|
92
|
+
*
|
|
93
|
+
* The arguments common to every caller are stated once above the list; a wall of
|
|
94
|
+
* eight calls × six arguments is its own way of being unreadable. A value is never
|
|
95
|
+
* dropped to shorten it.
|
|
96
|
+
*
|
|
97
|
+
* NOTHING HERE IS ABOUT A FAILING CALLER. This refuses on the ABSENCE of a result,
|
|
98
|
+
* which the agent can always remedy with one call per file. A caller that ran and
|
|
99
|
+
* failed is disclosed with its result and refuses nothing — see SKYR-4451.
|
|
100
|
+
*/
|
|
101
|
+
export declare function unrunCallerRefusal(owed: OwedCaller[], ctx: {
|
|
102
|
+
repoRoot: string;
|
|
103
|
+
repository: string;
|
|
104
|
+
stateFile: string;
|
|
105
|
+
}): string | undefined;
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import * as fs from "fs/promises";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import { TestSource } from "../../types/TestAnalysis.js";
|
|
4
|
+
import { freshExecution, } from "../../types/TestExecution.js";
|
|
5
|
+
import { runTestCall } from "../../skills/runTestSkill.js";
|
|
6
|
+
import { canonPath } from "./reuse-state.js";
|
|
7
|
+
/**
|
|
8
|
+
* The report gate over the callers of a changed helper: each one the run CAN execute
|
|
9
|
+
* must have been run on the module as it stands. A caller that ran and FAILED is
|
|
10
|
+
* disclosed with its result and blocks nothing — the reviewer reads the red and
|
|
11
|
+
* decides, and SKYR-4451 adds the classification that decides for them.
|
|
12
|
+
*
|
|
13
|
+
* This is the retrofit gate's rule turned outward. A retrofit is a caller this run
|
|
14
|
+
* EDITED; a caller here is one it did not touch at all, which is why nothing ran it.
|
|
15
|
+
* The identity check differs accordingly (`freshExecution` is the same rule for
|
|
16
|
+
* both): a retrofit's execution must post-date the CALLER's last edit, and this one's
|
|
17
|
+
* must post-date the MODULE's — the caller is unchanged, and the bytes that changed
|
|
18
|
+
* under it are the module's.
|
|
19
|
+
*
|
|
20
|
+
* FAILS OPEN on every doubt, and the doubts are enumerated rather than inferred. It
|
|
21
|
+
* demands an execution only for a caller it has positive evidence about: the caller
|
|
22
|
+
* set is known, the file was inventoried as a Skyramp-generated test this run did not
|
|
23
|
+
* deliver, and the run is able to execute it. A helper whose callers could not be
|
|
24
|
+
* enumerated demands nothing; a false demand on a helper nobody calls is worse than
|
|
25
|
+
* a missing report line.
|
|
26
|
+
*/
|
|
27
|
+
export async function changedHelperGate(params) {
|
|
28
|
+
const { views, state } = params;
|
|
29
|
+
const changed = new Set(params.changedFilesAbs.map((f) => canonPath(f)));
|
|
30
|
+
const inventory = new Map((state.existingTests ?? []).map((t) => [canonPath(t.testFile), t]));
|
|
31
|
+
const moduleChangedAt = new Map();
|
|
32
|
+
for (const utilsFile of new Set(views.map((v) => v.utilsFile)))
|
|
33
|
+
moduleChangedAt.set(utilsFile, await moduleMtime(utilsFile));
|
|
34
|
+
const perCaller = new Map();
|
|
35
|
+
for (const v of views) {
|
|
36
|
+
// The gate's predicate, stated once: only a caller set this run established.
|
|
37
|
+
if (!v.callers)
|
|
38
|
+
continue;
|
|
39
|
+
// Set above for every view's module.
|
|
40
|
+
const at = moduleChangedAt.get(v.utilsFile);
|
|
41
|
+
for (const caller of v.callers) {
|
|
42
|
+
const key = canonPath(caller);
|
|
43
|
+
const entry = inventory.get(key);
|
|
44
|
+
let seen = perCaller.get(key);
|
|
45
|
+
if (!seen) {
|
|
46
|
+
// A caller this run delivered or edited is still RECORDED, because the
|
|
47
|
+
// report lists every caller and an omitted execution there reads as "never
|
|
48
|
+
// run". Not inventoried, or the repository's own test: the demand would be
|
|
49
|
+
// one the agent cannot comply with as stated — no entry means no language or
|
|
50
|
+
// type to name, and the repository's own suite is the maintenance gate's
|
|
51
|
+
// (see `skyramp_actions`), not this one's. An unsatisfiable gate is the worse
|
|
52
|
+
// failure.
|
|
53
|
+
if (changed.has(key))
|
|
54
|
+
seen = {
|
|
55
|
+
kind: "delivered",
|
|
56
|
+
file: caller,
|
|
57
|
+
helpers: [],
|
|
58
|
+
changedAt: 0,
|
|
59
|
+
entry,
|
|
60
|
+
};
|
|
61
|
+
else if (entry && entry.source !== TestSource.External)
|
|
62
|
+
seen = {
|
|
63
|
+
kind: "inventoried",
|
|
64
|
+
file: caller,
|
|
65
|
+
helpers: [],
|
|
66
|
+
changedAt: 0,
|
|
67
|
+
entry,
|
|
68
|
+
};
|
|
69
|
+
else
|
|
70
|
+
continue;
|
|
71
|
+
perCaller.set(key, seen);
|
|
72
|
+
}
|
|
73
|
+
seen.helpers.push({
|
|
74
|
+
utilsFile: v.utilsFile,
|
|
75
|
+
helper: v.helper,
|
|
76
|
+
change: v.change,
|
|
77
|
+
});
|
|
78
|
+
seen.changedAt = Math.max(seen.changedAt, at);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const executions = new Map();
|
|
82
|
+
const owed = [];
|
|
83
|
+
for (const [key, c] of perCaller) {
|
|
84
|
+
// The execution must post-date the MODULE's last write, not the caller's, and
|
|
85
|
+
// its status must be a result — `freshExecution` says both. Read off the
|
|
86
|
+
// per-file record first: a caller this run CREATED has no inventory row, and
|
|
87
|
+
// reading the inventory alone left it with no result on the disclosure while
|
|
88
|
+
// the callers the run edited showed theirs (repo20-SR04, run 35773589796).
|
|
89
|
+
const latest = state.testExecutions?.[key];
|
|
90
|
+
const ran = freshExecution(latest?.phase === "after" ? latest : c.entry?.executionAfter, c.changedAt);
|
|
91
|
+
executions.set(key, {
|
|
92
|
+
file: c.file,
|
|
93
|
+
...(ran ? { execution: ran } : {}),
|
|
94
|
+
...(c.kind === "delivered" ? { deliveredByThisRun: true } : {}),
|
|
95
|
+
});
|
|
96
|
+
// A delivered caller owes nothing here: its own row carries its execution, and
|
|
97
|
+
// its own row's checks judge it.
|
|
98
|
+
if (c.kind === "delivered" || ran)
|
|
99
|
+
continue;
|
|
100
|
+
owed.push({
|
|
101
|
+
file: c.file,
|
|
102
|
+
helpers: c.helpers,
|
|
103
|
+
language: c.entry.language,
|
|
104
|
+
framework: c.entry.framework,
|
|
105
|
+
testType: String(c.entry.testType),
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
return { executions, owed };
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* The refusal a set of unexecuted callers makes, and the exact call that clears it.
|
|
112
|
+
*
|
|
113
|
+
* GIVES the remedy rather than describing it. A sentence naming only the path and
|
|
114
|
+
* the phase produced a call missing arguments `skyramp_execute_test` requires, so the
|
|
115
|
+
* agent spent a round discovering which and could not always recover. The
|
|
116
|
+
* `unresolved-module` finding is the model: it hands over the literal header line to
|
|
117
|
+
* write, not a description of one. Here every value the server holds is printed per
|
|
118
|
+
* file, and the one it does not — the repository's own command for the file — is
|
|
119
|
+
* covered by the same instruction `skyramp_actions` gives for a before-run
|
|
120
|
+
* (`runTestCall`), so the two gates never describe the same call two ways.
|
|
121
|
+
*
|
|
122
|
+
* The arguments common to every caller are stated once above the list; a wall of
|
|
123
|
+
* eight calls × six arguments is its own way of being unreadable. A value is never
|
|
124
|
+
* dropped to shorten it.
|
|
125
|
+
*
|
|
126
|
+
* NOTHING HERE IS ABOUT A FAILING CALLER. This refuses on the ABSENCE of a result,
|
|
127
|
+
* which the agent can always remedy with one call per file. A caller that ran and
|
|
128
|
+
* failed is disclosed with its result and refuses nothing — see SKYR-4451.
|
|
129
|
+
*/
|
|
130
|
+
export function unrunCallerRefusal(owed, ctx) {
|
|
131
|
+
if (owed.length === 0)
|
|
132
|
+
return undefined;
|
|
133
|
+
const rel = (f) => path.relative(ctx.repoRoot, f) || path.basename(f);
|
|
134
|
+
return (`${owed.length} pre-existing generated test(s) call a shared helper this run CHANGED, and have not been run since the change — ` +
|
|
135
|
+
`Testbot will not ship a helper edit it has not exercised against the tests that already call it. ` +
|
|
136
|
+
`A changed selector or a changed request reaches every caller at once and appears in no calling test's diff.\n` +
|
|
137
|
+
`Run each file below through skyramp_execute_test with phase: "after", stateFile: "${ctx.stateFile}", repository: "${ctx.repository}": ${runTestCall()}. ` +
|
|
138
|
+
`That records the execution and clears this. Each line gives the file's arguments as \`<absolute path> <language> <framework> <test type>\`:\n` +
|
|
139
|
+
owed
|
|
140
|
+
.map((o) => ` - ${rel(o.file)} · calls ${o.helpers.map((h) => h.helper).join(", ")}\n` +
|
|
141
|
+
` \`${o.file} ${o.language} ${o.framework} ${o.testType}\``)
|
|
142
|
+
.join("\n") +
|
|
143
|
+
`\nDo NOT remove imports, inline a helper back, or restore a caller from HEAD to satisfy this check — un-sharing the helpers is the tamper path, not the fix.`);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* When the module last changed, in epoch milliseconds — the "since" every caller's
|
|
147
|
+
* execution is measured against.
|
|
148
|
+
*
|
|
149
|
+
* A DELETED module has no mtime of its own, and answering `0` for it accepted any
|
|
150
|
+
* execution ever recorded — including one from before the deletion, which exercised
|
|
151
|
+
* an import that still resolved and proves nothing about the one that no longer does.
|
|
152
|
+
* Its directory's mtime is the timestamp to use: POSIX updates it when an entry is
|
|
153
|
+
* removed, so it is the moment the module disappeared. Later writes to that directory
|
|
154
|
+
* only push it later, which is stricter rather than wrong, and an execution recorded
|
|
155
|
+
* after the deletion still clears it.
|
|
156
|
+
*
|
|
157
|
+
* Only a module whose directory ALSO cannot be read falls back to `0`, which fails
|
|
158
|
+
* open the way the retrofit gate does: an unreadable timestamp must not demand a
|
|
159
|
+
* re-run of every caller for ever.
|
|
160
|
+
*/
|
|
161
|
+
async function moduleMtime(file) {
|
|
162
|
+
try {
|
|
163
|
+
return (await fs.stat(file)).mtimeMs;
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
/* gone: its directory records when */
|
|
167
|
+
}
|
|
168
|
+
try {
|
|
169
|
+
return (await fs.stat(path.dirname(file))).mtimeMs;
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
return 0;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { UnifiedAnalysisState } from "../../utils/AnalysisStateManager.js";
|
|
2
|
+
import type { ChangedHelperEntry } from "../../types/ReuseOutcome.js";
|
|
3
|
+
import { type ChangedHelperView } from "./helper-callers.js";
|
|
4
|
+
import { type CallerExecution } from "./caller-gate.js";
|
|
5
|
+
/** Everything the report needs about the helpers this run changed: the views by the
|
|
6
|
+
* canonical directory of the module, the execution recorded for each caller by its
|
|
7
|
+
* canonical path, and the refusal if any caller the run can execute has not been.
|
|
8
|
+
* Built once per report. */
|
|
9
|
+
export interface ChangedHelperReport {
|
|
10
|
+
byDir: Map<string, ChangedHelperView[]>;
|
|
11
|
+
executions: Map<string, CallerExecution>;
|
|
12
|
+
/** Set when a caller the run CAN execute has not been run against the changed
|
|
13
|
+
* module. Unbounded by design, like the retrofit gate's: the remedy is one tool
|
|
14
|
+
* call the agent can always make, and only callers it can make it for are named. */
|
|
15
|
+
refusal?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface ChangedHelperReportParams {
|
|
18
|
+
/** Every checkout the run may have written to — the same roots the working-tree
|
|
19
|
+
* scan lists changes for. A module is looked for in each. */
|
|
20
|
+
roots: Array<string | undefined>;
|
|
21
|
+
/** Absolute paths of everything this run changed, across every checkout: a caller
|
|
22
|
+
* among them is delivered, and its own row carries its execution. */
|
|
23
|
+
changedFilesAbs: string[];
|
|
24
|
+
state: Pick<UnifiedAnalysisState, "existingTests" | "testExecutions">;
|
|
25
|
+
/** The primary checkout, for the repository-relative names the refusal shows. */
|
|
26
|
+
repoRoot: string;
|
|
27
|
+
/** The section `skyramp_execute_test` records a run under, and the state file it
|
|
28
|
+
* appends to. Both required: a remedy naming an empty repository is a call the
|
|
29
|
+
* tool rejects, which is the round trip the remedy text exists to prevent. */
|
|
30
|
+
repository: string;
|
|
31
|
+
stateFile: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The changed-helper set for a whole report: every shared module that differs from
|
|
35
|
+
* HEAD in any root, the helpers changed in it, and the tests beside it that call them.
|
|
36
|
+
*
|
|
37
|
+
* FROM GIT, NOT FROM THE REUSE RECORDS. What makes a file a module is the header it
|
|
38
|
+
* carried at HEAD, and what makes it changed is `git diff`; who changed it does not
|
|
39
|
+
* enter into it. So a helper the maintenance phase repaired with an UPDATE on the
|
|
40
|
+
* module — the path where nothing else runs `incumbent-signature` — is found the same
|
|
41
|
+
* way as one the reuse pass rewrote, and its callers are executed and disclosed the
|
|
42
|
+
* same way. Every git call in the feature is here.
|
|
43
|
+
*
|
|
44
|
+
* Views are keyed by the module's canonical DIRECTORY, and a row looks its own up by
|
|
45
|
+
* the directory of its spec: a module belongs to every row beside it, and to none
|
|
46
|
+
* elsewhere. Never throws: a root git cannot answer for contributes nothing, logged.
|
|
47
|
+
*/
|
|
48
|
+
export declare function deriveChangedHelpers(params: ChangedHelperReportParams): Promise<ChangedHelperReport>;
|
|
49
|
+
/** The views a row carries: those of its spec's directory. */
|
|
50
|
+
export declare function changedHelpersForRow(report: ChangedHelperReport, specPath: string): ChangedHelperView[];
|
|
51
|
+
/**
|
|
52
|
+
* What the report states about the helpers this run changed.
|
|
53
|
+
*
|
|
54
|
+
* The DISCLOSURE predicate, separate from the gate's by construction: `callers` is
|
|
55
|
+
* present only when the caller set was established, so the report says "callers
|
|
56
|
+
* unknown" rather than asserting a list it is not sure of. A caller with no
|
|
57
|
+
* execution was not run, which the row shows as an omission rather than as a pass.
|
|
58
|
+
* The module is named per helper, repository-relative, as the orphan entry names
|
|
59
|
+
* its: a delivery can carry several modules, and the same helper name in two of them
|
|
60
|
+
* must not read as one entry.
|
|
61
|
+
*/
|
|
62
|
+
export declare function discloseChangedHelpers(views: ChangedHelperView[], executions: ChangedHelperReport["executions"]): ChangedHelperEntry[];
|