@skyramp/mcp 0.3.8 → 0.4.0-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 +1 -1
- package/build/commands/commandLibrary.js +3 -3
- package/build/commands/recommendTestsAndExecuteCommand.d.ts +1 -1
- package/build/commands/recommendTestsAndExecuteCommand.js +35 -20
- package/build/commands/testThisEndpointCommand.js +35 -19
- package/build/index.js +9 -3
- package/build/playwright/blueprintDigest.d.ts +15 -0
- package/build/playwright/blueprintDigest.js +152 -0
- package/build/playwright/blueprintDigestStore.d.ts +31 -0
- package/build/playwright/blueprintDigestStore.js +117 -0
- package/build/playwright/registerPlaywrightTools.js +60 -12
- package/build/playwright/traceRecordingPrompt.js +8 -7
- package/build/prompts/enhance-assertions/sharedAssertionRules.js +9 -8
- package/build/prompts/enhance-assertions/uiAssertionsPrompt.js +24 -2
- package/build/prompts/promptAssets.d.ts +20 -0
- package/build/prompts/promptAssets.js +55 -0
- package/build/prompts/sut-setup/modes/dockerComposePrompt.js +19 -5
- package/build/prompts/test-maintenance/actionsInstructions.d.ts +4 -0
- package/build/prompts/test-maintenance/actionsInstructions.js +14 -2
- package/build/prompts/test-maintenance/drift-analysis-prompt.d.ts +0 -10
- package/build/prompts/test-maintenance/drift-analysis-prompt.js +2 -11
- package/build/prompts/test-maintenance/uiDriftAnalysisSections.js +8 -4
- package/build/prompts/test-recommendation/diffExecutionPlan.d.ts +5 -22
- package/build/prompts/test-recommendation/diffExecutionPlan.js +37 -465
- package/build/prompts/test-recommendation/recommendationSections.d.ts +7 -17
- package/build/prompts/test-recommendation/recommendationSections.js +67 -309
- package/build/prompts/test-recommendation/recommendationShared.d.ts +19 -47
- package/build/prompts/test-recommendation/recommendationShared.js +49 -155
- package/build/prompts/test-recommendation/registerRecommendTestsPrompt.d.ts +0 -5
- package/build/prompts/test-recommendation/registerRecommendTestsPrompt.js +10 -153
- package/build/prompts/test-recommendation/test-recommendation-prompt.d.ts +2 -29
- package/build/prompts/test-recommendation/test-recommendation-prompt.js +32 -457
- package/build/prompts/testbot/planDeclarations.d.ts +6 -0
- package/build/prompts/testbot/planDeclarations.js +9 -0
- package/build/prompts/testbot/testbot-prompts.d.ts +8 -0
- package/build/prompts/testbot/testbot-prompts.js +256 -381
- package/build/recommendation/answers.d.ts +35 -0
- package/build/recommendation/answers.js +96 -0
- package/build/recommendation/registerPlan.d.ts +49 -0
- package/build/recommendation/registerPlan.js +117 -0
- package/build/recommendation/runVerifiers.d.ts +10 -0
- package/build/recommendation/runVerifiers.js +49 -0
- package/build/recommendation/subjectStep.d.ts +42 -0
- package/build/recommendation/subjectStep.js +86 -0
- package/build/recommendation/types.d.ts +163 -0
- package/build/recommendation/types.js +20 -0
- package/build/recommendation/verifierContracts.d.ts +382 -0
- package/build/recommendation/verifierContracts.js +263 -0
- package/build/recommendation/verifiers/changedFile.d.ts +2 -0
- package/build/recommendation/verifiers/changedFile.js +82 -0
- package/build/recommendation/verifiers/citedPath.d.ts +12 -0
- package/build/recommendation/verifiers/citedPath.js +35 -0
- package/build/recommendation/verifiers/coverage.d.ts +7 -0
- package/build/recommendation/verifiers/coverage.js +617 -0
- package/build/recommendation/verifiers/deliveredMatchesPlan.d.ts +11 -0
- package/build/recommendation/verifiers/deliveredMatchesPlan.js +33 -0
- package/build/recommendation/verifiers/endpointGrounded.d.ts +17 -0
- package/build/recommendation/verifiers/endpointGrounded.js +128 -0
- package/build/recommendation/verifiers/existingCoverage.d.ts +6 -0
- package/build/recommendation/verifiers/existingCoverage.js +51 -0
- package/build/recommendation/verifiers/expectedOutcome.d.ts +31 -0
- package/build/recommendation/verifiers/expectedOutcome.js +105 -0
- package/build/recommendation/verifiers/removedElementGuarded.d.ts +2 -0
- package/build/recommendation/verifiers/removedElementGuarded.js +57 -0
- package/build/recommendation/verifiers/reportedCategory.d.ts +26 -0
- package/build/recommendation/verifiers/reportedCategory.js +84 -0
- package/build/recommendation/verifiers/screenRoute.d.ts +10 -0
- package/build/recommendation/verifiers/screenRoute.js +118 -0
- package/build/recommendation/verifiers/statedDifference.d.ts +6 -0
- package/build/recommendation/verifiers/statedDifference.js +140 -0
- package/build/recommendation/verifiers/uiElementGrounded.d.ts +7 -0
- package/build/recommendation/verifiers/uiElementGrounded.js +318 -0
- package/build/resources/analysisResources.js +1 -114
- package/build/resources/testbotResource.js +23 -13
- package/build/services/ModularizationService.js +2 -1
- package/build/services/TestDiscoveryService.d.ts +3 -72
- package/build/services/TestDiscoveryService.js +10 -303
- package/build/services/containerEnv.d.ts +1 -1
- package/build/services/containerEnv.js +12 -0
- package/build/skills/fixTestImportErrorsSkill.d.ts +13 -0
- package/build/skills/fixTestImportErrorsSkill.js +20 -0
- package/build/toolNames.d.ts +1 -0
- package/build/toolNames.js +1 -0
- package/build/tools/code-refactor/enhanceAssertionsTool.js +3 -3
- package/build/tools/code-refactor/modularizationTool.js +2 -1
- package/build/tools/executeSkyrampTestTool.d.ts +80 -0
- package/build/tools/executeSkyrampTestTool.js +246 -19
- package/build/tools/generate-tests/generateBatchScenarioRestTool.js +6 -0
- package/build/tools/generate-tests/generateContractRestTool.js +3 -3
- package/build/tools/generate-tests/planGuard.d.ts +2 -2
- package/build/tools/generate-tests/planGuard.js +78 -18
- package/build/tools/one-click/oneClickTool.d.ts +0 -1
- package/build/tools/one-click/oneClickTool.js +0 -5
- package/build/tools/submitReportTool.d.ts +48 -42
- package/build/tools/submitReportTool.js +576 -193
- package/build/tools/test-management/actionsTool.js +72 -4
- package/build/tools/test-management/analyzeChangesTool.d.ts +144 -48
- package/build/tools/test-management/analyzeChangesTool.js +212 -1219
- package/build/tools/test-management/analyzeTestHealthTool.js +13 -24
- package/build/tools/test-management/index.d.ts +1 -0
- package/build/tools/test-management/index.js +1 -0
- package/build/tools/test-management/registerTestPlanTool.d.ts +795 -172
- package/build/tools/test-management/registerTestPlanTool.js +609 -542
- package/build/tools/test-management/resolveScreenTool.d.ts +75 -0
- package/build/tools/test-management/resolveScreenTool.js +289 -0
- package/build/types/BlueprintDigest.d.ts +34 -0
- package/build/types/BlueprintDigest.js +1 -0
- package/build/types/RepositoryAnalysis.d.ts +20 -1559
- package/build/types/RepositoryAnalysis.js +2 -58
- package/build/types/StepMethod.d.ts +40 -0
- package/build/types/StepMethod.js +77 -0
- package/build/types/TestAnalysis.d.ts +12 -0
- package/build/types/TestExecution.d.ts +4 -0
- package/build/types/TestRecommendation.d.ts +24 -24
- package/build/types/TestRecommendation.js +91 -89
- package/build/types/TestbotPromptOptions.d.ts +0 -4
- package/build/types/TestbotReport.d.ts +64 -2
- package/build/utils/AnalysisStateManager.d.ts +79 -113
- package/build/utils/AnalysisStateManager.js +147 -57
- package/build/utils/assertion-verify/api-shared-lints.js +1 -1
- package/build/utils/assertion-verify/metrics.js +85 -36
- package/build/utils/assertion-verify/ui-lints.d.ts +0 -5
- package/build/utils/assertion-verify/ui-lints.js +32 -0
- package/build/utils/branchDiff.d.ts +63 -31
- package/build/utils/branchDiff.js +242 -94
- package/build/utils/containedPath.d.ts +18 -0
- package/build/utils/containedPath.js +73 -0
- package/build/utils/dartRouteExtractor.d.ts +18 -34
- package/build/utils/dartRouteExtractor.js +101 -173
- package/build/utils/featureFlags.d.ts +12 -0
- package/build/utils/featureFlags.js +14 -0
- package/build/utils/frontendSelectors.d.ts +48 -27
- package/build/utils/frontendSelectors.js +241 -80
- package/build/utils/pathMatching.d.ts +2 -4
- package/build/utils/pathMatching.js +2 -4
- package/build/utils/planMatchKeys.d.ts +38 -47
- package/build/utils/planMatchKeys.js +143 -81
- package/build/utils/rebaselineSnapshots.d.ts +24 -0
- package/build/utils/rebaselineSnapshots.js +65 -0
- package/build/utils/removedUiElements.d.ts +22 -0
- package/build/utils/removedUiElements.js +106 -0
- package/build/utils/reportVerification.d.ts +2 -6
- package/build/utils/reportVerification.js +61 -2
- package/build/utils/screenRoutes.d.ts +66 -0
- package/build/utils/screenRoutes.js +727 -0
- package/build/utils/sourceRouteExtractor.js +320 -112
- package/build/utils/testFileClassification.d.ts +11 -2
- package/build/utils/testFileClassification.js +44 -2
- package/build/utils/testFixtures.d.ts +5 -0
- package/build/utils/testFixtures.js +13 -0
- package/build/utils/utils.d.ts +0 -1
- package/build/utils/utils.js +0 -11
- package/build/utils/versions.d.ts +3 -3
- package/build/utils/versions.js +1 -1
- package/build/workspace/workspace.d.ts +12 -12
- package/node_modules/playwright/lib/mcp/skyramp/assertHiddenTool.js +56 -0
- package/node_modules/playwright/lib/mcp/skyramp/assertTool.js +2 -1
- package/node_modules/playwright/lib/mcp/skyramp/loadTraceTool.js +10 -0
- package/node_modules/playwright/lib/mcp/skyramp/skyRampImport.js +4 -1
- package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +160 -1
- package/node_modules/playwright/lib/mcp/test/skyRampExport.js +4 -2
- package/node_modules/playwright/node_modules/playwright-core/lib/server/codegen/skyramp/jsonlReader.js +1 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/recorderSignalProcessor.js +2 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder.js +5 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{index.-Id052Lr.js → index.B7KbSQcC.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html +1 -1
- package/node_modules/playwright/node_modules/playwright-core/package.json +1 -1
- package/node_modules/playwright/node_modules/playwright-core/src/server/codegen/skyramp/jsonlReader.ts +1 -1
- package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/recorderSignalProcessor.ts +7 -0
- package/node_modules/playwright/node_modules/playwright-core/src/server/recorder.ts +6 -1
- package/node_modules/playwright/package.json +1 -1
- package/package.json +4 -3
- package/plugin/.claude-plugin/plugin.json +8 -0
- package/plugin/plugin.json +6 -0
- package/plugin/prompts/declaring-a-plan.md +20 -0
- package/plugin/prompts/generate-tests/context-fetching.md +4 -0
- package/plugin/prompts/generate-tests/execution-plan.md +63 -0
- package/plugin/prompts/generate-tests/generation.md +108 -0
- package/plugin/prompts/generate-tests/path-parameters.md +1 -0
- package/plugin/prompts/generate-tests/reasoning-protocol.md +17 -0
- package/plugin/prompts/generate-tests/tool-workflow-variants.md +61 -0
- package/plugin/prompts/generate-tests/tool-workflows.md +65 -0
- package/plugin/prompts/plan-tests.md +42 -0
- package/plugin/prompts/testbot-task1.md +82 -0
- package/plugin/skills/fix-test-import-errors/SKILL.md +98 -0
- package/build/prompts/test-recommendation/analysisOutputPrompt.d.ts +0 -84
- package/build/prompts/test-recommendation/analysisOutputPrompt.js +0 -369
- package/build/prompts/test-recommendation/fullRepoCatalog.d.ts +0 -7
- package/build/prompts/test-recommendation/fullRepoCatalog.js +0 -283
- package/build/prompts/test-recommendation/scopeAssessment.d.ts +0 -81
- package/build/prompts/test-recommendation/scopeAssessment.js +0 -359
- package/build/recommendation/budgeters/diversityBalancedBudgeter.d.ts +0 -7
- package/build/recommendation/budgeters/diversityBalancedBudgeter.js +0 -105
- package/build/recommendation/budgeters/fixedNBudgeter.d.ts +0 -7
- package/build/recommendation/budgeters/fixedNBudgeter.js +0 -11
- package/build/recommendation/budgeters/shared.d.ts +0 -32
- package/build/recommendation/budgeters/shared.js +0 -246
- package/build/recommendation/discriminators.d.ts +0 -37
- package/build/recommendation/discriminators.js +0 -379
- package/build/recommendation/diversity.d.ts +0 -47
- package/build/recommendation/diversity.js +0 -101
- package/build/recommendation/planRanker.d.ts +0 -65
- package/build/recommendation/planRanker.js +0 -83
- package/build/recommendation/testFixtures.d.ts +0 -25
- package/build/recommendation/testFixtures.js +0 -45
- package/build/types/FrontendIntegration.d.ts +0 -28
- package/build/types/FrontendIntegration.js +0 -22
- package/build/types/Recommendation.d.ts +0 -146
- package/build/types/Recommendation.js +0 -74
- package/build/utils/changedRoutes.d.ts +0 -29
- package/build/utils/changedRoutes.js +0 -87
- package/build/utils/frontendIntegration.d.ts +0 -9
- package/build/utils/frontendIntegration.js +0 -243
- package/build/utils/importerHop.d.ts +0 -135
- package/build/utils/importerHop.js +0 -489
- package/build/utils/pathAffinityClassification.d.ts +0 -49
- package/build/utils/pathAffinityClassification.js +0 -180
- package/build/utils/pythonMountPrefixes.d.ts +0 -25
- package/build/utils/pythonMountPrefixes.js +0 -347
- package/build/utils/repoScanner.d.ts +0 -34
- package/build/utils/repoScanner.js +0 -300
- package/build/utils/routeParsers.d.ts +0 -95
- package/build/utils/routeParsers.js +0 -951
- package/build/utils/scenarioDrafting.d.ts +0 -92
- package/build/utils/scenarioDrafting.js +0 -951
- package/build/utils/subjectEndpoints.d.ts +0 -19
- package/build/utils/subjectEndpoints.js +0 -98
- package/build/utils/uiPageEnumerator.d.ts +0 -172
- package/build/utils/uiPageEnumerator.js +0 -474
|
@@ -1,489 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Importer-hop endpoint classification — SKYR-3855 (backend half).
|
|
3
|
-
*
|
|
4
|
-
* `classifyEndpointsByChangedFiles` (routeParsers.ts) matches changed files to
|
|
5
|
-
* scanned endpoints by exact `sourceFile` equality. A changed DTO/service/model
|
|
6
|
-
* file — a non-route backend file that a route handler *imports* rather than
|
|
7
|
-
* declares routes in — matches no endpoint and lands in `unmatchedFiles`. That
|
|
8
|
-
* silently drops the change from `diffContext.modifiedEndpoints`, which makes
|
|
9
|
-
* every drafted scenario classify as `novelty: existing` and the ranked plan
|
|
10
|
-
* never mentions the actual change (see testbot run 27468308525 — immich PR
|
|
11
|
-
* adding a derived `initials` field to `user.dto.ts`).
|
|
12
|
-
*
|
|
13
|
-
* This module "hops" one import edge: for each unmatched backend file, it
|
|
14
|
-
* finds already-scanned endpoints whose source file textually imports the
|
|
15
|
-
* changed file (via the same path-reference signature used by
|
|
16
|
-
* `frontendIntegration.ts`), then narrows to endpoints that plausibly belong
|
|
17
|
-
* to the same REST resource as the changed file (the "resource-affinity"
|
|
18
|
-
* filter) — critical containment so a widely-imported DTO (e.g. immich's
|
|
19
|
-
* `user.dto.ts`, imported by nearly every controller) doesn't flag the whole
|
|
20
|
-
* endpoint catalog. If no candidate survives the affinity filter, the file is
|
|
21
|
-
* left unmapped — conservative by design, matching the pre-existing
|
|
22
|
-
* "unmatched" behavior rather than guessing.
|
|
23
|
-
*
|
|
24
|
-
* Every entry point here is fail-safe: any error for a given file is logged
|
|
25
|
-
* and that file is simply skipped, never thrown to the caller.
|
|
26
|
-
*
|
|
27
|
-
* KNOWN LIMITATIONS (conscious first-cut scope — both no-op conservatively,
|
|
28
|
-
* leaving classification exactly as it was pre-hop):
|
|
29
|
-
* 1. Import edge is TS/JS path-quoted imports only. Python/Go/Java dotted or
|
|
30
|
-
* package imports produce zero signature hits (e.g. FastAPI's
|
|
31
|
-
* `schemas.actions.DeploymentCreate`). Language-aware signatures tracked
|
|
32
|
-
* in SKYR-3921's lane. Symbol-based affinity (see point 2) widens which
|
|
33
|
-
* candidates PASS the filter once an edge fires — it does not create new
|
|
34
|
-
* import-edge candidates, so this limitation stands on its own for
|
|
35
|
-
* Python/Go/Java until SKYR-3921/3929 lands.
|
|
36
|
-
* 2. MITIGATED: affinity used to be FILENAME-only, so generic multi-DTO
|
|
37
|
-
* modules defeated it even when the edge fired: `schemas/actions.py`
|
|
38
|
-
* holding `DeploymentCreate` derived token "action", never matching
|
|
39
|
-
* `/deployments`. `deriveResourceTokensForFile` now additionally derives
|
|
40
|
-
* tokens from the changed SYMBOL names in the diff hunk
|
|
41
|
-
* (`class DeploymentCreate` -> "deployment"), and the affinity filter
|
|
42
|
-
* passes if an endpoint matches ANY derived token. Shared with the
|
|
43
|
-
* maintenance path — SKYR-3924 consumes `deriveResourceTokensForFile`
|
|
44
|
-
* directly for its own "changed DTO -> affected resource" signal, so the
|
|
45
|
-
* derivation lives here once rather than being duplicated.
|
|
46
|
-
*
|
|
47
|
-
* SKYR-3857 consolidation: pathAffinityClassification.ts's fallback used to
|
|
48
|
-
* carry its own, basename-only `deriveFileResourceToken` derivation. It now
|
|
49
|
-
* consumes `deriveResourceTokensForFile` directly (gaining symbol-based
|
|
50
|
-
* affinity for route files for free), so this is the ONLY filename/symbol to
|
|
51
|
-
* resource-token derivation in the codebase. `deriveResourceToken` here
|
|
52
|
-
* additionally strips the route-ish filename suffixes ("-router"/"-routes"/
|
|
53
|
-
* ".controller"/etc.) that module used to handle on its own, and
|
|
54
|
-
* `GENERIC_RESOURCE_TOKENS` absorbed its generic-token vocabulary.
|
|
55
|
-
*/
|
|
56
|
-
import * as fs from "fs";
|
|
57
|
-
import * as path from "path";
|
|
58
|
-
import { logger } from "./logger.js";
|
|
59
|
-
import { extractResourceFromPath, isParamSegment, isVersionLikeSegment, SKIP_PATH_SEGMENTS } from "./routeParsers.js";
|
|
60
|
-
import { sliceAddedLinesByFile } from "./branchDiff.js";
|
|
61
|
-
import { buildPathSignatures, importsIndexModule, isIndexModule } from "./pathSignatures.js";
|
|
62
|
-
import { singularize } from "./pluralization.js";
|
|
63
|
-
import { isFrontendFile, isTestFile } from "../prompts/test-recommendation/scopeAssessment.js";
|
|
64
|
-
const MAX_FILE_SIZE_BYTES = 512 * 1024;
|
|
65
|
-
// Mirrors the small `SOURCE_EXTS` copies already living in routeParsers.ts and
|
|
66
|
-
// analyzeChangesTool.ts — kept local rather than shared so each domain stays
|
|
67
|
-
// independently tunable (same precedent as frontendIntegration.ts's own
|
|
68
|
-
// FRONTEND_SOURCE_EXT_PATTERN, which is deliberately not shared either).
|
|
69
|
-
const SOURCE_EXTS = /\.(ts|tsx|js|jsx|py|java|kt|go|rb|php|rs|cs|ex|exs)$/;
|
|
70
|
-
// Common backend "framework layer" suffixes stripped before deriving a resource
|
|
71
|
-
// token from a source file name — e.g. "user.dto.ts" -> "user",
|
|
72
|
-
// "order.service.ts" -> "order", "UsersRepository.ts" handled via extension only.
|
|
73
|
-
const FRAMEWORK_SUFFIX_RE = /\.(dto|service|controller|repository|entity|module|schema|model|resolver|gateway|guard|middleware|interceptor|mapper|serializer)$/i;
|
|
74
|
-
// Route-ish suffix forms, merged in from pathAffinityClassification.ts's former
|
|
75
|
-
// `ROUTE_FILE_SUFFIX_RE` during the SKYR-3857 derivation consolidation — e.g.
|
|
76
|
-
// "secret-router.ts" -> "secret", "orders-routes.js" -> "order". Hyphen, dot,
|
|
77
|
-
// or underscore delimited, since route-file naming conventions vary by
|
|
78
|
-
// framework (Fastify favors hyphens, Nest/Angular favor dots). Applied after
|
|
79
|
-
// FRAMEWORK_SUFFIX_RE in `deriveResourceToken` below; both regexes are
|
|
80
|
-
// end-anchored, so together they only ever strip within the final path
|
|
81
|
-
// segment — directory names are never touched.
|
|
82
|
-
const ROUTE_SUFFIX_RE = /[-_.](?:router|routes?|controllers?|handlers?|endpoints?|resource|resources|api)$/i;
|
|
83
|
-
// Resource tokens too generic to safely anchor the affinity filter — matching on
|
|
84
|
-
// these would defeat its purpose (e.g. a shared base.service.ts imported everywhere).
|
|
85
|
-
const GENERIC_RESOURCE_TOKENS = new Set([
|
|
86
|
-
"base", "common", "shared", "abstract", "index", "types", "utils", "helpers",
|
|
87
|
-
"constants", "config", "settings", "app", "main", "server", "core",
|
|
88
|
-
// Merged from pathAffinityClassification.ts's former GENERIC_RESOURCE_TOKENS
|
|
89
|
-
// (SKYR-3857 derivation consolidation) — route-file vocabulary that is
|
|
90
|
-
// structural, not resource-identifying, e.g. a bare "router.ts" or "api.ts".
|
|
91
|
-
"route", "router", "routes", "controller", "controllers", "handler",
|
|
92
|
-
"handlers", "endpoint", "endpoints", "api", "service", "gateway",
|
|
93
|
-
"resolver", "middleware", "resource", "resources",
|
|
94
|
-
]);
|
|
95
|
-
// Sibling to GENERIC_RESOURCE_TOKENS, for words split out of a declared SYMBOL
|
|
96
|
-
// name rather than a filename — DTO/action/framework vocabulary that appears
|
|
97
|
-
// inside class/type names constantly (e.g. "DeploymentCreate", "UserRequest")
|
|
98
|
-
// but never identifies the resource itself. Checked in addition to
|
|
99
|
-
// GENERIC_RESOURCE_TOKENS (a word generic enough to reject as a filename token
|
|
100
|
-
// is equally generic as a symbol-word token).
|
|
101
|
-
const GENERIC_SYMBOL_TOKENS = new Set([
|
|
102
|
-
"create", "update", "delete", "request", "response", "dto", "schema",
|
|
103
|
-
"input", "output", "type", "base", "params", "options", "config", "error",
|
|
104
|
-
]);
|
|
105
|
-
// Caps the token list `deriveResourceTokensForFile` returns so a single huge
|
|
106
|
-
// schema/module file (many declared symbols) can't blow up the affinity
|
|
107
|
-
// filter into matching nearly everything.
|
|
108
|
-
const MAX_RESOURCE_TOKENS = 8;
|
|
109
|
-
// Declared-symbol forms recognized on an ADDED diff line (leading "+"), in
|
|
110
|
-
// the languages importer-hop already targets. Best-effort regex, not a
|
|
111
|
-
// parser — mirrors the precedent set by TS_FIELD_RE/PY_FIELD_RE below. Method
|
|
112
|
-
// and function declarations never match any of these (no "class"/"interface"/
|
|
113
|
-
// "type"/"enum" keyword), so `getUsers(): User[] {` is correctly ignored.
|
|
114
|
-
const TS_SYMBOL_DECL_RES = [
|
|
115
|
-
/^\+\s*(?:export\s+)?(?:default\s+)?(?:abstract\s+)?class\s+([A-Za-z_]\w*)/,
|
|
116
|
-
/^\+\s*(?:export\s+)?interface\s+([A-Za-z_]\w*)/,
|
|
117
|
-
/^\+\s*(?:export\s+)?type\s+([A-Za-z_]\w*)\s*=/,
|
|
118
|
-
/^\+\s*(?:export\s+)?(?:const\s+)?enum\s+([A-Za-z_]\w*)/,
|
|
119
|
-
// "schema-ish" export const — e.g. a zod DTO: `export const UserSchema = z.object({...})`.
|
|
120
|
-
/^\+\s*export\s+const\s+([A-Za-z_]\w*)\s*=\s*z\./,
|
|
121
|
-
];
|
|
122
|
-
const PY_SYMBOL_DECL_RE = /^\+\s*class\s+([A-Za-z_]\w*)/;
|
|
123
|
-
/**
|
|
124
|
-
* Extracts declared class/interface/type/enum/schema symbol names from an
|
|
125
|
-
* array of raw added diff lines (each still prefixed with the unified-diff
|
|
126
|
-
* `+` marker). Sibling to `extractFieldNamesFromAddedLines` — same
|
|
127
|
-
* best-effort, non-parser contract — but looks for type-level declarations
|
|
128
|
-
* instead of field/property assignments.
|
|
129
|
-
*/
|
|
130
|
-
function extractDeclaredSymbolsFromAddedLines(lines) {
|
|
131
|
-
const symbols = new Set();
|
|
132
|
-
for (const line of lines) {
|
|
133
|
-
if (line.startsWith("++"))
|
|
134
|
-
continue; // "+++ b/file" diff header line
|
|
135
|
-
let name;
|
|
136
|
-
for (const re of TS_SYMBOL_DECL_RES) {
|
|
137
|
-
const match = re.exec(line);
|
|
138
|
-
if (match) {
|
|
139
|
-
name = match[1];
|
|
140
|
-
break;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
if (!name)
|
|
144
|
-
name = PY_SYMBOL_DECL_RE.exec(line)?.[1];
|
|
145
|
-
if (name)
|
|
146
|
-
symbols.add(name);
|
|
147
|
-
}
|
|
148
|
-
return [...symbols];
|
|
149
|
-
}
|
|
150
|
-
/** Splits a CamelCase/PascalCase/snake_case symbol name into lowercase words —
|
|
151
|
-
* "DeploymentCreate" -> ["Deployment", "Create"], "secret_version" -> ["secret", "version"],
|
|
152
|
-
* "HTTPServer" -> ["HTTP", "Server"] (acronym-aware boundary). */
|
|
153
|
-
function splitSymbolIntoWords(symbol) {
|
|
154
|
-
const withBoundaries = symbol
|
|
155
|
-
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
|
156
|
-
.replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2")
|
|
157
|
-
.replace(/_/g, " ");
|
|
158
|
-
return withBoundaries.split(/\s+/).filter(Boolean);
|
|
159
|
-
}
|
|
160
|
-
/** Splits a declared symbol into filtered, singularized resource-token
|
|
161
|
-
* candidates — drops generic/framework words and tokens too short (<=2
|
|
162
|
-
* chars) to safely anchor affinity. */
|
|
163
|
-
function tokensFromSymbol(symbol) {
|
|
164
|
-
return splitSymbolIntoWords(symbol)
|
|
165
|
-
.map((word) => singularize(word.toLowerCase()))
|
|
166
|
-
.filter((token) => token.length > 2 &&
|
|
167
|
-
!GENERIC_SYMBOL_TOKENS.has(token) &&
|
|
168
|
-
!GENERIC_RESOURCE_TOKENS.has(token));
|
|
169
|
-
}
|
|
170
|
-
/**
|
|
171
|
-
* Derives a singular, lowercase "resource token" from a backend source file
|
|
172
|
-
* path — e.g. "src/dtos/user.dto.ts" -> "user",
|
|
173
|
-
* "modules/orders/order.service.ts" -> "order". Strips the file extension and
|
|
174
|
-
* a trailing framework-layer suffix, then reuses `extractResourceFromPath`'s
|
|
175
|
-
* "last meaningful path segment" rule by treating the remaining slash-joined
|
|
176
|
-
* path as if it were a URL path.
|
|
177
|
-
*/
|
|
178
|
-
export function deriveResourceToken(file) {
|
|
179
|
-
const normalized = file.replace(/\\/g, "/");
|
|
180
|
-
const ext = path.extname(normalized);
|
|
181
|
-
const withoutExt = ext ? normalized.slice(0, -ext.length) : normalized;
|
|
182
|
-
const withoutSuffix = withoutExt.replace(FRAMEWORK_SUFFIX_RE, "").replace(ROUTE_SUFFIX_RE, "");
|
|
183
|
-
return singularize(extractResourceFromPath(withoutSuffix).toLowerCase());
|
|
184
|
-
}
|
|
185
|
-
/**
|
|
186
|
-
* Derives ALL plausible resource-affinity tokens for a changed file: the
|
|
187
|
-
* filename-derived token from `deriveResourceToken` (when it isn't generic),
|
|
188
|
-
* plus tokens split out of any class/interface/type/enum/schema symbols
|
|
189
|
-
* declared on the file's ADDED diff lines (when `diffHunk` is supplied).
|
|
190
|
-
*
|
|
191
|
-
* This is the sharper anchor requested on PR #588: a generic multi-DTO
|
|
192
|
-
* module (`schemas/actions.py` holding `class DeploymentCreate`, `class
|
|
193
|
-
* FlowCreate`, ...) has no single filename->resource mapping, but the
|
|
194
|
-
* declared symbol name IS the resource — `DeploymentCreate` -> "deployment".
|
|
195
|
-
* Same problem exists for TS (`types/common.ts` holding many interfaces).
|
|
196
|
-
*
|
|
197
|
-
* `diffHunk` is expected to be the file's own added-line slice (e.g. one
|
|
198
|
-
* entry's lines from `sliceAddedLinesByFile`, joined with "\n") — this
|
|
199
|
-
* function does not re-slice a full multi-file diff itself, to avoid
|
|
200
|
-
* duplicating that walking logic in two places.
|
|
201
|
-
*
|
|
202
|
-
* Deduped; capped at `MAX_RESOURCE_TOKENS` so a huge schema file with many
|
|
203
|
-
* declared symbols can't explode the affinity filter into a near-match-all.
|
|
204
|
-
* With no `diffHunk` (or no symbols found), the result is exactly
|
|
205
|
-
* `[deriveResourceToken(file)]` (or `[]` if that's generic/unknown) —
|
|
206
|
-
* identical to today's filename-only behavior.
|
|
207
|
-
*/
|
|
208
|
-
/**
|
|
209
|
-
* Declared symbol names changed in a unified diff — from added-line declarations
|
|
210
|
-
* AND from the hunk-header enclosing type (so a field-only edit to an existing class,
|
|
211
|
-
* e.g. `DeploymentCreate.work_pool_name` → required, still yields `DeploymentCreate`).
|
|
212
|
-
* Returns raw names, used both for resource-token derivation (via `tokensFromSymbol`)
|
|
213
|
-
* and for content-grep matching against test bodies (SKYR-3924).
|
|
214
|
-
*/
|
|
215
|
-
export function extractChangedSymbols(diffHunk) {
|
|
216
|
-
const lines = diffHunk.split("\n");
|
|
217
|
-
const symbols = new Set(extractDeclaredSymbolsFromAddedLines(lines.filter((line) => line.startsWith("+"))));
|
|
218
|
-
const HUNK_CTX_DECL = /^@@.*@@.*\b(?:class|struct|interface|type|enum)\s+([A-Za-z_]\w*)/;
|
|
219
|
-
for (const line of lines) {
|
|
220
|
-
if (!line.startsWith("@@"))
|
|
221
|
-
continue;
|
|
222
|
-
const m = HUNK_CTX_DECL.exec(line);
|
|
223
|
-
if (m)
|
|
224
|
-
symbols.add(m[1]);
|
|
225
|
-
}
|
|
226
|
-
return [...symbols];
|
|
227
|
-
}
|
|
228
|
-
export function deriveResourceTokensForFile(file, diffHunk) {
|
|
229
|
-
const tokens = [];
|
|
230
|
-
const seen = new Set();
|
|
231
|
-
const addToken = (token) => {
|
|
232
|
-
if (!token || seen.has(token))
|
|
233
|
-
return;
|
|
234
|
-
seen.add(token);
|
|
235
|
-
tokens.push(token);
|
|
236
|
-
};
|
|
237
|
-
const filenameToken = deriveResourceToken(file);
|
|
238
|
-
if (filenameToken && filenameToken !== "unknown" && !GENERIC_RESOURCE_TOKENS.has(filenameToken)) {
|
|
239
|
-
addToken(filenameToken);
|
|
240
|
-
}
|
|
241
|
-
if (diffHunk) {
|
|
242
|
-
for (const symbol of extractChangedSymbols(diffHunk)) {
|
|
243
|
-
for (const token of tokensFromSymbol(symbol))
|
|
244
|
-
addToken(token);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
return tokens.slice(0, MAX_RESOURCE_TOKENS);
|
|
248
|
-
}
|
|
249
|
-
/**
|
|
250
|
-
* All singular resource tokens implied by an endpoint path, used for affinity
|
|
251
|
-
* matching against a `deriveResourceToken` result. Shared by the importer-hop
|
|
252
|
-
* and path-affinity fallbacks (SKYR-3857) — the ONLY endpoint-path to
|
|
253
|
-
* resource-token derivation in the codebase, mirroring how
|
|
254
|
-
* `deriveResourceTokensForFile` is the only file-side one:
|
|
255
|
-
* - the primary (first non-skip, non-param, non-version) path segment —
|
|
256
|
-
* catches literal action-style segments like "/users/me", which
|
|
257
|
-
* `extractResourceFromPath`'s "last segment" rule would otherwise resolve
|
|
258
|
-
* to "me" instead of "users". Version-like segments are excluded via
|
|
259
|
-
* `isVersionLikeSegment` — `SKIP_PATH_SEGMENTS` covers "api"/"public" but
|
|
260
|
-
* only v1-v3, so without it any endpoint versioned beyond v3 would derive
|
|
261
|
-
* a nonsense token ("v4") as its resource.
|
|
262
|
-
* - `extractResourceFromPath`'s own result, split on "_" for the sub-resource
|
|
263
|
-
* composite form ("orders_items" -> "order", "item"). That function only
|
|
264
|
-
* recognizes OpenAPI-style "{param}" segments (its dedup-key callers see
|
|
265
|
-
* normalized paths), so ":id"/"[id]"-style params can flow through as its
|
|
266
|
-
* result — filtered here rather than widening a function whose other
|
|
267
|
-
* callers don't need it.
|
|
268
|
-
*/
|
|
269
|
-
export function endpointResourceTokens(endpointPath) {
|
|
270
|
-
const tokens = new Set();
|
|
271
|
-
const segments = endpointPath.split("/").filter(Boolean);
|
|
272
|
-
const primary = segments.find((s) => !isParamSegment(s) && !SKIP_PATH_SEGMENTS.has(s.toLowerCase()) && !isVersionLikeSegment(s));
|
|
273
|
-
if (primary)
|
|
274
|
-
tokens.add(singularize(primary.toLowerCase()));
|
|
275
|
-
const composite = extractResourceFromPath(endpointPath).toLowerCase();
|
|
276
|
-
for (const part of composite.split("_")) {
|
|
277
|
-
if (part && !isParamSegment(part))
|
|
278
|
-
tokens.add(singularize(part));
|
|
279
|
-
}
|
|
280
|
-
return tokens;
|
|
281
|
-
}
|
|
282
|
-
/**
|
|
283
|
-
* Maps unmatched changed files (from `classifyEndpointsByChangedFiles`) to
|
|
284
|
-
* endpoints that import them, when the import target and the endpoint share
|
|
285
|
-
* the same REST resource.
|
|
286
|
-
*
|
|
287
|
-
* `alreadyClassifiedEndpoints` (typically `changedEndpoints` + `newEndpoints`
|
|
288
|
-
* from the same classification pass) is used purely for dedup — an endpoint
|
|
289
|
-
* already classified directly is not duplicated into the returned `endpoints`
|
|
290
|
-
* list, though the mapping is still established (harmless to skip re-adding
|
|
291
|
-
* something already present).
|
|
292
|
-
*
|
|
293
|
-
* `diffContent`, when supplied, is the full unified diff — used to derive
|
|
294
|
-
* per-file added-line hunks for `deriveResourceTokensForFile`'s symbol-based
|
|
295
|
-
* affinity anchors (see module doc, limitation 2). Without it, affinity falls
|
|
296
|
-
* back to the filename-only token, identical to pre-symbol-affinity behavior.
|
|
297
|
-
*/
|
|
298
|
-
export function mapUnmatchedFilesToImporterEndpoints(repositoryPath, unmatchedFiles, scannedEndpoints, alreadyClassifiedEndpoints = [], diffContent) {
|
|
299
|
-
const endpoints = [];
|
|
300
|
-
const byFile = {};
|
|
301
|
-
const alreadyKeys = new Set(alreadyClassifiedEndpoints.map((ep) => `${ep.sourceFile}::${ep.path}`));
|
|
302
|
-
const addedKeys = new Set();
|
|
303
|
-
const addedLinesByFile = diffContent ? sliceAddedLinesByFile(diffContent) : undefined;
|
|
304
|
-
// Cache file contents across unmatched files — multiple changed files may be
|
|
305
|
-
// imported by the same controller/service, and controllers can be large.
|
|
306
|
-
const contentCache = new Map();
|
|
307
|
-
const readCached = (sourceFile) => {
|
|
308
|
-
if (contentCache.has(sourceFile))
|
|
309
|
-
return contentCache.get(sourceFile);
|
|
310
|
-
let content = null;
|
|
311
|
-
try {
|
|
312
|
-
const abs = path.isAbsolute(sourceFile) ? sourceFile : path.join(repositoryPath, sourceFile);
|
|
313
|
-
const stat = fs.statSync(abs);
|
|
314
|
-
if (stat.size <= MAX_FILE_SIZE_BYTES) {
|
|
315
|
-
content = fs.readFileSync(abs, "utf-8");
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
catch {
|
|
319
|
-
content = null; // unreadable / too large — tolerate, treat as no content to match against
|
|
320
|
-
}
|
|
321
|
-
contentCache.set(sourceFile, content);
|
|
322
|
-
return content;
|
|
323
|
-
};
|
|
324
|
-
for (const file of unmatchedFiles) {
|
|
325
|
-
try {
|
|
326
|
-
if (isFrontendFile(file) || isTestFile(file) || !SOURCE_EXTS.test(file))
|
|
327
|
-
continue;
|
|
328
|
-
const fileAddedLines = addedLinesByFile?.get(file);
|
|
329
|
-
const diffHunk = fileAddedLines && fileAddedLines.length > 0 ? fileAddedLines.join("\n") : undefined;
|
|
330
|
-
const resourceTokens = deriveResourceTokensForFile(file, diffHunk);
|
|
331
|
-
if (resourceTokens.length === 0)
|
|
332
|
-
continue;
|
|
333
|
-
const signatures = buildPathSignatures(file);
|
|
334
|
-
// For an index module the directory stem is a plain string match, so also
|
|
335
|
-
// require the specifier to resolve to this module's directory.
|
|
336
|
-
const indexModule = isIndexModule(file);
|
|
337
|
-
const rawCandidates = scannedEndpoints.filter((ep) => {
|
|
338
|
-
const content = readCached(ep.sourceFile);
|
|
339
|
-
if (content === null || !signatures.some((sig) => content.includes(sig)))
|
|
340
|
-
return false;
|
|
341
|
-
return !indexModule || importsIndexModule(ep.sourceFile, content, file);
|
|
342
|
-
});
|
|
343
|
-
if (rawCandidates.length === 0)
|
|
344
|
-
continue;
|
|
345
|
-
// Critical containment: only keep candidates whose own resource affinity
|
|
346
|
-
// matches ANY of this file's resource tokens (filename OR symbol-derived
|
|
347
|
-
// — see deriveResourceTokensForFile). If nothing survives, flag NOTHING
|
|
348
|
-
// for this file — conservative, preserves today's "unmatched" behavior
|
|
349
|
-
// rather than guessing.
|
|
350
|
-
const affineCandidates = rawCandidates.filter((ep) => {
|
|
351
|
-
const epTokens = endpointResourceTokens(ep.path);
|
|
352
|
-
return resourceTokens.some((token) => epTokens.has(token));
|
|
353
|
-
});
|
|
354
|
-
if (affineCandidates.length === 0)
|
|
355
|
-
continue;
|
|
356
|
-
const mappedPaths = [];
|
|
357
|
-
for (const ep of affineCandidates) {
|
|
358
|
-
const key = `${ep.sourceFile}::${ep.path}`;
|
|
359
|
-
mappedPaths.push(ep.path);
|
|
360
|
-
if (alreadyKeys.has(key) || addedKeys.has(key))
|
|
361
|
-
continue;
|
|
362
|
-
addedKeys.add(key);
|
|
363
|
-
endpoints.push(ep);
|
|
364
|
-
}
|
|
365
|
-
if (mappedPaths.length > 0) {
|
|
366
|
-
byFile[file] = [...new Set(mappedPaths)];
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
catch (err) {
|
|
370
|
-
logger.warning("Importer-hop classification failed for file — skipping", {
|
|
371
|
-
file,
|
|
372
|
-
error: err instanceof Error ? err.message : String(err),
|
|
373
|
-
});
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
return { endpoints, byFile };
|
|
377
|
-
}
|
|
378
|
-
/**
|
|
379
|
-
* Builds the inputs `draftResponseShapeScenarios` needs from an
|
|
380
|
-
* {@link ImporterHopResult}, keyed off `byFile` rather than `endpoints`.
|
|
381
|
-
*
|
|
382
|
-
* `hopResult.endpoints` is deduped against endpoints already present in
|
|
383
|
-
* `changedEndpoints`/`newEndpoints` (see `mapUnmatchedFilesToImporterEndpoints`),
|
|
384
|
-
* so it can be empty — or missing entries — even when `byFile` holds valid
|
|
385
|
-
* path mappings (e.g. a controller and the DTO it imports both changed in the
|
|
386
|
-
* same PR: the controller's endpoint is already classified directly, so the
|
|
387
|
-
* hop drops it from `endpoints`, but `byFile` still records the mapping).
|
|
388
|
-
* Response-shape drafting wants every hop-mapped endpoint regardless of
|
|
389
|
-
* whether it was newly added to the change set, so this re-derives the
|
|
390
|
-
* endpoint list from `scannedEndpoints` filtered to the paths `byFile` maps
|
|
391
|
-
* to, instead of relying on the deduped `endpoints` array.
|
|
392
|
-
*/
|
|
393
|
-
export function buildHopResponseShapeInputs(hopResult, scannedEndpoints, diffContent) {
|
|
394
|
-
const mappedPaths = new Set(Object.values(hopResult.byFile).flat());
|
|
395
|
-
if (mappedPaths.size === 0)
|
|
396
|
-
return { endpoints: [], addedFieldsByFile: {} };
|
|
397
|
-
return {
|
|
398
|
-
endpoints: scannedEndpoints.filter((ep) => mappedPaths.has(ep.path)),
|
|
399
|
-
addedFieldsByFile: diffContent
|
|
400
|
-
? computeAddedFieldsByFile(diffContent, Object.keys(hopResult.byFile))
|
|
401
|
-
: {},
|
|
402
|
-
};
|
|
403
|
-
}
|
|
404
|
-
// ── Added-field extraction from diff hunks ──────────────────────────────────
|
|
405
|
-
const TS_FIELD_RE = /^\+\s*(?:public |readonly |private )?([A-Za-z_]\w*)\??!?\s*[:=]/;
|
|
406
|
-
const PY_FIELD_RE = /^\+\s*([a-z_]\w*)\s*:\s*\w+/;
|
|
407
|
-
// Words that can precede a bare colon/equals in real code without being a
|
|
408
|
-
// field/property declaration (control-flow keywords, declarations, labels).
|
|
409
|
-
const NON_FIELD_KEYWORDS = new Set([
|
|
410
|
-
"if", "for", "while", "return", "const", "let", "var",
|
|
411
|
-
"import", "export", "function", "class", "case", "default",
|
|
412
|
-
"else", "switch", "do", "try", "catch", "finally",
|
|
413
|
-
]);
|
|
414
|
-
/**
|
|
415
|
-
* Extracts likely property/field names from an array of raw added diff lines
|
|
416
|
-
* (each still prefixed with the unified-diff `+` marker). Best-effort regex,
|
|
417
|
-
* not a parser — intended to give a scenario a field name to name-drop, not
|
|
418
|
-
* to be exhaustive.
|
|
419
|
-
*/
|
|
420
|
-
export function extractFieldNamesFromAddedLines(lines) {
|
|
421
|
-
const fields = new Set();
|
|
422
|
-
for (const line of lines) {
|
|
423
|
-
// A genuine content line never starts with a second "+" — that's the
|
|
424
|
-
// "+++ b/file" diff header line.
|
|
425
|
-
if (line.startsWith("++"))
|
|
426
|
-
continue;
|
|
427
|
-
const match = TS_FIELD_RE.exec(line) ?? PY_FIELD_RE.exec(line);
|
|
428
|
-
if (!match)
|
|
429
|
-
continue;
|
|
430
|
-
const name = match[1];
|
|
431
|
-
if (NON_FIELD_KEYWORDS.has(name.toLowerCase()))
|
|
432
|
-
continue;
|
|
433
|
-
fields.add(name);
|
|
434
|
-
}
|
|
435
|
-
return [...fields];
|
|
436
|
-
}
|
|
437
|
-
// `sliceAddedLinesByFile` now lives in `branchDiff.ts` (imported above) — moved
|
|
438
|
-
// there so modules this one already imports can reuse it without an import
|
|
439
|
-
// cycle. Re-exported here so existing importers of it from this module
|
|
440
|
-
// (pathAffinityClassification.ts) don't need to change their import path.
|
|
441
|
-
export { sliceAddedLinesByFile };
|
|
442
|
-
/**
|
|
443
|
-
* Splits a unified diff into per-file raw sections — every line after a file's
|
|
444
|
-
* `diff --git` header up to the next one — keyed by the `b/` (post-change) path.
|
|
445
|
-
* Unlike `sliceAddedLinesByFile` (added lines only), this preserves the `@@`
|
|
446
|
-
* hunk headers, so `extractChangedSymbols`' enclosing-type extraction (which
|
|
447
|
-
* reads `@@ ... class Foo`) still works when a single file's section is passed
|
|
448
|
-
* to `deriveResourceTokensForFile`. Use this for per-file token derivation that
|
|
449
|
-
* must not mix symbols across files in a multi-file diff (SKYR-3924).
|
|
450
|
-
*/
|
|
451
|
-
export function sliceDiffByFile(diffContent) {
|
|
452
|
-
const sections = new Map();
|
|
453
|
-
let currentFile = null;
|
|
454
|
-
for (const line of diffContent.split("\n")) {
|
|
455
|
-
const headerMatch = /^diff --git a\/.+ b\/(.+)$/.exec(line);
|
|
456
|
-
if (headerMatch) {
|
|
457
|
-
currentFile = headerMatch[1];
|
|
458
|
-
sections.set(currentFile, []);
|
|
459
|
-
continue;
|
|
460
|
-
}
|
|
461
|
-
if (currentFile)
|
|
462
|
-
sections.get(currentFile).push(line);
|
|
463
|
-
}
|
|
464
|
-
const joined = new Map();
|
|
465
|
-
for (const [file, lines] of sections)
|
|
466
|
-
joined.set(file, lines.join("\n"));
|
|
467
|
-
return joined;
|
|
468
|
-
}
|
|
469
|
-
/**
|
|
470
|
-
* Computes added-field names per file for the given files, from a full
|
|
471
|
-
* unified diff. Files with no added-line matches are omitted from the result
|
|
472
|
-
* (an empty field list is a valid, expected outcome — the caller still drafts
|
|
473
|
-
* a scenario, just without field hints).
|
|
474
|
-
*/
|
|
475
|
-
export function computeAddedFieldsByFile(diffContent, files) {
|
|
476
|
-
const result = {};
|
|
477
|
-
if (!diffContent || files.length === 0)
|
|
478
|
-
return result;
|
|
479
|
-
const sections = sliceAddedLinesByFile(diffContent);
|
|
480
|
-
for (const file of files) {
|
|
481
|
-
const addedLines = sections.get(file);
|
|
482
|
-
if (!addedLines || addedLines.length === 0)
|
|
483
|
-
continue;
|
|
484
|
-
const names = extractFieldNamesFromAddedLines(addedLines);
|
|
485
|
-
if (names.length > 0)
|
|
486
|
-
result[file] = names;
|
|
487
|
-
}
|
|
488
|
-
return result;
|
|
489
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import type { ScannedEndpoint } from "./repoScanner.js";
|
|
2
|
-
/** Version/prefix directory segments ("v4", "api", ...) present in a changed
|
|
3
|
-
* file's own path — required, as a conjunction, in any matching endpoint path.
|
|
4
|
-
* Prevents a `routes/v4/secret-router.ts` change from matching a `/api/v1/*`
|
|
5
|
-
* sibling endpoint on resource-token alone. */
|
|
6
|
-
export declare function deriveVersionSegments(file: string): Set<string>;
|
|
7
|
-
export interface PathAffinityFallbackResult {
|
|
8
|
-
/** Endpoints newly classified via path affinity, deduped against `alreadyClassifiedEndpoints`. */
|
|
9
|
-
endpoints: ScannedEndpoint[];
|
|
10
|
-
/** Unmatched files that mapped to at least one endpoint (for unmatched-state cleanup). */
|
|
11
|
-
mappedFiles: string[];
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* Maps changed files that `classifyEndpointsByChangedFiles` could not match by
|
|
15
|
-
* `sourceFile` (its `unmatchedFiles`) to catalog endpoints that plausibly belong
|
|
16
|
-
* to the same REST resource, purely from the file's own path and the endpoint's
|
|
17
|
-
* own path — no `sourceFile` needed on either side. Intended to run against the
|
|
18
|
-
* *final* endpoint catalog (after any OpenAPI spec merge), since a catalog with
|
|
19
|
-
* no source-scan coverage for a file is, by definition, only useful for this
|
|
20
|
-
* fallback once spec-derived entries are present to match against.
|
|
21
|
-
*
|
|
22
|
-
* `alreadyClassifiedEndpoints` (typically `changedEndpoints` + `newEndpoints`
|
|
23
|
-
* from the same classification pass) is used only for dedup — never duplicates
|
|
24
|
-
* an endpoint already classified directly.
|
|
25
|
-
*
|
|
26
|
-
* `diffContent`, when supplied, is the full unified diff — sliced per-file
|
|
27
|
-
* (via `sliceAddedLinesByFile`, shared with `mapUnmatchedFilesToImporterEndpoints`)
|
|
28
|
-
* and passed to `deriveResourceTokensForFile` so a route file whose filename
|
|
29
|
-
* alone is too generic to anchor a match (e.g. `routes/v4/index-router.ts`)
|
|
30
|
-
* can still match via a symbol declared on its added diff lines. Without it,
|
|
31
|
-
* matching falls back to the filename-only token, identical to pre-symbol-
|
|
32
|
-
* affinity behavior.
|
|
33
|
-
*
|
|
34
|
-
* `removedEndpoints` (the classifier's removal result, recovered from the base
|
|
35
|
-
* tree) is excluded from the candidate set BY PATH+METHOD, not via the
|
|
36
|
-
* sourceFile-keyed dedup: removal is classified at Step 2.5 against the
|
|
37
|
-
* pre-spec-merge catalog, but this fallback matches the post-spec-merge
|
|
38
|
-
* catalog — where a stale checked-in OpenAPI spec can still list a deleted
|
|
39
|
-
* endpoint as a `sourceFile: ""` entry. Without this guard, a sibling
|
|
40
|
-
* unmatched file sharing the resource token would resurrect the removed
|
|
41
|
-
* endpoint as "modified", contradicting the removal classification. A `MULTI`
|
|
42
|
-
* removed method covers every method on that path; a candidate survives if
|
|
43
|
-
* any of its methods is not removed.
|
|
44
|
-
*
|
|
45
|
-
* Matched endpoints are always attributed as MODIFIED (never new/removed) —
|
|
46
|
-
* path affinity says an endpoint is *related* to the change, not that the
|
|
47
|
-
* relationship is an addition or removal.
|
|
48
|
-
*/
|
|
49
|
-
export declare function classifyUnmatchedFilesByPathAffinity(unmatchedFiles: string[], scannedEndpoints: ScannedEndpoint[], alreadyClassifiedEndpoints?: ScannedEndpoint[], maxEndpointsPerFile?: number, diffContent?: string, removedEndpoints?: ScannedEndpoint[]): PathAffinityFallbackResult;
|