@skyramp/mcp 0.2.6 → 0.2.8-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.
- package/build/commands/commandLibrary.d.ts +12 -0
- package/build/commands/recommendTestsAndExecuteCommand.d.ts +2 -0
- package/build/commands/testThisEndpointCommand.d.ts +2 -0
- package/build/index.d.ts +2 -0
- package/build/index.js +6 -4
- package/build/playwright/PlaywrightTraceService.d.ts +32 -0
- package/build/playwright/index.d.ts +3 -0
- package/build/playwright/registerPlaywrightTools.d.ts +8 -0
- package/build/playwright/registerPlaywrightTools.js +12 -11
- package/build/playwright/traceRecordingPrompt.d.ts +13 -0
- package/build/playwright/traceRecordingPrompt.js +3 -11
- package/build/prompts/code-reuse.d.ts +1 -0
- package/build/prompts/enhance-assertions/contractProviderAssertionsPrompt.d.ts +2 -0
- package/build/prompts/enhance-assertions/integrationAssertionsPrompt.d.ts +2 -0
- package/build/prompts/enhance-assertions/sharedAssertionRules.d.ts +15 -0
- package/build/prompts/enhance-assertions/uiAssertionsPrompt.d.ts +2 -0
- package/build/prompts/enhance-assertions/uiAssertionsPrompt.js +24 -5
- package/build/prompts/fix-error-prompt.d.ts +1 -0
- package/build/prompts/initialize-workspace/initializeWorkspacePrompt.d.ts +1 -0
- package/build/prompts/modularization/integration-test-modularization.d.ts +1 -0
- package/build/prompts/modularization/ui-test-modularization.d.ts +1 -0
- package/build/prompts/personas.d.ts +21 -0
- package/build/prompts/pom-aware-code-reuse.d.ts +1 -0
- package/build/prompts/startTraceCollectionPrompts.d.ts +2 -0
- package/build/prompts/sut-setup/modes/adaptWorkflowPrompt.d.ts +2 -0
- package/build/prompts/sut-setup/modes/adaptWorkflowPrompt.js +114 -0
- package/build/prompts/sut-setup/modes/dockerComposePrompt.d.ts +2 -0
- package/build/prompts/sut-setup/modes/dockerComposePrompt.js +292 -0
- package/build/prompts/sut-setup/shared.d.ts +39 -0
- package/build/prompts/sut-setup/shared.js +132 -0
- package/build/prompts/test-maintenance/actionsInstructions.d.ts +35 -0
- package/build/prompts/test-maintenance/drift-analysis-prompt.d.ts +4 -0
- package/build/prompts/test-maintenance/driftAnalysisSections.d.ts +13 -0
- package/build/prompts/test-maintenance/driftAnalysisSections.js +9 -3
- package/build/prompts/test-recommendation/analysisOutputPrompt.d.ts +84 -0
- package/build/prompts/test-recommendation/analysisOutputPrompt.js +1 -1
- package/build/prompts/test-recommendation/analysisOutputPrompt.test.d.ts +1 -0
- package/build/prompts/test-recommendation/analysisOutputPrompt.test.js +1 -0
- package/build/prompts/test-recommendation/diffExecutionPlan.d.ts +17 -0
- package/build/prompts/test-recommendation/diffExecutionPlan.js +83 -18
- package/build/prompts/test-recommendation/fullRepoCatalog.d.ts +7 -0
- package/build/prompts/test-recommendation/mergeEnrichedScenarios.test.d.ts +1 -0
- package/build/prompts/test-recommendation/mergeEnrichedScenarios.test.js +1 -0
- package/build/prompts/test-recommendation/promptPlan.d.ts +203 -0
- package/build/prompts/test-recommendation/promptPlan.test.d.ts +1 -0
- package/build/prompts/test-recommendation/recommendationSections.d.ts +35 -0
- package/build/prompts/test-recommendation/recommendationShared.d.ts +31 -0
- package/build/prompts/test-recommendation/registerRecommendTestsPrompt.d.ts +7 -0
- package/build/prompts/test-recommendation/scopeAssessment.d.ts +55 -0
- package/build/prompts/test-recommendation/scopeAssessment.js +2 -2
- package/build/prompts/test-recommendation/scopeAssessment.test.d.ts +1 -0
- package/build/prompts/test-recommendation/scopeAssessment.test.js +1 -0
- package/build/prompts/test-recommendation/test-recommendation-prompt.d.ts +6 -0
- package/build/prompts/test-recommendation/test-recommendation-prompt.js +4 -4
- package/build/prompts/test-recommendation/test-recommendation-prompt.test.d.ts +1 -0
- package/build/prompts/test-recommendation/test-recommendation-prompt.test.js +97 -99
- package/build/prompts/testbot/testbot-prompts.d.ts +26 -0
- package/build/prompts/testbot/testbot-prompts.js +301 -46
- package/build/prompts/testbot/testbot-prompts.test.d.ts +1 -0
- package/build/prompts/testbot/testbot-prompts.test.js +109 -117
- package/build/resources/analysisResources.d.ts +17 -0
- package/build/resources/progressResource.d.ts +2 -0
- package/build/resources/sutSetupResource.d.ts +2 -0
- package/build/resources/sutSetupResource.js +45 -0
- package/build/resources/testbotResource.d.ts +2 -0
- package/build/resources/testbotResource.js +2 -2
- package/build/services/AnalyticsService.d.ts +13 -0
- package/build/services/AnalyticsService.test.d.ts +1 -0
- package/build/services/AnalyticsService.test.js +86 -0
- package/build/services/ModularizationService.d.ts +11 -0
- package/build/services/ScenarioGenerationService.d.ts +37 -0
- package/build/services/ScenarioGenerationService.integration.test.d.ts +1 -0
- package/build/services/ScenarioGenerationService.integration.test.js +4 -0
- package/build/services/ScenarioGenerationService.test.d.ts +1 -0
- package/build/services/TestDiscoveryService.d.ts +128 -0
- package/build/services/TestDiscoveryService.js +22 -12
- package/build/services/TestDiscoveryService.test.d.ts +1 -0
- package/build/services/TestDiscoveryService.test.js +4 -3
- package/build/services/TestExecutionService.d.ts +55 -0
- package/build/services/TestExecutionService.js +158 -26
- package/build/services/TestExecutionService.test.d.ts +1 -0
- package/build/services/TestExecutionService.test.js +339 -25
- package/build/services/TestGenerationService.d.ts +56 -0
- package/build/services/TestGenerationService.test.d.ts +1 -0
- package/build/services/TestGenerationService.test.js +10 -8
- package/build/services/containerEnv.d.ts +14 -0
- package/build/tool-phase-coverage.test.d.ts +1 -0
- package/build/tool-phase-coverage.test.js +2 -0
- package/build/tool-phases.d.ts +38 -0
- package/build/tools/auth/loginTool.d.ts +2 -0
- package/build/tools/auth/logoutTool.d.ts +2 -0
- package/build/tools/code-refactor/codeReuseTool.d.ts +2 -0
- package/build/tools/code-refactor/enhanceAssertionsTool.d.ts +2 -0
- package/build/tools/code-refactor/modularizationTool.d.ts +2 -0
- package/build/tools/executeSkyrampTestTool.d.ts +2 -0
- package/build/tools/executeSkyrampTestTool.js +26 -7
- package/build/tools/fixErrorTool.d.ts +2 -0
- package/build/tools/generate-tests/generateBatchScenarioRestTool.d.ts +45 -0
- package/build/tools/generate-tests/generateBatchScenarioRestTool.js +1 -1
- package/build/tools/generate-tests/generateBatchScenarioRestTool.test.d.ts +1 -0
- package/build/tools/generate-tests/generateBatchScenarioRestTool.test.js +11 -9
- package/build/tools/generate-tests/generateContractRestTool.d.ts +31 -0
- package/build/tools/generate-tests/generateE2ERestTool.d.ts +13 -0
- package/build/tools/generate-tests/generateFuzzRestTool.d.ts +10 -0
- package/build/tools/generate-tests/generateIntegrationRestTool.d.ts +16 -0
- package/build/tools/generate-tests/generateLoadRestTool.d.ts +18 -0
- package/build/tools/generate-tests/generateLoadRestTool.test.d.ts +1 -0
- package/build/tools/generate-tests/generateMockRestTool.d.ts +64 -0
- package/build/tools/generate-tests/generateSmokeRestTool.d.ts +8 -0
- package/build/tools/generate-tests/generateUIRestTool.d.ts +14 -0
- package/build/tools/generate-tests/generateUIRestTool.js +1 -1
- package/build/tools/generate-tests/loadTestSchema.d.ts +39 -0
- package/build/tools/one-click/oneClickTool.d.ts +11 -0
- package/build/tools/submitReportTool.d.ts +394 -0
- package/build/tools/submitReportTool.js +250 -41
- package/build/tools/submitReportTool.test.d.ts +1 -0
- package/build/tools/submitReportTool.test.js +511 -55
- package/build/tools/test-management/actionsTool.d.ts +10 -0
- package/build/tools/test-management/actionsTool.js +21 -7
- package/build/tools/test-management/analyzeChangesTool.d.ts +27 -0
- package/build/tools/test-management/analyzeChangesTool.js +150 -48
- package/build/tools/test-management/analyzeChangesTool.test.d.ts +1 -0
- package/build/tools/test-management/analyzeChangesTool.test.js +95 -78
- package/build/tools/test-management/analyzeTestHealthTool.d.ts +2 -0
- package/build/tools/test-management/analyzeTestHealthTool.js +16 -9
- package/build/tools/test-management/index.d.ts +3 -0
- package/build/tools/test-management/index.js +0 -1
- package/build/tools/trace/resolveSaveStoragePath.d.ts +7 -0
- package/build/tools/trace/resolveSaveStoragePath.test.d.ts +1 -0
- package/build/tools/trace/resolveSessionPaths.d.ts +40 -0
- package/build/tools/trace/resolveSessionPaths.test.d.ts +1 -0
- package/build/tools/trace/sessionState.d.ts +2 -0
- package/build/tools/trace/sessionState.test.d.ts +1 -0
- package/build/tools/trace/startTraceCollectionTool.d.ts +2 -0
- package/build/tools/trace/stopTraceCollectionTool.d.ts +2 -0
- package/build/tools/workspace/initScanWorkspaceTool.d.ts +4 -0
- package/build/tools/workspace/initializeWorkspaceTool.d.ts +2 -0
- package/build/tools/workspace/initializeWorkspaceTool.js +39 -22
- package/build/tools/workspace/initializeWorkspaceTool.test.d.ts +1 -0
- package/build/tools/workspace/initializeWorkspaceTool.test.js +134 -0
- package/build/tools/workspace/serviceUpsert.d.ts +12 -0
- package/build/tools/workspace/serviceUpsert.js +23 -0
- package/build/tools/workspace/serviceUpsert.test.d.ts +1 -0
- package/build/tools/workspace/serviceUpsert.test.js +50 -0
- package/build/types/OneClickCommands.d.ts +68 -0
- package/build/types/RepositoryAnalysis.d.ts +3004 -0
- package/build/types/TestAnalysis.d.ts +97 -0
- package/build/types/TestExecution.d.ts +63 -0
- package/build/types/TestExecution.js +11 -1
- package/build/types/TestRecommendation.d.ts +12 -0
- package/build/types/TestTypes.d.ts +234 -0
- package/build/types/TestbotReport.d.ts +68 -0
- package/build/types/TestbotReport.js +1 -0
- package/build/types/index.d.ts +4 -0
- package/build/types/index.js +3 -0
- package/build/utils/AnalysisStateManager.d.ts +242 -0
- package/build/utils/AnalysisStateManager.js +144 -3
- package/build/utils/AnalysisStateManager.test.d.ts +1 -0
- package/build/utils/AnalysisStateManager.test.js +98 -0
- package/build/utils/analyze-openapi.d.ts +1 -0
- package/build/utils/branchDiff.d.ts +18 -0
- package/build/utils/dartRouteExtractor.d.ts +45 -0
- package/build/utils/dartRouteExtractor.test.d.ts +1 -0
- package/build/utils/docker.d.ts +19 -0
- package/build/utils/docker.test.d.ts +1 -0
- package/build/utils/docker.test.js +10 -9
- package/build/utils/featureFlags.d.ts +37 -0
- package/build/utils/featureFlags.test.d.ts +1 -0
- package/build/utils/gitStaging.d.ts +24 -0
- package/build/utils/gitStaging.test.d.ts +1 -0
- package/build/utils/gitStaging.test.js +13 -7
- package/build/utils/httpDefaults.d.ts +10 -0
- package/build/utils/httpDefaults.test.d.ts +1 -0
- package/build/utils/initAgent.d.ts +36 -0
- package/build/utils/language-helper.d.ts +6 -0
- package/build/utils/logger.d.ts +11 -0
- package/build/utils/normalizeParams.d.ts +14 -0
- package/build/utils/normalizeSkyrampImports.d.ts +13 -0
- package/build/utils/pr-comment-parser.d.ts +31 -0
- package/build/utils/pr-comment-parser.test.d.ts +1 -0
- package/build/utils/pr-comment-parser.test.js +6 -5
- package/build/utils/projectMetadata.d.ts +15 -0
- package/build/utils/projectMetadata.test.d.ts +1 -0
- package/build/utils/proxy-terminal.d.ts +2 -0
- package/build/utils/repoScanner.d.ts +27 -0
- package/build/utils/repoScanner.test.d.ts +1 -0
- package/build/utils/routeParsers.d.ts +53 -0
- package/build/utils/routeParsers.js +31 -38
- package/build/utils/routeParsers.test.d.ts +1 -0
- package/build/utils/scenarioDrafting.d.ts +71 -0
- package/build/utils/scenarioDrafting.js +23 -1
- package/build/utils/scenarioDrafting.test.d.ts +1 -0
- package/build/utils/skyrampMdContent.d.ts +5 -0
- package/build/utils/sourceRouteExtractor.d.ts +48 -0
- package/build/utils/sourceRouteExtractor.test.d.ts +1 -0
- package/build/utils/telemetry.d.ts +32 -0
- package/build/utils/telemetry.test.d.ts +1 -0
- package/build/utils/trace-parser.d.ts +32 -0
- package/build/utils/trace-parser.test.d.ts +6 -0
- package/build/utils/uiPageEnumerator.d.ts +146 -0
- package/build/utils/uiPageEnumerator.js +58 -14
- package/build/utils/uiPageEnumerator.test.d.ts +1 -0
- package/build/utils/uiPageEnumerator.test.js +223 -43
- package/build/utils/utils.d.ts +45 -0
- package/build/utils/utils.js +10 -0
- package/build/utils/utils.test.d.ts +1 -0
- package/build/utils/versions.d.ts +3 -0
- package/build/utils/versions.js +1 -1
- package/build/utils/workspaceAuth.d.ts +127 -0
- package/build/utils/workspaceAuth.test.d.ts +1 -0
- package/build/utils/workspaceAuth.test.js +25 -25
- package/build/workspace/index.d.ts +1 -0
- package/build/workspace/workspace.d.ts +390 -0
- package/build/workspace/workspace.js +7 -7
- package/build/workspace/workspace.test.d.ts +1 -0
- package/build/workspace/workspace.test.js +6 -6
- package/node_modules/playwright/lib/mcp/skyramp/loadTraceTool.js +31 -13
- package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +88 -15
- package/node_modules/playwright/lib/mcp/test/skyRampExport.js +4 -3
- package/node_modules/playwright/node_modules/playwright-core/ThirdPartyNotices.txt +23 -126
- package/node_modules/playwright/node_modules/playwright-core/lib/generated/injectedScriptSource.js +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/recorderApp.js +79 -19
- package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/skyramp/aliasRemapper.js +84 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/skyramp/replayEngine.js +15 -4
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/assets/{codeMirrorModule-CPZKmjFR.js → codeMirrorModule-D0BjbCb7.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/assets/index-lvTRGFx-.js +193 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/index.html +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{codeMirrorModule-B0JOjboO.js → codeMirrorModule-Cqp3cwEJ.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{defaultSettingsView-1anWeyDf.js → defaultSettingsView-CYf9adZh.js} +79 -79
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{index.Cc5029a3.js → index.B_7ywgmr.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html +2 -2
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.html +2 -2
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{uiMode.Wo5yvvVh.js → uiMode.u4_8VnCV.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/package.json +1 -1
- package/node_modules/playwright/node_modules/playwright-core/src/generated/injectedScriptSource.ts +1 -1
- package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/recorderApp.ts +129 -43
- package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/skyramp/aliasRemapper.ts +119 -0
- package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/skyramp/replayEngine.ts +33 -10
- package/node_modules/playwright/package.json +1 -1
- package/package.json +16 -4
- package/build/tools/test-management/uiAnalyzeChangesTool.js +0 -155
- package/build/tools/test-management/uiAnalyzeChangesTool.test.js +0 -147
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/.bin/node-which +0 -52
- package/node_modules/playwright/node_modules/playwright-core/bundles/utils/node_modules/.bin/is-docker +0 -5
- package/node_modules/playwright/node_modules/playwright-core/bundles/utils/node_modules/.bin/mime +0 -46
- package/node_modules/playwright/node_modules/playwright-core/bundles/utils/node_modules/.bin/yaml +0 -11
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/assets/index-CsgPR2wy.js +0 -193
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.CP3Y8Zsb.js +0 -2
|
@@ -1,70 +1,101 @@
|
|
|
1
|
-
//
|
|
2
|
-
jest
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
// @ts-nocheck - Jest ESM type inference issues
|
|
2
|
+
import { jest } from "@jest/globals";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
// Mock all heavy dependencies so the module can be loaded in isolation.
|
|
5
|
+
// Under native ESM, jest.mock is not hoisted — every mock the subject's import
|
|
6
|
+
// graph touches must use unstable_mockModule before the dynamic import below.
|
|
7
|
+
jest.unstable_mockModule("simple-git", () => ({ simpleGit: jest.fn() }));
|
|
8
|
+
jest.unstable_mockModule("../../services/AnalyticsService.js", () => ({
|
|
5
9
|
AnalyticsService: { pushMCPToolEvent: jest.fn() },
|
|
6
10
|
}));
|
|
7
|
-
jest.
|
|
11
|
+
jest.unstable_mockModule("../../prompts/test-recommendation/test-recommendation-prompt.js", () => ({
|
|
8
12
|
buildRecommendationPrompt: jest.fn(),
|
|
9
13
|
}));
|
|
10
|
-
jest.
|
|
14
|
+
jest.unstable_mockModule("../../prompts/test-recommendation/recommendationSections.js", () => ({
|
|
11
15
|
MAX_RECOMMENDATIONS: 10,
|
|
12
16
|
MAX_TESTS_TO_GENERATE: 3,
|
|
13
17
|
}));
|
|
14
|
-
jest.
|
|
18
|
+
jest.unstable_mockModule("../../prompts/test-recommendation/analysisOutputPrompt.js", () => ({
|
|
15
19
|
buildAnalysisOutputText: jest.fn(),
|
|
16
20
|
}));
|
|
17
|
-
jest.
|
|
21
|
+
jest.unstable_mockModule("../../services/TestDiscoveryService.js", () => ({
|
|
18
22
|
TestDiscoveryService: jest.fn(),
|
|
19
23
|
}));
|
|
20
|
-
jest.
|
|
24
|
+
jest.unstable_mockModule("../../utils/branchDiff.js", () => ({
|
|
21
25
|
computeBranchDiff: jest.fn(),
|
|
22
26
|
}));
|
|
23
|
-
jest.
|
|
27
|
+
jest.unstable_mockModule("../../utils/routeParsers.js", () => ({
|
|
24
28
|
classifyEndpointsByChangedFiles: jest.fn(),
|
|
25
29
|
extractResourceFromPath: jest.fn(),
|
|
26
30
|
parseFileEndpoints: jest.fn(),
|
|
27
31
|
}));
|
|
28
|
-
jest.
|
|
32
|
+
jest.unstable_mockModule("../../utils/repoScanner.js", () => ({
|
|
29
33
|
scanAllRepoEndpoints: jest.fn(),
|
|
30
34
|
scanRelatedEndpoints: jest.fn(),
|
|
31
35
|
grepRouterMountingContext: jest.fn(),
|
|
32
36
|
findCandidateRouteFiles: jest.fn(),
|
|
33
37
|
}));
|
|
34
|
-
jest.
|
|
38
|
+
jest.unstable_mockModule("../../utils/projectMetadata.js", () => ({
|
|
35
39
|
detectProjectMetadata: jest.fn(),
|
|
36
40
|
}));
|
|
37
|
-
jest.
|
|
41
|
+
jest.unstable_mockModule("../../utils/scenarioDrafting.js", () => ({
|
|
38
42
|
draftScenariosFromEndpoints: jest.fn(),
|
|
39
43
|
}));
|
|
40
|
-
jest.
|
|
44
|
+
jest.unstable_mockModule("../../utils/trace-parser.js", () => ({
|
|
41
45
|
parseTraceFile: jest.fn(),
|
|
42
46
|
discoverTraceFiles: jest.fn(),
|
|
43
47
|
discoverPlaywrightZips: jest.fn(),
|
|
44
48
|
}));
|
|
45
|
-
jest.
|
|
49
|
+
jest.unstable_mockModule("../../utils/pr-comment-parser.js", () => ({
|
|
46
50
|
parsePRComments: jest.fn(),
|
|
47
51
|
}));
|
|
48
|
-
jest.
|
|
49
|
-
|
|
52
|
+
jest.unstable_mockModule("../../utils/AnalysisStateManager.js", () => {
|
|
53
|
+
const makeInstance = () => ({
|
|
50
54
|
writeData: jest.fn(),
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
jest.
|
|
55
|
+
writeRepoData: jest.fn(),
|
|
56
|
+
getStatePath: jest.fn().mockReturnValue("/tmp/skyramp/analysis-state.json"),
|
|
57
|
+
});
|
|
58
|
+
const StateManager = jest.fn().mockImplementation(makeInstance);
|
|
59
|
+
StateManager.fromStatePath = jest.fn().mockImplementation(makeInstance);
|
|
60
|
+
return {
|
|
61
|
+
StateManager,
|
|
62
|
+
registerSession: jest.fn(),
|
|
63
|
+
storeSessionData: jest.fn(),
|
|
64
|
+
setTestsRepoDir: jest.fn(),
|
|
65
|
+
getActiveRunStatePath: jest.fn(),
|
|
66
|
+
setActiveRunStatePath: jest.fn(),
|
|
67
|
+
clearActiveRunStatePath: jest.fn(),
|
|
68
|
+
};
|
|
69
|
+
});
|
|
70
|
+
// Keep every real workspaceAuth export the subject's import graph needs
|
|
71
|
+
// (readWorkspaceConfigRaw, getDefaultAuthHeader, WorkspaceAuthType, …) and
|
|
72
|
+
// override only parseWorkspaceAuthType. requireActual can't load ESM
|
|
73
|
+
// synchronously, so import the real module first (mock is not hoisted).
|
|
74
|
+
const realWorkspaceAuth = await import("../../utils/workspaceAuth.js");
|
|
75
|
+
jest.unstable_mockModule("../../utils/workspaceAuth.js", () => ({
|
|
76
|
+
...realWorkspaceAuth,
|
|
56
77
|
parseWorkspaceAuthType: jest.fn(),
|
|
57
78
|
}));
|
|
58
|
-
jest.
|
|
79
|
+
jest.unstable_mockModule("../../utils/logger.js", () => ({
|
|
59
80
|
logger: { info: jest.fn(), debug: jest.fn(), error: jest.fn(), warn: jest.fn(), warning: jest.fn() },
|
|
60
81
|
}));
|
|
61
|
-
jest.
|
|
82
|
+
jest.unstable_mockModule("@modelcontextprotocol/sdk/server/mcp.js", () => ({
|
|
62
83
|
McpServer: jest.fn(),
|
|
63
84
|
}));
|
|
64
|
-
jest.
|
|
65
|
-
jest.
|
|
66
|
-
|
|
67
|
-
|
|
85
|
+
jest.unstable_mockModule("@modelcontextprotocol/sdk/types.js", () => ({}));
|
|
86
|
+
jest.unstable_mockModule("@modelcontextprotocol/sdk/shared/protocol.js", () => ({}));
|
|
87
|
+
// All subject + mocked-module imports must be dynamic and live AFTER the
|
|
88
|
+
// unstable_mockModule calls above — static imports hoist above the mocks under
|
|
89
|
+
// native ESM, loading the real (heavy) modules unmocked.
|
|
90
|
+
const { analyzeChangesInputSchema, isSecurityRelevantDiff, filterEndpointsBySpec, filterUnsupportedGraphQLEndpoints, isGraphQLFile, registerAnalyzeChangesTool, buildTraceFileEntry, buildCaptureInstructions, NO_UI_INSTRUCTIONS, NO_RESOLVABLE_URLS_INSTRUCTIONS, } = await import("./analyzeChangesTool.js");
|
|
91
|
+
const { computeBranchDiff } = await import("../../utils/branchDiff.js");
|
|
92
|
+
const { classifyEndpointsByChangedFiles } = await import("../../utils/routeParsers.js");
|
|
93
|
+
const { scanRelatedEndpoints, scanAllRepoEndpoints } = await import("../../utils/repoScanner.js");
|
|
94
|
+
const { StateManager } = await import("../../utils/AnalysisStateManager.js");
|
|
95
|
+
const os = await import("os");
|
|
96
|
+
const path = await import("path");
|
|
97
|
+
const fsSync = await import("fs");
|
|
98
|
+
const fsModule = fsSync;
|
|
68
99
|
const schema = z.object(analyzeChangesInputSchema);
|
|
69
100
|
describe("isSecurityRelevantDiff", () => {
|
|
70
101
|
it("matches auth-specific middleware and admin-key signals", () => {
|
|
@@ -78,68 +109,35 @@ describe("isSecurityRelevantDiff", () => {
|
|
|
78
109
|
expect(isSecurityRelevantDiff("move compression middleware to server setup")).toBe(false);
|
|
79
110
|
});
|
|
80
111
|
});
|
|
81
|
-
describe("analyzeChangesInputSchema — stateOutputFile validation", () => {
|
|
82
|
-
it("accepts a valid absolute path", () => {
|
|
83
|
-
const result = schema.safeParse({
|
|
84
|
-
repositoryPath: "/repo",
|
|
85
|
-
stateOutputFile: "/tmp/analyze-changes-state.json",
|
|
86
|
-
});
|
|
87
|
-
expect(result.success).toBe(true);
|
|
88
|
-
});
|
|
89
|
-
it("rejects a relative path for stateOutputFile", () => {
|
|
90
|
-
// stateOutputFile must be absolute so the caller can guarantee the file location.
|
|
91
|
-
// Relative paths are silently ambiguous and should be rejected.
|
|
92
|
-
const result = schema.safeParse({
|
|
93
|
-
repositoryPath: "/repo",
|
|
94
|
-
stateOutputFile: "relative/path/state.json",
|
|
95
|
-
});
|
|
96
|
-
expect(result.success).toBe(false);
|
|
97
|
-
});
|
|
98
|
-
it("accepts absence of stateOutputFile (optional field)", () => {
|
|
99
|
-
const result = schema.safeParse({ repositoryPath: "/repo" });
|
|
100
|
-
expect(result.success).toBe(true);
|
|
101
|
-
});
|
|
102
|
-
});
|
|
103
112
|
describe("automatic old files cleanup", () => {
|
|
104
|
-
let StateManager;
|
|
105
113
|
let cleanupOldFilesSpy;
|
|
106
114
|
beforeEach(() => {
|
|
107
115
|
jest.clearAllMocks();
|
|
108
|
-
StateManager = require("../../utils/AnalysisStateManager.js").StateManager;
|
|
109
116
|
cleanupOldFilesSpy = jest.fn().mockResolvedValue(0);
|
|
110
117
|
StateManager.cleanupOldFiles = cleanupOldFilesSpy;
|
|
111
118
|
});
|
|
112
|
-
it("calls cleanupOldFiles with default temp dir
|
|
113
|
-
// This test verifies the cleanup call is made without stateDir when using default location
|
|
119
|
+
it("calls cleanupOldFiles with default temp dir", async () => {
|
|
114
120
|
expect(StateManager.cleanupOldFiles).toBeDefined();
|
|
115
121
|
await StateManager.cleanupOldFiles(24, undefined);
|
|
116
122
|
expect(cleanupOldFilesSpy).toHaveBeenCalledWith(24, undefined);
|
|
117
123
|
});
|
|
118
|
-
it("calls cleanupOldFiles with custom dir
|
|
119
|
-
// This test verifies the cleanup call is made with the directory of stateOutputFile
|
|
124
|
+
it("calls cleanupOldFiles with custom dir", async () => {
|
|
120
125
|
const customPath = "/custom/dir";
|
|
121
126
|
await StateManager.cleanupOldFiles(24, customPath);
|
|
122
127
|
expect(cleanupOldFilesSpy).toHaveBeenCalledWith(24, customPath);
|
|
123
128
|
});
|
|
124
|
-
it("calls cleanupOldFiles with empty stateTypes
|
|
125
|
-
// analyzeTestHealthTool passes [] so state files (skyramp-analysis-*, skyramp-recommendation-*)
|
|
126
|
-
// are never deleted — the caller still needs args.stateFile for skyramp_actions.
|
|
129
|
+
it("calls cleanupOldFiles with empty stateTypes", async () => {
|
|
127
130
|
await StateManager.cleanupOldFiles(24, undefined, []);
|
|
128
131
|
expect(cleanupOldFilesSpy).toHaveBeenCalledWith(24, undefined, []);
|
|
129
132
|
});
|
|
130
133
|
it("continues execution if cleanup fails", async () => {
|
|
131
|
-
// Cleanup failures should not crash the analyze flow
|
|
132
134
|
cleanupOldFilesSpy.mockRejectedValue(new Error("permission denied"));
|
|
133
|
-
await expect(StateManager.cleanupOldFiles(24).catch(() =>
|
|
134
|
-
// In the real code, this is caught and logged as a warning
|
|
135
|
-
return Promise.resolve();
|
|
136
|
-
})).resolves.toBeUndefined();
|
|
135
|
+
await expect(StateManager.cleanupOldFiles(24).catch(() => Promise.resolve())).resolves.toBeUndefined();
|
|
137
136
|
});
|
|
138
137
|
});
|
|
139
138
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
140
139
|
// filterEndpointsBySpec — spec filtering + merge logic (Step 4c / 4c-merge)
|
|
141
140
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
142
|
-
import { filterEndpointsBySpec } from "./analyzeChangesTool.js";
|
|
143
141
|
describe("filterEndpointsBySpec", () => {
|
|
144
142
|
const makeEp = (path, methods = ["GET"]) => ({ path, methods, sourceFile: "" });
|
|
145
143
|
it("removes scanned endpoints that are not in the spec and not diff-changed", () => {
|
|
@@ -224,10 +222,6 @@ describe("filterEndpointsBySpec", () => {
|
|
|
224
222
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
225
223
|
// isGraphQLFile — unit tests
|
|
226
224
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
227
|
-
import { filterUnsupportedGraphQLEndpoints, isGraphQLFile } from "./analyzeChangesTool.js";
|
|
228
|
-
import * as os from "os";
|
|
229
|
-
import * as path from "path";
|
|
230
|
-
import * as fsSync from "fs";
|
|
231
225
|
describe("isGraphQLFile", () => {
|
|
232
226
|
const tmpDir = os.tmpdir();
|
|
233
227
|
it("returns true for .graphql extension without reading content", async () => {
|
|
@@ -303,14 +297,6 @@ describe("filterUnsupportedGraphQLEndpoints", () => {
|
|
|
303
297
|
readSpy.mockRestore();
|
|
304
298
|
});
|
|
305
299
|
});
|
|
306
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
307
|
-
// analyzeChangesTool handler — GraphQL-only early return (handler-level)
|
|
308
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
309
|
-
import { registerAnalyzeChangesTool, buildTraceFileEntry } from "./analyzeChangesTool.js";
|
|
310
|
-
import { computeBranchDiff } from "../../utils/branchDiff.js";
|
|
311
|
-
import { classifyEndpointsByChangedFiles } from "../../utils/routeParsers.js";
|
|
312
|
-
import { scanRelatedEndpoints } from "../../utils/repoScanner.js";
|
|
313
|
-
import * as fsModule from "fs";
|
|
314
300
|
/** Register the tool against a minimal mock server and return the captured handler.
|
|
315
301
|
* The handler takes (params, extra) — extra is pre-filled with a no-op sendNotification. */
|
|
316
302
|
function captureAnalyzeHandler() {
|
|
@@ -399,7 +385,6 @@ describe("analyzeChangesTool handler — GraphQL-only early return", () => {
|
|
|
399
385
|
readSpy.mockRestore();
|
|
400
386
|
});
|
|
401
387
|
it("does NOT early-return when scope is full_repo (only fires for PR diffs)", async () => {
|
|
402
|
-
const { scanAllRepoEndpoints } = require("../../utils/repoScanner.js");
|
|
403
388
|
scanAllRepoEndpoints.mockReturnValue([]);
|
|
404
389
|
const handler = captureAnalyzeHandler();
|
|
405
390
|
const result = await handler({ ...baseParams, scope: "full_repo" });
|
|
@@ -437,3 +422,35 @@ describe("buildTraceFileEntry", () => {
|
|
|
437
422
|
expect(entry.userFlows).toEqual([]);
|
|
438
423
|
});
|
|
439
424
|
});
|
|
425
|
+
describe("UI blueprint-capture instructions (merged from ui_analyze_changes)", () => {
|
|
426
|
+
it("NO_UI_INSTRUCTIONS signals no capture is needed and references no follow-up tool", () => {
|
|
427
|
+
expect(NO_UI_INSTRUCTIONS).toMatch(/no blueprint capture needed/i);
|
|
428
|
+
// The former pre-flight tool told the agent to "proceed to
|
|
429
|
+
// skyramp_analyze_changes" — that's redundant now we're inside it.
|
|
430
|
+
expect(NO_UI_INSTRUCTIONS).not.toMatch(/skyramp_analyze_changes/);
|
|
431
|
+
});
|
|
432
|
+
it("NO_RESOLVABLE_URLS_INSTRUCTIONS falls back to source-grounded", () => {
|
|
433
|
+
expect(NO_RESOLVABLE_URLS_INSTRUCTIONS).toMatch(/source-grounded/i);
|
|
434
|
+
});
|
|
435
|
+
it("buildCaptureInstructions numbers each candidate URL with its provenance", () => {
|
|
436
|
+
const pages = [
|
|
437
|
+
{ url: "http://localhost:3000/notifications", sourcedFrom: ["app/routes.tsx"], strategy: "framework-route-grep" },
|
|
438
|
+
{ url: "http://localhost:3000/settings", sourcedFrom: [], strategy: "root-fallback" },
|
|
439
|
+
];
|
|
440
|
+
const text = buildCaptureInstructions(pages);
|
|
441
|
+
expect(text).toContain("1. http://localhost:3000/notifications");
|
|
442
|
+
expect(text).toContain("2. http://localhost:3000/settings");
|
|
443
|
+
expect(text).toContain("sourcedFrom: app/routes.tsx");
|
|
444
|
+
expect(text).toContain("sourcedFrom: (none)");
|
|
445
|
+
expect(text).toContain("browser_navigate");
|
|
446
|
+
expect(text).toContain("browser_blueprint");
|
|
447
|
+
});
|
|
448
|
+
it("buildCaptureInstructions does NOT emit a login step (uiCredentials dropped)", () => {
|
|
449
|
+
const pages = [
|
|
450
|
+
{ url: "http://localhost:3000/x", sourcedFrom: [], strategy: "root-fallback" },
|
|
451
|
+
];
|
|
452
|
+
const text = buildCaptureInstructions(pages);
|
|
453
|
+
expect(text).not.toMatch(/log in first/i);
|
|
454
|
+
expect(text).not.toMatch(/ui-credentials/i);
|
|
455
|
+
});
|
|
456
|
+
});
|
|
@@ -15,11 +15,15 @@ export function registerAnalyzeTestHealthTool(server) {
|
|
|
15
15
|
idempotentHint: true,
|
|
16
16
|
openWorldHint: false,
|
|
17
17
|
},
|
|
18
|
-
description: `Generate drift assessment instructions for existing tests — second step of the unified Test Health Analysis Flow
|
|
18
|
+
description: `Generate drift assessment instructions for existing tests — second step of the unified Test Health Analysis Flow.`,
|
|
19
19
|
inputSchema: {
|
|
20
20
|
stateFile: z
|
|
21
21
|
.string()
|
|
22
|
-
.describe("Path to state file
|
|
22
|
+
.describe("Path to the analyze-changes-state.json file returned by skyramp_analyze_changes (the stateFile field in its output). Do NOT pass summaryOutputFile or testbot-result.txt here."),
|
|
23
|
+
repository: z
|
|
24
|
+
.string()
|
|
25
|
+
.optional()
|
|
26
|
+
.describe("The owner/repo whose analysis section to assess (e.g. 'letsramp/api-insight'). In a multi-repo run, ALWAYS set it — the primary's owner/repo for the primary repo, or a related repo's owner/repo for that repo's section within the run-scoped stateFile. Omit only for a single-repo run."),
|
|
23
27
|
},
|
|
24
28
|
outputSchema: {
|
|
25
29
|
prompt: z.string().describe("LLM drift analysis prompt and assessment instructions."),
|
|
@@ -27,17 +31,20 @@ export function registerAnalyzeTestHealthTool(server) {
|
|
|
27
31
|
}, async (args) => {
|
|
28
32
|
let errorResult;
|
|
29
33
|
try {
|
|
30
|
-
logger.info(`Analyzing test health from state file: ${args.stateFile}`);
|
|
31
|
-
// Load UnifiedAnalysisState
|
|
34
|
+
logger.info(`Analyzing test health from state file: ${args.stateFile}${args.repository ? ` (repository: ${args.repository})` : ""}`);
|
|
35
|
+
// Load UnifiedAnalysisState for the requested repo section (primary when
|
|
36
|
+
// repository omitted/matches the root) from the run-scoped state file.
|
|
32
37
|
const stateManager = StateManager.fromStatePath(args.stateFile);
|
|
33
|
-
const stateData = await stateManager.
|
|
34
|
-
const
|
|
35
|
-
const repositoryPath = fullState?.metadata.repositoryPath || "";
|
|
38
|
+
const stateData = await stateManager.readRepoData(args.repository);
|
|
39
|
+
const repositoryPath = (await stateManager.getRepoRepositoryPath(args.repository)) || "";
|
|
36
40
|
if (!stateData) {
|
|
37
|
-
return toolError(`State file is empty or invalid: ${args.stateFile}. Call skyramp_analyze_changes first to generate a valid state file.`);
|
|
41
|
+
return toolError(`State file is empty or invalid: ${args.stateFile}${args.repository ? ` (no section for repository ${args.repository})` : ""}. Call skyramp_analyze_changes first to generate a valid state file.`);
|
|
38
42
|
}
|
|
39
43
|
if (!repositoryPath || typeof repositoryPath !== "string") {
|
|
40
|
-
return toolError(`repositoryPath not found in state file metadata. The
|
|
44
|
+
return toolError(`repositoryPath not found in state file metadata. The stateFile parameter must be the analyze-changes-state.json path returned by skyramp_analyze_changes — not summaryOutputFile or testbot-result.txt. Re-run skyramp_analyze_changes to regenerate it.`);
|
|
45
|
+
}
|
|
46
|
+
if (!Array.isArray(stateData.existingTests)) {
|
|
47
|
+
return toolError(`stateFile does not contain test discovery data (existingTests missing). The stateFile parameter must be the analyze-changes-state.json path returned by skyramp_analyze_changes — not summaryOutputFile or testbot-result.txt.`);
|
|
41
48
|
}
|
|
42
49
|
// Skyramp tests: full drift analysis + all actions permitted.
|
|
43
50
|
// Relevant external tests (user-written, relevant to this PR's endpoints): drift analysis
|
|
@@ -1,4 +1,3 @@
|
|
|
1
1
|
export { registerAnalyzeChangesTool } from "./analyzeChangesTool.js";
|
|
2
|
-
export { registerUiAnalyzeChangesTool } from "./uiAnalyzeChangesTool.js";
|
|
3
2
|
export { registerAnalyzeTestHealthTool } from "./analyzeTestHealthTool.js";
|
|
4
3
|
export { registerActionsTool } from "./actionsTool.js";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the user-supplied `playwrightSaveStoragePath` against `outputDir`:
|
|
3
|
+
* - A bare filename (no separators, not absolute) is joined with `outputDir`.
|
|
4
|
+
* - Anything else (absolute path, or a relative path containing separators)
|
|
5
|
+
* is returned unchanged.
|
|
6
|
+
*/
|
|
7
|
+
export declare function resolveSaveStoragePath(input: string, outputDir: string | undefined): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SKYR-3321: pick which Playwright session-storage paths to pass to the
|
|
3
|
+
* recorder based on a single, explicit `sessionMode` signal.
|
|
4
|
+
*
|
|
5
|
+
* The original start-trace tool had an asymmetric default: it always
|
|
6
|
+
* resolved a save path (so every recording overwrote the session file on
|
|
7
|
+
* stop) but never auto-loaded. That was the root cause of the "load →
|
|
8
|
+
* 31-min idle → stop → session destroyed" regression that fills SKYR-3321.
|
|
9
|
+
* The mode vocabulary replaced that with the three real intents — capture a
|
|
10
|
+
* new session, reuse an existing one, or ignore session storage entirely.
|
|
11
|
+
*
|
|
12
|
+
* SKYR-3793: `auto` must never store credentials the user did not ask for.
|
|
13
|
+
* It loads an existing session if one is present in `outputDir` (reuse), but
|
|
14
|
+
* when none exists it does NOTHING — it does not auto-capture. Storing is
|
|
15
|
+
* opt-in only: `sessionMode=capture` or an explicit `saveOverride`.
|
|
16
|
+
*
|
|
17
|
+
* Both `loadOverride` and `saveOverride` are plain optional strings: when
|
|
18
|
+
* the LLM omits them they arrive as `undefined`, which means "use the
|
|
19
|
+
* mode's default". No sentinel values; explicit overrides win when given.
|
|
20
|
+
*/
|
|
21
|
+
export type SessionMode = "auto" | "capture" | "reuse" | "ignore";
|
|
22
|
+
export interface ResolvedSessionPaths {
|
|
23
|
+
loadPath?: string;
|
|
24
|
+
savePath?: string;
|
|
25
|
+
}
|
|
26
|
+
export interface ResolveSessionPathsArgs {
|
|
27
|
+
mode: SessionMode;
|
|
28
|
+
/** Raw value of `playwrightStoragePath` (LLM may omit). */
|
|
29
|
+
loadOverride: string | undefined;
|
|
30
|
+
/** Raw value of `playwrightSaveStoragePath` (LLM may omit). */
|
|
31
|
+
saveOverride: string | undefined;
|
|
32
|
+
/** Workspace directory used to resolve relative paths and locate the default session file. */
|
|
33
|
+
outputDir: string;
|
|
34
|
+
/**
|
|
35
|
+
* Whether `<outputDir>/<SESSION_STORAGE_FILENAME>` exists on disk. Injected
|
|
36
|
+
* rather than read in-function so the resolver stays pure and testable.
|
|
37
|
+
*/
|
|
38
|
+
sessionExists: boolean;
|
|
39
|
+
}
|
|
40
|
+
export declare function resolveSessionPaths(args: ResolveSessionPathsArgs): ResolvedSessionPaths;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
export declare function issueScanToken(): string;
|
|
3
|
+
export declare function validateAndConsumeScanToken(token: string): boolean;
|
|
4
|
+
export declare function registerInitScanWorkspaceTool(server: McpServer): void;
|
|
@@ -7,6 +7,7 @@ import { logger } from "../../utils/logger.js";
|
|
|
7
7
|
import { AnalyticsService } from "../../services/AnalyticsService.js";
|
|
8
8
|
import { SKYRAMP_IMAGE_VERSION } from "../../utils/versions.js";
|
|
9
9
|
import { validateAndConsumeScanToken } from "./initScanWorkspaceTool.js";
|
|
10
|
+
import { upsertServicesByRepo } from "./serviceUpsert.js";
|
|
10
11
|
const require = createRequire(import.meta.url);
|
|
11
12
|
const MCP_VERSION = require("../../../package.json").version || "";
|
|
12
13
|
function getExecutorVersion() {
|
|
@@ -16,8 +17,8 @@ const TOOL_NAME = "skyramp_init_workspace";
|
|
|
16
17
|
// writeWorkspaceConfig() writes the YAML directly (rather than via
|
|
17
18
|
// WorkspaceConfigManager.addService) so all services are written in a single
|
|
18
19
|
// pass without N+1 reads. The workspace serviceSchema accepts the full authType
|
|
19
|
-
// set (incl. cookie/session/token)
|
|
20
|
-
// directly — no local schema extension needed.
|
|
20
|
+
// set (incl. cookie/session/token) and the multi-repo `repository` field, so input
|
|
21
|
+
// services are validated against it directly — no local schema extension needed.
|
|
21
22
|
const initializeWorkspaceSchema = z.object({
|
|
22
23
|
workspacePath: z
|
|
23
24
|
.string()
|
|
@@ -32,6 +33,10 @@ const initializeWorkspaceSchema = z.object({
|
|
|
32
33
|
.boolean()
|
|
33
34
|
.default(false)
|
|
34
35
|
.describe("Set to true to overwrite an existing workspace file. Use when the user explicitly requests it, or when recovering from a workspace validation failure (schema mismatch, unknown fields). Default is false."),
|
|
36
|
+
merge: z
|
|
37
|
+
.boolean()
|
|
38
|
+
.default(false)
|
|
39
|
+
.describe("Set to true to MERGE the provided services into an existing workspace.yml instead of overwriting it. The primary use is registering a related repository's services into one unified workspace file (each service carrying its `repository`), but it applies whenever you want to add services without discarding existing ones. Services are upserted by the composite (repository, serviceName), so same-named services in different repos coexist. Ignored on first init (no existing file). Default is false."),
|
|
35
40
|
});
|
|
36
41
|
/**
|
|
37
42
|
* Write a YAML workspace config to disk, bypassing the library's restricted
|
|
@@ -72,13 +77,17 @@ export function registerInitializeWorkspaceTool(server) {
|
|
|
72
77
|
try {
|
|
73
78
|
const workspacePath = params.workspacePath;
|
|
74
79
|
const manager = new WorkspaceConfigManager(workspacePath);
|
|
75
|
-
|
|
76
|
-
if
|
|
80
|
+
const alreadyExists = await manager.exists();
|
|
81
|
+
// Check if already initialized. In merge mode we proceed (read the
|
|
82
|
+
// existing file and upsert into it) instead of refusing — this is how a
|
|
83
|
+
// multi-repo run registers each additional repo's services into one
|
|
84
|
+
// unified workspace.yml without a per-repo file.
|
|
85
|
+
if (alreadyExists && !params.force && !params.merge) {
|
|
77
86
|
return {
|
|
78
87
|
content: [
|
|
79
88
|
{
|
|
80
89
|
type: "text",
|
|
81
|
-
text: `Workspace already initialized at ${manager.getConfigPath()}. Set force: true to overwrite.`,
|
|
90
|
+
text: `Workspace already initialized at ${manager.getConfigPath()}. Set force: true to overwrite, or merge: true to add services to it.`,
|
|
82
91
|
},
|
|
83
92
|
],
|
|
84
93
|
isError: false,
|
|
@@ -107,24 +116,30 @@ export function registerInitializeWorkspaceTool(server) {
|
|
|
107
116
|
isError: true,
|
|
108
117
|
};
|
|
109
118
|
}
|
|
110
|
-
// Initialize
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
119
|
+
// Initialize (fresh) or read (merge into existing). Merge mode preserves
|
|
120
|
+
// the existing primary repo identity + previously-registered services;
|
|
121
|
+
// initialize() would reset both, dropping other repos' services.
|
|
122
|
+
let config;
|
|
123
|
+
if (params.merge && alreadyExists) {
|
|
124
|
+
config = await manager.read();
|
|
125
|
+
config = await manager.updateMetadata({
|
|
126
|
+
mcpVersion: MCP_VERSION,
|
|
127
|
+
executorVersion: getExecutorVersion(),
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
config = await manager.initialize();
|
|
132
|
+
config = await manager.updateMetadata({
|
|
133
|
+
mcpVersion: MCP_VERSION,
|
|
134
|
+
executorVersion: getExecutorVersion(),
|
|
135
|
+
});
|
|
127
136
|
}
|
|
137
|
+
// Batch all service upserts: update in memory, write once (direct YAML
|
|
138
|
+
// write avoids N+1 reads and a mid-loop validation error). Upsert is keyed
|
|
139
|
+
// on the composite (repo, serviceName) — see upsertServicesByRepo — so a
|
|
140
|
+
// same-named service from an additional repo is a distinct entry, not an
|
|
141
|
+
// overwrite of the primary's.
|
|
142
|
+
const services = upsertServicesByRepo(config.services ?? [], params.services);
|
|
128
143
|
config.services = services;
|
|
129
144
|
if (config.metadata) {
|
|
130
145
|
config.metadata.updatedAt = new Date().toISOString();
|
|
@@ -142,6 +157,8 @@ export function registerInitializeWorkspaceTool(server) {
|
|
|
142
157
|
lines.push(`\nServices (${config.services.length}):`);
|
|
143
158
|
for (const svc of config.services) {
|
|
144
159
|
const parts = [svc.serviceName];
|
|
160
|
+
if (svc.repository)
|
|
161
|
+
parts.push(svc.repository);
|
|
145
162
|
if (svc.language)
|
|
146
163
|
parts.push(svc.language);
|
|
147
164
|
if (svc.framework)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|