@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,45 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Routes a Flutter app declares with `go_router` — SKYR-4341.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Answers one question, the same one `sourceRouteExtractor` answers for the web:
|
|
5
|
+
* which URL renders which file. Restored from the version `d0982a04` deleted and
|
|
6
|
+
* narrowed to that question — the v1 reader also carried metadata for a caller
|
|
7
|
+
* that ranked pages and fell back to every renderable route, and that caller is
|
|
8
|
+
* gone.
|
|
6
9
|
*
|
|
7
10
|
* GoRoute(path: '/login', builder: (ctx, st) => LoginScreen())
|
|
8
|
-
* GoRoute(path: "/profile/:id", builder: ...)
|
|
9
11
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* as a URL `_kHome` would mislead the agent worse than dropping them.
|
|
15
|
-
*
|
|
16
|
-
* Each route also carries metadata to support diff-matching in the caller
|
|
17
|
-
* (findCandidatePagesByDartRoute):
|
|
18
|
-
*
|
|
19
|
-
* - `screenWidgets`: identifiers referenced as builder/pageBuilder targets,
|
|
20
|
-
* e.g. `AuthorsScreen`. Resolved through the file's imports to absolute
|
|
21
|
-
* paths in `screenFiles`.
|
|
22
|
-
* - `screenFiles`: the imported source files those identifiers come from.
|
|
23
|
-
* - `isRedirectOnly`: true if the GoRoute has `redirect:` but no
|
|
24
|
-
* `builder:` or `pageBuilder:` — these don't render UI, just redirect.
|
|
25
|
-
*
|
|
26
|
-
* Scope choices for slice 1:
|
|
27
|
-
* - GoRouter only (`MaterialApp.routes` and `auto_route` deferred)
|
|
28
|
-
* - String-literal `path:` only (no constant resolution)
|
|
29
|
-
* - Nested GoRoutes emit each declaration verbatim — the absolute parent
|
|
30
|
-
* plus relative children. Caller may compose them later.
|
|
12
|
+
* Regex rather than a Dart parser: every Dart parser needs a Dart SDK on the
|
|
13
|
+
* host. The string-literal `path:` subset covers the shapes real apps write at
|
|
14
|
+
* no install cost. A computed path (`path: _kHome`) is skipped rather than
|
|
15
|
+
* emitted as the literal `_kHome`, which would name a URL the app never serves.
|
|
31
16
|
*/
|
|
32
|
-
/**
|
|
17
|
+
/** One route found in Dart source. Same shape as `SourceRoute`, so both readers
|
|
18
|
+
* feed the route table the same way. */
|
|
33
19
|
export interface DartRoute {
|
|
34
|
-
/** URL path
|
|
20
|
+
/** URL path from `GoRoute(path: ...)`, verbatim. */
|
|
35
21
|
path: string;
|
|
36
|
-
/**
|
|
22
|
+
/** Widget the route builds, e.g. `LoginScreen`. */
|
|
23
|
+
componentName: string;
|
|
24
|
+
/** Absolute path of the file declaring that widget, when an import names it. */
|
|
25
|
+
componentFile?: string;
|
|
26
|
+
/** Absolute path of the file holding the declaration. */
|
|
37
27
|
declaredIn: string;
|
|
38
|
-
/** Widget identifiers referenced from builder:/pageBuilder: (e.g. `AuthorsScreen`). */
|
|
39
|
-
screenWidgets: string[];
|
|
40
|
-
/** Resolved absolute paths for screenWidgets via the file's imports. */
|
|
41
|
-
screenFiles: string[];
|
|
42
|
-
/** True when GoRoute has `redirect:` but no `builder:`/`pageBuilder:` — no UI. */
|
|
43
|
-
isRedirectOnly: boolean;
|
|
44
28
|
}
|
|
45
29
|
export declare function extractDartRoutes(repositoryPath: string): DartRoute[];
|
|
@@ -1,33 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Routes a Flutter app declares with `go_router` — SKYR-4341.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Answers one question, the same one `sourceRouteExtractor` answers for the web:
|
|
5
|
+
* which URL renders which file. Restored from the version `d0982a04` deleted and
|
|
6
|
+
* narrowed to that question — the v1 reader also carried metadata for a caller
|
|
7
|
+
* that ranked pages and fell back to every renderable route, and that caller is
|
|
8
|
+
* gone.
|
|
6
9
|
*
|
|
7
10
|
* GoRoute(path: '/login', builder: (ctx, st) => LoginScreen())
|
|
8
|
-
* GoRoute(path: "/profile/:id", builder: ...)
|
|
9
11
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* as a URL `_kHome` would mislead the agent worse than dropping them.
|
|
15
|
-
*
|
|
16
|
-
* Each route also carries metadata to support diff-matching in the caller
|
|
17
|
-
* (findCandidatePagesByDartRoute):
|
|
18
|
-
*
|
|
19
|
-
* - `screenWidgets`: identifiers referenced as builder/pageBuilder targets,
|
|
20
|
-
* e.g. `AuthorsScreen`. Resolved through the file's imports to absolute
|
|
21
|
-
* paths in `screenFiles`.
|
|
22
|
-
* - `screenFiles`: the imported source files those identifiers come from.
|
|
23
|
-
* - `isRedirectOnly`: true if the GoRoute has `redirect:` but no
|
|
24
|
-
* `builder:` or `pageBuilder:` — these don't render UI, just redirect.
|
|
25
|
-
*
|
|
26
|
-
* Scope choices for slice 1:
|
|
27
|
-
* - GoRouter only (`MaterialApp.routes` and `auto_route` deferred)
|
|
28
|
-
* - String-literal `path:` only (no constant resolution)
|
|
29
|
-
* - Nested GoRoutes emit each declaration verbatim — the absolute parent
|
|
30
|
-
* plus relative children. Caller may compose them later.
|
|
12
|
+
* Regex rather than a Dart parser: every Dart parser needs a Dart SDK on the
|
|
13
|
+
* host. The string-literal `path:` subset covers the shapes real apps write at
|
|
14
|
+
* no install cost. A computed path (`path: _kHome`) is skipped rather than
|
|
15
|
+
* emitted as the literal `_kHome`, which would name a URL the app never serves.
|
|
31
16
|
*/
|
|
32
17
|
import * as fs from "fs";
|
|
33
18
|
import * as path from "path";
|
|
@@ -35,35 +20,21 @@ import { collectFiles } from "./fileWalk.js";
|
|
|
35
20
|
const MAX_CANDIDATE_FILES = 200;
|
|
36
21
|
const MAX_WALK_DEPTH = 10;
|
|
37
22
|
const SKIP_DIRS = new Set([
|
|
38
|
-
"build",
|
|
39
|
-
".
|
|
40
|
-
".git",
|
|
41
|
-
".idea",
|
|
42
|
-
"android",
|
|
43
|
-
"ios",
|
|
44
|
-
"macos",
|
|
45
|
-
"linux",
|
|
46
|
-
"windows",
|
|
47
|
-
"web",
|
|
48
|
-
"node_modules",
|
|
49
|
-
"test",
|
|
50
|
-
".pub-cache",
|
|
23
|
+
"build", ".dart_tool", ".git", ".idea", "android", "ios", "macos", "linux",
|
|
24
|
+
"windows", "web", "node_modules", "test", ".pub-cache",
|
|
51
25
|
]);
|
|
52
26
|
/**
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
* the
|
|
56
|
-
*
|
|
57
|
-
* The regex captures the literal string after `path:` and the start of the
|
|
58
|
-
* GoRoute call body; we then walk forward manually to find the matching
|
|
59
|
-
* close paren (regex can't match balanced parens reliably).
|
|
27
|
+
* A `GoRoute(` through its `path:` string literal. The lazy `[\s\S]*?` scans
|
|
28
|
+
* forward for the first `path:`, so a GoRoute with no path of its own can borrow
|
|
29
|
+
* the next declaration's — those routes are dropped downstream instead, because
|
|
30
|
+
* a route with no resolvable widget produces no entry.
|
|
60
31
|
*/
|
|
61
32
|
const GO_ROUTE_PATH_REGEX = /\bGoRoute\s*\(\s*(?:<[^>]+>\s*)?[\s\S]*?path\s*:\s*(['"])([^'"\\]*)\1/g;
|
|
62
|
-
/**
|
|
33
|
+
/** An identifier used as a constructor inside a builder body. */
|
|
63
34
|
const SCREEN_WIDGET_REGEX = /\b([A-Z][A-Za-z0-9_]*)\s*\(/g;
|
|
64
|
-
/**
|
|
35
|
+
/** A top-level Dart import. */
|
|
65
36
|
const IMPORT_REGEX = /^\s*import\s+['"]([^'"]+)['"]/gm;
|
|
66
|
-
/** Identifiers
|
|
37
|
+
/** Identifiers that are never a screen: Flutter and go_router internals. */
|
|
67
38
|
const NON_SCREEN_IDENTIFIERS = new Set([
|
|
68
39
|
"BuildContext", "GoRouterState", "GoRoute", "GoRouter", "ShellRoute",
|
|
69
40
|
"StatefulShellRoute", "ShellRouteBase", "MaterialPage", "CupertinoPage",
|
|
@@ -72,54 +43,90 @@ const NON_SCREEN_IDENTIFIERS = new Set([
|
|
|
72
43
|
"MaterialApp", "CupertinoApp", "Scaffold", "AppBar",
|
|
73
44
|
]);
|
|
74
45
|
export function extractDartRoutes(repositoryPath) {
|
|
75
|
-
const candidates = findDartFiles(repositoryPath);
|
|
76
|
-
const seen = new Set();
|
|
77
46
|
const routes = [];
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
47
|
+
const sources = new Map();
|
|
48
|
+
const read = (file) => {
|
|
49
|
+
if (!sources.has(file)) {
|
|
50
|
+
try {
|
|
51
|
+
sources.set(file, fs.readFileSync(file, "utf-8"));
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
sources.set(file, undefined);
|
|
55
|
+
}
|
|
82
56
|
}
|
|
83
|
-
|
|
57
|
+
return sources.get(file);
|
|
58
|
+
};
|
|
59
|
+
for (const file of findDartFiles(repositoryPath)) {
|
|
60
|
+
const source = read(file);
|
|
61
|
+
if (!source)
|
|
84
62
|
continue;
|
|
85
|
-
|
|
86
|
-
const imports = collectImports(source, file, repositoryPath);
|
|
63
|
+
const imports = dartImports(source, file, repositoryPath);
|
|
87
64
|
GO_ROUTE_PATH_REGEX.lastIndex = 0;
|
|
88
65
|
let match;
|
|
89
66
|
while ((match = GO_ROUTE_PATH_REGEX.exec(source)) !== null) {
|
|
90
|
-
const
|
|
91
|
-
|
|
67
|
+
const routePath = match[2];
|
|
68
|
+
// A child route states its path relative to its parent. Composing the
|
|
69
|
+
// hierarchy is not done here, and emitting `details` as if it were a URL
|
|
70
|
+
// would send the browser to a page that does not exist.
|
|
71
|
+
if (!routePath.startsWith("/"))
|
|
92
72
|
continue;
|
|
93
|
-
seen.add(pathValue);
|
|
94
73
|
const body = extractGoRouteBody(source, match.index);
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
74
|
+
for (const componentName of screenWidgetsOf(body)) {
|
|
75
|
+
routes.push({
|
|
76
|
+
path: routePath,
|
|
77
|
+
componentName,
|
|
78
|
+
componentFile: declaringFile(componentName, imports, read),
|
|
79
|
+
declaredIn: file,
|
|
80
|
+
});
|
|
101
81
|
}
|
|
102
|
-
routes.push({
|
|
103
|
-
path: pathValue,
|
|
104
|
-
declaredIn: file,
|
|
105
|
-
screenWidgets: inspect.screenWidgets,
|
|
106
|
-
screenFiles,
|
|
107
|
-
isRedirectOnly: inspect.isRedirectOnly,
|
|
108
|
-
});
|
|
109
82
|
}
|
|
110
83
|
}
|
|
111
84
|
return routes;
|
|
112
85
|
}
|
|
113
86
|
/**
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
87
|
+
* The file that declares `widget`, found by reading the importing file's own
|
|
88
|
+
* imports for the class. The v1 reader guessed instead — it derived candidate
|
|
89
|
+
* identifiers from each import's filename (`authors.dart` was assumed to export
|
|
90
|
+
* `Authors`, `AuthorsScreen`, `AuthorsPage`, `AuthorsView`) and accepted the
|
|
91
|
+
* first. A guess that lands wrong names a URL the file does not render, which is
|
|
92
|
+
* the one answer this scan must never give.
|
|
93
|
+
*/
|
|
94
|
+
function declaringFile(widget, imports, read) {
|
|
95
|
+
const declaration = new RegExp(`\\bclass\\s+${widget}\\b`);
|
|
96
|
+
for (const imported of imports) {
|
|
97
|
+
const source = read(imported);
|
|
98
|
+
if (source && declaration.test(source))
|
|
99
|
+
return imported;
|
|
100
|
+
}
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
/** Absolute paths of the repository files a Dart file imports. `package:` and
|
|
104
|
+
* `dart:` imports are not resolved, so a widget reached only that way has no
|
|
105
|
+
* file and its route is dropped rather than guessed. */
|
|
106
|
+
function dartImports(source, filePath, repositoryPath) {
|
|
107
|
+
const resolved = [];
|
|
108
|
+
const fileDir = path.dirname(filePath);
|
|
109
|
+
IMPORT_REGEX.lastIndex = 0;
|
|
110
|
+
let match;
|
|
111
|
+
while ((match = IMPORT_REGEX.exec(source)) !== null) {
|
|
112
|
+
const spec = match[1];
|
|
113
|
+
if (spec.startsWith("package:") || spec.startsWith("dart:"))
|
|
114
|
+
continue;
|
|
115
|
+
if (!spec.endsWith(".dart"))
|
|
116
|
+
continue;
|
|
117
|
+
const target = path.resolve(fileDir, spec);
|
|
118
|
+
if (!target.startsWith(repositoryPath))
|
|
119
|
+
continue;
|
|
120
|
+
if (!resolved.includes(target))
|
|
121
|
+
resolved.push(target);
|
|
122
|
+
}
|
|
123
|
+
return resolved;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* The GoRoute call body, from the `(` after `GoRoute` to its matching `)`.
|
|
127
|
+
* String contents are skipped so a `)` inside a literal does not close the call.
|
|
120
128
|
*/
|
|
121
129
|
function extractGoRouteBody(source, start) {
|
|
122
|
-
// Find the first '(' after `GoRoute`
|
|
123
130
|
const openParen = source.indexOf("(", start);
|
|
124
131
|
if (openParen < 0)
|
|
125
132
|
return "";
|
|
@@ -150,49 +157,32 @@ function extractGoRouteBody(source, start) {
|
|
|
150
157
|
return "";
|
|
151
158
|
return source.slice(openParen + 1, i - 1);
|
|
152
159
|
}
|
|
153
|
-
/**
|
|
154
|
-
*
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
* we don't pick up child route widgets as part of the parent's screens.
|
|
160
|
-
*/
|
|
161
|
-
function inspectGoRouteBody(body) {
|
|
162
|
-
const builderPart = extractKeyValue(body, "builder");
|
|
163
|
-
const pageBuilderPart = extractKeyValue(body, "pageBuilder");
|
|
164
|
-
const redirectPart = extractKeyValue(body, "redirect");
|
|
165
|
-
const screenWidgets = [];
|
|
166
|
-
for (const part of [builderPart, pageBuilderPart]) {
|
|
160
|
+
/** Widgets a GoRoute body builds. A route with `redirect:` and no builder
|
|
161
|
+
* renders nothing, and yields none. */
|
|
162
|
+
function screenWidgetsOf(body) {
|
|
163
|
+
const widgets = [];
|
|
164
|
+
for (const key of ["builder", "pageBuilder"]) {
|
|
165
|
+
const part = keyValueOf(body, key);
|
|
167
166
|
if (!part)
|
|
168
167
|
continue;
|
|
169
168
|
SCREEN_WIDGET_REGEX.lastIndex = 0;
|
|
170
169
|
let m;
|
|
171
170
|
while ((m = SCREEN_WIDGET_REGEX.exec(part)) !== null) {
|
|
172
|
-
|
|
173
|
-
if (NON_SCREEN_IDENTIFIERS.has(id))
|
|
171
|
+
if (NON_SCREEN_IDENTIFIERS.has(m[1]))
|
|
174
172
|
continue;
|
|
175
|
-
if (!
|
|
176
|
-
|
|
173
|
+
if (!widgets.includes(m[1]))
|
|
174
|
+
widgets.push(m[1]);
|
|
177
175
|
}
|
|
178
176
|
}
|
|
179
|
-
|
|
180
|
-
const hasRedirect = !!redirectPart;
|
|
181
|
-
const isRedirectOnly = hasRedirect && !hasBuilder;
|
|
182
|
-
return { screenWidgets, isRedirectOnly };
|
|
177
|
+
return widgets;
|
|
183
178
|
}
|
|
184
|
-
/**
|
|
185
|
-
|
|
186
|
-
* up to the next top-level comma (depth 0 brace/paren/bracket), or `null`
|
|
187
|
-
* when the key isn't present. Strings and nested groups are skipped.
|
|
188
|
-
*/
|
|
189
|
-
function extractKeyValue(body, key) {
|
|
179
|
+
/** The value of `key:` in a GoRoute body, up to the next top-level comma. */
|
|
180
|
+
function keyValueOf(body, key) {
|
|
190
181
|
const re = new RegExp(`\\b${key}\\s*:`, "g");
|
|
191
182
|
const m = re.exec(body);
|
|
192
183
|
if (!m)
|
|
193
184
|
return null;
|
|
194
185
|
let i = m.index + m[0].length;
|
|
195
|
-
// Skip whitespace
|
|
196
186
|
while (i < body.length && /\s/.test(body[i]))
|
|
197
187
|
i++;
|
|
198
188
|
const start = i;
|
|
@@ -225,70 +215,8 @@ function extractKeyValue(body, key) {
|
|
|
225
215
|
}
|
|
226
216
|
return body.slice(start, i).trim();
|
|
227
217
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
*
|
|
231
|
-
* Dart imports two ways:
|
|
232
|
-
* import 'src/screens/authors.dart'; → relative within current package
|
|
233
|
-
* import 'package:foo/screens/x.dart'; → cross-package, not resolved here
|
|
234
|
-
*
|
|
235
|
-
* We only resolve relative imports (the common case for in-app screens).
|
|
236
|
-
* The "local name" for a relative import is taken as ALL identifiers that
|
|
237
|
-
* the file exports — but since we don't parse the imported file's exports,
|
|
238
|
-
* we use a coarse approximation: the importing file's `import 'X.dart';`
|
|
239
|
-
* makes available any PascalCase identifier referenced from the file's
|
|
240
|
-
* scope. We map every identifier we see in builder/pageBuilder bodies to
|
|
241
|
-
* the union of relative imports — first matching one wins.
|
|
242
|
-
*
|
|
243
|
-
* For the common Flutter pattern (one screen widget per screen file with
|
|
244
|
-
* a matching name like `AuthorsScreen`), we approximate via filename:
|
|
245
|
-
* import 'src/screens/authors.dart' → expose `AuthorsScreen`, `Authors`
|
|
246
|
-
*
|
|
247
|
-
* False positives on this approximation just mean we surface a route that
|
|
248
|
-
* shouldn't have been; false negatives just mean we fall back to "no
|
|
249
|
-
* diff-match" and surface all routes (the previous behavior).
|
|
250
|
-
*/
|
|
251
|
-
function collectImports(source, filePath, repoPath) {
|
|
252
|
-
const result = new Map();
|
|
253
|
-
const fileDir = path.dirname(filePath);
|
|
254
|
-
IMPORT_REGEX.lastIndex = 0;
|
|
255
|
-
let match;
|
|
256
|
-
while ((match = IMPORT_REGEX.exec(source)) !== null) {
|
|
257
|
-
const spec = match[1];
|
|
258
|
-
if (spec.startsWith("package:") || spec.startsWith("dart:"))
|
|
259
|
-
continue;
|
|
260
|
-
if (!spec.endsWith(".dart"))
|
|
261
|
-
continue;
|
|
262
|
-
const resolved = path.resolve(fileDir, spec);
|
|
263
|
-
if (!resolved.startsWith(repoPath))
|
|
264
|
-
continue;
|
|
265
|
-
// Filename-based heuristic: `authors.dart` exposes `Authors`, `AuthorsScreen`,
|
|
266
|
-
// `AuthorsScreenState`, etc. We register a few common shapes derived from
|
|
267
|
-
// the file's basename. Conflicts (the same identifier present in two
|
|
268
|
-
// imports) resolve to the first registration.
|
|
269
|
-
const base = path.basename(spec, ".dart");
|
|
270
|
-
const camel = snakeToCamel(base);
|
|
271
|
-
const candidates = [
|
|
272
|
-
camel,
|
|
273
|
-
camel + "Screen",
|
|
274
|
-
camel + "Page",
|
|
275
|
-
camel + "View",
|
|
276
|
-
];
|
|
277
|
-
for (const id of candidates) {
|
|
278
|
-
if (!result.has(id))
|
|
279
|
-
result.set(id, resolved);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
return result;
|
|
283
|
-
}
|
|
284
|
-
function snakeToCamel(s) {
|
|
285
|
-
return s
|
|
286
|
-
.split("_")
|
|
287
|
-
.map((p) => p.charAt(0).toUpperCase() + p.slice(1))
|
|
288
|
-
.join("");
|
|
289
|
-
}
|
|
290
|
-
function findDartFiles(repoPath) {
|
|
291
|
-
return collectFiles(repoPath, {
|
|
218
|
+
function findDartFiles(repositoryPath) {
|
|
219
|
+
return collectFiles(repositoryPath, {
|
|
292
220
|
match: (entry) => entry.name.endsWith(".dart"),
|
|
293
221
|
limit: MAX_CANDIDATE_FILES,
|
|
294
222
|
maxDepth: MAX_WALK_DEPTH,
|
|
@@ -97,3 +97,15 @@ export declare function isPomReuseEnabled(): boolean;
|
|
|
97
97
|
* yields the single reuse pass it did before the feature existed.
|
|
98
98
|
*/
|
|
99
99
|
export declare function isUtilsReuseEnabled(): boolean;
|
|
100
|
+
/**
|
|
101
|
+
* Returns true when the calling agent has the Skyramp Agent Skills plugin
|
|
102
|
+
* loaded (SKYRAMP_SKILLS_LOADED=1). Testbot sets this only for Claude Code
|
|
103
|
+
* runs that also pass `--plugin-dir` at the `plugin/` directory of this
|
|
104
|
+
* package (SKYR-4296).
|
|
105
|
+
*
|
|
106
|
+
* When ON, tools whose only job is to hand back instruction text that now
|
|
107
|
+
* lives in a skill are not registered, and the prompts that used to name
|
|
108
|
+
* those tools name the skill instead. Two entry points for one text would let
|
|
109
|
+
* the tool result override the prompt on conflict.
|
|
110
|
+
*/
|
|
111
|
+
export declare function isSkillsLoaded(): boolean;
|
|
@@ -129,3 +129,17 @@ function isUtilsReuseDefaultOrg() {
|
|
|
129
129
|
"";
|
|
130
130
|
return UTILS_REUSE_DEFAULT_ORGS.includes(owner.toLowerCase());
|
|
131
131
|
}
|
|
132
|
+
/**
|
|
133
|
+
* Returns true when the calling agent has the Skyramp Agent Skills plugin
|
|
134
|
+
* loaded (SKYRAMP_SKILLS_LOADED=1). Testbot sets this only for Claude Code
|
|
135
|
+
* runs that also pass `--plugin-dir` at the `plugin/` directory of this
|
|
136
|
+
* package (SKYR-4296).
|
|
137
|
+
*
|
|
138
|
+
* When ON, tools whose only job is to hand back instruction text that now
|
|
139
|
+
* lives in a skill are not registered, and the prompts that used to name
|
|
140
|
+
* those tools name the skill instead. Two entry points for one text would let
|
|
141
|
+
* the tool result override the prompt on conflict.
|
|
142
|
+
*/
|
|
143
|
+
export function isSkillsLoaded() {
|
|
144
|
+
return process.env.SKYRAMP_SKILLS_LOADED === "1";
|
|
145
|
+
}
|
|
@@ -1,34 +1,55 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* The `data-*` attributes and selector literals a frontend diff touched.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* filename-token (pass 1) and import-edge (pass 2) promotion signals in
|
|
7
|
-
* TestDiscoveryService Step 2 both miss them: a page object `table.component.ts` shares
|
|
8
|
-
* no name token with the changed `CascadeFields.tsx` and does not import it — it selects
|
|
9
|
-
* `.t--table-filter-columns-dropdown` at runtime.
|
|
4
|
+
* One reader now: the removal guard (SKYR-4305), which reports an element the
|
|
5
|
+
* diff deleted from a page that survives so the agent can assert it is gone.
|
|
10
6
|
*
|
|
11
|
-
* This module
|
|
12
|
-
* discovery
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* the test that needs maintenance.
|
|
16
|
-
*
|
|
17
|
-
* This is the frontend analog of importerHop's `extractChangedSymbols` (backend type
|
|
18
|
-
* names) — kept in its own frontend-scoped module rather than in the backend-resource
|
|
19
|
-
* importerHop, per the codebase convention of keeping domain vocabularies local.
|
|
7
|
+
* This module also used to lift every selector literal a diff added or removed,
|
|
8
|
+
* so discovery could promote a page object whose body referenced one. That was
|
|
9
|
+
* the server deciding which existing tests a change touches — the external
|
|
10
|
+
* tests are loaded uncut up to the discovery cap now, and the agent reads them.
|
|
20
11
|
*/
|
|
21
|
-
|
|
12
|
+
/** A removed element's identity: the `data-*` attribute and its literal value. */
|
|
13
|
+
export interface RemovedAttribute {
|
|
14
|
+
/** Lower-cased attribute name, e.g. `data-testid`, `data-cy`, `data-section`. */
|
|
15
|
+
attribute: string;
|
|
16
|
+
value: string;
|
|
17
|
+
}
|
|
18
|
+
/** Identifying attribute VALUES that appear on `+` lines of a diff (any number of files). */
|
|
19
|
+
export declare function extractAddedAttributeValues(diff: string): string[];
|
|
22
20
|
/**
|
|
23
|
-
*
|
|
21
|
+
* Identifying `data-*` attributes one file's diff REMOVED: literals on `-` lines, minus the
|
|
22
|
+
* cases where the element still renders somewhere:
|
|
23
|
+
*
|
|
24
|
+
* - **moved / extracted** — the same value appears on a `+` line of this file or, via
|
|
25
|
+
* `addedValuesElsewhere`, anywhere else in the diff (a component extracted into a new
|
|
26
|
+
* file is the common shape);
|
|
27
|
+
* - **not an element** — a `-` line counts only when it opens a JSX/HTML tag or is a
|
|
28
|
+
* wrapped attribute line of a tag opened above it, on a changed or an unchanged
|
|
29
|
+
* line (the tag is carried until the `>` that closes it; a `>` inside a quoted
|
|
30
|
+
* value, a `{…}` expression or an `=>` arrow is content and does not close it). A deleted comment, docs snippet, story arg or page-object
|
|
31
|
+
* `getByTestId(...)` call that merely mentions the attribute is not an element
|
|
32
|
+
* removal — the element itself may well survive on an unchanged context line;
|
|
33
|
+
* - **renamed** — a rename cannot be told apart by literal identity (old and new
|
|
34
|
+
* value differ by definition), so it is paired structurally: within the same hunk,
|
|
35
|
+
* each removed ELEMENT is paired with AT MOST ONE added element that opens the same
|
|
36
|
+
* tag, carries a NEW identifying attribute, and keeps every other attribute NAME
|
|
37
|
+
* the removed element set on its changed lines (a rename may add attributes or
|
|
38
|
+
* edit their values, never drop one). With the tag kept on a context line, the
|
|
39
|
+
* changed lines are exactly the attributes that differ, so an in-place rename
|
|
40
|
+
* compares only what it touched while an in-place replacement that drops the old
|
|
41
|
+
* element's handlers does not pair. An element whose changed lines carry nothing
|
|
42
|
+
* but the identifier pairs by tag alone: a relabelled rename (`clear-state` →
|
|
43
|
+
* `reset-session`, "Clear State" → "Reset Session") and a same-shape replacement
|
|
44
|
+
* are the same diff, and the tie goes to rename — a guard on a retired identifier
|
|
45
|
+
* passes vacuously, and the agent still reads the diff for what the list lacks. A paired added element is consumed, so one addition
|
|
46
|
+
* suppresses one removal, not every removal in the hunk. SKYR-4339: pairing by tag
|
|
47
|
+
* alone consumed a genuinely removed `<a href="#">` with a genuinely added
|
|
48
|
+
* `<a href="https://…">` in the same hunk (judgement eval RG03), so the guard was
|
|
49
|
+
* never listed.
|
|
24
50
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* `data-test-id` / `data-cy` / `data-qa` attribute values and `getByTestId(...)`
|
|
29
|
-
* arguments (trusted — accepts camelCase or single-delimiter ids).
|
|
30
|
-
* 2. Distinctive kebab / BEM class tokens anywhere on the line (>= 2 hyphens or `--`),
|
|
31
|
-
* which catches CSS-class-based coupling (`t--table-filter-columns-dropdown`,
|
|
32
|
-
* `ads-button--primary`).
|
|
51
|
+
* Feeds `uiContext.removedElements` (SKYR-4305) — the grounding for a removal guard
|
|
52
|
+
* (`toBeHidden()` on `getByTestId(value)` for `data-testid`, on
|
|
53
|
+
* `locator('[<attribute>="<value>"]')` for any other `data-*`).
|
|
33
54
|
*/
|
|
34
|
-
export declare function
|
|
55
|
+
export declare function extractRemovedAttributes(fileDiff: string, addedValuesElsewhere?: Iterable<string>): RemovedAttribute[];
|