swipium 1.4.0 → 2.0.0
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/CHANGELOG.md +313 -0
- package/README.md +175 -219
- package/THREAT_MODEL.md +223 -47
- package/dist/appMap/automationLink.js +39 -26
- package/dist/appMap/automationLink.js.map +1 -1
- package/dist/appMap/build.js +68 -20
- package/dist/appMap/build.js.map +1 -1
- package/dist/appMap/codeIndex.js +31 -5
- package/dist/appMap/codeIndex.js.map +1 -1
- package/dist/appMap/featureIndex.js +23 -5
- package/dist/appMap/featureIndex.js.map +1 -1
- package/dist/appMap/featureModel.js +44 -8
- package/dist/appMap/featureModel.js.map +1 -1
- package/dist/appMap/firstRunApply.js +24 -6
- package/dist/appMap/firstRunApply.js.map +1 -1
- package/dist/appMap/fsWalk.js +19 -2
- package/dist/appMap/fsWalk.js.map +1 -1
- package/dist/appMap/issues.js +10 -30
- package/dist/appMap/issues.js.map +1 -1
- package/dist/appMap/migrations.js +6 -6
- package/dist/appMap/migrations.js.map +1 -1
- package/dist/appMap/prelaunch.js +2 -2
- package/dist/appMap/prelaunch.js.map +1 -1
- package/dist/appMap/projectRegistry.js +83 -11
- package/dist/appMap/projectRegistry.js.map +1 -1
- package/dist/appMap/provenance.js +2 -2
- package/dist/appMap/provenance.js.map +1 -1
- package/dist/appMap/query.js +21 -5
- package/dist/appMap/query.js.map +1 -1
- package/dist/appMap/runtimeMerge.js +12 -5
- package/dist/appMap/runtimeMerge.js.map +1 -1
- package/dist/appMap/schema.js +1 -1
- package/dist/appMap/schema.js.map +1 -1
- package/dist/appMap/screenMatch.js +6 -2
- package/dist/appMap/screenMatch.js.map +1 -1
- package/dist/appMap/staticScan.js +151 -39
- package/dist/appMap/staticScan.js.map +1 -1
- package/dist/appMap/store.js +58 -85
- package/dist/appMap/store.js.map +1 -1
- package/dist/appMap/tsAstScan.js +34 -12
- package/dist/appMap/tsAstScan.js.map +1 -1
- package/dist/artifacts/bundletool.js +49 -12
- package/dist/artifacts/bundletool.js.map +1 -1
- package/dist/artifacts/resolve.js +41 -14
- package/dist/artifacts/resolve.js.map +1 -1
- package/dist/automation/capabilities.js +112 -30
- package/dist/automation/capabilities.js.map +1 -1
- package/dist/automation/gestures.js +6 -2
- package/dist/automation/gestures.js.map +1 -1
- package/dist/automation/plan.js +71 -23
- package/dist/automation/plan.js.map +1 -1
- package/dist/automation/report.js +15 -5
- package/dist/automation/report.js.map +1 -1
- package/dist/automation/selectors.js +22 -28
- package/dist/automation/selectors.js.map +1 -1
- package/dist/automation/types.js +1 -1
- package/dist/automation/types.js.map +1 -1
- package/dist/automation/waits.js +14 -8
- package/dist/automation/waits.js.map +1 -1
- package/dist/automation/webview.js +9 -3
- package/dist/automation/webview.js.map +1 -1
- package/dist/automationGen/appiumModel.js +16 -8
- package/dist/automationGen/appiumModel.js.map +1 -1
- package/dist/automationGen/ciEmitter.js +6 -6
- package/dist/automationGen/ciEmitter.js.map +1 -1
- package/dist/automationGen/identifiers.js +224 -0
- package/dist/automationGen/identifiers.js.map +1 -0
- package/dist/automationGen/jsEmitter.js +288 -121
- package/dist/automationGen/jsEmitter.js.map +1 -1
- package/dist/automationGen/packagePatch.js +2 -2
- package/dist/automationGen/packagePatch.js.map +1 -1
- package/dist/automationGen/platformResolve.js +36 -0
- package/dist/automationGen/platformResolve.js.map +1 -0
- package/dist/automationGen/projectProfile.js +76 -27
- package/dist/automationGen/projectProfile.js.map +1 -1
- package/dist/automationGen/pythonEmitter.js +278 -107
- package/dist/automationGen/pythonEmitter.js.map +1 -1
- package/dist/automationGen/readmeEmitter.js +7 -6
- package/dist/automationGen/readmeEmitter.js.map +1 -1
- package/dist/automationGen/run.js +388 -0
- package/dist/automationGen/run.js.map +1 -0
- package/dist/automationGen/suitePlan.js +17 -10
- package/dist/automationGen/suitePlan.js.map +1 -1
- package/dist/automationGen/validation.js +52 -12
- package/dist/automationGen/validation.js.map +1 -1
- package/dist/build/parseBuildLog.js +235 -31
- package/dist/build/parseBuildLog.js.map +1 -1
- package/dist/build/plan.js +39 -18
- package/dist/build/plan.js.map +1 -1
- package/dist/ci/preflight.js +13 -4
- package/dist/ci/preflight.js.map +1 -1
- package/dist/cli/gc.js +74 -0
- package/dist/cli/gc.js.map +1 -0
- package/dist/cli/init.js +217 -26
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/main.js +50 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/report.js +218 -0
- package/dist/cli/report.js.map +1 -0
- package/dist/cli/scan.js +36 -15
- package/dist/cli/scan.js.map +1 -1
- package/dist/cli/suite.js +7 -7
- package/dist/cli/suite.js.map +1 -1
- package/dist/cli/verify.js +7 -7
- package/dist/cli/verify.js.map +1 -1
- package/dist/consent/consent.js +162 -5
- package/dist/consent/consent.js.map +1 -1
- package/dist/context/detect.js +55 -18
- package/dist/context/detect.js.map +1 -1
- package/dist/context/findApps.js +22 -8
- package/dist/context/findApps.js.map +1 -1
- package/dist/context/projectRoot.js +174 -19
- package/dist/context/projectRoot.js.map +1 -1
- package/dist/context/scan.js +1 -1
- package/dist/context/scan.js.map +1 -1
- package/dist/core/capabilityGroups.js +90 -0
- package/dist/core/capabilityGroups.js.map +1 -0
- package/dist/core/target.js +10 -8
- package/dist/core/target.js.map +1 -1
- package/dist/core/targetPlan.js +160 -39
- package/dist/core/targetPlan.js.map +1 -1
- package/dist/drivers/DirectDriver.js +317 -43
- package/dist/drivers/DirectDriver.js.map +1 -1
- package/dist/drivers/SimctlDriver.js +124 -8
- package/dist/drivers/SimctlDriver.js.map +1 -1
- package/dist/drivers/WdaDriver.js +365 -55
- package/dist/drivers/WdaDriver.js.map +1 -1
- package/dist/explore/candidates.js +7 -3
- package/dist/explore/candidates.js.map +1 -1
- package/dist/explore/graph.js +15 -6
- package/dist/explore/graph.js.map +1 -1
- package/dist/explore/memory.js +4 -1
- package/dist/explore/memory.js.map +1 -1
- package/dist/explore/planner.js +54 -9
- package/dist/explore/planner.js.map +1 -1
- package/dist/explore/policy.js +11 -6
- package/dist/explore/policy.js.map +1 -1
- package/dist/explore/runner.js +363 -190
- package/dist/explore/runner.js.map +1 -1
- package/dist/explore/signatures.js +4 -2
- package/dist/explore/signatures.js.map +1 -1
- package/dist/explore/suite.js +3 -1
- package/dist/explore/suite.js.map +1 -1
- package/dist/featureTesting/executionBootstrap.js +137 -29
- package/dist/featureTesting/executionBootstrap.js.map +1 -1
- package/dist/featureTesting/featureMap.js +6 -3
- package/dist/featureTesting/featureMap.js.map +1 -1
- package/dist/featureTesting/featureScope.js +101 -20
- package/dist/featureTesting/featureScope.js.map +1 -1
- package/dist/featureTesting/mapFeatureScope.js +44 -15
- package/dist/featureTesting/mapFeatureScope.js.map +1 -1
- package/dist/featureTesting/objectiveModel.js +15 -11
- package/dist/featureTesting/objectiveModel.js.map +1 -1
- package/dist/featureTesting/resultMerge.js +16 -8
- package/dist/featureTesting/resultMerge.js.map +1 -1
- package/dist/featureTesting/sources.js +8 -5
- package/dist/featureTesting/sources.js.map +1 -1
- package/dist/featureTesting/suiteBridge.js +8 -2
- package/dist/featureTesting/suiteBridge.js.map +1 -1
- package/dist/featureTesting/synonyms.js +32 -4
- package/dist/featureTesting/synonyms.js.map +1 -1
- package/dist/featureTesting/testCaseFactory.js +27 -15
- package/dist/featureTesting/testCaseFactory.js.map +1 -1
- package/dist/featureTesting/testPlan.js +34 -12
- package/dist/featureTesting/testPlan.js.map +1 -1
- package/dist/firstRun/authStateMachine.js +1 -1
- package/dist/firstRun/authStateMachine.js.map +1 -1
- package/dist/firstRun/classifyScreen.js +9 -6
- package/dist/firstRun/classifyScreen.js.map +1 -1
- package/dist/firstRun/firstRunPlanner.js +16 -13
- package/dist/firstRun/firstRunPlanner.js.map +1 -1
- package/dist/firstRun/firstRunRunner.js +92 -31
- package/dist/firstRun/firstRunRunner.js.map +1 -1
- package/dist/firstRun/generatedDataPolicy.js +25 -15
- package/dist/firstRun/generatedDataPolicy.js.map +1 -1
- package/dist/firstRun/inputPlanner.js +45 -11
- package/dist/firstRun/inputPlanner.js.map +1 -1
- package/dist/firstRun/onboardingStateMachine.js +1 -1
- package/dist/firstRun/onboardingStateMachine.js.map +1 -1
- package/dist/firstRun/paywallPolicy.js +3 -3
- package/dist/firstRun/paywallPolicy.js.map +1 -1
- package/dist/firstRun/types.js +1 -1
- package/dist/firstRun/types.js.map +1 -1
- package/dist/fixtures/catalog.js +72 -9
- package/dist/fixtures/catalog.js.map +1 -1
- package/dist/fixtures/load.js +50 -0
- package/dist/fixtures/load.js.map +1 -0
- package/dist/flows/discover.js +2 -2
- package/dist/flows/discover.js.map +1 -1
- package/dist/flows/generate.js +68 -17
- package/dist/flows/generate.js.map +1 -1
- package/dist/flows/lint.js +88 -14
- package/dist/flows/lint.js.map +1 -1
- package/dist/flows/pack.js +21 -5
- package/dist/flows/pack.js.map +1 -1
- package/dist/flows/paths.js +57 -0
- package/dist/flows/paths.js.map +1 -0
- package/dist/flows/repair.js +176 -37
- package/dist/flows/repair.js.map +1 -1
- package/dist/flows/run.js +308 -99
- package/dist/flows/run.js.map +1 -1
- package/dist/flows/schema.js +107 -25
- package/dist/flows/schema.js.map +1 -1
- package/dist/flows/seedExec.js +8 -4
- package/dist/flows/seedExec.js.map +1 -1
- package/dist/flows/templates.js +5 -5
- package/dist/index.js +50 -8
- package/dist/index.js.map +1 -1
- package/dist/ios/signing.js +32 -3
- package/dist/ios/signing.js.map +1 -1
- package/dist/issues/classify.js +28 -20
- package/dist/issues/classify.js.map +1 -1
- package/dist/issues/fingerprint.js +35 -9
- package/dist/issues/fingerprint.js.map +1 -1
- package/dist/issues/index.js +93 -46
- package/dist/issues/index.js.map +1 -1
- package/dist/issues/metrics.js +6 -5
- package/dist/issues/metrics.js.map +1 -1
- package/dist/issues/recurrence.js +35 -8
- package/dist/issues/recurrence.js.map +1 -1
- package/dist/issues/report.js +12 -8
- package/dist/issues/report.js.map +1 -1
- package/dist/issues/reportBridge.js +40 -9
- package/dist/issues/reportBridge.js.map +1 -1
- package/dist/issues/schema.js +2 -2
- package/dist/issues/schema.js.map +1 -1
- package/dist/issues/sourceRevision.js +2 -2
- package/dist/issues/sourceRevision.js.map +1 -1
- package/dist/issues/store.js +110 -32
- package/dist/issues/store.js.map +1 -1
- package/dist/lib/abortScope.js +48 -0
- package/dist/lib/abortScope.js.map +1 -0
- package/dist/lib/android.js +84 -8
- package/dist/lib/android.js.map +1 -1
- package/dist/lib/coordSpace.js +1 -1
- package/dist/lib/coordSpace.js.map +1 -1
- package/dist/lib/device.js +53 -12
- package/dist/lib/device.js.map +1 -1
- package/dist/lib/gestures.js +141 -0
- package/dist/lib/gestures.js.map +1 -0
- package/dist/lib/gitignore.js +4 -3
- package/dist/lib/gitignore.js.map +1 -1
- package/dist/lib/image.js +12 -3
- package/dist/lib/image.js.map +1 -1
- package/dist/lib/lockfile.js +399 -0
- package/dist/lib/lockfile.js.map +1 -0
- package/dist/lib/needsInput.js +9 -6
- package/dist/lib/needsInput.js.map +1 -1
- package/dist/lib/png.js +4 -3
- package/dist/lib/png.js.map +1 -1
- package/dist/lib/redact.js +170 -9
- package/dist/lib/redact.js.map +1 -1
- package/dist/lib/result.js +94 -10
- package/dist/lib/result.js.map +1 -1
- package/dist/lib/schemaHash.js +7 -5
- package/dist/lib/schemaHash.js.map +1 -1
- package/dist/lib/sensitive.js +6 -3
- package/dist/lib/sensitive.js.map +1 -1
- package/dist/lib/simctl.js +3 -3
- package/dist/lib/simctl.js.map +1 -1
- package/dist/lib/spawn.js +78 -17
- package/dist/lib/spawn.js.map +1 -1
- package/dist/lib/toolAnnotations.js +114 -0
- package/dist/lib/toolAnnotations.js.map +1 -0
- package/dist/lib/wda.js +321 -25
- package/dist/lib/wda.js.map +1 -1
- package/dist/lib/wdaConfig.js +12 -5
- package/dist/lib/wdaConfig.js.map +1 -1
- package/dist/lib/wdaTune.js +52 -23
- package/dist/lib/wdaTune.js.map +1 -1
- package/dist/mobileAudit/checks.js +150 -28
- package/dist/mobileAudit/checks.js.map +1 -1
- package/dist/mobileAudit/evidence.js +4 -4
- package/dist/mobileAudit/evidence.js.map +1 -1
- package/dist/mobileAudit/profiles.js +121 -20
- package/dist/mobileAudit/profiles.js.map +1 -1
- package/dist/mobileAudit/results.js +9 -4
- package/dist/mobileAudit/results.js.map +1 -1
- package/dist/mobileAudit/runner.js +214 -35
- package/dist/mobileAudit/runner.js.map +1 -1
- package/dist/oracle/auth.js +1 -1
- package/dist/oracle/auth.js.map +1 -1
- package/dist/oracle/failures.js +1085 -145
- package/dist/oracle/failures.js.map +1 -1
- package/dist/oracle/health.js +148 -26
- package/dist/oracle/health.js.map +1 -1
- package/dist/oracle/locator.js +148 -19
- package/dist/oracle/locator.js.map +1 -1
- package/dist/oracle/record.js +3 -3
- package/dist/oracle/record.js.map +1 -1
- package/dist/orchestration/envelope.js +2 -2
- package/dist/orchestration/envelope.js.map +1 -1
- package/dist/orchestration/goal.js +7 -13
- package/dist/orchestration/goal.js.map +1 -1
- package/dist/orchestration/testThis/execute.js +167 -0
- package/dist/orchestration/testThis/execute.js.map +1 -0
- package/dist/orchestration/testThis/pipeline.js +413 -0
- package/dist/orchestration/testThis/pipeline.js.map +1 -0
- package/dist/orchestration/testThis/plan.js +519 -0
- package/dist/orchestration/testThis/plan.js.map +1 -0
- package/dist/orchestration/testThis/sessionIntent.js +72 -0
- package/dist/orchestration/testThis/sessionIntent.js.map +1 -0
- package/dist/orchestration/testThis/terminal.js +136 -0
- package/dist/orchestration/testThis/terminal.js.map +1 -0
- package/dist/orchestration/testThis/types.js +12 -0
- package/dist/orchestration/testThis/types.js.map +1 -0
- package/dist/plan/plan.js +9 -9
- package/dist/plan/plan.js.map +1 -1
- package/dist/prompts/index.js +24 -24
- package/dist/prompts/index.js.map +1 -1
- package/dist/report/coverage.js +0 -1
- package/dist/report/coverage.js.map +1 -1
- package/dist/report/evidence.js.map +1 -1
- package/dist/report/export.js +449 -72
- package/dist/report/export.js.map +1 -1
- package/dist/report/findingsDedupe.js +41 -0
- package/dist/report/findingsDedupe.js.map +1 -0
- package/dist/report/flake.js +13 -14
- package/dist/report/flake.js.map +1 -1
- package/dist/report/history.js +72 -38
- package/dist/report/history.js.map +1 -1
- package/dist/report/policy.js +18 -7
- package/dist/report/policy.js.map +1 -1
- package/dist/report/qaLevel.js +10 -16
- package/dist/report/qaLevel.js.map +1 -1
- package/dist/report/readiness.js +42 -12
- package/dist/report/readiness.js.map +1 -1
- package/dist/report/sarifSources.js +160 -0
- package/dist/report/sarifSources.js.map +1 -0
- package/dist/report/summary.js +2 -2
- package/dist/report/summary.js.map +1 -1
- package/dist/report/testCatalog.js +4 -4
- package/dist/report/testCatalog.js.map +1 -1
- package/dist/report/timing.js +23 -23
- package/dist/report/timing.js.map +1 -1
- package/dist/report/toolHealth.js +124 -0
- package/dist/report/toolHealth.js.map +1 -0
- package/dist/server.js +501 -69
- package/dist/server.js.map +1 -1
- package/dist/services/automationGenerate.js +8 -2
- package/dist/services/automationGenerate.js.map +1 -1
- package/dist/services/build.js +10 -4
- package/dist/services/build.js.map +1 -1
- package/dist/services/flowGenerate.js +75 -0
- package/dist/services/flowGenerate.js.map +1 -0
- package/dist/services/preflight.js +40 -8
- package/dist/services/preflight.js.map +1 -1
- package/dist/services/prepareAndroid.js +92 -18
- package/dist/services/prepareAndroid.js.map +1 -1
- package/dist/services/prepareIos.js +94 -17
- package/dist/services/prepareIos.js.map +1 -1
- package/dist/services/report.js +271 -80
- package/dist/services/report.js.map +1 -1
- package/dist/services/smoke.js +68 -22
- package/dist/services/smoke.js.map +1 -1
- package/dist/services/suiteGenerate.js +43 -6
- package/dist/services/suiteGenerate.js.map +1 -1
- package/dist/services/testSuiteKnowledge.js +78 -33
- package/dist/services/testSuiteKnowledge.js.map +1 -1
- package/dist/session/attach.js +284 -4
- package/dist/session/attach.js.map +1 -1
- package/dist/session/processRegistry.js +433 -0
- package/dist/session/processRegistry.js.map +1 -0
- package/dist/session/progress.js +1 -1
- package/dist/session/progress.js.map +1 -1
- package/dist/session/retention.js +201 -0
- package/dist/session/retention.js.map +1 -0
- package/dist/session/store.js +459 -59
- package/dist/session/store.js.map +1 -1
- package/dist/snapshot/overlays.js +60 -10
- package/dist/snapshot/overlays.js.map +1 -1
- package/dist/snapshot/parse.js +30 -12
- package/dist/snapshot/parse.js.map +1 -1
- package/dist/snapshot/present.js +36 -6
- package/dist/snapshot/present.js.map +1 -1
- package/dist/snapshot/settle.js +39 -9
- package/dist/snapshot/settle.js.map +1 -1
- package/dist/state/consent.js +99 -0
- package/dist/state/consent.js.map +1 -0
- package/dist/state/profile.js +82 -11
- package/dist/state/profile.js.map +1 -1
- package/dist/suite/compile.js +15 -6
- package/dist/suite/compile.js.map +1 -1
- package/dist/suite/lint.js +22 -4
- package/dist/suite/lint.js.map +1 -1
- package/dist/suite/pom.js +75 -44
- package/dist/suite/pom.js.map +1 -1
- package/dist/suite/secretGuard.js +266 -0
- package/dist/suite/secretGuard.js.map +1 -0
- package/dist/suite/testcase.js +15 -11
- package/dist/suite/testcase.js.map +1 -1
- package/dist/testSuite/exporter.js +7 -3
- package/dist/testSuite/exporter.js.map +1 -1
- package/dist/testSuite/generator.js +43 -14
- package/dist/testSuite/generator.js.map +1 -1
- package/dist/testSuite/history.js +1 -1
- package/dist/testSuite/history.js.map +1 -1
- package/dist/testSuite/issueLinks.js +3 -3
- package/dist/testSuite/issueLinks.js.map +1 -1
- package/dist/testSuite/lint.js +4 -4
- package/dist/testSuite/lint.js.map +1 -1
- package/dist/testSuite/merge.js +13 -7
- package/dist/testSuite/merge.js.map +1 -1
- package/dist/testSuite/schema.js +21 -16
- package/dist/testSuite/schema.js.map +1 -1
- package/dist/testSuite/store.js +12 -4
- package/dist/testSuite/store.js.map +1 -1
- package/dist/testSuite/traceability.js +2 -2
- package/dist/testSuite/traceability.js.map +1 -1
- package/dist/tools/act.js +868 -174
- package/dist/tools/act.js.map +1 -1
- package/dist/tools/agent.js +521 -112
- package/dist/tools/agent.js.map +1 -1
- package/dist/tools/appControl.js +82 -19
- package/dist/tools/appControl.js.map +1 -1
- package/dist/tools/appMap.js +393 -189
- package/dist/tools/appMap.js.map +1 -1
- package/dist/tools/build.js +92 -51
- package/dist/tools/build.js.map +1 -1
- package/dist/tools/bundletool.js +141 -40
- package/dist/tools/bundletool.js.map +1 -1
- package/dist/tools/clearOverlay.js +98 -18
- package/dist/tools/clearOverlay.js.map +1 -1
- package/dist/tools/device.js +108 -19
- package/dist/tools/device.js.map +1 -1
- package/dist/tools/doctor.js +98 -35
- package/dist/tools/doctor.js.map +1 -1
- package/dist/tools/explore.js +110 -42
- package/dist/tools/explore.js.map +1 -1
- package/dist/tools/featureTesting.js +152 -130
- package/dist/tools/featureTesting.js.map +1 -1
- package/dist/tools/firstRun.js +94 -66
- package/dist/tools/firstRun.js.map +1 -1
- package/dist/tools/flow.js +347 -132
- package/dist/tools/flow.js.map +1 -1
- package/dist/tools/flowRepair.js +42 -10
- package/dist/tools/flowRepair.js.map +1 -1
- package/dist/tools/generate.js +199 -0
- package/dist/tools/generate.js.map +1 -0
- package/dist/tools/getArtifact.js +37 -8
- package/dist/tools/getArtifact.js.map +1 -1
- package/dist/tools/health.js +17 -12
- package/dist/tools/health.js.map +1 -1
- package/dist/tools/ios.js +148 -194
- package/dist/tools/ios.js.map +1 -1
- package/dist/tools/issues.js +256 -171
- package/dist/tools/issues.js.map +1 -1
- package/dist/tools/jobs.js +55 -11
- package/dist/tools/jobs.js.map +1 -1
- package/dist/tools/metro.js +84 -38
- package/dist/tools/metro.js.map +1 -1
- package/dist/tools/mobileAudit.js +83 -26
- package/dist/tools/mobileAudit.js.map +1 -1
- package/dist/tools/network.js +36 -14
- package/dist/tools/network.js.map +1 -1
- package/dist/tools/note.js +31 -17
- package/dist/tools/note.js.map +1 -1
- package/dist/tools/prepareIosTarget.js +31 -14
- package/dist/tools/prepareIosTarget.js.map +1 -1
- package/dist/tools/prepareTarget.js +193 -47
- package/dist/tools/prepareTarget.js.map +1 -1
- package/dist/tools/report.js +16 -8
- package/dist/tools/report.js.map +1 -1
- package/dist/tools/resolveArtifact.js +12 -12
- package/dist/tools/resolveArtifact.js.map +1 -1
- package/dist/tools/resolveTarget.js +81 -18
- package/dist/tools/resolveTarget.js.map +1 -1
- package/dist/tools/screenRecord.js +85 -20
- package/dist/tools/screenRecord.js.map +1 -1
- package/dist/tools/screenshot.js +42 -18
- package/dist/tools/screenshot.js.map +1 -1
- package/dist/tools/smoke.js +29 -12
- package/dist/tools/smoke.js.map +1 -1
- package/dist/tools/snapshot.js +157 -107
- package/dist/tools/snapshot.js.map +1 -1
- package/dist/tools/startSession.js +117 -84
- package/dist/tools/startSession.js.map +1 -1
- package/dist/tools/suite.js +391 -310
- package/dist/tools/suite.js.map +1 -1
- package/dist/tools/testSuite.js +112 -36
- package/dist/tools/testSuite.js.map +1 -1
- package/dist/tools/testThis.js +35 -802
- package/dist/tools/testThis.js.map +1 -1
- package/dist/tools/visual.js +698 -116
- package/dist/tools/visual.js.map +1 -1
- package/dist/tools/wait.js +9 -23
- package/dist/tools/wait.js.map +1 -1
- package/dist/tools/wda.js +312 -54
- package/dist/tools/wda.js.map +1 -1
- package/dist/version.js +37 -64
- package/dist/version.js.map +1 -1
- package/dist/visual/ocr.js +65 -8
- package/dist/visual/ocr.js.map +1 -1
- package/dist/visual/provider.js +87 -15
- package/dist/visual/provider.js.map +1 -1
- package/docs/README.md +24 -10
- package/docs/ci-reports.md +328 -0
- package/docs/concepts.md +227 -0
- package/docs/flows.md +153 -0
- package/docs/mcp-server.md +210 -104
- package/docs/physical-devices.md +84 -0
- package/docs/tools.md +808 -217
- package/package.json +16 -6
- package/dist/appMap/diff.js +0 -60
- package/dist/appMap/diff.js.map +0 -1
- package/dist/automation/maestroIr.js +0 -282
- package/dist/automation/maestroIr.js.map +0 -1
- package/dist/interop/maestro.js +0 -151
- package/dist/interop/maestro.js.map +0 -1
- package/dist/tools/assertVisual.js +0 -74
- package/dist/tools/assertVisual.js.map +0 -1
- package/dist/tools/automationGenerate.js +0 -394
- package/dist/tools/automationGenerate.js.map +0 -1
- package/dist/tools/capabilities.js +0 -209
- package/dist/tools/capabilities.js.map +0 -1
- package/dist/tools/detectContext.js +0 -41
- package/dist/tools/detectContext.js.map +0 -1
- package/dist/tools/flowGenerate.js +0 -55
- package/dist/tools/flowGenerate.js.map +0 -1
- package/dist/tools/history.js +0 -56
- package/dist/tools/history.js.map +0 -1
- package/dist/tools/idling.js +0 -104
- package/dist/tools/idling.js.map +0 -1
- package/dist/tools/inputCapabilities.js +0 -30
- package/dist/tools/inputCapabilities.js.map +0 -1
- package/dist/tools/locator.js +0 -57
- package/dist/tools/locator.js.map +0 -1
- package/dist/tools/maestro.js +0 -78
- package/dist/tools/maestro.js.map +0 -1
- package/dist/tools/permissions.js +0 -149
- package/dist/tools/permissions.js.map +0 -1
- package/dist/tools/plan.js +0 -49
- package/dist/tools/plan.js.map +0 -1
- package/dist/tools/screenInfo.js +0 -70
- package/dist/tools/screenInfo.js.map +0 -1
- package/dist/tools/seed.js +0 -101
- package/dist/tools/seed.js.map +0 -1
- package/dist/tools/state.js +0 -170
- package/dist/tools/state.js.map +0 -1
- package/dist/tools/visualText.js +0 -61
- package/dist/tools/visualText.js.map +0 -1
package/dist/tools/appMap.js
CHANGED
|
@@ -1,19 +1,21 @@
|
|
|
1
|
-
// App Knowledge Map MCP tools. Public v1 reads, builds, queries,
|
|
1
|
+
// App Knowledge Map MCP tools. Public v1 reads, builds, queries, and scopes the
|
|
2
2
|
// durable `.swipium/app-map.json`. Large map data is returned by RESOURCE
|
|
3
3
|
// URI (swipium://project/<id>/app-map…) rather than flooded into the text channel; compact,
|
|
4
4
|
// structured data is returned inline. All heavy logic lives in src/appMap/*; these are thin.
|
|
5
5
|
import { existsSync, readFileSync } from 'node:fs';
|
|
6
|
+
import { basename } from 'node:path';
|
|
6
7
|
import { z } from 'zod';
|
|
7
|
-
import { qaOk, qaError } from '../lib/result.js';
|
|
8
|
-
import {
|
|
8
|
+
import { qaOk, qaError, unknownSessionError } from '../lib/result.js';
|
|
9
|
+
import { qaNeedsInput } from '../lib/needsInput.js';
|
|
10
|
+
import { resolveProjectRoot, unresolvedProjectRootError } from '../context/projectRoot.js';
|
|
9
11
|
import { buildAppMap, summarizeMap } from '../appMap/build.js';
|
|
10
12
|
import { queryAppMap } from '../appMap/query.js';
|
|
11
|
-
import { loadAppMap, loadCodeIndex, saveAppMap, saveIndexes,
|
|
13
|
+
import { loadAppMap, loadCodeIndex, saveAppMap, saveIndexes, withAppMapLock, appMapResourceUri, appMapPath, projectId, } from '../appMap/store.js';
|
|
12
14
|
import { addProvenance, makeProvenance, recomputeConfidence } from '../appMap/provenance.js';
|
|
13
|
-
import { diffAppMaps } from '../appMap/diff.js';
|
|
14
|
-
import { migrateAppMap } from '../appMap/migrations.js';
|
|
15
15
|
import { rememberProject, lookupRoot } from '../appMap/projectRegistry.js';
|
|
16
|
+
import { resolveFeatureContext } from './featureTesting.js';
|
|
16
17
|
import { detectFramework } from '../context/detect.js';
|
|
18
|
+
import { encodeUriSegment } from '../session/store.js';
|
|
17
19
|
// projectId(root) is a one-way hash, so resource reads need a reverse lookup. We remember every root
|
|
18
20
|
// touched this session (in-memory) AND in a DURABLE registry (~/.swipium/projects.json, Fix 8) so a
|
|
19
21
|
// resource URI stays resolvable across server restarts. The resource handler also falls back to live
|
|
@@ -45,7 +47,7 @@ async function rootFor(server, sessions, args) {
|
|
|
45
47
|
if (args.sessionId) {
|
|
46
48
|
const s = sessions.get(args.sessionId);
|
|
47
49
|
if (!s)
|
|
48
|
-
return {
|
|
50
|
+
return { error: unknownSessionError(args.sessionId) };
|
|
49
51
|
return { root: s.root, session: s };
|
|
50
52
|
}
|
|
51
53
|
const resolved = await resolveProjectRoot(server, args.projectRoot);
|
|
@@ -55,7 +57,14 @@ async function rootFor(server, sessions, args) {
|
|
|
55
57
|
}
|
|
56
58
|
function fallbackProject(root) {
|
|
57
59
|
const fw = detectFramework(root);
|
|
58
|
-
return {
|
|
60
|
+
return {
|
|
61
|
+
root,
|
|
62
|
+
gitRemote: null,
|
|
63
|
+
packageName: null,
|
|
64
|
+
workspaceTarget: null,
|
|
65
|
+
framework: fw,
|
|
66
|
+
platforms: fw === 'native-android' ? ['android'] : fw === 'native-ios' ? ['ios'] : ['android', 'ios'],
|
|
67
|
+
};
|
|
59
68
|
}
|
|
60
69
|
/** Read an existing map WITHOUT rescanning. Returns null when no map file exists yet. */
|
|
61
70
|
function readExistingMap(root) {
|
|
@@ -79,19 +88,23 @@ export function registerAppMap(server, sessions) {
|
|
|
79
88
|
// ------------------------------------------------------------------ build
|
|
80
89
|
server.registerTool('qa_app_map_build', {
|
|
81
90
|
title: 'Build / update the app knowledge map',
|
|
82
|
-
description: 'Build or
|
|
91
|
+
description: 'Build or update the app knowledge map (.swipium/app-map.json): framework-aware static scan (Expo Router, React ' +
|
|
92
|
+
'Navigation, Android manifest, SwiftUI/UIKit, Flutter) plus, with sessionId, a merge of the latest exploration screen ' +
|
|
93
|
+
'graph. Returns a summary + map URI. Does not commit.',
|
|
83
94
|
inputSchema: {
|
|
84
|
-
projectRoot: z.string().optional().describe('
|
|
95
|
+
projectRoot: z.string().optional().describe('Default: the session or resolved project root.'),
|
|
85
96
|
sessionId: z.string().optional().describe('Reuse a session (its root + latest exploration graph).'),
|
|
86
|
-
mode: z.enum(['static_only', 'runtime_merge', 'full']).optional().describe('static_only
|
|
97
|
+
mode: z.enum(['static_only', 'runtime_merge', 'full']).optional().describe('static_only | runtime_merge | full (default).'),
|
|
87
98
|
includeCodeIndex: z.boolean().optional().describe('Persist a code symbol index for queries (default true).'),
|
|
88
|
-
forceRescan: z.boolean().optional().describe('Re-
|
|
99
|
+
forceRescan: z.boolean().optional().describe('Re-scan even if the map is current.'),
|
|
89
100
|
},
|
|
90
|
-
|
|
101
|
+
// NOTE: no outputSchema. A declared (closed) output schema makes strict MCP clients
|
|
102
|
+
// reject BOTH the rich qaOk payload and the qaError envelope as "additional properties"
|
|
103
|
+
// (caught by test/errorContract.test.ts). structuredContent stays self-describing.
|
|
91
104
|
}, async ({ projectRoot, sessionId, mode, includeCodeIndex, forceRescan }) => {
|
|
92
|
-
const { root, session, hint } = await rootFor(server, sessions, { projectRoot, sessionId });
|
|
105
|
+
const { root, session, hint, error } = await rootFor(server, sessions, { projectRoot, sessionId });
|
|
93
106
|
if (!root)
|
|
94
|
-
return
|
|
107
|
+
return error ?? unresolvedProjectRootError({ source: 'none', hint });
|
|
95
108
|
remember(root);
|
|
96
109
|
const m = (mode ?? 'full');
|
|
97
110
|
const exploreGraph = m === 'static_only' ? null : latestExploreGraph(sessions, session);
|
|
@@ -101,7 +114,9 @@ export function registerAppMap(server, sessions) {
|
|
|
101
114
|
const merge = res.mergeResult;
|
|
102
115
|
const text = `🗺️ app map built (${m}) for ${root}\n` +
|
|
103
116
|
`framework=${res.map.project.framework} router=${res.map.staticTopology.router ?? 'none'} · static screens=${res.map.staticTopology.screens.length} · runtime screens=${res.map.runtimeTopology.screens.length}\n` +
|
|
104
|
-
(merge
|
|
117
|
+
(merge
|
|
118
|
+
? `merge: +${merge.newRuntimeScreens} new, ~${merge.updatedRuntimeScreens} updated, ${merge.linkedScreens} linked, ${merge.unmappedRuntimeScreens} unmapped\n`
|
|
119
|
+
: '') +
|
|
105
120
|
`confidence=${res.map.confidence.overall} · features=${res.map.features.length}\n` +
|
|
106
121
|
(Array.isArray(summary.topGaps) && summary.topGaps.length ? `gaps: ${summary.topGaps.join('; ')}\n` : '') +
|
|
107
122
|
`appMapUri: ${res.save?.resourceUri}`;
|
|
@@ -113,18 +128,30 @@ export function registerAppMap(server, sessions) {
|
|
|
113
128
|
staticScreens: res.map.staticTopology.screens.length,
|
|
114
129
|
runtimeScreens: res.map.runtimeTopology.screens.length,
|
|
115
130
|
mergeResult: merge ?? null,
|
|
116
|
-
migration: res.migration
|
|
131
|
+
migration: res.migration
|
|
132
|
+
? {
|
|
133
|
+
migratedFrom: res.migration.migratedFrom,
|
|
134
|
+
applied: res.migration.applied,
|
|
135
|
+
recoveredFrom: res.migration.recoveredFrom ?? null,
|
|
136
|
+
}
|
|
137
|
+
: null,
|
|
117
138
|
summary,
|
|
118
139
|
}, text);
|
|
119
140
|
}
|
|
120
141
|
catch (e) {
|
|
121
|
-
return qaError({
|
|
142
|
+
return qaError({
|
|
143
|
+
what: `App map build failed: ${String(e)}`,
|
|
144
|
+
changedState: false,
|
|
145
|
+
retrySafe: true,
|
|
146
|
+
nextSteps: ['Check the project root is a supported mobile project.'],
|
|
147
|
+
});
|
|
122
148
|
}
|
|
123
149
|
});
|
|
124
150
|
// ------------------------------------------------------------------- read
|
|
125
151
|
server.registerTool('qa_app_map_read', {
|
|
126
152
|
title: 'Read the app knowledge map',
|
|
127
|
-
description: '
|
|
153
|
+
description: 'Read a compact app-map section: summary (default) | screens | features | auth | automation | testSuite | full; ' +
|
|
154
|
+
'featureId or screenId drills into one node. Large sections come back as a resource URI.',
|
|
128
155
|
inputSchema: {
|
|
129
156
|
projectRoot: z.string().optional(),
|
|
130
157
|
sessionId: z.string().optional(),
|
|
@@ -132,29 +159,50 @@ export function registerAppMap(server, sessions) {
|
|
|
132
159
|
featureId: z.string().optional(),
|
|
133
160
|
screenId: z.string().optional(),
|
|
134
161
|
},
|
|
135
|
-
outputSchema: { ok: z.boolean(), appMapUri: z.string().optional(), section: z.string().optional() },
|
|
136
162
|
}, async ({ projectRoot, sessionId, section, featureId, screenId }) => {
|
|
137
|
-
const { root, hint } = await rootFor(server, sessions, { projectRoot, sessionId });
|
|
163
|
+
const { root, hint, error } = await rootFor(server, sessions, { projectRoot, sessionId });
|
|
138
164
|
if (!root)
|
|
139
|
-
return
|
|
165
|
+
return error ?? unresolvedProjectRootError({ source: 'none', hint });
|
|
140
166
|
remember(root);
|
|
141
167
|
const map = readExistingMap(root);
|
|
142
168
|
if (!map)
|
|
143
|
-
return qaError({
|
|
169
|
+
return qaError({
|
|
170
|
+
what: 'No app map yet',
|
|
171
|
+
changedState: false,
|
|
172
|
+
retrySafe: true,
|
|
173
|
+
nextSteps: ['Run qa_app_map_build first.'],
|
|
174
|
+
failureCode: 'NO_APP_MAP',
|
|
175
|
+
});
|
|
144
176
|
const uri = appMapResourceUri(root);
|
|
145
177
|
const sec = section ?? 'summary';
|
|
146
178
|
if (featureId) {
|
|
147
179
|
const f = map.features.find((x) => x.id === featureId);
|
|
148
180
|
if (!f)
|
|
149
|
-
return qaError({
|
|
150
|
-
|
|
181
|
+
return qaError({
|
|
182
|
+
what: `Unknown featureId ${featureId}`,
|
|
183
|
+
changedState: false,
|
|
184
|
+
retrySafe: true,
|
|
185
|
+
nextSteps: ['Call qa_app_map_read { section:"features" } to list ids.'],
|
|
186
|
+
});
|
|
187
|
+
return qaOk({ appMapUri: uri, section: 'feature', feature: f, featureResourceUri: `${uri}/feature/${featureId}` }, `feature ${f.title}: ${f.testCoverage} coverage, ${f.status}, confidence ${f.confidence}`);
|
|
151
188
|
}
|
|
152
189
|
if (screenId) {
|
|
153
190
|
const s = map.staticTopology.screens.find((x) => x.id === screenId);
|
|
154
191
|
const r = map.runtimeTopology.screens.find((x) => x.id === screenId);
|
|
155
192
|
if (!s && !r)
|
|
156
|
-
return qaError({
|
|
157
|
-
|
|
193
|
+
return qaError({
|
|
194
|
+
what: `Unknown screenId ${screenId}`,
|
|
195
|
+
changedState: false,
|
|
196
|
+
retrySafe: true,
|
|
197
|
+
nextSteps: ['Call qa_app_map_read { section:"screens" } to list ids.'],
|
|
198
|
+
});
|
|
199
|
+
return qaOk({
|
|
200
|
+
appMapUri: uri,
|
|
201
|
+
section: 'screen',
|
|
202
|
+
staticScreen: s ?? null,
|
|
203
|
+
runtimeScreen: r ?? null,
|
|
204
|
+
screenResourceUri: `${uri}/screen/${screenId}`,
|
|
205
|
+
}, `screen ${screenId}`);
|
|
158
206
|
}
|
|
159
207
|
switch (sec) {
|
|
160
208
|
case 'summary':
|
|
@@ -163,8 +211,21 @@ export function registerAppMap(server, sessions) {
|
|
|
163
211
|
return qaOk({
|
|
164
212
|
appMapUri: uri,
|
|
165
213
|
section: sec,
|
|
166
|
-
staticScreens: map.staticTopology.screens.map((s) => ({
|
|
167
|
-
|
|
214
|
+
staticScreens: map.staticTopology.screens.map((s) => ({
|
|
215
|
+
id: s.id,
|
|
216
|
+
name: s.name,
|
|
217
|
+
route: s.route,
|
|
218
|
+
kind: s.kind,
|
|
219
|
+
confidence: s.confidence,
|
|
220
|
+
})),
|
|
221
|
+
runtimeScreens: map.runtimeTopology.screens.map((r) => ({
|
|
222
|
+
id: r.id,
|
|
223
|
+
title: r.title,
|
|
224
|
+
visits: r.visits,
|
|
225
|
+
linkedStaticScreenId: r.linkedStaticScreenId,
|
|
226
|
+
unmapped: r.unmapped,
|
|
227
|
+
locatorReadiness: r.locatorReadiness,
|
|
228
|
+
})),
|
|
168
229
|
unvisitedStaticScreens: map.runtimeTopology.unvisitedStaticScreens,
|
|
169
230
|
}, `${map.staticTopology.screens.length} static / ${map.runtimeTopology.screens.length} runtime screens; ${map.runtimeTopology.unvisitedStaticScreens.length} unvisited`);
|
|
170
231
|
case 'features':
|
|
@@ -178,7 +239,12 @@ export function registerAppMap(server, sessions) {
|
|
|
178
239
|
case 'full':
|
|
179
240
|
default:
|
|
180
241
|
// Protect context: point at the resource instead of inlining the whole map.
|
|
181
|
-
return qaOk({
|
|
242
|
+
return qaOk({
|
|
243
|
+
appMapUri: uri,
|
|
244
|
+
section: 'full',
|
|
245
|
+
summary: summarizeMap(map),
|
|
246
|
+
note: 'Full map omitted from text to protect context. Read the appMapUri resource for everything.',
|
|
247
|
+
}, `Full map at resource: ${uri}`);
|
|
182
248
|
}
|
|
183
249
|
});
|
|
184
250
|
// ------------------------------------------------------------------ query
|
|
@@ -192,57 +258,145 @@ export function registerAppMap(server, sessions) {
|
|
|
192
258
|
intent: z.enum(['feature', 'screen', 'code', 'test', 'freeform']).optional(),
|
|
193
259
|
limit: z.number().optional(),
|
|
194
260
|
},
|
|
195
|
-
outputSchema: { ok: z.boolean(), total: z.number().optional() },
|
|
196
261
|
}, async ({ query, projectRoot, sessionId, intent, limit }) => {
|
|
197
|
-
const { root, hint } = await rootFor(server, sessions, { projectRoot, sessionId });
|
|
262
|
+
const { root, hint, error } = await rootFor(server, sessions, { projectRoot, sessionId });
|
|
198
263
|
if (!root)
|
|
199
|
-
return
|
|
264
|
+
return error ?? unresolvedProjectRootError({ source: 'none', hint });
|
|
200
265
|
remember(root);
|
|
201
266
|
const map = readExistingMap(root);
|
|
202
267
|
if (!map)
|
|
203
|
-
return qaError({
|
|
268
|
+
return qaError({
|
|
269
|
+
what: 'No app map yet',
|
|
270
|
+
changedState: false,
|
|
271
|
+
retrySafe: true,
|
|
272
|
+
nextSteps: ['Run qa_app_map_build first.'],
|
|
273
|
+
failureCode: 'NO_APP_MAP',
|
|
274
|
+
});
|
|
204
275
|
const codeIndex = loadCodeIndex(root);
|
|
205
276
|
const out = queryAppMap(map, codeIndex, { query, intent, limit });
|
|
206
|
-
const top = out.results
|
|
207
|
-
|
|
277
|
+
const top = out.results
|
|
278
|
+
.slice(0, 5)
|
|
279
|
+
.map((r, i) => ` ${i + 1}. [${r.type}] ${r.title} (score ${r.score}${r.confidence !== undefined ? `, conf ${r.confidence}` : ''}) > ${r.recommendedNextTool.tool}`)
|
|
280
|
+
.join('\n');
|
|
281
|
+
return qaOk({ ...out, appMapUri: appMapResourceUri(root) }, `🔎 "${query}": ${out.total} result(s)\n${top || ' (no matches)'}`);
|
|
208
282
|
});
|
|
209
283
|
// ----------------------------------------------------------- feature_scope
|
|
210
284
|
server.registerTool('qa_app_map_feature_scope', {
|
|
211
285
|
title: 'Scope testing to a feature',
|
|
212
|
-
description: 'Resolve a feature (
|
|
286
|
+
description: 'Resolve a feature (featureId, or free-text query such as "checkout") to a focused test scope: code symbols, static + ' +
|
|
287
|
+
'runtime screens, existing tests, objective, coverage gaps, strategy, and ranked candidates (one disambiguation ' +
|
|
288
|
+
'question only on a genuine tie). Works without a map (falls back to a code scan). Read-only.',
|
|
213
289
|
inputSchema: {
|
|
214
|
-
projectRoot: z.string().optional(),
|
|
215
|
-
sessionId: z.string().optional(),
|
|
216
|
-
featureId: z.string().optional(),
|
|
217
|
-
query: z.string().optional().describe('
|
|
290
|
+
projectRoot: z.string().optional().describe('Project root when no session exists.'),
|
|
291
|
+
sessionId: z.string().optional().describe('Adds runtime screen-graph evidence.'),
|
|
292
|
+
featureId: z.string().optional().describe('Exact map feature id (or use query).'),
|
|
293
|
+
query: z.string().optional().describe('e.g. "checkout flow".'),
|
|
294
|
+
platform: z.enum(['android', 'ios']).optional(),
|
|
295
|
+
includeCode: z.boolean().optional().describe('query: scan source code (default true).'),
|
|
296
|
+
limit: z.number().optional().describe('Query mode: max items per list in the scope (default 8).'),
|
|
218
297
|
},
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
298
|
+
}, async ({ projectRoot, sessionId, featureId, query, platform, includeCode, limit }) => {
|
|
299
|
+
if (!featureId && !query) {
|
|
300
|
+
return qaError({
|
|
301
|
+
what: 'Provide one of: featureId or query',
|
|
302
|
+
failureCode: 'INVALID_ARGUMENT',
|
|
303
|
+
changedState: false,
|
|
304
|
+
retrySafe: true,
|
|
305
|
+
nextSteps: ['e.g. qa_app_map_feature_scope { query:"login" }'],
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
// Free-text QUERY path: full feature scoping (app-map-first, code-index + runtime-graph
|
|
309
|
+
// fallback, objective model, candidate disambiguation). Works without an existing map.
|
|
310
|
+
if (!featureId) {
|
|
311
|
+
const r = await resolveFeatureContext(server, sessions, { sessionId, projectRoot, feature: query, platform, includeCode, limit });
|
|
312
|
+
if (!r.ok)
|
|
313
|
+
return r.result;
|
|
314
|
+
remember(r.ctx.root);
|
|
315
|
+
const { scopeResult, objective, index } = r.ctx;
|
|
316
|
+
const scope = scopeResult.primary;
|
|
317
|
+
if (!scopeResult.found) {
|
|
318
|
+
return qaOk({
|
|
319
|
+
sessionId: sessionId ?? null,
|
|
320
|
+
query,
|
|
321
|
+
found: false,
|
|
322
|
+
scope,
|
|
323
|
+
searched: scopeResult.searched,
|
|
324
|
+
nextRecommendedAction: {
|
|
325
|
+
tool: 'qa_test_this',
|
|
326
|
+
args: { ...(sessionId ? { sessionId } : { projectRoot: r.ctx.root }), goal: 'explore' },
|
|
327
|
+
why: 'Grow the map with an initial run, then re-scope the feature',
|
|
328
|
+
},
|
|
329
|
+
}, `🔎 No feature matched "${query}". Searched ${scopeResult.searched.symbols} symbols, ${scopeResult.searched.routes} routes, ${scopeResult.searched.files} files, ${scopeResult.searched.runtimeScreens} runtime screens with terms: ${scopeResult.searched.terms.slice(0, 12).join(', ')}. Run qa_test_this/qa_explore to grow coverage, or refine the feature name.`);
|
|
330
|
+
}
|
|
331
|
+
if (scopeResult.needsInput) {
|
|
332
|
+
return qaNeedsInput({
|
|
333
|
+
needsInput: true,
|
|
334
|
+
kind: 'monorepo_target',
|
|
335
|
+
question: scopeResult.needsInput.question,
|
|
336
|
+
fields: [{ name: 'query', description: 'The exact feature to test', example: scopeResult.candidates[0]?.title }],
|
|
337
|
+
fallbackOptions: scopeResult.needsInput.options,
|
|
338
|
+
resume: { tool: 'qa_app_map_feature_scope', args: {} },
|
|
339
|
+
attempted: [`scoped "${query}" matched ${scopeResult.candidates.length} distinct candidates that tie`],
|
|
340
|
+
ifDeclined: 'Swipium scopes the highest-confidence candidate and records the others as alternatives.',
|
|
341
|
+
}, { sessionId: sessionId ?? undefined, candidates: scopeResult.candidates });
|
|
342
|
+
}
|
|
343
|
+
const nextRecommendedAction = scope.recommendedStrategy === 'manual_blocked'
|
|
344
|
+
? {
|
|
345
|
+
tool: 'qa_test_feature',
|
|
346
|
+
args: { ...(sessionId ? { sessionId } : { projectRoot: r.ctx.root }), feature: query, mode: 'plan' },
|
|
347
|
+
why: 'Review the plan + setup needed before any automated execution',
|
|
348
|
+
}
|
|
349
|
+
: {
|
|
350
|
+
tool: 'qa_test_feature',
|
|
351
|
+
args: { sessionId: sessionId ?? '${sessionId}', feature: query, mode: 'execute' },
|
|
352
|
+
why: 'Run a focused test of this feature',
|
|
353
|
+
};
|
|
354
|
+
return qaOk({
|
|
355
|
+
sessionId: sessionId ?? null,
|
|
356
|
+
query,
|
|
357
|
+
found: true,
|
|
358
|
+
featureId: scope.featureId,
|
|
359
|
+
scope,
|
|
360
|
+
objective,
|
|
361
|
+
appMapUri: r.ctx.appMapUri,
|
|
362
|
+
mapFeatureId: scopeResult.mapFeatureId ?? null,
|
|
363
|
+
ticketRefs: scopeResult.ticketRefs,
|
|
364
|
+
runtimeSource: scopeResult.runtimeSource,
|
|
365
|
+
candidates: scopeResult.candidates,
|
|
366
|
+
searched: scopeResult.searched,
|
|
367
|
+
codeIndex: { scannedFiles: index.scannedFiles, truncated: index.truncated },
|
|
368
|
+
nextRecommendedAction,
|
|
369
|
+
}, `🔎 ${scope.title} (confidence ${Math.round(scope.confidence * 100)}%, strategy ${scope.recommendedStrategy}): ` +
|
|
370
|
+
`${scope.staticScreens.length} static screen(s), ${scope.runtimeScreens.length} runtime screen(s), ${scope.functions.length} symbol(s), ${scope.existingTests.length} existing test(s).` +
|
|
371
|
+
(scopeResult.candidates.length > 1 ? ` ${scopeResult.candidates.length} candidate(s).` : ''));
|
|
372
|
+
}
|
|
373
|
+
// FEATURE-ID path: exact map lookup (requires an existing map).
|
|
374
|
+
const { root, hint, error } = await rootFor(server, sessions, { projectRoot, sessionId });
|
|
222
375
|
if (!root)
|
|
223
|
-
return
|
|
376
|
+
return error ?? unresolvedProjectRootError({ source: 'none', hint });
|
|
224
377
|
remember(root);
|
|
225
378
|
const map = readExistingMap(root);
|
|
226
379
|
if (!map)
|
|
227
|
-
return qaError({
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
return qaError({ what: 'Provide one of: featureId or query', changedState: false, retrySafe: true, nextSteps: ['e.g. qa_app_map_feature_scope { query:"login" }'] });
|
|
243
|
-
}
|
|
380
|
+
return qaError({
|
|
381
|
+
what: 'No app map yet',
|
|
382
|
+
changedState: false,
|
|
383
|
+
retrySafe: true,
|
|
384
|
+
nextSteps: ['Run qa_app_map_build first, or pass query= for map-free scoping.'],
|
|
385
|
+
failureCode: 'NO_APP_MAP',
|
|
386
|
+
});
|
|
387
|
+
const features = map.features.filter((f) => f.id === featureId);
|
|
388
|
+
if (!features.length)
|
|
389
|
+
return qaError({
|
|
390
|
+
what: `Unknown featureId ${featureId}`,
|
|
391
|
+
changedState: false,
|
|
392
|
+
retrySafe: true,
|
|
393
|
+
nextSteps: ['List ids with qa_app_map_read { section:"features" }, or pass query= for free-text scoping.'],
|
|
394
|
+
});
|
|
244
395
|
const scope = features.map((f) => {
|
|
245
|
-
const staticScreens = f.staticScreens
|
|
396
|
+
const staticScreens = f.staticScreens
|
|
397
|
+
.map((id) => map.staticTopology.screens.find((s) => s.id === id))
|
|
398
|
+
.filter(Boolean)
|
|
399
|
+
.map((s) => ({ id: s.id, name: s.name, route: s.route, sourceFiles: s.sourceFiles }));
|
|
246
400
|
const needsCreds = f.id === 'feature:auth' || f.blockers.some((b) => /credential/i.test(b));
|
|
247
401
|
const recommendedGoal = f.id === 'feature:auth' ? 'test_login' : 'reproduce_bug';
|
|
248
402
|
return {
|
|
@@ -259,156 +413,134 @@ export function registerAppMap(server, sessions) {
|
|
|
259
413
|
blockers: f.blockers,
|
|
260
414
|
recommendedPlan: {
|
|
261
415
|
tool: 'qa_test_this',
|
|
262
|
-
args: {
|
|
263
|
-
|
|
416
|
+
args: {
|
|
417
|
+
mode: 'execute',
|
|
418
|
+
goal: recommendedGoal,
|
|
419
|
+
goalText: f.title,
|
|
420
|
+
explore: true,
|
|
421
|
+
...(needsCreds ? { stopOnNeedsInput: true } : {}),
|
|
422
|
+
},
|
|
423
|
+
why: needsCreds
|
|
424
|
+
? `Drive "${f.title}"; will stop for test credentials (fixture); ${f.testCoverage} coverage today`
|
|
425
|
+
: `Drive "${f.title}" with focused exploration; ${f.testCoverage} coverage today`,
|
|
264
426
|
},
|
|
265
427
|
};
|
|
266
428
|
});
|
|
267
429
|
const text = `scope: ${scope.length} feature(s)\n` +
|
|
268
|
-
scope
|
|
430
|
+
scope
|
|
431
|
+
.map((s) => ` • ${s.title} [${s.testCoverage}]: ${s.staticScreens.length} screen(s), ${s.sourceFiles.length} file(s)${s.blockers.length ? ` · blockers: ${s.blockers.join(', ')}` : ''}\n > ${s.recommendedPlan.tool} ${JSON.stringify(s.recommendedPlan.args)}`)
|
|
432
|
+
.join('\n');
|
|
269
433
|
return qaOk({ appMapUri: appMapResourceUri(root), featureId: scope[0]?.featureId, scope }, text);
|
|
270
434
|
});
|
|
271
|
-
// --------------------------------------------------------------- validate
|
|
272
|
-
server.registerTool('qa_app_map_validate', {
|
|
273
|
-
title: 'Validate the app knowledge map',
|
|
274
|
-
description: 'Validate the app map: schema version, provenance completeness, missing/dangling links, duplicate ids, impossible states (e.g. covered-without-runtime), and (optionally) stale source fingerprints. Returns errors + warnings with codes. Use before trusting an old map for feature-focused testing.',
|
|
275
|
-
inputSchema: {
|
|
276
|
-
projectRoot: z.string().optional(),
|
|
277
|
-
sessionId: z.string().optional(),
|
|
278
|
-
checkFingerprint: z.boolean().optional().describe('Also re-hash fingerprinted source files to detect a stale map (default false).'),
|
|
279
|
-
},
|
|
280
|
-
outputSchema: { ok: z.boolean(), valid: z.boolean().optional(), errors: z.number().optional(), warnings: z.number().optional() },
|
|
281
|
-
}, async ({ projectRoot, sessionId, checkFingerprint }) => {
|
|
282
|
-
const { root, hint } = await rootFor(server, sessions, { projectRoot, sessionId });
|
|
283
|
-
if (!root)
|
|
284
|
-
return qaError({ what: 'Could not resolve a project root', changedState: false, retrySafe: true, nextSteps: ['Pass projectRoot or sessionId.'], clientHint: hint });
|
|
285
|
-
remember(root);
|
|
286
|
-
const map = readExistingMap(root);
|
|
287
|
-
if (!map)
|
|
288
|
-
return qaError({ what: 'No app map yet', changedState: false, retrySafe: true, nextSteps: ['Run qa_app_map_build first.'], failureCode: 'NO_APP_MAP' });
|
|
289
|
-
const v = validateAppMap(map, { checkFingerprint, root });
|
|
290
|
-
const text = `${v.ok ? '✅' : '❌'} app map ${v.ok ? 'valid' : 'INVALID'} — ${v.errors} error(s), ${v.warnings} warning(s)\n` +
|
|
291
|
-
v.issues.slice(0, 12).map((i) => ` ${i.severity === 'error' ? '✗' : '⚠'} ${i.code}: ${i.detail}`).join('\n');
|
|
292
|
-
return qaOk({ valid: v.ok, errors: v.errors, warnings: v.warnings, issues: v.issues, appMapUri: appMapResourceUri(root) }, text);
|
|
293
|
-
});
|
|
294
435
|
// ----------------------------------------------------------------- update
|
|
295
436
|
server.registerTool('qa_app_map_update', {
|
|
296
437
|
title: 'Update the app knowledge map',
|
|
297
|
-
description:
|
|
438
|
+
description: 'Targeted, provenance-tracked app-map edits without a rebuild: add a note, register test cases, link an automation ' +
|
|
439
|
+
"suite to features/screens, set the environment, or override a feature's coverage. Existing entries with the same " +
|
|
440
|
+
'id/path are overwritten.',
|
|
298
441
|
inputSchema: {
|
|
299
442
|
projectRoot: z.string().optional(),
|
|
300
443
|
sessionId: z.string().optional(),
|
|
301
444
|
note: z.string().optional().describe('A free-text user note added with user_note provenance.'),
|
|
302
|
-
testCases: z
|
|
303
|
-
|
|
445
|
+
testCases: z
|
|
446
|
+
.array(z.object({
|
|
447
|
+
id: z.string(),
|
|
448
|
+
title: z.string(),
|
|
449
|
+
featureId: z.string().optional(),
|
|
450
|
+
screenId: z.string().optional(),
|
|
451
|
+
status: z.string().optional(),
|
|
452
|
+
source: z.string().optional(),
|
|
453
|
+
stale: z.boolean().optional(),
|
|
454
|
+
}))
|
|
455
|
+
.optional(),
|
|
456
|
+
automationSuite: z
|
|
457
|
+
.object({
|
|
458
|
+
name: z.string(),
|
|
459
|
+
path: z.string(),
|
|
460
|
+
framework: z.string().optional(),
|
|
461
|
+
linkedFeatureIds: z.array(z.string()).optional(),
|
|
462
|
+
linkedScreenIds: z.array(z.string()).optional(),
|
|
463
|
+
})
|
|
464
|
+
.optional(),
|
|
304
465
|
environment: z.string().optional().describe("App environment, e.g. 'test' | 'staging'."),
|
|
305
466
|
featureCoverage: z.object({ featureId: z.string(), coverage: z.enum(['none', 'partial', 'covered']) }).optional(),
|
|
306
467
|
},
|
|
307
|
-
outputSchema: { ok: z.boolean(), appMapUri: z.string().optional(), applied: z.array(z.string()).optional() },
|
|
308
468
|
}, async ({ projectRoot, sessionId, note, testCases, automationSuite, environment, featureCoverage }) => {
|
|
309
|
-
const { root, hint } = await rootFor(server, sessions, { projectRoot, sessionId });
|
|
469
|
+
const { root, hint, error } = await rootFor(server, sessions, { projectRoot, sessionId });
|
|
310
470
|
if (!root)
|
|
311
|
-
return
|
|
471
|
+
return error ?? unresolvedProjectRootError({ source: 'none', hint });
|
|
312
472
|
remember(root);
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
473
|
+
// Synchronous load > mutate > save cycle, held under the cross-process app-map lock (see store.ts).
|
|
474
|
+
return withAppMapLock(root, () => {
|
|
475
|
+
const map = readExistingMap(root);
|
|
476
|
+
if (!map)
|
|
477
|
+
return qaError({
|
|
478
|
+
what: 'No app map yet',
|
|
479
|
+
changedState: false,
|
|
480
|
+
retrySafe: true,
|
|
481
|
+
nextSteps: ['Run qa_app_map_build first.'],
|
|
482
|
+
failureCode: 'NO_APP_MAP',
|
|
483
|
+
});
|
|
484
|
+
const at = nowIso();
|
|
485
|
+
const applied = [];
|
|
486
|
+
if (note) {
|
|
487
|
+
addProvenance(map, makeProvenance('user_note', at, note, { targetType: 'map' }));
|
|
488
|
+
applied.push('note');
|
|
329
489
|
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
map.
|
|
339
|
-
|
|
340
|
-
applied.push('automationSuite');
|
|
341
|
-
}
|
|
342
|
-
if (environment) {
|
|
343
|
-
map.appIdentity.environment = environment;
|
|
344
|
-
applied.push('environment');
|
|
345
|
-
}
|
|
346
|
-
if (featureCoverage) {
|
|
347
|
-
const f = map.features.find((x) => x.id === featureCoverage.featureId);
|
|
348
|
-
if (!f)
|
|
349
|
-
return qaError({ what: `Unknown featureId ${featureCoverage.featureId}`, changedState: false, retrySafe: true, nextSteps: ['List ids with qa_app_map_read { section:"features" }.'] });
|
|
350
|
-
f.testCoverage = featureCoverage.coverage;
|
|
351
|
-
addProvenance(map, makeProvenance('user_note', at, `coverage(${f.id})=${featureCoverage.coverage}`, { targetType: 'feature', targetId: f.id }));
|
|
352
|
-
applied.push('featureCoverage');
|
|
353
|
-
}
|
|
354
|
-
if (!applied.length)
|
|
355
|
-
return qaError({ what: 'No update fields provided', changedState: false, retrySafe: true, nextSteps: ['Pass at least one of: note, testCases, automationSuite, environment, featureCoverage.'] });
|
|
356
|
-
map.updatedAt = at;
|
|
357
|
-
map.coverage.staleTests = map.testSuite.cases.filter((c) => c.stale).length;
|
|
358
|
-
recomputeConfidence(map);
|
|
359
|
-
const save = saveAppMap(root, map);
|
|
360
|
-
saveIndexes(root, loadCodeIndex(root), map.features);
|
|
361
|
-
return qaOk({ appMapUri: save.resourceUri, applied }, `app map updated: ${applied.join(', ')}\nappMapUri: ${save.resourceUri}`);
|
|
362
|
-
});
|
|
363
|
-
// ------------------------------------------------------------------- diff
|
|
364
|
-
server.registerTool('qa_app_map_diff', {
|
|
365
|
-
title: 'Diff two app map snapshots',
|
|
366
|
-
description: 'Compare two App Knowledge Map snapshots and report added/removed/changed screens, feature coverage changes, locator-readiness changes, stale test cases, and new untested code areas. With no paths, compares the most recent history snapshot (baseline) against the current map.',
|
|
367
|
-
inputSchema: {
|
|
368
|
-
projectRoot: z.string().optional(),
|
|
369
|
-
sessionId: z.string().optional(),
|
|
370
|
-
baseline: z.string().optional().describe('Path to a baseline app-map.json (or history snapshot). Defaults to the latest history snapshot.'),
|
|
371
|
-
current: z.string().optional().describe('Path to the current app-map.json. Defaults to .swipium/app-map.json.'),
|
|
372
|
-
},
|
|
373
|
-
outputSchema: { ok: z.boolean(), summary: z.string().optional() },
|
|
374
|
-
}, async ({ projectRoot, sessionId, baseline, current }) => {
|
|
375
|
-
const { root, hint } = await rootFor(server, sessions, { projectRoot, sessionId });
|
|
376
|
-
if (!root)
|
|
377
|
-
return qaError({ what: 'Could not resolve a project root', changedState: false, retrySafe: true, nextSteps: ['Pass projectRoot or sessionId.'], clientHint: hint });
|
|
378
|
-
remember(root);
|
|
379
|
-
const readMap = (p) => {
|
|
380
|
-
if (!existsSync(p))
|
|
381
|
-
return null;
|
|
382
|
-
try {
|
|
383
|
-
const raw = JSON.parse(readFileSync(p, 'utf8'));
|
|
384
|
-
return migrateAppMap(raw, fallbackProject(root), nowIso()).map;
|
|
490
|
+
if (testCases?.length) {
|
|
491
|
+
for (const c of testCases) {
|
|
492
|
+
const existing = map.testSuite.cases.find((x) => x.id === c.id);
|
|
493
|
+
if (existing)
|
|
494
|
+
Object.assign(existing, c);
|
|
495
|
+
else
|
|
496
|
+
map.testSuite.cases.push(c);
|
|
497
|
+
}
|
|
498
|
+
addProvenance(map, makeProvenance('test_case', at, `${testCases.length} test case(s) registered`, { targetType: 'test' }));
|
|
499
|
+
applied.push(`testCases(${testCases.length})`);
|
|
385
500
|
}
|
|
386
|
-
|
|
387
|
-
|
|
501
|
+
if (automationSuite) {
|
|
502
|
+
const existing = map.automation.suites.find((s) => s.path === automationSuite.path);
|
|
503
|
+
if (existing)
|
|
504
|
+
Object.assign(existing, automationSuite);
|
|
505
|
+
else
|
|
506
|
+
map.automation.suites.push(automationSuite);
|
|
507
|
+
addProvenance(map, makeProvenance('test_case', at, `Automation suite ${automationSuite.name} linked`, {
|
|
508
|
+
targetType: 'test',
|
|
509
|
+
refs: [automationSuite.path],
|
|
510
|
+
}));
|
|
511
|
+
applied.push('automationSuite');
|
|
388
512
|
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
return qaError({ what: 'No current app map to diff', changedState: false, retrySafe: true, nextSteps: ['Run qa_app_map_build first, or pass current=.'], failureCode: 'NO_APP_MAP' });
|
|
393
|
-
let baselineMap = baseline ? readMap(baseline) : null;
|
|
394
|
-
if (!baselineMap && !baseline) {
|
|
395
|
-
const { appMapHistoryDir } = await import('../appMap/store.js');
|
|
396
|
-
const dir = appMapHistoryDir(root);
|
|
397
|
-
try {
|
|
398
|
-
const { readdirSync } = await import('node:fs');
|
|
399
|
-
const snaps = existsSync(dir) ? readdirSync(dir).filter((f) => f.endsWith('.json')).sort() : [];
|
|
400
|
-
const prev = snaps.length >= 2 ? snaps[snaps.length - 2] : snaps[0];
|
|
401
|
-
if (prev)
|
|
402
|
-
baselineMap = readMap(`${dir}/${prev}`);
|
|
513
|
+
if (environment) {
|
|
514
|
+
map.appIdentity.environment = environment;
|
|
515
|
+
applied.push('environment');
|
|
403
516
|
}
|
|
404
|
-
|
|
405
|
-
|
|
517
|
+
if (featureCoverage) {
|
|
518
|
+
const f = map.features.find((x) => x.id === featureCoverage.featureId);
|
|
519
|
+
if (!f)
|
|
520
|
+
return qaError({
|
|
521
|
+
what: `Unknown featureId ${featureCoverage.featureId}`,
|
|
522
|
+
changedState: false,
|
|
523
|
+
retrySafe: true,
|
|
524
|
+
nextSteps: ['List ids with qa_app_map_read { section:"features" }.'],
|
|
525
|
+
});
|
|
526
|
+
f.testCoverage = featureCoverage.coverage;
|
|
527
|
+
addProvenance(map, makeProvenance('user_note', at, `coverage(${f.id})=${featureCoverage.coverage}`, { targetType: 'feature', targetId: f.id }));
|
|
528
|
+
applied.push('featureCoverage');
|
|
406
529
|
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
530
|
+
if (!applied.length)
|
|
531
|
+
return qaError({
|
|
532
|
+
what: 'No update fields provided',
|
|
533
|
+
changedState: false,
|
|
534
|
+
retrySafe: true,
|
|
535
|
+
nextSteps: ['Pass at least one of: note, testCases, automationSuite, environment, featureCoverage.'],
|
|
536
|
+
});
|
|
537
|
+
map.updatedAt = at;
|
|
538
|
+
map.coverage.staleTests = map.testSuite.cases.filter((c) => c.stale).length;
|
|
539
|
+
recomputeConfidence(map);
|
|
540
|
+
const save = saveAppMap(root, map);
|
|
541
|
+
saveIndexes(root, loadCodeIndex(root), map.features);
|
|
542
|
+
return qaOk({ appMapUri: save.resourceUri, applied }, `app map updated: ${applied.join(', ')}\nappMapUri: ${save.resourceUri}`);
|
|
543
|
+
});
|
|
412
544
|
});
|
|
413
545
|
}
|
|
414
546
|
/** Resolve a feature/screen/test-suite/full app-map resource read for the MCP resource handler.
|
|
@@ -436,4 +568,76 @@ export function readAppMapResource(root, sub) {
|
|
|
436
568
|
}
|
|
437
569
|
return null;
|
|
438
570
|
}
|
|
571
|
+
/** Project roots whose app map can be listed RIGHT NOW: roots touched this server run
|
|
572
|
+
* (in-memory registry) plus live-session roots, filtered to those with an app map on disk.
|
|
573
|
+
* Deliberately NOT the durable machine-wide registry. Listing every project ever mapped on
|
|
574
|
+
* this machine into an unrelated client session would be noise, not discovery. */
|
|
575
|
+
function appMapRoots(sessions) {
|
|
576
|
+
const roots = new Set(projectRegistry.values());
|
|
577
|
+
for (const s of sessions.list())
|
|
578
|
+
roots.add(s.root);
|
|
579
|
+
return [...roots].filter((r) => existsSync(appMapPath(r)));
|
|
580
|
+
}
|
|
581
|
+
/** Enumerate the actually-readable app-map resource URIs for the MCP resources/list callbacks.
|
|
582
|
+
* `full`: one complete-map URI per known project; `sections`: the per-feature /
|
|
583
|
+
* per-screen / test-suite section URIs served by readAppMapResource above (same URI shapes
|
|
584
|
+
* qa_app_map_read emits). Section ids are percent-encoded (encodeUriSegment) so ids containing
|
|
585
|
+
* `/`, spaces or `(` still match the `{kind}/{id}` template; the read handlers decode them.
|
|
586
|
+
* Read-only and never throws: an unparseable map is skipped, and an
|
|
587
|
+
* empty world lists as []. Caller applies any cap. */
|
|
588
|
+
export function listAppMapResources(sessions, which) {
|
|
589
|
+
const out = [];
|
|
590
|
+
for (const root of appMapRoots(sessions)) {
|
|
591
|
+
try {
|
|
592
|
+
const map = loadAppMap(root, fallbackProject(root), nowIso()).map;
|
|
593
|
+
if (!map)
|
|
594
|
+
continue;
|
|
595
|
+
const base = appMapResourceUri(root);
|
|
596
|
+
const project = map.appIdentity.appName ?? basename(root);
|
|
597
|
+
if (which === 'full') {
|
|
598
|
+
out.push({
|
|
599
|
+
uri: base,
|
|
600
|
+
name: `app-map (${project})`,
|
|
601
|
+
mimeType: 'application/json',
|
|
602
|
+
description: `Complete app knowledge map for ${root}`,
|
|
603
|
+
});
|
|
604
|
+
continue;
|
|
605
|
+
}
|
|
606
|
+
for (const f of map.features) {
|
|
607
|
+
out.push({
|
|
608
|
+
uri: `${base}/feature/${encodeUriSegment(f.id)}`,
|
|
609
|
+
name: f.title,
|
|
610
|
+
mimeType: 'application/json',
|
|
611
|
+
description: `feature section: ${project}`,
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
// Static + runtime screens share the /screen/{id} read path; dedupe on id (static wins).
|
|
615
|
+
const screens = new Map();
|
|
616
|
+
for (const s of map.staticTopology.screens)
|
|
617
|
+
screens.set(s.id, s.name);
|
|
618
|
+
for (const r of map.runtimeTopology.screens)
|
|
619
|
+
if (!screens.has(r.id))
|
|
620
|
+
screens.set(r.id, r.title ?? r.id);
|
|
621
|
+
for (const [id, name] of screens) {
|
|
622
|
+
out.push({
|
|
623
|
+
uri: `${base}/screen/${encodeUriSegment(id)}`,
|
|
624
|
+
name,
|
|
625
|
+
mimeType: 'application/json',
|
|
626
|
+
description: `screen section: ${project}`,
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
// The read path ignores {id} for test-suite; "cases" is the listed canonical spelling.
|
|
630
|
+
out.push({
|
|
631
|
+
uri: `${base}/test-suite/cases`,
|
|
632
|
+
name: `test-suite (${project})`,
|
|
633
|
+
mimeType: 'application/json',
|
|
634
|
+
description: `test-suite section: ${map.testSuite.cases.length} cases`,
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
catch {
|
|
638
|
+
// Listing must never throw. A broken map is simply not browsable; reads still error loudly.
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
return out;
|
|
642
|
+
}
|
|
439
643
|
//# sourceMappingURL=appMap.js.map
|