@skyramp/mcp 0.3.2-rc.pom-4 → 0.3.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 (132) hide show
  1. package/build/adapters/jestAdapter.d.ts +14 -0
  2. package/build/adapters/jestAdapter.js +113 -0
  3. package/build/adapters/mochaAdapter.d.ts +13 -0
  4. package/build/adapters/mochaAdapter.js +87 -0
  5. package/build/adapters/playwrightAdapter.d.ts +17 -0
  6. package/build/adapters/playwrightAdapter.js +182 -0
  7. package/build/adapters/pytestAdapter.d.ts +15 -0
  8. package/build/adapters/pytestAdapter.js +108 -0
  9. package/build/commands/commandLibrary.d.ts +1 -0
  10. package/build/commands/commandLibrary.js +19 -13
  11. package/build/commands/localDevTestChangesCommand.d.ts +15 -0
  12. package/build/commands/localDevTestChangesCommand.js +201 -0
  13. package/build/index.js +82 -6
  14. package/build/prompts/code-reuse.js +3 -0
  15. package/build/prompts/enhance-assertions/sharedAssertionRules.d.ts +1 -0
  16. package/build/prompts/enhance-assertions/sharedAssertionRules.js +17 -0
  17. package/build/prompts/initialize-workspace/initializeWorkspacePrompt.js +11 -9
  18. package/build/prompts/local-dev/local-dev-plan.d.ts +35 -0
  19. package/build/prompts/local-dev/local-dev-plan.js +429 -0
  20. package/build/prompts/local-dev/local-dev-prompts.d.ts +4 -0
  21. package/build/prompts/local-dev/local-dev-prompts.js +190 -0
  22. package/build/prompts/prompt-utils.d.ts +8 -0
  23. package/build/prompts/prompt-utils.js +33 -0
  24. package/build/prompts/sut-setup/modes/adaptWorkflowPrompt.js +70 -7
  25. package/build/prompts/sut-setup/modes/dockerComposePrompt.js +1 -1
  26. package/build/prompts/sut-setup/shared.js +19 -17
  27. package/build/prompts/test-maintenance/drift-analysis-prompt.d.ts +10 -1
  28. package/build/prompts/test-maintenance/drift-analysis-prompt.js +54 -1
  29. package/build/prompts/test-recommendation/analysisOutputPrompt.js +21 -29
  30. package/build/prompts/test-recommendation/scopeAssessment.d.ts +5 -2
  31. package/build/prompts/test-recommendation/scopeAssessment.js +78 -6
  32. package/build/prompts/test-recommendation/test-recommendation-prompt.js +41 -4
  33. package/build/prompts/testbot/testbot-prompts.d.ts +0 -5
  34. package/build/prompts/testbot/testbot-prompts.js +39 -55
  35. package/build/recommendation/planRanker.d.ts +15 -2
  36. package/build/recommendation/planRanker.js +76 -5
  37. package/build/resources/testbotResource.js +2 -1
  38. package/build/services/AnalyticsService.d.ts +1 -1
  39. package/build/services/TestExecutionService.d.ts +2 -1
  40. package/build/services/TestExecutionService.js +8 -3
  41. package/build/services/TestGenerationService.d.ts +2 -2
  42. package/build/services/TestGenerationService.js +39 -21
  43. package/build/services/containerEnv.js +3 -1
  44. package/build/tool-phases.js +7 -0
  45. package/build/tools/code-refactor/codeReuseTool.js +43 -4
  46. package/build/tools/code-refactor/enhanceAssertionsTool.js +68 -18
  47. package/build/tools/code-refactor/reuse-outcome.d.ts +109 -0
  48. package/build/tools/code-refactor/reuse-outcome.js +158 -0
  49. package/build/tools/code-refactor/reuse-state.d.ts +45 -0
  50. package/build/tools/code-refactor/reuse-state.js +140 -0
  51. package/build/tools/enrichTestWithMocksTool.d.ts +28 -0
  52. package/build/tools/enrichTestWithMocksTool.js +726 -0
  53. package/build/tools/executeSkyrampTestTool.d.ts +11 -0
  54. package/build/tools/executeSkyrampTestTool.js +62 -21
  55. package/build/tools/generate-tests/batchMockGenerationTool.d.ts +106 -0
  56. package/build/tools/generate-tests/batchMockGenerationTool.js +545 -0
  57. package/build/tools/generate-tests/generateBatchScenarioRestTool.js +25 -23
  58. package/build/tools/generate-tests/generateContractRestTool.js +2 -2
  59. package/build/tools/generate-tests/generateE2ERestTool.d.ts +1 -1
  60. package/build/tools/generate-tests/generateE2ERestTool.js +1 -1
  61. package/build/tools/generate-tests/generateLoadRestTool.d.ts +1 -1
  62. package/build/tools/generate-tests/generateLoadRestTool.js +1 -1
  63. package/build/tools/generate-tests/generateMockRestTool.d.ts +179 -6
  64. package/build/tools/generate-tests/generateMockRestTool.js +391 -22
  65. package/build/tools/generate-tests/loadTestSchema.d.ts +1 -1
  66. package/build/tools/generate-tests/planGuard.js +2 -22
  67. package/build/tools/generateEnrichedIntegrationTestTool.d.ts +25 -0
  68. package/build/tools/generateEnrichedIntegrationTestTool.js +235 -0
  69. package/build/tools/localDevWorkerComposeTool.d.ts +24 -0
  70. package/build/tools/localDevWorkerComposeTool.js +264 -0
  71. package/build/tools/one-click/oneClickTool.d.ts +13 -0
  72. package/build/tools/one-click/oneClickTool.js +195 -24
  73. package/build/tools/preflightMockCheckTool.d.ts +2 -0
  74. package/build/tools/preflightMockCheckTool.js +96 -0
  75. package/build/tools/queryProxyMocksTool.d.ts +70 -0
  76. package/build/tools/queryProxyMocksTool.js +522 -0
  77. package/build/tools/runExistingTestsTool.d.ts +138 -0
  78. package/build/tools/runExistingTestsTool.js +644 -0
  79. package/build/tools/submitReportTool.js +30 -1
  80. package/build/tools/test-management/analyzeChangesTool.d.ts +2 -1
  81. package/build/tools/test-management/analyzeChangesTool.js +63 -40
  82. package/build/tools/test-management/analyzeTestHealthTool.d.ts +11 -0
  83. package/build/tools/test-management/analyzeTestHealthTool.js +63 -1
  84. package/build/tools/test-management/registerTestPlanTool.js +55 -7
  85. package/build/tools/trace/startTraceCollectionTool.js +3 -3
  86. package/build/types/ExternalTestExecution.d.ts +67 -0
  87. package/build/types/ExternalTestExecution.js +8 -0
  88. package/build/types/OneClickCommands.d.ts +1 -1
  89. package/build/types/Recommendation.d.ts +20 -0
  90. package/build/types/Recommendation.js +32 -6
  91. package/build/types/RepositoryAnalysis.d.ts +131 -14
  92. package/build/types/RepositoryAnalysis.js +16 -2
  93. package/build/types/ReuseOutcome.d.ts +63 -0
  94. package/build/types/ReuseOutcome.js +33 -0
  95. package/build/types/TestExecution.d.ts +1 -0
  96. package/build/types/TestTypes.d.ts +25 -7
  97. package/build/types/TestTypes.js +25 -7
  98. package/build/types/TestbotReport.d.ts +7 -0
  99. package/build/types/index.d.ts +2 -0
  100. package/build/types/index.js +1 -0
  101. package/build/utils/AnalysisStateManager.d.ts +33 -0
  102. package/build/utils/AnalysisStateManager.js +36 -2
  103. package/build/utils/analyze-openapi.js +18 -1
  104. package/build/utils/branchDiff.d.ts +17 -1
  105. package/build/utils/branchDiff.js +99 -14
  106. package/build/utils/featureFlags.d.ts +31 -0
  107. package/build/utils/featureFlags.js +37 -0
  108. package/build/utils/grpcMockValidation.d.ts +1 -0
  109. package/build/utils/grpcMockValidation.js +49 -0
  110. package/build/utils/httpMethodValidation.d.ts +4 -0
  111. package/build/utils/httpMethodValidation.js +15 -0
  112. package/build/utils/logger.js +1 -1
  113. package/build/utils/mockCompatibility.d.ts +49 -0
  114. package/build/utils/mockCompatibility.js +82 -0
  115. package/build/utils/pom-verify/verify.d.ts +5 -0
  116. package/build/utils/pom-verify/verify.js +1 -0
  117. package/build/utils/progress.js +10 -5
  118. package/build/utils/proxy-terminal.js +3 -3
  119. package/build/utils/routeParsers.d.ts +3 -9
  120. package/build/utils/routeParsers.js +79 -4
  121. package/build/utils/utils.js +2 -2
  122. package/build/utils/versions.d.ts +4 -3
  123. package/build/utils/versions.js +3 -1
  124. package/build/utils/workspaceAuth.d.ts +46 -0
  125. package/build/utils/workspaceAuth.js +156 -1
  126. package/build/workspace/frameworks.d.ts +11 -0
  127. package/build/workspace/frameworks.js +22 -0
  128. package/build/workspace/testSuites.d.ts +20 -0
  129. package/build/workspace/testSuites.js +17 -0
  130. package/build/workspace/workspace.d.ts +206 -24
  131. package/build/workspace/workspace.js +52 -2
  132. package/package.json +5 -2
