@skyramp/mcp 0.3.2-rc.pom-3 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/commands/commandLibrary.d.ts +1 -0
- package/build/commands/commandLibrary.js +19 -13
- package/build/commands/localDevTestChangesCommand.d.ts +15 -0
- package/build/commands/localDevTestChangesCommand.js +201 -0
- package/build/index.js +80 -6
- package/build/prompts/code-reuse.js +3 -0
- package/build/prompts/enhance-assertions/sharedAssertionRules.d.ts +1 -0
- package/build/prompts/enhance-assertions/sharedAssertionRules.js +17 -0
- package/build/prompts/initialize-workspace/initializeWorkspacePrompt.js +11 -9
- package/build/prompts/local-dev/local-dev-plan.d.ts +35 -0
- package/build/prompts/local-dev/local-dev-plan.js +429 -0
- package/build/prompts/local-dev/local-dev-prompts.d.ts +4 -0
- package/build/prompts/local-dev/local-dev-prompts.js +190 -0
- package/build/prompts/pom-aware-code-reuse.js +12 -0
- package/build/prompts/prompt-utils.d.ts +8 -0
- package/build/prompts/prompt-utils.js +33 -0
- package/build/prompts/sut-setup/modes/adaptWorkflowPrompt.js +37 -14
- package/build/prompts/sut-setup/shared.js +16 -12
- package/build/prompts/test-recommendation/analysisOutputPrompt.js +21 -29
- package/build/prompts/test-recommendation/scopeAssessment.d.ts +24 -7
- package/build/prompts/test-recommendation/scopeAssessment.js +111 -12
- package/build/prompts/test-recommendation/test-recommendation-prompt.js +41 -4
- package/build/prompts/testbot/testbot-prompts.d.ts +0 -5
- package/build/prompts/testbot/testbot-prompts.js +32 -52
- package/build/recommendation/planRanker.d.ts +15 -2
- package/build/recommendation/planRanker.js +76 -5
- package/build/resources/testbotResource.js +2 -1
- package/build/services/AnalyticsService.d.ts +1 -1
- package/build/services/TestExecutionService.d.ts +2 -1
- package/build/services/TestExecutionService.js +8 -3
- package/build/services/TestGenerationService.d.ts +2 -2
- package/build/services/TestGenerationService.js +39 -21
- package/build/services/containerEnv.js +3 -1
- package/build/tool-phases.js +6 -0
- package/build/tools/code-refactor/codeReuseTool.js +43 -4
- package/build/tools/code-refactor/enhanceAssertionsTool.js +68 -18
- package/build/tools/code-refactor/reuse-outcome.d.ts +109 -0
- package/build/tools/code-refactor/reuse-outcome.js +158 -0
- package/build/tools/code-refactor/reuse-state.d.ts +45 -0
- package/build/tools/code-refactor/reuse-state.js +140 -0
- package/build/tools/enrichTestWithMocksTool.d.ts +28 -0
- package/build/tools/enrichTestWithMocksTool.js +726 -0
- package/build/tools/executeSkyrampTestTool.d.ts +11 -0
- package/build/tools/executeSkyrampTestTool.js +62 -21
- package/build/tools/generate-tests/batchMockGenerationTool.d.ts +106 -0
- package/build/tools/generate-tests/batchMockGenerationTool.js +545 -0
- package/build/tools/generate-tests/generateBatchScenarioRestTool.js +25 -23
- package/build/tools/generate-tests/generateContractRestTool.js +2 -2
- package/build/tools/generate-tests/generateE2ERestTool.d.ts +1 -1
- package/build/tools/generate-tests/generateE2ERestTool.js +1 -1
- package/build/tools/generate-tests/generateLoadRestTool.d.ts +1 -1
- package/build/tools/generate-tests/generateLoadRestTool.js +1 -1
- package/build/tools/generate-tests/generateMockRestTool.d.ts +179 -6
- package/build/tools/generate-tests/generateMockRestTool.js +391 -22
- package/build/tools/generate-tests/loadTestSchema.d.ts +1 -1
- package/build/tools/generate-tests/planGuard.js +2 -22
- package/build/tools/generateEnrichedIntegrationTestTool.d.ts +25 -0
- package/build/tools/generateEnrichedIntegrationTestTool.js +235 -0
- package/build/tools/localDevWorkerComposeTool.d.ts +24 -0
- package/build/tools/localDevWorkerComposeTool.js +264 -0
- package/build/tools/one-click/oneClickTool.d.ts +13 -0
- package/build/tools/one-click/oneClickTool.js +195 -24
- package/build/tools/preflightMockCheckTool.d.ts +2 -0
- package/build/tools/preflightMockCheckTool.js +96 -0
- package/build/tools/queryProxyMocksTool.d.ts +70 -0
- package/build/tools/queryProxyMocksTool.js +522 -0
- package/build/tools/runExistingTestsTool.d.ts +31 -0
- package/build/tools/runExistingTestsTool.js +214 -13
- package/build/tools/submitReportTool.js +38 -3
- package/build/tools/test-management/analyzeChangesTool.d.ts +2 -1
- package/build/tools/test-management/analyzeChangesTool.js +63 -40
- package/build/tools/test-management/registerTestPlanTool.js +55 -7
- package/build/tools/trace/startTraceCollectionTool.js +3 -3
- package/build/types/FrontendIntegration.d.ts +3 -0
- package/build/types/FrontendIntegration.js +3 -0
- package/build/types/OneClickCommands.d.ts +1 -1
- package/build/types/Recommendation.d.ts +20 -0
- package/build/types/Recommendation.js +32 -6
- package/build/types/RepositoryAnalysis.d.ts +131 -14
- package/build/types/RepositoryAnalysis.js +16 -2
- package/build/types/ReuseOutcome.d.ts +63 -0
- package/build/types/ReuseOutcome.js +33 -0
- package/build/types/TestExecution.d.ts +1 -0
- package/build/types/TestTypes.d.ts +25 -7
- package/build/types/TestTypes.js +22 -6
- package/build/types/TestbotReport.d.ts +7 -0
- package/build/types/index.d.ts +2 -0
- package/build/types/index.js +1 -0
- package/build/utils/AnalysisStateManager.d.ts +26 -0
- package/build/utils/AnalysisStateManager.js +31 -1
- package/build/utils/analyze-openapi.js +18 -1
- package/build/utils/branchDiff.d.ts +17 -1
- package/build/utils/branchDiff.js +99 -14
- package/build/utils/featureFlags.d.ts +31 -0
- package/build/utils/featureFlags.js +37 -0
- package/build/utils/frontendIntegration.js +8 -1
- package/build/utils/grpcMockValidation.d.ts +1 -0
- package/build/utils/grpcMockValidation.js +49 -0
- package/build/utils/httpMethodValidation.d.ts +4 -0
- package/build/utils/httpMethodValidation.js +15 -0
- package/build/utils/logger.js +1 -1
- package/build/utils/mockCompatibility.d.ts +49 -0
- package/build/utils/mockCompatibility.js +82 -0
- package/build/utils/pom-scope/pom-files.js +7 -0
- package/build/utils/pom-verify/bindings.js +11 -1
- package/build/utils/pom-verify/re-exports.d.ts +10 -0
- package/build/utils/pom-verify/re-exports.js +61 -0
- package/build/utils/pom-verify/resolve.d.ts +4 -1
- package/build/utils/pom-verify/resolve.js +7 -4
- package/build/utils/pom-verify/verify.d.ts +5 -0
- package/build/utils/pom-verify/verify.js +25 -3
- package/build/utils/progress.js +10 -5
- package/build/utils/proxy-terminal.js +3 -3
- package/build/utils/routeParsers.d.ts +3 -9
- package/build/utils/routeParsers.js +79 -4
- package/build/utils/utils.js +2 -2
- package/build/utils/versions.d.ts +4 -3
- package/build/utils/versions.js +3 -1
- package/build/utils/workspaceAuth.d.ts +46 -0
- package/build/utils/workspaceAuth.js +156 -1
- package/build/workspace/testSuites.d.ts +2 -1
- package/build/workspace/testSuites.js +1 -1
- package/build/workspace/workspace.d.ts +108 -32
- package/build/workspace/workspace.js +32 -4
- package/package.json +5 -2
- package/build/adapters/jestAdapter.test.d.ts +0 -1
- package/build/adapters/jestAdapter.test.js +0 -93
- package/build/adapters/mochaAdapter.test.d.ts +0 -1
- package/build/adapters/mochaAdapter.test.js +0 -63
- package/build/adapters/playwrightAdapter.test.d.ts +0 -1
- package/build/adapters/playwrightAdapter.test.js +0 -169
- package/build/adapters/pytestAdapter.test.d.ts +0 -1
- package/build/adapters/pytestAdapter.test.js +0 -90
- package/build/prompts/code-reuse.test.d.ts +0 -1
- package/build/prompts/code-reuse.test.js +0 -62
- package/build/prompts/pom-aware-code-reuse.test.d.ts +0 -1
- package/build/prompts/pom-aware-code-reuse.test.js +0 -11
- package/build/prompts/test-maintenance/drift-analysis-prompt.test.d.ts +0 -1
- package/build/prompts/test-maintenance/drift-analysis-prompt.test.js +0 -51
- package/build/prompts/test-recommendation/analysisOutputPrompt.test.d.ts +0 -1
- package/build/prompts/test-recommendation/analysisOutputPrompt.test.js +0 -264
- package/build/prompts/test-recommendation/mergeEnrichedScenarios.test.d.ts +0 -1
- package/build/prompts/test-recommendation/mergeEnrichedScenarios.test.js +0 -126
- package/build/prompts/test-recommendation/promptPlan.test.d.ts +0 -1
- package/build/prompts/test-recommendation/promptPlan.test.js +0 -336
- package/build/prompts/test-recommendation/scopeAssessment.test.d.ts +0 -1
- package/build/prompts/test-recommendation/scopeAssessment.test.js +0 -371
- package/build/prompts/test-recommendation/test-recommendation-prompt.test.d.ts +0 -1
- package/build/prompts/test-recommendation/test-recommendation-prompt.test.js +0 -1925
- package/build/prompts/testbot/testbot-prompts.test.d.ts +0 -1
- package/build/prompts/testbot/testbot-prompts.test.js +0 -451
- package/build/recommendation/budgeters/diversityBalancedBudgeter.test.d.ts +0 -1
- package/build/recommendation/budgeters/diversityBalancedBudgeter.test.js +0 -138
- package/build/recommendation/budgeters/fixedNBudgeter.test.d.ts +0 -1
- package/build/recommendation/budgeters/fixedNBudgeter.test.js +0 -66
- package/build/recommendation/discriminators.test.d.ts +0 -1
- package/build/recommendation/discriminators.test.js +0 -324
- package/build/recommendation/diversity.test.d.ts +0 -1
- package/build/recommendation/diversity.test.js +0 -77
- package/build/recommendation/planRanker.test.d.ts +0 -1
- package/build/recommendation/planRanker.test.js +0 -110
- package/build/resources/testbotResource.test.d.ts +0 -1
- package/build/resources/testbotResource.test.js +0 -44
- package/build/services/AnalyticsService.test.d.ts +0 -1
- package/build/services/AnalyticsService.test.js +0 -86
- package/build/services/ScenarioGenerationService.integration.test.d.ts +0 -1
- package/build/services/ScenarioGenerationService.integration.test.js +0 -162
- package/build/services/ScenarioGenerationService.test.d.ts +0 -1
- package/build/services/ScenarioGenerationService.test.js +0 -414
- package/build/services/TestDiscoveryService.test.d.ts +0 -1
- package/build/services/TestDiscoveryService.test.js +0 -983
- package/build/services/TestExecutionService.test.d.ts +0 -1
- package/build/services/TestExecutionService.test.js +0 -1032
- package/build/services/TestGenerationService.test.d.ts +0 -1
- package/build/services/TestGenerationService.test.js +0 -578
- package/build/tool-phase-coverage.test.d.ts +0 -1
- package/build/tool-phase-coverage.test.js +0 -49
- package/build/tools/code-refactor/codeReuseTool.test.d.ts +0 -1
- package/build/tools/code-refactor/codeReuseTool.test.js +0 -572
- package/build/tools/generate-tests/generateBatchScenarioRestTool.test.d.ts +0 -1
- package/build/tools/generate-tests/generateBatchScenarioRestTool.test.js +0 -433
- package/build/tools/generate-tests/generateContractRestTool.test.d.ts +0 -1
- package/build/tools/generate-tests/generateContractRestTool.test.js +0 -142
- package/build/tools/generate-tests/generateIntegrationRestTool.test.d.ts +0 -1
- package/build/tools/generate-tests/generateIntegrationRestTool.test.js +0 -159
- package/build/tools/generate-tests/generateLoadRestTool.test.d.ts +0 -1
- package/build/tools/generate-tests/generateLoadRestTool.test.js +0 -169
- package/build/tools/generate-tests/generateUIRestTool.test.d.ts +0 -1
- package/build/tools/generate-tests/generateUIRestTool.test.js +0 -32
- package/build/tools/generate-tests/planGuard.test.d.ts +0 -1
- package/build/tools/generate-tests/planGuard.test.js +0 -185
- package/build/tools/generate-tests/scenarioLint.test.d.ts +0 -1
- package/build/tools/generate-tests/scenarioLint.test.js +0 -100
- package/build/tools/runExistingTestsTool.test.d.ts +0 -1
- package/build/tools/runExistingTestsTool.test.js +0 -379
- package/build/tools/submitReportTool.test.d.ts +0 -1
- package/build/tools/submitReportTool.test.js +0 -1428
- package/build/tools/test-management/actionsTool.test.d.ts +0 -1
- package/build/tools/test-management/actionsTool.test.js +0 -436
- package/build/tools/test-management/analyzeChangesTool.test.d.ts +0 -1
- package/build/tools/test-management/analyzeChangesTool.test.js +0 -522
- package/build/tools/test-management/analyzeTestHealthTool.test.d.ts +0 -1
- package/build/tools/test-management/analyzeTestHealthTool.test.js +0 -385
- package/build/tools/test-management/registerTestPlanTool.test.d.ts +0 -1
- package/build/tools/test-management/registerTestPlanTool.test.js +0 -296
- package/build/tools/trace/resolveSaveStoragePath.test.d.ts +0 -1
- package/build/tools/trace/resolveSaveStoragePath.test.js +0 -17
- package/build/tools/trace/resolveSessionPaths.test.d.ts +0 -1
- package/build/tools/trace/resolveSessionPaths.test.js +0 -104
- package/build/tools/trace/sessionState.test.d.ts +0 -1
- package/build/tools/trace/sessionState.test.js +0 -17
- package/build/tools/workspace/initializeWorkspaceTool.test.d.ts +0 -1
- package/build/tools/workspace/initializeWorkspaceTool.test.js +0 -139
- package/build/tools/workspace/serviceUpsert.test.d.ts +0 -1
- package/build/tools/workspace/serviceUpsert.test.js +0 -50
- package/build/utils/AnalysisStateManager.test.d.ts +0 -1
- package/build/utils/AnalysisStateManager.test.js +0 -134
- package/build/utils/dartRouteExtractor.test.d.ts +0 -1
- package/build/utils/dartRouteExtractor.test.js +0 -307
- package/build/utils/docker.test.d.ts +0 -1
- package/build/utils/docker.test.js +0 -114
- package/build/utils/featureFlags.test.d.ts +0 -1
- package/build/utils/featureFlags.test.js +0 -81
- package/build/utils/fileWalk.test.d.ts +0 -1
- package/build/utils/fileWalk.test.js +0 -252
- package/build/utils/frontendIntegration.test.d.ts +0 -1
- package/build/utils/frontendIntegration.test.js +0 -229
- package/build/utils/frontendSelectors.test.d.ts +0 -1
- package/build/utils/frontendSelectors.test.js +0 -118
- package/build/utils/gitStaging.test.d.ts +0 -1
- package/build/utils/gitStaging.test.js +0 -111
- package/build/utils/httpDefaults.test.d.ts +0 -1
- package/build/utils/httpDefaults.test.js +0 -21
- package/build/utils/importerHop.test.d.ts +0 -1
- package/build/utils/importerHop.test.js +0 -469
- package/build/utils/pathAffinityClassification.test.d.ts +0 -1
- package/build/utils/pathAffinityClassification.test.js +0 -208
- package/build/utils/planMatchKeys.test.d.ts +0 -1
- package/build/utils/planMatchKeys.test.js +0 -123
- package/build/utils/pom-scope/index.test.d.ts +0 -1
- package/build/utils/pom-scope/index.test.js +0 -278
- package/build/utils/pom-scope/pom-files.test.d.ts +0 -1
- package/build/utils/pom-scope/pom-files.test.js +0 -29
- package/build/utils/pom-scope/scoring.test.d.ts +0 -1
- package/build/utils/pom-scope/scoring.test.js +0 -39
- package/build/utils/pom-scope/selector-extractor.test.d.ts +0 -1
- package/build/utils/pom-scope/selector-extractor.test.js +0 -67
- package/build/utils/pom-verify/bindings.test.d.ts +0 -1
- package/build/utils/pom-verify/bindings.test.js +0 -164
- package/build/utils/pom-verify/calls.test.d.ts +0 -1
- package/build/utils/pom-verify/calls.test.js +0 -61
- package/build/utils/pom-verify/resolve.test.d.ts +0 -1
- package/build/utils/pom-verify/resolve.test.js +0 -114
- package/build/utils/pom-verify/verify.test.d.ts +0 -1
- package/build/utils/pom-verify/verify.test.js +0 -374
- package/build/utils/pr-comment-parser.test.d.ts +0 -1
- package/build/utils/pr-comment-parser.test.js +0 -428
- package/build/utils/progress.test.d.ts +0 -1
- package/build/utils/progress.test.js +0 -37
- package/build/utils/projectMetadata.test.d.ts +0 -1
- package/build/utils/projectMetadata.test.js +0 -172
- package/build/utils/pythonMountPrefixes.test.d.ts +0 -1
- package/build/utils/pythonMountPrefixes.test.js +0 -113
- package/build/utils/repoScanner.test.d.ts +0 -1
- package/build/utils/repoScanner.test.js +0 -190
- package/build/utils/reportVerification.test.d.ts +0 -1
- package/build/utils/reportVerification.test.js +0 -185
- package/build/utils/routeParsers.test.d.ts +0 -1
- package/build/utils/routeParsers.test.js +0 -1011
- package/build/utils/scenarioDrafting.test.d.ts +0 -1
- package/build/utils/scenarioDrafting.test.js +0 -876
- package/build/utils/sourceRouteExtractor.test.d.ts +0 -1
- package/build/utils/sourceRouteExtractor.test.js +0 -738
- package/build/utils/telemetry.test.d.ts +0 -1
- package/build/utils/telemetry.test.js +0 -70
- package/build/utils/trace-parser.test.d.ts +0 -6
- package/build/utils/trace-parser.test.js +0 -140
- package/build/utils/uiPageEnumerator.test.d.ts +0 -1
- package/build/utils/uiPageEnumerator.test.js +0 -824
- package/build/utils/utils.test.d.ts +0 -1
- package/build/utils/utils.test.js +0 -103
- package/build/utils/walkerCharacterization.test.d.ts +0 -1
- package/build/utils/walkerCharacterization.test.js +0 -233
- package/build/utils/workspaceAuth.test.d.ts +0 -1
- package/build/utils/workspaceAuth.test.js +0 -260
- package/build/workspace/testSuites.test.d.ts +0 -1
- package/build/workspace/testSuites.test.js +0 -23
- package/build/workspace/workspace.test.d.ts +0 -1
- package/build/workspace/workspace.test.js +0 -264
|
@@ -12,6 +12,7 @@ import { buildPlaywrightArgs, parsePlaywrightJson, } from "../adapters/playwrigh
|
|
|
12
12
|
import { buildPytestArgs, parsePytestJson } from "../adapters/pytestAdapter.js";
|
|
13
13
|
import { buildJestArgs, buildVitestArgs, parseJestJson } from "../adapters/jestAdapter.js";
|
|
14
14
|
import { buildMochaArgs, parseMochaJson } from "../adapters/mochaAdapter.js";
|
|
15
|
+
import picomatch from "picomatch";
|
|
15
16
|
import { AnalyticsService } from "../services/AnalyticsService.js";
|
|
16
17
|
import { logger } from "../utils/logger.js";
|
|
17
18
|
import { toolError } from "../utils/utils.js";
|
|
@@ -128,6 +129,81 @@ export function resolveRunConfig(config, opts = {}) {
|
|
|
128
129
|
}
|
|
129
130
|
return { runs: runnable.map(toResolvedRunConfig), skips };
|
|
130
131
|
}
|
|
132
|
+
/** The file part of a selector — `file::test title` keeps only `file`. */
|
|
133
|
+
function selectorFile(selector) {
|
|
134
|
+
const idx = selector.indexOf("::");
|
|
135
|
+
return (idx >= 0 ? selector.slice(0, idx) : selector).trim();
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Resolves a selector's file to a repo-relative path, the form `pathGlobs` are
|
|
139
|
+
* written in.
|
|
140
|
+
*
|
|
141
|
+
* Selectors arrive in whatever form the caller used — repo-relative, absolute, or
|
|
142
|
+
* a shorter tail. Discovery (analyze_changes) records the ABSOLUTE path of every
|
|
143
|
+
* test it found, so those paths are the authority: when a selector does not
|
|
144
|
+
* resolve to one of them directly, match it by tail to recover the real file.
|
|
145
|
+
*/
|
|
146
|
+
function toRepoRelative(file, workspacePath, knownTestFiles) {
|
|
147
|
+
let abs = path.isAbsolute(file) ? file : path.resolve(workspacePath, file);
|
|
148
|
+
if (knownTestFiles?.length && !knownTestFiles.includes(abs)) {
|
|
149
|
+
const match = knownTestFiles.find((known) => known.endsWith(`/${file}`));
|
|
150
|
+
if (match)
|
|
151
|
+
abs = match;
|
|
152
|
+
}
|
|
153
|
+
return path.relative(workspacePath, abs);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Partitions `selectors` into the ones this suite owns and the ones it does not,
|
|
157
|
+
* using the suite's own `pathGlobs` from workspace.yml.
|
|
158
|
+
*
|
|
159
|
+
* One repo commonly hosts several runners (mocha for a core package, vitest for
|
|
160
|
+
* app packages, Playwright for e2e), and handing a runner another runner's spec is
|
|
161
|
+
* not a test failure — it is a loader crash that yields no report at all. Which
|
|
162
|
+
* suite owns which paths is a fact about the repo, so it is recorded as config the
|
|
163
|
+
* user can read and correct, rather than guessed at run time. Run 30297865488 is
|
|
164
|
+
* the cautionary case: routing on a guessed framework tag sent Ghost's Playwright
|
|
165
|
+
* specs away from the Playwright suite, and nothing ran.
|
|
166
|
+
*
|
|
167
|
+
* A suite with no globs owns everything. That is deliberate: absent globs means
|
|
168
|
+
* the suite is unscoped, not that it owns nothing, and narrowing on no
|
|
169
|
+
* information would silently shrink the run.
|
|
170
|
+
*/
|
|
171
|
+
export function routeSelectorsToSuite(input) {
|
|
172
|
+
const globs = input.pathGlobs ?? [];
|
|
173
|
+
if (globs.length === 0)
|
|
174
|
+
return { kept: [...input.selectors], dropped: [] };
|
|
175
|
+
const kept = [];
|
|
176
|
+
const dropped = [];
|
|
177
|
+
for (const selector of input.selectors) {
|
|
178
|
+
const rel = toRepoRelative(selectorFile(selector), input.workspacePath, input.knownTestFiles);
|
|
179
|
+
if (picomatch.isMatch(rel, globs))
|
|
180
|
+
kept.push(selector);
|
|
181
|
+
else
|
|
182
|
+
dropped.push(selector);
|
|
183
|
+
}
|
|
184
|
+
return { kept, dropped };
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* The absolute test-file paths analyze_changes recorded, used to canonicalize a
|
|
188
|
+
* selector before glob matching. Best-effort: with no state file, an unreadable
|
|
189
|
+
* one, or a repo section that was never written, routing just resolves selectors
|
|
190
|
+
* against the repo root.
|
|
191
|
+
*/
|
|
192
|
+
async function loadKnownTestFiles(stateFile, repository) {
|
|
193
|
+
if (!stateFile)
|
|
194
|
+
return [];
|
|
195
|
+
try {
|
|
196
|
+
const sm = StateManager.fromStatePath(stateFile);
|
|
197
|
+
const state = await sm.readRepoData(repository);
|
|
198
|
+
return (state?.existingTests ?? [])
|
|
199
|
+
.map((t) => t.testFile)
|
|
200
|
+
.filter((f) => !!f);
|
|
201
|
+
}
|
|
202
|
+
catch (err) {
|
|
203
|
+
logger.warning(`${TOOL_NAME}: could not read existingTests for selector routing: ${err.message}`);
|
|
204
|
+
return [];
|
|
205
|
+
}
|
|
206
|
+
}
|
|
131
207
|
/** Maps a NormalizedSuite to the run-time config the runner consumes. */
|
|
132
208
|
function toResolvedRunConfig(s) {
|
|
133
209
|
return {
|
|
@@ -135,6 +211,7 @@ function toResolvedRunConfig(s) {
|
|
|
135
211
|
framework: s.framework,
|
|
136
212
|
testRunCommand: s.testRunCommand,
|
|
137
213
|
testEnv: s.testEnv,
|
|
214
|
+
pathGlobs: s.pathGlobs,
|
|
138
215
|
};
|
|
139
216
|
}
|
|
140
217
|
/** Combines per-suite runs into one ParsedExternalRun for the tool's summary
|
|
@@ -166,14 +243,57 @@ export function aggregateRuns(parsed) {
|
|
|
166
243
|
}
|
|
167
244
|
}
|
|
168
245
|
}
|
|
246
|
+
// Only when EVERY suite was skipped is the whole run a skip — otherwise the
|
|
247
|
+
// suites that did run own the verdict. Surfacing it matters: the tool's caller
|
|
248
|
+
// branches on `skipped` to say why nothing ran instead of reporting a bare
|
|
249
|
+
// "ran 0 tests", which reads as a clean pass.
|
|
250
|
+
const allSkipped = parsed.length > 0 && parsed.every((p) => p.skipped);
|
|
251
|
+
const skipReasons = parsed.map((p) => p.skipReason).filter(Boolean);
|
|
169
252
|
return {
|
|
170
253
|
results,
|
|
171
254
|
summary,
|
|
172
255
|
environmentHealthy,
|
|
173
256
|
healthDetail: details.length ? details.join(" | ") : undefined,
|
|
174
257
|
diagnostics,
|
|
258
|
+
...(allSkipped
|
|
259
|
+
? { skipped: true, skipReason: skipReasons.join(" | ") || undefined }
|
|
260
|
+
: {}),
|
|
175
261
|
};
|
|
176
262
|
}
|
|
263
|
+
/**
|
|
264
|
+
* How much of a failed run's stderr to carry into `healthDetail`/`skipReason`.
|
|
265
|
+
*
|
|
266
|
+
* This is often the ONLY record of the failure: a run command that shells out to
|
|
267
|
+
* a container writes to the MCP server's child process, not to the CI job log, so
|
|
268
|
+
* nothing else captures it. Run 30314973126 lost the real error to the previous
|
|
269
|
+
* 1200-char tail — it cut off mid-line, one line after the failing command.
|
|
270
|
+
*/
|
|
271
|
+
const STDERR_TAIL_CHARS = 4000;
|
|
272
|
+
/**
|
|
273
|
+
* How much of a failed run's stdout to carry alongside the stderr tail.
|
|
274
|
+
*
|
|
275
|
+
* Every framework EXCEPT mocha reports via a file (`outputVia: "file"`), and those
|
|
276
|
+
* runners print their human-readable failure text — a Playwright global-setup stack
|
|
277
|
+
* trace, a pytest collection error — on STDOUT, which used to be dropped unread to
|
|
278
|
+
* avoid backpressure. So the stderr-only tail left exactly the failures it was added
|
|
279
|
+
* for with no cause attached: run 30402703836 reported `global.setup.ts` as failed
|
|
280
|
+
* and carried nothing about why, because the reason went to stdout.
|
|
281
|
+
*/
|
|
282
|
+
const STDOUT_TAIL_CHARS = 4000;
|
|
283
|
+
/**
|
|
284
|
+
* Label and join whichever of the child's output streams carried something, so a
|
|
285
|
+
* failure path reports the cause regardless of which stream the runner chose.
|
|
286
|
+
*/
|
|
287
|
+
function buildOutputDiagnostic(stderrTail, stdoutTail) {
|
|
288
|
+
const parts = [];
|
|
289
|
+
const err = stripVTControlCharacters(stderrTail).trim();
|
|
290
|
+
const out = stripVTControlCharacters(stdoutTail).trim();
|
|
291
|
+
if (err)
|
|
292
|
+
parts.push(`stderr: ${err}`);
|
|
293
|
+
if (out)
|
|
294
|
+
parts.push(`stdout: ${out}`);
|
|
295
|
+
return parts.join(" | ");
|
|
296
|
+
}
|
|
177
297
|
/** POSIX single-quote a shell argument. */
|
|
178
298
|
function shellQuote(s) {
|
|
179
299
|
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
@@ -200,7 +320,7 @@ export async function runExternalSuite(spec, cfg, opts) {
|
|
|
200
320
|
...(cfg.testEnv ?? {}),
|
|
201
321
|
...(spec.reportEnvVar ? { [spec.reportEnvVar]: reportPath } : {}),
|
|
202
322
|
};
|
|
203
|
-
const { code, stdout, stderrTail } = await new Promise((resolve) => {
|
|
323
|
+
const { code, stdout, stderrTail, stdoutTail } = await new Promise((resolve) => {
|
|
204
324
|
const child = spawn("bash", ["-c", cmd], {
|
|
205
325
|
cwd: opts.workspacePath,
|
|
206
326
|
env,
|
|
@@ -208,20 +328,33 @@ export async function runExternalSuite(spec, cfg, opts) {
|
|
|
208
328
|
});
|
|
209
329
|
let stderr = "";
|
|
210
330
|
let out = "";
|
|
331
|
+
let outTail = "";
|
|
211
332
|
child.stderr?.on("data", (d) => {
|
|
212
333
|
stderr += d.toString();
|
|
213
334
|
if (stderr.length > 16000)
|
|
214
335
|
stderr = stderr.slice(-16000);
|
|
215
336
|
});
|
|
216
337
|
child.stdout?.on("data", (d) => {
|
|
338
|
+
const chunk = d.toString();
|
|
339
|
+
// Always keep a bounded tail: for `outputVia: "file"` frameworks this is the
|
|
340
|
+
// ONLY record of why the run failed. Accumulating the full stream stays gated
|
|
341
|
+
// on "stdout" readers, which parse their JSON report out of it.
|
|
342
|
+
outTail += chunk;
|
|
343
|
+
if (outTail.length > STDOUT_TAIL_CHARS)
|
|
344
|
+
outTail = outTail.slice(-STDOUT_TAIL_CHARS);
|
|
217
345
|
if (spec.outputVia !== "stdout")
|
|
218
346
|
return; // drain to avoid backpressure
|
|
219
|
-
out +=
|
|
347
|
+
out += chunk;
|
|
220
348
|
if (out.length > 8_000_000)
|
|
221
349
|
out = out.slice(-8_000_000);
|
|
222
350
|
});
|
|
223
|
-
child.on("close", (c) => resolve({
|
|
224
|
-
|
|
351
|
+
child.on("close", (c) => resolve({
|
|
352
|
+
code: c,
|
|
353
|
+
stdout: out,
|
|
354
|
+
stderrTail: stderr.slice(-STDERR_TAIL_CHARS),
|
|
355
|
+
stdoutTail: outTail,
|
|
356
|
+
}));
|
|
357
|
+
child.on("error", (e) => resolve({ code: -1, stdout: out, stderrTail: String(e), stdoutTail: outTail }));
|
|
225
358
|
});
|
|
226
359
|
let report;
|
|
227
360
|
try {
|
|
@@ -233,11 +366,40 @@ export async function runExternalSuite(spec, cfg, opts) {
|
|
|
233
366
|
catch {
|
|
234
367
|
if (spec.outputVia === "file")
|
|
235
368
|
await fs.unlink(reportPath).catch(() => { });
|
|
369
|
+
const emptySummary = {
|
|
370
|
+
ran: 0,
|
|
371
|
+
failed: 0,
|
|
372
|
+
errored: 0,
|
|
373
|
+
skipped: 0,
|
|
374
|
+
truncated: opts.truncated,
|
|
375
|
+
};
|
|
376
|
+
const outputDetail = buildOutputDiagnostic(stderrTail, stdoutTail) || "(no output)";
|
|
377
|
+
// A SCOPED run that produced no report is not evidence the environment is
|
|
378
|
+
// down: the usual causes are a spec this runner cannot load (the wrong suite
|
|
379
|
+
// for the file) or this suite's own setup failing. Calling it unhealthy is
|
|
380
|
+
// what let a mocha loader crash read as a dead SUT and suppress PR findings,
|
|
381
|
+
// so report a skip and let the other suites speak for the environment.
|
|
382
|
+
//
|
|
383
|
+
// Do NOT guess between those causes here — run 30314973126 reported "they
|
|
384
|
+
// probably belong to another suite" about selectors that provably belonged to
|
|
385
|
+
// this one, while the real fault was the suite's containerised run command
|
|
386
|
+
// never emitting a report. State both and hand over the stderr.
|
|
387
|
+
if (opts.selectors.length > 0) {
|
|
388
|
+
return {
|
|
389
|
+
results: [],
|
|
390
|
+
summary: emptySummary,
|
|
391
|
+
environmentHealthy: true,
|
|
392
|
+
skipped: true,
|
|
393
|
+
skipReason: `${spec.framework} produced no parseable JSON report (exit ${code}) for the ${opts.selectors.length} selector(s) it was given, so this suite reported nothing either way. Either the selectors are not this suite's to run (check its testSuites pathGlobs) or its own run command failed before emitting a report — the output below says which. ${outputDetail}`,
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
// Nothing was narrowed, so a suite that cannot even produce a report does
|
|
397
|
+
// point at the environment.
|
|
236
398
|
return {
|
|
237
399
|
results: [],
|
|
238
|
-
summary:
|
|
400
|
+
summary: emptySummary,
|
|
239
401
|
environmentHealthy: false,
|
|
240
|
-
healthDetail: `${spec.framework} produced no parseable JSON report (exit ${code}); the suite likely failed to install/collect or the environment never came up.
|
|
402
|
+
healthDetail: `${spec.framework} produced no parseable JSON report (exit ${code}); the suite likely failed to install/collect or the environment never came up. ${outputDetail}`,
|
|
241
403
|
};
|
|
242
404
|
}
|
|
243
405
|
if (spec.outputVia === "file")
|
|
@@ -246,13 +408,19 @@ export async function runExternalSuite(spec, cfg, opts) {
|
|
|
246
408
|
// The parser builds healthDetail from the report contents only. When it flags
|
|
247
409
|
// an unhealthy env (e.g. a Playwright global-setup spec failed), the report is
|
|
248
410
|
// parseable but the actual cause — a stack trace, "container not found", a
|
|
249
|
-
// dependency download that hung — lives on the runner's
|
|
250
|
-
// report never carries. Append
|
|
251
|
-
//
|
|
411
|
+
// dependency download that hung — lives on the runner's output, which the
|
|
412
|
+
// report never carries. Append it so a maintenance run's env failure is
|
|
413
|
+
// diagnosable from healthDetail alone, not only by re-running blind.
|
|
414
|
+
//
|
|
415
|
+
// Both streams, not just stderr: the runners that hit this path all report via a
|
|
416
|
+
// file and print their failure text on stdout, so a stderr-only tail named the
|
|
417
|
+
// failing setup spec and dropped the reason (run 30402703836). A dead end here
|
|
418
|
+
// costs the whole maintenance loop — with no baseline, testbot's step (f) skips
|
|
419
|
+
// the `mode: "verify"` re-run, so the agent never confirms its own fix.
|
|
252
420
|
if (!parsed.environmentHealthy) {
|
|
253
|
-
const
|
|
254
|
-
if (
|
|
255
|
-
parsed.healthDetail = `${parsed.healthDetail ?? `${spec.framework} environment unhealthy (exit ${code}).`}
|
|
421
|
+
const detail = buildOutputDiagnostic(stderrTail, stdoutTail);
|
|
422
|
+
if (detail) {
|
|
423
|
+
parsed.healthDetail = `${parsed.healthDetail ?? `${spec.framework} environment unhealthy (exit ${code}).`} ${detail}`;
|
|
256
424
|
}
|
|
257
425
|
}
|
|
258
426
|
return parsed;
|
|
@@ -325,11 +493,44 @@ export async function runExternalTests(params, deps = defaultDeps) {
|
|
|
325
493
|
}
|
|
326
494
|
return skippedRun;
|
|
327
495
|
}
|
|
496
|
+
const knownTestFiles = await loadKnownTestFiles(params.stateFile, params.repository);
|
|
328
497
|
const perSuite = [];
|
|
329
498
|
for (const cfg of resolution.runs) {
|
|
499
|
+
const routing = routeSelectorsToSuite({
|
|
500
|
+
selectors: effective,
|
|
501
|
+
pathGlobs: cfg.pathGlobs,
|
|
502
|
+
knownTestFiles,
|
|
503
|
+
workspacePath: params.workspacePath,
|
|
504
|
+
});
|
|
505
|
+
// An empty selector list means "run the whole configured suite", so routing
|
|
506
|
+
// every selector away must NOT collapse into that: it would silently widen a
|
|
507
|
+
// scoped run into the entire suite. Skip the suite instead, loudly.
|
|
508
|
+
if (effective.length > 0 && routing.kept.length === 0) {
|
|
509
|
+
const owned = (cfg.pathGlobs ?? []).join(", ");
|
|
510
|
+
const routedOut = {
|
|
511
|
+
results: [],
|
|
512
|
+
summary: { ran: 0, failed: 0, errored: 0, skipped: 0, truncated },
|
|
513
|
+
environmentHealthy: true,
|
|
514
|
+
skipped: true,
|
|
515
|
+
skipReason: `suite "${cfg.serviceName}" owns ${owned}, and none of the ${routing.dropped.length} requested selector(s) fall under it — nothing for this suite to run. Widen its testSuites pathGlobs, or add a suite that owns those paths.`,
|
|
516
|
+
};
|
|
517
|
+
perSuite.push(routedOut);
|
|
518
|
+
if (params.stateFile) {
|
|
519
|
+
await persistExternalRun(params.stateFile, params.repository, {
|
|
520
|
+
...routedOut,
|
|
521
|
+
mode,
|
|
522
|
+
ranAt: deps.now(),
|
|
523
|
+
repository: params.repository,
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
continue;
|
|
527
|
+
}
|
|
528
|
+
if (routing.dropped.length > 0) {
|
|
529
|
+
logger.warning(`${TOOL_NAME}: dropped ${routing.dropped.length} selector(s) outside ${cfg.serviceName}'s ${cfg.framework} suite paths (${(cfg.pathGlobs ?? []).join(", ")}): ${routing.dropped.join(", ")}`);
|
|
530
|
+
}
|
|
330
531
|
const parsed = await deps.runner(cfg, {
|
|
331
532
|
workspacePath: params.workspacePath,
|
|
332
|
-
selectors:
|
|
533
|
+
selectors: routing.kept,
|
|
333
534
|
mode,
|
|
334
535
|
project: params.project,
|
|
335
536
|
timeoutMs: params.timeoutMs,
|
|
@@ -12,6 +12,7 @@ import { toolError, testFileMatches } from "../utils/utils.js";
|
|
|
12
12
|
import { matchesApprovedPlan } from "../utils/planMatchKeys.js";
|
|
13
13
|
import { isTestbotEnabled } from "../utils/featureFlags.js";
|
|
14
14
|
import { findUnbackedClaims, listChangedFiles } from "../utils/reportVerification.js";
|
|
15
|
+
import { rederiveReuseOutcome } from "./code-refactor/reuse-state.js";
|
|
15
16
|
// SKYR-3879 Path B: which testTypes the register-plan checkpoint gates. Mirrors
|
|
16
17
|
// the generation tools actually wired to planGuard (batch-scenario/integration,
|
|
17
18
|
// contract) — UI and E2E are on a separate blueprint-grounded pipeline and are
|
|
@@ -337,6 +338,34 @@ function computeReportMetrics(params) {
|
|
|
337
338
|
issuesLow: String(countBy(params.issuesFound, (i) => i.severity === "low")),
|
|
338
339
|
};
|
|
339
340
|
}
|
|
341
|
+
/**
|
|
342
|
+
* Attach the POM code-reuse outcome for a UI test, matched by the report's
|
|
343
|
+
* `fileName` (the basename skyramp_reuse_code keyed run state on).
|
|
344
|
+
*
|
|
345
|
+
* Server-derived, never supplied by the LLM — the same line this file already
|
|
346
|
+
* draws for testMaintenance's beforeStatus/afterStatus. `skyramp_reuse_code`
|
|
347
|
+
* computes every value in-process (tier-1 detection by selectScopedPoms, the call
|
|
348
|
+
* counts and violations by verifyReuse, the declines from the `// kept inline:`
|
|
349
|
+
* comments in the spec), so asking the agent to read them out of tool text and
|
|
350
|
+
* retype them later would leave the signal prose-mediated — droppable and
|
|
351
|
+
* fabricable, which is the failure this field exists to fix.
|
|
352
|
+
*
|
|
353
|
+
* The counts are RE-DERIVED from the delivered spec rather than read back from
|
|
354
|
+
* state: the execution fix-up can restore `<testFile>.raw.bak` over it afterwards
|
|
355
|
+
* by plain `cp`, which this tool never sees. See rederiveReuseOutcome.
|
|
356
|
+
*
|
|
357
|
+
* Non-UI tests, tests the reuse tool never ran for, and specs whose outcome cannot
|
|
358
|
+
* be re-derived all get nothing — which consumers already treat as "no reuse
|
|
359
|
+
* summary". */
|
|
360
|
+
async function attachReuseOutcome(test, outcomes) {
|
|
361
|
+
if (test.testType !== TestType.UI || !outcomes)
|
|
362
|
+
return test;
|
|
363
|
+
const found = outcomes[path.basename(test.fileName)];
|
|
364
|
+
if (!found)
|
|
365
|
+
return test;
|
|
366
|
+
const reuse = await rederiveReuseOutcome(found);
|
|
367
|
+
return reuse ? { ...test, reuse } : test;
|
|
368
|
+
}
|
|
340
369
|
function deduplicateById(items) {
|
|
341
370
|
const seen = new Set();
|
|
342
371
|
const result = [];
|
|
@@ -558,9 +587,15 @@ export function registerSubmitReportTool(server) {
|
|
|
558
587
|
primaryRepository: fullState?.metadata?.repository,
|
|
559
588
|
});
|
|
560
589
|
if (unbacked.length > 0) {
|
|
590
|
+
// Offer the attribution-correction path explicitly and show what DID change:
|
|
591
|
+
// without it, the cheapest compliant move is a token no-op edit to the
|
|
592
|
+
// mis-claimed file, which pollutes the delivered branch and leaves the report
|
|
593
|
+
// crediting the wrong file (observed in the Ghost rc.pom-3 tester run, D-04).
|
|
594
|
+
const changedList = changedFiles.slice(0, 20).map((f) => ` - ${f}`).join("\n");
|
|
561
595
|
errorResult = toolError(`${unbacked.length} report claim(s) are not backed by any change in the working tree — ` +
|
|
562
|
-
`Testbot will not report file work that hasn't actually been made.
|
|
563
|
-
unbacked.map((u) => ` - ${u}`).join("\n")
|
|
596
|
+
`Testbot will not report file work that hasn't actually been made. If the work WAS done but in a different file, correct the claim's fileName/description to the file you actually edited (see the changed files below) — do NOT make a token edit to the claimed file just to satisfy this check. Otherwise make the edit, or remove the claim. Then resubmit.\n` +
|
|
597
|
+
`Unbacked claims:\n${unbacked.map((u) => ` - ${u}`).join("\n")}\n` +
|
|
598
|
+
`Files with actual working-tree changes:\n${changedList || " (none)"}`);
|
|
564
599
|
return errorResult;
|
|
565
600
|
}
|
|
566
601
|
}
|
|
@@ -576,7 +611,7 @@ export function registerSubmitReportTool(server) {
|
|
|
576
611
|
// generation — downstream scoring scripts don't expect them and fail if
|
|
577
612
|
// they encounter these string fields while traversing the object.
|
|
578
613
|
// Also normalize each item's `repository` (blank → undefined).
|
|
579
|
-
const sanitizedNewTests = dedupedNewTests.map(({ scenarioFile: _sf, traceFile: _tf, frontendTrace: _ft, ...rest }) => normalizeRepository(rest));
|
|
614
|
+
const sanitizedNewTests = await Promise.all(dedupedNewTests.map(({ scenarioFile: _sf, traceFile: _tf, frontendTrace: _ft, ...rest }) => attachReuseOutcome(normalizeRepository(rest), stateData.reuseOutcomes)));
|
|
580
615
|
const report = {
|
|
581
616
|
businessCaseAnalysis: params.businessCaseAnalysis,
|
|
582
617
|
newTestsCreated: sanitizedNewTests,
|
|
@@ -16,11 +16,12 @@ export declare const analyzeChangesInputSchema: {
|
|
|
16
16
|
scope: z.ZodOptional<z.ZodDefault<z.ZodEnum<["full_repo", "branch_diff"]>>>;
|
|
17
17
|
baseBranch: z.ZodOptional<z.ZodString>;
|
|
18
18
|
testDirectory: z.ZodOptional<z.ZodString>;
|
|
19
|
-
topN: z.
|
|
19
|
+
topN: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
20
20
|
maxGenerate: z.ZodOptional<z.ZodNumber>;
|
|
21
21
|
prNumber: z.ZodOptional<z.ZodNumber>;
|
|
22
22
|
repository: z.ZodOptional<z.ZodString>;
|
|
23
23
|
testsRepoDir: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
24
|
+
includeUncommitted: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
24
25
|
};
|
|
25
26
|
export declare const NO_UI_INSTRUCTIONS = "No UI changes detected \u2014 no blueprint capture needed.";
|
|
26
27
|
export declare const NO_RESOLVABLE_URLS_INSTRUCTIONS = "Frontend changes detected but no candidate URLs could be resolved (workspace baseUrl missing or no router files matched). UI recommendations will be source-grounded only.";
|
|
@@ -287,8 +287,8 @@ export const analyzeChangesInputSchema = {
|
|
|
287
287
|
.describe("Directory containing existing tests (auto-detected if omitted)"),
|
|
288
288
|
topN: z
|
|
289
289
|
.number()
|
|
290
|
-
.default(MAX_RECOMMENDATIONS)
|
|
291
290
|
.optional()
|
|
291
|
+
.default(MAX_RECOMMENDATIONS)
|
|
292
292
|
.describe(`Number of ranked test recommendations to generate. Defaults to ${MAX_RECOMMENDATIONS}.`),
|
|
293
293
|
maxGenerate: z
|
|
294
294
|
.number()
|
|
@@ -309,6 +309,11 @@ export const analyzeChangesInputSchema = {
|
|
|
309
309
|
.refine((v) => path.isAbsolute(v), { message: "testsRepoDir must be an absolute path" })
|
|
310
310
|
.optional()
|
|
311
311
|
.describe("Absolute path to a separate test repository clone. When set, existing test discovery scans this directory instead of repositoryPath. Used in cross-repo test delivery mode where tests live in a separate repo."),
|
|
312
|
+
includeUncommitted: z
|
|
313
|
+
.boolean()
|
|
314
|
+
.optional()
|
|
315
|
+
.default(false)
|
|
316
|
+
.describe("When true, diffs the base ref against the working tree (captures uncommitted and unstaged changes). Use for local-dev workflows. Defaults to false (CI mode — committed changes only)."),
|
|
312
317
|
};
|
|
313
318
|
// ── UI blueprint-capture instructions ──
|
|
314
319
|
// Moved here from the former skyramp_ui_analyze_changes pre-flight tool. These
|
|
@@ -391,7 +396,7 @@ export function registerAnalyzeChangesTool(server) {
|
|
|
391
396
|
if (analysisScope === AnalysisScope.CurrentBranchDiff) {
|
|
392
397
|
await sendProgress(10, 100, "Computing branch diff...");
|
|
393
398
|
try {
|
|
394
|
-
diffData = await computeBranchDiff(params.repositoryPath, params.baseBranch);
|
|
399
|
+
diffData = await computeBranchDiff(params.repositoryPath, params.baseBranch, params.includeUncommitted ?? false);
|
|
395
400
|
logger.info("Branch diff computed", {
|
|
396
401
|
currentBranch: diffData.currentBranch,
|
|
397
402
|
baseBranch: diffData.baseBranch,
|
|
@@ -489,7 +494,7 @@ export function registerAnalyzeChangesTool(server) {
|
|
|
489
494
|
const removalCandidateFiles = selectRemovalCandidateFiles(diffData);
|
|
490
495
|
const git = simpleGit(params.repositoryPath);
|
|
491
496
|
const recoveredBaseEndpoints = removalCandidateFiles.length > 0
|
|
492
|
-
? await recoverRemovedEndpointsFromBase(removalCandidateFiles, (file) => git.show([`${diffData.baseBranch}:${file}`]))
|
|
497
|
+
? await recoverRemovedEndpointsFromBase(removalCandidateFiles, (file) => git.show([`${diffData.baseBranch}:${file}`]), params.repositoryPath, diffData.baseBranch)
|
|
493
498
|
: [];
|
|
494
499
|
classifiedEndpoints = classifyEndpointsByChangedFiles(diffData, scannedEndpoints, recoveredBaseEndpoints);
|
|
495
500
|
classifiedEndpoints = {
|
|
@@ -1181,6 +1186,56 @@ export function registerAnalyzeChangesTool(server) {
|
|
|
1181
1186
|
affectedServices: classifiedEndpoints.affectedServices,
|
|
1182
1187
|
summary: "",
|
|
1183
1188
|
} : undefined;
|
|
1189
|
+
// ── Route discovery context for LLM grounding and state persistence ──
|
|
1190
|
+
// fullAnalysis lives only in inMemorySessionStore (for MCP resources
|
|
1191
|
+
// and registerRecommendTestsPrompt). The disk state carries only the
|
|
1192
|
+
// slim fields that downstream tools (health, execute, actions) need.
|
|
1193
|
+
// routerMountContext and candidateRouteFiles are computed here so they
|
|
1194
|
+
// can be persisted to the state file for downstream tools (health, drift).
|
|
1195
|
+
// Without them, analyzeTestHealth would work only off the static catalog
|
|
1196
|
+
// which has wrong paths for nested resources and unsupported frameworks.
|
|
1197
|
+
const routeLikeUnmatchedFiles = [];
|
|
1198
|
+
for (const file of classifiedEndpoints?.unmatchedFiles ?? []) {
|
|
1199
|
+
const routeLike = SOURCE_EXTS.test(file) &&
|
|
1200
|
+
(ROUTE_FILE_PATTERN.test(file) || ROUTE_FILE_BASENAME_PATTERN.test(path.basename(file)));
|
|
1201
|
+
if (routeLike && !(await isGraphQLFile(file, params.repositoryPath))) {
|
|
1202
|
+
routeLikeUnmatchedFiles.push(file);
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
const shouldIncludeCandidateRouteFiles = analysisScope !== AnalysisScope.CurrentBranchDiff ||
|
|
1206
|
+
rawRelatedEndpointCount === 0 ||
|
|
1207
|
+
scannedEndpoints.length === 0 ||
|
|
1208
|
+
routeLikeUnmatchedFiles.length > 0;
|
|
1209
|
+
let candidateRouteFiles;
|
|
1210
|
+
if (shouldIncludeCandidateRouteFiles) {
|
|
1211
|
+
candidateRouteFiles = [];
|
|
1212
|
+
for (const file of findCandidateRouteFiles(params.repositoryPath)) {
|
|
1213
|
+
if (!(await isGraphQLFile(file, params.repositoryPath))) {
|
|
1214
|
+
candidateRouteFiles.push(file);
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
// Write the full diff to a temp file before building state so the path
|
|
1219
|
+
// can be persisted and read by analyzeTestHealthTool for per-line detection.
|
|
1220
|
+
let diffFilePath;
|
|
1221
|
+
if (diffData?.diffContent) {
|
|
1222
|
+
diffFilePath = path.join(os.tmpdir(), `skyramp-diff-${sessionId}.diff`);
|
|
1223
|
+
await fs.promises.writeFile(diffFilePath, diffData.diffContent, { encoding: "utf-8", mode: 0o600 });
|
|
1224
|
+
}
|
|
1225
|
+
const routeDiscovery = {
|
|
1226
|
+
candidateFiles: [
|
|
1227
|
+
...(diffData?.changedFiles ?? []),
|
|
1228
|
+
...(candidateRouteFiles ?? []),
|
|
1229
|
+
].filter((file, index, files) => files.indexOf(file) === index),
|
|
1230
|
+
staticHints: scannedEndpoints.map((ep) => ({
|
|
1231
|
+
path: ep.path,
|
|
1232
|
+
methods: ep.methods,
|
|
1233
|
+
sourceFile: ep.sourceFile,
|
|
1234
|
+
})),
|
|
1235
|
+
openApiPaths: specPaths ? [...specPaths] : [],
|
|
1236
|
+
routerMountContext,
|
|
1237
|
+
...(diffFilePath ? { diffFilePath } : {}),
|
|
1238
|
+
};
|
|
1184
1239
|
const fullAnalysis = {
|
|
1185
1240
|
metadata: {
|
|
1186
1241
|
repositoryName: path.basename(params.repositoryPath),
|
|
@@ -1237,6 +1292,7 @@ export function registerAnalyzeChangesTool(server) {
|
|
|
1237
1292
|
hasCoverageReports: false,
|
|
1238
1293
|
relevantExternalTestPaths,
|
|
1239
1294
|
},
|
|
1295
|
+
routeDiscovery,
|
|
1240
1296
|
...(diffContext ? { branchDiffContext: diffContext } : {}),
|
|
1241
1297
|
};
|
|
1242
1298
|
// Store RecommendationState in memory so it's compatible with skyramp_recommend_tests if needed
|
|
@@ -1246,42 +1302,6 @@ export function registerAnalyzeChangesTool(server) {
|
|
|
1246
1302
|
analysis: fullAnalysis,
|
|
1247
1303
|
};
|
|
1248
1304
|
storeSessionData(sessionId, recommendationState);
|
|
1249
|
-
// ── Step 11: Build UnifiedAnalysisState and save ──
|
|
1250
|
-
// fullAnalysis lives only in inMemorySessionStore (for MCP resources
|
|
1251
|
-
// and registerRecommendTestsPrompt). The disk state carries only the
|
|
1252
|
-
// slim fields that downstream tools (health, execute, actions) need.
|
|
1253
|
-
// routerMountContext and candidateRouteFiles are computed here so they
|
|
1254
|
-
// can be persisted to the state file for downstream tools (health, drift).
|
|
1255
|
-
// Without them, analyzeTestHealth would work only off the static catalog
|
|
1256
|
-
// which has wrong paths for nested resources and unsupported frameworks.
|
|
1257
|
-
const routeLikeUnmatchedFiles = [];
|
|
1258
|
-
for (const file of classifiedEndpoints?.unmatchedFiles ?? []) {
|
|
1259
|
-
const routeLike = SOURCE_EXTS.test(file) &&
|
|
1260
|
-
(ROUTE_FILE_PATTERN.test(file) || ROUTE_FILE_BASENAME_PATTERN.test(path.basename(file)));
|
|
1261
|
-
if (routeLike && !(await isGraphQLFile(file, params.repositoryPath))) {
|
|
1262
|
-
routeLikeUnmatchedFiles.push(file);
|
|
1263
|
-
}
|
|
1264
|
-
}
|
|
1265
|
-
const shouldIncludeCandidateRouteFiles = analysisScope !== AnalysisScope.CurrentBranchDiff ||
|
|
1266
|
-
rawRelatedEndpointCount === 0 ||
|
|
1267
|
-
scannedEndpoints.length === 0 ||
|
|
1268
|
-
routeLikeUnmatchedFiles.length > 0;
|
|
1269
|
-
let candidateRouteFiles;
|
|
1270
|
-
if (shouldIncludeCandidateRouteFiles) {
|
|
1271
|
-
candidateRouteFiles = [];
|
|
1272
|
-
for (const file of findCandidateRouteFiles(params.repositoryPath)) {
|
|
1273
|
-
if (!(await isGraphQLFile(file, params.repositoryPath))) {
|
|
1274
|
-
candidateRouteFiles.push(file);
|
|
1275
|
-
}
|
|
1276
|
-
}
|
|
1277
|
-
}
|
|
1278
|
-
// Write the full diff to a temp file before building state so the path
|
|
1279
|
-
// can be persisted and read by analyzeTestHealthTool for per-line detection.
|
|
1280
|
-
let diffFilePath;
|
|
1281
|
-
if (diffData?.diffContent) {
|
|
1282
|
-
diffFilePath = path.join(os.tmpdir(), `skyramp-diff-${sessionId}.diff`);
|
|
1283
|
-
await fs.promises.writeFile(diffFilePath, diffData.diffContent, { encoding: "utf-8", mode: 0o600 });
|
|
1284
|
-
}
|
|
1285
1305
|
// SKYR-3879 Path B: size-capped raw diff text, persisted (not just the
|
|
1286
1306
|
// file path above) so skyramp_register_test_plan can verify a
|
|
1287
1307
|
// discriminator's changedCodeAnchor occurs verbatim in the diff without
|
|
@@ -1367,7 +1387,7 @@ export function registerAnalyzeChangesTool(server) {
|
|
|
1367
1387
|
source: CandidateSource.SERVER,
|
|
1368
1388
|
candidateId: computeCandidateId(scenario),
|
|
1369
1389
|
}));
|
|
1370
|
-
const plan = selectPlan(serverCandidates, budgetCtx);
|
|
1390
|
+
const plan = selectPlan(serverCandidates, { ...budgetCtx, diffText });
|
|
1371
1391
|
approvedPlan = {
|
|
1372
1392
|
planId: crypto.randomUUID(),
|
|
1373
1393
|
createdAt: new Date().toISOString(),
|
|
@@ -1429,6 +1449,7 @@ export function registerAnalyzeChangesTool(server) {
|
|
|
1429
1449
|
sessionId,
|
|
1430
1450
|
routerMountContext,
|
|
1431
1451
|
candidateRouteFiles,
|
|
1452
|
+
routeDiscovery,
|
|
1432
1453
|
relevantExternalTestPaths,
|
|
1433
1454
|
},
|
|
1434
1455
|
};
|
|
@@ -1526,6 +1547,8 @@ export function registerAnalyzeChangesTool(server) {
|
|
|
1526
1547
|
modifiedEndpointCount: classifiedEndpoints?.changedEndpoints.length ?? 0,
|
|
1527
1548
|
removedEndpointCount: classifiedEndpoints?.removedEndpoints.length ?? 0,
|
|
1528
1549
|
endpointCount: skeletonEndpoints.reduce((acc, ep) => acc + ep.methods.length, 0),
|
|
1550
|
+
newEndpoints: (classifiedEndpoints?.newEndpoints ?? []).flatMap((ep) => ep.methods.map((m) => `${m} ${ep.path}`)),
|
|
1551
|
+
modifiedEndpoints: (classifiedEndpoints?.changedEndpoints ?? []).flatMap((ep) => ep.methods.map((m) => `${m} ${ep.path}`)),
|
|
1529
1552
|
},
|
|
1530
1553
|
// Surface uiContext inline so the testbot prompt can iterate
|
|
1531
1554
|
// candidateUiPages and inspect changedFrontendFiles without
|