swipium 1.3.0 → 1.5.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 +142 -0
- package/README.md +89 -25
- package/THREAT_MODEL.md +1 -1
- package/dist/appMap/automationLink.js +13 -3
- package/dist/appMap/automationLink.js.map +1 -1
- package/dist/appMap/build.js +28 -6
- package/dist/appMap/build.js.map +1 -1
- package/dist/appMap/codeIndex.js +29 -3
- package/dist/appMap/codeIndex.js.map +1 -1
- package/dist/appMap/featureIndex.js +20 -2
- package/dist/appMap/featureIndex.js.map +1 -1
- package/dist/appMap/featureModel.js +42 -6
- package/dist/appMap/featureModel.js.map +1 -1
- package/dist/appMap/firstRunApply.js +22 -4
- 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 +0 -20
- package/dist/appMap/issues.js.map +1 -1
- package/dist/appMap/migrations.js +1 -1
- package/dist/appMap/migrations.js.map +1 -1
- package/dist/appMap/prelaunch.js.map +1 -1
- package/dist/appMap/projectRegistry.js +8 -2
- package/dist/appMap/projectRegistry.js.map +1 -1
- package/dist/appMap/query.js +20 -4
- package/dist/appMap/query.js.map +1 -1
- package/dist/appMap/runtimeMerge.js +9 -2
- package/dist/appMap/runtimeMerge.js.map +1 -1
- package/dist/appMap/schema.js.map +1 -1
- package/dist/appMap/screenMatch.js +5 -1
- package/dist/appMap/screenMatch.js.map +1 -1
- package/dist/appMap/staticScan.js +134 -22
- package/dist/appMap/staticScan.js.map +1 -1
- package/dist/appMap/store.js +4 -79
- package/dist/appMap/store.js.map +1 -1
- package/dist/appMap/tsAstScan.js +31 -9
- package/dist/appMap/tsAstScan.js.map +1 -1
- package/dist/artifacts/bundletool.js +44 -7
- package/dist/artifacts/bundletool.js.map +1 -1
- package/dist/artifacts/resolve.js +33 -6
- package/dist/artifacts/resolve.js.map +1 -1
- package/dist/automation/capabilities.js +109 -27
- package/dist/automation/capabilities.js.map +1 -1
- package/dist/automation/gestures.js +5 -1
- package/dist/automation/gestures.js.map +1 -1
- package/dist/automation/plan.js +65 -17
- package/dist/automation/plan.js.map +1 -1
- package/dist/automation/report.js +12 -2
- package/dist/automation/report.js.map +1 -1
- package/dist/automation/selectors.js +8 -3
- package/dist/automation/selectors.js.map +1 -1
- package/dist/automation/types.js.map +1 -1
- package/dist/automation/waits.js +9 -3
- package/dist/automation/waits.js.map +1 -1
- package/dist/automation/webview.js +7 -1
- package/dist/automation/webview.js.map +1 -1
- package/dist/automationGen/appiumModel.js +3 -1
- package/dist/automationGen/appiumModel.js.map +1 -1
- package/dist/automationGen/ciEmitter.js +1 -1
- package/dist/automationGen/ciEmitter.js.map +1 -1
- package/dist/automationGen/jsEmitter.js +29 -21
- package/dist/automationGen/jsEmitter.js.map +1 -1
- package/dist/automationGen/projectProfile.js +5 -1
- package/dist/automationGen/projectProfile.js.map +1 -1
- package/dist/automationGen/pythonEmitter.js +3 -3
- package/dist/automationGen/pythonEmitter.js.map +1 -1
- package/dist/automationGen/readmeEmitter.js.map +1 -1
- package/dist/automationGen/suitePlan.js +11 -6
- package/dist/automationGen/suitePlan.js.map +1 -1
- package/dist/automationGen/validation.js +33 -5
- package/dist/automationGen/validation.js.map +1 -1
- package/dist/build/parseBuildLog.js +234 -30
- package/dist/build/parseBuildLog.js.map +1 -1
- package/dist/build/plan.js +33 -12
- package/dist/build/plan.js.map +1 -1
- package/dist/ci/preflight.js.map +1 -1
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/scan.js +1 -1
- package/dist/cli/scan.js.map +1 -1
- package/dist/cli/suite.js +2 -2
- package/dist/cli/suite.js.map +1 -1
- package/dist/cli/verify.js.map +1 -1
- package/dist/context/detect.js +10 -4
- package/dist/context/detect.js.map +1 -1
- package/dist/context/findApps.js +16 -2
- package/dist/context/findApps.js.map +1 -1
- package/dist/context/scan.js.map +1 -1
- package/dist/core/target.js +7 -5
- package/dist/core/target.js.map +1 -1
- package/dist/core/targetPlan.js +135 -17
- package/dist/core/targetPlan.js.map +1 -1
- package/dist/drivers/DirectDriver.js +17 -7
- package/dist/drivers/DirectDriver.js.map +1 -1
- package/dist/drivers/SimctlDriver.js +4 -1
- package/dist/drivers/SimctlDriver.js.map +1 -1
- package/dist/drivers/WdaDriver.js +15 -5
- package/dist/drivers/WdaDriver.js.map +1 -1
- package/dist/explore/candidates.js +5 -1
- package/dist/explore/candidates.js.map +1 -1
- package/dist/explore/graph.js +11 -2
- 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 +6 -1
- package/dist/explore/policy.js.map +1 -1
- package/dist/explore/runner.js +179 -23
- package/dist/explore/runner.js.map +1 -1
- package/dist/explore/signatures.js +3 -1
- 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 +128 -19
- package/dist/featureTesting/executionBootstrap.js.map +1 -1
- package/dist/featureTesting/featureMap.js +4 -1
- package/dist/featureTesting/featureMap.js.map +1 -1
- package/dist/featureTesting/featureScope.js +94 -13
- package/dist/featureTesting/featureScope.js.map +1 -1
- package/dist/featureTesting/mapFeatureScope.js +37 -8
- package/dist/featureTesting/mapFeatureScope.js.map +1 -1
- package/dist/featureTesting/objectiveModel.js +5 -1
- package/dist/featureTesting/objectiveModel.js.map +1 -1
- package/dist/featureTesting/resultMerge.js +10 -2
- package/dist/featureTesting/resultMerge.js.map +1 -1
- package/dist/featureTesting/sources.js +4 -1
- package/dist/featureTesting/sources.js.map +1 -1
- package/dist/featureTesting/suiteBridge.js +7 -1
- package/dist/featureTesting/suiteBridge.js.map +1 -1
- package/dist/featureTesting/synonyms.js +31 -3
- package/dist/featureTesting/synonyms.js.map +1 -1
- package/dist/featureTesting/testCaseFactory.js +16 -4
- package/dist/featureTesting/testCaseFactory.js.map +1 -1
- package/dist/featureTesting/testPlan.js +32 -10
- package/dist/featureTesting/testPlan.js.map +1 -1
- package/dist/firstRun/classifyScreen.js +4 -1
- package/dist/firstRun/classifyScreen.js.map +1 -1
- package/dist/firstRun/firstRunPlanner.js +5 -2
- package/dist/firstRun/firstRunPlanner.js.map +1 -1
- package/dist/firstRun/firstRunRunner.js +84 -23
- package/dist/firstRun/firstRunRunner.js.map +1 -1
- package/dist/firstRun/generatedDataPolicy.js +16 -6
- package/dist/firstRun/generatedDataPolicy.js.map +1 -1
- package/dist/firstRun/inputPlanner.js +40 -6
- package/dist/firstRun/inputPlanner.js.map +1 -1
- package/dist/firstRun/paywallPolicy.js.map +1 -1
- package/dist/fixtures/catalog.js +39 -6
- package/dist/fixtures/catalog.js.map +1 -1
- package/dist/flows/generate.js +53 -12
- 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 +18 -2
- package/dist/flows/pack.js.map +1 -1
- package/dist/flows/repair.js +71 -20
- package/dist/flows/repair.js.map +1 -1
- package/dist/flows/run.js +164 -51
- package/dist/flows/run.js.map +1 -1
- package/dist/flows/schema.js +57 -20
- package/dist/flows/schema.js.map +1 -1
- package/dist/flows/seedExec.js +5 -1
- package/dist/flows/seedExec.js.map +1 -1
- package/dist/ios/signing.js +32 -3
- package/dist/ios/signing.js.map +1 -1
- package/dist/issues/classify.js +12 -4
- package/dist/issues/classify.js.map +1 -1
- package/dist/issues/fingerprint.js +11 -5
- package/dist/issues/fingerprint.js.map +1 -1
- package/dist/issues/index.js +4 -20
- package/dist/issues/index.js.map +1 -1
- package/dist/issues/metrics.js +2 -1
- package/dist/issues/metrics.js.map +1 -1
- package/dist/issues/recurrence.js.map +1 -1
- package/dist/issues/report.js +8 -4
- package/dist/issues/report.js.map +1 -1
- package/dist/issues/reportBridge.js +36 -5
- package/dist/issues/reportBridge.js.map +1 -1
- package/dist/issues/schema.js.map +1 -1
- package/dist/issues/store.js.map +1 -1
- package/dist/lib/android.js +9 -1
- package/dist/lib/android.js.map +1 -1
- package/dist/lib/device.js +21 -5
- package/dist/lib/device.js.map +1 -1
- package/dist/lib/image.js +11 -2
- package/dist/lib/image.js.map +1 -1
- package/dist/lib/lockfile.js +60 -0
- package/dist/lib/lockfile.js.map +1 -0
- package/dist/lib/needsInput.js +6 -3
- package/dist/lib/needsInput.js.map +1 -1
- package/dist/lib/png.js +2 -1
- package/dist/lib/png.js.map +1 -1
- package/dist/lib/result.js +16 -1
- package/dist/lib/result.js.map +1 -1
- package/dist/lib/schemaHash.js +4 -2
- package/dist/lib/schemaHash.js.map +1 -1
- package/dist/lib/sensitive.js +3 -1
- package/dist/lib/sensitive.js.map +1 -1
- package/dist/lib/simctl.js.map +1 -1
- package/dist/lib/spawn.js +29 -10
- package/dist/lib/spawn.js.map +1 -1
- package/dist/lib/wda.js +27 -10
- 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 +141 -19
- package/dist/mobileAudit/checks.js.map +1 -1
- package/dist/mobileAudit/profiles.js +120 -19
- package/dist/mobileAudit/profiles.js.map +1 -1
- package/dist/mobileAudit/results.js +7 -2
- package/dist/mobileAudit/results.js.map +1 -1
- package/dist/mobileAudit/runner.js +144 -19
- package/dist/mobileAudit/runner.js.map +1 -1
- package/dist/oracle/auth.js.map +1 -1
- package/dist/oracle/failures.js +888 -131
- package/dist/oracle/failures.js.map +1 -1
- package/dist/oracle/health.js +90 -14
- package/dist/oracle/health.js.map +1 -1
- package/dist/oracle/locator.js +147 -18
- package/dist/oracle/locator.js.map +1 -1
- package/dist/oracle/record.js.map +1 -1
- package/dist/orchestration/goal.js +3 -9
- package/dist/orchestration/goal.js.map +1 -1
- package/dist/orchestration/testThis/execute.js +126 -0
- package/dist/orchestration/testThis/execute.js.map +1 -0
- package/dist/orchestration/testThis/pipeline.js +324 -0
- package/dist/orchestration/testThis/pipeline.js.map +1 -0
- package/dist/orchestration/testThis/plan.js +430 -0
- package/dist/orchestration/testThis/plan.js.map +1 -0
- package/dist/orchestration/testThis/terminal.js +118 -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.map +1 -1
- package/dist/prompts/index.js +3 -3
- 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 +59 -37
- package/dist/report/export.js.map +1 -1
- 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 +11 -3
- package/dist/report/policy.js.map +1 -1
- package/dist/report/qaLevel.js +2 -8
- 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/summary.js.map +1 -1
- package/dist/report/timing.js +23 -23
- package/dist/report/timing.js.map +1 -1
- package/dist/server.js +42 -37
- package/dist/server.js.map +1 -1
- package/dist/services/automationGenerate.js.map +1 -1
- package/dist/services/build.js +8 -2
- package/dist/services/build.js.map +1 -1
- package/dist/services/preflight.js +38 -6
- package/dist/services/preflight.js.map +1 -1
- package/dist/services/prepareAndroid.js +46 -10
- package/dist/services/prepareAndroid.js.map +1 -1
- package/dist/services/prepareIos.js +72 -10
- package/dist/services/prepareIos.js.map +1 -1
- package/dist/services/report.js +173 -37
- package/dist/services/report.js.map +1 -1
- package/dist/services/smoke.js +36 -6
- package/dist/services/smoke.js.map +1 -1
- package/dist/services/suiteGenerate.js +16 -4
- package/dist/services/suiteGenerate.js.map +1 -1
- package/dist/services/testSuiteKnowledge.js +38 -6
- package/dist/services/testSuiteKnowledge.js.map +1 -1
- package/dist/session/attach.js +16 -0
- package/dist/session/attach.js.map +1 -1
- package/dist/session/processRegistry.js +164 -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/store.js +216 -43
- package/dist/session/store.js.map +1 -1
- package/dist/snapshot/overlays.js +7 -2
- package/dist/snapshot/overlays.js.map +1 -1
- package/dist/snapshot/parse.js +2 -8
- package/dist/snapshot/parse.js.map +1 -1
- package/dist/snapshot/present.js +27 -3
- package/dist/snapshot/present.js.map +1 -1
- package/dist/state/profile.js +36 -9
- package/dist/state/profile.js.map +1 -1
- package/dist/suite/compile.js +7 -2
- package/dist/suite/compile.js.map +1 -1
- package/dist/suite/lint.js +21 -3
- package/dist/suite/lint.js.map +1 -1
- package/dist/suite/pom.js +26 -4
- package/dist/suite/pom.js.map +1 -1
- package/dist/suite/testcase.js +6 -4
- package/dist/suite/testcase.js.map +1 -1
- package/dist/testSuite/exporter.js +6 -2
- package/dist/testSuite/exporter.js.map +1 -1
- package/dist/testSuite/generator.js +31 -6
- package/dist/testSuite/generator.js.map +1 -1
- package/dist/testSuite/issueLinks.js.map +1 -1
- package/dist/testSuite/lint.js +1 -1
- package/dist/testSuite/lint.js.map +1 -1
- package/dist/testSuite/merge.js +8 -2
- package/dist/testSuite/merge.js.map +1 -1
- package/dist/testSuite/schema.js +16 -11
- package/dist/testSuite/schema.js.map +1 -1
- package/dist/testSuite/store.js +9 -1
- package/dist/testSuite/store.js.map +1 -1
- package/dist/tools/act.js +107 -43
- package/dist/tools/act.js.map +1 -1
- package/dist/tools/agent.js +123 -25
- package/dist/tools/agent.js.map +1 -1
- package/dist/tools/appControl.js +16 -3
- package/dist/tools/appControl.js.map +1 -1
- package/dist/tools/appMap.js +331 -63
- package/dist/tools/appMap.js.map +1 -1
- package/dist/tools/assertVisual.js +12 -2
- package/dist/tools/assertVisual.js.map +1 -1
- package/dist/tools/automationGenerate.js +276 -241
- package/dist/tools/automationGenerate.js.map +1 -1
- package/dist/tools/build.js +92 -41
- package/dist/tools/build.js.map +1 -1
- package/dist/tools/bundletool.js +139 -28
- package/dist/tools/bundletool.js.map +1 -1
- package/dist/tools/capabilities.js +45 -71
- package/dist/tools/capabilities.js.map +1 -1
- package/dist/tools/clearOverlay.js +45 -8
- package/dist/tools/clearOverlay.js.map +1 -1
- package/dist/tools/detectContext.js +1 -1
- package/dist/tools/detectContext.js.map +1 -1
- package/dist/tools/device.js +44 -7
- package/dist/tools/device.js.map +1 -1
- package/dist/tools/doctor.js +38 -15
- package/dist/tools/doctor.js.map +1 -1
- package/dist/tools/explore.js +96 -22
- package/dist/tools/explore.js.map +1 -1
- package/dist/tools/featureTesting.js +153 -114
- package/dist/tools/featureTesting.js.map +1 -1
- package/dist/tools/firstRun.js +95 -60
- package/dist/tools/firstRun.js.map +1 -1
- package/dist/tools/flow.js +236 -101
- package/dist/tools/flow.js.map +1 -1
- package/dist/tools/flowGenerate.js +51 -47
- package/dist/tools/flowGenerate.js.map +1 -1
- package/dist/tools/flowRepair.js +24 -4
- package/dist/tools/flowRepair.js.map +1 -1
- package/dist/tools/generate.js +236 -0
- package/dist/tools/generate.js.map +1 -0
- package/dist/tools/getArtifact.js +21 -3
- package/dist/tools/getArtifact.js.map +1 -1
- package/dist/tools/health.js +3 -1
- package/dist/tools/health.js.map +1 -1
- package/dist/tools/history.js +40 -10
- package/dist/tools/history.js.map +1 -1
- package/dist/tools/ios.js +157 -25
- package/dist/tools/ios.js.map +1 -1
- package/dist/tools/issues.js +19 -150
- package/dist/tools/issues.js.map +1 -1
- package/dist/tools/jobs.js +23 -4
- package/dist/tools/jobs.js.map +1 -1
- package/dist/tools/locator.js +36 -8
- package/dist/tools/locator.js.map +1 -1
- package/dist/tools/metro.js +61 -16
- package/dist/tools/metro.js.map +1 -1
- package/dist/tools/mobileAudit.js +67 -15
- package/dist/tools/mobileAudit.js.map +1 -1
- package/dist/tools/network.js +6 -1
- package/dist/tools/network.js.map +1 -1
- package/dist/tools/note.js +20 -6
- package/dist/tools/note.js.map +1 -1
- package/dist/tools/permissions.js +31 -6
- package/dist/tools/permissions.js.map +1 -1
- package/dist/tools/plan.js +5 -2
- package/dist/tools/plan.js.map +1 -1
- package/dist/tools/prepareIosTarget.js +31 -8
- package/dist/tools/prepareIosTarget.js.map +1 -1
- package/dist/tools/prepareTarget.js +97 -18
- package/dist/tools/prepareTarget.js.map +1 -1
- package/dist/tools/report.js +24 -5
- package/dist/tools/report.js.map +1 -1
- package/dist/tools/resolveArtifact.js +9 -5
- package/dist/tools/resolveArtifact.js.map +1 -1
- package/dist/tools/resolveTarget.js +11 -2
- package/dist/tools/resolveTarget.js.map +1 -1
- package/dist/tools/screenInfo.js +8 -2
- package/dist/tools/screenInfo.js.map +1 -1
- package/dist/tools/screenRecord.js +74 -11
- package/dist/tools/screenRecord.js.map +1 -1
- package/dist/tools/screenshot.js +23 -6
- package/dist/tools/screenshot.js.map +1 -1
- package/dist/tools/seed.js +38 -6
- package/dist/tools/seed.js.map +1 -1
- package/dist/tools/smoke.js +16 -3
- package/dist/tools/smoke.js.map +1 -1
- package/dist/tools/snapshot.js +59 -6
- package/dist/tools/snapshot.js.map +1 -1
- package/dist/tools/startSession.js +54 -9
- package/dist/tools/startSession.js.map +1 -1
- package/dist/tools/state.js +62 -15
- package/dist/tools/state.js.map +1 -1
- package/dist/tools/suite.js +350 -298
- package/dist/tools/suite.js.map +1 -1
- package/dist/tools/testSuite.js +94 -28
- package/dist/tools/testSuite.js.map +1 -1
- package/dist/tools/testThis.js +30 -793
- package/dist/tools/testThis.js.map +1 -1
- package/dist/tools/visual.js +124 -18
- package/dist/tools/visual.js.map +1 -1
- package/dist/tools/visualText.js +51 -6
- package/dist/tools/visualText.js.map +1 -1
- package/dist/tools/wait.js +18 -3
- package/dist/tools/wait.js.map +1 -1
- package/dist/tools/wda.js +157 -29
- package/dist/tools/wda.js.map +1 -1
- package/dist/version.js +17 -49
- package/dist/version.js.map +1 -1
- package/dist/visual/ocr.js +12 -2
- package/dist/visual/ocr.js.map +1 -1
- package/dist/visual/provider.js +7 -3
- package/dist/visual/provider.js.map +1 -1
- package/docs/mcp-server.md +44 -4
- package/docs/tools.md +42 -100
- package/package.json +14 -5
- 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/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/maestro.js +0 -78
- package/dist/tools/maestro.js.map +0 -1
package/dist/tools/appMap.js
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
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
6
|
import { z } from 'zod';
|
|
7
7
|
import { qaOk, qaError } from '../lib/result.js';
|
|
8
|
+
import { qaNeedsInput } from '../lib/needsInput.js';
|
|
8
9
|
import { resolveProjectRoot } from '../context/projectRoot.js';
|
|
9
10
|
import { buildAppMap, summarizeMap } from '../appMap/build.js';
|
|
10
11
|
import { queryAppMap } from '../appMap/query.js';
|
|
11
|
-
import { loadAppMap, loadCodeIndex,
|
|
12
|
+
import { loadAppMap, loadCodeIndex, saveAppMap, saveIndexes, appMapResourceUri, appMapPath, projectId } from '../appMap/store.js';
|
|
13
|
+
import { addProvenance, makeProvenance, recomputeConfidence } from '../appMap/provenance.js';
|
|
12
14
|
import { rememberProject, lookupRoot } from '../appMap/projectRegistry.js';
|
|
15
|
+
import { resolveFeatureContext } from './featureTesting.js';
|
|
13
16
|
import { detectFramework } from '../context/detect.js';
|
|
14
17
|
// projectId(root) is a one-way hash, so resource reads need a reverse lookup. We remember every root
|
|
15
18
|
// touched this session (in-memory) AND in a DURABLE registry (~/.swipium/projects.json, Fix 8) so a
|
|
@@ -52,7 +55,14 @@ async function rootFor(server, sessions, args) {
|
|
|
52
55
|
}
|
|
53
56
|
function fallbackProject(root) {
|
|
54
57
|
const fw = detectFramework(root);
|
|
55
|
-
return {
|
|
58
|
+
return {
|
|
59
|
+
root,
|
|
60
|
+
gitRemote: null,
|
|
61
|
+
packageName: null,
|
|
62
|
+
workspaceTarget: null,
|
|
63
|
+
framework: fw,
|
|
64
|
+
platforms: fw === 'native-android' ? ['android'] : fw === 'native-ios' ? ['ios'] : ['android', 'ios'],
|
|
65
|
+
};
|
|
56
66
|
}
|
|
57
67
|
/** Read an existing map WITHOUT rescanning. Returns null when no map file exists yet. */
|
|
58
68
|
function readExistingMap(root) {
|
|
@@ -80,15 +90,26 @@ export function registerAppMap(server, sessions) {
|
|
|
80
90
|
inputSchema: {
|
|
81
91
|
projectRoot: z.string().optional().describe('Absolute project root. Omit to use a session root or the MCP workspace root.'),
|
|
82
92
|
sessionId: z.string().optional().describe('Reuse a session (its root + latest exploration graph).'),
|
|
83
|
-
mode: z
|
|
93
|
+
mode: z
|
|
94
|
+
.enum(['static_only', 'runtime_merge', 'full'])
|
|
95
|
+
.optional()
|
|
96
|
+
.describe('static_only: code scan only; runtime_merge: merge the session exploration graph only; full (default): both.'),
|
|
84
97
|
includeCodeIndex: z.boolean().optional().describe('Persist a code symbol index for queries (default true).'),
|
|
85
98
|
forceRescan: z.boolean().optional().describe('Re-run the static scan even if an up-to-date map exists (default false).'),
|
|
86
99
|
},
|
|
87
|
-
|
|
100
|
+
// NOTE: no outputSchema — a declared (closed) output schema makes strict MCP clients
|
|
101
|
+
// reject BOTH the rich qaOk payload and the qaError envelope as "additional properties"
|
|
102
|
+
// (caught by test/errorContract.test.ts). structuredContent stays self-describing.
|
|
88
103
|
}, async ({ projectRoot, sessionId, mode, includeCodeIndex, forceRescan }) => {
|
|
89
104
|
const { root, session, hint } = await rootFor(server, sessions, { projectRoot, sessionId });
|
|
90
105
|
if (!root)
|
|
91
|
-
return qaError({
|
|
106
|
+
return qaError({
|
|
107
|
+
what: 'Could not resolve a project root',
|
|
108
|
+
changedState: false,
|
|
109
|
+
retrySafe: true,
|
|
110
|
+
nextSteps: ['Pass projectRoot="/abs/path" or a valid sessionId.'],
|
|
111
|
+
clientHint: hint,
|
|
112
|
+
});
|
|
92
113
|
remember(root);
|
|
93
114
|
const m = (mode ?? 'full');
|
|
94
115
|
const exploreGraph = m === 'static_only' ? null : latestExploreGraph(sessions, session);
|
|
@@ -98,7 +119,9 @@ export function registerAppMap(server, sessions) {
|
|
|
98
119
|
const merge = res.mergeResult;
|
|
99
120
|
const text = `🗺️ app map built (${m}) for ${root}\n` +
|
|
100
121
|
`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` +
|
|
101
|
-
(merge
|
|
122
|
+
(merge
|
|
123
|
+
? `merge: +${merge.newRuntimeScreens} new, ~${merge.updatedRuntimeScreens} updated, ${merge.linkedScreens} linked, ${merge.unmappedRuntimeScreens} unmapped\n`
|
|
124
|
+
: '') +
|
|
102
125
|
`confidence=${res.map.confidence.overall} · features=${res.map.features.length}\n` +
|
|
103
126
|
(Array.isArray(summary.topGaps) && summary.topGaps.length ? `gaps: ${summary.topGaps.join('; ')}\n` : '') +
|
|
104
127
|
`appMapUri: ${res.save?.resourceUri}`;
|
|
@@ -115,7 +138,12 @@ export function registerAppMap(server, sessions) {
|
|
|
115
138
|
}, text);
|
|
116
139
|
}
|
|
117
140
|
catch (e) {
|
|
118
|
-
return qaError({
|
|
141
|
+
return qaError({
|
|
142
|
+
what: `App map build failed: ${String(e)}`,
|
|
143
|
+
changedState: false,
|
|
144
|
+
retrySafe: true,
|
|
145
|
+
nextSteps: ['Check the project root is a supported mobile project.'],
|
|
146
|
+
});
|
|
119
147
|
}
|
|
120
148
|
});
|
|
121
149
|
// ------------------------------------------------------------------- read
|
|
@@ -129,29 +157,56 @@ export function registerAppMap(server, sessions) {
|
|
|
129
157
|
featureId: z.string().optional(),
|
|
130
158
|
screenId: z.string().optional(),
|
|
131
159
|
},
|
|
132
|
-
outputSchema: { ok: z.boolean(), appMapUri: z.string().optional(), section: z.string().optional() },
|
|
133
160
|
}, async ({ projectRoot, sessionId, section, featureId, screenId }) => {
|
|
134
161
|
const { root, hint } = await rootFor(server, sessions, { projectRoot, sessionId });
|
|
135
162
|
if (!root)
|
|
136
|
-
return qaError({
|
|
163
|
+
return qaError({
|
|
164
|
+
what: 'Could not resolve a project root',
|
|
165
|
+
changedState: false,
|
|
166
|
+
retrySafe: true,
|
|
167
|
+
nextSteps: ['Pass projectRoot or sessionId.'],
|
|
168
|
+
clientHint: hint,
|
|
169
|
+
});
|
|
137
170
|
remember(root);
|
|
138
171
|
const map = readExistingMap(root);
|
|
139
172
|
if (!map)
|
|
140
|
-
return qaError({
|
|
173
|
+
return qaError({
|
|
174
|
+
what: 'No app map yet',
|
|
175
|
+
changedState: false,
|
|
176
|
+
retrySafe: true,
|
|
177
|
+
nextSteps: ['Run qa_app_map_build first.'],
|
|
178
|
+
failureCode: 'NO_APP_MAP',
|
|
179
|
+
});
|
|
141
180
|
const uri = appMapResourceUri(root);
|
|
142
181
|
const sec = section ?? 'summary';
|
|
143
182
|
if (featureId) {
|
|
144
183
|
const f = map.features.find((x) => x.id === featureId);
|
|
145
184
|
if (!f)
|
|
146
|
-
return qaError({
|
|
185
|
+
return qaError({
|
|
186
|
+
what: `Unknown featureId ${featureId}`,
|
|
187
|
+
changedState: false,
|
|
188
|
+
retrySafe: true,
|
|
189
|
+
nextSteps: ['Call qa_app_map_read { section:"features" } to list ids.'],
|
|
190
|
+
});
|
|
147
191
|
return qaOk({ appMapUri: uri, section: 'feature', feature: f, featureResourceUri: `${uri}/feature/${featureId}` }, `feature ${f.title} — ${f.testCoverage} coverage, ${f.status}, confidence ${f.confidence}`);
|
|
148
192
|
}
|
|
149
193
|
if (screenId) {
|
|
150
194
|
const s = map.staticTopology.screens.find((x) => x.id === screenId);
|
|
151
195
|
const r = map.runtimeTopology.screens.find((x) => x.id === screenId);
|
|
152
196
|
if (!s && !r)
|
|
153
|
-
return qaError({
|
|
154
|
-
|
|
197
|
+
return qaError({
|
|
198
|
+
what: `Unknown screenId ${screenId}`,
|
|
199
|
+
changedState: false,
|
|
200
|
+
retrySafe: true,
|
|
201
|
+
nextSteps: ['Call qa_app_map_read { section:"screens" } to list ids.'],
|
|
202
|
+
});
|
|
203
|
+
return qaOk({
|
|
204
|
+
appMapUri: uri,
|
|
205
|
+
section: 'screen',
|
|
206
|
+
staticScreen: s ?? null,
|
|
207
|
+
runtimeScreen: r ?? null,
|
|
208
|
+
screenResourceUri: `${uri}/screen/${screenId}`,
|
|
209
|
+
}, `screen ${screenId}`);
|
|
155
210
|
}
|
|
156
211
|
switch (sec) {
|
|
157
212
|
case 'summary':
|
|
@@ -160,8 +215,21 @@ export function registerAppMap(server, sessions) {
|
|
|
160
215
|
return qaOk({
|
|
161
216
|
appMapUri: uri,
|
|
162
217
|
section: sec,
|
|
163
|
-
staticScreens: map.staticTopology.screens.map((s) => ({
|
|
164
|
-
|
|
218
|
+
staticScreens: map.staticTopology.screens.map((s) => ({
|
|
219
|
+
id: s.id,
|
|
220
|
+
name: s.name,
|
|
221
|
+
route: s.route,
|
|
222
|
+
kind: s.kind,
|
|
223
|
+
confidence: s.confidence,
|
|
224
|
+
})),
|
|
225
|
+
runtimeScreens: map.runtimeTopology.screens.map((r) => ({
|
|
226
|
+
id: r.id,
|
|
227
|
+
title: r.title,
|
|
228
|
+
visits: r.visits,
|
|
229
|
+
linkedStaticScreenId: r.linkedStaticScreenId,
|
|
230
|
+
unmapped: r.unmapped,
|
|
231
|
+
locatorReadiness: r.locatorReadiness,
|
|
232
|
+
})),
|
|
165
233
|
unvisitedStaticScreens: map.runtimeTopology.unvisitedStaticScreens,
|
|
166
234
|
}, `${map.staticTopology.screens.length} static / ${map.runtimeTopology.screens.length} runtime screens; ${map.runtimeTopology.unvisitedStaticScreens.length} unvisited`);
|
|
167
235
|
case 'features':
|
|
@@ -175,7 +243,12 @@ export function registerAppMap(server, sessions) {
|
|
|
175
243
|
case 'full':
|
|
176
244
|
default:
|
|
177
245
|
// Protect context: point at the resource instead of inlining the whole map.
|
|
178
|
-
return qaOk({
|
|
246
|
+
return qaOk({
|
|
247
|
+
appMapUri: uri,
|
|
248
|
+
section: 'full',
|
|
249
|
+
summary: summarizeMap(map),
|
|
250
|
+
note: 'Full map omitted from text to protect context — read the appMapUri resource for everything.',
|
|
251
|
+
}, `Full map at resource: ${uri}`);
|
|
179
252
|
}
|
|
180
253
|
});
|
|
181
254
|
// ------------------------------------------------------------------ query
|
|
@@ -189,57 +262,154 @@ export function registerAppMap(server, sessions) {
|
|
|
189
262
|
intent: z.enum(['feature', 'screen', 'code', 'test', 'freeform']).optional(),
|
|
190
263
|
limit: z.number().optional(),
|
|
191
264
|
},
|
|
192
|
-
outputSchema: { ok: z.boolean(), total: z.number().optional() },
|
|
193
265
|
}, async ({ query, projectRoot, sessionId, intent, limit }) => {
|
|
194
266
|
const { root, hint } = await rootFor(server, sessions, { projectRoot, sessionId });
|
|
195
267
|
if (!root)
|
|
196
|
-
return qaError({
|
|
268
|
+
return qaError({
|
|
269
|
+
what: 'Could not resolve a project root',
|
|
270
|
+
changedState: false,
|
|
271
|
+
retrySafe: true,
|
|
272
|
+
nextSteps: ['Pass projectRoot or sessionId.'],
|
|
273
|
+
clientHint: hint,
|
|
274
|
+
});
|
|
197
275
|
remember(root);
|
|
198
276
|
const map = readExistingMap(root);
|
|
199
277
|
if (!map)
|
|
200
|
-
return qaError({
|
|
278
|
+
return qaError({
|
|
279
|
+
what: 'No app map yet',
|
|
280
|
+
changedState: false,
|
|
281
|
+
retrySafe: true,
|
|
282
|
+
nextSteps: ['Run qa_app_map_build first.'],
|
|
283
|
+
failureCode: 'NO_APP_MAP',
|
|
284
|
+
});
|
|
201
285
|
const codeIndex = loadCodeIndex(root);
|
|
202
286
|
const out = queryAppMap(map, codeIndex, { query, intent, limit });
|
|
203
|
-
const top = out.results
|
|
287
|
+
const top = out.results
|
|
288
|
+
.slice(0, 5)
|
|
289
|
+
.map((r, i) => ` ${i + 1}. [${r.type}] ${r.title} (score ${r.score}${r.confidence !== undefined ? `, conf ${r.confidence}` : ''}) → ${r.recommendedNextTool.tool}`)
|
|
290
|
+
.join('\n');
|
|
204
291
|
return qaOk({ ...out, appMapUri: appMapResourceUri(root) }, `🔎 "${query}" — ${out.total} result(s)\n${top || ' (no matches)'}`);
|
|
205
292
|
});
|
|
206
293
|
// ----------------------------------------------------------- feature_scope
|
|
207
294
|
server.registerTool('qa_app_map_feature_scope', {
|
|
208
295
|
title: 'Scope testing to a feature',
|
|
209
|
-
description: 'Resolve a feature (by featureId or free-text query) to a focused test scope
|
|
296
|
+
description: 'Resolve a feature (by featureId or free-text query) to a focused test scope. READ-ONLY — no device, no mutation. With query (e.g. "weather analysis"), it scopes app-map-FIRST and falls back to a fresh code-index scan + the latest exploration screen graph when no map exists yet — returning ranked code symbols, static + runtime screens, existing tests, an inferred objective model, coverage gaps, a recommended test strategy, and ALL plausible candidates with confidence (asking ONE disambiguation question only when genuinely-different features tie). With featureId, it returns that map feature\'s source files, screens, coverage, blockers, and a recommended plan (requires an existing map). Pass sessionId (preferred — adds runtime evidence) or projectRoot.',
|
|
210
297
|
inputSchema: {
|
|
211
|
-
projectRoot: z.string().optional(),
|
|
212
|
-
sessionId: z.string().optional(),
|
|
213
|
-
featureId: z.string().optional(),
|
|
214
|
-
query: z.string().optional().describe('Free-text feature description, e.g. "checkout flow".'),
|
|
298
|
+
projectRoot: z.string().optional().describe('Project root when no session exists.'),
|
|
299
|
+
sessionId: z.string().optional().describe('Session to scope against (adds runtime screen-graph evidence).'),
|
|
300
|
+
featureId: z.string().optional().describe('Exact app-map feature id (requires an existing map). Provide this OR query.'),
|
|
301
|
+
query: z.string().optional().describe('Free-text feature description, e.g. "checkout flow" or "weather analysis".'),
|
|
302
|
+
platform: z.enum(['android', 'ios']).optional(),
|
|
303
|
+
includeCode: z.boolean().optional().describe('Query mode: scan source for code-aware matches (default true).'),
|
|
304
|
+
limit: z.number().optional().describe('Query mode: max items per list in the scope (default 8).'),
|
|
215
305
|
},
|
|
216
|
-
|
|
217
|
-
|
|
306
|
+
}, async ({ projectRoot, sessionId, featureId, query, platform, includeCode, limit }) => {
|
|
307
|
+
if (!featureId && !query) {
|
|
308
|
+
return qaError({
|
|
309
|
+
what: 'Provide one of: featureId or query',
|
|
310
|
+
changedState: false,
|
|
311
|
+
retrySafe: true,
|
|
312
|
+
nextSteps: ['e.g. qa_app_map_feature_scope { query:"login" }'],
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
// Free-text QUERY path: full feature scoping (app-map-first, code-index + runtime-graph
|
|
316
|
+
// fallback, objective model, candidate disambiguation). Works without an existing map.
|
|
317
|
+
if (!featureId) {
|
|
318
|
+
const r = await resolveFeatureContext(server, sessions, { sessionId, projectRoot, feature: query, platform, includeCode, limit });
|
|
319
|
+
if (!r.ok)
|
|
320
|
+
return r.result;
|
|
321
|
+
remember(r.ctx.root);
|
|
322
|
+
const { scopeResult, objective, index } = r.ctx;
|
|
323
|
+
const scope = scopeResult.primary;
|
|
324
|
+
if (!scopeResult.found) {
|
|
325
|
+
return qaOk({
|
|
326
|
+
sessionId: sessionId ?? null,
|
|
327
|
+
query,
|
|
328
|
+
found: false,
|
|
329
|
+
scope,
|
|
330
|
+
searched: scopeResult.searched,
|
|
331
|
+
nextRecommendedAction: {
|
|
332
|
+
tool: 'qa_test_this',
|
|
333
|
+
args: { ...(sessionId ? { sessionId } : { projectRoot: r.ctx.root }), goal: 'explore' },
|
|
334
|
+
why: 'Grow the map with an initial run, then re-scope the feature',
|
|
335
|
+
},
|
|
336
|
+
}, `🔎 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.`);
|
|
337
|
+
}
|
|
338
|
+
if (scopeResult.needsInput) {
|
|
339
|
+
return qaNeedsInput({
|
|
340
|
+
needsInput: true,
|
|
341
|
+
kind: 'monorepo_target',
|
|
342
|
+
question: scopeResult.needsInput.question,
|
|
343
|
+
fields: [{ name: 'query', description: 'The exact feature to test', example: scopeResult.candidates[0]?.title }],
|
|
344
|
+
fallbackOptions: scopeResult.needsInput.options,
|
|
345
|
+
resume: { tool: 'qa_app_map_feature_scope', args: {} },
|
|
346
|
+
attempted: [`scoped "${query}" — matched ${scopeResult.candidates.length} distinct candidates that tie`],
|
|
347
|
+
ifDeclined: 'Swipium scopes the highest-confidence candidate and records the others as alternatives.',
|
|
348
|
+
}, { sessionId: sessionId ?? undefined, candidates: scopeResult.candidates });
|
|
349
|
+
}
|
|
350
|
+
const nextRecommendedAction = scope.recommendedStrategy === 'manual_blocked'
|
|
351
|
+
? {
|
|
352
|
+
tool: 'qa_test_feature',
|
|
353
|
+
args: { ...(sessionId ? { sessionId } : { projectRoot: r.ctx.root }), feature: query, mode: 'plan' },
|
|
354
|
+
why: 'Review the plan + setup needed before any automated execution',
|
|
355
|
+
}
|
|
356
|
+
: {
|
|
357
|
+
tool: 'qa_test_feature',
|
|
358
|
+
args: { sessionId: sessionId ?? '${sessionId}', feature: query, mode: 'execute' },
|
|
359
|
+
why: 'Run a focused test of this feature',
|
|
360
|
+
};
|
|
361
|
+
return qaOk({
|
|
362
|
+
sessionId: sessionId ?? null,
|
|
363
|
+
query,
|
|
364
|
+
found: true,
|
|
365
|
+
featureId: scope.featureId,
|
|
366
|
+
scope,
|
|
367
|
+
objective,
|
|
368
|
+
appMapUri: r.ctx.appMapUri,
|
|
369
|
+
mapFeatureId: scopeResult.mapFeatureId ?? null,
|
|
370
|
+
ticketRefs: scopeResult.ticketRefs,
|
|
371
|
+
runtimeSource: scopeResult.runtimeSource,
|
|
372
|
+
candidates: scopeResult.candidates,
|
|
373
|
+
searched: scopeResult.searched,
|
|
374
|
+
codeIndex: { scannedFiles: index.scannedFiles, truncated: index.truncated },
|
|
375
|
+
nextRecommendedAction,
|
|
376
|
+
}, `🔎 ${scope.title} (confidence ${Math.round(scope.confidence * 100)}%, strategy ${scope.recommendedStrategy}) — ` +
|
|
377
|
+
`${scope.staticScreens.length} static screen(s), ${scope.runtimeScreens.length} runtime screen(s), ${scope.functions.length} symbol(s), ${scope.existingTests.length} existing test(s).` +
|
|
378
|
+
(scopeResult.candidates.length > 1 ? ` ${scopeResult.candidates.length} candidate(s).` : ''));
|
|
379
|
+
}
|
|
380
|
+
// FEATURE-ID path: exact map lookup (requires an existing map).
|
|
218
381
|
const { root, hint } = await rootFor(server, sessions, { projectRoot, sessionId });
|
|
219
382
|
if (!root)
|
|
220
|
-
return qaError({
|
|
383
|
+
return qaError({
|
|
384
|
+
what: 'Could not resolve a project root',
|
|
385
|
+
changedState: false,
|
|
386
|
+
retrySafe: true,
|
|
387
|
+
nextSteps: ['Pass projectRoot or sessionId.'],
|
|
388
|
+
clientHint: hint,
|
|
389
|
+
});
|
|
221
390
|
remember(root);
|
|
222
391
|
const map = readExistingMap(root);
|
|
223
392
|
if (!map)
|
|
224
|
-
return qaError({
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
return qaError({ what: 'Provide one of: featureId or query', changedState: false, retrySafe: true, nextSteps: ['e.g. qa_app_map_feature_scope { query:"login" }'] });
|
|
240
|
-
}
|
|
393
|
+
return qaError({
|
|
394
|
+
what: 'No app map yet',
|
|
395
|
+
changedState: false,
|
|
396
|
+
retrySafe: true,
|
|
397
|
+
nextSteps: ['Run qa_app_map_build first, or pass query= for map-free scoping.'],
|
|
398
|
+
failureCode: 'NO_APP_MAP',
|
|
399
|
+
});
|
|
400
|
+
const features = map.features.filter((f) => f.id === featureId);
|
|
401
|
+
if (!features.length)
|
|
402
|
+
return qaError({
|
|
403
|
+
what: `Unknown featureId ${featureId}`,
|
|
404
|
+
changedState: false,
|
|
405
|
+
retrySafe: true,
|
|
406
|
+
nextSteps: ['List ids with qa_app_map_read { section:"features" }, or pass query= for free-text scoping.'],
|
|
407
|
+
});
|
|
241
408
|
const scope = features.map((f) => {
|
|
242
|
-
const staticScreens = f.staticScreens
|
|
409
|
+
const staticScreens = f.staticScreens
|
|
410
|
+
.map((id) => map.staticTopology.screens.find((s) => s.id === id))
|
|
411
|
+
.filter(Boolean)
|
|
412
|
+
.map((s) => ({ id: s.id, name: s.name, route: s.route, sourceFiles: s.sourceFiles }));
|
|
243
413
|
const needsCreds = f.id === 'feature:auth' || f.blockers.some((b) => /credential/i.test(b));
|
|
244
414
|
const recommendedGoal = f.id === 'feature:auth' ? 'test_login' : 'reproduce_bug';
|
|
245
415
|
return {
|
|
@@ -256,37 +426,135 @@ export function registerAppMap(server, sessions) {
|
|
|
256
426
|
blockers: f.blockers,
|
|
257
427
|
recommendedPlan: {
|
|
258
428
|
tool: 'qa_test_this',
|
|
259
|
-
args: {
|
|
260
|
-
|
|
429
|
+
args: {
|
|
430
|
+
mode: 'execute',
|
|
431
|
+
goal: recommendedGoal,
|
|
432
|
+
goalText: f.title,
|
|
433
|
+
explore: true,
|
|
434
|
+
...(needsCreds ? { stopOnNeedsInput: true } : {}),
|
|
435
|
+
},
|
|
436
|
+
why: needsCreds
|
|
437
|
+
? `Drive "${f.title}"; will stop for test credentials (fixture) — ${f.testCoverage} coverage today`
|
|
438
|
+
: `Drive "${f.title}" with focused exploration — ${f.testCoverage} coverage today`,
|
|
261
439
|
},
|
|
262
440
|
};
|
|
263
441
|
});
|
|
264
442
|
const text = `scope: ${scope.length} feature(s)\n` +
|
|
265
|
-
scope
|
|
443
|
+
scope
|
|
444
|
+
.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)}`)
|
|
445
|
+
.join('\n');
|
|
266
446
|
return qaOk({ appMapUri: appMapResourceUri(root), featureId: scope[0]?.featureId, scope }, text);
|
|
267
447
|
});
|
|
268
|
-
//
|
|
269
|
-
server.registerTool('
|
|
270
|
-
title: '
|
|
271
|
-
description:
|
|
448
|
+
// ----------------------------------------------------------------- update
|
|
449
|
+
server.registerTool('qa_app_map_update', {
|
|
450
|
+
title: 'Update the app knowledge map',
|
|
451
|
+
description: "Apply targeted, provenance-tracked updates to the app map without a full rebuild: attach a user note, add test cases, link an automation suite to feature/screen ids, set the app environment, or override a feature's coverage. Recomputes confidence + coverage and persists.",
|
|
272
452
|
inputSchema: {
|
|
273
453
|
projectRoot: z.string().optional(),
|
|
274
454
|
sessionId: z.string().optional(),
|
|
275
|
-
|
|
455
|
+
note: z.string().optional().describe('A free-text user note added with user_note provenance.'),
|
|
456
|
+
testCases: z
|
|
457
|
+
.array(z.object({
|
|
458
|
+
id: z.string(),
|
|
459
|
+
title: z.string(),
|
|
460
|
+
featureId: z.string().optional(),
|
|
461
|
+
screenId: z.string().optional(),
|
|
462
|
+
status: z.string().optional(),
|
|
463
|
+
source: z.string().optional(),
|
|
464
|
+
stale: z.boolean().optional(),
|
|
465
|
+
}))
|
|
466
|
+
.optional(),
|
|
467
|
+
automationSuite: z
|
|
468
|
+
.object({
|
|
469
|
+
name: z.string(),
|
|
470
|
+
path: z.string(),
|
|
471
|
+
framework: z.string().optional(),
|
|
472
|
+
linkedFeatureIds: z.array(z.string()).optional(),
|
|
473
|
+
linkedScreenIds: z.array(z.string()).optional(),
|
|
474
|
+
})
|
|
475
|
+
.optional(),
|
|
476
|
+
environment: z.string().optional().describe("App environment, e.g. 'test' | 'staging'."),
|
|
477
|
+
featureCoverage: z.object({ featureId: z.string(), coverage: z.enum(['none', 'partial', 'covered']) }).optional(),
|
|
276
478
|
},
|
|
277
|
-
|
|
278
|
-
}, async ({ projectRoot, sessionId, checkFingerprint }) => {
|
|
479
|
+
}, async ({ projectRoot, sessionId, note, testCases, automationSuite, environment, featureCoverage }) => {
|
|
279
480
|
const { root, hint } = await rootFor(server, sessions, { projectRoot, sessionId });
|
|
280
481
|
if (!root)
|
|
281
|
-
return qaError({
|
|
482
|
+
return qaError({
|
|
483
|
+
what: 'Could not resolve a project root',
|
|
484
|
+
changedState: false,
|
|
485
|
+
retrySafe: true,
|
|
486
|
+
nextSteps: ['Pass projectRoot or sessionId.'],
|
|
487
|
+
clientHint: hint,
|
|
488
|
+
});
|
|
282
489
|
remember(root);
|
|
283
490
|
const map = readExistingMap(root);
|
|
284
491
|
if (!map)
|
|
285
|
-
return qaError({
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
492
|
+
return qaError({
|
|
493
|
+
what: 'No app map yet',
|
|
494
|
+
changedState: false,
|
|
495
|
+
retrySafe: true,
|
|
496
|
+
nextSteps: ['Run qa_app_map_build first.'],
|
|
497
|
+
failureCode: 'NO_APP_MAP',
|
|
498
|
+
});
|
|
499
|
+
const at = nowIso();
|
|
500
|
+
const applied = [];
|
|
501
|
+
if (note) {
|
|
502
|
+
addProvenance(map, makeProvenance('user_note', at, note, { targetType: 'map' }));
|
|
503
|
+
applied.push('note');
|
|
504
|
+
}
|
|
505
|
+
if (testCases?.length) {
|
|
506
|
+
for (const c of testCases) {
|
|
507
|
+
const existing = map.testSuite.cases.find((x) => x.id === c.id);
|
|
508
|
+
if (existing)
|
|
509
|
+
Object.assign(existing, c);
|
|
510
|
+
else
|
|
511
|
+
map.testSuite.cases.push(c);
|
|
512
|
+
}
|
|
513
|
+
addProvenance(map, makeProvenance('test_case', at, `${testCases.length} test case(s) registered`, { targetType: 'test' }));
|
|
514
|
+
applied.push(`testCases(${testCases.length})`);
|
|
515
|
+
}
|
|
516
|
+
if (automationSuite) {
|
|
517
|
+
const existing = map.automation.suites.find((s) => s.path === automationSuite.path);
|
|
518
|
+
if (existing)
|
|
519
|
+
Object.assign(existing, automationSuite);
|
|
520
|
+
else
|
|
521
|
+
map.automation.suites.push(automationSuite);
|
|
522
|
+
addProvenance(map, makeProvenance('test_case', at, `Automation suite ${automationSuite.name} linked`, {
|
|
523
|
+
targetType: 'test',
|
|
524
|
+
refs: [automationSuite.path],
|
|
525
|
+
}));
|
|
526
|
+
applied.push('automationSuite');
|
|
527
|
+
}
|
|
528
|
+
if (environment) {
|
|
529
|
+
map.appIdentity.environment = environment;
|
|
530
|
+
applied.push('environment');
|
|
531
|
+
}
|
|
532
|
+
if (featureCoverage) {
|
|
533
|
+
const f = map.features.find((x) => x.id === featureCoverage.featureId);
|
|
534
|
+
if (!f)
|
|
535
|
+
return qaError({
|
|
536
|
+
what: `Unknown featureId ${featureCoverage.featureId}`,
|
|
537
|
+
changedState: false,
|
|
538
|
+
retrySafe: true,
|
|
539
|
+
nextSteps: ['List ids with qa_app_map_read { section:"features" }.'],
|
|
540
|
+
});
|
|
541
|
+
f.testCoverage = featureCoverage.coverage;
|
|
542
|
+
addProvenance(map, makeProvenance('user_note', at, `coverage(${f.id})=${featureCoverage.coverage}`, { targetType: 'feature', targetId: f.id }));
|
|
543
|
+
applied.push('featureCoverage');
|
|
544
|
+
}
|
|
545
|
+
if (!applied.length)
|
|
546
|
+
return qaError({
|
|
547
|
+
what: 'No update fields provided',
|
|
548
|
+
changedState: false,
|
|
549
|
+
retrySafe: true,
|
|
550
|
+
nextSteps: ['Pass at least one of: note, testCases, automationSuite, environment, featureCoverage.'],
|
|
551
|
+
});
|
|
552
|
+
map.updatedAt = at;
|
|
553
|
+
map.coverage.staleTests = map.testSuite.cases.filter((c) => c.stale).length;
|
|
554
|
+
recomputeConfidence(map);
|
|
555
|
+
const save = saveAppMap(root, map);
|
|
556
|
+
saveIndexes(root, loadCodeIndex(root), map.features);
|
|
557
|
+
return qaOk({ appMapUri: save.resourceUri, applied }, `app map updated: ${applied.join(', ')}\nappMapUri: ${save.resourceUri}`);
|
|
290
558
|
});
|
|
291
559
|
}
|
|
292
560
|
/** Resolve a feature/screen/test-suite/full app-map resource read for the MCP resource handler.
|