taphound 0.2.0-dev.1 → 0.2.0-dev.12
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/README.md +49 -149
- package/README.zh-CN.md +47 -147
- package/assets/skills/taphound-case-suite/SKILL.md +363 -0
- package/assets/skills/taphound-case-suite/schemas/base-flow-record.schema.json +22 -0
- package/assets/skills/taphound-case-suite/schemas/case-catalog.schema.json +26 -0
- package/assets/skills/taphound-case-suite/schemas/case-ledger.schema.json +158 -0
- package/assets/skills/taphound-case-suite/schemas/suite-input.schema.json +41 -0
- package/assets/skills/taphound-case-suite/schemas/transition.schema.json +61 -0
- package/assets/skills/taphound-case-suite/scripts/ledger.mjs +1207 -0
- package/assets/skills/taphound-case-suite/templates/base-flow-record.example.json +20 -0
- package/assets/skills/taphound-case-suite/templates/suite-input.example.json +27 -0
- package/assets/skills/taphound-case-suite/templates/transition.example.json +8 -0
- package/assets/skills/taphound-flash/SKILL.md +115 -0
- package/assets/skills/taphound-flash/scripts/flash.mjs +585 -0
- package/assets/skills/taphound-flash/templates/flash-plan.example.json +15 -0
- package/assets/skills/taphound-journey-brief-author/CONTEXT-GUIDE.md +471 -0
- package/assets/skills/taphound-journey-brief-author/SKILL.md +494 -0
- package/assets/skills/taphound-journey-brief-author/prompts/brief-author-role.md +96 -0
- package/assets/skills/taphound-journey-brief-author/prompts/brief-author-role.zh-CN.md +80 -0
- package/assets/skills/taphound-journey-brief-author/prompts/context-analyze-project.md +309 -0
- package/assets/skills/taphound-journey-brief-author/schemas/context-refresh-result.json +123 -0
- package/assets/skills/taphound-journey-brief-author/schemas/project-context-module.json +192 -0
- package/assets/skills/taphound-journey-brief-author/schemas/project-context.json +152 -0
- package/assets/skills/taphound-journey-brief-author/templates/project-context-module.example.json +58 -0
- package/assets/skills/taphound-journey-brief-author/templates/project-context.example.json +51 -0
- package/assets/skills/taphound-journey-brief-author/templates/taphound-journey-brief.template.md +63 -0
- package/assets/skills/taphound-journey-generator/SKILL.md +507 -0
- package/assets/skills/{taphound-ai-journey → taphound-journey-generator}/prompts/check-completion.md +3 -0
- package/assets/skills/taphound-journey-generator/prompts/consume-journey-brief.md +52 -0
- package/assets/skills/taphound-journey-generator/prompts/generate-step.md +211 -0
- package/assets/skills/taphound-journey-generator/prompts/select-flow.md +91 -0
- package/assets/skills/taphound-journey-generator/references/bridge.md +35 -0
- package/assets/skills/taphound-journey-generator/references/mid-session.md +44 -0
- package/assets/skills/taphound-journey-generator/schemas/external-flow.json +37 -0
- package/assets/skills/taphound-journey-generator/schemas/flow.json +28 -0
- package/assets/skills/taphound-journey-generator/schemas/journey-source.json +25 -0
- package/assets/skills/{taphound-ai-journey → taphound-journey-generator}/schemas/observe-output.json +13 -4
- package/assets/skills/{taphound-ai-journey → taphound-journey-generator}/schemas/proposed-step-envelope.json +127 -5
- package/assets/skills/taphound-journey-generator/scripts/envelope.mjs +578 -0
- package/assets/skills/taphound-journey-generator/templates/external-flow.example.json +22 -0
- package/assets/skills/taphound-journey-generator/templates/flow.example.json +22 -0
- package/assets/skills/taphound-journey-generator/templates/journey-source.example.json +17 -0
- package/assets/skills/taphound-journey-generator/templates/taphound-journey-brief.example.md +70 -0
- package/assets/skills/taphound-verify-change/SKILL.md +91 -0
- package/assets/skills/taphound-verify-change/references/accept.md +38 -0
- package/assets/skills/taphound-verify-change/references/preserve.md +357 -0
- package/assets/skills/taphound-verify-change/schemas/workflow-manifest.schema.json +295 -0
- package/assets/skills/taphound-verify-change/scripts/handoff.mjs +482 -0
- package/assets/skills/taphound-verify-change/scripts/ui-refactor.mjs +505 -0
- package/dist/adapters/adb/adb-adapter.d.ts +16 -2
- package/dist/adapters/adb/adb-adapter.js +199 -0
- package/dist/adapters/adb/system-uiautomator-snapshot-provider.d.ts +13 -0
- package/dist/adapters/adb/system-uiautomator-snapshot-provider.js +122 -0
- package/dist/adapters/adb/ui-automator-parser.d.ts +3 -0
- package/dist/adapters/adb/ui-automator-parser.js +160 -0
- package/dist/adapters/adb/window-topology-parser.d.ts +2 -0
- package/dist/adapters/adb/window-topology-parser.js +101 -0
- package/dist/adapters/android-cli/android-cli-adapter.d.ts +16 -8
- package/dist/adapters/android-cli/android-cli-adapter.js +137 -15
- package/dist/adapters/android-cli/android-cli-snapshot-provider.d.ts +9 -0
- package/dist/adapters/android-cli/android-cli-snapshot-provider.js +99 -0
- package/dist/adapters/android-cli/layout-parser.js +12 -8
- package/dist/adapters/appium/appium-doctor.d.ts +7 -0
- package/dist/adapters/appium/appium-doctor.js +128 -0
- package/dist/adapters/appium/appium-ui-snapshot-provider.d.ts +42 -0
- package/dist/adapters/appium/appium-ui-snapshot-provider.js +309 -0
- package/dist/adapters/camera/camera-probe-adapter.d.ts +18 -0
- package/dist/adapters/camera/camera-probe-adapter.js +286 -0
- package/dist/adapters/filesystem/artifact-store.js +40 -1
- package/dist/adapters/filesystem/context-document-writer.d.ts +15 -0
- package/dist/adapters/filesystem/context-document-writer.js +112 -0
- package/dist/adapters/filesystem/diagnostics-journal.d.ts +17 -0
- package/dist/adapters/filesystem/diagnostics-journal.js +130 -0
- package/dist/adapters/filesystem/external-flow-registry.d.ts +17 -0
- package/dist/adapters/filesystem/external-flow-registry.js +325 -0
- package/dist/adapters/filesystem/generation-session-store.d.ts +17 -11
- package/dist/adapters/filesystem/generation-session-store.js +281 -855
- package/dist/adapters/filesystem/generation-store/session-lock.d.ts +29 -0
- package/dist/adapters/filesystem/generation-store/session-lock.js +202 -0
- package/dist/adapters/filesystem/generation-store/session-transitions.d.ts +24 -0
- package/dist/adapters/filesystem/generation-store/session-transitions.js +340 -0
- package/dist/adapters/filesystem/generation-store/store-files.d.ts +50 -0
- package/dist/adapters/filesystem/generation-store/store-files.js +363 -0
- package/dist/adapters/filesystem/journey-composition-store.d.ts +23 -0
- package/dist/adapters/filesystem/journey-composition-store.js +151 -0
- package/dist/adapters/filesystem/knowledge-registry.d.ts +12 -0
- package/dist/adapters/filesystem/knowledge-registry.js +223 -0
- package/dist/adapters/filesystem/project-bound-file.js +3 -5
- package/dist/adapters/filesystem/project-file-inspector.d.ts +2 -0
- package/dist/adapters/filesystem/project-file-inspector.js +57 -28
- package/dist/adapters/filesystem/project-identity-inspector.d.ts +7 -0
- package/dist/adapters/filesystem/project-identity-inspector.js +215 -0
- package/dist/adapters/filesystem/project-inventory-inspector.d.ts +8 -0
- package/dist/adapters/filesystem/project-inventory-inspector.js +93 -0
- package/dist/adapters/filesystem/project-module-discoverer.d.ts +6 -0
- package/dist/adapters/filesystem/project-module-discoverer.js +158 -0
- package/dist/adapters/filesystem/skill-installer.d.ts +7 -2
- package/dist/adapters/filesystem/skill-installer.js +40 -11
- package/dist/adapters/filesystem/workspace-layout.d.ts +7 -0
- package/dist/adapters/filesystem/workspace-layout.js +57 -0
- package/dist/adapters/git/node-git-diff.d.ts +17 -0
- package/dist/adapters/git/node-git-diff.js +114 -0
- package/dist/adapters/process/node-detached-process-launcher.d.ts +6 -0
- package/dist/adapters/process/node-detached-process-launcher.js +96 -0
- package/dist/adapters/process/node-process-runner.js +11 -2
- package/dist/adapters/prompt/inquirer-align-prompt.d.ts +27 -0
- package/dist/adapters/prompt/inquirer-align-prompt.js +65 -0
- package/dist/adapters/prompt/inquirer-generation-prompt.js +3 -0
- package/dist/adapters/prompt/inquirer-recorder-prompt.d.ts +9 -1
- package/dist/adapters/prompt/inquirer-recorder-prompt.js +63 -0
- package/dist/adapters/runtime/adb-runtime-backend.d.ts +67 -0
- package/dist/adapters/runtime/adb-runtime-backend.js +206 -0
- package/dist/adapters/runtime/fake-runtime-backend.d.ts +87 -0
- package/dist/adapters/runtime/fake-runtime-backend.js +240 -0
- package/dist/adapters/runtime/mobile-mcp/mcp-tool-client.d.ts +45 -0
- package/dist/adapters/runtime/mobile-mcp/mcp-tool-client.js +153 -0
- package/dist/adapters/runtime/mobile-mcp/mobile-mcp-errors.d.ts +23 -0
- package/dist/adapters/runtime/mobile-mcp/mobile-mcp-errors.js +39 -0
- package/dist/adapters/runtime/mobile-mcp/mobile-mcp-responses.d.ts +20 -0
- package/dist/adapters/runtime/mobile-mcp/mobile-mcp-responses.js +180 -0
- package/dist/adapters/runtime/mobile-mcp/mobile-mcp-runtime-backend.d.ts +78 -0
- package/dist/adapters/runtime/mobile-mcp/mobile-mcp-runtime-backend.js +379 -0
- package/dist/adapters/runtime/mobile-mcp/mobile-mcp-tools.d.ts +46 -0
- package/dist/adapters/runtime/session-adb-view.d.ts +53 -0
- package/dist/adapters/runtime/session-adb-view.js +193 -0
- package/dist/adapters/runtime/session-backed-ports.d.ts +21 -0
- package/dist/adapters/runtime/session-backed-ports.js +43 -0
- package/dist/adapters/runtime/shared-session-runtime-backend.d.ts +12 -0
- package/dist/adapters/runtime/shared-session-runtime-backend.js +64 -0
- package/dist/adapters/ui/auto-ui-snapshot-provider.d.ts +8 -0
- package/dist/adapters/ui/auto-ui-snapshot-provider.js +48 -0
- package/dist/adapters/ui/device-layout-temp.d.ts +6 -0
- package/dist/adapters/ui/device-layout-temp.js +18 -0
- package/dist/adapters/ui/device-ui-environment.d.ts +13 -0
- package/dist/adapters/ui/device-ui-environment.js +81 -0
- package/dist/adapters/ui/layout-normalization.d.ts +9 -0
- package/dist/adapters/ui/layout-normalization.js +44 -0
- package/dist/adapters/ui/ui-snapshot-error.d.ts +11 -0
- package/dist/adapters/ui/ui-snapshot-error.js +12 -0
- package/dist/adapters/ui/ui-snapshot-support.d.ts +15 -0
- package/dist/adapters/ui/ui-snapshot-support.js +12 -0
- package/dist/application/align/align-service.d.ts +42 -0
- package/dist/application/align/align-service.js +106 -0
- package/dist/application/assertion/expectation-evaluator.d.ts +18 -5
- package/dist/application/assertion/expectation-evaluator.js +156 -18
- package/dist/application/assertion/guarded-expectation.d.ts +45 -0
- package/dist/application/assertion/guarded-expectation.js +74 -0
- package/dist/application/checkpoint/baseline-capturer.d.ts +26 -0
- package/dist/application/checkpoint/baseline-capturer.js +163 -0
- package/dist/application/checkpoint/baseline-error.d.ts +5 -0
- package/dist/application/checkpoint/baseline-error.js +8 -0
- package/dist/application/checkpoint/baseline-service.d.ts +31 -0
- package/dist/application/checkpoint/baseline-service.js +147 -0
- package/dist/application/checkpoint/checkpoint-evaluator.d.ts +35 -0
- package/dist/application/checkpoint/checkpoint-evaluator.js +415 -0
- package/dist/application/checkpoint/regression-comparator.d.ts +18 -0
- package/dist/application/checkpoint/regression-comparator.js +192 -0
- package/dist/application/collector/logcat-collector.d.ts +22 -1
- package/dist/application/collector/logcat-collector.js +103 -15
- package/dist/application/collector/logcat-event.d.ts +8 -0
- package/dist/application/collector/logcat-event.js +79 -0
- package/dist/application/collector/logcat-stop.d.ts +2 -0
- package/dist/application/collector/logcat-stop.js +11 -0
- package/dist/application/context/context-generator.d.ts +45 -0
- package/dist/application/context/context-generator.js +228 -0
- package/dist/application/context/context-loader.d.ts +40 -0
- package/dist/application/context/context-loader.js +223 -0
- package/dist/application/context/context-refresher.d.ts +59 -0
- package/dist/application/context/context-refresher.js +349 -0
- package/dist/application/context/context-rehasher.d.ts +36 -0
- package/dist/application/context/context-rehasher.js +110 -0
- package/dist/application/context/context-validator.d.ts +23 -1
- package/dist/application/context/context-validator.js +116 -9
- package/dist/application/context/evidence-hash.d.ts +1 -0
- package/dist/application/context/evidence-hash.js +125 -0
- package/dist/application/context/evidence-match.d.ts +9 -0
- package/dist/application/context/evidence-match.js +8 -0
- package/dist/application/context/shard-identity.d.ts +2 -0
- package/dist/application/context/shard-identity.js +7 -0
- package/dist/application/contract/contract-loader.d.ts +25 -0
- package/dist/application/contract/contract-loader.js +68 -0
- package/dist/application/contract/contract-review.d.ts +25 -0
- package/dist/application/contract/contract-review.js +51 -0
- package/dist/application/contract/contract-verifier.d.ts +53 -0
- package/dist/application/contract/contract-verifier.js +645 -0
- package/dist/application/devices/resolve-device-assignments.d.ts +21 -0
- package/dist/application/devices/resolve-device-assignments.js +60 -0
- package/dist/application/diagnosis/failure-classifier.d.ts +23 -0
- package/dist/application/diagnosis/failure-classifier.js +179 -0
- package/dist/application/diagnostics/diagnostics-exporter.d.ts +29 -0
- package/dist/application/diagnostics/diagnostics-exporter.js +283 -0
- package/dist/application/diagnostics/ui-capture-telemetry.d.ts +16 -0
- package/dist/application/diagnostics/ui-capture-telemetry.js +81 -0
- package/dist/application/doctor/doctor-service.d.ts +36 -3
- package/dist/application/doctor/doctor-service.js +255 -25
- package/dist/application/generation/generation-app-preparer.d.ts +20 -0
- package/dist/application/generation/generation-app-preparer.js +28 -0
- package/dist/application/generation/generation-config-service.d.ts +21 -0
- package/dist/application/generation/generation-config-service.js +53 -0
- package/dist/application/generation/generation-confirmation-service.d.ts +14 -3
- package/dist/application/generation/generation-confirmation-service.js +109 -24
- package/dist/application/generation/generation-context-snapshot.d.ts +6 -0
- package/dist/application/generation/generation-context-snapshot.js +29 -0
- package/dist/application/generation/generation-finalizer.d.ts +15 -9
- package/dist/application/generation/generation-finalizer.js +167 -70
- package/dist/application/generation/generation-publisher.js +12 -8
- package/dist/application/generation/generation-recovery-service.d.ts +35 -0
- package/dist/application/generation/generation-recovery-service.js +144 -0
- package/dist/application/generation/generation-reopen-service.d.ts +15 -0
- package/dist/application/generation/generation-reopen-service.js +37 -0
- package/dist/application/generation/generation-replace-service.d.ts +32 -0
- package/dist/application/generation/generation-replace-service.js +116 -0
- package/dist/application/generation/generation-starter.d.ts +44 -4
- package/dist/application/generation/generation-starter.js +175 -8
- package/dist/application/generation/generation-step-executor.d.ts +67 -5
- package/dist/application/generation/generation-step-executor.js +471 -108
- package/dist/application/generation/proposed-step-validator.js +96 -30
- package/dist/application/generation/replay-policy-loader.d.ts +16 -0
- package/dist/application/generation/replay-policy-loader.js +29 -0
- package/dist/application/generation/risk-evaluator.d.ts +4 -1
- package/dist/application/generation/risk-evaluator.js +118 -3
- package/dist/application/generation/runtime-observer.d.ts +39 -7
- package/dist/application/generation/runtime-observer.js +193 -24
- package/dist/application/impact/impact-resolver.d.ts +32 -0
- package/dist/application/impact/impact-resolver.js +160 -0
- package/dist/application/init/init-service.js +26 -13
- package/dist/application/interaction/action-executor.d.ts +3 -2
- package/dist/application/interaction/action-executor.js +11 -1
- package/dist/application/interaction/action-target.d.ts +17 -0
- package/dist/application/interaction/action-target.js +40 -0
- package/dist/application/interaction/external-step-runner.d.ts +153 -0
- package/dist/application/interaction/external-step-runner.js +332 -0
- package/dist/application/interaction/fallback-resolver.d.ts +6 -3
- package/dist/application/interaction/fallback-resolver.js +7 -5
- package/dist/application/interaction/scroll-to-executor.d.ts +17 -3
- package/dist/application/interaction/scroll-to-executor.js +118 -36
- package/dist/application/journey/external-flow-resolver.d.ts +25 -0
- package/dist/application/journey/external-flow-resolver.js +22 -0
- package/dist/application/journey/journey-check-service.d.ts +54 -0
- package/dist/application/journey/journey-check-service.js +207 -0
- package/dist/application/journey/journey-promoter.d.ts +26 -0
- package/dist/application/journey/journey-promoter.js +123 -0
- package/dist/application/journey/journey-resolver.d.ts +42 -0
- package/dist/application/journey/journey-resolver.js +239 -0
- package/dist/application/journey/journey-retirer.d.ts +25 -0
- package/dist/application/journey/journey-retirer.js +87 -0
- package/dist/application/knowledge/anchor-resolver.d.ts +36 -0
- package/dist/application/knowledge/anchor-resolver.js +110 -0
- package/dist/application/knowledge/knowledge-loader.d.ts +16 -0
- package/dist/application/knowledge/knowledge-loader.js +36 -0
- package/dist/application/locator/layout-failure-summary.d.ts +2 -0
- package/dist/application/locator/layout-failure-summary.js +67 -0
- package/dist/application/locator/layout-traversal.d.ts +11 -0
- package/dist/application/locator/layout-traversal.js +18 -0
- package/dist/application/locator/locator-resolver.d.ts +13 -1
- package/dist/application/locator/locator-resolver.js +182 -18
- package/dist/application/observe/observe-service.d.ts +20 -0
- package/dist/application/observe/observe-service.js +91 -0
- package/dist/application/project/project-describer.d.ts +14 -6
- package/dist/application/project/project-describer.js +38 -5
- package/dist/application/recognition/screen-detector.d.ts +28 -0
- package/dist/application/recognition/screen-detector.js +111 -0
- package/dist/application/recorder/locator-selector.js +151 -17
- package/dist/application/recorder/recorder-service.d.ts +16 -4
- package/dist/application/recorder/recorder-service.js +420 -128
- package/dist/application/report/report-writer.d.ts +5 -0
- package/dist/application/report/report-writer.js +17 -1
- package/dist/application/runtime/cold-launch.d.ts +28 -0
- package/dist/application/runtime/cold-launch.js +59 -0
- package/dist/application/runtime/step-runner.d.ts +49 -4
- package/dist/application/runtime/step-runner.js +721 -130
- package/dist/application/runtime/verify-runtime.d.ts +57 -7
- package/dist/application/runtime/verify-runtime.js +577 -175
- package/dist/application/ui/cached-ui-snapshot-provider.d.ts +45 -0
- package/dist/application/ui/cached-ui-snapshot-provider.js +111 -0
- package/dist/application/ui/observed-ui-snapshot-provider.d.ts +15 -0
- package/dist/application/ui/observed-ui-snapshot-provider.js +66 -0
- package/dist/application/ui/ui-snapshot-lifecycle.d.ts +2 -0
- package/dist/application/ui/ui-snapshot-lifecycle.js +8 -0
- package/dist/application/ui/ui-stability-probe.d.ts +9 -0
- package/dist/application/ui/ui-stability-probe.js +24 -0
- package/dist/application/wait/idle-advice.d.ts +6 -0
- package/dist/application/wait/idle-advice.js +27 -0
- package/dist/application/wait/idle-profiles.d.ts +15 -0
- package/dist/application/wait/idle-profiles.js +75 -0
- package/dist/application/wait/idle-waiter.d.ts +28 -4
- package/dist/application/wait/idle-waiter.js +201 -15
- package/dist/cli/commands/align.d.ts +3 -0
- package/dist/cli/commands/align.js +124 -0
- package/dist/cli/commands/baseline.d.ts +3 -0
- package/dist/cli/commands/baseline.js +130 -0
- package/dist/cli/commands/context.js +300 -27
- package/dist/cli/commands/contract.d.ts +3 -0
- package/dist/cli/commands/contract.js +120 -0
- package/dist/cli/commands/diagnose.d.ts +3 -0
- package/dist/cli/commands/diagnose.js +84 -0
- package/dist/cli/commands/doctor.js +16 -9
- package/dist/cli/commands/failure.d.ts +3 -0
- package/dist/cli/commands/failure.js +63 -0
- package/dist/cli/commands/generation/lifecycle-commands.d.ts +5 -0
- package/dist/cli/commands/generation/lifecycle-commands.js +211 -0
- package/dist/cli/commands/generation/session-commands.d.ts +11 -0
- package/dist/cli/commands/generation/session-commands.js +472 -0
- package/dist/cli/commands/generation/shared.d.ts +1414 -0
- package/dist/cli/commands/generation/shared.js +291 -0
- package/dist/cli/commands/generation/step-commands.d.ts +8 -0
- package/dist/cli/commands/generation/step-commands.js +315 -0
- package/dist/cli/commands/generation.js +10 -507
- package/dist/cli/commands/impact.d.ts +3 -0
- package/dist/cli/commands/impact.js +87 -0
- package/dist/cli/commands/init.js +13 -6
- package/dist/cli/commands/journey.d.ts +3 -0
- package/dist/cli/commands/journey.js +382 -0
- package/dist/cli/commands/knowledge.d.ts +3 -0
- package/dist/cli/commands/knowledge.js +93 -0
- package/dist/cli/commands/observe.d.ts +3 -0
- package/dist/cli/commands/observe.js +127 -0
- package/dist/cli/commands/project.js +2 -1
- package/dist/cli/commands/record.js +18 -5
- package/dist/cli/commands/verify.js +326 -64
- package/dist/cli/dependencies.d.ts +136 -1
- package/dist/cli/dependencies.js +504 -52
- package/dist/cli/diagnostics-recorder.d.ts +31 -0
- package/dist/cli/diagnostics-recorder.js +82 -0
- package/dist/cli/diff-verification.d.ts +41 -0
- package/dist/cli/diff-verification.js +192 -0
- package/dist/cli/main.d.ts +1 -1
- package/dist/cli/main.js +107 -2
- package/dist/cli/output.d.ts +5 -3
- package/dist/cli/output.js +12 -0
- package/dist/cli/program.js +21 -1
- package/dist/cli/project-root.d.ts +1 -0
- package/dist/cli/project-root.js +6 -0
- package/dist/cli/runtime-selection.d.ts +22 -0
- package/dist/cli/runtime-selection.js +92 -0
- package/dist/cli/version.d.ts +1 -0
- package/dist/cli/version.js +14 -0
- package/dist/cli/workspace-guard.d.ts +2 -0
- package/dist/cli/workspace-guard.js +3 -0
- package/dist/domain/binding-reference.d.ts +4 -0
- package/dist/domain/binding-reference.js +23 -0
- package/dist/domain/checkpoint.d.ts +404 -0
- package/dist/domain/checkpoint.js +297 -0
- package/dist/domain/config.d.ts +107 -1
- package/dist/domain/config.js +52 -6
- package/dist/domain/contract.d.ts +413 -0
- package/dist/domain/contract.js +196 -0
- package/dist/domain/diagnostics.d.ts +1396 -0
- package/dist/domain/diagnostics.js +193 -0
- package/dist/domain/external-flow.d.ts +493 -0
- package/dist/domain/external-flow.js +15 -0
- package/dist/domain/failure-classification.d.ts +176 -0
- package/dist/domain/failure-classification.js +169 -0
- package/dist/domain/failure.d.ts +2 -1
- package/dist/domain/failure.js +123 -0
- package/dist/domain/generation.d.ts +1671 -132
- package/dist/domain/generation.js +202 -13
- package/dist/domain/geometry.d.ts +13 -0
- package/dist/domain/geometry.js +16 -0
- package/dist/domain/impact.d.ts +71 -0
- package/dist/domain/impact.js +76 -0
- package/dist/domain/init.d.ts +12 -4
- package/dist/domain/init.js +24 -18
- package/dist/domain/journey-composition.d.ts +2285 -0
- package/dist/domain/journey-composition.js +85 -0
- package/dist/domain/journey-lifecycle.d.ts +10 -0
- package/dist/domain/journey-lifecycle.js +9 -0
- package/dist/domain/journey.d.ts +2419 -212
- package/dist/domain/journey.js +384 -14
- package/dist/domain/knowledge.d.ts +165 -0
- package/dist/domain/knowledge.js +188 -0
- package/dist/domain/layout.d.ts +34 -5
- package/dist/domain/layout.js +64 -6
- package/dist/domain/locator-evidence.d.ts +3 -0
- package/dist/domain/locator-evidence.js +36 -0
- package/dist/domain/logcat-event.d.ts +54 -0
- package/dist/domain/logcat-event.js +40 -0
- package/dist/domain/observation.d.ts +33 -0
- package/dist/domain/observation.js +18 -0
- package/dist/domain/project-context.d.ts +219 -1
- package/dist/domain/project-context.js +209 -6
- package/dist/domain/proposed-step.d.ts +340 -60
- package/dist/domain/proposed-step.js +23 -3
- package/dist/domain/report.d.ts +1455 -28
- package/dist/domain/report.js +211 -14
- package/dist/domain/runtime-snapshot.d.ts +58 -1
- package/dist/domain/runtime-snapshot.js +14 -3
- package/dist/domain/runtime.d.ts +50 -0
- package/dist/domain/runtime.js +36 -0
- package/dist/domain/system-app-profiles.d.ts +6 -0
- package/dist/domain/system-app-profiles.js +43 -0
- package/dist/domain/ui-backend.d.ts +28 -0
- package/dist/domain/ui-backend.js +33 -0
- package/dist/domain/ui-cache.d.ts +11 -0
- package/dist/domain/ui-cache.js +10 -0
- package/dist/domain/verify-receipt.d.ts +17 -0
- package/dist/domain/verify-receipt.js +17 -0
- package/dist/domain/window-hierarchy.d.ts +84 -0
- package/dist/domain/window-hierarchy.js +172 -0
- package/dist/domain/workflow-manifest.d.ts +77 -0
- package/dist/domain/workflow-manifest.js +136 -0
- package/dist/domain/workspace.d.ts +30 -0
- package/dist/domain/workspace.js +74 -0
- package/dist/ports/adb.d.ts +34 -1
- package/dist/ports/align-prompt.d.ts +11 -0
- package/dist/ports/align-prompt.js +6 -0
- package/dist/ports/anchor-resolver.d.ts +30 -0
- package/dist/ports/anchor-resolver.js +1 -0
- package/dist/ports/annotated-screen-resolver.d.ts +4 -0
- package/dist/ports/annotated-screen-resolver.js +1 -0
- package/dist/ports/camera-probe.d.ts +23 -0
- package/dist/ports/camera-probe.js +8 -0
- package/dist/ports/context-document-writer.d.ts +24 -0
- package/dist/ports/context-document-writer.js +1 -0
- package/dist/ports/detached-process-launcher.d.ts +12 -0
- package/dist/ports/detached-process-launcher.js +1 -0
- package/dist/ports/diagnostics.d.ts +15 -0
- package/dist/ports/diagnostics.js +1 -0
- package/dist/ports/external-flow-registry.d.ts +37 -0
- package/dist/ports/external-flow-registry.js +1 -0
- package/dist/ports/generation-prompt.d.ts +2 -1
- package/dist/ports/generation-session-store.d.ts +12 -2
- package/dist/ports/git-diff.d.ts +9 -0
- package/dist/ports/git-diff.js +1 -0
- package/dist/ports/journey-composition-store.d.ts +17 -0
- package/dist/ports/journey-composition-store.js +1 -0
- package/dist/ports/knowledge-registry.d.ts +20 -0
- package/dist/ports/knowledge-registry.js +1 -0
- package/dist/ports/process-runner.d.ts +7 -0
- package/dist/ports/project-file-inspector.d.ts +1 -0
- package/dist/ports/project-identity-inspector.d.ts +24 -0
- package/dist/ports/project-identity-inspector.js +1 -0
- package/dist/ports/project-inventory-inspector.d.ts +21 -0
- package/dist/ports/project-inventory-inspector.js +1 -0
- package/dist/ports/project-module-discoverer.d.ts +26 -0
- package/dist/ports/project-module-discoverer.js +1 -0
- package/dist/ports/recorder-prompt.d.ts +10 -1
- package/dist/ports/runtime-backend.d.ts +89 -0
- package/dist/ports/runtime-backend.js +13 -0
- package/dist/ports/runtime-capability.d.ts +12 -0
- package/dist/ports/runtime-capability.js +20 -0
- package/dist/ports/runtime-session-ports.d.ts +25 -0
- package/dist/ports/runtime-session-ports.js +1 -0
- package/dist/ports/screenshot.d.ts +11 -0
- package/dist/ports/screenshot.js +1 -0
- package/dist/ports/skill-installer.d.ts +2 -2
- package/dist/ports/ui-snapshot.d.ts +35 -0
- package/dist/ports/ui-snapshot.js +1 -0
- package/dist/ports/ui-stability.d.ts +19 -0
- package/dist/ports/ui-stability.js +1 -0
- package/dist/ports/workspace-layout.d.ts +4 -0
- package/dist/ports/workspace-layout.js +1 -0
- package/dist/shared/errors.d.ts +2 -0
- package/dist/shared/errors.js +22 -0
- package/dist/shared/paths.d.ts +2 -0
- package/dist/shared/paths.js +24 -0
- package/dist/shared/strings.d.ts +2 -0
- package/dist/shared/strings.js +16 -0
- package/package.json +7 -2
- package/assets/skills/taphound-ai-journey/GUIDE.md +0 -862
- package/assets/skills/taphound-ai-journey/SKILL.md +0 -377
- package/assets/skills/taphound-ai-journey/prompts/analyze-project.md +0 -526
- package/assets/skills/taphound-ai-journey/prompts/generate-step.md +0 -109
- package/assets/skills/taphound-ai-journey/schemas/project-context.json +0 -81
- package/assets/skills/taphound-ai-journey/templates/project-context.example.json +0 -49
- package/dist/ports/android-cli.d.ts +0 -21
- /package/dist/{ports/android-cli.js → adapters/runtime/mobile-mcp/mobile-mcp-tools.js} +0 -0
|
@@ -0,0 +1,505 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Skill-side cross-version evidence gate. This does not relax Core's
|
|
3
|
+
// same-Journey baseline compare or claim pixel/visual equivalence.
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
5
|
+
import { spawnSync } from "node:child_process";
|
|
6
|
+
import { constants } from "node:fs";
|
|
7
|
+
import {
|
|
8
|
+
copyFile, lstat, mkdtemp, readFile, readdir, realpath, rename, rm,
|
|
9
|
+
writeFile
|
|
10
|
+
} from "node:fs/promises";
|
|
11
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
12
|
+
import process from "node:process";
|
|
13
|
+
|
|
14
|
+
const hash = (value) => createHash("sha256").update(value).digest("hex");
|
|
15
|
+
const sha = (value) => /^[a-f0-9]{64}$/.test(value);
|
|
16
|
+
const id = (value) => typeof value === "string"
|
|
17
|
+
&& /^[a-z][a-z0-9-]{0,63}$/.test(value);
|
|
18
|
+
const inside = (root, path) => {
|
|
19
|
+
const rel = relative(root, path);
|
|
20
|
+
return rel === "" || (rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel));
|
|
21
|
+
};
|
|
22
|
+
const pause = (message) => { throw new Error(message); };
|
|
23
|
+
const json = async (path) => JSON.parse(await readFile(path, "utf8"));
|
|
24
|
+
const fileHash = async (path) => hash(await readFile(path));
|
|
25
|
+
function canonical(value) {
|
|
26
|
+
if (Array.isArray(value)) return value.map(canonical);
|
|
27
|
+
if (value !== null && typeof value === "object") {
|
|
28
|
+
return Object.fromEntries(Object.entries(value)
|
|
29
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
30
|
+
.map(([key, item]) => [key, canonical(item)]));
|
|
31
|
+
}
|
|
32
|
+
return value;
|
|
33
|
+
}
|
|
34
|
+
const journeyHash = (value) => hash(JSON.stringify(canonical(value)));
|
|
35
|
+
async function file(path) {
|
|
36
|
+
const actual = resolve(path);
|
|
37
|
+
if (!isAbsolute(path) || await realpath(actual) !== actual
|
|
38
|
+
|| !(await lstat(actual)).isFile()) {
|
|
39
|
+
pause("Evidence must be a regular absolute file with no symlink parents");
|
|
40
|
+
}
|
|
41
|
+
return actual;
|
|
42
|
+
}
|
|
43
|
+
async function root(path) {
|
|
44
|
+
const actual = resolve(path);
|
|
45
|
+
if (!isAbsolute(path) || await realpath(actual) !== actual
|
|
46
|
+
|| !(await lstat(actual)).isDirectory()) {
|
|
47
|
+
pause("Workspace must be an absolute non-symlink directory");
|
|
48
|
+
}
|
|
49
|
+
return actual;
|
|
50
|
+
}
|
|
51
|
+
function checkCase(value) {
|
|
52
|
+
if (value.version !== 1 || !id(value.caseId)
|
|
53
|
+
|| typeof value.sourceRef !== "string" || !value.sourceRef.trim()
|
|
54
|
+
|| !/^[a-zA-Z_$][\w$]*(?:\.[a-zA-Z_$][\w$]*)+$/.test(value.packageName ?? "")
|
|
55
|
+
|| typeof value.goal !== "string" || !value.goal.trim()
|
|
56
|
+
|| typeof value.fixtureRef !== "string" || !value.fixtureRef.trim()
|
|
57
|
+
|| !Array.isArray(value.scenario) || value.scenario.length === 0
|
|
58
|
+
|| value.scenario.some((step) => typeof step !== "string" || !step.trim())
|
|
59
|
+
|| !Array.isArray(value.observables) || value.observables.length === 0
|
|
60
|
+
|| value.observables.some((fact) => !validObservable(fact))
|
|
61
|
+
|| new Set(value.observables.map((fact) => fact.id)).size !== value.observables.length
|
|
62
|
+
|| Object.keys(value).sort().join(",")
|
|
63
|
+
!== "caseId,fixtureRef,goal,observables,packageName,scenario,sourceRef,version") {
|
|
64
|
+
pause("Frozen UI-refactor Case must have at least one concrete, distinct observable");
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function validLocator(locator) {
|
|
68
|
+
if (locator === null || typeof locator !== "object"
|
|
69
|
+
|| Object.keys(locator).length !== 1) return false;
|
|
70
|
+
const key = Object.keys(locator)[0];
|
|
71
|
+
return ["resourceId", "text", "contentDescription"].includes(key)
|
|
72
|
+
&& typeof locator[key] === "string" && locator[key].trim().length > 0;
|
|
73
|
+
}
|
|
74
|
+
function validEvent(expect) {
|
|
75
|
+
const fieldValues = expect?.fields !== null && typeof expect?.fields === "object"
|
|
76
|
+
&& !Array.isArray(expect.fields) ? Object.values(expect.fields) : [];
|
|
77
|
+
if (expect === null || typeof expect !== "object"
|
|
78
|
+
|| expect.type !== "logcatEvent"
|
|
79
|
+
|| typeof expect.tag !== "string" || !expect.tag.trim()
|
|
80
|
+
|| typeof expect.event !== "string" || !expect.event.trim()
|
|
81
|
+
|| expect.fields === null || typeof expect.fields !== "object"
|
|
82
|
+
|| Array.isArray(expect.fields) || expect.unique !== true
|
|
83
|
+
|| fieldValues.some((value) => !(
|
|
84
|
+
typeof value === "boolean"
|
|
85
|
+
|| typeof value === "number" && Number.isFinite(value)
|
|
86
|
+
|| typeof value === "string" && value.length <= 512
|
|
87
|
+
))
|
|
88
|
+
|| expect.capture !== undefined
|
|
89
|
+
|| !["stepStart", "runStart", "marker"].includes(expect.window?.from)
|
|
90
|
+
|| (expect.window.from === "marker") !== id(expect.window.markerId)
|
|
91
|
+
|| Object.keys(expect.window).sort().join(",") !== (
|
|
92
|
+
expect.window.from === "marker" ? "from,markerId" : "from"
|
|
93
|
+
)) return false;
|
|
94
|
+
const keys = Object.keys(expect).sort().join(",");
|
|
95
|
+
return keys === "event,fields,tag,type,unique,window";
|
|
96
|
+
}
|
|
97
|
+
function validObservable(fact) {
|
|
98
|
+
if (!id(fact?.id) || !Number.isInteger(fact.timeoutMs)
|
|
99
|
+
|| fact.timeoutMs <= 0 || fact.timeoutMs > 60000) return false;
|
|
100
|
+
if (fact.kind === "visibleText") {
|
|
101
|
+
return Object.keys(fact).sort().join(",") === "afterAction,id,kind,text,timeoutMs"
|
|
102
|
+
&& ["click", "wait"].includes(fact.afterAction)
|
|
103
|
+
&& typeof fact.text === "string" && fact.text.trim().length > 0;
|
|
104
|
+
}
|
|
105
|
+
if (fact.kind === "visibleElement" || fact.kind === "absentElement") {
|
|
106
|
+
return Object.keys(fact).sort().join(",") === "id,kind,locator,timeoutMs"
|
|
107
|
+
&& validLocator(fact.locator);
|
|
108
|
+
}
|
|
109
|
+
if (fact.kind === "logcatEvent") {
|
|
110
|
+
return Object.keys(fact).sort().join(",") === "expect,id,kind,timeoutMs"
|
|
111
|
+
&& validEvent(fact.expect);
|
|
112
|
+
}
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
function expectedCheckpoint(fact) {
|
|
116
|
+
const condition = fact.kind === "logcatEvent"
|
|
117
|
+
? { kind: "logcatEvent", expect: fact.expect }
|
|
118
|
+
: { kind: fact.kind, locator: fact.locator };
|
|
119
|
+
return { condition, expect: { allOf: [condition], timeoutMs: fact.timeoutMs } };
|
|
120
|
+
}
|
|
121
|
+
function checkpointMatches(actual, expected) {
|
|
122
|
+
if (actual === null || typeof actual !== "object"
|
|
123
|
+
|| actual.timeoutMs !== expected.timeoutMs
|
|
124
|
+
|| !Array.isArray(actual.allOf) || actual.allOf.length !== 1
|
|
125
|
+
|| JSON.stringify(canonical(actual.allOf[0]))
|
|
126
|
+
!== JSON.stringify(canonical(expected.allOf[0]))) {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
return Object.keys(actual).every((key) => ["allOf", "timeoutMs"].includes(key));
|
|
130
|
+
}
|
|
131
|
+
function observablePassed(report, evidence) {
|
|
132
|
+
if (evidence.source === "step") {
|
|
133
|
+
return report.steps[evidence.index]?.expectation?.status === "passed";
|
|
134
|
+
}
|
|
135
|
+
const checkpoint = report.checkpoints?.find((entry) => entry.id === evidence.id);
|
|
136
|
+
return checkpoint?.status === "passed"
|
|
137
|
+
&& checkpoint.conditions.length === 1
|
|
138
|
+
&& checkpoint.conditions[0]?.status === "passed";
|
|
139
|
+
}
|
|
140
|
+
function validCheckpointReport(checkpoint) {
|
|
141
|
+
if (checkpoint.conditions?.length !== 1
|
|
142
|
+
|| !["passed", "failed", "unresolved"].includes(checkpoint.status)
|
|
143
|
+
|| checkpoint.status !== checkpoint.conditions[0].status) return false;
|
|
144
|
+
const condition = checkpoint.conditions[0];
|
|
145
|
+
if (condition.kind === "logcatEvent" && condition.status === "passed") {
|
|
146
|
+
return condition.matchedCount === 1
|
|
147
|
+
&& sha(condition.matchedLineSha256)
|
|
148
|
+
&& Number.isFinite(condition.matchedAtMs)
|
|
149
|
+
&& typeof condition.evidenceRef === "string"
|
|
150
|
+
&& condition.evidenceRef.length > 0;
|
|
151
|
+
}
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
function checkReplay(project, device, caseData, journey, meta, report, reportPath) {
|
|
155
|
+
if (journey.version !== 2 || !journey.steps?.length
|
|
156
|
+
|| !journey.devices?.some((entry) => entry.role === "default")
|
|
157
|
+
|| !Array.isArray(journey.steps)
|
|
158
|
+
|| meta.version !== 1 || !["verified", "promoted"].includes(meta.status)
|
|
159
|
+
|| typeof meta.generationId !== "string"
|
|
160
|
+
|| !sha(meta.bindings?.projectHash) || !sha(meta.bindings?.configHash)
|
|
161
|
+
|| !sha(meta.bindings?.contextHash)
|
|
162
|
+
|| !sha(meta.verification?.reportSha256)
|
|
163
|
+
|| meta.journeyPath === undefined
|
|
164
|
+
|| !meta.replayPolicy?.generatedReplayPolicy
|
|
165
|
+
|| !meta.replayPolicy?.requireFocusedInput
|
|
166
|
+
|| meta.journeySha256 !== journeyHash(journey)
|
|
167
|
+
|| report.schemaVersion !== 4
|
|
168
|
+
|| report.project?.packageName !== caseData.packageName
|
|
169
|
+
|| resolve(report.project?.root ?? "") !== project
|
|
170
|
+
|| report.environment?.devices?.length !== 1
|
|
171
|
+
|| report.environment.devices[0].deviceSerial !== device
|
|
172
|
+
|| report.journey?.sha256 !== meta.journeySha256
|
|
173
|
+
|| report.journey.name !== journey.name
|
|
174
|
+
|| resolve(report.artifacts?.directory ?? "") !== dirname(reportPath)
|
|
175
|
+
|| !Array.isArray(report.steps) || report.steps.length > journey.steps.length
|
|
176
|
+
|| (report.status === "passed" && report.steps.length !== journey.steps.length)
|
|
177
|
+
|| report.steps.some((step, index) => step.index !== index
|
|
178
|
+
|| journey.steps[index] === undefined
|
|
179
|
+
|| step.action !== journey.steps[index].action)
|
|
180
|
+
|| report.fallbackUsed !== false) {
|
|
181
|
+
pause("Independent Replay, strict policy, target project and Journey do not bind");
|
|
182
|
+
}
|
|
183
|
+
const covered = [];
|
|
184
|
+
for (const fact of caseData.observables) {
|
|
185
|
+
if (fact.kind === "visibleText") {
|
|
186
|
+
const expected = {
|
|
187
|
+
type: "element", locator: { text: fact.text }, timeoutMs: fact.timeoutMs
|
|
188
|
+
};
|
|
189
|
+
const matches = journey.steps.map((step, index) =>
|
|
190
|
+
step.action === fact.afterAction
|
|
191
|
+
&& JSON.stringify(canonical(step.expect)) === JSON.stringify(canonical(expected))
|
|
192
|
+
? index : -1).filter((index) => index >= 0);
|
|
193
|
+
if (matches.length !== 1 || (report.status === "passed"
|
|
194
|
+
&& report.steps[matches[0]]?.expectation?.type !== "element")
|
|
195
|
+
|| (report.steps[matches[0]]?.expectation !== undefined
|
|
196
|
+
&& report.steps[matches[0]].expectation.type !== "element")) {
|
|
197
|
+
pause(`Observable ${fact.id} is missing, duplicated, or unsupported in this Journey`);
|
|
198
|
+
}
|
|
199
|
+
covered.push({ id: fact.id, source: "step", index: matches[0] });
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
const expected = expectedCheckpoint(fact);
|
|
203
|
+
const definitions = (journey.checkpoints ?? []).filter(
|
|
204
|
+
(checkpoint) => checkpoint.id === fact.id
|
|
205
|
+
);
|
|
206
|
+
if (definitions.length !== 1 || definitions[0].version !== 1
|
|
207
|
+
|| !checkpointMatches(definitions[0].expect, expected.expect)) {
|
|
208
|
+
pause(`Observable ${fact.id} needs one exact dedicated Checkpoint`);
|
|
209
|
+
}
|
|
210
|
+
const reports = (report.checkpoints ?? []).filter((checkpoint) =>
|
|
211
|
+
checkpoint.id === fact.id);
|
|
212
|
+
if (reports.length > 1 || (report.status === "passed" && reports.length !== 1)
|
|
213
|
+
|| (reports[0] !== undefined && (
|
|
214
|
+
!validCheckpointReport(reports[0])
|
|
215
|
+
||
|
|
216
|
+
reports[0].conditions.length !== 1
|
|
217
|
+
|| JSON.stringify(canonical({
|
|
218
|
+
kind: reports[0].conditions[0]?.kind,
|
|
219
|
+
...("locator" in (reports[0].conditions[0] ?? {})
|
|
220
|
+
? { locator: reports[0].conditions[0].locator }
|
|
221
|
+
: { expect: reports[0].conditions[0]?.expect })
|
|
222
|
+
})) !== JSON.stringify(canonical(expected.condition))
|
|
223
|
+
))) {
|
|
224
|
+
pause(`Observable ${fact.id} Checkpoint report is missing or changed`);
|
|
225
|
+
}
|
|
226
|
+
covered.push({ id: fact.id, source: "checkpoint", index: fact.id });
|
|
227
|
+
}
|
|
228
|
+
if (new Set(covered.map((entry) => `${entry.source}:${String(entry.index)}`)).size
|
|
229
|
+
!== covered.length) {
|
|
230
|
+
pause("Distinct observable facts must have separate evidence identities");
|
|
231
|
+
}
|
|
232
|
+
return covered;
|
|
233
|
+
}
|
|
234
|
+
async function checkReceipt(receiptPath, project, journeyPath, reportPath,
|
|
235
|
+
device, hashes, expectedExit = 0) {
|
|
236
|
+
const receipt = await json(receiptPath);
|
|
237
|
+
if (receipt.version !== 1 || receipt.exitCode !== expectedExit
|
|
238
|
+
|| !Array.isArray(receipt.argv) || receipt.argv[0] !== "verify"
|
|
239
|
+
|| !receipt.argv.includes("--policy-from-meta") || !receipt.argv.includes("--json")
|
|
240
|
+
|| receipt.argv.includes("--contract")
|
|
241
|
+
|| receipt.argv[receipt.argv.indexOf("--project") + 1] !== project
|
|
242
|
+
|| receipt.argv[receipt.argv.indexOf("--journey") + 1] !== journeyPath
|
|
243
|
+
|| receipt.argv[receipt.argv.indexOf("--device") + 1] !== device
|
|
244
|
+
|| receipt.journeySha256 !== hashes.journey
|
|
245
|
+
|| receipt.reportSha256 !== hashes.report
|
|
246
|
+
|| receipt.reportPath !== reportPath
|
|
247
|
+
|| Object.keys(receipt).sort().join(",")
|
|
248
|
+
!== "argv,exitCode,journeySha256,reportPath,reportSha256,version") {
|
|
249
|
+
pause("Independent strict verify process receipt is unavailable or does not bind the run");
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
const bundleFiles = [
|
|
253
|
+
"case.json", "before-journey.json", "before-meta.json",
|
|
254
|
+
"before-report.json", "before-receipt.json"
|
|
255
|
+
];
|
|
256
|
+
async function publish(inputPath, sharedPath) {
|
|
257
|
+
const input = await json(await file(inputPath));
|
|
258
|
+
const base = await root(input.base?.projectRoot);
|
|
259
|
+
const shared = await root(sharedPath);
|
|
260
|
+
if (inside(base, shared)) pause("Shared Case directory must be outside the historical project");
|
|
261
|
+
const casePath = await file(input.casePath);
|
|
262
|
+
if (!inside(base, casePath) || inside(join(base, ".taphound", "build"), casePath)) {
|
|
263
|
+
pause("Frozen Case must belong to historical committed project assets");
|
|
264
|
+
}
|
|
265
|
+
const caseData = await json(casePath);
|
|
266
|
+
checkCase(caseData);
|
|
267
|
+
if (input.version !== 1 || caseData.caseId !== input.caseId
|
|
268
|
+
|| caseData.packageName !== input.base.packageName
|
|
269
|
+
|| !sha(input.base.apkSha256) || !/^[a-zA-Z0-9._:-]+$/.test(input.base.deviceSerial ?? "")
|
|
270
|
+
|| await fileHash(await file(input.base.apkPath)) !== input.base.apkSha256) {
|
|
271
|
+
pause("Case/package/device or A's locally built APK attestation is invalid");
|
|
272
|
+
}
|
|
273
|
+
const journeyPath = await file(input.before?.journeyPath);
|
|
274
|
+
const metaPath = await file(input.before?.metaPath);
|
|
275
|
+
const reportPath = await file(input.before?.reportPath);
|
|
276
|
+
const receiptPath = await file(input.before?.receiptPath);
|
|
277
|
+
if (!inside(join(base, ".taphound", "journeys"), journeyPath)
|
|
278
|
+
|| metaPath !== journeyPath.replace(/\.json$/, ".meta.json")
|
|
279
|
+
|| !inside(base, reportPath) || !inside(base, receiptPath)) {
|
|
280
|
+
pause("Historical Journey/meta and Replay evidence must belong to the base project");
|
|
281
|
+
}
|
|
282
|
+
const expectedInputKeys = "base,before,caseId,casePath,version";
|
|
283
|
+
const expectedBaseKeys = "apkPath,apkSha256,deviceSerial,packageName,projectRoot";
|
|
284
|
+
const expectedBeforeKeys = "journeyPath,metaPath,receiptPath,reportPath";
|
|
285
|
+
if (Object.keys(input).sort().join(",") !== expectedInputKeys
|
|
286
|
+
|| Object.keys(input.base).sort().join(",") !== expectedBaseKeys
|
|
287
|
+
|| Object.keys(input.before).sort().join(",") !== expectedBeforeKeys) {
|
|
288
|
+
pause("Preparation input contains unsupported fields");
|
|
289
|
+
}
|
|
290
|
+
const journey = await json(journeyPath);
|
|
291
|
+
const meta = await json(metaPath);
|
|
292
|
+
const report = await json(reportPath);
|
|
293
|
+
if (meta.journeyPath !== relative(base, journeyPath).split(sep).join("/")) {
|
|
294
|
+
pause("Historical meta does not bind its conventional Journey path");
|
|
295
|
+
}
|
|
296
|
+
const covered = checkReplay(base, input.base.deviceSerial,
|
|
297
|
+
caseData, journey, meta, report, reportPath);
|
|
298
|
+
const hashes = {
|
|
299
|
+
journey: journeyHash(journey), report: await fileHash(reportPath)
|
|
300
|
+
};
|
|
301
|
+
await checkReceipt(receiptPath, base, journeyPath, reportPath,
|
|
302
|
+
input.base.deviceSerial, hashes);
|
|
303
|
+
if (report.status !== "passed"
|
|
304
|
+
|| report.steps.some((step) => step.status !== "passed")
|
|
305
|
+
|| covered.some((entry) => !observablePassed(report, entry))) {
|
|
306
|
+
pause("Historical APK has no passing evidence for all frozen observables");
|
|
307
|
+
}
|
|
308
|
+
const git = spawnSync("git", ["-C", base, "rev-parse", "HEAD"], {
|
|
309
|
+
encoding: "utf8", timeout: 10000
|
|
310
|
+
});
|
|
311
|
+
if (git.status !== 0 || !/^[a-f0-9]{40,64}$/.test(git.stdout.trim())) {
|
|
312
|
+
pause("Historical Git commit is unavailable");
|
|
313
|
+
}
|
|
314
|
+
const output = join(shared, caseData.caseId);
|
|
315
|
+
try {
|
|
316
|
+
await lstat(output);
|
|
317
|
+
pause("Case already published; silent overwrite is forbidden");
|
|
318
|
+
} catch (error) {
|
|
319
|
+
if (error.code !== "ENOENT") throw error;
|
|
320
|
+
}
|
|
321
|
+
const stage = await mkdtemp(join(shared, `.ui-refactor-${caseData.caseId}-`));
|
|
322
|
+
const sources = [
|
|
323
|
+
casePath, journeyPath, metaPath, reportPath, receiptPath
|
|
324
|
+
];
|
|
325
|
+
try {
|
|
326
|
+
const before = await Promise.all(sources.map(fileHash));
|
|
327
|
+
for (const [index, name] of bundleFiles.entries()) {
|
|
328
|
+
const source = sources[index];
|
|
329
|
+
if (source === undefined) pause("Internal bundle source inventory is incomplete");
|
|
330
|
+
await copyFile(source, join(stage, name), constants.COPYFILE_EXCL);
|
|
331
|
+
}
|
|
332
|
+
const artifacts = {};
|
|
333
|
+
for (const name of bundleFiles) {
|
|
334
|
+
artifacts[name] = await fileHash(join(stage, name));
|
|
335
|
+
}
|
|
336
|
+
const after = await Promise.all(sources.map(fileHash));
|
|
337
|
+
if (before.some((value, index) => value !== after[index]
|
|
338
|
+
|| bundleFiles[index] === undefined
|
|
339
|
+
|| value !== artifacts[bundleFiles[index]])) pause("Before-run assets changed during export");
|
|
340
|
+
const md = [
|
|
341
|
+
"# UI-refactor Preserve handoff", "",
|
|
342
|
+
`Case: \`${caseData.caseId}\` (READY)`,
|
|
343
|
+
`Goal: ${caseData.goal}`,
|
|
344
|
+
`Test fixture reference: \`${caseData.fixtureRef}\``,
|
|
345
|
+
`Base Git commit: \`${git.stdout.trim()}\``,
|
|
346
|
+
`Package: \`${caseData.packageName}\`; device: \`${input.base.deviceSerial}\``,
|
|
347
|
+
`Old built/installed APK hash (Agent A attestation): \`${input.base.apkSha256}\``,
|
|
348
|
+
`Old report: \`${report.runId}\` (passed)`,
|
|
349
|
+
"", "| Frozen file | SHA-256 |", "|---|---|",
|
|
350
|
+
...Object.entries(artifacts).map(([name, value]) =>
|
|
351
|
+
`| \`${name}\` | \`${value}\` |`),
|
|
352
|
+
"", "B receives only this path. Validate the Case bundle before device work.",
|
|
353
|
+
"Generate a NEW Journey and strict independent Replay for the same",
|
|
354
|
+
"frozen observables. Old Journey/meta are audit-only, not staged in B.",
|
|
355
|
+
""
|
|
356
|
+
].join("\n");
|
|
357
|
+
await writeFile(join(stage, "handoff.md"), md, { flag: "wx" });
|
|
358
|
+
await writeFile(join(stage, "handoff.json"), `${JSON.stringify({
|
|
359
|
+
version: 1, status: "READY", caseId: caseData.caseId,
|
|
360
|
+
base: {
|
|
361
|
+
projectRoot: base, commit: git.stdout.trim(), device: input.base.deviceSerial,
|
|
362
|
+
apkSha256: input.base.apkSha256, runId: report.runId
|
|
363
|
+
},
|
|
364
|
+
artifacts, mdSha256: hash(md)
|
|
365
|
+
}, null, 2)}\n`, { flag: "wx" });
|
|
366
|
+
await rename(stage, output);
|
|
367
|
+
return { status: "READY", handoff: join(output, "handoff.md") };
|
|
368
|
+
} catch (error) {
|
|
369
|
+
await rm(stage, { recursive: true, force: true });
|
|
370
|
+
throw error;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
async function validate(handoffPath, targetPath) {
|
|
374
|
+
const handoff = await file(handoffPath);
|
|
375
|
+
if (handoff.split(sep).at(-1) !== "handoff.md") pause("B only accepts the MD handoff path");
|
|
376
|
+
const dir = await root(dirname(handoff));
|
|
377
|
+
const manifestPath = await file(join(dir, "handoff.json"));
|
|
378
|
+
const manifest = await json(manifestPath);
|
|
379
|
+
const md = await readFile(handoff, "utf8");
|
|
380
|
+
if (manifest.version !== 1 || manifest.status !== "READY"
|
|
381
|
+
|| !id(manifest.caseId) || manifest.caseId !== dir.split(sep).at(-1)
|
|
382
|
+
|| !sha(manifest.mdSha256) || hash(md) !== manifest.mdSha256
|
|
383
|
+
|| !/^[a-f0-9]{40,64}$/.test(manifest.base?.commit ?? "")
|
|
384
|
+
|| !sha(manifest.base?.apkSha256)
|
|
385
|
+
|| !isAbsolute(manifest.base?.projectRoot ?? "")
|
|
386
|
+
|| !manifest.artifacts || typeof manifest.artifacts !== "object"
|
|
387
|
+
|| JSON.stringify((await readdir(dir)).sort()) !== JSON.stringify(
|
|
388
|
+
[...bundleFiles, "handoff.md", "handoff.json"].sort())
|
|
389
|
+
|| Object.keys(manifest.artifacts).sort().join(",") !== bundleFiles.slice().sort().join(",")) {
|
|
390
|
+
pause("Handoff MD, manifest and file inventory disagree");
|
|
391
|
+
}
|
|
392
|
+
for (const name of bundleFiles) {
|
|
393
|
+
const path = await file(join(dir, name));
|
|
394
|
+
if (!sha(manifest.artifacts[name]) || await fileHash(path) !== manifest.artifacts[name]
|
|
395
|
+
|| !md.includes(`| \`${name}\` | \`${manifest.artifacts[name]}\` |`)) {
|
|
396
|
+
pause("Frozen historical file or MD digest changed");
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
const caseData = await json(join(dir, "case.json"));
|
|
400
|
+
checkCase(caseData);
|
|
401
|
+
if (caseData.caseId !== manifest.caseId) pause("Frozen Case identity changed");
|
|
402
|
+
const journey = await json(join(dir, "before-journey.json"));
|
|
403
|
+
const meta = await json(join(dir, "before-meta.json"));
|
|
404
|
+
const report = await json(join(dir, "before-report.json"));
|
|
405
|
+
const originalJourney = join(manifest.base.projectRoot, meta.journeyPath ?? "");
|
|
406
|
+
const originalReport = join(report.artifacts?.directory ?? "", "report.json");
|
|
407
|
+
if (!inside(manifest.base.projectRoot, originalJourney)
|
|
408
|
+
|| !inside(manifest.base.projectRoot, originalReport)
|
|
409
|
+
|| report.runId !== manifest.base.runId
|
|
410
|
+
|| caseData.packageName !== report.project?.packageName) {
|
|
411
|
+
pause("Historical evidence provenance disagrees with frozen Case");
|
|
412
|
+
}
|
|
413
|
+
await checkReceipt(join(dir, "before-receipt.json"), manifest.base.projectRoot,
|
|
414
|
+
originalJourney, originalReport, manifest.base.device, {
|
|
415
|
+
journey: journeyHash(journey), report: manifest.artifacts["before-report.json"]
|
|
416
|
+
});
|
|
417
|
+
const covered = checkReplay(manifest.base.projectRoot, manifest.base.device,
|
|
418
|
+
caseData, journey, meta, report, originalReport);
|
|
419
|
+
if (report.status !== "passed"
|
|
420
|
+
|| covered.some((entry) => !observablePassed(report, entry))) {
|
|
421
|
+
pause("Historical observable evidence is not passed");
|
|
422
|
+
}
|
|
423
|
+
if (targetPath !== undefined) {
|
|
424
|
+
const project = await root(targetPath);
|
|
425
|
+
const configPath = await file(join(project, ".taphound", "config.json"));
|
|
426
|
+
const config = await json(configPath);
|
|
427
|
+
if (config.run?.packageName !== caseData.packageName) {
|
|
428
|
+
pause("Target package is not the frozen Case package");
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
return { status: "READY", caseId: caseData.caseId, dir, caseData, manifest };
|
|
432
|
+
}
|
|
433
|
+
async function compare(handoff, projectPath, journeyPath, reportPath, receiptPath) {
|
|
434
|
+
const data = await validate(handoff, projectPath);
|
|
435
|
+
const project = await root(projectPath);
|
|
436
|
+
const journeyFile = await file(journeyPath);
|
|
437
|
+
const metaFile = await file(journeyFile.replace(/\.json$/, ".meta.json"));
|
|
438
|
+
const reportFile = await file(reportPath);
|
|
439
|
+
const receiptFile = await file(receiptPath);
|
|
440
|
+
if (!inside(join(project, ".taphound", "journeys"), journeyFile)
|
|
441
|
+
|| !inside(project, reportFile) || !inside(project, receiptFile)
|
|
442
|
+
|| !inside(project, metaFile)) pause("Target evidence must belong to B's project");
|
|
443
|
+
const journey = await json(journeyFile);
|
|
444
|
+
const meta = await json(metaFile);
|
|
445
|
+
const report = await json(reportFile);
|
|
446
|
+
if (meta.journeyPath !== relative(project, journeyFile).split(sep).join("/")) {
|
|
447
|
+
pause("New strict meta does not bind B's generated Journey path");
|
|
448
|
+
}
|
|
449
|
+
await checkReceipt(receiptFile, project, journeyFile, reportFile,
|
|
450
|
+
data.manifest.base.device, {
|
|
451
|
+
journey: journeyHash(journey), report: await fileHash(reportFile)
|
|
452
|
+
}, report.status === "failed" ? 1 : 0);
|
|
453
|
+
const covered = checkReplay(project, data.manifest.base.device,
|
|
454
|
+
data.caseData, journey, meta, report, reportFile);
|
|
455
|
+
if (report.runId === data.manifest.base.runId) {
|
|
456
|
+
pause("Before and after evidence must come from independent run IDs");
|
|
457
|
+
}
|
|
458
|
+
if (report.journey.sha256 === (await json(join(data.dir, "before-report.json"))).journey.sha256) {
|
|
459
|
+
pause("UI-refactor mode needs a newly authored Journey, not the frozen old one");
|
|
460
|
+
}
|
|
461
|
+
const failed = covered.filter((entry) =>
|
|
462
|
+
!observablePassed(report, entry));
|
|
463
|
+
if (report.status === "error" || report.status === "manualRequired") {
|
|
464
|
+
pause("Target Replay did not produce comparable device evidence");
|
|
465
|
+
}
|
|
466
|
+
if (report.status === "failed"
|
|
467
|
+
&& (report.primaryFailure?.code === undefined || report.layers?.run !== "failed"
|
|
468
|
+
|| !report.steps.some((step) => step.status === "failed"))) {
|
|
469
|
+
pause("Failed target report lacks a deterministic primary Replay failure");
|
|
470
|
+
}
|
|
471
|
+
const outcome = report.status !== "passed" || failed.length > 0 ? "FAIL" : "PASS";
|
|
472
|
+
return {
|
|
473
|
+
status: outcome, caseId: data.caseId,
|
|
474
|
+
coverage: { required: covered.length, evidenced: covered.length - failed.length },
|
|
475
|
+
beforeRunId: data.manifest.base.runId, afterRunId: report.runId,
|
|
476
|
+
beforeJourneySha256: (await json(join(data.dir, "before-report.json"))).journey.sha256,
|
|
477
|
+
afterJourneySha256: report.journey.sha256,
|
|
478
|
+
...(failed.length === 0 ? {} : { failedObservables: failed.map((entry) => entry.id) })
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
const [operation, ...args] = process.argv.slice(2);
|
|
482
|
+
function flag(name) {
|
|
483
|
+
const index = args.indexOf(`--${name}`);
|
|
484
|
+
if (index < 0 || index + 1 >= args.length) pause(`Missing --${name}`);
|
|
485
|
+
return args[index + 1];
|
|
486
|
+
}
|
|
487
|
+
try {
|
|
488
|
+
const result = operation === "prepare"
|
|
489
|
+
? await publish(flag("input"), flag("out"))
|
|
490
|
+
: operation === "validate"
|
|
491
|
+
? await validate(flag("handoff"), args.includes("--project") ? flag("project") : undefined)
|
|
492
|
+
: operation === "compare"
|
|
493
|
+
? await compare(flag("handoff"), flag("project"), flag("journey"),
|
|
494
|
+
flag("report"), flag("receipt"))
|
|
495
|
+
: pause("Use prepare, validate or compare");
|
|
496
|
+
const publicResult = operation === "validate"
|
|
497
|
+
? { status: result.status, caseId: result.caseId } : result;
|
|
498
|
+
process.stdout.write(`${JSON.stringify(publicResult)}\n`);
|
|
499
|
+
process.exitCode = result.status === "FAIL" ? 1 : 0;
|
|
500
|
+
} catch (error) {
|
|
501
|
+
process.stdout.write(`${JSON.stringify({
|
|
502
|
+
status: "PAUSED", reason: error instanceof Error ? error.message : String(error)
|
|
503
|
+
})}\n`);
|
|
504
|
+
process.exitCode = 2;
|
|
505
|
+
}
|
|
@@ -1,23 +1,37 @@
|
|
|
1
1
|
import { type ForegroundComponent } from "../../domain/activity.js";
|
|
2
2
|
import { type AppProcess } from "../../domain/app-process.js";
|
|
3
|
-
import type { AdbPort, AppIdentity, DeviceInfo, LaunchActivityOptions, LogcatOptions } from "../../ports/adb.js";
|
|
4
|
-
import type { Point } from "../../
|
|
3
|
+
import type { AdbPort, AppIdentity, DeviceIdentity, DeviceInfo, DumpLogcatOptions, LaunchActivityOptions, LogcatOptions, ResolveLauncherActivityOptions, ResolvedActivity, StartActivityByIntentOptions } from "../../ports/adb.js";
|
|
4
|
+
import type { Point } from "../../domain/geometry.js";
|
|
5
5
|
import type { CommandResult, ProcessRunner, RunningCommand } from "../../ports/process-runner.js";
|
|
6
|
+
import { type WindowTopology } from "../../domain/window-hierarchy.js";
|
|
6
7
|
export declare class AdbAdapter implements AdbPort {
|
|
7
8
|
private readonly runner;
|
|
8
9
|
constructor(runner: ProcessRunner);
|
|
9
10
|
private run;
|
|
10
11
|
devices(signal?: AbortSignal): Promise<readonly DeviceInfo[]>;
|
|
12
|
+
deviceIdentity(identity: AppIdentity): Promise<DeviceIdentity>;
|
|
11
13
|
foregroundComponent(identity: AppIdentity): Promise<ForegroundComponent>;
|
|
12
14
|
currentActivity(identity: AppIdentity): Promise<string>;
|
|
13
15
|
isInstalled(identity: AppIdentity): Promise<boolean>;
|
|
14
16
|
launchActivity(options: LaunchActivityOptions): Promise<CommandResult>;
|
|
17
|
+
startActivityByIntent(options: StartActivityByIntentOptions): Promise<CommandResult>;
|
|
18
|
+
resolveLauncherActivity(options: ResolveLauncherActivityOptions): Promise<ResolvedActivity | undefined>;
|
|
15
19
|
forceStop(identity: AppIdentity): Promise<CommandResult>;
|
|
16
20
|
appProcesses(identity: AppIdentity): Promise<readonly AppProcess[]>;
|
|
21
|
+
windowTopology(identity: AppIdentity): Promise<WindowTopology>;
|
|
17
22
|
tap(point: Point, deviceSerial: string, signal?: AbortSignal): Promise<CommandResult>;
|
|
18
23
|
longClick(point: Point, durationMs: number, deviceSerial: string, signal?: AbortSignal): Promise<CommandResult>;
|
|
19
24
|
swipe(from: Point, to: Point, durationMs: number, deviceSerial: string, signal?: AbortSignal): Promise<CommandResult>;
|
|
20
25
|
back(deviceSerial: string, signal?: AbortSignal): Promise<CommandResult>;
|
|
21
26
|
inputText(text: string, deviceSerial: string, signal?: AbortSignal): Promise<CommandResult>;
|
|
27
|
+
/**
|
|
28
|
+
* `adb shell input text` cannot type non-ASCII characters (KeyCharacterMap
|
|
29
|
+
* resolves them to no events and the input command fails), so non-ASCII text
|
|
30
|
+
* is delivered through the mobilenext devicekit clipboard: set the clipboard
|
|
31
|
+
* by broadcast, paste it into the focused element with KEYCODE_PASTE, then
|
|
32
|
+
* clear the clipboard again.
|
|
33
|
+
*/
|
|
34
|
+
private inputNonAsciiText;
|
|
22
35
|
startLogcat(options: LogcatOptions): RunningCommand;
|
|
36
|
+
dumpLogcat(options: DumpLogcatOptions): Promise<CommandResult>;
|
|
23
37
|
}
|