@skyramp/mcp 0.3.8 → 0.4.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (229) hide show
  1. package/build/commands/commandLibrary.d.ts +1 -1
  2. package/build/commands/commandLibrary.js +3 -3
  3. package/build/commands/recommendTestsAndExecuteCommand.d.ts +1 -1
  4. package/build/commands/recommendTestsAndExecuteCommand.js +35 -20
  5. package/build/commands/testThisEndpointCommand.js +35 -19
  6. package/build/index.js +9 -3
  7. package/build/playwright/blueprintDigest.d.ts +15 -0
  8. package/build/playwright/blueprintDigest.js +152 -0
  9. package/build/playwright/blueprintDigestStore.d.ts +31 -0
  10. package/build/playwright/blueprintDigestStore.js +117 -0
  11. package/build/playwright/registerPlaywrightTools.js +60 -12
  12. package/build/playwright/traceRecordingPrompt.js +8 -7
  13. package/build/prompts/enhance-assertions/sharedAssertionRules.js +9 -8
  14. package/build/prompts/enhance-assertions/uiAssertionsPrompt.js +24 -2
  15. package/build/prompts/promptAssets.d.ts +20 -0
  16. package/build/prompts/promptAssets.js +55 -0
  17. package/build/prompts/sut-setup/modes/dockerComposePrompt.js +19 -5
  18. package/build/prompts/test-maintenance/actionsInstructions.d.ts +4 -0
  19. package/build/prompts/test-maintenance/actionsInstructions.js +14 -2
  20. package/build/prompts/test-maintenance/drift-analysis-prompt.d.ts +0 -10
  21. package/build/prompts/test-maintenance/drift-analysis-prompt.js +2 -11
  22. package/build/prompts/test-maintenance/uiDriftAnalysisSections.js +8 -4
  23. package/build/prompts/test-recommendation/diffExecutionPlan.d.ts +5 -22
  24. package/build/prompts/test-recommendation/diffExecutionPlan.js +37 -465
  25. package/build/prompts/test-recommendation/recommendationSections.d.ts +7 -17
  26. package/build/prompts/test-recommendation/recommendationSections.js +67 -309
  27. package/build/prompts/test-recommendation/recommendationShared.d.ts +19 -47
  28. package/build/prompts/test-recommendation/recommendationShared.js +49 -155
  29. package/build/prompts/test-recommendation/registerRecommendTestsPrompt.d.ts +0 -5
  30. package/build/prompts/test-recommendation/registerRecommendTestsPrompt.js +10 -153
  31. package/build/prompts/test-recommendation/test-recommendation-prompt.d.ts +2 -29
  32. package/build/prompts/test-recommendation/test-recommendation-prompt.js +32 -457
  33. package/build/prompts/testbot/planDeclarations.d.ts +6 -0
  34. package/build/prompts/testbot/planDeclarations.js +9 -0
  35. package/build/prompts/testbot/testbot-prompts.d.ts +8 -0
  36. package/build/prompts/testbot/testbot-prompts.js +256 -381
  37. package/build/recommendation/answers.d.ts +35 -0
  38. package/build/recommendation/answers.js +96 -0
  39. package/build/recommendation/registerPlan.d.ts +49 -0
  40. package/build/recommendation/registerPlan.js +117 -0
  41. package/build/recommendation/runVerifiers.d.ts +10 -0
  42. package/build/recommendation/runVerifiers.js +49 -0
  43. package/build/recommendation/subjectStep.d.ts +42 -0
  44. package/build/recommendation/subjectStep.js +86 -0
  45. package/build/recommendation/types.d.ts +163 -0
  46. package/build/recommendation/types.js +20 -0
  47. package/build/recommendation/verifierContracts.d.ts +382 -0
  48. package/build/recommendation/verifierContracts.js +263 -0
  49. package/build/recommendation/verifiers/changedFile.d.ts +2 -0
  50. package/build/recommendation/verifiers/changedFile.js +82 -0
  51. package/build/recommendation/verifiers/citedPath.d.ts +12 -0
  52. package/build/recommendation/verifiers/citedPath.js +35 -0
  53. package/build/recommendation/verifiers/coverage.d.ts +7 -0
  54. package/build/recommendation/verifiers/coverage.js +617 -0
  55. package/build/recommendation/verifiers/deliveredMatchesPlan.d.ts +11 -0
  56. package/build/recommendation/verifiers/deliveredMatchesPlan.js +33 -0
  57. package/build/recommendation/verifiers/endpointGrounded.d.ts +17 -0
  58. package/build/recommendation/verifiers/endpointGrounded.js +128 -0
  59. package/build/recommendation/verifiers/existingCoverage.d.ts +6 -0
  60. package/build/recommendation/verifiers/existingCoverage.js +51 -0
  61. package/build/recommendation/verifiers/expectedOutcome.d.ts +31 -0
  62. package/build/recommendation/verifiers/expectedOutcome.js +105 -0
  63. package/build/recommendation/verifiers/removedElementGuarded.d.ts +2 -0
  64. package/build/recommendation/verifiers/removedElementGuarded.js +57 -0
  65. package/build/recommendation/verifiers/reportedCategory.d.ts +26 -0
  66. package/build/recommendation/verifiers/reportedCategory.js +84 -0
  67. package/build/recommendation/verifiers/screenRoute.d.ts +10 -0
  68. package/build/recommendation/verifiers/screenRoute.js +118 -0
  69. package/build/recommendation/verifiers/statedDifference.d.ts +6 -0
  70. package/build/recommendation/verifiers/statedDifference.js +140 -0
  71. package/build/recommendation/verifiers/uiElementGrounded.d.ts +7 -0
  72. package/build/recommendation/verifiers/uiElementGrounded.js +318 -0
  73. package/build/resources/analysisResources.js +1 -114
  74. package/build/resources/testbotResource.js +23 -13
  75. package/build/services/ModularizationService.js +2 -1
  76. package/build/services/TestDiscoveryService.d.ts +3 -72
  77. package/build/services/TestDiscoveryService.js +10 -303
  78. package/build/services/containerEnv.d.ts +1 -1
  79. package/build/services/containerEnv.js +12 -0
  80. package/build/skills/fixTestImportErrorsSkill.d.ts +13 -0
  81. package/build/skills/fixTestImportErrorsSkill.js +20 -0
  82. package/build/toolNames.d.ts +1 -0
  83. package/build/toolNames.js +1 -0
  84. package/build/tools/code-refactor/enhanceAssertionsTool.js +3 -3
  85. package/build/tools/code-refactor/modularizationTool.js +2 -1
  86. package/build/tools/executeSkyrampTestTool.d.ts +80 -0
  87. package/build/tools/executeSkyrampTestTool.js +246 -19
  88. package/build/tools/generate-tests/generateBatchScenarioRestTool.js +6 -0
  89. package/build/tools/generate-tests/generateContractRestTool.js +3 -3
  90. package/build/tools/generate-tests/planGuard.d.ts +2 -2
  91. package/build/tools/generate-tests/planGuard.js +78 -18
  92. package/build/tools/one-click/oneClickTool.d.ts +0 -1
  93. package/build/tools/one-click/oneClickTool.js +0 -5
  94. package/build/tools/submitReportTool.d.ts +48 -42
  95. package/build/tools/submitReportTool.js +576 -193
  96. package/build/tools/test-management/actionsTool.js +72 -4
  97. package/build/tools/test-management/analyzeChangesTool.d.ts +144 -48
  98. package/build/tools/test-management/analyzeChangesTool.js +212 -1219
  99. package/build/tools/test-management/analyzeTestHealthTool.js +13 -24
  100. package/build/tools/test-management/index.d.ts +1 -0
  101. package/build/tools/test-management/index.js +1 -0
  102. package/build/tools/test-management/registerTestPlanTool.d.ts +795 -172
  103. package/build/tools/test-management/registerTestPlanTool.js +609 -542
  104. package/build/tools/test-management/resolveScreenTool.d.ts +75 -0
  105. package/build/tools/test-management/resolveScreenTool.js +289 -0
  106. package/build/types/BlueprintDigest.d.ts +34 -0
  107. package/build/types/BlueprintDigest.js +1 -0
  108. package/build/types/RepositoryAnalysis.d.ts +20 -1559
  109. package/build/types/RepositoryAnalysis.js +2 -58
  110. package/build/types/StepMethod.d.ts +40 -0
  111. package/build/types/StepMethod.js +77 -0
  112. package/build/types/TestAnalysis.d.ts +12 -0
  113. package/build/types/TestExecution.d.ts +4 -0
  114. package/build/types/TestRecommendation.d.ts +24 -24
  115. package/build/types/TestRecommendation.js +91 -89
  116. package/build/types/TestbotPromptOptions.d.ts +0 -4
  117. package/build/types/TestbotReport.d.ts +64 -2
  118. package/build/utils/AnalysisStateManager.d.ts +79 -113
  119. package/build/utils/AnalysisStateManager.js +147 -57
  120. package/build/utils/assertion-verify/api-shared-lints.js +1 -1
  121. package/build/utils/assertion-verify/metrics.js +85 -36
  122. package/build/utils/assertion-verify/ui-lints.d.ts +0 -5
  123. package/build/utils/assertion-verify/ui-lints.js +32 -0
  124. package/build/utils/branchDiff.d.ts +63 -31
  125. package/build/utils/branchDiff.js +242 -94
  126. package/build/utils/containedPath.d.ts +18 -0
  127. package/build/utils/containedPath.js +73 -0
  128. package/build/utils/dartRouteExtractor.d.ts +18 -34
  129. package/build/utils/dartRouteExtractor.js +101 -173
  130. package/build/utils/featureFlags.d.ts +12 -0
  131. package/build/utils/featureFlags.js +14 -0
  132. package/build/utils/frontendSelectors.d.ts +48 -27
  133. package/build/utils/frontendSelectors.js +241 -80
  134. package/build/utils/pathMatching.d.ts +2 -4
  135. package/build/utils/pathMatching.js +2 -4
  136. package/build/utils/planMatchKeys.d.ts +38 -47
  137. package/build/utils/planMatchKeys.js +143 -81
  138. package/build/utils/rebaselineSnapshots.d.ts +24 -0
  139. package/build/utils/rebaselineSnapshots.js +65 -0
  140. package/build/utils/removedUiElements.d.ts +22 -0
  141. package/build/utils/removedUiElements.js +106 -0
  142. package/build/utils/reportVerification.d.ts +2 -6
  143. package/build/utils/reportVerification.js +61 -2
  144. package/build/utils/screenRoutes.d.ts +66 -0
  145. package/build/utils/screenRoutes.js +727 -0
  146. package/build/utils/sourceRouteExtractor.js +320 -112
  147. package/build/utils/testFileClassification.d.ts +11 -2
  148. package/build/utils/testFileClassification.js +44 -2
  149. package/build/utils/testFixtures.d.ts +5 -0
  150. package/build/utils/testFixtures.js +13 -0
  151. package/build/utils/utils.d.ts +0 -1
  152. package/build/utils/utils.js +0 -11
  153. package/build/utils/versions.d.ts +3 -3
  154. package/build/utils/versions.js +1 -1
  155. package/build/workspace/workspace.d.ts +12 -12
  156. package/node_modules/playwright/lib/mcp/skyramp/assertHiddenTool.js +56 -0
  157. package/node_modules/playwright/lib/mcp/skyramp/assertTool.js +2 -1
  158. package/node_modules/playwright/lib/mcp/skyramp/loadTraceTool.js +10 -0
  159. package/node_modules/playwright/lib/mcp/skyramp/skyRampImport.js +4 -1
  160. package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +160 -1
  161. package/node_modules/playwright/lib/mcp/test/skyRampExport.js +4 -2
  162. package/node_modules/playwright/node_modules/playwright-core/lib/server/codegen/skyramp/jsonlReader.js +1 -0
  163. package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/recorderSignalProcessor.js +2 -0
  164. package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder.js +5 -1
  165. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{index.-Id052Lr.js → index.B7KbSQcC.js} +1 -1
  166. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html +1 -1
  167. package/node_modules/playwright/node_modules/playwright-core/package.json +1 -1
  168. package/node_modules/playwright/node_modules/playwright-core/src/server/codegen/skyramp/jsonlReader.ts +1 -1
  169. package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/recorderSignalProcessor.ts +7 -0
  170. package/node_modules/playwright/node_modules/playwright-core/src/server/recorder.ts +6 -1
  171. package/node_modules/playwright/package.json +1 -1
  172. package/package.json +4 -3
  173. package/plugin/.claude-plugin/plugin.json +8 -0
  174. package/plugin/plugin.json +6 -0
  175. package/plugin/prompts/declaring-a-plan.md +20 -0
  176. package/plugin/prompts/generate-tests/context-fetching.md +4 -0
  177. package/plugin/prompts/generate-tests/execution-plan.md +63 -0
  178. package/plugin/prompts/generate-tests/generation.md +108 -0
  179. package/plugin/prompts/generate-tests/path-parameters.md +1 -0
  180. package/plugin/prompts/generate-tests/reasoning-protocol.md +17 -0
  181. package/plugin/prompts/generate-tests/tool-workflow-variants.md +61 -0
  182. package/plugin/prompts/generate-tests/tool-workflows.md +65 -0
  183. package/plugin/prompts/plan-tests.md +42 -0
  184. package/plugin/prompts/testbot-task1.md +82 -0
  185. package/plugin/skills/fix-test-import-errors/SKILL.md +98 -0
  186. package/build/prompts/test-recommendation/analysisOutputPrompt.d.ts +0 -84
  187. package/build/prompts/test-recommendation/analysisOutputPrompt.js +0 -369
  188. package/build/prompts/test-recommendation/fullRepoCatalog.d.ts +0 -7
  189. package/build/prompts/test-recommendation/fullRepoCatalog.js +0 -283
  190. package/build/prompts/test-recommendation/scopeAssessment.d.ts +0 -81
  191. package/build/prompts/test-recommendation/scopeAssessment.js +0 -359
  192. package/build/recommendation/budgeters/diversityBalancedBudgeter.d.ts +0 -7
  193. package/build/recommendation/budgeters/diversityBalancedBudgeter.js +0 -105
  194. package/build/recommendation/budgeters/fixedNBudgeter.d.ts +0 -7
  195. package/build/recommendation/budgeters/fixedNBudgeter.js +0 -11
  196. package/build/recommendation/budgeters/shared.d.ts +0 -32
  197. package/build/recommendation/budgeters/shared.js +0 -246
  198. package/build/recommendation/discriminators.d.ts +0 -37
  199. package/build/recommendation/discriminators.js +0 -379
  200. package/build/recommendation/diversity.d.ts +0 -47
  201. package/build/recommendation/diversity.js +0 -101
  202. package/build/recommendation/planRanker.d.ts +0 -65
  203. package/build/recommendation/planRanker.js +0 -83
  204. package/build/recommendation/testFixtures.d.ts +0 -25
  205. package/build/recommendation/testFixtures.js +0 -45
  206. package/build/types/FrontendIntegration.d.ts +0 -28
  207. package/build/types/FrontendIntegration.js +0 -22
  208. package/build/types/Recommendation.d.ts +0 -146
  209. package/build/types/Recommendation.js +0 -74
  210. package/build/utils/changedRoutes.d.ts +0 -29
  211. package/build/utils/changedRoutes.js +0 -87
  212. package/build/utils/frontendIntegration.d.ts +0 -9
  213. package/build/utils/frontendIntegration.js +0 -243
  214. package/build/utils/importerHop.d.ts +0 -135
  215. package/build/utils/importerHop.js +0 -489
  216. package/build/utils/pathAffinityClassification.d.ts +0 -49
  217. package/build/utils/pathAffinityClassification.js +0 -180
  218. package/build/utils/pythonMountPrefixes.d.ts +0 -25
  219. package/build/utils/pythonMountPrefixes.js +0 -347
  220. package/build/utils/repoScanner.d.ts +0 -34
  221. package/build/utils/repoScanner.js +0 -300
  222. package/build/utils/routeParsers.d.ts +0 -95
  223. package/build/utils/routeParsers.js +0 -951
  224. package/build/utils/scenarioDrafting.d.ts +0 -92
  225. package/build/utils/scenarioDrafting.js +0 -951
  226. package/build/utils/subjectEndpoints.d.ts +0 -19
  227. package/build/utils/subjectEndpoints.js +0 -98
  228. package/build/utils/uiPageEnumerator.d.ts +0 -172
  229. package/build/utils/uiPageEnumerator.js +0 -474
