@skyramp/mcp 0.1.8 → 0.2.0-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.
Files changed (122) hide show
  1. package/build/index.js +4 -2
  2. package/build/playwright/registerPlaywrightTools.js +12 -0
  3. package/build/playwright/traceRecordingPrompt.js +15 -0
  4. package/build/prompts/code-reuse.js +106 -7
  5. package/build/prompts/pom-aware-code-reuse.js +106 -7
  6. package/build/prompts/startTraceCollectionPrompts.js +37 -15
  7. package/build/prompts/test-maintenance/drift-analysis-prompt.js +26 -31
  8. package/build/prompts/test-maintenance/drift-analysis-prompt.test.js +40 -1
  9. package/build/prompts/test-maintenance/driftAnalysisSections.js +90 -86
  10. package/build/prompts/test-recommendation/analysisOutputPrompt.js +286 -163
  11. package/build/prompts/test-recommendation/analysisOutputPrompt.test.js +154 -45
  12. package/build/prompts/test-recommendation/diffExecutionPlan.js +246 -117
  13. package/build/prompts/test-recommendation/promptPlan.js +290 -0
  14. package/build/prompts/test-recommendation/promptPlan.test.js +336 -0
  15. package/build/prompts/test-recommendation/recommendationSections.js +4 -3
  16. package/build/prompts/test-recommendation/recommendationShared.js +23 -1
  17. package/build/prompts/test-recommendation/scopeAssessment.js +65 -14
  18. package/build/prompts/test-recommendation/scopeAssessment.test.js +93 -2
  19. package/build/prompts/test-recommendation/test-recommendation-prompt.js +36 -12
  20. package/build/prompts/test-recommendation/test-recommendation-prompt.test.js +316 -1
  21. package/build/prompts/testbot/testbot-prompts.js +73 -13
  22. package/build/prompts/testbot/testbot-prompts.test.js +114 -1
  23. package/build/resources/testbotResource.js +1 -1
  24. package/build/services/ScenarioGenerationService.integration.test.js +158 -0
  25. package/build/services/ScenarioGenerationService.js +47 -4
  26. package/build/services/ScenarioGenerationService.test.js +158 -22
  27. package/build/services/TestExecutionService.js +73 -15
  28. package/build/services/TestExecutionService.test.js +105 -0
  29. package/build/services/TestGenerationService.js +11 -1
  30. package/build/tools/executeSkyrampTestTool.js +1 -10
  31. package/build/tools/generate-tests/generateBatchScenarioRestTool.js +16 -4
  32. package/build/tools/generate-tests/generateIntegrationRestTool.js +2 -0
  33. package/build/tools/generate-tests/generateUIRestTool.js +2 -0
  34. package/build/tools/test-management/actionsTool.js +152 -63
  35. package/build/tools/test-management/analyzeChangesTool.js +178 -64
  36. package/build/tools/test-management/analyzeChangesTool.test.js +103 -16
  37. package/build/tools/test-management/analyzeTestHealthTool.js +30 -81
  38. package/build/tools/test-management/index.js +1 -0
  39. package/build/tools/test-management/uiAnalyzeChangesTool.js +149 -0
  40. package/build/tools/test-management/uiAnalyzeChangesTool.test.js +100 -0
  41. package/build/tools/trace/resolveSaveStoragePath.js +16 -0
  42. package/build/tools/trace/resolveSaveStoragePath.test.js +17 -0
  43. package/build/tools/trace/resolveSessionPaths.js +39 -0
  44. package/build/tools/trace/resolveSessionPaths.test.js +103 -0
  45. package/build/tools/trace/sessionState.js +14 -0
  46. package/build/tools/trace/sessionState.test.js +17 -0
  47. package/build/tools/trace/startTraceCollectionTool.js +84 -14
  48. package/build/tools/trace/stopTraceCollectionTool.js +9 -2
  49. package/build/types/TestAnalysis.js +50 -0
  50. package/build/types/TestRecommendation.js +6 -58
  51. package/build/types/TestTypes.js +1 -1
  52. package/build/utils/AnalysisStateManager.js +22 -11
  53. package/build/utils/branchDiff.js +11 -2
  54. package/build/utils/docker.test.js +1 -1
  55. package/build/utils/gitStaging.js +52 -3
  56. package/build/utils/gitStaging.test.js +19 -1
  57. package/build/utils/repoScanner.js +18 -10
  58. package/build/utils/repoScanner.test.js +92 -0
  59. package/build/utils/routeParsers.js +180 -25
  60. package/build/utils/routeParsers.test.js +180 -1
  61. package/build/utils/scenarioDrafting.js +220 -17
  62. package/build/utils/scenarioDrafting.test.js +182 -9
  63. package/build/utils/sourceRouteExtractor.js +806 -0
  64. package/build/utils/sourceRouteExtractor.test.js +565 -0
  65. package/build/utils/uiPageEnumerator.js +319 -0
  66. package/build/utils/uiPageEnumerator.test.js +422 -0
  67. package/build/utils/utils.js +27 -0
  68. package/build/utils/versions.js +1 -1
  69. package/build/utils/workspaceAuth.js +33 -4
  70. package/node_modules/playwright/ThirdPartyNotices.txt +6 -6
  71. package/node_modules/playwright/lib/dom-analyzer/analyze.js +111 -0
  72. package/node_modules/playwright/lib/dom-analyzer/blueprint.js +1210 -0
  73. package/node_modules/playwright/lib/dom-analyzer/blueprint.test.js +396 -0
  74. package/node_modules/playwright/lib/dom-analyzer/blueprintCache.js +57 -0
  75. package/node_modules/playwright/lib/dom-analyzer/blueprintCache.test.js +57 -0
  76. package/node_modules/playwright/lib/dom-analyzer/blueprintDiff.js +254 -0
  77. package/node_modules/playwright/lib/dom-analyzer/blueprintDiff.test.js +304 -0
  78. package/node_modules/playwright/lib/dom-analyzer/crawler.js +384 -0
  79. package/node_modules/playwright/lib/dom-analyzer/curatedWidgets.js +73 -0
  80. package/node_modules/playwright/lib/dom-analyzer/dynamicId.js +43 -0
  81. package/node_modules/playwright/lib/dom-analyzer/dynamicId.test.js +85 -0
  82. package/node_modules/playwright/lib/dom-analyzer/fingerprint.js +90 -0
  83. package/node_modules/playwright/lib/dom-analyzer/fingerprint.test.js +231 -0
  84. package/node_modules/playwright/lib/dom-analyzer/fingerprintAblation.fixtures.js +145 -0
  85. package/node_modules/playwright/lib/dom-analyzer/fingerprintAblation.test.js +41 -0
  86. package/node_modules/playwright/lib/dom-analyzer/graph.js +36 -0
  87. package/node_modules/playwright/lib/dom-analyzer/liveFingerprints.js +43 -0
  88. package/node_modules/playwright/lib/dom-analyzer/logicalNameResolver.js +72 -0
  89. package/node_modules/playwright/lib/dom-analyzer/logicalNameResolver.test.js +182 -0
  90. package/node_modules/playwright/lib/dom-analyzer/possibleAssertions.js +150 -0
  91. package/node_modules/playwright/lib/dom-analyzer/possibleAssertions.test.js +470 -0
  92. package/node_modules/playwright/lib/dom-analyzer/sectionGrouper.js +169 -0
  93. package/node_modules/playwright/lib/dom-analyzer/sectionGrouper.test.js +269 -0
  94. package/node_modules/playwright/lib/dom-analyzer/serialization.js +75 -0
  95. package/node_modules/playwright/lib/dom-analyzer/slug.js +30 -0
  96. package/node_modules/playwright/lib/dom-analyzer/slug.test.js +84 -0
  97. package/node_modules/playwright/lib/dom-analyzer/widgetContract.js +127 -0
  98. package/node_modules/playwright/lib/dom-analyzer/widgetContract.test.js +212 -0
  99. package/node_modules/playwright/lib/mcp/browser/browserContextFactory.js +3 -1
  100. package/node_modules/playwright/lib/mcp/browser/config.js +1 -1
  101. package/node_modules/playwright/lib/mcp/browser/context.js +17 -1
  102. package/node_modules/playwright/lib/mcp/browser/tab.js +38 -0
  103. package/node_modules/playwright/lib/mcp/browser/tools/domAnalyzer.js +261 -0
  104. package/node_modules/playwright/lib/mcp/browser/tools/keyboard.js +3 -3
  105. package/node_modules/playwright/lib/mcp/browser/tools/pageBlueprint.js +146 -0
  106. package/node_modules/playwright/lib/mcp/browser/tools/pageBlueprint.test.js +140 -0
  107. package/node_modules/playwright/lib/mcp/browser/tools/sitemap.js +226 -0
  108. package/node_modules/playwright/lib/mcp/browser/tools/snapshot.js +2 -2
  109. package/node_modules/playwright/lib/mcp/browser/tools/widgetContract.js +168 -0
  110. package/node_modules/playwright/lib/mcp/browser/tools.js +6 -0
  111. package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +52 -12
  112. package/node_modules/playwright/lib/mcp/test/skyRampExport.js +64 -13
  113. package/node_modules/playwright/package.json +1 -1
  114. package/node_modules/playwright/skyramp-playwright-1.58.2-skyramp.8.9.3.tgz +0 -0
  115. package/node_modules/playwright/skyramp-playwright-1.58.2-skyramp.8.9.4.tgz +0 -0
  116. package/node_modules/playwright/skyramp-playwright-1.58.2-skyramp.8.9.5.tgz +0 -0
  117. package/node_modules/playwright/skyramp-playwright-1.58.2-skyramp.8.9.6.tgz +0 -0
  118. package/package.json +3 -3
  119. package/build/services/TestHealthService.js +0 -694
  120. package/build/services/TestHealthService.test.js +0 -241
  121. package/build/types/TestDriftAnalysis.js +0 -1
  122. package/build/types/TestHealth.js +0 -4
