@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.
- 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/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 +24 -2
- package/build/recommendation/verifierContracts.js +32 -4
- package/build/recommendation/verifiers/changedLinesClaimed.d.ts +17 -0
- package/build/recommendation/verifiers/changedLinesClaimed.js +88 -0
- 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/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 +6 -0
- package/build/tools/code-refactor/reuse-record-store.d.ts +52 -0
- package/build/tools/code-refactor/reuse-record-store.js +126 -0
- package/build/tools/code-refactor/reuse-state.d.ts +18 -6
- package/build/tools/code-refactor/reuse-state.js +78 -131
- package/build/tools/code-refactor/utils-verify-gates.js +3 -3
- 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 +91 -29
- 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 +70 -22
- package/build/types/RepositoryAnalysis.d.ts +20 -20
- package/build/types/ReuseOutcome.d.ts +96 -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/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/head.d.ts +48 -0
- package/build/utils/utils-verify/head.js +115 -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/verify.d.ts +9 -3
- package/build/utils/utils-verify/verify.js +26 -12
- 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/traceRecordingBackend.js +303 -150
- 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.BAkLd5DX.js → index.ZYuEFHsi.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html +2 -2
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{uiMode.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 +2 -2
- 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
|
@@ -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[];
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import * as path from "path";
|
|
2
|
+
import { logger } from "../../utils/logger.js";
|
|
3
|
+
import { changedSinceHead, contentAtHead, UTILS_FILE_HEADER_RE, UTILS_LANGUAGE_SPECS, } from "../../utils/utils-verify/index.js";
|
|
4
|
+
import { canonPath, utilsFileLabel } from "./reuse-state.js";
|
|
5
|
+
import { changedHelperCallers, } from "./helper-callers.js";
|
|
6
|
+
import { changedHelperGate, unrunCallerRefusal, } from "./caller-gate.js";
|
|
7
|
+
/**
|
|
8
|
+
* The changed-helper set for a whole report: every shared module that differs from
|
|
9
|
+
* HEAD in any root, the helpers changed in it, and the tests beside it that call them.
|
|
10
|
+
*
|
|
11
|
+
* FROM GIT, NOT FROM THE REUSE RECORDS. What makes a file a module is the header it
|
|
12
|
+
* carried at HEAD, and what makes it changed is `git diff`; who changed it does not
|
|
13
|
+
* enter into it. So a helper the maintenance phase repaired with an UPDATE on the
|
|
14
|
+
* module — the path where nothing else runs `incumbent-signature` — is found the same
|
|
15
|
+
* way as one the reuse pass rewrote, and its callers are executed and disclosed the
|
|
16
|
+
* same way. Every git call in the feature is here.
|
|
17
|
+
*
|
|
18
|
+
* Views are keyed by the module's canonical DIRECTORY, and a row looks its own up by
|
|
19
|
+
* the directory of its spec: a module belongs to every row beside it, and to none
|
|
20
|
+
* elsewhere. Never throws: a root git cannot answer for contributes nothing, logged.
|
|
21
|
+
*/
|
|
22
|
+
export async function deriveChangedHelpers(params) {
|
|
23
|
+
const byDir = new Map();
|
|
24
|
+
const modulesByDir = new Map();
|
|
25
|
+
const extensions = Object.values(UTILS_LANGUAGE_SPECS).flatMap((s) => s.extensions);
|
|
26
|
+
for (const root of new Set(params.roots.filter((r) => !!r))) {
|
|
27
|
+
let candidates;
|
|
28
|
+
try {
|
|
29
|
+
// Deleted AND modified, renames as deletions: a rename or a stripped header
|
|
30
|
+
// leaves every caller importing a module that is no longer a module.
|
|
31
|
+
candidates = await changedSinceHead(root, { filter: "DM" });
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
logger.warning("Could not list the files this run changed — shared helpers it changed there will not be reported", { root, error: String(err) });
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
// `candidates` are canonical (changedSinceHead's contract), so the directory
|
|
38
|
+
// key is taken straight off them.
|
|
39
|
+
for (const file of candidates) {
|
|
40
|
+
if (!extensions.some((e) => file.toLowerCase().endsWith(e)))
|
|
41
|
+
continue;
|
|
42
|
+
// The header AT HEAD is what made it a shared module. A test the run edited or
|
|
43
|
+
// deleted is not one, and neither is a module this run created. Read once, and
|
|
44
|
+
// handed on: the derivation compares against this same content.
|
|
45
|
+
const head = await contentAtHead(file, root);
|
|
46
|
+
if (head === undefined || !UTILS_FILE_HEADER_RE.test(head))
|
|
47
|
+
continue;
|
|
48
|
+
const dir = path.dirname(file);
|
|
49
|
+
modulesByDir.set(dir, [...(modulesByDir.get(dir) ?? []), { file, head }]);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
for (const [dir, modules] of modulesByDir) {
|
|
53
|
+
try {
|
|
54
|
+
const views = await changedHelperCallers(modules);
|
|
55
|
+
if (views.length > 0)
|
|
56
|
+
byDir.set(dir, views);
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
logger.warning("Could not establish which shared helpers a run changed — its callers will not be named or run", { dir, error: String(err) });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const views = [...byDir.values()].flat();
|
|
63
|
+
if (views.length === 0)
|
|
64
|
+
return { byDir, executions: new Map() };
|
|
65
|
+
const gate = await changedHelperGate({
|
|
66
|
+
views,
|
|
67
|
+
state: params.state,
|
|
68
|
+
changedFilesAbs: params.changedFilesAbs,
|
|
69
|
+
});
|
|
70
|
+
const refusal = unrunCallerRefusal(gate.owed, {
|
|
71
|
+
repoRoot: params.repoRoot,
|
|
72
|
+
repository: params.repository,
|
|
73
|
+
stateFile: params.stateFile,
|
|
74
|
+
});
|
|
75
|
+
return {
|
|
76
|
+
byDir,
|
|
77
|
+
executions: gate.executions,
|
|
78
|
+
...(refusal ? { refusal } : {}),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
/** The views a row carries: those of its spec's directory. */
|
|
82
|
+
export function changedHelpersForRow(report, specPath) {
|
|
83
|
+
return report.byDir.get(path.dirname(canonPath(specPath))) ?? [];
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* What the report states about the helpers this run changed.
|
|
87
|
+
*
|
|
88
|
+
* The DISCLOSURE predicate, separate from the gate's by construction: `callers` is
|
|
89
|
+
* present only when the caller set was established, so the report says "callers
|
|
90
|
+
* unknown" rather than asserting a list it is not sure of. A caller with no
|
|
91
|
+
* execution was not run, which the row shows as an omission rather than as a pass.
|
|
92
|
+
* The module is named per helper, repository-relative, as the orphan entry names
|
|
93
|
+
* its: a delivery can carry several modules, and the same helper name in two of them
|
|
94
|
+
* must not read as one entry.
|
|
95
|
+
*/
|
|
96
|
+
export function discloseChangedHelpers(views, executions) {
|
|
97
|
+
return views.map((v) => ({
|
|
98
|
+
utilsFile: utilsFileLabel([v.utilsFile]),
|
|
99
|
+
helper: v.helper,
|
|
100
|
+
change: v.change,
|
|
101
|
+
...(v.callers
|
|
102
|
+
? {
|
|
103
|
+
callers: v.callers.map((f) => {
|
|
104
|
+
const e = executions.get(canonPath(f));
|
|
105
|
+
return {
|
|
106
|
+
file: path.basename(f),
|
|
107
|
+
...(e?.execution ? { execution: e.execution } : {}),
|
|
108
|
+
...(e?.deliveredByThisRun ? { deliveredByThisRun: true } : {}),
|
|
109
|
+
};
|
|
110
|
+
}),
|
|
111
|
+
}
|
|
112
|
+
: {}),
|
|
113
|
+
}));
|
|
114
|
+
}
|