@skyramp/mcp 0.2.8 → 0.2.150-rc.local.de

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 (93) hide show
  1. package/build/commands/commandLibrary.d.ts +1 -0
  2. package/build/commands/commandLibrary.js +18 -17
  3. package/build/commands/localDevTestChangesCommand.d.ts +15 -0
  4. package/build/commands/localDevTestChangesCommand.js +175 -0
  5. package/build/index.js +74 -13
  6. package/build/prompts/enhance-assertions/sharedAssertionRules.js +1 -0
  7. package/build/prompts/initialize-workspace/initializeWorkspacePrompt.js +7 -6
  8. package/build/prompts/local-dev/local-dev-plan.d.ts +25 -0
  9. package/build/prompts/local-dev/local-dev-plan.js +414 -0
  10. package/build/prompts/local-dev/local-dev-prompts.d.ts +4 -0
  11. package/build/prompts/local-dev/local-dev-prompts.js +152 -0
  12. package/build/prompts/prompt-utils.d.ts +8 -0
  13. package/build/prompts/prompt-utils.js +33 -0
  14. package/build/prompts/sut-setup/modes/adaptWorkflowPrompt.js +3 -1
  15. package/build/prompts/sut-setup/modes/dockerComposePrompt.js +3 -1
  16. package/build/prompts/sut-setup/shared.d.ts +20 -0
  17. package/build/prompts/sut-setup/shared.js +69 -7
  18. package/build/prompts/test-recommendation/analysisOutputPrompt.js +21 -29
  19. package/build/prompts/test-recommendation/analysisOutputPrompt.test.js +79 -16
  20. package/build/prompts/test-recommendation/test-recommendation-prompt.js +41 -4
  21. package/build/prompts/test-recommendation/test-recommendation-prompt.test.js +34 -1
  22. package/build/prompts/testbot/testbot-prompts.d.ts +0 -5
  23. package/build/prompts/testbot/testbot-prompts.js +4 -34
  24. package/build/resources/testbotResource.js +2 -1
  25. package/build/services/TestExecutionService.d.ts +10 -2
  26. package/build/services/TestExecutionService.js +220 -28
  27. package/build/services/TestExecutionService.test.js +447 -16
  28. package/build/services/TestGenerationService.d.ts +2 -2
  29. package/build/tool-phases.js +4 -0
  30. package/build/tools/code-refactor/enhanceAssertionsTool.js +34 -2
  31. package/build/tools/enrichTestWithMocksTool.d.ts +15 -0
  32. package/build/tools/enrichTestWithMocksTool.js +414 -0
  33. package/build/tools/enrichTestWithMocksTool.test.d.ts +1 -0
  34. package/build/tools/enrichTestWithMocksTool.test.js +397 -0
  35. package/build/tools/executeSkyrampTestTool.d.ts +3 -0
  36. package/build/tools/executeSkyrampTestTool.js +73 -18
  37. package/build/tools/executeSkyrampTestTool.test.d.ts +1 -0
  38. package/build/tools/executeSkyrampTestTool.test.js +111 -0
  39. package/build/tools/generate-tests/batchMockGenerationTool.d.ts +72 -0
  40. package/build/tools/generate-tests/batchMockGenerationTool.js +382 -0
  41. package/build/tools/generate-tests/batchMockGenerationTool.test.d.ts +1 -0
  42. package/build/tools/generate-tests/batchMockGenerationTool.test.js +514 -0
  43. package/build/tools/generate-tests/generateContractRestTool.js +2 -2
  44. package/build/tools/generate-tests/generateMockRestTool.d.ts +56 -6
  45. package/build/tools/generate-tests/generateMockRestTool.js +193 -21
  46. package/build/tools/generate-tests/generateMockRestTool.test.d.ts +1 -0
  47. package/build/tools/generate-tests/generateMockRestTool.test.js +340 -0
  48. package/build/tools/generateEnrichedIntegrationTestTool.d.ts +24 -0
  49. package/build/tools/generateEnrichedIntegrationTestTool.js +209 -0
  50. package/build/tools/generateEnrichedIntegrationTestTool.test.d.ts +1 -0
  51. package/build/tools/generateEnrichedIntegrationTestTool.test.js +199 -0
  52. package/build/tools/one-click/oneClickTool.d.ts +10 -0
  53. package/build/tools/one-click/oneClickTool.js +182 -40
  54. package/build/tools/one-click/oneClickTool.test.d.ts +1 -0
  55. package/build/tools/one-click/oneClickTool.test.js +204 -0
  56. package/build/tools/preflightMockCheckTool.d.ts +2 -0
  57. package/build/tools/preflightMockCheckTool.js +91 -0
  58. package/build/tools/test-management/analyzeChangesTool.d.ts +2 -1
  59. package/build/tools/test-management/analyzeChangesTool.js +59 -38
  60. package/build/tools/workspace/initializeWorkspaceTool.js +2 -9
  61. package/build/tools/workspace/initializeWorkspaceTool.test.js +9 -4
  62. package/build/types/OneClickCommands.d.ts +1 -1
  63. package/build/types/RepositoryAnalysis.d.ts +117 -0
  64. package/build/types/RepositoryAnalysis.js +16 -2
  65. package/build/types/TestTypes.d.ts +11 -2
  66. package/build/types/TestTypes.js +11 -1
  67. package/build/utils/analyze-openapi.js +18 -1
  68. package/build/utils/analyze-openapi.test.d.ts +1 -0
  69. package/build/utils/analyze-openapi.test.js +19 -0
  70. package/build/utils/branchDiff.d.ts +17 -1
  71. package/build/utils/branchDiff.js +96 -14
  72. package/build/utils/branchDiff.test.d.ts +1 -0
  73. package/build/utils/branchDiff.test.js +107 -0
  74. package/build/utils/docker.test.js +1 -1
  75. package/build/utils/featureFlags.d.ts +15 -1
  76. package/build/utils/featureFlags.js +19 -2
  77. package/build/utils/featureFlags.test.js +57 -2
  78. package/build/utils/grpcMockValidation.d.ts +1 -0
  79. package/build/utils/grpcMockValidation.js +49 -0
  80. package/build/utils/grpcMockValidation.test.d.ts +1 -0
  81. package/build/utils/grpcMockValidation.test.js +41 -0
  82. package/build/utils/httpMethodValidation.d.ts +4 -0
  83. package/build/utils/httpMethodValidation.js +13 -0
  84. package/build/utils/mockCompatibility.d.ts +49 -0
  85. package/build/utils/mockCompatibility.js +80 -0
  86. package/build/utils/mockCompatibility.test.d.ts +1 -0
  87. package/build/utils/mockCompatibility.test.js +88 -0
  88. package/build/utils/versions.d.ts +3 -3
  89. package/build/utils/versions.js +1 -1
  90. package/build/workspace/workspace.d.ts +35 -21
  91. package/build/workspace/workspace.js +8 -4
  92. package/build/workspace/workspace.test.js +65 -6
  93. package/package.json +2 -2