@@ -1,7 +1,7 @@
1
1
  jest.mock("@skyramp/skyramp", () => ({
2
2
  WorkspaceConfigManager: { create: jest.fn() },
3
3
  }));
4
- import { buildAnalysisOutputText } from "./analysisOutputPrompt.js";
4
+ import { buildAnalysisOutputText, getStepTraceCallers, ANALYSIS_STEP_READ_FILES, ANALYSIS_STEP_RESOLVE_PATHS, ANALYSIS_STEP_EXTRACT, ANALYSIS_STEP_DRAFT, ANALYSIS_STEP_CALL_TOOL, } from "./analysisOutputPrompt.js";
5
5
  import { AnalysisScope } from "../../types/RepositoryAnalysis.js";
6
6
  // ---------------------------------------------------------------------------
7
7
  // Minimal fixture factory
@@ -26,10 +26,62 @@ function baseParams(overrides = {}) {
26
26
  };
27
27
  }
28
28
  // ---------------------------------------------------------------------------
29
- // Step 2.3 caller-tracing block
29
+ // Static step label constants
30
30
  // ---------------------------------------------------------------------------
31
- describe("buildAnalysisOutputTextunmatchedFiles / Step 2.3 caller-tracing", () => {
32
- it("includes Step 2.3 block when unmatchedFiles is non-empty and scope is CurrentBranchDiff", () => {
31
+ describe("analysisOutputPromptstatic step label constants", () => {
32
+ it("exports sequential main-step labels", () => {
33
+ expect(ANALYSIS_STEP_READ_FILES).toBe("1");
34
+ expect(ANALYSIS_STEP_EXTRACT).toBe("2");
35
+ expect(ANALYSIS_STEP_DRAFT).toBe("3");
36
+ expect(ANALYSIS_STEP_CALL_TOOL).toBe("4");
37
+ });
38
+ it("exports sequential sub-step label for RESOLVE_PATHS", () => {
39
+ expect(ANALYSIS_STEP_RESOLVE_PATHS).toBe("1.1");
40
+ });
41
+ });
42
+ // ---------------------------------------------------------------------------
43
+ // getStepTraceCallers() — dynamic label for the conditional sub-step
44
+ // ---------------------------------------------------------------------------
45
+ describe("getStepTraceCallers()", () => {
46
+ it("returns '2.1' when non-frontend unmatched backend files are present in diff scope", () => {
47
+ const params = baseParams({
48
+ unmatchedFiles: ["server/helpers/DataUtils.java"],
49
+ });
50
+ expect(getStepTraceCallers(params)).toBe("2.1");
51
+ });
52
+ it("returns null when unmatchedFiles is empty", () => {
53
+ const params = baseParams({ unmatchedFiles: [] });
54
+ expect(getStepTraceCallers(params)).toBeNull();
55
+ });
56
+ it("returns null when unmatchedFiles is undefined", () => {
57
+ const params = baseParams({ unmatchedFiles: undefined });
58
+ expect(getStepTraceCallers(params)).toBeNull();
59
+ });
60
+ it("returns null when scope is full_repo even if unmatchedFiles is non-empty", () => {
61
+ const params = baseParams({
62
+ analysisScope: AnalysisScope.FullRepo,
63
+ unmatchedFiles: ["src/services/SomeService.ts"],
64
+ });
65
+ expect(getStepTraceCallers(params)).toBeNull();
66
+ });
67
+ it("returns null when unmatchedFiles contains only frontend component files", () => {
68
+ const params = baseParams({
69
+ unmatchedFiles: ["src/components/Button.tsx", "src/pages/Dashboard.jsx"],
70
+ });
71
+ expect(getStepTraceCallers(params)).toBeNull();
72
+ });
73
+ it("returns null when unmatchedFiles contains only non-code files", () => {
74
+ const params = baseParams({
75
+ unmatchedFiles: ["README.md", "package.json", "docker-compose.yml"],
76
+ });
77
+ expect(getStepTraceCallers(params)).toBeNull();
78
+ });
79
+ });
80
+ // ---------------------------------------------------------------------------
81
+ // Trace-callers sub-step output (Step 2.1)
82
+ // ---------------------------------------------------------------------------
83
+ describe("buildAnalysisOutputText — unmatchedFiles / trace-callers sub-step", () => {
84
+ it("includes the trace-callers block when unmatchedFiles is non-empty and scope is CurrentBranchDiff", () => {
33
85
  const params = baseParams({
34
86
  unmatchedFiles: [
35
87
  "server/src/main/java/helpers/DataUtils.java",
@@ -37,12 +89,14 @@ describe("buildAnalysisOutputText — unmatchedFiles / Step 2.3 caller-tracing",
37
89
  ],
38
90
  });
39
91
  const output = buildAnalysisOutputText(params);
40
- expect(output).toContain("### Step 2.3: Trace callers of changed non-route files");
92
+ const label = getStepTraceCallers(params);
93
+ expect(label).not.toBeNull();
94
+ expect(output).toContain(`### Step ${label}: Trace callers of changed non-route files`);
41
95
  expect(output).toContain("DataUtils.java");
42
96
  expect(output).toContain("MustacheHelper.java");
43
97
  expect(output).toContain("/execute");
44
98
  });
45
- it("lists each unmatched file as a bullet in the Step 2.3 block", () => {
99
+ it("lists each unmatched file as a bullet in the trace-callers block", () => {
46
100
  const params = baseParams({
47
101
  unmatchedFiles: ["src/services/OrderService.ts", "src/utils/pricingHelper.ts"],
48
102
  });
@@ -50,46 +104,61 @@ describe("buildAnalysisOutputText — unmatchedFiles / Step 2.3 caller-tracing",
50
104
  expect(output).toContain("- `src/services/OrderService.ts`");
51
105
  expect(output).toContain("- `src/utils/pricingHelper.ts`");
52
106
  });
53
- it("omits Step 2.3 block when unmatchedFiles is empty", () => {
107
+ it("omits the trace-callers block when unmatchedFiles is empty", () => {
54
108
  const params = baseParams({ unmatchedFiles: [] });
55
109
  const output = buildAnalysisOutputText(params);
56
- expect(output).not.toContain("Step 2.3");
110
+ expect(getStepTraceCallers(params)).toBeNull();
57
111
  expect(output).not.toContain("Trace callers of changed non-route files");
58
112
  });
59
- it("omits Step 2.3 block when unmatchedFiles is undefined", () => {
113
+ it("omits the trace-callers block when unmatchedFiles is undefined", () => {
60
114
  const params = baseParams({ unmatchedFiles: undefined });
61
115
  const output = buildAnalysisOutputText(params);
62
- expect(output).not.toContain("Step 2.3");
116
+ expect(getStepTraceCallers(params)).toBeNull();
117
+ expect(output).not.toContain("Trace callers of changed non-route files");
63
118
  });
64
- it("omits Step 2.3 block when scope is full_repo even if unmatchedFiles is non-empty", () => {
119
+ it("omits the trace-callers block when scope is full_repo even if unmatchedFiles is non-empty", () => {
65
120
  const params = baseParams({
66
121
  analysisScope: AnalysisScope.FullRepo,
67
122
  unmatchedFiles: ["src/services/SomeService.ts"],
68
123
  });
69
124
  const output = buildAnalysisOutputText(params);
70
- expect(output).not.toContain("Step 2.3");
125
+ expect(getStepTraceCallers(params)).toBeNull();
126
+ expect(output).not.toContain("Trace callers of changed non-route files");
71
127
  });
72
- it("Step 2.3 appears before Step 2.5 in the output", () => {
128
+ it("trace-callers sub-step appears after the extract step in the output (phase 4b — no Step 2.5)", () => {
129
+ // This test uses no diff — falls back to the source-files branch.
73
130
  const params = baseParams({
74
131
  unmatchedFiles: ["src/utils/helper.ts"],
75
132
  });
76
133
  const output = buildAnalysisOutputText(params);
77
- const pos23 = output.indexOf("Step 2.3");
78
- const pos25 = output.indexOf("Step 2.5");
79
- expect(pos23).toBeGreaterThan(-1);
80
- expect(pos25).toBeGreaterThan(-1);
81
- expect(pos23).toBeLessThan(pos25);
82
- });
83
- it("Step 2.5 critical-patterns block is always present regardless of unmatchedFiles", () => {
84
- const withUnmatched = buildAnalysisOutputText(baseParams({ unmatchedFiles: ["src/utils/foo.ts"] }));
85
- const withoutUnmatched = buildAnalysisOutputText(baseParams({ unmatchedFiles: [] }));
86
- expect(withUnmatched).toContain("Step 2.5: Identify critical patterns");
87
- expect(withoutUnmatched).toContain("Step 2.5: Identify critical patterns");
88
- });
89
- it("omits Step 2.3 block when unmatchedFiles contains only frontend component files (UI-only PR)", () => {
90
- // Frontend files (.tsx, .jsx, .vue, .svelte) end up in unmatchedFiles because they
91
- // have no route annotations, but they have no HTTP callers to trace — emitting
92
- // Step 2.3 for them would produce irrelevant instructions. (Copilot review fix)
134
+ const label = getStepTraceCallers(params);
135
+ expect(label).not.toBeNull();
136
+ expect(output).toContain(`Step ${label}`);
137
+ expect(output).not.toContain("Step 2.5");
138
+ });
139
+ it("critical-patterns checklist is present in Step 2 regardless of diff availability (phase 4b)", () => {
140
+ // Step 2.5 was merged into Step 2 patterns appear inline in ALL diff-scope branches.
141
+ const withDiff = baseParams({ unmatchedFiles: ["src/utils/foo.ts"], diffFilePath: "/tmp/pr.diff" });
142
+ const withDiffNoUnmatched = baseParams({ unmatchedFiles: [], diffFilePath: "/tmp/pr.diff" });
143
+ const noDiff = baseParams({ unmatchedFiles: [] });
144
+ const uiOnly = baseParams({
145
+ parsedDiff: {
146
+ changedFiles: ["src/components/Cart.tsx", "src/components/OrderList.vue"],
147
+ newEndpoints: [],
148
+ modifiedEndpoints: [],
149
+ },
150
+ unmatchedFiles: [],
151
+ });
152
+ expect(buildAnalysisOutputText(withDiff)).toContain("While reading, also note these patterns");
153
+ expect(buildAnalysisOutputText(withDiffNoUnmatched)).toContain("While reading, also note these patterns");
154
+ expect(buildAnalysisOutputText(noDiff)).toContain("While reading, also note these patterns");
155
+ expect(buildAnalysisOutputText(uiOnly)).toContain("While reading, also note these patterns");
156
+ // Step 2.5 header should never appear
157
+ expect(buildAnalysisOutputText(withDiff)).not.toContain("Step 2.5");
158
+ expect(buildAnalysisOutputText(noDiff)).not.toContain("Step 2.5");
159
+ expect(buildAnalysisOutputText(uiOnly)).not.toContain("Step 2.5");
160
+ });
161
+ it("omits trace-callers block when unmatchedFiles contains only frontend component files (UI-only PR)", () => {
93
162
  const params = baseParams({
94
163
  unmatchedFiles: [
95
164
  "src/components/Button.tsx",
@@ -99,12 +168,10 @@ describe("buildAnalysisOutputText — unmatchedFiles / Step 2.3 caller-tracing",
99
168
  ],
100
169
  });
101
170
  const output = buildAnalysisOutputText(params);
102
- expect(output).not.toContain("Step 2.3");
171
+ expect(getStepTraceCallers(params)).toBeNull();
103
172
  expect(output).not.toContain("Trace callers of changed non-route files");
104
173
  });
105
- it("omits Step 2.3 block when unmatchedFiles contains only non-code files (docs/config)", () => {
106
- // README.md, package.json, etc. have no changed symbols to trace — listing them
107
- // in Step 2.3 is misleading. (Copilot review fix)
174
+ it("omits trace-callers block when unmatchedFiles contains only non-code files (docs/config)", () => {
108
175
  const params = baseParams({
109
176
  unmatchedFiles: [
110
177
  "README.md",
@@ -114,12 +181,12 @@ describe("buildAnalysisOutputText — unmatchedFiles / Step 2.3 caller-tracing",
114
181
  ],
115
182
  });
116
183
  const output = buildAnalysisOutputText(params);
117
- expect(output).not.toContain("Step 2.3");
184
+ expect(getStepTraceCallers(params)).toBeNull();
118
185
  expect(output).not.toContain("Trace callers of changed non-route files");
119
186
  });
120
- it("emits Step 2.3 for backend code files but excludes frontend/non-code siblings", () => {
187
+ it("emits trace-callers for backend code files but excludes frontend/non-code siblings", () => {
121
188
  // Mixed PR: one Java helper + one React component + one config file.
122
- // Only the Java file should appear in the Step 2.3 bullets.
189
+ // Only the Java file should appear in the trace-callers bullets.
123
190
  const params = baseParams({
124
191
  unmatchedFiles: [
125
192
  "server/helpers/DataUtils.java",
@@ -128,18 +195,32 @@ describe("buildAnalysisOutputText — unmatchedFiles / Step 2.3 caller-tracing",
128
195
  ],
129
196
  });
130
197
  const output = buildAnalysisOutputText(params);
131
- expect(output).toContain("Step 2.3");
198
+ const label = getStepTraceCallers(params);
199
+ expect(label).not.toBeNull();
200
+ expect(output).toContain(`Step ${label}`);
132
201
  expect(output).toContain("DataUtils.java");
133
202
  expect(output).not.toContain("ActionButton.tsx");
134
203
  expect(output).not.toContain("package.json");
135
204
  });
136
- it("omits Step 2.3 when unmatchedFiles contains .ts/.js frontend files but isUIOnly is true", () => {
137
- // Angular services, React hooks, Vue composables all .ts/.js — pass the
138
- // BACKEND_CODE_EXT filter but belong to a UI-only PR. The !isUIOnly guard
139
- // prevents Step 2.3 from emitting contradictory caller-tracing instructions
140
- // alongside the UI-only Step 2 guidance. (Copilot review fix)
205
+ it("omits trace-callers when unmatchedFiles contains frontend .ts/.js files and isUIOnly is true", () => {
206
+ // Files in recognized frontend directories (components/, pages/) are classified
207
+ // as frontend by isFrontendFile() making this a UI-only PR.
208
+ const params = baseParams({
209
+ parsedDiff: {
210
+ changedFiles: ["src/components/AuthForm.ts", "src/pages/LoginPage.ts"],
211
+ newEndpoints: [],
212
+ modifiedEndpoints: [],
213
+ },
214
+ unmatchedFiles: ["src/components/AuthForm.ts", "src/pages/LoginPage.ts"],
215
+ });
216
+ const output = buildAnalysisOutputText(params);
217
+ expect(getStepTraceCallers(params)).toBeNull();
218
+ expect(output).not.toContain("Trace callers of changed non-route files");
219
+ });
220
+ it("does NOT treat backend .ts/.js files as UI-only (directory-aware heuristic)", () => {
221
+ // Files in src/services/ or src/hooks/ are NOT in frontend directories —
222
+ // they should NOT trigger isUIOnly, so trace-callers should fire.
141
223
  const params = baseParams({
142
- // parsedDiff.changedFiles drives isUIOnly detection; all frontend-ext → isUIOnly=true
143
224
  parsedDiff: {
144
225
  changedFiles: ["src/services/auth.service.ts", "src/hooks/useAuth.ts"],
145
226
  newEndpoints: [],
@@ -148,7 +229,35 @@ describe("buildAnalysisOutputText — unmatchedFiles / Step 2.3 caller-tracing",
148
229
  unmatchedFiles: ["src/services/auth.service.ts", "src/hooks/useAuth.ts"],
149
230
  });
150
231
  const output = buildAnalysisOutputText(params);
151
- expect(output).not.toContain("Step 2.3");
152
- expect(output).not.toContain("Trace callers of changed non-route files");
232
+ expect(getStepTraceCallers(params)).not.toBeNull();
233
+ expect(output).toContain("Trace callers of changed non-route files");
234
+ });
235
+ });
236
+ describe("buildAnalysisOutputText — scanner fallback", () => {
237
+ it("includes manual endpoint discovery in diff scope when candidate route files are present despite scanned endpoints", () => {
238
+ const params = baseParams({
239
+ analysisScope: AnalysisScope.CurrentBranchDiff,
240
+ scannedEndpoints: [{ path: "/openapi-only", methods: ["GET"], sourceFile: "" }],
241
+ candidateRouteFiles: ["src/routes/items.ts"],
242
+ });
243
+ const output = buildAnalysisOutputText(params);
244
+ expect(output).toContain("Scanner Fallback — Manual Endpoint Discovery Required");
245
+ expect(output).toContain("may have missed route/controller files");
246
+ expect(output).toContain("src/routes/items.ts");
247
+ expect(output).toContain("supplemental gap check");
248
+ expect(output).toContain("keep the existing catalog as the primary endpoint source");
249
+ expect(output).not.toContain("use it as the authoritative endpoint list");
250
+ });
251
+ it("tells fallback discovery not to turn GraphQL artifacts into REST endpoints", () => {
252
+ const params = baseParams({
253
+ analysisScope: AnalysisScope.CurrentBranchDiff,
254
+ scannedEndpoints: [],
255
+ candidateRouteFiles: ["src/graphql/users.resolver.ts"],
256
+ });
257
+ const output = buildAnalysisOutputText(params);
258
+ expect(output).toContain("GraphQL: schema/resolver artifacts are unsupported for REST test generation");
259
+ expect(output).toContain("do not invent REST endpoints from them");
260
+ expect(output).toContain("use it as the authoritative endpoint list");
261
+ expect(output).not.toContain("Query/Mutation resolvers mapping to endpoints");
153
262
  });
154
263
  });