@skyramp/mcp 0.4.0 → 0.4.1-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/prompts/enhance-assertions/contractProviderAssertionsPrompt.js +2 -1
- package/build/prompts/enhance-assertions/integrationAssertionsPrompt.js +2 -1
- package/build/prompts/enhance-assertions/sharedAssertionRules.d.ts +1 -1
- package/build/prompts/enhance-assertions/sharedAssertionRules.js +57 -22
- package/build/prompts/enhance-assertions/uiAssertionsPrompt.js +17 -9
- package/build/prompts/test-recommendation/diffExecutionPlan.js +0 -2
- package/build/prompts/test-recommendation/test-recommendation-prompt.js +11 -6
- package/build/prompts/testbot/testbot-prompts.js +19 -12
- package/build/recommendation/answers.d.ts +11 -7
- package/build/recommendation/answers.js +14 -10
- package/build/recommendation/pullRequestText.d.ts +18 -0
- package/build/recommendation/pullRequestText.js +31 -0
- package/build/recommendation/registerPlan.d.ts +9 -1
- package/build/recommendation/registerPlan.js +8 -1
- package/build/recommendation/runVerifiers.js +6 -0
- package/build/recommendation/types.d.ts +58 -0
- package/build/recommendation/verifierContracts.d.ts +108 -13
- package/build/recommendation/verifierContracts.js +147 -29
- package/build/recommendation/verifiers/coverage.d.ts +10 -0
- package/build/recommendation/verifiers/coverage.js +144 -22
- package/build/recommendation/verifiers/defects.d.ts +9 -0
- package/build/recommendation/verifiers/defects.js +117 -0
- package/build/recommendation/verifiers/deliveredMatchesPlan.d.ts +22 -0
- package/build/recommendation/verifiers/deliveredMatchesPlan.js +43 -0
- package/build/recommendation/verifiers/existingCoverage.js +53 -0
- package/build/recommendation/verifiers/expectedValueSourced.d.ts +14 -0
- package/build/recommendation/verifiers/expectedValueSourced.js +246 -0
- package/build/recommendation/verifiers/issueTraceability.d.ts +52 -0
- package/build/recommendation/verifiers/issueTraceability.js +197 -0
- package/build/recommendation/verifiers/requirementSourced.d.ts +2 -0
- package/build/recommendation/verifiers/requirementSourced.js +168 -0
- package/build/services/TestGenerationService.js +3 -1
- package/build/tools/code-refactor/codeReuseTool.js +1 -1
- package/build/tools/code-refactor/reuse-outcome.d.ts +1 -1
- package/build/tools/code-refactor/reuse-state.d.ts +85 -7
- package/build/tools/code-refactor/reuse-state.js +239 -34
- package/build/tools/code-refactor/utils-verify-gates.d.ts +5 -0
- package/build/tools/code-refactor/utils-verify-gates.js +103 -11
- package/build/tools/generate-tests/generateBatchScenarioRestTool.js +2 -1
- package/build/tools/submitReportTool.js +276 -39
- package/build/tools/test-management/actionsTool.js +5 -0
- package/build/tools/test-management/analyzeChangesTool.d.ts +53 -0
- package/build/tools/test-management/analyzeChangesTool.js +55 -2
- package/build/tools/test-management/registerTestPlanTool.d.ts +32 -17
- package/build/tools/test-management/registerTestPlanTool.js +105 -16
- package/build/types/ReuseOutcome.d.ts +73 -7
- package/build/types/TestAnalysis.d.ts +6 -0
- package/build/types/TestbotReport.d.ts +22 -4
- package/build/utils/AnalysisStateManager.d.ts +7 -1
- package/build/utils/AnalysisStateManager.js +5 -1
- package/build/utils/assertion-verify/api-shared-lints.js +118 -11
- package/build/utils/assertion-verify/format.js +2 -2
- package/build/utils/assertion-verify/helper-imports.d.ts +7 -0
- package/build/utils/assertion-verify/helper-imports.js +119 -27
- package/build/utils/assertion-verify/lint-types.d.ts +31 -2
- package/build/utils/assertion-verify/lint-types.js +66 -0
- package/build/utils/assertion-verify/metrics.d.ts +13 -0
- package/build/utils/assertion-verify/metrics.js +16 -0
- package/build/utils/assertion-verify/verify.d.ts +11 -6
- package/build/utils/assertion-verify/verify.js +56 -15
- package/build/utils/canonicalJson.d.ts +11 -0
- package/build/utils/canonicalJson.js +17 -0
- package/build/utils/utils-verify/action-key.d.ts +27 -0
- package/build/utils/utils-verify/action-key.js +292 -0
- package/build/utils/utils-verify/allow.d.ts +8 -1
- package/build/utils/utils-verify/allow.js +14 -1
- package/build/utils/utils-verify/call-sites.d.ts +76 -8
- package/build/utils/utils-verify/call-sites.js +256 -70
- package/build/utils/utils-verify/language-spec.d.ts +3 -2
- package/build/utils/utils-verify/parse.d.ts +22 -3
- package/build/utils/utils-verify/parse.js +123 -52
- package/build/utils/utils-verify/verify.d.ts +33 -3
- package/build/utils/utils-verify/verify.js +126 -12
- package/build/utils/workspaceAuth.d.ts +59 -19
- package/build/utils/workspaceAuth.js +228 -31
- package/package.json +1 -1
- package/plugin/prompts/generate-tests/execution-plan.md +3 -3
- package/plugin/prompts/generate-tests/generation.md +1 -0
- package/plugin/prompts/plan-tests.md +41 -15
- package/plugin/prompts/testbot-task1.md +3 -9
- package/build/prompts/testbot/planDeclarations.d.ts +0 -6
- package/build/prompts/testbot/planDeclarations.js +0 -9
- package/plugin/prompts/declaring-a-plan.md +0 -20
|
@@ -13,14 +13,15 @@ import { StateManager, runArtifactDir, getTestsRepoDir, resolveRunStatePath, } f
|
|
|
13
13
|
import { toolError, testFileMatches } from "../utils/utils.js";
|
|
14
14
|
import { isTestbotEnabled, } from "../utils/featureFlags.js";
|
|
15
15
|
import { answerFor, unknownAnswerObjections } from "../recommendation/answers.js";
|
|
16
|
-
import { checkDeliveredMatchesPlan } from "../recommendation/verifiers/deliveredMatchesPlan.js";
|
|
16
|
+
import { checkDeliveredMatchesPlan, checkMaintenanceDelivered } from "../recommendation/verifiers/deliveredMatchesPlan.js";
|
|
17
17
|
import { checkReportedCategoryMatchesPlan, checkRequirementConflictReported, } from "../recommendation/verifiers/reportedCategory.js";
|
|
18
|
+
import { checkDefectsReported, checkIssueTraceability } from "../recommendation/verifiers/issueTraceability.js";
|
|
18
19
|
import { checkExpectedOutcomeAfterExecution, } from "../recommendation/verifiers/expectedOutcome.js";
|
|
19
20
|
import { findInvalidSourceCitations, findUnchangedFileClaims, listChangedFiles, listChangedFilesAcross, listChangedFilesAbs, } from "../utils/reportVerification.js";
|
|
20
21
|
import { isPlanOnlyMode } from "../utils/planOnlyMode.js";
|
|
21
22
|
import { getReportLanguage, isEnforcedReportLanguage, findLanguageViolations, findLanguageNearMisses, reportLanguageDisplayName, } from "../utils/reportLanguage.js";
|
|
22
23
|
import { canonicalTestPath, findAssertionRecordByFileName, rederiveAssertionOutcome, } from "./code-refactor/assertion-state.js";
|
|
23
|
-
import {
|
|
24
|
+
import { rederiveReuse, REUSE_SUBMIT_MAX_REFUSALS, reuseChainSkipped, samePath, } from "./code-refactor/reuse-state.js";
|
|
24
25
|
import { retrofitGate, } from "./code-refactor/retrofit-state.js";
|
|
25
26
|
// Mirrors the tools wired to planGuard: UI and E2E are not gated at generation
|
|
26
27
|
// time, so gating them here would be a report-time-only surprise.
|
|
@@ -50,6 +51,22 @@ const MAINTENANCE_CHANGE_ACTIONS = new Set([
|
|
|
50
51
|
DriftAction.Regenerate,
|
|
51
52
|
DriftAction.Delete,
|
|
52
53
|
]);
|
|
54
|
+
// Narrower than the set above, and for a different question. That one asks whether a
|
|
55
|
+
// row touched a file; this asks whether the row left a test standing that a `maintains`
|
|
56
|
+
// claim can rest on. DELETE touched the file and removed the coverage with it, VERIFY
|
|
57
|
+
// and IGNORE never edited anything — none of the three corroborate a claim that an
|
|
58
|
+
// existing test covers a change.
|
|
59
|
+
const MAINTENANCE_COVERAGE_ACTIONS = new Set([
|
|
60
|
+
DriftAction.Update,
|
|
61
|
+
DriftAction.Regenerate,
|
|
62
|
+
]);
|
|
63
|
+
/** The maintenance rows a `maintains` claim may be checked against: an action that leaves
|
|
64
|
+
* a test standing, and an edit this run actually made. `reportOnly` is the second half —
|
|
65
|
+
* an external test's REGENERATE keeps its real action and touches no file, so counting it
|
|
66
|
+
* would credit a claim nothing backs. */
|
|
67
|
+
function rowsLeavingCoverage(rows) {
|
|
68
|
+
return (rows ?? []).filter((row) => row?.action !== undefined && MAINTENANCE_COVERAGE_ACTIONS.has(row.action) && row?.reportOnly !== true);
|
|
69
|
+
}
|
|
53
70
|
/** Objection ids for an untested declared change, `coverage:change:<id>`. The
|
|
54
71
|
* change table reads the agent's answer back off them. */
|
|
55
72
|
const CHANGE_OBJECTION_PREFIX = "coverage:change:";
|
|
@@ -322,7 +339,15 @@ const issueFoundSchema = z
|
|
|
322
339
|
plannedTestId: z
|
|
323
340
|
.string()
|
|
324
341
|
.optional()
|
|
325
|
-
.describe("The `plannedTestId` of the
|
|
342
|
+
.describe("The `plannedTestId` of the DELIVERED test that proves this issue. Your plan declares that test with `expected.outcome: fail`, so it stays red until the issue is fixed; a `requirement_conflict` test that asserts the requirement is declared the same way. A planned test you did not write proves nothing, and a test the plan declares green proves nothing either. " +
|
|
343
|
+
"This field is CONDITIONAL, not required on every bug: set it when such a test exists; set `defectId` instead when a plan defect traces the issue; leave both out when no test proves the issue. " +
|
|
344
|
+
"CHECKED on a `bug` entry: one that neither names a delivered red test here nor traces through `defectId` draws an objection you answer in one line."),
|
|
345
|
+
defectId: z
|
|
346
|
+
.string()
|
|
347
|
+
.optional()
|
|
348
|
+
.describe("The `id` of the plan defect this issue reports, from the `defects` list you registered with skyramp_register_test_plan. Spell it as that list spells it: an id the plan does not declare traces nothing. " +
|
|
349
|
+
"Set it on the `category: bug` entry that reports the defect. A `defectId` on a `lint`, `type` or `config` entry does not report the defect — the report renders those in its own Configuration Errors section. " +
|
|
350
|
+
"CHECKED: every plan defect has a `bug` issue that names it, and a `bug` entry with no `plannedTestId` traces through this defect when a delivered test that expects to fail cites it, or when you answered `defects:untested:<id>` at plan time. Leave it out for an issue the plan did not declare."),
|
|
326
351
|
sourceFile: citationString.describe("Path of the application file whose code is missing or wrong, relative to the repository root (e.g. 'src/crud/products.py'). " +
|
|
327
352
|
"REQUIRED when category is 'bug'. " +
|
|
328
353
|
"For code that is MISSING — an unmounted router, an unregistered route, an import never added — name the file where the line should be, not the file that defines what is unmounted. " +
|
|
@@ -484,7 +509,6 @@ export const additionalRecommendationSchema = z.preprocess(stripNullGroundingFie
|
|
|
484
509
|
// `targetElements: null` is its normal state rather than a lost capture.
|
|
485
510
|
}
|
|
486
511
|
}));
|
|
487
|
-
// TODO(multi-repo maintenance): no `repository` field yet — see readData() TODO below.
|
|
488
512
|
const testMaintenanceSchema = z.object({
|
|
489
513
|
testType: z.nativeEnum(TestType).describe("Type of test."),
|
|
490
514
|
endpoint: z
|
|
@@ -514,6 +538,10 @@ const testMaintenanceSchema = z.object({
|
|
|
514
538
|
"For failing runs: failure name and one-line root cause, e.g. " +
|
|
515
539
|
"'FAILED test_foo — check_schema fails, order_id=1 has discount from prior PATCH test'. " +
|
|
516
540
|
"Empty string for VERIFY/IGNORE/DELETE entries where no after-execution was run."),
|
|
541
|
+
// Server-populated from the verdict's own state section, falling back to the
|
|
542
|
+
// run's primary repo. Always set: a consumer that has to treat absence as
|
|
543
|
+
// "probably the primary" cannot tell a single-repo row from a mis-stamped one.
|
|
544
|
+
repository: z.string(),
|
|
517
545
|
// Server-populated from stateFile execution records — never supplied by the LLM.
|
|
518
546
|
beforeStatus: z.nativeEnum(TestExecutionStatus),
|
|
519
547
|
afterStatus: z.nativeEnum(TestExecutionStatus),
|
|
@@ -592,7 +620,7 @@ function computeReportMetrics(params) {
|
|
|
592
620
|
*
|
|
593
621
|
* The counts are RE-DERIVED from the delivered spec rather than read back from
|
|
594
622
|
* state: the execution fix-up can restore `<testFile>.raw.bak` over it afterwards
|
|
595
|
-
* by plain `cp`, which this tool never sees. See
|
|
623
|
+
* by plain `cp`, which this tool never sees. See rederiveReuse.
|
|
596
624
|
*
|
|
597
625
|
* Tests the reuse tool never ran for and specs whose outcome cannot be re-derived
|
|
598
626
|
* get nothing — which consumers already treat as "no reuse summary" — with one
|
|
@@ -619,7 +647,12 @@ async function attachAssertionOutcome(test, outcomes, checkouts) {
|
|
|
619
647
|
return test;
|
|
620
648
|
return { ...test, assertions: await rederiveAssertionOutcome(record) };
|
|
621
649
|
}
|
|
622
|
-
async function attachReuseOutcome(test, outcomes, handOffs, retrofits
|
|
650
|
+
async function attachReuseOutcome(test, outcomes, handOffs, retrofits,
|
|
651
|
+
// A blocking verdict measured on the delivered files is collected here rather
|
|
652
|
+
// than written into the row: the caller refuses the report on it. Required, not
|
|
653
|
+
// defaulted — a caller that forgot it would ship the report with no refusal and
|
|
654
|
+
// no compiler error. See the refusal below the row map.
|
|
655
|
+
blocking) {
|
|
623
656
|
// POM records describe browser specs; a basename collision with an API test's
|
|
624
657
|
// fileName must not attach them there. A utils-path record is attachable anywhere.
|
|
625
658
|
const record = outcomes?.[path.basename(test.fileName)];
|
|
@@ -629,7 +662,10 @@ async function attachReuseOutcome(test, outcomes, handOffs, retrofits = []) {
|
|
|
629
662
|
const found = record && typeMatches && (test.testType === TestType.UI || record.utils)
|
|
630
663
|
? record
|
|
631
664
|
: undefined;
|
|
632
|
-
const
|
|
665
|
+
const rederived = found ? await rederiveReuse(found) : undefined;
|
|
666
|
+
const derived = rederived?.outcome;
|
|
667
|
+
if (rederived?.blocking)
|
|
668
|
+
blocking.push(rederived.blocking);
|
|
633
669
|
// Pre-existing generated tests this spec's reuse pass rewired (SKYR-4276 A4): the
|
|
634
670
|
// report names each with its recorded execution, so a reviewer sees that the
|
|
635
671
|
// module became a dependency of code they already owned — and that it still runs.
|
|
@@ -672,6 +708,13 @@ function attachVideoPath(row, videos) {
|
|
|
672
708
|
videoPath: videos?.[path.basename(row.testFilePath)]?.videoPath,
|
|
673
709
|
};
|
|
674
710
|
}
|
|
711
|
+
/** Two-space indent on every line, so a multi-line verdict reads as one bullet's body. */
|
|
712
|
+
function indent(text) {
|
|
713
|
+
return text
|
|
714
|
+
.split("\n")
|
|
715
|
+
.map((line) => ` ${line}`)
|
|
716
|
+
.join("\n");
|
|
717
|
+
}
|
|
675
718
|
function deduplicateById(items) {
|
|
676
719
|
const seen = new Set();
|
|
677
720
|
const result = [];
|
|
@@ -812,7 +855,10 @@ const PLANNABLE_TEST_TYPES = new Set([
|
|
|
812
855
|
TestType.E2E,
|
|
813
856
|
TestType.UI,
|
|
814
857
|
]);
|
|
815
|
-
function runPostExecutionChecks(plan, delivered, results, issues
|
|
858
|
+
function runPostExecutionChecks(plan, delivered, results, issues,
|
|
859
|
+
/** The plan-only lane delivers nothing, so there a planned test is proof
|
|
860
|
+
* enough for a bug. Passed in, not read here, so the checks stay pure. */
|
|
861
|
+
planOnly, maintained = []) {
|
|
816
862
|
const shipped = delivered
|
|
817
863
|
// A smoke, fuzz or load entry is filtered out rather than reported as
|
|
818
864
|
// unplanned: a plan cannot hold one, so the objection would name a mistake the
|
|
@@ -823,9 +869,16 @@ function runPostExecutionChecks(plan, delivered, results, issues) {
|
|
|
823
869
|
.map((plannedTestId) => ({ plannedTestId }));
|
|
824
870
|
return [
|
|
825
871
|
...runPostExecutionCheck("deliveredMatchesPlan", "deliveredMatchesPlan", () => checkDeliveredMatchesPlan(plan, shipped)),
|
|
872
|
+
// The maintenance mirror: a `maintains` entry credited coverage at plan time,
|
|
873
|
+
// so something has to check the file was edited.
|
|
874
|
+
...runPostExecutionCheck("deliveredMatchesPlan", "deliveredMatchesPlan:maintains", () => checkMaintenanceDelivered(plan, maintained)),
|
|
826
875
|
...runPostExecutionCheck("expectedOutcome", "expectedOutcome:executed", () => checkExpectedOutcomeAfterExecution(plan, collectExecutionOutcomes(delivered, results), issues)),
|
|
827
876
|
...runPostExecutionCheck("reportedCategory", "reportedCategory", () => checkReportedCategoryMatchesPlan(plan, delivered)),
|
|
828
877
|
...runPostExecutionCheck("requirementConflictReported", "requirementConflictReported", () => checkRequirementConflictReported(plan, delivered, issues)),
|
|
878
|
+
// Both halves of the review's accountability, and neither refuses the report:
|
|
879
|
+
// a bug with no test behind it, and a plan defect the report never mentions.
|
|
880
|
+
...runPostExecutionCheck("issueTraceability", "issueTraceability", () => checkIssueTraceability(plan, shipped, issues, { planOnly })),
|
|
881
|
+
...runPostExecutionCheck("defectsReported", "defects:unreported", () => checkDefectsReported(plan, issues)),
|
|
829
882
|
];
|
|
830
883
|
}
|
|
831
884
|
export function registerSubmitReportTool(server) {
|
|
@@ -1044,8 +1097,8 @@ export function registerSubmitReportTool(server) {
|
|
|
1044
1097
|
const stateManager = StateManager.fromStatePath(stateFile);
|
|
1045
1098
|
let stateData;
|
|
1046
1099
|
try {
|
|
1047
|
-
//
|
|
1048
|
-
//
|
|
1100
|
+
// Primary section only; related sections are read where testMaintenance
|
|
1101
|
+
// is built, so each row keeps its own repo's executions.
|
|
1049
1102
|
stateData = await stateManager.readData();
|
|
1050
1103
|
}
|
|
1051
1104
|
catch (err) {
|
|
@@ -1111,29 +1164,74 @@ export function registerSubmitReportTool(server) {
|
|
|
1111
1164
|
return errorResult;
|
|
1112
1165
|
}
|
|
1113
1166
|
}
|
|
1114
|
-
//
|
|
1115
|
-
//
|
|
1116
|
-
//
|
|
1117
|
-
|
|
1118
|
-
//
|
|
1119
|
-
|
|
1120
|
-
for
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1167
|
+
// Every section that can hold verdicts: primary, then each related repo.
|
|
1168
|
+
// Read once — the file can carry a large diffText and readRepoData would
|
|
1169
|
+
// re-parse it twice per repo.
|
|
1170
|
+
const fullMaintenanceState = await stateManager.readFullState();
|
|
1171
|
+
// The state file's root names the primary only when a caller declared one
|
|
1172
|
+
// (or wrote first). GITHUB_REPOSITORY is the trigger event's own repo, so
|
|
1173
|
+
// it names the primary even for a run that passed neither repo argument.
|
|
1174
|
+
const primaryRepository = fullMaintenanceState?.metadata?.repository?.trim() ||
|
|
1175
|
+
process.env.GITHUB_REPOSITORY?.trim() ||
|
|
1176
|
+
undefined;
|
|
1177
|
+
const maintenanceSections = [{ data: stateData }];
|
|
1178
|
+
for (const [repo, section] of Object.entries(fullMaintenanceState?.relatedRepos ?? {})) {
|
|
1179
|
+
// Only these two keys resolve back to the primary, already collected
|
|
1180
|
+
// above. Any other key — a case variant, or another name over the same
|
|
1181
|
+
// checkout — is a SEPARATE store, so skipping it would discard its
|
|
1182
|
+
// verdicts; duplicates collapse per row below instead.
|
|
1183
|
+
if (!repo.trim())
|
|
1184
|
+
continue;
|
|
1185
|
+
if (repo === primaryRepository)
|
|
1186
|
+
continue;
|
|
1187
|
+
const data = section?.data;
|
|
1188
|
+
if (data)
|
|
1189
|
+
maintenanceSections.push({ repository: repo, data });
|
|
1190
|
+
}
|
|
1191
|
+
// Stamp every row, primary included and single-repo runs included — as the
|
|
1192
|
+
// prompt already requires of the other report sections. A section without
|
|
1193
|
+
// its own name is the primary's, so it takes the run's primary repo.
|
|
1194
|
+
const repositoryOf = (section) => section.repository ?? primaryRepository ?? "";
|
|
1195
|
+
// Required field, so refuse rather than stamp a blank one: nothing named
|
|
1196
|
+
// the primary and the run is not on a GitHub runner either. Only sections
|
|
1197
|
+
// that actually produce rows need a name — a run with no verdicts at all
|
|
1198
|
+
// has nothing to attribute and must still be able to submit.
|
|
1199
|
+
if (!primaryRepository &&
|
|
1200
|
+
maintenanceSections.some((section) => !section.repository &&
|
|
1201
|
+
(section.data.maintenanceVerdicts?.length ?? 0) > 0)) {
|
|
1202
|
+
errorResult = toolError("Cannot name the repository for the maintenance rows: the stateFile records no primary repository " +
|
|
1203
|
+
"and GITHUB_REPOSITORY is unset. Pass `repository` (and `primaryRepository` in a multi-repo run) " +
|
|
1204
|
+
"to skyramp_analyze_changes, then re-run it.");
|
|
1205
|
+
return errorResult;
|
|
1124
1206
|
}
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1207
|
+
// Reject rather than throw: a non-array here, or a missing testFilePath
|
|
1208
|
+
// below, escapes the handler and loses the analytics event in its `finally`.
|
|
1209
|
+
const malformedSections = maintenanceSections
|
|
1210
|
+
.filter(({ data }) => data.maintenanceVerdicts !== undefined &&
|
|
1211
|
+
!Array.isArray(data.maintenanceVerdicts))
|
|
1212
|
+
.map(({ repository }) => repository ?? "the primary repo");
|
|
1213
|
+
if (malformedSections.length > 0) {
|
|
1214
|
+
errorResult = toolError(`stateFile has a non-array maintenanceVerdicts for: ${malformedSections.join(", ")}. ` +
|
|
1215
|
+
`Re-run skyramp_actions for that repository to rewrite the section.`);
|
|
1128
1216
|
return errorResult;
|
|
1129
1217
|
}
|
|
1130
|
-
|
|
1131
|
-
//
|
|
1132
|
-
//
|
|
1218
|
+
const badVerdicts = [];
|
|
1219
|
+
// action/testType/endpoint/description come entirely from each section's
|
|
1220
|
+
// maintenanceVerdicts (written by skyramp_actions right after drift analysis) —
|
|
1221
|
+
// the LLM never supplies testMaintenance directly, so there's nothing to
|
|
1222
|
+
// re-transcribe here.
|
|
1133
1223
|
// `pomFile` rides along per row and is stripped from the wire format below,
|
|
1134
1224
|
// like testFilePath. It cannot be keyed by spec path: a spec emits one UPDATE
|
|
1135
1225
|
// verdict PER page object, so a spec-keyed map keeps only the last.
|
|
1136
|
-
const rawMaintenance = (
|
|
1226
|
+
const rawMaintenance = maintenanceSections.flatMap((section) => (section.data.maintenanceVerdicts ?? [])
|
|
1227
|
+
.filter((v) => {
|
|
1228
|
+
if (v && typeof v.testFilePath === "string" && v.testFilePath.trim())
|
|
1229
|
+
return true;
|
|
1230
|
+
badVerdicts.push(section.repository ?? "the primary repo");
|
|
1231
|
+
return false;
|
|
1232
|
+
})
|
|
1233
|
+
.map((v) => ({
|
|
1234
|
+
repository: repositoryOf(section),
|
|
1137
1235
|
testFilePath: v.testFilePath,
|
|
1138
1236
|
pomFile: v.pomFile,
|
|
1139
1237
|
testType: v.testType,
|
|
@@ -1146,20 +1244,47 @@ export function registerSubmitReportTool(server) {
|
|
|
1146
1244
|
: v.rationale,
|
|
1147
1245
|
beforeDetails: "",
|
|
1148
1246
|
afterDetails: "",
|
|
1149
|
-
}));
|
|
1247
|
+
})));
|
|
1248
|
+
// GitHub slugs are case-insensitive, so two sections can name one repo and
|
|
1249
|
+
// repeat a file. Keep the first — the primary's, given the order above.
|
|
1250
|
+
// pomFile is part of the key: one spec emits one UPDATE verdict per page
|
|
1251
|
+
// object, and those rows are distinct, not duplicates.
|
|
1252
|
+
const seenRows = new Set();
|
|
1253
|
+
const dedupedMaintenance = rawMaintenance.filter((row) => {
|
|
1254
|
+
const key = `${(row.repository ?? "").trim().toLowerCase()}\u0000${row.testFilePath}\u0000${row.pomFile ?? ""}`;
|
|
1255
|
+
if (seenRows.has(key))
|
|
1256
|
+
return false;
|
|
1257
|
+
seenRows.add(key);
|
|
1258
|
+
return true;
|
|
1259
|
+
});
|
|
1260
|
+
if (badVerdicts.length > 0) {
|
|
1261
|
+
errorResult = toolError(`${badVerdicts.length} maintenance verdict(s) in the stateFile have no testFilePath ` +
|
|
1262
|
+
`(sections: ${[...new Set(badVerdicts)].join(", ")}). ` +
|
|
1263
|
+
`Re-run skyramp_actions for those repositories to rewrite the section.`);
|
|
1264
|
+
return errorResult;
|
|
1265
|
+
}
|
|
1266
|
+
// Keyed by the row's own repo, so executions come from its own section.
|
|
1267
|
+
const sectionByRepository = new Map(maintenanceSections.map((section) => [
|
|
1268
|
+
repositoryOf(section),
|
|
1269
|
+
section.data,
|
|
1270
|
+
]));
|
|
1150
1271
|
let testMaintenance;
|
|
1151
1272
|
// Rows missing a required detail (a real execution happened but the LLM didn't
|
|
1152
1273
|
// draft a summary for it) — collected here and rejected below, rather than
|
|
1153
1274
|
// silently shipping a blank field.
|
|
1154
1275
|
const missingDetails = [];
|
|
1155
|
-
if (
|
|
1276
|
+
if (dedupedMaintenance.length > 0) {
|
|
1156
1277
|
// beforeStatus/afterStatus are always stateFile-authoritative. beforeDetails/
|
|
1157
1278
|
// afterDetails come from the LLM's drafted summary (testMaintenanceDetails) —
|
|
1158
1279
|
// required whenever the stateFile shows a matching execution actually ran.
|
|
1159
|
-
testMaintenance =
|
|
1160
|
-
const recorded =
|
|
1280
|
+
testMaintenance = dedupedMaintenance.map((m) => {
|
|
1281
|
+
const recorded = sectionByRepository
|
|
1282
|
+
.get(m.repository)
|
|
1283
|
+
?.existingTests?.find((t) => testFileMatches(t.testFile, m.testFilePath));
|
|
1161
1284
|
const detail = params.testMaintenanceDetails?.find((d) => d.testFilePath === m.testFilePath);
|
|
1162
|
-
|
|
1285
|
+
// Two repos can hold the same basename; name the repo so the agent
|
|
1286
|
+
// knows which row to draft for.
|
|
1287
|
+
const displayName = `${path.basename(m.testFilePath)} (${m.repository})`;
|
|
1163
1288
|
const defaultBeforeStatus = MAINTENANCE_CHANGE_ACTIONS.has(m.action)
|
|
1164
1289
|
? TestExecutionStatus.Unknown
|
|
1165
1290
|
: TestExecutionStatus.Skipped;
|
|
@@ -1189,7 +1314,8 @@ export function registerSubmitReportTool(server) {
|
|
|
1189
1314
|
});
|
|
1190
1315
|
}
|
|
1191
1316
|
if (missingDetails.length > 0) {
|
|
1192
|
-
|
|
1317
|
+
const uniqueMissing = [...new Set(missingDetails)];
|
|
1318
|
+
errorResult = toolError(`${uniqueMissing.length} maintenance row(s) have a recorded execution but no drafted summary: ${uniqueMissing.join(", ")}. ` +
|
|
1193
1319
|
"Add a testMaintenanceDetails entry with the missing field(s), drafted from the execution output you already saw.");
|
|
1194
1320
|
return errorResult;
|
|
1195
1321
|
}
|
|
@@ -1227,6 +1353,11 @@ export function registerSubmitReportTool(server) {
|
|
|
1227
1353
|
...Object.values(fullState?.relatedRepos ?? {}).map((section) => section.repositoryPath),
|
|
1228
1354
|
])),
|
|
1229
1355
|
];
|
|
1356
|
+
// Primary verdicts only, by design: this checks UPDATEs, and
|
|
1357
|
+
// skyramp_actions downgrades every actionable related-repo verdict to
|
|
1358
|
+
// VERIFY (actionsTool.ts). If that changes, pass each section's
|
|
1359
|
+
// verdicts with its OWN checkout root — isBacked relativizes against
|
|
1360
|
+
// `repoRoot` and would otherwise exempt them all.
|
|
1230
1361
|
const unbacked = findUnchangedFileClaims({
|
|
1231
1362
|
repoRoot,
|
|
1232
1363
|
changedFiles,
|
|
@@ -1315,10 +1446,70 @@ export function registerSubmitReportTool(server) {
|
|
|
1315
1446
|
const assertionCheckouts = await stateManager
|
|
1316
1447
|
.listRepoCheckouts()
|
|
1317
1448
|
.catch(() => []);
|
|
1449
|
+
const reuseBlocking = [];
|
|
1318
1450
|
const sanitizedNewTests = await Promise.all(dedupedNewTests.map(async ({ scenarioFile: _sf, traceFile: _tf, frontendTrace: _ft, ...rest }) => {
|
|
1319
|
-
const row = await attachReuseOutcome(normalizeRepository(rest), stateData.reuseOutcomes, stateData.reuseHandOffs, retrofitViews);
|
|
1451
|
+
const row = await attachReuseOutcome(normalizeRepository(rest), stateData.reuseOutcomes, stateData.reuseHandOffs, retrofitViews, reuseBlocking);
|
|
1320
1452
|
return attachAssertionOutcome(row, stateData.assertionOutcomes ?? {}, assertionCheckouts);
|
|
1321
1453
|
}));
|
|
1454
|
+
// A blocking reuse verdict on the delivered files refuses the report. The
|
|
1455
|
+
// live verify pass is a one-time checkpoint on a file that keeps changing —
|
|
1456
|
+
// the execution fix loop and a post-verification rewrite both edit after it —
|
|
1457
|
+
// and the verdict re-derived here is the only one measured on what ships. It
|
|
1458
|
+
// used to become a row field, so the run committed the fault, detected it,
|
|
1459
|
+
// disclosed it, and delivered it. Reject rather than publish it, exactly like
|
|
1460
|
+
// the maintenanceVerdicts check above: this is a repair loop, not an abort —
|
|
1461
|
+
// the agent fixes the file (or documents the decline the check names) and
|
|
1462
|
+
// calls again. Advisories never reach this list, so a report-time advisory
|
|
1463
|
+
// stays reportable.
|
|
1464
|
+
//
|
|
1465
|
+
// BOUNDED, per spec: after REUSE_SUBMIT_MAX_REFUSALS refusals of one spec the
|
|
1466
|
+
// report is accepted and its row ships `verification: failed` with the
|
|
1467
|
+
// reasons. A loop with no exit at the last step of a run delivers nothing when
|
|
1468
|
+
// the agent cannot reach a pass, and a customer with no pull request is worse
|
|
1469
|
+
// off than one with a disclosed fault (see REUSE_SUBMIT_MAX_REFUSALS). The
|
|
1470
|
+
// count is persisted per spec path; a count that cannot be persisted cannot
|
|
1471
|
+
// bound anything, so a failed write accepts rather than refuses forever.
|
|
1472
|
+
// `reuseOutcomes` is keyed by basename, so two rows sharing a file name find
|
|
1473
|
+
// one record and push one verdict twice — deduplicated by path here, as
|
|
1474
|
+
// pendingReuseVerification skips that case on its side. Sorted by file so the
|
|
1475
|
+
// text is stable across calls.
|
|
1476
|
+
if (reuseBlocking.length > 0) {
|
|
1477
|
+
const verdicts = [
|
|
1478
|
+
...new Map(reuseBlocking.map((v) => [v.file, v])).values(),
|
|
1479
|
+
].sort((a, b) => a.file.localeCompare(b.file));
|
|
1480
|
+
const refusals = { ...(stateData.reuseRefusals ?? {}) };
|
|
1481
|
+
const owed = verdicts.filter((v) => (refusals[v.file] ?? 0) < REUSE_SUBMIT_MAX_REFUSALS);
|
|
1482
|
+
let persisted = owed.length > 0;
|
|
1483
|
+
if (owed.length > 0) {
|
|
1484
|
+
for (const v of owed)
|
|
1485
|
+
refusals[v.file] = (refusals[v.file] ?? 0) + 1;
|
|
1486
|
+
try {
|
|
1487
|
+
await stateManager.writeData({ ...stateData, reuseRefusals: refusals });
|
|
1488
|
+
}
|
|
1489
|
+
catch (err) {
|
|
1490
|
+
persisted = false;
|
|
1491
|
+
logger.warning("Could not persist the reuse refusal count — accepting the report rather than refusing without a bound", { error: String(err) });
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
if (owed.length > 0 && persisted) {
|
|
1495
|
+
errorResult = toolError(`${owed.length} delivered test${owed.length === 1 ? "" : "s"} fail${owed.length === 1 ? "s" : ""} a blocking code-reuse check on the file as it stands now — the same check skyramp_reuse_code runs with verify: true, and a report cannot ship work that does not pass it:\n` +
|
|
1496
|
+
owed
|
|
1497
|
+
.map((v) => `\n- ${v.file} · ${v.failures.map((f) => f.kind).join(", ")} · refusal ${refusals[v.file]} of ${REUSE_SUBMIT_MAX_REFUSALS}\n${indent(v.detail)}\n Then call ${v.verifyCall} so the repaired files are re-checked.`)
|
|
1498
|
+
.join("\n") +
|
|
1499
|
+
// The bound is stated as a counter only. What happens when it is
|
|
1500
|
+
// reached is not — an agent told that two more calls ship the report
|
|
1501
|
+
// may wait instead of repair, and the sanctioned exits (repair, or the
|
|
1502
|
+
// documented decline) are already named.
|
|
1503
|
+
`\n\nRepair each file above (or document a deliberate decline with the exact marker the check names), re-verify it, and call skyramp_submit_report again.`);
|
|
1504
|
+
return errorResult;
|
|
1505
|
+
}
|
|
1506
|
+
logger.warning("Accepting a report whose delivered files fail a blocking code-reuse check — the refusal bound is reached, the rows record the failure", {
|
|
1507
|
+
files: verdicts.map((v) => ({
|
|
1508
|
+
file: v.file,
|
|
1509
|
+
kinds: v.failures.map((f) => f.kind),
|
|
1510
|
+
})),
|
|
1511
|
+
});
|
|
1512
|
+
}
|
|
1322
1513
|
// The report enum has no `bug_caught` value, so which failure was on purpose
|
|
1323
1514
|
// is derived from the plan rather than from agent prose. Read defensively:
|
|
1324
1515
|
// the plan comes off disk.
|
|
@@ -1357,7 +1548,13 @@ export function registerSubmitReportTool(server) {
|
|
|
1357
1548
|
let objections;
|
|
1358
1549
|
let changeTable;
|
|
1359
1550
|
{
|
|
1360
|
-
const postExecution = runPostExecutionChecks(stateData.plan ?? { plannedTests: [], registrationNumber: 0, answers: [], openObjections: [], answeredObjections: [] }, dedupedNewTests, params.testResults, params.issuesFound ?? [])
|
|
1551
|
+
const postExecution = runPostExecutionChecks(stateData.plan ?? { plannedTests: [], registrationNumber: 0, answers: [], openObjections: [], answeredObjections: [] }, dedupedNewTests, params.testResults, params.issuesFound ?? [], isPlanOnlyMode(),
|
|
1552
|
+
// The triage's own verdicts, not this tool's input, so the join is against
|
|
1553
|
+
// what the run is recorded as having edited — and only the rows that left a
|
|
1554
|
+
// test standing and were actually applied, so a no-op assessment, a deletion
|
|
1555
|
+
// or a report-only recommendation cannot corroborate. The verdicts rather
|
|
1556
|
+
// than the published rows because `reportOnly` lives here.
|
|
1557
|
+
rowsLeavingCoverage(stateData.maintenanceVerdicts));
|
|
1361
1558
|
// Read defensively, like the crash-contained checks above: the plan comes
|
|
1362
1559
|
// off disk, so its declared array type holds only on the validated path.
|
|
1363
1560
|
const stillOpen = Array.isArray(stateData.plan?.openObjections)
|
|
@@ -1501,6 +1698,46 @@ export function registerSubmitReportTool(server) {
|
|
|
1501
1698
|
changesByCandidate.set(id, cited.map((entry) => String(entry ?? "").trim()));
|
|
1502
1699
|
}
|
|
1503
1700
|
}
|
|
1701
|
+
// A change an existing test covers raises no `coverage:change:` objection
|
|
1702
|
+
// and ships no planned test, so neither `testedBy` nor `answer` would say
|
|
1703
|
+
// anything about it. Without this the table reads as untested for a change
|
|
1704
|
+
// `coverage` counts as covered.
|
|
1705
|
+
//
|
|
1706
|
+
// Only what a maintenance row corroborates: publishing a claim on its own
|
|
1707
|
+
// would let the table assert coverage the verifier refused — `coverage`
|
|
1708
|
+
// declines to credit an entry whose file does not resolve, and this row
|
|
1709
|
+
// would still have named it. An uncorroborated claim draws
|
|
1710
|
+
// `deliveredMatchesPlan:maintains:` instead of a table entry.
|
|
1711
|
+
//
|
|
1712
|
+
// `testFileMatches` rather than a basename, so a spec under one directory
|
|
1713
|
+
// cannot lend its coverage to a same-named spec under another; and only the
|
|
1714
|
+
// rows that left a test standing, so DELETE and the no-op actions cannot
|
|
1715
|
+
// corroborate a claim that an existing test covers the change.
|
|
1716
|
+
const editedPaths = [];
|
|
1717
|
+
for (const row of rowsLeavingCoverage(stateData.maintenanceVerdicts)) {
|
|
1718
|
+
for (const full of [row?.testFilePath, row?.pomFile]) {
|
|
1719
|
+
const path = typeof full === "string" ? full.trim() : "";
|
|
1720
|
+
if (path)
|
|
1721
|
+
editedPaths.push(path);
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
const maintainedByChange = new Map();
|
|
1725
|
+
for (const entry of Array.isArray(stateData.plan?.maintains) ? stateData.plan.maintains : []) {
|
|
1726
|
+
const file = String(entry?.file ?? "").trim();
|
|
1727
|
+
if (!file || !Array.isArray(entry?.changes))
|
|
1728
|
+
continue;
|
|
1729
|
+
if (!editedPaths.some((full) => testFileMatches(full, file)))
|
|
1730
|
+
continue;
|
|
1731
|
+
for (const cited of entry.changes) {
|
|
1732
|
+
const changeId = String(cited ?? "").trim();
|
|
1733
|
+
if (!changeId)
|
|
1734
|
+
continue;
|
|
1735
|
+
const files = maintainedByChange.get(changeId) ?? [];
|
|
1736
|
+
if (!files.includes(file))
|
|
1737
|
+
files.push(file);
|
|
1738
|
+
maintainedByChange.set(changeId, files);
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1504
1741
|
const answerByChange = new Map();
|
|
1505
1742
|
for (const objection of objections) {
|
|
1506
1743
|
if (!objection.objectionId.startsWith(CHANGE_OBJECTION_PREFIX))
|
|
@@ -1515,11 +1752,13 @@ export function registerSubmitReportTool(server) {
|
|
|
1515
1752
|
.map((test) => (test.plannedTestId ?? "").trim())
|
|
1516
1753
|
.filter((plannedTestId) => plannedTestId && (changesByCandidate.get(plannedTestId) ?? []).includes(id));
|
|
1517
1754
|
const answer = answerByChange.get(id);
|
|
1755
|
+
const maintainedBy = maintainedByChange.get(id) ?? [];
|
|
1518
1756
|
return {
|
|
1519
1757
|
id,
|
|
1520
1758
|
text: String(change?.text ?? ""),
|
|
1521
1759
|
source: String(change?.source ?? ""),
|
|
1522
1760
|
testedBy,
|
|
1761
|
+
...(maintainedBy.length > 0 ? { maintainedBy } : {}),
|
|
1523
1762
|
...(answer ? { answer } : {}),
|
|
1524
1763
|
};
|
|
1525
1764
|
});
|
|
@@ -1565,8 +1804,6 @@ export function registerSubmitReportTool(server) {
|
|
|
1565
1804
|
additionalRecommendations: dedupedRecommendations.map(normalizeRepository),
|
|
1566
1805
|
// Report wire format keeps main's original `fileName` (basename) — testFilePath is
|
|
1567
1806
|
// an internal-only field, needed for matching but never meant to reach the report.
|
|
1568
|
-
// TODO(multi-repo maintenance): map(normalizeRepository) once testMaintenanceSchema
|
|
1569
|
-
// has a repository field (see TODO above).
|
|
1570
1807
|
testMaintenance: testMaintenance
|
|
1571
1808
|
? await Promise.all(testMaintenance.map(async ({ testFilePath, pomFile, ...row }) => {
|
|
1572
1809
|
// `pomFile` is the file skyramp_actions told the agent to edit;
|
|
@@ -1582,7 +1819,7 @@ export function registerSubmitReportTool(server) {
|
|
|
1582
1819
|
const assertions = record
|
|
1583
1820
|
? await rederiveAssertionOutcome(record)
|
|
1584
1821
|
: undefined;
|
|
1585
|
-
return {
|
|
1822
|
+
return normalizeRepository({
|
|
1586
1823
|
...row,
|
|
1587
1824
|
fileName: path.basename(testFilePath),
|
|
1588
1825
|
...(pomFile &&
|
|
@@ -1590,7 +1827,7 @@ export function registerSubmitReportTool(server) {
|
|
|
1590
1827
|
? { editedFileName: path.basename(pomFile) }
|
|
1591
1828
|
: {}),
|
|
1592
1829
|
...(assertions ? { assertions } : {}),
|
|
1593
|
-
};
|
|
1830
|
+
});
|
|
1594
1831
|
}))
|
|
1595
1832
|
: undefined,
|
|
1596
1833
|
// videoPath is filled from the run's execution records; testFilePath is the
|
|
@@ -410,6 +410,11 @@ export function registerActionsTool(server) {
|
|
|
410
410
|
...(r.rebaselineSnapshots?.length
|
|
411
411
|
? { rebaselineSnapshots: r.rebaselineSnapshots, rebaselineOnly: r.rebaselineOnly === true }
|
|
412
412
|
: {}),
|
|
413
|
+
// Persisted so maintenance coverage can tell a REGENERATE that rewrote a
|
|
414
|
+
// file from one that only advised the developer to: an external test's
|
|
415
|
+
// REGENERATE/DELETE keeps its real action and touches nothing, so without
|
|
416
|
+
// this the report credits a `maintains` claim no edit backs.
|
|
417
|
+
...(r.reportOnly ? { reportOnly: true } : {}),
|
|
413
418
|
})),
|
|
414
419
|
}, { repo: args.repository, repositoryPath, step: "actions" });
|
|
415
420
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
+
import { AuthFinding } from "../../utils/workspaceAuth.js";
|
|
3
4
|
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
4
5
|
import { TraceFile } from "../../types/RepositoryAnalysis.js";
|
|
5
6
|
import { ChangedFileName } from "../../utils/branchDiff.js";
|
|
@@ -58,6 +59,10 @@ export interface AnalyzeChangesData {
|
|
|
58
59
|
openApiSpecPath?: string;
|
|
59
60
|
/** Whether the spec loaded and carried usable path entries. */
|
|
60
61
|
openApiSpecLoaded: boolean;
|
|
62
|
+
/** What the agent has to decide about this repository's auth, when the
|
|
63
|
+
* workspace and the code disagree. Each carries the sentence the agent acts
|
|
64
|
+
* on; the server changes no value on the strength of one. */
|
|
65
|
+
authFindings?: AuthFinding[];
|
|
61
66
|
};
|
|
62
67
|
/**
|
|
63
68
|
* Identifying `data-*` attributes the diff removed from files that survive it
|
|
@@ -116,18 +121,66 @@ export declare const analyzeChangesOutputSchema: {
|
|
|
116
121
|
authHeader: z.ZodOptional<z.ZodString>;
|
|
117
122
|
openApiSpecPath: z.ZodOptional<z.ZodString>;
|
|
118
123
|
openApiSpecLoaded: z.ZodBoolean;
|
|
124
|
+
authFindings: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
125
|
+
kind: z.ZodLiteral<"workspace-disagreement">;
|
|
126
|
+
message: z.ZodString;
|
|
127
|
+
serviceName: z.ZodString;
|
|
128
|
+
field: z.ZodEnum<["authType", "authHeader", "authScheme"]>;
|
|
129
|
+
primary: z.ZodString;
|
|
130
|
+
analysed: z.ZodString;
|
|
131
|
+
primaryWorkspaceFile: z.ZodString;
|
|
132
|
+
analysedWorkspaceFile: z.ZodString;
|
|
133
|
+
}, "strip", z.ZodTypeAny, {
|
|
134
|
+
message: string;
|
|
135
|
+
kind: "workspace-disagreement";
|
|
136
|
+
serviceName: string;
|
|
137
|
+
field: "authHeader" | "authScheme" | "authType";
|
|
138
|
+
primaryWorkspaceFile: string;
|
|
139
|
+
analysedWorkspaceFile: string;
|
|
140
|
+
primary: string;
|
|
141
|
+
analysed: string;
|
|
142
|
+
}, {
|
|
143
|
+
message: string;
|
|
144
|
+
kind: "workspace-disagreement";
|
|
145
|
+
serviceName: string;
|
|
146
|
+
field: "authHeader" | "authScheme" | "authType";
|
|
147
|
+
primaryWorkspaceFile: string;
|
|
148
|
+
analysedWorkspaceFile: string;
|
|
149
|
+
primary: string;
|
|
150
|
+
analysed: string;
|
|
151
|
+
}>, "many">>;
|
|
119
152
|
}, "strip", z.ZodTypeAny, {
|
|
120
153
|
baseUrl: string;
|
|
121
154
|
authMethod: string;
|
|
122
155
|
openApiSpecLoaded: boolean;
|
|
123
156
|
authHeader?: string | undefined;
|
|
124
157
|
openApiSpecPath?: string | undefined;
|
|
158
|
+
authFindings?: {
|
|
159
|
+
message: string;
|
|
160
|
+
kind: "workspace-disagreement";
|
|
161
|
+
serviceName: string;
|
|
162
|
+
field: "authHeader" | "authScheme" | "authType";
|
|
163
|
+
primaryWorkspaceFile: string;
|
|
164
|
+
analysedWorkspaceFile: string;
|
|
165
|
+
primary: string;
|
|
166
|
+
analysed: string;
|
|
167
|
+
}[] | undefined;
|
|
125
168
|
}, {
|
|
126
169
|
baseUrl: string;
|
|
127
170
|
authMethod: string;
|
|
128
171
|
openApiSpecLoaded: boolean;
|
|
129
172
|
authHeader?: string | undefined;
|
|
130
173
|
openApiSpecPath?: string | undefined;
|
|
174
|
+
authFindings?: {
|
|
175
|
+
message: string;
|
|
176
|
+
kind: "workspace-disagreement";
|
|
177
|
+
serviceName: string;
|
|
178
|
+
field: "authHeader" | "authScheme" | "authType";
|
|
179
|
+
primaryWorkspaceFile: string;
|
|
180
|
+
analysedWorkspaceFile: string;
|
|
181
|
+
primary: string;
|
|
182
|
+
analysed: string;
|
|
183
|
+
}[] | undefined;
|
|
131
184
|
}>>;
|
|
132
185
|
uiContext: z.ZodOptional<z.ZodObject<{
|
|
133
186
|
removedElements: z.ZodOptional<z.ZodArray<z.ZodObject<{
|