@@ -1,6 +1,7 @@
1
1
  import type { DriftAction } from "./TestAnalysis.js";
2
2
  import type { TestExecutionStatus } from "./TestExecution.js";
3
3
  import type { HttpMethod, TestType } from "./TestTypes.js";
4
+ import type { ReuseOutcome } from "./ReuseOutcome.js";
4
5
  /**
5
6
  * Shape of the JSON report written by skyramp_submit_report and read by testbot
6
7
  * for rendering as Markdown. All fields mirror the corresponding Zod schemas in
@@ -18,6 +19,12 @@ export interface TestbotReport {
18
19
  scenarioFile?: string;
19
20
  traceFile?: string;
20
21
  frontendTrace?: string;
22
+ /** UI tests only: POM code-reuse outcome, present when skyramp_reuse_code ran
23
+ * for this test. Server-derived — imported from its source of truth rather
24
+ * than re-declared, since that module already exports a plain interface (the
25
+ * rest of this file mirrors Zod schemas by hand, which is why only this field
26
+ * is imported). Absent in reports from MCP versions predating it. */
27
+ reuse?: ReuseOutcome;
21
28
  }[];
22
29
  /** Omitted when no existing tests were found (agent omits the field). */
23
30
  testMaintenance?: {
@@ -2,3 +2,5 @@ export { TestExecutionStatus } from "./TestExecution.js";
2
2
  export { DriftAction } from "./TestAnalysis.js";
3
3
  export { TestType, HttpMethod } from "./TestTypes.js";
4
4
  export type { TestbotReport } from "./TestbotReport.js";
5
+ export { ReuseDeclinedBy, ReuseVerificationOutcome } from "./ReuseOutcome.js";
6
+ export type { ReuseOutcome, ReuseSkippedEntry } from "./ReuseOutcome.js";
@@ -1,3 +1,4 @@
1
1
  export { TestExecutionStatus } from "./TestExecution.js";
2
2
  export { DriftAction } from "./TestAnalysis.js";
3
3
  export { TestType, HttpMethod } from "./TestTypes.js";
4
+ export { ReuseDeclinedBy, ReuseVerificationOutcome } from "./ReuseOutcome.js";
@@ -1,3 +1,4 @@
1
+ import type { ReuseRecord } from "../tools/code-refactor/reuse-outcome.js";
1
2
  import { TestAnalysisResult, MaintenanceActionCore } from "../types/TestAnalysis.js";
2
3
  import { RepositoryAnalysis, AnalysisScope } from "../types/RepositoryAnalysis.js";
3
4
  import { PRTestContext } from "./pr-comment-parser.js";
@@ -5,13 +6,34 @@ import { ParsedDiffEndpoint } from "./routeParsers.js";
5
6
  import type { CandidateUiPage } from "./uiPageEnumerator.js";
6
7
  import type { FrontendFileIntegration } from "../types/FrontendIntegration.js";
7
8
  import type { ApprovedPlanItem } from "../types/Recommendation.js";
9
+ import type { ExternalTestRunRecord } from "../types/ExternalTestExecution.js";
8
10
  export type { CandidateUiPage } from "./uiPageEnumerator.js";
9
11
  export declare function setTestsRepoDir(dir: string | undefined): void;
10
12
  export declare function getTestsRepoDir(): string | undefined;
13
+ /** Filename of the run-scoped analysis state file under `<RUNNER_TEMP>/skyramp/`.
14
+ * Single-sourced so the constructor that WRITES there and `resolveRunStatePath`
15
+ * which LOOKS there cannot drift apart. */
16
+ export declare const RUN_STATE_FILE_NAME = "analyze-changes-state.json";
11
17
  export declare function setActiveRunStatePath(stateFilePath: string): void;
12
18
  export declare function getActiveRunStatePath(): string | undefined;
13
19
  /** Reset the run anchor — for tests, and as a safety hook between runs. */
14
20
  export declare function clearActiveRunStatePath(): void;
21
+ /**
22
+ * The run's state-file path, for tools that need to read or amend it without the
23
+ * agent naming it.
24
+ *
25
+ * `setActiveRunStatePath` is only called by skyramp_analyze_changes on multi-repo
26
+ * runs (the ones carrying a `repository` param), but a single-repo Testbot run's
27
+ * path is still deterministic in CI: the StateManager constructor above roots
28
+ * "analysis" state under `$RUNNER_TEMP/skyramp/`. Falling back to that covers
29
+ * single-repo runs without a new anchor mechanism. Standalone/IDE usage has no
30
+ * RUNNER_TEMP and resolves to undefined — correct, since there is no run state.
31
+ *
32
+ * Lives here rather than in each caller so the convention has one home: this is
33
+ * the module that decides where the file goes, so it is the module that should
34
+ * decide where to look for it.
35
+ */
36
+ export declare function resolveRunStatePath(explicitPath?: string): string | undefined;
15
37
  export declare function registerSession(sessionId: string, stateFilePath: string): void;
16
38
  export declare function getSessionFilePath(sessionId: string): string | undefined;
17
39
  export declare function getRegisteredSessions(): ReadonlyMap<string, string>;
@@ -133,6 +155,17 @@ export interface UnifiedAnalysisState {
133
155
  planBudgetContext?: PlanBudgetContext;
134
156
  /** SKYR-3879 Path B — see {@link ApprovedPlan}. */
135
157
  approvedPlan?: ApprovedPlan;
158
+ /**
159
+ * External (repo-owned) test runs recorded by skyramp_run_existing_tests, one
160
+ * record per confirm/verify invocation. Drift analysis and the report fold in
161
+ * CONFIRMED failures from here instead of guessing. Absent until the tool runs.
162
+ */
163
+ externalTestResults?: ExternalTestRunRecord[];
164
+ /** POM code-reuse outcome per generated UI spec, keyed by test-file BASENAME
165
+ * (what `newTestsCreated[].fileName` carries). Written by skyramp_reuse_code,
166
+ * which computes every value in-process; skyramp_submit_report merges it into
167
+ * the report's `reuse` field. Never supplied by the LLM. */
168
+ reuseOutcomes?: Record<string, ReuseRecord>;
136
169
  }
137
170
  /**
138
171
  * State file metadata
@@ -35,6 +35,10 @@ export function getTestsRepoDir() {
35
35
  * Single-repo / standalone calls (no `repository`) never set or read this.
36
36
  */
37
37
  let _activeRunStatePath;
38
+ /** Filename of the run-scoped analysis state file under `<RUNNER_TEMP>/skyramp/`.
39
+ * Single-sourced so the constructor that WRITES there and `resolveRunStatePath`
40
+ * which LOOKS there cannot drift apart. */
41
+ export const RUN_STATE_FILE_NAME = "analyze-changes-state.json";
38
42
  export function setActiveRunStatePath(stateFilePath) {
39
43
  _activeRunStatePath = stateFilePath;
40
44
  }
@@ -45,6 +49,32 @@ export function getActiveRunStatePath() {
45
49
  export function clearActiveRunStatePath() {
46
50
  _activeRunStatePath = undefined;
47
51
  }
52
+ /**
53
+ * The run's state-file path, for tools that need to read or amend it without the
54
+ * agent naming it.
55
+ *
56
+ * `setActiveRunStatePath` is only called by skyramp_analyze_changes on multi-repo
57
+ * runs (the ones carrying a `repository` param), but a single-repo Testbot run's
58
+ * path is still deterministic in CI: the StateManager constructor above roots
59
+ * "analysis" state under `$RUNNER_TEMP/skyramp/`. Falling back to that covers
60
+ * single-repo runs without a new anchor mechanism. Standalone/IDE usage has no
61
+ * RUNNER_TEMP and resolves to undefined — correct, since there is no run state.
62
+ *
63
+ * Lives here rather than in each caller so the convention has one home: this is
64
+ * the module that decides where the file goes, so it is the module that should
65
+ * decide where to look for it.
66
+ */
67
+ export function resolveRunStatePath(explicitPath) {
68
+ if (explicitPath)
69
+ return explicitPath;
70
+ const anchored = getActiveRunStatePath();
71
+ if (anchored)
72
+ return anchored;
73
+ if (process.env.RUNNER_TEMP) {
74
+ return path.join(process.env.RUNNER_TEMP, "skyramp", RUN_STATE_FILE_NAME);
75
+ }
76
+ return undefined;
77
+ }
48
78
  /**
49
79
  * In-memory session store: sessionId → { data, storedAt }.
50
80
  * Eliminates the need for the LLM to read/write state files on disk.
@@ -149,7 +179,7 @@ export class StateManager {
149
179
  // (see getTestbotPrompt TODO). RUNNER_TEMP is GitHub Actions-specific; other CI systems
150
180
  // (Jenkins, GitLab, Buildkite) must set it explicitly. Once merged, path flows through
151
181
  // summaryOutputFile which is already testbot-controlled and CI-agnostic.
152
- this.stateFile = path.join(process.env.RUNNER_TEMP, "skyramp", "analyze-changes-state.json");
182
+ this.stateFile = path.join(process.env.RUNNER_TEMP, "skyramp", RUN_STATE_FILE_NAME);
153
183
  }
154
184
  else {
155
185
  const baseDir = stateDir || os.tmpdir();
@@ -246,7 +276,11 @@ export class StateManager {
246
276
  repository: options?.repository ?? existingMetadata?.repository,
247
277
  createdAt: existingMetadata?.createdAt || new Date().toISOString(),
248
278
  updatedAt: new Date().toISOString(),
249
- step: options?.step,
279
+ // Preserve the prior step when the caller omits one (matches
280
+ // repositoryPath/repository/createdAt above) so a metadata-agnostic
281
+ // write — e.g. persistExternalRun appending externalTestResults —
282
+ // does not wipe the pipeline step recorded by an earlier phase.
283
+ step: options?.step ?? existingMetadata?.step,
250
284
  },
251
285
  ...(existingRelated ? { relatedRepos: existingRelated } : {}),
252
286
  };
@@ -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())
@@ -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>;
@@ -14,6 +14,9 @@ export function parseChangedFilesFromDiff(rawDiff) {
14
14
  return out;
15
15
  }
16
16
  /** Parse diff headers to find newly created and deleted files. */
17
+ function isShaLikeRef(ref) {
18
+ return /^[0-9a-f]{7,40}$/i.test(ref);
19
+ }
17
20
  function parseNewAndDeletedFiles(rawDiff) {
18
21
  const newFiles = [];
19
22
  const deletedFiles = [];
@@ -34,24 +37,87 @@ function parseNewAndDeletedFiles(rawDiff) {
34
37
  }
35
38
  return { newFiles, deletedFiles };
36
39
  }
37
- async function tryDiff(git, ref) {
40
+ /**
41
+ * Normalize and validate a caller-provided base branch/ref before passing it
42
+ * to git. Reject option-like or ambiguous values so the ref is always treated
43
+ * as a revision name, not as a command-line flag.
44
+ */
45
+ export function normalizeProvidedBaseRef(providedBaseBranch) {
46
+ if (providedBaseBranch === undefined)
47
+ return undefined;
48
+ const trimmed = providedBaseBranch.trim();
49
+ if (!trimmed)
50
+ return undefined;
51
+ if (trimmed.startsWith("-")) {
52
+ throw new Error(`Invalid base branch/ref "${providedBaseBranch}": refs must not start with '-'.`);
53
+ }
54
+ if (/\s/.test(trimmed) || trimmed.includes("\0")) {
55
+ throw new Error(`Invalid base branch/ref "${providedBaseBranch}": refs must not contain whitespace or NUL characters.`);
56
+ }
57
+ if (trimmed.includes("..")) {
58
+ throw new Error(`Invalid base branch/ref "${providedBaseBranch}": range syntax (".."/""..."") is not allowed — provide a single ref.`);
59
+ }
60
+ return trimmed;
61
+ }
62
+ async function tryDiff(git, ref, includeUncommitted = false) {
38
63
  try {
39
- const changedFilesRaw = await git.diff([`${ref}...HEAD`, "--name-only"]);
64
+ // When includeUncommitted is true, diff the ref directly against the
65
+ // working tree so uncommitted/unstaged local changes are captured.
66
+ const diffRange = includeUncommitted ? ref : `${ref}...HEAD`;
67
+ const changedFilesRaw = await git.diff([diffRange, "--name-only"]);
40
68
  const changedFiles = changedFilesRaw
41
69
  .split("\n")
42
70
  .map((f) => f.trim())
43
71
  .filter((f) => f.length > 0);
44
- const diffStat = await git.diff([`${ref}...HEAD`, "--stat"]);
45
- const fullDiff = await git.diff([`${ref}...HEAD`]);
72
+ const diffStat = await git.diff([diffRange, "--stat"]);
73
+ const fullDiff = await git.diff([diffRange]);
46
74
  const { newFiles, deletedFiles } = parseNewAndDeletedFiles(fullDiff);
47
- return { changedFiles, diffContent: fullDiff, diffStat, newFiles, deletedFiles };
75
+ // git diff does not include untracked files. Include them as both changed
76
+ // and new so endpoint classification treats newly created local files as new.
77
+ if (includeUncommitted) {
78
+ const statusResult = await git.status();
79
+ const untrackedFiles = statusResult.not_added ?? [];
80
+ if (untrackedFiles.length > 0) {
81
+ const changedSet = new Set(changedFiles);
82
+ const newSet = new Set(newFiles);
83
+ for (const file of untrackedFiles) {
84
+ if (!changedSet.has(file)) {
85
+ changedFiles.push(file);
86
+ changedSet.add(file);
87
+ }
88
+ if (!newSet.has(file)) {
89
+ newFiles.push(file);
90
+ newSet.add(file);
91
+ }
92
+ }
93
+ }
94
+ }
95
+ return {
96
+ changedFiles,
97
+ diffContent: fullDiff,
98
+ diffStat,
99
+ newFiles,
100
+ deletedFiles,
101
+ };
48
102
  }
49
103
  catch (err) {
50
- logger.debug(`tryDiff against ${ref} failed`, { error: err instanceof Error ? err.message : String(err) });
104
+ logger.debug(`tryDiff against ${ref} failed`, {
105
+ error: err instanceof Error ? err.message : String(err),
106
+ });
51
107
  return undefined;
52
108
  }
53
109
  }
54
- export async function computeBranchDiff(repositoryPath, providedBaseBranch) {
110
+ /**
111
+ * Compute the diff between a base reference and the current state.
112
+ *
113
+ * @param repositoryPath Absolute path to the git repository.
114
+ * @param providedBaseBranch A branch name, tag, or commit SHA to diff against.
115
+ * When omitted, the remote default branch is auto-detected.
116
+ * @param includeUncommitted When true, diffs against the working tree so
117
+ * uncommitted/unstaged changes are included. Defaults to false (CI mode
118
+ * that only considers committed changes via the three-dot form).
119
+ */
120
+ export async function computeBranchDiff(repositoryPath, providedBaseBranch, includeUncommitted = false) {
55
121
  const git = simpleGit(repositoryPath);
56
122
  const isRepo = await git.checkIsRepo();
57
123
  if (!isRepo) {
@@ -60,14 +126,32 @@ export async function computeBranchDiff(repositoryPath, providedBaseBranch) {
60
126
  const branchInfo = await git.branch();
61
127
  const currentBranch = branchInfo.current || "HEAD";
62
128
  // Build candidate base refs in priority order:
63
- // 1. Provided branch name (most specific)
64
- // 2. Remote default branch auto-detected from available remotes
65
- // 3. FETCH_HEAD — present in base_commit repos (git fetch --depth 1 origin <sha>)
129
+ // 1. Provided value used directly (supports SHAs, local branches, tags)
130
+ // 2. Provided value with origin/ prefix (remote branch)
131
+ // 3. Remote default branch auto-detected from available remotes
132
+ // 4. FETCH_HEAD — present in base_commit repos (git fetch --depth 1 origin <sha>)
66
133
  // where no remote tracking branch is created
67
- // 4. HEAD~1 — last resort for repos with no remote at all
134
+ // 5. HEAD~1 — last resort for repos with no remote at all
68
135
  const candidates = [];
69
- if (providedBaseBranch) {
70
- candidates.push(`origin/${providedBaseBranch}`);
136
+ const normalizedBaseBranch = normalizeProvidedBaseRef(providedBaseBranch);
137
+ if (normalizedBaseBranch) {
138
+ const isRemoteQualified = normalizedBaseBranch.startsWith("origin/");
139
+ const looksLikeSimpleBranch = !isRemoteQualified &&
140
+ !isShaLikeRef(normalizedBaseBranch) &&
141
+ !/[~^:@]/.test(normalizedBaseBranch);
142
+ if (includeUncommitted && looksLikeSimpleBranch) {
143
+ // Local-dev diffs should prefer the remote base branch when available;
144
+ // a developer's local main may be ahead of origin/main.
145
+ candidates.push(`origin/${normalizedBaseBranch}`);
146
+ candidates.push(normalizedBaseBranch);
147
+ }
148
+ else {
149
+ // Try the raw value first — handles commit SHAs, tags, and explicit local refs.
150
+ candidates.push(normalizedBaseBranch);
151
+ if (!isRemoteQualified) {
152
+ candidates.push(`origin/${normalizedBaseBranch}`);
153
+ }
154
+ }
71
155
  }
72
156
  // Auto-detect remote default branch
73
157
  try {
@@ -89,12 +173,13 @@ export async function computeBranchDiff(repositoryPath, providedBaseBranch) {
89
173
  // Final fallback
90
174
  candidates.push("HEAD~1");
91
175
  for (const ref of candidates) {
92
- const result = await tryDiff(git, ref);
176
+ const result = await tryDiff(git, ref, includeUncommitted);
93
177
  if (result) {
94
178
  logger.info("Branch diff computed", {
95
179
  currentBranch,
96
180
  baseBranch: ref,
97
181
  changedFiles: result.changedFiles.length,
182
+ includeUncommitted,
98
183
  });
99
184
  return { currentBranch, baseBranch: ref, ...result };
100
185
  }
@@ -35,3 +35,34 @@ export declare function isContractConsumerModeEnabled(): boolean;
35
35
  * (SKYRAMP_FEATURE_TESTBOT=1).
36
36
  */
37
37
  export declare function isTestbotEnabled(): boolean;
38
+ /**
39
+ * Returns true when the local-dev workflow is enabled
40
+ * (SKYRAMP_FEATURE_LOCAL_DEV=1).
41
+ *
42
+ * The local-dev workflow registers the `skyramp_local_dev` prompt and
43
+ * one-click routing, which diffs from a base commit against the current
44
+ * working state, generates mock configurations and backend tests, executes
45
+ * them locally, and reports results — without requiring a pull request or
46
+ * CI environment.
47
+ */
48
+ export declare function isLocalDevEnabled(): boolean;
49
+ /**
50
+ * Returns true when general one-click workflows are enabled
51
+ * (SKYRAMP_FEATURE_ONE_CLICK=1).
52
+ */
53
+ export declare function isOneClickEnabled(): boolean;
54
+ /**
55
+ * Gates the POM-aware path of `skyramp_reuse_code`
56
+ * (SKYRAMP_FEATURE_POM_REUSE=1).
57
+ *
58
+ * When OFF (default), TypeScript/JavaScript + Playwright targets are treated
59
+ * like every other target: `skyramp_reuse_code` returns the SkyrampUtils
60
+ * consolidation prompt, and none of the POM machinery runs — no deterministic
61
+ * POM pre-scoping, no POM catalog / mapping-table workflow, no `verify: true`
62
+ * compliance gates, and consequently no `reuse` summary on the report (nothing
63
+ * records an outcome to re-derive).
64
+ *
65
+ * When ON, the test is refactored against the repo's existing page objects and
66
+ * the verification gates apply.
67
+ */
68
+ export declare function isPomReuseEnabled(): boolean;
@@ -39,3 +39,40 @@ export function isContractConsumerModeEnabled() {
39
39
  export function isTestbotEnabled() {
40
40
  return process.env.SKYRAMP_FEATURE_TESTBOT === "1";
41
41
  }
42
+ /**
43
+ * Returns true when the local-dev workflow is enabled
44
+ * (SKYRAMP_FEATURE_LOCAL_DEV=1).
45
+ *
46
+ * The local-dev workflow registers the `skyramp_local_dev` prompt and
47
+ * one-click routing, which diffs from a base commit against the current
48
+ * working state, generates mock configurations and backend tests, executes
49
+ * them locally, and reports results — without requiring a pull request or
50
+ * CI environment.
51
+ */
52
+ export function isLocalDevEnabled() {
53
+ return process.env.SKYRAMP_FEATURE_LOCAL_DEV === "1";
54
+ }
55
+ /**
56
+ * Returns true when general one-click workflows are enabled
57
+ * (SKYRAMP_FEATURE_ONE_CLICK=1).
58
+ */
59
+ export function isOneClickEnabled() {
60
+ return process.env.SKYRAMP_FEATURE_ONE_CLICK === "1";
61
+ }
62
+ /**
63
+ * Gates the POM-aware path of `skyramp_reuse_code`
64
+ * (SKYRAMP_FEATURE_POM_REUSE=1).
65
+ *
66
+ * When OFF (default), TypeScript/JavaScript + Playwright targets are treated
67
+ * like every other target: `skyramp_reuse_code` returns the SkyrampUtils
68
+ * consolidation prompt, and none of the POM machinery runs — no deterministic
69
+ * POM pre-scoping, no POM catalog / mapping-table workflow, no `verify: true`
70
+ * compliance gates, and consequently no `reuse` summary on the report (nothing
71
+ * records an outcome to re-derive).
72
+ *
73
+ * When ON, the test is refactored against the repo's existing page objects and
74
+ * the verification gates apply.
75
+ */
76
+ export function isPomReuseEnabled() {
77
+ return process.env.SKYRAMP_FEATURE_POM_REUSE === "1";
78
+ }
@@ -0,0 +1 @@
1
+ export declare function getGrpcMockResponseValidationError(responseData?: string): string | null;
@@ -0,0 +1,49 @@
1
+ const GRPC_STATUS_ERROR_NAMES = new Set([
2
+ "CANCELLED",
3
+ "UNKNOWN",
4
+ "INVALID_ARGUMENT",
5
+ "DEADLINE_EXCEEDED",
6
+ "NOT_FOUND",
7
+ "ALREADY_EXISTS",
8
+ "PERMISSION_DENIED",
9
+ "RESOURCE_EXHAUSTED",
10
+ "FAILED_PRECONDITION",
11
+ "ABORTED",
12
+ "OUT_OF_RANGE",
13
+ "UNIMPLEMENTED",
14
+ "INTERNAL",
15
+ "UNAVAILABLE",
16
+ "DATA_LOSS",
17
+ "UNAUTHENTICATED",
18
+ ]);
19
+ export function getGrpcMockResponseValidationError(responseData) {
20
+ if (!responseData || responseData.trim().startsWith("@"))
21
+ return null;
22
+ let parsed;
23
+ try {
24
+ parsed = JSON.parse(responseData);
25
+ }
26
+ catch {
27
+ return null;
28
+ }
29
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
30
+ return null;
31
+ }
32
+ const record = parsed;
33
+ const candidate = record.error;
34
+ if (typeof candidate !== "string")
35
+ return null;
36
+ const normalized = normalizeGrpcStatusName(candidate);
37
+ if (!GRPC_STATUS_ERROR_NAMES.has(normalized))
38
+ return null;
39
+ return `gRPC mock responseData cannot simulate gRPC status errors such as "${normalized}". Use grpcStatusCode/grpcStatusMessage for gRPC status errors, generate a successful protobuf-shaped response, use a real failing service, or record this scenario as blocked`;
40
+ }
41
+ function normalizeGrpcStatusName(value) {
42
+ return value
43
+ .replace(/^GRPC::Status(?:::|\.)?/i, "")
44
+ .replace(/^GRPC::/i, "")
45
+ .replace(/([a-z0-9])([A-Z])/g, "$1_$2")
46
+ .replace(/[\s.:-]+/g, "_")
47
+ .replace(/^_+|_+$/g, "")
48
+ .toUpperCase();
49
+ }
@@ -0,0 +1,4 @@
1
+ import { HttpMethod } from "../types/TestTypes.js";
2
+ export declare const REST_MOCK_HTTP_METHODS: HttpMethod[];
3
+ export declare function normalizeRestMockMethod(method?: string): string;
4
+ export declare function getRestMockMethodValidationError(method?: string): string | null;
@@ -0,0 +1,15 @@
1
+ import { HttpMethod } from "../types/TestTypes.js";
2
+ export const REST_MOCK_HTTP_METHODS = Object.values(HttpMethod);
3
+ export function normalizeRestMockMethod(method) {
4
+ return method ? method.trim().toUpperCase() : "";
5
+ }
6
+ export function getRestMockMethodValidationError(method) {
7
+ if (!method)
8
+ return null;
9
+ const normalized = normalizeRestMockMethod(method);
10
+ if (!normalized)
11
+ return null; // whitespace-only → treated as empty (valid)
12
+ if (REST_MOCK_HTTP_METHODS.includes(normalized))
13
+ return null;
14
+ return `REST mock method must be one of ${REST_MOCK_HTTP_METHODS.join(", ")} or empty string (got "${method}")`;
15
+ }
@@ -22,7 +22,7 @@ export class McpLogger {
22
22
  try {
23
23
  console.error(JSON.stringify(logEntry));
24
24
  }
25
- catch (error) {
25
+ catch (_error) {
26
26
  // silently ignore
27
27
  }
28
28
  }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Mock Compatibility Pre-flight Check
3
+ *
4
+ * Validates mock protocol combinations BEFORE deployment to surface
5
+ * known incompatibilities early rather than at test execution time.
6
+ */
7
+ import { MockProtocol } from "../types/TestTypes.js";
8
+ export { MockProtocol };
9
+ /** Narrow view of a mock spec used only for protocol compatibility checking. */
10
+ export interface MockCompatibilitySpec {
11
+ protocol: MockProtocol;
12
+ endpointURL: string;
13
+ method?: string;
14
+ grpcServiceName?: string;
15
+ kafkaTopic?: string;
16
+ }
17
+ export interface CompatibilityWarning {
18
+ code: string;
19
+ message: string;
20
+ suggestion: string;
21
+ affectedMocks: string[];
22
+ /** When true, the affected mocks cannot intercept traffic and must not be generated or deployed. */
23
+ blocking?: boolean;
24
+ }
25
+ export interface CompatibilityResult {
26
+ compatible: boolean;
27
+ /** True when at least one warning is blocking — those mocks must be skipped entirely. */
28
+ hasBlockers: boolean;
29
+ warnings: CompatibilityWarning[];
30
+ }
31
+ export interface MockCompatibilityOptions {
32
+ /** Enable Docker local-dev checks that require mocks to intercept service DNS aliases. */
33
+ dockerLocalDev?: boolean;
34
+ }
35
+ /**
36
+ * Validates a set of mock specs for known protocol compatibility issues.
37
+ *
38
+ * Rules:
39
+ * - Any gRPC mock present → GRPC_PERSISTENT_CONN warning (DNS hijacking skips persistent conns)
40
+ * - Docker local-dev REST mock using loopback/host.docker.internal → REST_LOOPBACK_URL blocking warning
41
+ *
42
+ * REST mocks work correctly in Docker local-dev mode only when the mock URL uses
43
+ * the real Docker service hostname (e.g. "billing" from "http://billing:5001").
44
+ * The worker adds that hostname as a Docker network alias so DNS resolves to the mock server.
45
+ *
46
+ * `compatible` is false when any warning is blocking and the mock cannot intercept traffic.
47
+ * `hasBlockers` is true when at least one warning is blocking (mock must not be generated/deployed).
48
+ */
49
+ export declare function validateMockCompatibility(mocks: MockCompatibilitySpec[], options?: MockCompatibilityOptions): CompatibilityResult;