@@ -352,8 +352,8 @@ export const analyzeChangesInputSchema = {
352
352
  .describe("Directory containing existing tests (auto-detected if omitted)"),
353
353
  topN: z
354
354
  .number()
355
- .default(MAX_RECOMMENDATIONS)
356
355
  .optional()
356
+ .default(MAX_RECOMMENDATIONS)
357
357
  .describe(`Number of ranked test recommendations to generate. Defaults to ${MAX_RECOMMENDATIONS}.`),
358
358
  maxGenerate: z
359
359
  .number()
@@ -374,6 +374,11 @@ export const analyzeChangesInputSchema = {
374
374
  .refine((v) => path.isAbsolute(v), { message: "testsRepoDir must be an absolute path" })
375
375
  .optional()
376
376
  .describe("Absolute path to a separate test repository clone. When set, existing test discovery scans this directory instead of repositoryPath. Used in cross-repo test delivery mode where tests live in a separate repo."),
377
+ includeUncommitted: z
378
+ .boolean()
379
+ .optional()
380
+ .default(false)
381
+ .describe("When true, diffs the base ref against the working tree (captures uncommitted and unstaged changes). Use for local-dev workflows. Defaults to false (CI mode — committed changes only)."),
377
382
  };
378
383
  // ── UI blueprint-capture instructions ──
379
384
  // Moved here from the former skyramp_ui_analyze_changes pre-flight tool. These
@@ -441,7 +446,7 @@ export function registerAnalyzeChangesTool(server) {
441
446
  if (analysisScope === AnalysisScope.CurrentBranchDiff) {
442
447
  await sendProgress(10, 100, "Computing branch diff...");
443
448
  try {
444
- diffData = await computeBranchDiff(params.repositoryPath, params.baseBranch);
449
+ diffData = await computeBranchDiff(params.repositoryPath, params.baseBranch, params.includeUncommitted ?? false);
445
450
  logger.info("Branch diff computed", {
446
451
  currentBranch: diffData.currentBranch,
447
452
  baseBranch: diffData.baseBranch,
@@ -1106,6 +1111,56 @@ export function registerAnalyzeChangesTool(server) {
1106
1111
  affectedServices: classifiedEndpoints.affectedServices,
1107
1112
  summary: "",
1108
1113
  } : undefined;
1114
+ // ── Route discovery context for LLM grounding and state persistence ──
1115
+ // fullAnalysis lives only in inMemorySessionStore (for MCP resources
1116
+ // and registerRecommendTestsPrompt). The disk state carries only the
1117
+ // slim fields that downstream tools (health, execute, actions) need.
1118
+ // routerMountContext and candidateRouteFiles are computed here so they
1119
+ // can be persisted to the state file for downstream tools (health, drift).
1120
+ // Without them, analyzeTestHealth would work only off the static catalog
1121
+ // which has wrong paths for nested resources and unsupported frameworks.
1122
+ const routeLikeUnmatchedFiles = [];
1123
+ for (const file of classifiedEndpoints?.unmatchedFiles ?? []) {
1124
+ const routeLike = SOURCE_EXTS.test(file) &&
1125
+ (ROUTE_FILE_PATTERN.test(file) || ROUTE_FILE_BASENAME_PATTERN.test(path.basename(file)));
1126
+ if (routeLike && !(await isGraphQLFile(file, params.repositoryPath))) {
1127
+ routeLikeUnmatchedFiles.push(file);
1128
+ }
1129
+ }
1130
+ const shouldIncludeCandidateRouteFiles = analysisScope !== AnalysisScope.CurrentBranchDiff ||
1131
+ rawRelatedEndpointCount === 0 ||
1132
+ scannedEndpoints.length === 0 ||
1133
+ routeLikeUnmatchedFiles.length > 0;
1134
+ let candidateRouteFiles;
1135
+ if (shouldIncludeCandidateRouteFiles) {
1136
+ candidateRouteFiles = [];
1137
+ for (const file of findCandidateRouteFiles(params.repositoryPath)) {
1138
+ if (!(await isGraphQLFile(file, params.repositoryPath))) {
1139
+ candidateRouteFiles.push(file);
1140
+ }
1141
+ }
1142
+ }
1143
+ // Write the full diff to a temp file before building state so the path
1144
+ // can be persisted and read by analyzeTestHealthTool for per-line detection.
1145
+ let diffFilePath;
1146
+ if (diffData?.diffContent) {
1147
+ diffFilePath = path.join(os.tmpdir(), `skyramp-diff-${sessionId}.diff`);
1148
+ await fs.promises.writeFile(diffFilePath, diffData.diffContent, { encoding: "utf-8", mode: 0o600 });
1149
+ }
1150
+ const routeDiscovery = {
1151
+ candidateFiles: [
1152
+ ...(diffData?.changedFiles ?? []),
1153
+ ...(candidateRouteFiles ?? []),
1154
+ ].filter((file, index, files) => files.indexOf(file) === index),
1155
+ staticHints: scannedEndpoints.map((ep) => ({
1156
+ path: ep.path,
1157
+ methods: ep.methods,
1158
+ sourceFile: ep.sourceFile,
1159
+ })),
1160
+ openApiPaths: specPaths ? [...specPaths] : [],
1161
+ routerMountContext,
1162
+ ...(diffFilePath ? { diffFilePath } : {}),
1163
+ };
1109
1164
  const fullAnalysis = {
1110
1165
  metadata: {
1111
1166
  repositoryName: path.basename(params.repositoryPath),
@@ -1162,6 +1217,7 @@ export function registerAnalyzeChangesTool(server) {
1162
1217
  hasCoverageReports: false,
1163
1218
  relevantExternalTestPaths,
1164
1219
  },
1220
+ routeDiscovery,
1165
1221
  ...(diffContext ? { branchDiffContext: diffContext } : {}),
1166
1222
  };
1167
1223
  // Store RecommendationState in memory so it's compatible with skyramp_recommend_tests if needed
@@ -1171,42 +1227,6 @@ export function registerAnalyzeChangesTool(server) {
1171
1227
  analysis: fullAnalysis,
1172
1228
  };
1173
1229
  storeSessionData(sessionId, recommendationState);
1174
- // ── Step 11: Build UnifiedAnalysisState and save ──
1175
- // fullAnalysis lives only in inMemorySessionStore (for MCP resources
1176
- // and registerRecommendTestsPrompt). The disk state carries only the
1177
- // slim fields that downstream tools (health, execute, actions) need.
1178
- // routerMountContext and candidateRouteFiles are computed here so they
1179
- // can be persisted to the state file for downstream tools (health, drift).
1180
- // Without them, analyzeTestHealth would work only off the static catalog
1181
- // which has wrong paths for nested resources and unsupported frameworks.
1182
- const routeLikeUnmatchedFiles = [];
1183
- for (const file of classifiedEndpoints?.unmatchedFiles ?? []) {
1184
- const routeLike = SOURCE_EXTS.test(file) &&
1185
- (ROUTE_FILE_PATTERN.test(file) || ROUTE_FILE_BASENAME_PATTERN.test(path.basename(file)));
1186
- if (routeLike && !(await isGraphQLFile(file, params.repositoryPath))) {
1187
- routeLikeUnmatchedFiles.push(file);
1188
- }
1189
- }
1190
- const shouldIncludeCandidateRouteFiles = analysisScope !== AnalysisScope.CurrentBranchDiff ||
1191
- rawRelatedEndpointCount === 0 ||
1192
- scannedEndpoints.length === 0 ||
1193
- routeLikeUnmatchedFiles.length > 0;
1194
- let candidateRouteFiles;
1195
- if (shouldIncludeCandidateRouteFiles) {
1196
- candidateRouteFiles = [];
1197
- for (const file of findCandidateRouteFiles(params.repositoryPath)) {
1198
- if (!(await isGraphQLFile(file, params.repositoryPath))) {
1199
- candidateRouteFiles.push(file);
1200
- }
1201
- }
1202
- }
1203
- // Write the full diff to a temp file before building state so the path
1204
- // can be persisted and read by analyzeTestHealthTool for per-line detection.
1205
- let diffFilePath;
1206
- if (diffData?.diffContent) {
1207
- diffFilePath = path.join(os.tmpdir(), `skyramp-diff-${sessionId}.diff`);
1208
- await fs.promises.writeFile(diffFilePath, diffData.diffContent, { encoding: "utf-8", mode: 0o600 });
1209
- }
1210
1230
  // Compute UI context from the diff's changed files using the shared
1211
1231
  // `isFrontendFile` classifier. Persisting this in the stateFile lets
1212
1232
  // skyramp_analyze_test_health and the recommendation prompt consume the
@@ -1299,6 +1319,7 @@ export function registerAnalyzeChangesTool(server) {
1299
1319
  sessionId,
1300
1320
  routerMountContext,
1301
1321
  candidateRouteFiles,
1322
+ routeDiscovery,
1302
1323
  relevantExternalTestPaths,
1303
1324
  },
1304
1325
  };
@@ -1,6 +1,5 @@
1
1
  import { z } from "zod";
2
- import { WorkspaceConfigManager, serviceSchema } from "../../workspace/workspace.js";
3
- import { createRequire } from "module";
2
+ import { WorkspaceConfigManager, serviceSchema, } from "../../workspace/workspace.js";
4
3
  import fs from "fs/promises";
5
4
  import yaml from "js-yaml";
6
5
  import { logger } from "../../utils/logger.js";
@@ -8,8 +7,6 @@ import { AnalyticsService } from "../../services/AnalyticsService.js";
8
7
  import { SKYRAMP_IMAGE_VERSION } from "../../utils/versions.js";
9
8
  import { validateAndConsumeScanToken } from "./initScanWorkspaceTool.js";
10
9
  import { upsertServicesByRepo } from "./serviceUpsert.js";
11
- const require = createRequire(import.meta.url);
12
- const MCP_VERSION = require("../../../package.json").version || "";
13
10
  function getExecutorVersion() {
14
11
  return SKYRAMP_IMAGE_VERSION;
15
12
  }
@@ -20,9 +17,7 @@ const TOOL_NAME = "skyramp_init_workspace";
20
17
  // set (incl. cookie/session/token) and the multi-repo `repository` field, so input
21
18
  // services are validated against it directly — no local schema extension needed.
22
19
  const initializeWorkspaceSchema = z.object({
23
- workspacePath: z
24
- .string()
25
- .describe("Path to workspace directory"),
20
+ workspacePath: z.string().describe("Path to workspace directory"),
26
21
  services: z
27
22
  .array(serviceSchema)
28
23
  .describe("Array of services discovered by skyramp_init_scan. Must contain at least one service."),
@@ -123,14 +118,12 @@ export function registerInitializeWorkspaceTool(server) {
123
118
  if (params.merge && alreadyExists) {
124
119
  config = await manager.read();
125
120
  config = await manager.updateMetadata({
126
- mcpVersion: MCP_VERSION,
127
121
  executorVersion: getExecutorVersion(),
128
122
  });
129
123
  }
130
124
  else {
131
125
  config = await manager.initialize();
132
126
  config = await manager.updateMetadata({
133
- mcpVersion: MCP_VERSION,
134
127
  executorVersion: getExecutorVersion(),
135
128
  });
136
129
  }
@@ -36,7 +36,9 @@ describe("initializeWorkspaceTool", () => {
36
36
  // Build a mock WorkspaceConfigManager
37
37
  mockManager = {
38
38
  exists: jest.fn().mockResolvedValue(false),
39
- getConfigPath: jest.fn().mockReturnValue(path.join(tmpDir, ".skyramp/workspace.yml")),
39
+ getConfigPath: jest
40
+ .fn()
41
+ .mockReturnValue(path.join(tmpDir, ".skyramp/workspace.yml")),
40
42
  initialize: jest.fn().mockResolvedValue({ workspace: {}, services: [] }),
41
43
  updateMetadata: jest.fn().mockImplementation(async (data) => ({
42
44
  workspace: {},
@@ -95,7 +97,6 @@ describe("initializeWorkspaceTool", () => {
95
97
  expect(result.content[0].text).toContain("Workspace initialized");
96
98
  expect(mockManager.initialize).toHaveBeenCalled();
97
99
  expect(mockManager.updateMetadata).toHaveBeenCalledWith(expect.objectContaining({
98
- mcpVersion: expect.any(String),
99
100
  executorVersion: expect.any(String),
100
101
  }));
101
102
  });
@@ -103,7 +104,9 @@ describe("initializeWorkspaceTool", () => {
103
104
  mockManager.exists.mockResolvedValue(true);
104
105
  const result = await handler({
105
106
  workspacePath: tmpDir,
106
- services: [{ serviceName: "svc", language: "python", testDirectory: "tests" }],
107
+ services: [
108
+ { serviceName: "svc", language: "python", testDirectory: "tests" },
109
+ ],
107
110
  scanToken: "valid-token",
108
111
  force: false,
109
112
  });
@@ -114,7 +117,9 @@ describe("initializeWorkspaceTool", () => {
114
117
  validateAndConsumeScanToken.mockReturnValue(false);
115
118
  const result = await handler({
116
119
  workspacePath: tmpDir,
117
- services: [{ serviceName: "svc", language: "python", testDirectory: "tests" }],
120
+ services: [
121
+ { serviceName: "svc", language: "python", testDirectory: "tests" },
122
+ ],
118
123
  scanToken: "bad-token",
119
124
  force: false,
120
125
  });
@@ -4,7 +4,7 @@
4
4
  * and executed by the coding assistant following the workflow instructions.
5
5
  */
6
6
  /** Unique identifier for a one-click command */
7
- export type OneClickCommandId = "test_given_endpoint_comprehensively" | "full_repo_scan_recommend_generate_and_execute_top_n_tests";
7
+ export type OneClickCommandId = "test_given_endpoint_comprehensively" | "full_repo_scan_recommend_generate_and_execute_top_n_tests" | "local_dev_test_changes";
8
8
  /** Metadata for intent recognition by coding assistants */
9
9
  export interface CommandIntentMetadata {
10
10
  /** Context indicators describing when to use and when NOT to use this workflow */
@@ -145,6 +145,22 @@ export interface BranchDiffContext {
145
145
  affectedServices: string[];
146
146
  summary?: string;
147
147
  }
148
+ export interface RouteDiscoveryInfo {
149
+ /** Files the LLM should inspect to infer endpoint methods and paths from source. */
150
+ candidateFiles: string[];
151
+ /** Static scanner output. Best-effort hints only, not an authoritative catalog. */
152
+ staticHints: Array<{
153
+ path: string;
154
+ methods: string[];
155
+ sourceFile: string;
156
+ }>;
157
+ /** OpenAPI paths from workspace config. Authoritative when specFetchSucceeded is true. */
158
+ openApiPaths: string[];
159
+ /** Router/module entry points that help the LLM reconstruct full mounted paths. */
160
+ routerMountContext: string[];
161
+ /** Full diff file path when available; read this to ground endpoint extraction in changed code. */
162
+ diffFilePath?: string;
163
+ }
148
164
  export interface RepositoryAnalysis {
149
165
  metadata: AnalysisMetadata;
150
166
  projectClassification: ProjectClassification;
@@ -155,6 +171,7 @@ export interface RepositoryAnalysis {
155
171
  authentication: AuthenticationInfo;
156
172
  infrastructure: InfrastructureInfo;
157
173
  existingTests: ExistingTestInfo;
174
+ routeDiscovery?: RouteDiscoveryInfo;
158
175
  branchDiffContext?: BranchDiffContext;
159
176
  }
160
177
  export interface AnalysisMetadata {
@@ -1423,6 +1440,45 @@ export declare const branchDiffContextSchema: z.ZodObject<{
1423
1440
  }[] | undefined;
1424
1441
  summary?: string | undefined;
1425
1442
  }>;
1443
+ export declare const routeDiscoveryInfoSchema: z.ZodObject<{
1444
+ candidateFiles: z.ZodArray<z.ZodString, "many">;
1445
+ staticHints: z.ZodArray<z.ZodObject<{
1446
+ path: z.ZodString;
1447
+ methods: z.ZodArray<z.ZodString, "many">;
1448
+ sourceFile: z.ZodString;
1449
+ }, "strip", z.ZodTypeAny, {
1450
+ path: string;
1451
+ sourceFile: string;
1452
+ methods: string[];
1453
+ }, {
1454
+ path: string;
1455
+ sourceFile: string;
1456
+ methods: string[];
1457
+ }>, "many">;
1458
+ openApiPaths: z.ZodArray<z.ZodString, "many">;
1459
+ routerMountContext: z.ZodArray<z.ZodString, "many">;
1460
+ diffFilePath: z.ZodOptional<z.ZodString>;
1461
+ }, "strip", z.ZodTypeAny, {
1462
+ candidateFiles: string[];
1463
+ staticHints: {
1464
+ path: string;
1465
+ sourceFile: string;
1466
+ methods: string[];
1467
+ }[];
1468
+ openApiPaths: string[];
1469
+ routerMountContext: string[];
1470
+ diffFilePath?: string | undefined;
1471
+ }, {
1472
+ candidateFiles: string[];
1473
+ staticHints: {
1474
+ path: string;
1475
+ sourceFile: string;
1476
+ methods: string[];
1477
+ }[];
1478
+ openApiPaths: string[];
1479
+ routerMountContext: string[];
1480
+ diffFilePath?: string | undefined;
1481
+ }>;
1426
1482
  export declare const analysisMetadataSchema: z.ZodObject<{
1427
1483
  repositoryName: z.ZodString;
1428
1484
  analysisDate: z.ZodString;
@@ -2440,6 +2496,45 @@ export declare const repositoryAnalysisSchema: z.ZodObject<{
2440
2496
  estimatedCoverage?: number | undefined;
2441
2497
  relevantExternalTestPaths?: string[] | undefined;
2442
2498
  }>;
2499
+ routeDiscovery: z.ZodOptional<z.ZodObject<{
2500
+ candidateFiles: z.ZodArray<z.ZodString, "many">;
2501
+ staticHints: z.ZodArray<z.ZodObject<{
2502
+ path: z.ZodString;
2503
+ methods: z.ZodArray<z.ZodString, "many">;
2504
+ sourceFile: z.ZodString;
2505
+ }, "strip", z.ZodTypeAny, {
2506
+ path: string;
2507
+ sourceFile: string;
2508
+ methods: string[];
2509
+ }, {
2510
+ path: string;
2511
+ sourceFile: string;
2512
+ methods: string[];
2513
+ }>, "many">;
2514
+ openApiPaths: z.ZodArray<z.ZodString, "many">;
2515
+ routerMountContext: z.ZodArray<z.ZodString, "many">;
2516
+ diffFilePath: z.ZodOptional<z.ZodString>;
2517
+ }, "strip", z.ZodTypeAny, {
2518
+ candidateFiles: string[];
2519
+ staticHints: {
2520
+ path: string;
2521
+ sourceFile: string;
2522
+ methods: string[];
2523
+ }[];
2524
+ openApiPaths: string[];
2525
+ routerMountContext: string[];
2526
+ diffFilePath?: string | undefined;
2527
+ }, {
2528
+ candidateFiles: string[];
2529
+ staticHints: {
2530
+ path: string;
2531
+ sourceFile: string;
2532
+ methods: string[];
2533
+ }[];
2534
+ openApiPaths: string[];
2535
+ routerMountContext: string[];
2536
+ diffFilePath?: string | undefined;
2537
+ }>>;
2443
2538
  branchDiffContext: z.ZodOptional<z.ZodObject<{
2444
2539
  currentBranch: z.ZodString;
2445
2540
  baseBranch: z.ZodString;
@@ -2768,6 +2863,17 @@ export declare const repositoryAnalysisSchema: z.ZodObject<{
2768
2863
  estimatedCoverage?: number | undefined;
2769
2864
  relevantExternalTestPaths?: string[] | undefined;
2770
2865
  };
2866
+ routeDiscovery?: {
2867
+ candidateFiles: string[];
2868
+ staticHints: {
2869
+ path: string;
2870
+ sourceFile: string;
2871
+ methods: string[];
2872
+ }[];
2873
+ openApiPaths: string[];
2874
+ routerMountContext: string[];
2875
+ diffFilePath?: string | undefined;
2876
+ } | undefined;
2771
2877
  branchDiffContext?: {
2772
2878
  currentBranch: string;
2773
2879
  baseBranch: string;
@@ -2970,6 +3076,17 @@ export declare const repositoryAnalysisSchema: z.ZodObject<{
2970
3076
  estimatedCoverage?: number | undefined;
2971
3077
  relevantExternalTestPaths?: string[] | undefined;
2972
3078
  };
3079
+ routeDiscovery?: {
3080
+ candidateFiles: string[];
3081
+ staticHints: {
3082
+ path: string;
3083
+ sourceFile: string;
3084
+ methods: string[];
3085
+ }[];
3086
+ openApiPaths: string[];
3087
+ routerMountContext: string[];
3088
+ diffFilePath?: string | undefined;
3089
+ } | undefined;
2973
3090
  branchDiffContext?: {
2974
3091
  currentBranch: string;
2975
3092
  baseBranch: string;
@@ -128,17 +128,30 @@ export const branchDiffContextSchema = z.object({
128
128
  changeType: z.enum(["added", "modified", "removed"]),
129
129
  })),
130
130
  })),
131
- removedEndpoints: z.array(z.object({
131
+ removedEndpoints: z
132
+ .array(z.object({
132
133
  path: z.string(),
133
134
  methods: z.array(z.object({
134
135
  method: z.string(),
135
136
  sourceFile: z.string(),
136
137
  changeType: z.literal("removed"),
137
138
  })),
138
- })).optional(),
139
+ }))
140
+ .optional(),
139
141
  affectedServices: z.array(z.string()),
140
142
  summary: z.string().optional(),
141
143
  });
144
+ export const routeDiscoveryInfoSchema = z.object({
145
+ candidateFiles: z.array(z.string()),
146
+ staticHints: z.array(z.object({
147
+ path: z.string(),
148
+ methods: z.array(z.string()),
149
+ sourceFile: z.string(),
150
+ })),
151
+ openApiPaths: z.array(z.string()),
152
+ routerMountContext: z.array(z.string()),
153
+ diffFilePath: z.string().optional(),
154
+ });
142
155
  export const analysisMetadataSchema = z.object({
143
156
  repositoryName: z.string(),
144
157
  analysisDate: z.string(),
@@ -239,5 +252,6 @@ export const repositoryAnalysisSchema = z.object({
239
252
  estimatedCoverage: z.number().optional(),
240
253
  relevantExternalTestPaths: z.array(z.string()).optional(),
241
254
  }),
255
+ routeDiscovery: routeDiscoveryInfoSchema.optional(),
242
256
  branchDiffContext: branchDiffContextSchema.optional(),
243
257
  });
@@ -14,8 +14,17 @@ export declare enum TestType {
14
14
  LOAD = "load",
15
15
  INTEGRATION = "integration",
16
16
  E2E = "e2e",
17
- UI = "ui",
18
- MOCK = "mock"
17
+ UI = "ui"
18
+ }
19
+ /**
20
+ * Mock is not a test type — mock files are deployed via apply_mock(),
21
+ * not executed as tests. Kept as a standalone constant for mock generation.
22
+ */
23
+ export declare const MOCK_TYPE: "mock";
24
+ export declare enum MockProtocol {
25
+ Rest = "rest",
26
+ Grpc = "grpc",
27
+ Kafka = "kafka"
19
28
  }
20
29
  export declare enum HttpMethod {
21
30
  GET = "GET",
@@ -17,8 +17,18 @@ export var TestType;
17
17
  TestType["INTEGRATION"] = "integration";
18
18
  TestType["E2E"] = "e2e";
19
19
  TestType["UI"] = "ui";
20
- TestType["MOCK"] = "mock";
21
20
  })(TestType || (TestType = {}));
21
+ /**
22
+ * Mock is not a test type — mock files are deployed via apply_mock(),
23
+ * not executed as tests. Kept as a standalone constant for mock generation.
24
+ */
25
+ export const MOCK_TYPE = "mock";
26
+ export var MockProtocol;
27
+ (function (MockProtocol) {
28
+ MockProtocol["Rest"] = "rest";
29
+ MockProtocol["Grpc"] = "grpc";
30
+ MockProtocol["Kafka"] = "kafka";
31
+ })(MockProtocol || (MockProtocol = {}));
22
32
  export var HttpMethod;
23
33
  (function (HttpMethod) {
24
34
  HttpMethod["GET"] = "GET";
@@ -1,5 +1,10 @@
1
1
  import { SkyrampClient } from "@skyramp/skyramp";
2
2
  import { IsValidKeyValueList } from "./utils.js";
3
+ import { logger } from "./logger.js";
4
+ function isOpenApiRuntimeFailure(error) {
5
+ const message = error instanceof Error ? error.message : String(error);
6
+ return /(?:ECONNREFUSED|ECONNRESET|ENOTFOUND|EAI_AGAIN|ETIMEDOUT|socket hang up|connection refused|unavailable|timed? out)/i.test(message);
7
+ }
3
8
  // Analyze OpenAPI to check for required path params
4
9
  export async function analyzeOpenAPIWithGivenEndpoint(apiSchemaInput, uriInput, pathParamsInput) {
5
10
  const client = new SkyrampClient();
@@ -7,7 +12,19 @@ export async function analyzeOpenAPIWithGivenEndpoint(apiSchemaInput, uriInput,
7
12
  apiSchema: apiSchemaInput,
8
13
  uri: uriInput,
9
14
  };
10
- const pathParamRequired = await client.analyzeOpenapi(analyzeOpenapiOptions);
15
+ let pathParamRequired;
16
+ try {
17
+ pathParamRequired = await client.analyzeOpenapi(analyzeOpenapiOptions);
18
+ }
19
+ catch (e) {
20
+ if (isOpenApiRuntimeFailure(e)) {
21
+ logger.warning("Skipping OpenAPI path-param validation because schema analysis is unavailable", {
22
+ error: e instanceof Error ? e.message : String(e),
23
+ });
24
+ return null;
25
+ }
26
+ throw e;
27
+ }
11
28
  if (IsValidKeyValueList(pathParamRequired)) {
12
29
  const requiredPathParams = pathParamRequired
13
30
  ? pathParamRequired.split(",").map((p) => p.trim())
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,19 @@
1
+ import { jest, describe, it, expect, beforeEach } from "@jest/globals";
2
+ const analyzeOpenapi = jest.fn();
3
+ jest.unstable_mockModule("@skyramp/skyramp", () => ({
4
+ SkyrampClient: jest.fn(() => ({ analyzeOpenapi })),
5
+ }));
6
+ const { analyzeOpenAPIWithGivenEndpoint } = await import("./analyze-openapi.js");
7
+ describe("analyzeOpenAPIWithGivenEndpoint", () => {
8
+ beforeEach(() => {
9
+ analyzeOpenapi.mockReset();
10
+ });
11
+ it("skips path-param validation for runtime connectivity failures", async () => {
12
+ analyzeOpenapi.mockRejectedValue(new Error("gRPC unavailable: connection refused"));
13
+ await expect(analyzeOpenAPIWithGivenEndpoint("schema.yaml", "/users/{id}", "")).resolves.toBeNull();
14
+ });
15
+ it("rethrows schema errors instead of silently disabling validation", async () => {
16
+ analyzeOpenapi.mockRejectedValue(new Error("invalid apiSchema path: missing.yaml"));
17
+ await expect(analyzeOpenAPIWithGivenEndpoint("missing.yaml", "/users/{id}", "")).rejects.toThrow("invalid apiSchema path");
18
+ });
19
+ });
@@ -15,4 +15,20 @@ export interface BranchDiffData {
15
15
  * Always uses the `b/` form so renames return the new path.
16
16
  */
17
17
  export declare function parseChangedFilesFromDiff(rawDiff: string): string[];
18
- export declare function computeBranchDiff(repositoryPath: string, providedBaseBranch?: string): Promise<BranchDiffData>;
18
+ /**
19
+ * Normalize and validate a caller-provided base branch/ref before passing it
20
+ * to git. Reject option-like or ambiguous values so the ref is always treated
21
+ * as a revision name, not as a command-line flag.
22
+ */
23
+ export declare function normalizeProvidedBaseRef(providedBaseBranch?: string): string | undefined;
24
+ /**
25
+ * Compute the diff between a base reference and the current state.
26
+ *
27
+ * @param repositoryPath Absolute path to the git repository.
28
+ * @param providedBaseBranch A branch name, tag, or commit SHA to diff against.
29
+ * When omitted, the remote default branch is auto-detected.
30
+ * @param includeUncommitted When true, diffs against the working tree so
31
+ * uncommitted/unstaged changes are included. Defaults to false (CI mode
32
+ * that only considers committed changes via the three-dot form).
33
+ */
34
+ export declare function computeBranchDiff(repositoryPath: string, providedBaseBranch?: string, includeUncommitted?: boolean): Promise<BranchDiffData>;