@@ -1,47 +0,0 @@
1
- import { DraftedScenario } from "../types/RepositoryAnalysis.js";
2
- import { PriorityTier } from "../types/TestRecommendation.js";
3
- /**
4
- * Reorder a rank-ordered list so each attack-surface security_boundary scenario
5
- * sits immediately before the first ordinary direct-auth boundary — keeping
6
- * destructive sibling auth tests bundled ahead of their read counterparts.
7
- */
8
- export declare function prioritizeAttackSurfaceBundles<T extends {
9
- scenario: DraftedScenario;
10
- }>(items: T[]): T[];
11
- /** The test-type inference used everywhere GENERATE slots are distributed:
12
- * explicit testType, else single-step ⇒ contract, multi-step ⇒ integration
13
- * (the same inference used when rendering plan items). */
14
- export declare function inferScenarioType(s: DraftedScenario): string;
15
- /** Protected items always take a GENERATE slot before any round-robin:
16
- * CRITICAL-priority and attack-surface security_boundary scenarios. */
17
- export declare function isProtectedCandidate(priority: PriorityTier, scenario: DraftedScenario): boolean;
18
- /** Bucket items by type, preserving rank order within each bucket and
19
- * first-appearance order across buckets (so the highest-ranked type wins
20
- * round 1 of any subsequent round-robin). */
21
- export declare function bucketByType<T>(items: T[], typeOf: (item: T) => string): {
22
- order: string[];
23
- buckets: Map<string, T[]>;
24
- };
25
- /** Round-robin one item per non-empty bucket per round (skipping exhausted
26
- * buckets, so their freed slots spill over to the next type) until
27
- * `selected` reaches `count` or every bucket is empty. Mutates `selected`
28
- * and the buckets. */
29
- export declare function roundRobinFill<T>(selected: T[], order: string[], buckets: Map<string, T[]>, count: number): void;
30
- /**
31
- * Select `count` items from a rank-ordered list, distributing GENERATE slots
32
- * EVENLY across the test types present, with spillover.
33
- *
34
- * Policy (kept identical to the multi-repo prose in testbot-prompts.ts's
35
- * "Cross-repo test generation" block — change both together):
36
- * - Protected items first (see isProtectedCandidate) — they must stay in GENERATE.
37
- * - Bucket the rest by inferred test type and round-robin (see bucketByType /
38
- * roundRobinFill), preserving rank order within each bucket.
39
- *
40
- * Degenerate cases match the previous pure rank-order slice exactly: a single type
41
- * present, or `count >= items.length`, returns the same items in the same order —
42
- * so backend-only / single-type runs are unchanged (no regression).
43
- */
44
- export declare function roundRobinByType<T extends {
45
- scenario: DraftedScenario;
46
- priority: PriorityTier;
47
- }>(rankOrdered: T[], count: number): T[];
@@ -1,101 +0,0 @@
1
- import { PriorityTier } from "../types/TestRecommendation.js";
2
- import { isAttackSurfaceSecurityBoundary, isOrdinaryDirectAuthBoundary, } from "../prompts/test-recommendation/recommendationShared.js";
3
- /**
4
- * Reorder a rank-ordered list so each attack-surface security_boundary scenario
5
- * sits immediately before the first ordinary direct-auth boundary — keeping
6
- * destructive sibling auth tests bundled ahead of their read counterparts.
7
- */
8
- export function prioritizeAttackSurfaceBundles(items) {
9
- const reordered = [];
10
- for (const item of items) {
11
- if (isAttackSurfaceSecurityBoundary(item.scenario)) {
12
- const firstDirectAuthIndex = reordered.findIndex((candidate) => isOrdinaryDirectAuthBoundary(candidate.scenario));
13
- if (firstDirectAuthIndex >= 0) {
14
- reordered.splice(firstDirectAuthIndex, 0, item);
15
- continue;
16
- }
17
- }
18
- reordered.push(item);
19
- }
20
- return reordered;
21
- }
22
- /** The test-type inference used everywhere GENERATE slots are distributed:
23
- * explicit testType, else single-step ⇒ contract, multi-step ⇒ integration
24
- * (the same inference used when rendering plan items). */
25
- export function inferScenarioType(s) {
26
- return s.testType ?? (s.steps.length === 1 ? "contract" : "integration");
27
- }
28
- /** Protected items always take a GENERATE slot before any round-robin:
29
- * CRITICAL-priority and attack-surface security_boundary scenarios. */
30
- export function isProtectedCandidate(priority, scenario) {
31
- return (priority === PriorityTier.CRITICAL ||
32
- isAttackSurfaceSecurityBoundary(scenario));
33
- }
34
- /** Bucket items by type, preserving rank order within each bucket and
35
- * first-appearance order across buckets (so the highest-ranked type wins
36
- * round 1 of any subsequent round-robin). */
37
- export function bucketByType(items, typeOf) {
38
- const order = [];
39
- const buckets = new Map();
40
- for (const item of items) {
41
- const t = typeOf(item);
42
- if (!buckets.has(t)) {
43
- buckets.set(t, []);
44
- order.push(t);
45
- }
46
- buckets.get(t).push(item);
47
- }
48
- return { order, buckets };
49
- }
50
- /** Round-robin one item per non-empty bucket per round (skipping exhausted
51
- * buckets, so their freed slots spill over to the next type) until
52
- * `selected` reaches `count` or every bucket is empty. Mutates `selected`
53
- * and the buckets. */
54
- export function roundRobinFill(selected, order, buckets, count) {
55
- while (selected.length < count &&
56
- order.some((t) => (buckets.get(t)?.length ?? 0) > 0)) {
57
- for (const t of order) {
58
- if (selected.length >= count)
59
- break;
60
- const bucket = buckets.get(t);
61
- if (bucket && bucket.length > 0)
62
- selected.push(bucket.shift());
63
- }
64
- }
65
- }
66
- /**
67
- * Select `count` items from a rank-ordered list, distributing GENERATE slots
68
- * EVENLY across the test types present, with spillover.
69
- *
70
- * Policy (kept identical to the multi-repo prose in testbot-prompts.ts's
71
- * "Cross-repo test generation" block — change both together):
72
- * - Protected items first (see isProtectedCandidate) — they must stay in GENERATE.
73
- * - Bucket the rest by inferred test type and round-robin (see bucketByType /
74
- * roundRobinFill), preserving rank order within each bucket.
75
- *
76
- * Degenerate cases match the previous pure rank-order slice exactly: a single type
77
- * present, or `count >= items.length`, returns the same items in the same order —
78
- * so backend-only / single-type runs are unchanged (no regression).
79
- */
80
- export function roundRobinByType(rankOrdered, count) {
81
- if (count <= 0)
82
- return [];
83
- // Everything fits → no need to bucket; identical to the old slice.
84
- if (count >= rankOrdered.length)
85
- return rankOrdered.slice(0, count);
86
- // Protected items occupy GENERATE slots first, in rank order.
87
- const selected = [];
88
- const remaining = [];
89
- for (const item of rankOrdered) {
90
- if (selected.length < count &&
91
- isProtectedCandidate(item.priority, item.scenario)) {
92
- selected.push(item);
93
- }
94
- else {
95
- remaining.push(item);
96
- }
97
- }
98
- const { order, buckets } = bucketByType(remaining, (item) => inferScenarioType(item.scenario));
99
- roundRobinFill(selected, order, buckets, count);
100
- return selected;
101
- }
@@ -1,65 +0,0 @@
1
- import { Candidate, BudgetContext, Demotion, SelectionResult } from "../types/Recommendation.js";
2
- import { ScenarioCategory } from "../types/TestRecommendation.js";
3
- /** Options for {@link rankCandidates}. Reserved for phase-2 tuning. */
4
- export interface RankOptions {
5
- /**
6
- * Categories that take the top carve-out tier ahead of everything else.
7
- * Defaults to the `CATEGORY_PRIORITY === "CRITICAL"` categories (bug_caught and
8
- * requirement_conflict — new_endpoint is MEDIUM, not carved out). The two CRITICAL
9
- * categories are carved out independently, so a requirement conflict never competes
10
- * with a code-review bug for one slot. Exposed so phase 2 can tune the carve-out
11
- * WITHOUT reintroducing the agent's priority tag as a ranking input.
12
- */
13
- carveOutCategories?: ScenarioCategory[];
14
- /**
15
- * Raw PR diff text. When supplied, a candidate whose scenario references a
16
- * method+path that appears on an actual changed diff line (`+`/`-`) floats
17
- * ahead of a same-tier candidate that doesn't — e.g. the one endpoint really
18
- * removed by a PR outranks other same-category "verify-removed-*" candidates
19
- * for endpoints merely adjacent in the same file (SKYR-4026).
20
- */
21
- diffText?: string;
22
- }
23
- /** Context for {@link selectPlan}: the budget context plus the demotion channel
24
- * the register-plan tool fills from discriminator verification. */
25
- export interface SelectPlanContext extends BudgetContext {
26
- /** Demoted claims (failed discriminator verification) to surface on the result. */
27
- demotions?: Demotion[];
28
- /** Threaded into {@link rankCandidates} as `RankOptions.diffText`. */
29
- diffText?: string;
30
- }
31
- /**
32
- * Rank test candidates for the register-plan checkpoint. Pure and fully
33
- * deterministic: the same set of candidates always yields the same order,
34
- * independent of input order (the final tiebreak is the stable `candidateId`).
35
- *
36
- * Ordering (highest first):
37
- * 1. Carve-out — CRITICAL-category scenarios (bug_caught, requirement_conflict), preserving the
38
- * protected-first convention of `roundRobinByType` /
39
- * `prioritizeAttackSurfaceBundles`.
40
- * 2. Verified discriminators — candidates whose declared discriminator survived
41
- * `validateDiscriminator` (marked via `verifiedDiscriminator`) float ahead of
42
- * unverified peers in the same tier.
43
- * 3. Diff-hunk proximity — candidates referencing a method+path that appears
44
- * on an actual changed diff line float ahead of same-tier candidates that
45
- * don't (SKYR-4026). Only applied when `opts.diffText` is supplied.
46
- * 4. `CATEGORY_PRIORITY` mapped over `scenario.category` (CRITICAL > HIGH >
47
- * MEDIUM > LOW).
48
- * 5. Stable tiebreak on `candidateId` for determinism.
49
- *
50
- * IMPORTANT: the agent-supplied `priority` field (`scenario.priority`) is
51
- * DELIBERATELY IGNORED here. Round-1 experiments found it systematically
52
- * miscalibrated — the agent labels genuinely discriminating tests "low" and
53
- * generic happy-path tests "high" — so ranking on it inverts the intended order.
54
- * Priority in this pipeline is derived from the scenario's category and from the
55
- * verified discriminator, never from the agent's self-assessment.
56
- */
57
- export declare function rankCandidates(candidates: Candidate[], opts?: RankOptions): Candidate[];
58
- /**
59
- * Convenience composition: rank the candidates, split GENERATE vs ADDITIONAL via
60
- * the diversity-balanced budgeter, and surface the caller-supplied demotions on
61
- * the result. The caller (the register-plan tool) runs `validateDiscriminator`
62
- * first — setting `verifiedDiscriminator` on the candidates that passed and
63
- * passing the failed claims through `ctx.demotions`.
64
- */
65
- export declare function selectPlan(candidates: Candidate[], ctx: SelectPlanContext, opts?: RankOptions): SelectionResult;
@@ -1,83 +0,0 @@
1
- import { CATEGORY_PRIORITY, PriorityTier } from "../types/TestRecommendation.js";
2
- import { diversityBalancedBudgeter } from "./budgeters/diversityBalancedBudgeter.js";
3
- import { collectChangedRouteLines, findStepOnChangedRoute } from "../utils/changedRoutes.js";
4
- const PRIORITY_RANK = {
5
- CRITICAL: 0,
6
- HIGH: 1,
7
- MEDIUM: 2,
8
- LOW: 3,
9
- };
10
- const DEFAULT_CARVE_OUT_CATEGORIES = Object.keys(CATEGORY_PRIORITY).filter((category) => CATEGORY_PRIORITY[category] === PriorityTier.CRITICAL);
11
- /**
12
- * Rank test candidates for the register-plan checkpoint. Pure and fully
13
- * deterministic: the same set of candidates always yields the same order,
14
- * independent of input order (the final tiebreak is the stable `candidateId`).
15
- *
16
- * Ordering (highest first):
17
- * 1. Carve-out — CRITICAL-category scenarios (bug_caught, requirement_conflict), preserving the
18
- * protected-first convention of `roundRobinByType` /
19
- * `prioritizeAttackSurfaceBundles`.
20
- * 2. Verified discriminators — candidates whose declared discriminator survived
21
- * `validateDiscriminator` (marked via `verifiedDiscriminator`) float ahead of
22
- * unverified peers in the same tier.
23
- * 3. Diff-hunk proximity — candidates referencing a method+path that appears
24
- * on an actual changed diff line float ahead of same-tier candidates that
25
- * don't (SKYR-4026). Only applied when `opts.diffText` is supplied.
26
- * 4. `CATEGORY_PRIORITY` mapped over `scenario.category` (CRITICAL > HIGH >
27
- * MEDIUM > LOW).
28
- * 5. Stable tiebreak on `candidateId` for determinism.
29
- *
30
- * IMPORTANT: the agent-supplied `priority` field (`scenario.priority`) is
31
- * DELIBERATELY IGNORED here. Round-1 experiments found it systematically
32
- * miscalibrated — the agent labels genuinely discriminating tests "low" and
33
- * generic happy-path tests "high" — so ranking on it inverts the intended order.
34
- * Priority in this pipeline is derived from the scenario's category and from the
35
- * verified discriminator, never from the agent's self-assessment.
36
- */
37
- export function rankCandidates(candidates, opts = {}) {
38
- const carveOutSet = new Set(opts.carveOutCategories ?? DEFAULT_CARVE_OUT_CATEGORIES);
39
- const changedRoutes = opts.diffText ? collectChangedRouteLines(opts.diffText) : [];
40
- const onHunk = new Set(candidates
41
- .filter((c) => scenarioOnChangedHunk(c.scenario, changedRoutes))
42
- .map((c) => c.candidateId));
43
- return [...candidates].sort((a, b) => compareRank(a, b, carveOutSet, onHunk));
44
- }
45
- /**
46
- * Convenience composition: rank the candidates, split GENERATE vs ADDITIONAL via
47
- * the diversity-balanced budgeter, and surface the caller-supplied demotions on
48
- * the result. The caller (the register-plan tool) runs `validateDiscriminator`
49
- * first — setting `verifiedDiscriminator` on the candidates that passed and
50
- * passing the failed claims through `ctx.demotions`.
51
- */
52
- export function selectPlan(candidates, ctx, opts = {}) {
53
- const ranked = rankCandidates(candidates, { ...opts, diffText: opts.diffText ?? ctx.diffText });
54
- const result = diversityBalancedBudgeter.select(ranked, ctx);
55
- return { ...result, demotions: ctx.demotions ?? [] };
56
- }
57
- function compareRank(a, b, carveOutSet, onHunk) {
58
- const carveA = carveOutSet.has(a.scenario?.category) ? 0 : 1;
59
- const carveB = carveOutSet.has(b.scenario?.category) ? 0 : 1;
60
- if (carveA !== carveB)
61
- return carveA - carveB;
62
- const verifiedA = a.verifiedDiscriminator ? 0 : 1;
63
- const verifiedB = b.verifiedDiscriminator ? 0 : 1;
64
- if (verifiedA !== verifiedB)
65
- return verifiedA - verifiedB;
66
- const hunkA = onHunk.has(a.candidateId) ? 0 : 1;
67
- const hunkB = onHunk.has(b.candidateId) ? 0 : 1;
68
- if (hunkA !== hunkB)
69
- return hunkA - hunkB;
70
- const catRankA = categoryRank(a);
71
- const catRankB = categoryRank(b);
72
- if (catRankA !== catRankB)
73
- return catRankA - catRankB;
74
- return (a.candidateId ?? "").localeCompare(b.candidateId ?? "");
75
- }
76
- function categoryRank(candidate) {
77
- const tier = CATEGORY_PRIORITY[candidate.scenario?.category] ?? PriorityTier.LOW;
78
- return PRIORITY_RANK[tier];
79
- }
80
- /** Whether any step of `scenario` targets a method+path on the changed hunk. */
81
- function scenarioOnChangedHunk(scenario, changedRoutes) {
82
- return !!findStepOnChangedRoute(scenario.steps, changedRoutes);
83
- }
@@ -1,25 +0,0 @@
1
- import { ScenarioStep } from "../types/RepositoryAnalysis.js";
2
- import { Novelty, PriorityTier, ScenarioCategory } from "../types/TestRecommendation.js";
3
- import { Candidate } from "../types/Recommendation.js";
4
- /** Build a minimal ScenarioStep. */
5
- export declare function mkStep(method?: string, path?: string): ScenarioStep;
6
- interface CandidateOpts {
7
- priority?: PriorityTier;
8
- testType?: string;
9
- steps?: number;
10
- category?: ScenarioCategory;
11
- description?: string;
12
- novelty?: Novelty;
13
- }
14
- /**
15
- * Build a ranked Candidate for budgeter/ranker tests. Steps default to a
16
- * distinct path per candidate (so coverage keys don't collide) with the final
17
- * step as a mutation (so resolvePrimaryStep picks a stable primary step).
18
- *
19
- * Note the two distinct priority fields: `scenario.priority` is the agent's
20
- * own drafted label (lowercase — untrusted, ranking never reads it), while
21
- * `Candidate.priority` is the server-computed PriorityTier the rankers
22
- * actually order by.
23
- */
24
- export declare function mkCandidate(name: string, opts?: CandidateOpts): Candidate;
25
- export {};
@@ -1,45 +0,0 @@
1
- import { Novelty, PriorityTier, } from "../types/TestRecommendation.js";
2
- import { CandidateSource, computeCandidateId, } from "../types/Recommendation.js";
3
- /** Build a minimal ScenarioStep. */
4
- export function mkStep(method = "GET", path = "/r") {
5
- return {
6
- order: 0,
7
- method,
8
- path,
9
- description: "",
10
- interactionType: "success",
11
- expectedStatusCode: 200,
12
- };
13
- }
14
- /**
15
- * Build a ranked Candidate for budgeter/ranker tests. Steps default to a
16
- * distinct path per candidate (so coverage keys don't collide) with the final
17
- * step as a mutation (so resolvePrimaryStep picks a stable primary step).
18
- *
19
- * Note the two distinct priority fields: `scenario.priority` is the agent's
20
- * own drafted label (lowercase — untrusted, ranking never reads it), while
21
- * `Candidate.priority` is the server-computed PriorityTier the rankers
22
- * actually order by.
23
- */
24
- export function mkCandidate(name, opts = {}) {
25
- const stepCount = opts.steps ?? (opts.testType === "contract" ? 1 : 2);
26
- const steps = Array.from({ length: stepCount }, (_, i) => mkStep(i === stepCount - 1 ? "POST" : "GET", `/${name}`));
27
- const scenario = {
28
- scenarioName: name,
29
- description: opts.description ?? name,
30
- category: opts.category ?? "workflow",
31
- priority: "high",
32
- steps,
33
- chainingKeys: [],
34
- requiresAuth: false,
35
- estimatedComplexity: "moderate",
36
- ...(opts.testType ? { testType: opts.testType } : {}),
37
- };
38
- return {
39
- scenario,
40
- priority: opts.priority ?? PriorityTier.HIGH,
41
- novelty: opts.novelty ?? Novelty.EXISTING,
42
- source: CandidateSource.AGENT,
43
- candidateId: computeCandidateId(scenario),
44
- };
45
- }
@@ -1,28 +0,0 @@
1
- /**
2
- * Frontend component integration check types — SKYR-3855.
3
- *
4
- * Produced by `checkFrontendFileIntegration` (src/utils/frontendIntegration.ts)
5
- * and surfaced to the agent as `uiContext.frontendFileIntegration` in the
6
- * `skyramp_analyze_changes` output and state file.
7
- */
8
- /** String enum so the JSON wire format (state file, tool output) stays unchanged. */
9
- export declare enum IntegrationReason {
10
- /** Framework route/page/entrypoint file — reachable by convention, grep skipped. */
11
- RouteOrEntrypoint = "route-or-entrypoint",
12
- /** i18n/locale content file — often loaded via a runtime-templated path rather
13
- * than a static quoted import, so the grep-based check is skipped (SKYR-3977). */
14
- I18nLocaleContent = "i18n-locale-content",
15
- /** At least one production file imports/references the component's module path. */
16
- Imported = "imported",
17
- /** No production importer found — the component has no DOM presence in the running app. */
18
- NoImporters = "no-importers",
19
- /** The check could not run (missing file, scan failure) — reported integrated (fail-open). */
20
- ScanError = "scan-error"
21
- }
22
- export interface FrontendFileIntegration {
23
- file: string;
24
- integrated: boolean;
25
- /** Production files that import/reference the component, relative to repositoryPath. Capped at 10. */
26
- importers: string[];
27
- reason: IntegrationReason;
28
- }
@@ -1,22 +0,0 @@
1
- /**
2
- * Frontend component integration check types — SKYR-3855.
3
- *
4
- * Produced by `checkFrontendFileIntegration` (src/utils/frontendIntegration.ts)
5
- * and surfaced to the agent as `uiContext.frontendFileIntegration` in the
6
- * `skyramp_analyze_changes` output and state file.
7
- */
8
- /** String enum so the JSON wire format (state file, tool output) stays unchanged. */
9
- export var IntegrationReason;
10
- (function (IntegrationReason) {
11
- /** Framework route/page/entrypoint file — reachable by convention, grep skipped. */
12
- IntegrationReason["RouteOrEntrypoint"] = "route-or-entrypoint";
13
- /** i18n/locale content file — often loaded via a runtime-templated path rather
14
- * than a static quoted import, so the grep-based check is skipped (SKYR-3977). */
15
- IntegrationReason["I18nLocaleContent"] = "i18n-locale-content";
16
- /** At least one production file imports/references the component's module path. */
17
- IntegrationReason["Imported"] = "imported";
18
- /** No production importer found — the component has no DOM presence in the running app. */
19
- IntegrationReason["NoImporters"] = "no-importers";
20
- /** The check could not run (missing file, scan failure) — reported integrated (fail-open). */
21
- IntegrationReason["ScanError"] = "scan-error";
22
- })(IntegrationReason || (IntegrationReason = {}));
@@ -1,146 +0,0 @@
1
- import { DraftedScenario, SubjectEndpoint } from "../types/RepositoryAnalysis.js";
2
- import { Novelty, PriorityTier } from "../types/TestRecommendation.js";
3
- /**
4
- * The structural bug-shape a candidate claims to probe. Verified against the
5
- * candidate's `steps[]` by `validateDiscriminator` (discriminators.ts); a claim
6
- * that does not survive verification is demoted, never used to reject.
7
- */
8
- export declare enum DiscriminatorKind {
9
- BOUNDARY_EQUALITY = "boundary_equality",
10
- MULTI_RECORD_AGGREGATE = "multi_record_aggregate",
11
- STATE_TRANSITION = "state_transition",
12
- NEGATIVE_MATCH = "negative_match"
13
- }
14
- /** Who drafted a candidate: the LLM loop, or the MCP server's own pre-ranking. */
15
- export declare enum CandidateSource {
16
- AGENT = "agent",
17
- SERVER = "server"
18
- }
19
- /**
20
- * A ranked test candidate — the existing scored scenario plus the fields the
21
- * budgeting stage reads. Matches the shape produced today by the scoring block
22
- * in test-recommendation-prompt.ts (`{ scenario, priority, novelty }`), extended
23
- * for the SKYR-3879 Path B register-plan checkpoint with provenance, a stable id
24
- * and the verified-discriminator marker the ranker reads.
25
- */
26
- export interface Candidate {
27
- scenario: DraftedScenario;
28
- priority: PriorityTier;
29
- novelty: Novelty;
30
- /** Who drafted this candidate: the LLM loop or the MCP server. */
31
- source: CandidateSource;
32
- /**
33
- * Stable, deterministic id: slugified `scenarioName` + short content hash.
34
- * Reproducible across calls (same scenario → same id) so the generation-tool
35
- * plan guard can match a generated test back to its approved candidate.
36
- */
37
- candidateId: string;
38
- /**
39
- * Set by the caller AFTER `validateDiscriminator` succeeds for a declared
40
- * discriminator. Present only when the structural claim was verified against
41
- * `steps[]` and the diff anchor; the ranker floats these ahead of unverified
42
- * peers. Absent = no verified discriminator (unclaimed or demoted).
43
- */
44
- verifiedDiscriminator?: DiscriminatorKind;
45
- }
46
- /** Environment-derived inputs a Budgeter needs to split GENERATE vs ADDITIONAL. */
47
- export interface BudgetContext {
48
- /** Upper bound on GENERATE items (a strategy parameter, formerly the hard cap). */
49
- maxGenerate: number;
50
- /** Total recommendations (GENERATE + ADDITIONAL); today's `topN`. */
51
- maxTotal: number;
52
- isUIOnlyPR: boolean;
53
- hasFrontendChanges: boolean;
54
- /** Method-aware coverage keys of external tests; matching candidates are dropped. */
55
- externalCoverage: Set<string>;
56
- /**
57
- * Whether the PR's own diff changes any test file. External coverage is
58
- * discovered from the working tree, so a test the PR itself adds is
59
- * indistinguishable from pre-existing coverage; when this is true the
60
- * covered-candidate reserve stays shut rather than forcing duplicates of a
61
- * test the author already wrote.
62
- */
63
- diffChangesTestFiles: boolean;
64
- }
65
- /** A demoted claim: which candidate, and the human-readable reason returned to
66
- * the agent so it can fix the claim (never a silent drop of the claim itself).
67
- * A demotion records that a declared discriminator claim failed to verify.
68
- * It does not say whether the candidate is still in the plan. Budgeting
69
- * decides that separately. A candidate can be both demoted and dropped. */
70
- export interface Demotion {
71
- candidateId: string;
72
- reason: string;
73
- }
74
- /** A candidate the selection stage removed, and why. A demotion annotates a
75
- * claim. A drop removes a candidate from `generate` and `additional`. The
76
- * two are independent. The same candidate can carry both. */
77
- export interface Dropped {
78
- candidateId: string;
79
- reason: string;
80
- }
81
- /** Result of budgeting: the GENERATE set, the ADDITIONAL set, how many UI
82
- * placeholder slots the render layer should add (mixed/UI-only PRs), the
83
- * demoted-claim channel (the missing rejection surface for register-plan),
84
- * and the dropped-candidate channel (SKYR-4214: every candidate the
85
- * selection stage removed outright, with a human-actionable reason). */
86
- export interface SelectionResult {
87
- generate: Candidate[];
88
- additional: Candidate[];
89
- reservedUISlots: number;
90
- demotions: Demotion[];
91
- dropped: Dropped[];
92
- }
93
- /** A pluggable budgeting algorithm. Deterministic and pure. */
94
- export interface Budgeter {
95
- readonly name: string;
96
- select(ranked: Candidate[], ctx: BudgetContext): SelectionResult;
97
- }
98
- /**
99
- * Slug of a scenario name alone (no content hash) — stable across independent
100
- * drafts of "the same" scenario even when their steps[] differ (e.g. the
101
- * server's auto-drafted version vs. the agent's own re-drafted version of a
102
- * scenario with an identical name). Used to dedupe by scenario identity where
103
- * `computeCandidateId`'s content-sensitive hash would wrongly treat two drafts
104
- * of the same named scenario as distinct (SKYR-4026).
105
- */
106
- export declare function scenarioNameSlug(scenarioName: string | undefined): string;
107
- /**
108
- * Merge-dedup identity key for a scenario name — like {@link scenarioNameSlug}
109
- * but NOT truncated and with no shared fallback for a missing name. The 48-char
110
- * truncation in `scenarioNameSlug` is fine for `computeCandidateId` (combined
111
- * with a content hash for uniqueness), but used bare as a Map key it would
112
- * silently collapse two distinct scenarios sharing a long common prefix, and
113
- * collapse every candidate with no scenarioName onto the same key. Returns ""
114
- * for a missing name so the caller can fall back to something already unique
115
- * (e.g. candidateId) instead of a shared literal.
116
- */
117
- export declare function scenarioMergeKey(scenarioName: string | undefined): string;
118
- /**
119
- * Deterministic candidate id: a slug of the scenario name plus an 8-char content
120
- * hash. Pure and reproducible — the same scenario always yields the same id, so
121
- * a candidate registered in the plan can be matched back when its test is later
122
- * generated. The hash covers the salient, stable structural fields (name,
123
- * category, testType, and each step's method/path/interactionType/status) so
124
- * cosmetic re-serialization does not change the id while a real edit does.
125
- */
126
- export declare function computeCandidateId(scenario: DraftedScenario): string;
127
- /** One approved plan item as persisted in `UnifiedAnalysisState.approvedPlan`.
128
- * Deliberately slim (no steps/requestBody/etc.) — `matchKeys` already carries
129
- * everything a downstream matcher needs.
130
- *
131
- * `subjectEndpoints` is the one exception. `matchKeys` holds one key per
132
- * step and is deliberately broad, so a generated test can be matched back
133
- * to its plan item. It does not say which step is the subject. Dedup
134
- * judges the subject, not the whole step list, so the plan item must
135
- * record it too. See `DraftedScenario.subjectEndpoints` for the
136
- * absent-vs-empty contract this field carries over unchanged. */
137
- export interface ApprovedPlanItem {
138
- candidateId: string;
139
- scenarioName: string;
140
- testType: string;
141
- category: string;
142
- source: CandidateSource;
143
- verifiedDiscriminator?: string;
144
- subjectEndpoints?: SubjectEndpoint[];
145
- matchKeys: string[];
146
- }
@@ -1,74 +0,0 @@
1
- import * as crypto from "crypto";
2
- /**
3
- * The structural bug-shape a candidate claims to probe. Verified against the
4
- * candidate's `steps[]` by `validateDiscriminator` (discriminators.ts); a claim
5
- * that does not survive verification is demoted, never used to reject.
6
- */
7
- export var DiscriminatorKind;
8
- (function (DiscriminatorKind) {
9
- DiscriminatorKind["BOUNDARY_EQUALITY"] = "boundary_equality";
10
- DiscriminatorKind["MULTI_RECORD_AGGREGATE"] = "multi_record_aggregate";
11
- DiscriminatorKind["STATE_TRANSITION"] = "state_transition";
12
- DiscriminatorKind["NEGATIVE_MATCH"] = "negative_match";
13
- })(DiscriminatorKind || (DiscriminatorKind = {}));
14
- /** Who drafted a candidate: the LLM loop, or the MCP server's own pre-ranking. */
15
- export var CandidateSource;
16
- (function (CandidateSource) {
17
- CandidateSource["AGENT"] = "agent";
18
- CandidateSource["SERVER"] = "server";
19
- })(CandidateSource || (CandidateSource = {}));
20
- /**
21
- * Slug of a scenario name alone (no content hash) — stable across independent
22
- * drafts of "the same" scenario even when their steps[] differ (e.g. the
23
- * server's auto-drafted version vs. the agent's own re-drafted version of a
24
- * scenario with an identical name). Used to dedupe by scenario identity where
25
- * `computeCandidateId`'s content-sensitive hash would wrongly treat two drafts
26
- * of the same named scenario as distinct (SKYR-4026).
27
- */
28
- export function scenarioNameSlug(scenarioName) {
29
- return ((scenarioName ?? "")
30
- .toLowerCase()
31
- .replace(/[^a-z0-9]+/g, "-")
32
- .replace(/^-+|-+$/g, "")
33
- .slice(0, 48) || "scenario");
34
- }
35
- /**
36
- * Merge-dedup identity key for a scenario name — like {@link scenarioNameSlug}
37
- * but NOT truncated and with no shared fallback for a missing name. The 48-char
38
- * truncation in `scenarioNameSlug` is fine for `computeCandidateId` (combined
39
- * with a content hash for uniqueness), but used bare as a Map key it would
40
- * silently collapse two distinct scenarios sharing a long common prefix, and
41
- * collapse every candidate with no scenarioName onto the same key. Returns ""
42
- * for a missing name so the caller can fall back to something already unique
43
- * (e.g. candidateId) instead of a shared literal.
44
- */
45
- export function scenarioMergeKey(scenarioName) {
46
- return (scenarioName ?? "")
47
- .toLowerCase()
48
- .replace(/[^a-z0-9]+/g, "-")
49
- .replace(/^-+|-+$/g, "");
50
- }
51
- /**
52
- * Deterministic candidate id: a slug of the scenario name plus an 8-char content
53
- * hash. Pure and reproducible — the same scenario always yields the same id, so
54
- * a candidate registered in the plan can be matched back when its test is later
55
- * generated. The hash covers the salient, stable structural fields (name,
56
- * category, testType, and each step's method/path/interactionType/status) so
57
- * cosmetic re-serialization does not change the id while a real edit does.
58
- */
59
- export function computeCandidateId(scenario) {
60
- const slug = scenarioNameSlug(scenario.scenarioName);
61
- const canonical = {
62
- scenarioName: scenario.scenarioName ?? "",
63
- category: scenario.category ?? "",
64
- testType: scenario.testType ?? "",
65
- steps: (scenario.steps ?? []).map((s) => ({
66
- method: s?.method ?? "",
67
- path: s?.path ?? "",
68
- interactionType: s?.interactionType ?? "",
69
- expectedStatusCode: s?.expectedStatusCode ?? 0,
70
- })),
71
- };
72
- const hash = crypto.createHash("sha256").update(JSON.stringify(canonical)).digest("hex").slice(0, 8);
73
- return `${slug}-${hash}`;
74
- }