@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
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { TestAnalysisResult } from "../types/TestAnalysis.js";
|
|
2
|
+
import { RepositoryAnalysis, AnalysisScope } from "../types/RepositoryAnalysis.js";
|
|
3
|
+
import { PRTestContext } from "./pr-comment-parser.js";
|
|
4
|
+
import { ParsedDiffEndpoint } from "./routeParsers.js";
|
|
5
|
+
import type { CandidateUiPage } from "./uiPageEnumerator.js";
|
|
6
|
+
export type { CandidateUiPage } from "./uiPageEnumerator.js";
|
|
7
|
+
export declare function setTestsRepoDir(dir: string | undefined): void;
|
|
8
|
+
export declare function getTestsRepoDir(): string | undefined;
|
|
9
|
+
export declare function setActiveRunStatePath(stateFilePath: string): void;
|
|
10
|
+
export declare function getActiveRunStatePath(): string | undefined;
|
|
11
|
+
/** Reset the run anchor — for tests, and as a safety hook between runs. */
|
|
12
|
+
export declare function clearActiveRunStatePath(): void;
|
|
13
|
+
export declare function registerSession(sessionId: string, stateFilePath: string): void;
|
|
14
|
+
export declare function getSessionFilePath(sessionId: string): string | undefined;
|
|
15
|
+
export declare function getRegisteredSessions(): ReadonlyMap<string, string>;
|
|
16
|
+
export declare function storeSessionData(sessionId: string, data: any): void;
|
|
17
|
+
export declare function getSessionData(sessionId: string): any | undefined;
|
|
18
|
+
export declare function hasSessionData(sessionId: string): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Normalize a loaded RecommendationState: if the LLM saved a raw
|
|
21
|
+
* RepositoryAnalysis at the root (without the wrapper), detect it
|
|
22
|
+
* and wrap it properly.
|
|
23
|
+
*/
|
|
24
|
+
export declare function normalizeRecommendationState(data: RecommendationState): RecommendationState;
|
|
25
|
+
/**
|
|
26
|
+
* State types supported by the manager
|
|
27
|
+
*/
|
|
28
|
+
export type StateType = "analysis" | "recommendation";
|
|
29
|
+
/**
|
|
30
|
+
* State data for test maintenance workflow (discovery, drift, execution, health)
|
|
31
|
+
*/
|
|
32
|
+
export interface TestAnalysisState {
|
|
33
|
+
tests: TestAnalysisResult[];
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* State data for test recommendation workflow (analyze repo, recommend tests)
|
|
37
|
+
*/
|
|
38
|
+
export interface RecommendationState {
|
|
39
|
+
repositoryPath: string;
|
|
40
|
+
analysisScope?: AnalysisScope;
|
|
41
|
+
analysis: RepositoryAnalysis;
|
|
42
|
+
prContext?: PRTestContext;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Map of test type → numeric score (e.g., priority, relevance, confidence).
|
|
46
|
+
*/
|
|
47
|
+
type TestTypeScores = Record<string, number>;
|
|
48
|
+
/**
|
|
49
|
+
* Map of test type → grouping/label information used by downstream tools.
|
|
50
|
+
*/
|
|
51
|
+
type TestTypeMapping = Record<string, string | string[]>;
|
|
52
|
+
/**
|
|
53
|
+
* UI-specific analysis context populated when the diff contains frontend files.
|
|
54
|
+
*
|
|
55
|
+
* Computed deterministically by `skyramp_analyze_changes` (via `isFrontendFile`
|
|
56
|
+
* in scopeAssessment.ts). Persisted so downstream consumers —
|
|
57
|
+
* `skyramp_analyze_test_health`'s UI drift detection, recommendation prompt
|
|
58
|
+
* rendering, etc. — don't have to re-derive the classification.
|
|
59
|
+
*
|
|
60
|
+
* Absent on backend-only PRs (no frontend files in the diff).
|
|
61
|
+
*/
|
|
62
|
+
export interface UiAnalysisContext {
|
|
63
|
+
/**
|
|
64
|
+
* Subset of `repositoryAnalysis.diff.changedFiles` that classified as
|
|
65
|
+
* frontend by `isFrontendFile()`. Empty when no frontend files changed —
|
|
66
|
+
* callers can derive `hasFrontendChanges` from `changedFrontendFiles.length > 0`.
|
|
67
|
+
*/
|
|
68
|
+
changedFrontendFiles: string[];
|
|
69
|
+
/**
|
|
70
|
+
* Candidate pages the testbot agent should capture blueprints for, enumerated
|
|
71
|
+
* by the strategy ladder in `uiPageEnumerator.ts` (framework route grep,
|
|
72
|
+
* source-grounded routes, root fallback). Each entry carries the candidate
|
|
73
|
+
* URL plus the strategy + source files that surfaced it. May be empty when
|
|
74
|
+
* the workspace has no resolvable frontend baseUrl — caller should fall back
|
|
75
|
+
* to the agent's prose-driven enumeration.
|
|
76
|
+
*/
|
|
77
|
+
candidateUiPages: CandidateUiPage[];
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Unified state data combining test discovery + endpoint scanning
|
|
81
|
+
* Used by the unified test-management workflow
|
|
82
|
+
*/
|
|
83
|
+
export interface UnifiedAnalysisState {
|
|
84
|
+
existingTests: TestAnalysisResult[];
|
|
85
|
+
repositoryAnalysis?: any;
|
|
86
|
+
testTypeScores?: TestTypeScores;
|
|
87
|
+
testTypeMapping?: TestTypeMapping;
|
|
88
|
+
analysisScope: AnalysisScope;
|
|
89
|
+
newEndpoints: ParsedDiffEndpoint[];
|
|
90
|
+
/**
|
|
91
|
+
* UI-specific state populated when the diff contains frontend files.
|
|
92
|
+
* Absent on backend-only PRs.
|
|
93
|
+
*/
|
|
94
|
+
uiContext?: UiAnalysisContext;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* State file metadata
|
|
98
|
+
*/
|
|
99
|
+
interface StateMetadata {
|
|
100
|
+
sessionId: string;
|
|
101
|
+
stateType: StateType;
|
|
102
|
+
repositoryPath?: string;
|
|
103
|
+
/** owner/repo of the PRIMARY repo, recorded on the root so related-repo writes
|
|
104
|
+
* can be distinguished from the primary. Absent only for legacy single-repo
|
|
105
|
+
* state files written before explicit tagging. */
|
|
106
|
+
repository?: string;
|
|
107
|
+
createdAt: string;
|
|
108
|
+
updatedAt: string;
|
|
109
|
+
step?: string;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* One related repo's analysis section in a run-scoped (multi-repo) state file.
|
|
113
|
+
* The primary repo's analysis stays at the root for backward compatibility; each
|
|
114
|
+
* related repo's analysis is stored here keyed by its `owner/repo`.
|
|
115
|
+
*/
|
|
116
|
+
interface RepoStateSection<T> {
|
|
117
|
+
/** Analysis payload — same shape as the root (e.g. UnifiedAnalysisState). */
|
|
118
|
+
data: T;
|
|
119
|
+
/** Absolute checkout path of this repo (used to re-absolutize test paths). */
|
|
120
|
+
repositoryPath?: string;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* State file format - data fields at root level with metadata.
|
|
124
|
+
* Example: { tests: [...], metadata: {...} }
|
|
125
|
+
* Example: { repositoryPath: "...", analysis: {...}, mapping: {...}, metadata: {...} }
|
|
126
|
+
*
|
|
127
|
+
* Multi-repo (run-scoped) files additionally carry `relatedRepos`: a map of
|
|
128
|
+
* `owner/repo` → that repo's analysis section. State is per-RUN, not per-repo —
|
|
129
|
+
* one file holds the primary (at root) plus every related repo's section, so
|
|
130
|
+
* there is exactly one stateFile per Testbot run. A file with no `relatedRepos`
|
|
131
|
+
* key is a single-repo file and behaves exactly as before.
|
|
132
|
+
*/
|
|
133
|
+
type StateFile<T> = T & {
|
|
134
|
+
metadata: StateMetadata;
|
|
135
|
+
relatedRepos?: Record<string, RepoStateSection<T>>;
|
|
136
|
+
};
|
|
137
|
+
/**
|
|
138
|
+
* Generic State Manager for persisting workflow data
|
|
139
|
+
* Reduces token usage by storing intermediate results in filesystem
|
|
140
|
+
*
|
|
141
|
+
* Supports two state types:
|
|
142
|
+
* - analysis: Test maintenance workflow (discovery, drift, execution, health)
|
|
143
|
+
* - recommendation: Test recommendation workflow (analyze repo, map tests, recommendations)
|
|
144
|
+
*/
|
|
145
|
+
export declare class StateManager<T = any> {
|
|
146
|
+
private stateFile;
|
|
147
|
+
private sessionId;
|
|
148
|
+
private stateType;
|
|
149
|
+
/**
|
|
150
|
+
* Create a new state manager
|
|
151
|
+
* @param stateType Type of state (analysis, recommendation)
|
|
152
|
+
* @param sessionId Unique session identifier (defaults to UUID)
|
|
153
|
+
* @param stateDir Directory to store state files (defaults to /tmp)
|
|
154
|
+
*/
|
|
155
|
+
constructor(stateType?: StateType, sessionId?: string, stateDir?: string, stateFilePath?: string);
|
|
156
|
+
/**
|
|
157
|
+
* Create state manager from a sessionId (resolves the state file path internally)
|
|
158
|
+
*/
|
|
159
|
+
static fromSessionId<T = any>(sessionId: string, stateType?: StateType, stateDir?: string): StateManager<T>;
|
|
160
|
+
/**
|
|
161
|
+
* Create state manager from existing state file path
|
|
162
|
+
*/
|
|
163
|
+
static fromStatePath<T = any>(stateFilePath: string): StateManager<T>;
|
|
164
|
+
/**
|
|
165
|
+
* Read data from state file (excludes metadata)
|
|
166
|
+
*/
|
|
167
|
+
readData(): Promise<T | null>;
|
|
168
|
+
/**
|
|
169
|
+
* Read full state including metadata
|
|
170
|
+
*/
|
|
171
|
+
readFullState(): Promise<StateFile<T> | null>;
|
|
172
|
+
/**
|
|
173
|
+
* Write data to state file
|
|
174
|
+
* Data fields are spread at root level alongside metadata
|
|
175
|
+
*/
|
|
176
|
+
writeData(data: T, options?: {
|
|
177
|
+
repositoryPath?: string;
|
|
178
|
+
step?: string;
|
|
179
|
+
/** owner/repo recorded on the root metadata — identifies the PRIMARY repo
|
|
180
|
+
* so later section routing (isPrimaryRepo) can compare against it.
|
|
181
|
+
* Preserved when omitted. */
|
|
182
|
+
repository?: string;
|
|
183
|
+
}): Promise<void>;
|
|
184
|
+
/** Is `repo` the primary section? Primary when `repo` is falsy (single-repo /
|
|
185
|
+
* legacy callers), when no state file exists yet (the FIRST write of a run is
|
|
186
|
+
* the primary — the testbot prompt mandates the primary is analyzed first), or
|
|
187
|
+
* when `repo` matches the `repo` recorded on the root metadata. A non-empty
|
|
188
|
+
* `repo` that doesn't match an already-recorded root is a RELATED repo. */
|
|
189
|
+
private isPrimaryRepo;
|
|
190
|
+
/**
|
|
191
|
+
* Write one repo's analysis into the run-scoped state file. The primary repo
|
|
192
|
+
* (repo absent, or matching the root's recorded repo, or the first write of the
|
|
193
|
+
* run) is written to the root via writeData(); a related repo is upserted into
|
|
194
|
+
* `relatedRepos[repo]`, preserving the primary + other sections. Used by
|
|
195
|
+
* skyramp_analyze_changes — every repo in the run writes the SAME stateFile path.
|
|
196
|
+
*/
|
|
197
|
+
writeRepoData(data: T, options: {
|
|
198
|
+
repo?: string;
|
|
199
|
+
repositoryPath?: string;
|
|
200
|
+
step?: string;
|
|
201
|
+
}): Promise<void>;
|
|
202
|
+
/**
|
|
203
|
+
* Read one repo's analysis payload from the run-scoped state file. `repo`
|
|
204
|
+
* undefined → the primary (root) section (identical to readData()).
|
|
205
|
+
*/
|
|
206
|
+
readRepoData(repo?: string): Promise<T | null>;
|
|
207
|
+
/**
|
|
208
|
+
* Resolve the checkout path for a repo's section — used to re-absolutize test
|
|
209
|
+
* paths. `repo` undefined → the primary's metadata.repositoryPath.
|
|
210
|
+
*/
|
|
211
|
+
getRepoRepositoryPath(repo?: string): Promise<string | undefined>;
|
|
212
|
+
/** List the related repo keys present in the run-scoped file (empty if single-repo). */
|
|
213
|
+
listRelatedRepos(): Promise<string[]>;
|
|
214
|
+
getStatePath(): string;
|
|
215
|
+
getSessionId(): string;
|
|
216
|
+
getStateType(): StateType;
|
|
217
|
+
exists(): boolean;
|
|
218
|
+
delete(): Promise<void>;
|
|
219
|
+
getSize(): Promise<number>;
|
|
220
|
+
getSizeFormatted(): Promise<string>;
|
|
221
|
+
/**
|
|
222
|
+
* Cleanup old state files
|
|
223
|
+
* @param maxAgeHours Maximum age in hours
|
|
224
|
+
* @param stateDir Directory to clean (defaults to /tmp)
|
|
225
|
+
* @param stateTypes Which state types to clean (defaults to all)
|
|
226
|
+
* @returns Number of files deleted
|
|
227
|
+
*/
|
|
228
|
+
static cleanupOldFiles(maxAgeHours?: number, stateDir?: string, stateTypes?: StateType[]): Promise<number>;
|
|
229
|
+
/**
|
|
230
|
+
* List all state files in directory
|
|
231
|
+
* @param stateDir Directory to search (defaults to /tmp)
|
|
232
|
+
* @param stateTypes Which state types to list (defaults to all)
|
|
233
|
+
*/
|
|
234
|
+
static listStateFiles(stateDir?: string, stateTypes?: StateType[]): Promise<Array<{
|
|
235
|
+
sessionId: string;
|
|
236
|
+
stateType: StateType;
|
|
237
|
+
path: string;
|
|
238
|
+
size: number;
|
|
239
|
+
createdAt: Date;
|
|
240
|
+
modifiedAt: Date;
|
|
241
|
+
}>>;
|
|
242
|
+
}
|
|
@@ -22,6 +22,29 @@ export function setTestsRepoDir(dir) {
|
|
|
22
22
|
export function getTestsRepoDir() {
|
|
23
23
|
return _testsRepoDir;
|
|
24
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Multi-repo run anchor: the path of the run-scoped analysis state file for the
|
|
27
|
+
* current Testbot run. One Testbot run = one MCP server process, so a single
|
|
28
|
+
* module-level value is naturally run-scoped (same pattern as _testsRepoDir and
|
|
29
|
+
* processSessionRegistry above).
|
|
30
|
+
*
|
|
31
|
+
* skyramp_analyze_changes sets this on the FIRST call of a multi-repo run (the one
|
|
32
|
+
* carrying a `repository`) and REUSES it on subsequent related-repo calls, so every
|
|
33
|
+
* repo's analysis lands in ONE state file with per-repo sections — without the agent
|
|
34
|
+
* ever passing a file path (which previously caused filename hallucinations).
|
|
35
|
+
* Single-repo / standalone calls (no `repository`) never set or read this.
|
|
36
|
+
*/
|
|
37
|
+
let _activeRunStatePath;
|
|
38
|
+
export function setActiveRunStatePath(stateFilePath) {
|
|
39
|
+
_activeRunStatePath = stateFilePath;
|
|
40
|
+
}
|
|
41
|
+
export function getActiveRunStatePath() {
|
|
42
|
+
return _activeRunStatePath;
|
|
43
|
+
}
|
|
44
|
+
/** Reset the run anchor — for tests, and as a safety hook between runs. */
|
|
45
|
+
export function clearActiveRunStatePath() {
|
|
46
|
+
_activeRunStatePath = undefined;
|
|
47
|
+
}
|
|
25
48
|
/**
|
|
26
49
|
* In-memory session store: sessionId → { data, storedAt }.
|
|
27
50
|
* Eliminates the need for the LLM to read/write state files on disk.
|
|
@@ -119,6 +142,15 @@ export class StateManager {
|
|
|
119
142
|
if (stateFilePath) {
|
|
120
143
|
this.stateFile = stateFilePath;
|
|
121
144
|
}
|
|
145
|
+
else if (stateType === "analysis" && process.env.RUNNER_TEMP) {
|
|
146
|
+
// In CI (testbot action), RUNNER_TEMP is a job-isolated dir that both the
|
|
147
|
+
// action and this tool can independently derive — no LLM input needed.
|
|
148
|
+
// TODO: interim step — remove this branch when stateFile and summaryOutputFile are merged
|
|
149
|
+
// (see getTestbotPrompt TODO). RUNNER_TEMP is GitHub Actions-specific; other CI systems
|
|
150
|
+
// (Jenkins, GitLab, Buildkite) must set it explicitly. Once merged, path flows through
|
|
151
|
+
// summaryOutputFile which is already testbot-controlled and CI-agnostic.
|
|
152
|
+
this.stateFile = path.join(process.env.RUNNER_TEMP, "skyramp", "analyze-changes-state.json");
|
|
153
|
+
}
|
|
122
154
|
else {
|
|
123
155
|
const baseDir = stateDir || os.tmpdir();
|
|
124
156
|
const prefix = STATE_FILE_PREFIXES[stateType];
|
|
@@ -161,7 +193,11 @@ export class StateManager {
|
|
|
161
193
|
}
|
|
162
194
|
try {
|
|
163
195
|
const content = await fs.promises.readFile(this.stateFile, "utf-8");
|
|
164
|
-
|
|
196
|
+
// Strip both metadata and the multi-repo relatedRepos section so callers
|
|
197
|
+
// get only the PRIMARY repo's analysis payload (unchanged single-repo shape).
|
|
198
|
+
const { metadata, relatedRepos, ...data } = JSON.parse(content);
|
|
199
|
+
void metadata;
|
|
200
|
+
void relatedRepos;
|
|
165
201
|
logger.debug(`Read data from state file: ${this.stateFile}`);
|
|
166
202
|
return data;
|
|
167
203
|
}
|
|
@@ -192,11 +228,14 @@ export class StateManager {
|
|
|
192
228
|
*/
|
|
193
229
|
async writeData(data, options) {
|
|
194
230
|
try {
|
|
195
|
-
// Read existing metadata if file exists
|
|
231
|
+
// Read existing metadata + any related-repo sections if file exists, so
|
|
232
|
+
// rewriting the primary (root) payload preserves the multi-repo sections.
|
|
196
233
|
let existingMetadata;
|
|
234
|
+
let existingRelated;
|
|
197
235
|
if (this.exists()) {
|
|
198
236
|
const existing = await this.readFullState();
|
|
199
237
|
existingMetadata = existing?.metadata;
|
|
238
|
+
existingRelated = existing?.relatedRepos;
|
|
200
239
|
}
|
|
201
240
|
const state = {
|
|
202
241
|
...data,
|
|
@@ -204,10 +243,12 @@ export class StateManager {
|
|
|
204
243
|
sessionId: this.sessionId,
|
|
205
244
|
stateType: this.stateType,
|
|
206
245
|
repositoryPath: options?.repositoryPath || existingMetadata?.repositoryPath,
|
|
246
|
+
repository: options?.repository ?? existingMetadata?.repository,
|
|
207
247
|
createdAt: existingMetadata?.createdAt || new Date().toISOString(),
|
|
208
248
|
updatedAt: new Date().toISOString(),
|
|
209
249
|
step: options?.step,
|
|
210
250
|
},
|
|
251
|
+
...(existingRelated ? { relatedRepos: existingRelated } : {}),
|
|
211
252
|
};
|
|
212
253
|
await fs.promises.mkdir(path.dirname(this.stateFile), { recursive: true });
|
|
213
254
|
await fs.promises.writeFile(this.stateFile, JSON.stringify(state, null, 2), "utf-8");
|
|
@@ -218,6 +259,106 @@ export class StateManager {
|
|
|
218
259
|
throw new Error(`Failed to write state file: ${error.message}`);
|
|
219
260
|
}
|
|
220
261
|
}
|
|
262
|
+
// -----------------------------------------------------------------------
|
|
263
|
+
// Multi-repo (run-scoped) section access
|
|
264
|
+
//
|
|
265
|
+
// State is per-RUN: one file holds the primary repo at the root plus each
|
|
266
|
+
// related repo under `relatedRepos[<owner/repo>]`. `repo` undefined (or
|
|
267
|
+
// matching the root's metadata.repo) targets the PRIMARY section, so all
|
|
268
|
+
// single-repo behavior is unchanged.
|
|
269
|
+
// -----------------------------------------------------------------------
|
|
270
|
+
/** Is `repo` the primary section? Primary when `repo` is falsy (single-repo /
|
|
271
|
+
* legacy callers), when no state file exists yet (the FIRST write of a run is
|
|
272
|
+
* the primary — the testbot prompt mandates the primary is analyzed first), or
|
|
273
|
+
* when `repo` matches the `repo` recorded on the root metadata. A non-empty
|
|
274
|
+
* `repo` that doesn't match an already-recorded root is a RELATED repo. */
|
|
275
|
+
async isPrimaryRepo(repo) {
|
|
276
|
+
if (!repo)
|
|
277
|
+
return true;
|
|
278
|
+
// First write of the run is the primary (the prompt mandates the primary is
|
|
279
|
+
// analyzed first), even when it carries an explicit owner/repo.
|
|
280
|
+
if (!this.exists())
|
|
281
|
+
return true;
|
|
282
|
+
const full = await this.readFullState();
|
|
283
|
+
// File already exists: primary only if `repo` matches the repo recorded on
|
|
284
|
+
// the root. An untagged root means the primary was written without a repo
|
|
285
|
+
// (legacy/single-repo), so any non-empty `repo` here is a RELATED repo.
|
|
286
|
+
return full?.metadata.repository === repo;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Write one repo's analysis into the run-scoped state file. The primary repo
|
|
290
|
+
* (repo absent, or matching the root's recorded repo, or the first write of the
|
|
291
|
+
* run) is written to the root via writeData(); a related repo is upserted into
|
|
292
|
+
* `relatedRepos[repo]`, preserving the primary + other sections. Used by
|
|
293
|
+
* skyramp_analyze_changes — every repo in the run writes the SAME stateFile path.
|
|
294
|
+
*/
|
|
295
|
+
async writeRepoData(data, options) {
|
|
296
|
+
if (await this.isPrimaryRepo(options.repo)) {
|
|
297
|
+
await this.writeData(data, {
|
|
298
|
+
repositoryPath: options.repositoryPath,
|
|
299
|
+
step: options.step,
|
|
300
|
+
// Record the primary's owner/repo on the root so later related-repo
|
|
301
|
+
// writes can distinguish it from a section by matching this value.
|
|
302
|
+
repository: options.repo,
|
|
303
|
+
});
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
// Related repo: read the current file (must already exist — primary is
|
|
307
|
+
// analyzed first), upsert this repo's section, write back.
|
|
308
|
+
const existing = (await this.readFullState());
|
|
309
|
+
const base = existing ??
|
|
310
|
+
{
|
|
311
|
+
metadata: {
|
|
312
|
+
sessionId: this.sessionId,
|
|
313
|
+
stateType: this.stateType,
|
|
314
|
+
createdAt: new Date().toISOString(),
|
|
315
|
+
updatedAt: new Date().toISOString(),
|
|
316
|
+
},
|
|
317
|
+
};
|
|
318
|
+
const relatedRepos = { ...(base.relatedRepos ?? {}) };
|
|
319
|
+
// Preserve a previously-stored repositoryPath when this write omits it.
|
|
320
|
+
// Callers that update a section without re-supplying the checkout path (e.g.
|
|
321
|
+
// skyramp_execute_test writing execution results) must not clobber the path
|
|
322
|
+
// that skyramp_analyze_changes recorded — getRepoRepositoryPath() relies on
|
|
323
|
+
// it to re-absolutize that repo's test paths.
|
|
324
|
+
const priorRepositoryPath = relatedRepos[options.repo]?.repositoryPath;
|
|
325
|
+
relatedRepos[options.repo] = {
|
|
326
|
+
data,
|
|
327
|
+
repositoryPath: options.repositoryPath ?? priorRepositoryPath,
|
|
328
|
+
};
|
|
329
|
+
base.relatedRepos = relatedRepos;
|
|
330
|
+
base.metadata = { ...base.metadata, updatedAt: new Date().toISOString() };
|
|
331
|
+
await fs.promises.mkdir(path.dirname(this.stateFile), { recursive: true });
|
|
332
|
+
await fs.promises.writeFile(this.stateFile, JSON.stringify(base, null, 2), "utf-8");
|
|
333
|
+
logger.debug(`Wrote ${options.repo} section to state file: ${this.stateFile}`);
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Read one repo's analysis payload from the run-scoped state file. `repo`
|
|
337
|
+
* undefined → the primary (root) section (identical to readData()).
|
|
338
|
+
*/
|
|
339
|
+
async readRepoData(repo) {
|
|
340
|
+
if (await this.isPrimaryRepo(repo)) {
|
|
341
|
+
return this.readData();
|
|
342
|
+
}
|
|
343
|
+
const full = await this.readFullState();
|
|
344
|
+
return full?.relatedRepos?.[repo]?.data ?? null;
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Resolve the checkout path for a repo's section — used to re-absolutize test
|
|
348
|
+
* paths. `repo` undefined → the primary's metadata.repositoryPath.
|
|
349
|
+
*/
|
|
350
|
+
async getRepoRepositoryPath(repo) {
|
|
351
|
+
const full = await this.readFullState();
|
|
352
|
+
if (await this.isPrimaryRepo(repo)) {
|
|
353
|
+
return full?.metadata.repositoryPath;
|
|
354
|
+
}
|
|
355
|
+
return full?.relatedRepos?.[repo]?.repositoryPath;
|
|
356
|
+
}
|
|
357
|
+
/** List the related repo keys present in the run-scoped file (empty if single-repo). */
|
|
358
|
+
async listRelatedRepos() {
|
|
359
|
+
const full = await this.readFullState();
|
|
360
|
+
return Object.keys(full?.relatedRepos ?? {});
|
|
361
|
+
}
|
|
221
362
|
getStatePath() {
|
|
222
363
|
return this.stateFile;
|
|
223
364
|
}
|
|
@@ -260,7 +401,7 @@ export class StateManager {
|
|
|
260
401
|
* @returns Number of files deleted
|
|
261
402
|
*/
|
|
262
403
|
static async cleanupOldFiles(maxAgeHours = 24, stateDir, stateTypes) {
|
|
263
|
-
const baseDir = stateDir || os.tmpdir();
|
|
404
|
+
const baseDir = stateDir || (process.env.RUNNER_TEMP ? path.join(process.env.RUNNER_TEMP, "skyramp") : os.tmpdir());
|
|
264
405
|
const files = await fs.promises.readdir(baseDir).catch(() => []);
|
|
265
406
|
const statePrefixes = stateTypes
|
|
266
407
|
? stateTypes.map((t) => STATE_FILE_PREFIXES[t])
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -34,3 +34,101 @@ describe("StateManager.writeData", () => {
|
|
|
34
34
|
await fs.promises.rm(nestedDir, { recursive: true, force: true });
|
|
35
35
|
});
|
|
36
36
|
});
|
|
37
|
+
describe("StateManager — run-scoped per-repo sections (SKYR-3796)", () => {
|
|
38
|
+
let stateFile;
|
|
39
|
+
const mkState = (tag) => ({
|
|
40
|
+
existingTests: [{ testFile: `${tag}.spec.ts` }],
|
|
41
|
+
analysisScope: AnalysisScope.CurrentBranchDiff,
|
|
42
|
+
newEndpoints: [],
|
|
43
|
+
});
|
|
44
|
+
beforeEach(() => {
|
|
45
|
+
stateFile = path.join(os.tmpdir(), `skyramp-analysis-sections-${Date.now()}.json`);
|
|
46
|
+
});
|
|
47
|
+
afterEach(async () => {
|
|
48
|
+
await fs.promises.rm(stateFile, { force: true });
|
|
49
|
+
});
|
|
50
|
+
it("keeps the primary at root and additional repos in relatedRepos, all in ONE file", async () => {
|
|
51
|
+
const mgr = StateManager.fromStatePath(stateFile);
|
|
52
|
+
// Primary (repo omitted) → root; additional repo → section.
|
|
53
|
+
await mgr.writeRepoData(mkState("primary"), { repositoryPath: "/work/primary" });
|
|
54
|
+
await mgr.writeRepoData(mkState("api"), {
|
|
55
|
+
repo: "letsramp/api-insight",
|
|
56
|
+
repositoryPath: "/work/api-insight",
|
|
57
|
+
});
|
|
58
|
+
// Exactly one file on disk.
|
|
59
|
+
expect(fs.existsSync(stateFile)).toBe(true);
|
|
60
|
+
expect(fs.existsSync(`${stateFile}.1.json`)).toBe(false);
|
|
61
|
+
const raw = JSON.parse(await fs.promises.readFile(stateFile, "utf-8"));
|
|
62
|
+
// Primary analysis lives at root (backward-compatible single-repo shape).
|
|
63
|
+
expect(raw.existingTests[0].testFile).toBe("primary.spec.ts");
|
|
64
|
+
expect(raw.metadata.repositoryPath).toBe("/work/primary");
|
|
65
|
+
// Additional repo under relatedRepos, keyed by owner/repo.
|
|
66
|
+
expect(raw.relatedRepos["letsramp/api-insight"].data.existingTests[0].testFile).toBe("api.spec.ts");
|
|
67
|
+
expect(raw.relatedRepos["letsramp/api-insight"].repositoryPath).toBe("/work/api-insight");
|
|
68
|
+
});
|
|
69
|
+
it("reads back the correct section by repo (primary when omitted)", async () => {
|
|
70
|
+
const mgr = StateManager.fromStatePath(stateFile);
|
|
71
|
+
await mgr.writeRepoData(mkState("primary"), { repositoryPath: "/work/primary" });
|
|
72
|
+
await mgr.writeRepoData(mkState("api"), { repo: "letsramp/api-insight", repositoryPath: "/work/api-insight" });
|
|
73
|
+
expect((await mgr.readRepoData()).existingTests[0].testFile).toBe("primary.spec.ts");
|
|
74
|
+
expect((await mgr.readRepoData("letsramp/api-insight")).existingTests[0].testFile).toBe("api.spec.ts");
|
|
75
|
+
expect(await mgr.getRepoRepositoryPath()).toBe("/work/primary");
|
|
76
|
+
expect(await mgr.getRepoRepositoryPath("letsramp/api-insight")).toBe("/work/api-insight");
|
|
77
|
+
expect(await mgr.listRelatedRepos()).toEqual(["letsramp/api-insight"]);
|
|
78
|
+
});
|
|
79
|
+
it("preserves additional sections when the primary root is rewritten (writeData)", async () => {
|
|
80
|
+
const mgr = StateManager.fromStatePath(stateFile);
|
|
81
|
+
await mgr.writeRepoData(mkState("primary"), { repositoryPath: "/work/primary" });
|
|
82
|
+
await mgr.writeRepoData(mkState("api"), { repo: "letsramp/api-insight", repositoryPath: "/work/api-insight" });
|
|
83
|
+
// execute_test-style root rewrite must not drop the additional section.
|
|
84
|
+
const primary = (await mgr.readRepoData());
|
|
85
|
+
primary.existingTests[0].execution = { marker: "ran" };
|
|
86
|
+
await mgr.writeData(primary);
|
|
87
|
+
expect((await mgr.readRepoData("letsramp/api-insight")).existingTests[0].testFile).toBe("api.spec.ts");
|
|
88
|
+
expect((await mgr.readRepoData()).existingTests[0].execution.marker).toBe("ran");
|
|
89
|
+
});
|
|
90
|
+
it("preserves an additional repo's repositoryPath when a later write omits it", async () => {
|
|
91
|
+
const mgr = StateManager.fromStatePath(stateFile);
|
|
92
|
+
await mgr.writeRepoData(mkState("primary"), { repositoryPath: "/work/primary" });
|
|
93
|
+
// analyze_changes records the section WITH its checkout path.
|
|
94
|
+
await mgr.writeRepoData(mkState("api"), {
|
|
95
|
+
repo: "letsramp/api-insight",
|
|
96
|
+
repositoryPath: "/work/api-insight",
|
|
97
|
+
});
|
|
98
|
+
// A later write to the same section (e.g. execute_test) omits repositoryPath.
|
|
99
|
+
await mgr.writeRepoData(mkState("api-ran"), { repo: "letsramp/api-insight" });
|
|
100
|
+
// The path must survive so getRepoRepositoryPath can still re-absolutize.
|
|
101
|
+
expect(await mgr.getRepoRepositoryPath("letsramp/api-insight")).toBe("/work/api-insight");
|
|
102
|
+
expect((await mgr.readRepoData("letsramp/api-insight")).existingTests[0].testFile).toBe("api-ran.spec.ts");
|
|
103
|
+
});
|
|
104
|
+
it("routes the primary to root and a related repo to a section when BOTH pass an explicit repo", async () => {
|
|
105
|
+
const mgr = StateManager.fromStatePath(stateFile);
|
|
106
|
+
// The agent now always passes an explicit owner/repo, including for the
|
|
107
|
+
// primary. The FIRST write is the primary → root (tagged on metadata).
|
|
108
|
+
await mgr.writeRepoData(mkState("primary"), {
|
|
109
|
+
repo: "letsramp/primary-app",
|
|
110
|
+
repositoryPath: "/work/primary",
|
|
111
|
+
});
|
|
112
|
+
await mgr.writeRepoData(mkState("api"), {
|
|
113
|
+
repo: "letsramp/api-insight",
|
|
114
|
+
repositoryPath: "/work/api-insight",
|
|
115
|
+
});
|
|
116
|
+
const raw = JSON.parse(await fs.promises.readFile(stateFile, "utf-8"));
|
|
117
|
+
expect(raw.metadata.repository).toBe("letsramp/primary-app");
|
|
118
|
+
expect(raw.existingTests[0].testFile).toBe("primary.spec.ts");
|
|
119
|
+
expect(raw.relatedRepos["letsramp/api-insight"].data.existingTests[0].testFile).toBe("api.spec.ts");
|
|
120
|
+
// The primary is reachable BOTH by its explicit repo and by omitting it.
|
|
121
|
+
expect((await mgr.readRepoData("letsramp/primary-app")).existingTests[0].testFile).toBe("primary.spec.ts");
|
|
122
|
+
expect((await mgr.readRepoData()).existingTests[0].testFile).toBe("primary.spec.ts");
|
|
123
|
+
expect(await mgr.getRepoRepositoryPath("letsramp/primary-app")).toBe("/work/primary");
|
|
124
|
+
expect(await mgr.listRelatedRepos()).toEqual(["letsramp/api-insight"]);
|
|
125
|
+
});
|
|
126
|
+
it("reads a legacy single-section file (no relatedRepos) as the primary", async () => {
|
|
127
|
+
const mgr = StateManager.fromStatePath(stateFile);
|
|
128
|
+
// Legacy write path — no repo, no sections.
|
|
129
|
+
await mgr.writeData(mkState("legacy"), { repositoryPath: "/work/legacy" });
|
|
130
|
+
expect((await mgr.readRepoData()).existingTests[0].testFile).toBe("legacy.spec.ts");
|
|
131
|
+
expect(await mgr.getRepoRepositoryPath()).toBe("/work/legacy");
|
|
132
|
+
expect(await mgr.listRelatedRepos()).toEqual([]);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function analyzeOpenAPIWithGivenEndpoint(apiSchemaInput: string, uriInput: string, pathParamsInput: string): Promise<string | null>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface BranchDiffData {
|
|
2
|
+
currentBranch: string;
|
|
3
|
+
baseBranch: string;
|
|
4
|
+
changedFiles: string[];
|
|
5
|
+
/** Full unified diff content. */
|
|
6
|
+
diffContent: string;
|
|
7
|
+
diffStat: string;
|
|
8
|
+
/** Files newly created in this branch (--- /dev/null in diff header). Relative paths. */
|
|
9
|
+
newFiles: string[];
|
|
10
|
+
/** Files deleted in this branch (+++ /dev/null in diff header). Relative paths. */
|
|
11
|
+
deletedFiles: string[];
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Extract every file path mentioned in a unified-diff `diff --git` header.
|
|
15
|
+
* Always uses the `b/` form so renames return the new path.
|
|
16
|
+
*/
|
|
17
|
+
export declare function parseChangedFilesFromDiff(rawDiff: string): string[];
|
|
18
|
+
export declare function computeBranchDiff(repositoryPath: string, providedBaseBranch?: string): Promise<BranchDiffData>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dart route extraction for Flutter apps using the `go_router` package.
|
|
3
|
+
*
|
|
4
|
+
* Walks the repo's Dart sources looking for GoRoute declarations with
|
|
5
|
+
* string-literal `path:` arguments:
|
|
6
|
+
*
|
|
7
|
+
* GoRoute(path: '/login', builder: (ctx, st) => LoginScreen())
|
|
8
|
+
* GoRoute(path: "/profile/:id", builder: ...)
|
|
9
|
+
*
|
|
10
|
+
* Why regex (not a Dart compiler): Dart parsers (analyzer, dart-analyzer
|
|
11
|
+
* package) require a Dart SDK install. Regex on the GoRoute string-literal-path
|
|
12
|
+
* subset gives ~95% coverage of real customer apps with zero install cost.
|
|
13
|
+
* Constants like `path: _kHome` are deliberately skipped — emitting them
|
|
14
|
+
* as a URL `_kHome` would mislead the agent worse than dropping them.
|
|
15
|
+
*
|
|
16
|
+
* Each route also carries metadata to support diff-matching in the caller
|
|
17
|
+
* (findCandidatePagesByDartRoute):
|
|
18
|
+
*
|
|
19
|
+
* - `screenWidgets`: identifiers referenced as builder/pageBuilder targets,
|
|
20
|
+
* e.g. `AuthorsScreen`. Resolved through the file's imports to absolute
|
|
21
|
+
* paths in `screenFiles`.
|
|
22
|
+
* - `screenFiles`: the imported source files those identifiers come from.
|
|
23
|
+
* - `isRedirectOnly`: true if the GoRoute has `redirect:` but no
|
|
24
|
+
* `builder:` or `pageBuilder:` — these don't render UI, just redirect.
|
|
25
|
+
*
|
|
26
|
+
* Scope choices for slice 1:
|
|
27
|
+
* - GoRouter only (`MaterialApp.routes` and `auto_route` deferred)
|
|
28
|
+
* - String-literal `path:` only (no constant resolution)
|
|
29
|
+
* - Nested GoRoutes emit each declaration verbatim — the absolute parent
|
|
30
|
+
* plus relative children. Caller may compose them later.
|
|
31
|
+
*/
|
|
32
|
+
/** A route declaration discovered in Dart source. */
|
|
33
|
+
export interface DartRoute {
|
|
34
|
+
/** URL path string from the GoRoute(path: ...) declaration (verbatim). */
|
|
35
|
+
path: string;
|
|
36
|
+
/** Dart file the declaration was found in (absolute path). */
|
|
37
|
+
declaredIn: string;
|
|
38
|
+
/** Widget identifiers referenced from builder:/pageBuilder: (e.g. `AuthorsScreen`). */
|
|
39
|
+
screenWidgets: string[];
|
|
40
|
+
/** Resolved absolute paths for screenWidgets via the file's imports. */
|
|
41
|
+
screenFiles: string[];
|
|
42
|
+
/** True when GoRoute has `redirect:` but no `builder:`/`pageBuilder:` — no UI. */
|
|
43
|
+
isRedirectOnly: boolean;
|
|
44
|
+
}
|
|
45
|
+
export declare function extractDartRoutes(repositoryPath: string): DartRoute[];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pull a Docker image by name.
|
|
3
|
+
*
|
|
4
|
+
* When `dockerPlatform` is provided, pulls with that exact platform — no
|
|
5
|
+
* fallback logic runs. Use this for images whose platform is known ahead
|
|
6
|
+
* of time (e.g. executor is amd64-only).
|
|
7
|
+
*
|
|
8
|
+
* When `dockerPlatform` is omitted, uses the host architecture and falls
|
|
9
|
+
* back to linux/amd64 if the arm64 pull fails or the image doesn't land
|
|
10
|
+
* locally (Docker Desktop quirk on Apple Silicon).
|
|
11
|
+
*/
|
|
12
|
+
export declare function pullDockerImage(imageName: string, dockerPlatform?: string): Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Check if a Docker image exists locally for the given reference (repo:tag or digest).
|
|
15
|
+
* Uses image inspect (daemon name resolution), not listImages tag scanning.
|
|
16
|
+
* Returns true if found, false if the image is not present (404).
|
|
17
|
+
* Throws for any other error (e.g. Docker daemon unreachable) so callers can skip pulling.
|
|
18
|
+
*/
|
|
19
|
+
export declare function dockerImageExistsLocally(imageName: string): Promise<boolean>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|