@skyramp/mcp 0.3.6 → 0.3.7
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/prompts/code-reuse.js +17 -2
- package/build/prompts/enhance-assertions/sharedAssertionRules.js +1 -1
- package/build/prompts/modularization/ui-test-modularization.js +9 -6
- package/build/prompts/pom-aware-code-reuse.js +1 -1
- package/build/prompts/shared-helper-policy.js +5 -5
- package/build/services/TestGenerationService.js +15 -1
- package/build/tools/code-refactor/assertion-state.d.ts +91 -0
- package/build/tools/code-refactor/assertion-state.js +375 -0
- package/build/tools/code-refactor/codeReuseTool.js +6 -4
- package/build/tools/code-refactor/enhanceAssertionsTool.js +73 -18
- package/build/tools/code-refactor/retrofit-state.d.ts +53 -0
- package/build/tools/code-refactor/retrofit-state.js +162 -0
- package/build/tools/code-refactor/reuse-outcome.d.ts +7 -0
- package/build/tools/code-refactor/reuse-state.d.ts +9 -0
- package/build/tools/code-refactor/reuse-state.js +42 -4
- package/build/tools/code-refactor/utils-verify-gates.js +69 -15
- package/build/tools/executeSkyrampTestTool.js +19 -14
- package/build/tools/submitReportTool.js +81 -8
- package/build/types/AssertionOutcome.d.ts +68 -0
- package/build/types/AssertionOutcome.js +1 -0
- package/build/types/ReuseOutcome.d.ts +16 -0
- package/build/types/TestTypes.d.ts +4 -0
- package/build/types/TestTypes.js +8 -0
- package/build/types/TestbotReport.d.ts +13 -0
- package/build/types/index.d.ts +1 -1
- package/build/utils/AnalysisStateManager.d.ts +20 -7
- package/build/utils/assertion-verify/api-shared-lints.d.ts +5 -0
- package/build/utils/assertion-verify/api-shared-lints.js +315 -0
- package/build/utils/assertion-verify/contract-lints.d.ts +3 -0
- package/build/utils/assertion-verify/contract-lints.js +87 -0
- package/build/utils/assertion-verify/format.d.ts +5 -0
- package/build/utils/assertion-verify/format.js +65 -0
- package/build/utils/assertion-verify/helper-imports.d.ts +6 -0
- package/build/utils/assertion-verify/helper-imports.js +178 -0
- package/build/utils/assertion-verify/index.d.ts +3 -0
- package/build/utils/assertion-verify/index.js +7 -0
- package/build/utils/assertion-verify/integration-lints.d.ts +3 -0
- package/build/utils/assertion-verify/integration-lints.js +36 -0
- package/build/utils/assertion-verify/js-regex-blank.d.ts +1 -0
- package/build/utils/assertion-verify/js-regex-blank.js +153 -0
- package/build/utils/assertion-verify/lint-types.d.ts +33 -0
- package/build/utils/assertion-verify/lint-types.js +57 -0
- package/build/utils/assertion-verify/marker.d.ts +27 -0
- package/build/utils/assertion-verify/marker.js +61 -0
- package/build/utils/assertion-verify/metrics.d.ts +30 -0
- package/build/utils/assertion-verify/metrics.js +341 -0
- package/build/utils/assertion-verify/python-strip.d.ts +6 -0
- package/build/utils/assertion-verify/python-strip.js +75 -0
- package/build/utils/assertion-verify/strip-dispatch.d.ts +19 -0
- package/build/utils/assertion-verify/strip-dispatch.js +42 -0
- package/build/utils/assertion-verify/ui-lints.d.ts +8 -0
- package/build/utils/assertion-verify/ui-lints.js +244 -0
- package/build/utils/assertion-verify/verify.d.ts +61 -0
- package/build/utils/assertion-verify/verify.js +215 -0
- package/build/utils/featureFlags.d.ts +12 -2
- package/build/utils/featureFlags.js +33 -3
- package/build/utils/reportVerification.d.ts +4 -0
- package/build/utils/reportVerification.js +32 -4
- package/build/utils/utils-verify/allow.d.ts +22 -4
- package/build/utils/utils-verify/allow.js +8 -2
- package/build/utils/utils-verify/call-sites.d.ts +40 -1
- package/build/utils/utils-verify/call-sites.js +196 -30
- package/build/utils/utils-verify/importers.d.ts +31 -0
- package/build/utils/utils-verify/importers.js +78 -0
- package/build/utils/utils-verify/index.d.ts +1 -0
- package/build/utils/utils-verify/index.js +1 -0
- package/build/utils/utils-verify/language-spec.d.ts +13 -2
- package/build/utils/utils-verify/language-spec.js +12 -2
- package/build/utils/utils-verify/parse.d.ts +31 -3
- package/build/utils/utils-verify/parse.js +190 -9
- package/build/utils/utils-verify/retrofit-equivalence.d.ts +43 -0
- package/build/utils/utils-verify/retrofit-equivalence.js +218 -0
- package/build/utils/utils-verify/stage.d.ts +6 -0
- package/build/utils/utils-verify/stage.js +12 -2
- package/build/utils/utils-verify/verify.d.ts +54 -4
- package/build/utils/utils-verify/verify.js +224 -12
- package/node_modules/playwright/node_modules/playwright-core/lib/generated/injectedScriptSource.js +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{codeMirrorModule-CZfp96qZ.js → codeMirrorModule-LNgEKtdV.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{defaultSettingsView-gpLo02E0.js → defaultSettingsView-Bwr1eMKC.js} +135 -135
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{index.Bq1r1URj.js → index.-Id052Lr.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.VEfqi1qN.js → uiMode.BPopbasy.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/injectedScriptSource.ts +1 -1
- package/node_modules/playwright/package.json +1 -1
- package/package.json +2 -2
- package/build/tools/code-refactor/enhance-state.d.ts +0 -49
- package/build/tools/code-refactor/enhance-state.js +0 -109
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { escapeRegExp } from "../regex.js";
|
|
2
|
+
import { strippedSources } from "./strip-dispatch.js";
|
|
3
|
+
import { balancedCloseIndex, identifierRe, inScope, interpolationRe, lineOfOffset, } from "./lint-types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Deterministic UI (Playwright) lints — the machine-checkable slice of the D5
|
|
6
|
+
* rubric's UI dims:
|
|
7
|
+
* critical_assertions → expect import source, pageerror listener asserted,
|
|
8
|
+
* dead response captures, repeated elements w/o count
|
|
9
|
+
* computed_values → tautologies here; exact-vs-visibility is enforced by
|
|
10
|
+
* the strength score + weak-additions gate in verify.ts
|
|
11
|
+
* post_edit → unasserted trailing state-changing action
|
|
12
|
+
*/
|
|
13
|
+
const PLAYWRIGHT_EXPECT_IMPORT_RES = [
|
|
14
|
+
/import\s*(?:type\s*)?\{([^}]*)\}\s*from\s*['"]@playwright\/test['"]/g,
|
|
15
|
+
/(?:const|let|var)\s*\{([^}]*)\}\s*=\s*require\s*\(\s*['"]@playwright\/test['"]\s*\)/g,
|
|
16
|
+
];
|
|
17
|
+
/** `expect` imported from @playwright/test — banned flatly by the UI rules. */
|
|
18
|
+
function lintPlaywrightExpectImport(commentless) {
|
|
19
|
+
for (const re of PLAYWRIGHT_EXPECT_IMPORT_RES) {
|
|
20
|
+
re.lastIndex = 0;
|
|
21
|
+
let m;
|
|
22
|
+
while ((m = re.exec(commentless)) !== null) {
|
|
23
|
+
if (/\bexpect\b/.test(m[1])) {
|
|
24
|
+
return [
|
|
25
|
+
{
|
|
26
|
+
rule: "expect-import-source",
|
|
27
|
+
severity: "hard",
|
|
28
|
+
line: lineOfOffset(commentless, m.index),
|
|
29
|
+
message: "`expect` is imported from '@playwright/test'.",
|
|
30
|
+
remediation: "Import `expect` from '@skyramp/skyramp' instead (keep `test` on the playwright line).",
|
|
31
|
+
},
|
|
32
|
+
];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
// Any page-ish handle counts: specs keep a raw handle (`rawPage`) for
|
|
39
|
+
// assertions because expect() rejects the SkyrampPage proxy.
|
|
40
|
+
const PAGEERROR_LISTENER_RE = /[\w$]*[Pp]age\s*\.\s*on\s*\(\s*['"]pageerror['"]/;
|
|
41
|
+
const ERRORS_LENGTH_ASSERT_RES = [
|
|
42
|
+
/toHaveLength\s*\(\s*0\s*\)/,
|
|
43
|
+
// `(?:,[^)]*)?` admits expect's optional message argument.
|
|
44
|
+
/\.length\s*(?:,[^)]*)?\)\s*\.\s*(?:toBe|toEqual|toStrictEqual)\s*\(\s*0\s*\)/,
|
|
45
|
+
/(?:toEqual|toStrictEqual)\s*\(\s*\[\s*\]\s*\)/,
|
|
46
|
+
];
|
|
47
|
+
/** pageerror listener registered but its errors array never asserted (hard);
|
|
48
|
+
* listener absent entirely stays warn-only — helpers/fixtures may register it,
|
|
49
|
+
* and forcing a structural insertion conflicts with autoApply's
|
|
50
|
+
* only-add-assertion-lines constraint. Promotion pending run evidence. */
|
|
51
|
+
function lintPageErrors(commentless) {
|
|
52
|
+
const listener = PAGEERROR_LISTENER_RE.exec(commentless);
|
|
53
|
+
if (!listener) {
|
|
54
|
+
return [
|
|
55
|
+
{
|
|
56
|
+
rule: "pageerror-listener-absent",
|
|
57
|
+
severity: "warn",
|
|
58
|
+
message: "No `page.on('pageerror', ...)` listener found in this file.",
|
|
59
|
+
remediation: "Register the listener before the first navigation and assert the collected errors array is empty at the end of the test.",
|
|
60
|
+
},
|
|
61
|
+
];
|
|
62
|
+
}
|
|
63
|
+
if (ERRORS_LENGTH_ASSERT_RES.some((re) => re.test(commentless)))
|
|
64
|
+
return [];
|
|
65
|
+
return [
|
|
66
|
+
{
|
|
67
|
+
rule: "pageerror-unasserted",
|
|
68
|
+
severity: "hard",
|
|
69
|
+
line: lineOfOffset(commentless, listener.index),
|
|
70
|
+
message: "A `page.on('pageerror', ...)` listener collects errors, but the errors array is never asserted.",
|
|
71
|
+
remediation: "Add `expect(errors).toHaveLength(0)` (or equivalent) at the end of the test.",
|
|
72
|
+
},
|
|
73
|
+
];
|
|
74
|
+
}
|
|
75
|
+
// Codegen emits BOTH shapes: direct (`const r = await page.waitForResponse(…)`)
|
|
76
|
+
// and promise-then-await (`const p = page.waitForResponse(…); …; const r = await p;`).
|
|
77
|
+
// Matching only the direct form made this lint a no-op on real codegen output.
|
|
78
|
+
const DIRECT_RESPONSE_BINDING_RE = /(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*await\s+[\w$]*[Pp]age\s*\.\s*waitForResponse\s*\(/g;
|
|
79
|
+
const PROMISE_BINDING_RE = /(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*[\w$]*[Pp]age\s*\.\s*waitForResponse\s*\(/g;
|
|
80
|
+
/** A captured `waitForResponse` response that is never referenced again.
|
|
81
|
+
* Takes the raw comment-stripped source too: template-literal interpolations
|
|
82
|
+
* are blanked in `stripped`, so a use like `` `order-${resp.headers()...}` ``
|
|
83
|
+
* is only visible there.
|
|
84
|
+
*
|
|
85
|
+
* WARN, not hard — measured against skyramp's gen_reference UI goldens,
|
|
86
|
+
* pristine codegen emits 3–9 unused `const rN = await rPromiseN` captures per
|
|
87
|
+
* file (63 across the corpus): many are synchronization-only waits whose
|
|
88
|
+
* bodies aren't meaningfully assertable, and a hard finding per capture would
|
|
89
|
+
* dominate every UI verify loop with no marker escape. The rubric pressure to
|
|
90
|
+
* assert captured responses stays via this warn plus the strength gate. */
|
|
91
|
+
function lintDeadResponseCaptures(stripped, commentless) {
|
|
92
|
+
const findings = [];
|
|
93
|
+
// Response bindings from the direct shape…
|
|
94
|
+
const bindings = [];
|
|
95
|
+
let m;
|
|
96
|
+
DIRECT_RESPONSE_BINDING_RE.lastIndex = 0;
|
|
97
|
+
while ((m = DIRECT_RESPONSE_BINDING_RE.exec(stripped)) !== null) {
|
|
98
|
+
const close = balancedCloseIndex(stripped, m.index + m[0].length - 1);
|
|
99
|
+
bindings.push({
|
|
100
|
+
varName: m[1],
|
|
101
|
+
index: m.index,
|
|
102
|
+
end: close === -1 ? m.index + m[0].length : close + 1,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
// …plus the promise-then-await shape: resolve `const r = await <promiseVar>`
|
|
106
|
+
// for every promise binding, and treat a promise that is never referenced at
|
|
107
|
+
// all as a dead capture itself.
|
|
108
|
+
// (The promise pattern cannot match the direct form — `await ` between `=`
|
|
109
|
+
// and the page handle breaks its contiguous match — so no overlap handling.)
|
|
110
|
+
PROMISE_BINDING_RE.lastIndex = 0;
|
|
111
|
+
while ((m = PROMISE_BINDING_RE.exec(stripped)) !== null) {
|
|
112
|
+
const promiseVar = m[1];
|
|
113
|
+
const close = balancedCloseIndex(stripped, m.index + m[0].length - 1);
|
|
114
|
+
const restStart = close === -1 ? m.index + m[0].length : close + 1;
|
|
115
|
+
const rest = stripped.slice(restStart);
|
|
116
|
+
const awaited = new RegExp(`(?:const|let|var)\\s+([A-Za-z_$][\\w$]*)\\s*=\\s*await\\s+${escapeRegExp(promiseVar)}(?![\\w$])`).exec(rest);
|
|
117
|
+
if (awaited) {
|
|
118
|
+
bindings.push({
|
|
119
|
+
varName: awaited[1],
|
|
120
|
+
index: restStart + awaited.index,
|
|
121
|
+
end: restStart + awaited.index + awaited[0].length,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
else if (!identifierRe(promiseVar).test(rest)) {
|
|
125
|
+
bindings.push({ varName: promiseVar, index: m.index, end: restStart });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
for (const { varName, index, end } of bindings) {
|
|
129
|
+
const rest = stripped.slice(end);
|
|
130
|
+
const usedInInterpolation = interpolationRe(varName, "typescript").test(commentless.slice(end));
|
|
131
|
+
// Identifier-boundary lookarounds, not \b: `\b` breaks on `$`-prefixed
|
|
132
|
+
// names ($resp), silently turning this into a guaranteed false positive.
|
|
133
|
+
if (!identifierRe(varName).test(rest) && !usedInInterpolation) {
|
|
134
|
+
findings.push({
|
|
135
|
+
rule: "dead-response-capture",
|
|
136
|
+
severity: "warn",
|
|
137
|
+
line: lineOfOffset(stripped, index),
|
|
138
|
+
message: `\`${varName}\` captures a network response via waitForResponse but is never used.`,
|
|
139
|
+
remediation: `Assert at least one status, body, or header field on \`${varName}\` (e.g. \`expect(${varName}.status()).toBe(200)\`).`,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return findings;
|
|
144
|
+
}
|
|
145
|
+
// getByText('X') asserted toHaveText('X') with the identical literal. Restricted
|
|
146
|
+
// to getByText — getByRole+name asserted with the same text is a sanctioned
|
|
147
|
+
// pattern in the rules' own examples. Misses chains with links in between
|
|
148
|
+
// (.first() etc.) by design: a miss is fine, a false positive is not.
|
|
149
|
+
// The literal groups exclude `\` from the any-char alternative so the two
|
|
150
|
+
// alternatives are disjoint — the ambiguous form backtracks exponentially on
|
|
151
|
+
// escape-heavy literals (ReDoS that would hang the whole MCP server).
|
|
152
|
+
const TAUTOLOGY_RE = /(?:getByText|get_by_text)\s*\(\s*(['"`])((?:\\.|(?!\1)[^\\\n])*)\1[^)]*\)\s*\)?\s*\.\s*(?:toHaveText|toContainText|to_have_text|to_contain_text)\s*\(\s*(['"`])((?:\\.|(?!\3)[^\\\n])*)\3/g;
|
|
153
|
+
function lintTautologies(commentless, opts) {
|
|
154
|
+
const findings = [];
|
|
155
|
+
let m;
|
|
156
|
+
TAUTOLOGY_RE.lastIndex = 0;
|
|
157
|
+
while ((m = TAUTOLOGY_RE.exec(commentless)) !== null) {
|
|
158
|
+
const line = lineOfOffset(commentless, m.index);
|
|
159
|
+
if (m[2] === m[4] && inScope(line, opts)) {
|
|
160
|
+
findings.push({
|
|
161
|
+
rule: "tautological-assertion",
|
|
162
|
+
severity: "hard",
|
|
163
|
+
line,
|
|
164
|
+
message: `Tautology: element located by text ${JSON.stringify(m[2])} is asserted to have that same text.`,
|
|
165
|
+
remediation: "Assert a different knowable property (count, value, attribute), or locate the element by a structural selector and keep the text assertion.",
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return findings;
|
|
170
|
+
}
|
|
171
|
+
// Signals that the page renders repeated elements (rows, list items, nth/first
|
|
172
|
+
// access), which the rubric expects paired with an exact toHaveCount(N).
|
|
173
|
+
const REPEATED_ELEMENT_RE = /getByRole\s*\(\s*['"](?:row|listitem|cell|option)['"]|\.\s*nth\s*\(|\.\s*first\s*\(\s*\)|\.\s*last\s*\(\s*\)/;
|
|
174
|
+
const HAS_COUNT_RE = /toHaveCount\s*\(|to_have_count\s*\(/;
|
|
175
|
+
/** Repeated/collection elements are exercised but no exact count is asserted.
|
|
176
|
+
* Warn-only: whether a list truly renders is not knowable from the spec alone
|
|
177
|
+
* (the D5 judge has a matching N/A condition for the same reason). */
|
|
178
|
+
function lintRepeatedElementsWithoutCount(commentless) {
|
|
179
|
+
const repeated = REPEATED_ELEMENT_RE.exec(commentless);
|
|
180
|
+
if (!repeated || HAS_COUNT_RE.test(commentless))
|
|
181
|
+
return [];
|
|
182
|
+
return [
|
|
183
|
+
{
|
|
184
|
+
rule: "repeated-elements-without-count",
|
|
185
|
+
severity: "warn",
|
|
186
|
+
line: lineOfOffset(commentless, repeated.index),
|
|
187
|
+
message: "The test works with repeated/collection elements but never asserts an exact count.",
|
|
188
|
+
remediation: "Add `toHaveCount(N)` (N from the trace or rendered DOM) plus at least one per-item content assertion.",
|
|
189
|
+
},
|
|
190
|
+
];
|
|
191
|
+
}
|
|
192
|
+
// State-changing interactions; a test whose LAST such action is never followed
|
|
193
|
+
// by an assertion leaves the post-action state unverified (post_edit dim).
|
|
194
|
+
const ACTION_RE = /\.\s*(?:click|fill|press|check|uncheck|selectOption|select_option|set_input_files|setInputFiles)\s*\(/g;
|
|
195
|
+
const EXPECT_SITE_RE = /\bexpect(?:\s*\.\s*soft)?\s*\(/g;
|
|
196
|
+
function lintUnassertedTrailingAction(stripped) {
|
|
197
|
+
let lastAction = -1;
|
|
198
|
+
let m;
|
|
199
|
+
ACTION_RE.lastIndex = 0;
|
|
200
|
+
while ((m = ACTION_RE.exec(stripped)) !== null)
|
|
201
|
+
lastAction = m.index;
|
|
202
|
+
if (lastAction === -1)
|
|
203
|
+
return [];
|
|
204
|
+
let lastExpect = -1;
|
|
205
|
+
EXPECT_SITE_RE.lastIndex = 0;
|
|
206
|
+
while ((m = EXPECT_SITE_RE.exec(stripped)) !== null)
|
|
207
|
+
lastExpect = m.index;
|
|
208
|
+
if (lastExpect > lastAction)
|
|
209
|
+
return [];
|
|
210
|
+
return [
|
|
211
|
+
{
|
|
212
|
+
rule: "unasserted-trailing-action",
|
|
213
|
+
severity: "warn",
|
|
214
|
+
line: lineOfOffset(stripped, lastAction),
|
|
215
|
+
message: "The test's final state-changing action is never followed by an assertion — the updated state goes unverified.",
|
|
216
|
+
remediation: "After the last action, assert the visible updated outcome (`toHaveText`/`toHaveValue`/`toBeChecked`/`toHaveCount`) reflecting the edit.",
|
|
217
|
+
},
|
|
218
|
+
];
|
|
219
|
+
}
|
|
220
|
+
/** UI (Playwright) lints. Python (playwright-python) UI specs deliberately get
|
|
221
|
+
* the site-scoped lints only: the structural rules' patterns and accept-lists
|
|
222
|
+
* are JS/TS-specific (imports, waitForResponse shapes, expect matcher accept
|
|
223
|
+
* forms), and porting them without a python golden corpus to validate against
|
|
224
|
+
* would recreate the false-block class review round 2 removed. Pinned by test. */
|
|
225
|
+
export function lintUiSpec(raw, language, opts) {
|
|
226
|
+
if (language === "java")
|
|
227
|
+
return [];
|
|
228
|
+
const maintenance = opts?.scopeLines !== undefined;
|
|
229
|
+
const { commentless, stripped } = strippedSources(raw, language);
|
|
230
|
+
const findings = [];
|
|
231
|
+
if (language !== "python" && !maintenance) {
|
|
232
|
+
// Import lint is generation-only too: maintenance updates existing customer
|
|
233
|
+
// specs, which legitimately import expect from @playwright/test (the repo
|
|
234
|
+
// may not even depend on @skyramp/skyramp), and the maintenance scope
|
|
235
|
+
// forbids touching imports.
|
|
236
|
+
findings.push(...lintPlaywrightExpectImport(commentless));
|
|
237
|
+
findings.push(...lintPageErrors(commentless));
|
|
238
|
+
findings.push(...lintDeadResponseCaptures(stripped, commentless));
|
|
239
|
+
findings.push(...lintRepeatedElementsWithoutCount(commentless));
|
|
240
|
+
findings.push(...lintUnassertedTrailingAction(stripped));
|
|
241
|
+
}
|
|
242
|
+
findings.push(...lintTautologies(commentless, opts));
|
|
243
|
+
return findings;
|
|
244
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { type AssertionMetrics } from "./metrics.js";
|
|
2
|
+
import type { LintFinding } from "./lint-types.js";
|
|
3
|
+
export type AssertionEnhanceType = "generation" | "maintenance";
|
|
4
|
+
/** Snapshot taken when the enhancement instructions were handed out. */
|
|
5
|
+
export interface AssertionBaseline {
|
|
6
|
+
fileSha256: string;
|
|
7
|
+
count?: number;
|
|
8
|
+
strengthScore?: number;
|
|
9
|
+
fingerprints?: string[];
|
|
10
|
+
/** Copy of the generated file at hand-out time (run artifact dir). */
|
|
11
|
+
baselineFilePath?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface AssertionVerifyResult {
|
|
14
|
+
ok: boolean;
|
|
15
|
+
enhanceType: AssertionEnhanceType;
|
|
16
|
+
/** False when verifying stateless (no baseline) — differential gates skipped. */
|
|
17
|
+
baselinePresent: boolean;
|
|
18
|
+
/** Maintenance verified with an empty assertion diff — nothing was added,
|
|
19
|
+
* changed, or removed, so there was nothing to verify. */
|
|
20
|
+
maintenanceNoAssertionChanges?: boolean;
|
|
21
|
+
hashUnchanged: boolean;
|
|
22
|
+
/** Baseline assertions whose SUBJECT lost all coverage — genuine removals
|
|
23
|
+
* (sample of 5). Replaced assertions (subject still asserted) never appear. */
|
|
24
|
+
removedFingerprints?: string[];
|
|
25
|
+
/** Total genuine removals — lets the report say "showing 5 of N" so the
|
|
26
|
+
* agent restores everything in ONE pass instead of discovering the rest
|
|
27
|
+
* on the next verify round. */
|
|
28
|
+
removedCount?: number;
|
|
29
|
+
/** Baseline assertions superseded by a new assertion on the same subject. */
|
|
30
|
+
replacedCount?: number;
|
|
31
|
+
/** Baseline assertions whose subject now lives in a locally imported helper
|
|
32
|
+
* file (modularization) — sanctioned, never counted as removed. */
|
|
33
|
+
movedToHelperCount?: number;
|
|
34
|
+
/** Copy of the generated file at hand-out time, for diff/restore remediation. */
|
|
35
|
+
baselineFilePath?: string;
|
|
36
|
+
/** Current − baseline strength score; undefined without a baseline. */
|
|
37
|
+
strengthDelta?: number;
|
|
38
|
+
strengthGateFailed: boolean;
|
|
39
|
+
/** Assertions were added, but every one is existence/visibility-tier — the
|
|
40
|
+
* computed-values rubric wants exact matchers. Runs in generation AND
|
|
41
|
+
* maintenance (it requires additions to exist, so value-only maintenance
|
|
42
|
+
* fixes cannot trip it). Marker-clearable, like the strength gate. */
|
|
43
|
+
weakAdditionsOnly: boolean;
|
|
44
|
+
hardFindings: LintFinding[];
|
|
45
|
+
warnings: LintFinding[];
|
|
46
|
+
markerReason?: string;
|
|
47
|
+
currentMetrics?: AssertionMetrics;
|
|
48
|
+
}
|
|
49
|
+
export declare function sha256Of(content: string): string;
|
|
50
|
+
/**
|
|
51
|
+
* Deterministic verification that the enhancement instructions were applied:
|
|
52
|
+
* hash delta, no assertions removed, rubric lints clean, and (generation only)
|
|
53
|
+
* strength strictly increased unless a documented `assertions complete` marker
|
|
54
|
+
* declines it. Reads the file as delivered — throws if it cannot be read.
|
|
55
|
+
*/
|
|
56
|
+
export declare function verifyAssertionEnhancement(params: {
|
|
57
|
+
testFile: string;
|
|
58
|
+
testType: string;
|
|
59
|
+
enhanceType: AssertionEnhanceType;
|
|
60
|
+
baseline?: AssertionBaseline;
|
|
61
|
+
}): Promise<AssertionVerifyResult>;
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { createHash } from "crypto";
|
|
2
|
+
import { readFile } from "fs/promises";
|
|
3
|
+
import { computeAssertionMetrics, detectAssertionLanguage, subjectOf, } from "./metrics.js";
|
|
4
|
+
import { lintUiSpec } from "./ui-lints.js";
|
|
5
|
+
import { lintIntegrationTest } from "./integration-lints.js";
|
|
6
|
+
import { lintContractTest } from "./contract-lints.js";
|
|
7
|
+
import { findAssertionsCompleteMarker, findUnparsableAssertionsComplete, markerCommentToken, } from "./marker.js";
|
|
8
|
+
import { importedHelperSubjects } from "./helper-imports.js";
|
|
9
|
+
export function sha256Of(content) {
|
|
10
|
+
return createHash("sha256").update(content).digest("hex");
|
|
11
|
+
}
|
|
12
|
+
/** Current sites whose fingerprint occurs more often now than in the baseline
|
|
13
|
+
* multiset — the assertions ADDED since hand-out (includes the new side of a
|
|
14
|
+
* replacement). Powers maintenance lint scoping and the weak-additions gate. */
|
|
15
|
+
function addedSites(current, baselineFingerprints) {
|
|
16
|
+
const budget = new Map();
|
|
17
|
+
for (const fp of baselineFingerprints)
|
|
18
|
+
budget.set(fp, (budget.get(fp) ?? 0) + 1);
|
|
19
|
+
const added = [];
|
|
20
|
+
for (const site of current.sites) {
|
|
21
|
+
const left = budget.get(site.fingerprint) ?? 0;
|
|
22
|
+
if (left > 0)
|
|
23
|
+
budget.set(site.fingerprint, left - 1);
|
|
24
|
+
else
|
|
25
|
+
added.push(site);
|
|
26
|
+
}
|
|
27
|
+
return added;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Deterministic verification that the enhancement instructions were applied:
|
|
31
|
+
* hash delta, no assertions removed, rubric lints clean, and (generation only)
|
|
32
|
+
* strength strictly increased unless a documented `assertions complete` marker
|
|
33
|
+
* declines it. Reads the file as delivered — throws if it cannot be read.
|
|
34
|
+
*/
|
|
35
|
+
export async function verifyAssertionEnhancement(params) {
|
|
36
|
+
const { testFile, testType, enhanceType, baseline } = params;
|
|
37
|
+
const content = await readFile(testFile, "utf8");
|
|
38
|
+
const language = detectAssertionLanguage(testFile);
|
|
39
|
+
const hashUnchanged = baseline !== undefined && sha256Of(content) === baseline.fileSha256;
|
|
40
|
+
// Unknown language: only the hash gate is computable — everything else fails open.
|
|
41
|
+
if (language === undefined) {
|
|
42
|
+
return {
|
|
43
|
+
ok: !(hashUnchanged && enhanceType === "generation"),
|
|
44
|
+
enhanceType,
|
|
45
|
+
baselinePresent: baseline !== undefined,
|
|
46
|
+
hashUnchanged,
|
|
47
|
+
strengthGateFailed: false,
|
|
48
|
+
weakAdditionsOnly: false,
|
|
49
|
+
hardFindings: [],
|
|
50
|
+
warnings: [],
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
const metrics = computeAssertionMetrics(content, language);
|
|
54
|
+
const countDecreasedBy = baseline?.count !== undefined
|
|
55
|
+
? Math.max(0, baseline.count - metrics.count)
|
|
56
|
+
: 0;
|
|
57
|
+
// Correlate what disappeared since the baseline. A missing fingerprint whose
|
|
58
|
+
// SUBJECT is still asserted (by any current site) was REPLACED — a sanctioned
|
|
59
|
+
// enhancement (usually weak matcher → strong). A subject asserted inside a
|
|
60
|
+
// locally imported helper file was MOVED (modularization) — also sanctioned.
|
|
61
|
+
// A KNOWN subject that lost all coverage counts as removed regardless of the
|
|
62
|
+
// raw count (a same-count remove-A-add-B edit must not slip through);
|
|
63
|
+
// unrecoverable subjects stay conservative and count only when the raw count
|
|
64
|
+
// actually decreased.
|
|
65
|
+
let removedFingerprints;
|
|
66
|
+
let removedCount;
|
|
67
|
+
let replacedCount;
|
|
68
|
+
let movedToHelperCount;
|
|
69
|
+
if (baseline?.fingerprints !== undefined) {
|
|
70
|
+
const current = new Map();
|
|
71
|
+
for (const fp of metrics.fingerprints) {
|
|
72
|
+
current.set(fp, (current.get(fp) ?? 0) + 1);
|
|
73
|
+
}
|
|
74
|
+
const missing = baseline.fingerprints.filter((fp) => {
|
|
75
|
+
const left = current.get(fp) ?? 0;
|
|
76
|
+
if (left > 0) {
|
|
77
|
+
current.set(fp, left - 1);
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
return true;
|
|
81
|
+
});
|
|
82
|
+
const currentSubjects = new Set(metrics.fingerprints.map(subjectOf).filter((s) => s !== undefined));
|
|
83
|
+
let removed = missing.filter((fp) => {
|
|
84
|
+
const subject = subjectOf(fp);
|
|
85
|
+
if (subject === undefined)
|
|
86
|
+
return countDecreasedBy > 0;
|
|
87
|
+
return !currentSubjects.has(subject);
|
|
88
|
+
});
|
|
89
|
+
// Only pay the helper-file scan when something actually looks removed.
|
|
90
|
+
if (removed.length > 0) {
|
|
91
|
+
const helperSubjects = await importedHelperSubjects(testFile, content);
|
|
92
|
+
if (helperSubjects.size > 0) {
|
|
93
|
+
const stillRemoved = removed.filter((fp) => {
|
|
94
|
+
const subject = subjectOf(fp);
|
|
95
|
+
return subject === undefined || !helperSubjects.has(subject);
|
|
96
|
+
});
|
|
97
|
+
movedToHelperCount = removed.length - stillRemoved.length;
|
|
98
|
+
removed = stillRemoved;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
replacedCount = missing.length - removed.length - (movedToHelperCount ?? 0);
|
|
102
|
+
if (removed.length > 0) {
|
|
103
|
+
removedFingerprints = removed.slice(0, 5);
|
|
104
|
+
removedCount = removed.length;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const strengthDelta = baseline?.strengthScore !== undefined
|
|
108
|
+
? metrics.strengthScore - baseline.strengthScore
|
|
109
|
+
: undefined;
|
|
110
|
+
const added = baseline?.fingerprints !== undefined
|
|
111
|
+
? addedSites(metrics, baseline.fingerprints)
|
|
112
|
+
: undefined;
|
|
113
|
+
// Maintenance scopes site lints to assertions added since the baseline; the
|
|
114
|
+
// baseline fingerprints are what make that diff computable.
|
|
115
|
+
const scopeLines = enhanceType === "maintenance" && added !== undefined
|
|
116
|
+
? new Set(added.map((s) => s.line))
|
|
117
|
+
: undefined;
|
|
118
|
+
const lintOpts = scopeLines !== undefined ? { scopeLines } : undefined;
|
|
119
|
+
// Per-test-type lints; Java is never linted or strength-gated — count+hash
|
|
120
|
+
// only. Test types with no lint module (smoke/fuzz/load/e2e — the hand-out
|
|
121
|
+
// path refuses them) get NO lints rather than silently inheriting
|
|
122
|
+
// integration's: those rules were not written for them.
|
|
123
|
+
const findings = testType === "ui"
|
|
124
|
+
? lintUiSpec(content, language, lintOpts)
|
|
125
|
+
: testType === "contract"
|
|
126
|
+
? lintContractTest(content, language, lintOpts)
|
|
127
|
+
: testType === "integration"
|
|
128
|
+
? lintIntegrationTest(content, language, lintOpts)
|
|
129
|
+
: [];
|
|
130
|
+
const marker = findAssertionsCompleteMarker(content, testFile, language);
|
|
131
|
+
const hardFindings = findings.filter((f) => f.severity === "hard");
|
|
132
|
+
const commentToken = markerCommentToken(language);
|
|
133
|
+
for (const line of findUnparsableAssertionsComplete(content, language)) {
|
|
134
|
+
hardFindings.push({
|
|
135
|
+
rule: "malformed-assertions-complete-marker",
|
|
136
|
+
severity: "hard",
|
|
137
|
+
message: `Unreadable marker: \`${line}\``,
|
|
138
|
+
remediation: `Use \`${commentToken} assertions complete: <test-file basename> — <reason>\` (this language's comment token; em dash, or a hyphen with a space on BOTH sides), with a real reason.`,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
// Removals are NEVER marker-clearable: a marker that excused them would let
|
|
142
|
+
// "delete the failing assertion + one comment line" verify green — the exact
|
|
143
|
+
// case this gate exists to catch. The sanctioned coverage-moves (verify
|
|
144
|
+
// before modularization per the mandated steps; passed-verdict skip at
|
|
145
|
+
// execute time) don't need the exemption, and a post-modularize failure is
|
|
146
|
+
// always fixable by re-adding direct assertions beside the helper calls.
|
|
147
|
+
// (Derived, not stored: removedFingerprints !== undefined IS the gate.)
|
|
148
|
+
const removalGateFailed = removedFingerprints !== undefined;
|
|
149
|
+
// A decline marker is accepted on a GENERATION file only when work
|
|
150
|
+
// actually happened (something added or replaced). Without this, a
|
|
151
|
+
// marker-only comment edit was a full bypass: the comment changes the hash,
|
|
152
|
+
// the marker cleared both strength-family gates, and an untouched file
|
|
153
|
+
// trips nothing else — the exact "tests already have the key assertions"
|
|
154
|
+
// no-op the gate exists to catch (review finding on ae8b397e). The marker
|
|
155
|
+
// can excuse INSUFFICIENT strengthening ("only existence is knowable"); it
|
|
156
|
+
// cannot excuse ABSENT strengthening. Maintenance keeps marker semantics
|
|
157
|
+
// unchanged (its strength gates don't run; value-only fixes are legitimate).
|
|
158
|
+
const markerAccepted = marker !== undefined &&
|
|
159
|
+
(enhanceType !== "generation" ||
|
|
160
|
+
(added?.length ?? 0) > 0 ||
|
|
161
|
+
(replacedCount ?? 0) > 0);
|
|
162
|
+
const strengthGateFailed = enhanceType === "generation" &&
|
|
163
|
+
language !== "java" &&
|
|
164
|
+
strengthDelta !== undefined &&
|
|
165
|
+
strengthDelta <= 0 &&
|
|
166
|
+
!markerAccepted;
|
|
167
|
+
// Computed-values rubric: additions must not be exclusively existence/
|
|
168
|
+
// visibility-tier. A replacement's new side counts as an added site, so a
|
|
169
|
+
// weak→strong upgrade satisfies this. Marker-clearable (strength family).
|
|
170
|
+
// Unlike the strength-increase gate this runs in MAINTENANCE too: it is
|
|
171
|
+
// conditional on additions existing, so a value-only drift fix (no added
|
|
172
|
+
// sites) can never trip it — only genuinely weak new assertions do.
|
|
173
|
+
const weakAdditionsOnly = language !== "java" &&
|
|
174
|
+
added !== undefined &&
|
|
175
|
+
added.length > 0 &&
|
|
176
|
+
added.every((s) => s.weight === 1) &&
|
|
177
|
+
!markerAccepted;
|
|
178
|
+
// The hash gate ("byte-identical → nothing applied") is generation-only: in
|
|
179
|
+
// maintenance the file edits precede the enhance call, and "no assertion
|
|
180
|
+
// changes needed" is a legitimate outcome — the assertion-diff below is what
|
|
181
|
+
// decides whether there is anything to verify at all.
|
|
182
|
+
const hashGateFailed = hashUnchanged && enhanceType === "generation";
|
|
183
|
+
// Maintenance with an EMPTY assertion diff (nothing added, changed, or
|
|
184
|
+
// removed since hand-out): there is no assertion work to verify — pass, and
|
|
185
|
+
// let the report say so honestly.
|
|
186
|
+
const maintenanceNoAssertionChanges = enhanceType === "maintenance" &&
|
|
187
|
+
baseline?.fingerprints !== undefined &&
|
|
188
|
+
(added?.length ?? 0) === 0 &&
|
|
189
|
+
replacedCount === 0 &&
|
|
190
|
+
(removedCount ?? 0) === 0 &&
|
|
191
|
+
(movedToHelperCount ?? 0) === 0;
|
|
192
|
+
return {
|
|
193
|
+
ok: !hashGateFailed &&
|
|
194
|
+
!removalGateFailed &&
|
|
195
|
+
hardFindings.length === 0 &&
|
|
196
|
+
!strengthGateFailed &&
|
|
197
|
+
!weakAdditionsOnly,
|
|
198
|
+
maintenanceNoAssertionChanges,
|
|
199
|
+
enhanceType,
|
|
200
|
+
baselinePresent: baseline !== undefined,
|
|
201
|
+
hashUnchanged,
|
|
202
|
+
removedFingerprints,
|
|
203
|
+
removedCount,
|
|
204
|
+
replacedCount,
|
|
205
|
+
movedToHelperCount,
|
|
206
|
+
baselineFilePath: baseline?.baselineFilePath,
|
|
207
|
+
strengthDelta,
|
|
208
|
+
strengthGateFailed,
|
|
209
|
+
weakAdditionsOnly,
|
|
210
|
+
hardFindings,
|
|
211
|
+
warnings: findings.filter((f) => f.severity === "warn"),
|
|
212
|
+
markerReason: markerAccepted ? marker?.reason : undefined,
|
|
213
|
+
currentMetrics: metrics,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* Each helper in this module reads a `SKYRAMP_FEATURE_*` env var and returns
|
|
5
5
|
* a boolean. These helpers treat a flag as ON only when the env var is
|
|
6
6
|
* exactly `"1"`; anything else (unset, empty, "0", "true", "false", etc.) is
|
|
7
|
-
* treated as OFF.
|
|
7
|
+
* treated as OFF. The utils-reuse flag is additionally ON for the orgs in
|
|
8
|
+
* `UTILS_REUSE_DEFAULT_ORGS` regardless of the env vars.
|
|
8
9
|
*/
|
|
9
10
|
/**
|
|
10
11
|
* Gates BOTH consumer-side contract tests AND the SDK's "default mode"
|
|
@@ -55,6 +56,14 @@ export declare function isOneClickEnabled(): boolean;
|
|
|
55
56
|
* Gates the POM-aware path of `skyramp_reuse_code`
|
|
56
57
|
* (SKYRAMP_FEATURE_POM_REUSE=1).
|
|
57
58
|
*
|
|
59
|
+
* Deliberately NOT defaulted on for `UTILS_REUSE_DEFAULT_ORGS`: for UI tests
|
|
60
|
+
* the POM path supersedes the modularize-first flow (see
|
|
61
|
+
* `isModularizeFirstTarget` in utils/reuseRouting.ts), so an org-level POM
|
|
62
|
+
* default would stop those orgs' UI tests from ever seeding a SkyrampUtils
|
|
63
|
+
* file — the very thing the utils org default exists to provide (SKYR-4290).
|
|
64
|
+
* And an explicit-"0"-override escape hatch cannot work either, because
|
|
65
|
+
* testbot normalizes an unset flag to a literal "0" before forwarding.
|
|
66
|
+
*
|
|
58
67
|
* When OFF (default), TypeScript/JavaScript + Playwright targets are treated
|
|
59
68
|
* like every other target: `skyramp_reuse_code` returns the SkyrampUtils
|
|
60
69
|
* consolidation prompt, and none of the POM machinery runs — no deterministic
|
|
@@ -68,7 +77,8 @@ export declare function isOneClickEnabled(): boolean;
|
|
|
68
77
|
export declare function isPomReuseEnabled(): boolean;
|
|
69
78
|
/**
|
|
70
79
|
* Gates SkyrampUtils code reuse for integration tests on the testbot path, and
|
|
71
|
-
* for UI tests whenever the POM path is off (SKYRAMP_FEATURE_UTILS_REUSE=1
|
|
80
|
+
* for UI tests whenever the POM path is off (SKYRAMP_FEATURE_UTILS_REUSE=1,
|
|
81
|
+
* or a `UTILS_REUSE_DEFAULT_ORGS` org).
|
|
72
82
|
*
|
|
73
83
|
* When OFF (default), the testbot prompt does not set `codeReuse` on
|
|
74
84
|
* `skyramp_integration_test_generation` and its post-generation
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* Each helper in this module reads a `SKYRAMP_FEATURE_*` env var and returns
|
|
5
5
|
* a boolean. These helpers treat a flag as ON only when the env var is
|
|
6
6
|
* exactly `"1"`; anything else (unset, empty, "0", "true", "false", etc.) is
|
|
7
|
-
* treated as OFF.
|
|
7
|
+
* treated as OFF. The utils-reuse flag is additionally ON for the orgs in
|
|
8
|
+
* `UTILS_REUSE_DEFAULT_ORGS` regardless of the env vars.
|
|
8
9
|
*/
|
|
9
10
|
/**
|
|
10
11
|
* Gates BOTH consumer-side contract tests AND the SDK's "default mode"
|
|
@@ -63,6 +64,14 @@ export function isOneClickEnabled() {
|
|
|
63
64
|
* Gates the POM-aware path of `skyramp_reuse_code`
|
|
64
65
|
* (SKYRAMP_FEATURE_POM_REUSE=1).
|
|
65
66
|
*
|
|
67
|
+
* Deliberately NOT defaulted on for `UTILS_REUSE_DEFAULT_ORGS`: for UI tests
|
|
68
|
+
* the POM path supersedes the modularize-first flow (see
|
|
69
|
+
* `isModularizeFirstTarget` in utils/reuseRouting.ts), so an org-level POM
|
|
70
|
+
* default would stop those orgs' UI tests from ever seeding a SkyrampUtils
|
|
71
|
+
* file — the very thing the utils org default exists to provide (SKYR-4290).
|
|
72
|
+
* And an explicit-"0"-override escape hatch cannot work either, because
|
|
73
|
+
* testbot normalizes an unset flag to a literal "0" before forwarding.
|
|
74
|
+
*
|
|
66
75
|
* When OFF (default), TypeScript/JavaScript + Playwright targets are treated
|
|
67
76
|
* like every other target: `skyramp_reuse_code` returns the SkyrampUtils
|
|
68
77
|
* consolidation prompt, and none of the POM machinery runs — no deterministic
|
|
@@ -78,7 +87,8 @@ export function isPomReuseEnabled() {
|
|
|
78
87
|
}
|
|
79
88
|
/**
|
|
80
89
|
* Gates SkyrampUtils code reuse for integration tests on the testbot path, and
|
|
81
|
-
* for UI tests whenever the POM path is off (SKYRAMP_FEATURE_UTILS_REUSE=1
|
|
90
|
+
* for UI tests whenever the POM path is off (SKYRAMP_FEATURE_UTILS_REUSE=1,
|
|
91
|
+
* or a `UTILS_REUSE_DEFAULT_ORGS` org).
|
|
82
92
|
*
|
|
83
93
|
* When OFF (default), the testbot prompt does not set `codeReuse` on
|
|
84
94
|
* `skyramp_integration_test_generation` and its post-generation
|
|
@@ -97,5 +107,25 @@ export function isPomReuseEnabled() {
|
|
|
97
107
|
* yields the single reuse pass it did before the feature existed.
|
|
98
108
|
*/
|
|
99
109
|
export function isUtilsReuseEnabled() {
|
|
100
|
-
return process.env.SKYRAMP_FEATURE_UTILS_REUSE === "1";
|
|
110
|
+
return (process.env.SKYRAMP_FEATURE_UTILS_REUSE === "1" || isUtilsReuseDefaultOrg());
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* GitHub orgs where SkyrampUtils code reuse is on by default, without
|
|
114
|
+
* requiring the SKYRAMP_FEATURE_UTILS_REUSE env var. Compared
|
|
115
|
+
* case-insensitively (GitHub owner handles are case-insensitive, but
|
|
116
|
+
* GITHUB_REPOSITORY_OWNER preserves display casing). Because this ORs with
|
|
117
|
+
* the env check, these orgs cannot turn the feature off by setting the env
|
|
118
|
+
* var to "0". POM reuse has no org default — see `isPomReuseEnabled`.
|
|
119
|
+
*
|
|
120
|
+
* "Skyramp-HandsOn" is the Wipro / IntelliAssure pilot org (SKYR-4286).
|
|
121
|
+
*/
|
|
122
|
+
const UTILS_REUSE_DEFAULT_ORGS = ["skyramp-handson"];
|
|
123
|
+
function isUtilsReuseDefaultOrg() {
|
|
124
|
+
// GITHUB_REPOSITORY_OWNER is set on GitHub Actions runners; fall back to
|
|
125
|
+
// the owner half of GITHUB_REPOSITORY ("owner/repo") for environments that
|
|
126
|
+
// only set the latter.
|
|
127
|
+
const owner = process.env.GITHUB_REPOSITORY_OWNER ??
|
|
128
|
+
process.env.GITHUB_REPOSITORY?.split("/")[0] ??
|
|
129
|
+
"";
|
|
130
|
+
return UTILS_REUSE_DEFAULT_ORGS.includes(owner.toLowerCase());
|
|
101
131
|
}
|
|
@@ -24,6 +24,10 @@ import { DriftAction } from "../types/TestAnalysis.js";
|
|
|
24
24
|
* nothing rather than failing the whole check.
|
|
25
25
|
*/
|
|
26
26
|
export declare function listChangedFilesAcross(roots: Array<string | undefined>): Promise<string[]>;
|
|
27
|
+
/** Like listChangedFilesAcross, but ABSOLUTE: each changed path resolved against
|
|
28
|
+
* the root it came from, so a consumer never has to guess which checkout a
|
|
29
|
+
* repo-relative path belongs to. */
|
|
30
|
+
export declare function listChangedFilesAbs(roots: Array<string | undefined>): Promise<string[]>;
|
|
27
31
|
export declare function listChangedFiles(repoRoot: string): Promise<string[]>;
|
|
28
32
|
export interface UnchangedFileClaimsInput {
|
|
29
33
|
/** Absolute path of the primary repo checkout the report pertains to. */
|