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,112 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { mkdir, realpath, rename, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
4
|
+
import { isContained } from "../../shared/paths.js";
|
|
5
|
+
function errorMessage(error) {
|
|
6
|
+
return error instanceof Error ? error.message : "Unknown write failure";
|
|
7
|
+
}
|
|
8
|
+
export class FileSystemContextDocumentWriter {
|
|
9
|
+
writeContextDocument = async (input) => {
|
|
10
|
+
const normalizedPath = input.relativePath.replaceAll("\\", "/");
|
|
11
|
+
if (normalizedPath.length === 0
|
|
12
|
+
|| normalizedPath.startsWith("/")
|
|
13
|
+
|| /^[A-Za-z]:/.test(normalizedPath)
|
|
14
|
+
|| normalizedPath.split("/").includes("..")) {
|
|
15
|
+
return { status: "escape" };
|
|
16
|
+
}
|
|
17
|
+
let canonicalRoot;
|
|
18
|
+
try {
|
|
19
|
+
canonicalRoot = await realpath(input.projectRoot);
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
return { status: "unwritable", message: errorMessage(error) };
|
|
23
|
+
}
|
|
24
|
+
const target = resolve(canonicalRoot, normalizedPath);
|
|
25
|
+
if (!isContained(canonicalRoot, target)) {
|
|
26
|
+
return { status: "escape" };
|
|
27
|
+
}
|
|
28
|
+
const bytes = `${JSON.stringify(input.document, null, 2)}\n`;
|
|
29
|
+
const directory = dirname(target);
|
|
30
|
+
const temporaryPath = join(directory, `.${basename(target)}.${randomUUID()}.tmp`);
|
|
31
|
+
try {
|
|
32
|
+
await mkdir(directory, { recursive: true });
|
|
33
|
+
await writeFile(temporaryPath, bytes, "utf8");
|
|
34
|
+
await rename(temporaryPath, target);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
await rm(temporaryPath, { force: true }).catch(() => undefined);
|
|
38
|
+
return { status: "unwritable", message: errorMessage(error) };
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
status: "written",
|
|
42
|
+
sha256: createHash("sha256").update(bytes).digest("hex")
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
writeContextDocumentBatch = async (input) => {
|
|
46
|
+
let canonicalRoot;
|
|
47
|
+
try {
|
|
48
|
+
canonicalRoot = await realpath(input.projectRoot);
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
const failure = {
|
|
52
|
+
status: "unwritable",
|
|
53
|
+
message: errorMessage(error)
|
|
54
|
+
};
|
|
55
|
+
return input.documents.map(() => failure);
|
|
56
|
+
}
|
|
57
|
+
const prepared = [];
|
|
58
|
+
for (const doc of input.documents) {
|
|
59
|
+
const normalizedPath = doc.relativePath.replaceAll("\\", "/");
|
|
60
|
+
if (normalizedPath.length === 0
|
|
61
|
+
|| normalizedPath.startsWith("/")
|
|
62
|
+
|| /^[A-Za-z]:/.test(normalizedPath)
|
|
63
|
+
|| normalizedPath.split("/").includes("..")) {
|
|
64
|
+
for (const item of prepared) {
|
|
65
|
+
await rm(item.tempPath, { force: true }).catch(() => undefined);
|
|
66
|
+
}
|
|
67
|
+
const failure = { status: "escape" };
|
|
68
|
+
return input.documents.map(() => failure);
|
|
69
|
+
}
|
|
70
|
+
const target = resolve(canonicalRoot, normalizedPath);
|
|
71
|
+
if (!isContained(canonicalRoot, target)) {
|
|
72
|
+
for (const item of prepared) {
|
|
73
|
+
await rm(item.tempPath, { force: true }).catch(() => undefined);
|
|
74
|
+
}
|
|
75
|
+
const failure = { status: "escape" };
|
|
76
|
+
return input.documents.map(() => failure);
|
|
77
|
+
}
|
|
78
|
+
const bytes = `${JSON.stringify(doc.document, null, 2)}\n`;
|
|
79
|
+
const directory = dirname(target);
|
|
80
|
+
const tempPath = join(directory, `.${basename(target)}.${randomUUID()}.tmp`);
|
|
81
|
+
prepared.push({
|
|
82
|
+
target,
|
|
83
|
+
tempPath,
|
|
84
|
+
bytes,
|
|
85
|
+
sha256: createHash("sha256").update(bytes).digest("hex")
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
for (const item of prepared) {
|
|
90
|
+
await mkdir(dirname(item.target), { recursive: true });
|
|
91
|
+
await writeFile(item.tempPath, item.bytes, "utf8");
|
|
92
|
+
}
|
|
93
|
+
for (const item of prepared) {
|
|
94
|
+
await rename(item.tempPath, item.target);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
for (const item of prepared) {
|
|
99
|
+
await rm(item.tempPath, { force: true }).catch(() => undefined);
|
|
100
|
+
}
|
|
101
|
+
const failure = {
|
|
102
|
+
status: "unwritable",
|
|
103
|
+
message: errorMessage(error)
|
|
104
|
+
};
|
|
105
|
+
return input.documents.map(() => failure);
|
|
106
|
+
}
|
|
107
|
+
return prepared.map((item) => ({
|
|
108
|
+
status: "written",
|
|
109
|
+
sha256: item.sha256
|
|
110
|
+
}));
|
|
111
|
+
};
|
|
112
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type CommandEvent } from "../../domain/diagnostics.js";
|
|
2
|
+
import type { DiagnosticsJournal } from "../../ports/diagnostics.js";
|
|
3
|
+
/**
|
|
4
|
+
* Appends one JSON line per invocation under `.taphound/build/log/`. It only
|
|
5
|
+
* writes where a command already initialized the ignored build layout (the
|
|
6
|
+
* build directory and `.taphound/.gitignore`), so read-only commands never
|
|
7
|
+
* create project files, and keeps the current file under `maxBytes` with one
|
|
8
|
+
* rotated predecessor.
|
|
9
|
+
*/
|
|
10
|
+
export declare class FileSystemDiagnosticsJournal implements DiagnosticsJournal {
|
|
11
|
+
private readonly maxBytes;
|
|
12
|
+
constructor(maxBytes?: number);
|
|
13
|
+
append(projectRoot: string, event: CommandEvent): Promise<void>;
|
|
14
|
+
readLines(projectRoot: string): Promise<readonly string[]>;
|
|
15
|
+
salt(projectRoot: string): Promise<Buffer>;
|
|
16
|
+
private ensureLogDirectory;
|
|
17
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { constants } from "node:fs";
|
|
3
|
+
import { lstat, mkdir, open, readFile, rename } from "node:fs/promises";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { CommandEventSchema, DIAGNOSTICS_EVENTS_FILE, DIAGNOSTICS_EVENTS_ROTATED_FILE, DIAGNOSTICS_SALT_FILE } from "../../domain/diagnostics.js";
|
|
6
|
+
import { BUILD_DIR, BUILD_IGNORE_FILE, DIAGNOSTICS_LOG_DIR, TAPHOUND_DIR } from "../../domain/workspace.js";
|
|
7
|
+
import { ensureBuildLayout } from "./workspace-layout.js";
|
|
8
|
+
const DEFAULT_MAX_BYTES = 1024 * 1024;
|
|
9
|
+
function isMissing(error) {
|
|
10
|
+
return error?.code === "ENOENT";
|
|
11
|
+
}
|
|
12
|
+
async function isDirectory(path) {
|
|
13
|
+
try {
|
|
14
|
+
const stats = await lstat(path);
|
|
15
|
+
return stats.isDirectory() && !stats.isSymbolicLink();
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
if (isMissing(error))
|
|
19
|
+
return false;
|
|
20
|
+
throw error;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
async function sizeOf(path) {
|
|
24
|
+
try {
|
|
25
|
+
return (await lstat(path)).size;
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
if (isMissing(error))
|
|
29
|
+
return 0;
|
|
30
|
+
throw error;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async function readOptional(path) {
|
|
34
|
+
try {
|
|
35
|
+
return await readFile(path, "utf8");
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
if (isMissing(error))
|
|
39
|
+
return "";
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
async function isFile(path) {
|
|
44
|
+
try {
|
|
45
|
+
const stats = await lstat(path);
|
|
46
|
+
return stats.isFile();
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
if (isMissing(error))
|
|
50
|
+
return false;
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Appends one JSON line per invocation under `.taphound/build/log/`. It only
|
|
56
|
+
* writes where a command already initialized the ignored build layout (the
|
|
57
|
+
* build directory and `.taphound/.gitignore`), so read-only commands never
|
|
58
|
+
* create project files, and keeps the current file under `maxBytes` with one
|
|
59
|
+
* rotated predecessor.
|
|
60
|
+
*/
|
|
61
|
+
export class FileSystemDiagnosticsJournal {
|
|
62
|
+
maxBytes;
|
|
63
|
+
constructor(maxBytes = DEFAULT_MAX_BYTES) {
|
|
64
|
+
this.maxBytes = maxBytes;
|
|
65
|
+
}
|
|
66
|
+
async append(projectRoot, event) {
|
|
67
|
+
if (!await isDirectory(join(projectRoot, BUILD_DIR))
|
|
68
|
+
|| !await isFile(join(projectRoot, BUILD_IGNORE_FILE))) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const line = `${JSON.stringify(CommandEventSchema.parse(event))}\n`;
|
|
72
|
+
const directory = await this.ensureLogDirectory(projectRoot);
|
|
73
|
+
const current = join(directory, DIAGNOSTICS_EVENTS_FILE);
|
|
74
|
+
if (await sizeOf(current) + Buffer.byteLength(line) > this.maxBytes) {
|
|
75
|
+
await rename(current, join(directory, DIAGNOSTICS_EVENTS_ROTATED_FILE))
|
|
76
|
+
.catch((error) => {
|
|
77
|
+
if (!isMissing(error))
|
|
78
|
+
throw error;
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
const handle = await open(current, constants.O_WRONLY | constants.O_CREAT | constants.O_APPEND | constants.O_NOFOLLOW, 0o600);
|
|
82
|
+
try {
|
|
83
|
+
await handle.writeFile(line, "utf8");
|
|
84
|
+
}
|
|
85
|
+
finally {
|
|
86
|
+
await handle.close();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
async readLines(projectRoot) {
|
|
90
|
+
const directory = join(projectRoot, DIAGNOSTICS_LOG_DIR);
|
|
91
|
+
const text = await readOptional(join(directory, DIAGNOSTICS_EVENTS_ROTATED_FILE))
|
|
92
|
+
+ await readOptional(join(directory, DIAGNOSTICS_EVENTS_FILE));
|
|
93
|
+
return text.split("\n").filter((line) => line.trim().length > 0);
|
|
94
|
+
}
|
|
95
|
+
async salt(projectRoot) {
|
|
96
|
+
if (!await isDirectory(join(projectRoot, TAPHOUND_DIR))) {
|
|
97
|
+
throw Object.assign(new Error(`Not a TapHound project (no ${TAPHOUND_DIR}/ directory): ${projectRoot}`), { code: "CONFIG_INVALID" });
|
|
98
|
+
}
|
|
99
|
+
await ensureBuildLayout(projectRoot);
|
|
100
|
+
const path = join(await this.ensureLogDirectory(projectRoot), DIAGNOSTICS_SALT_FILE);
|
|
101
|
+
try {
|
|
102
|
+
const handle = await open(path, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 0o600);
|
|
103
|
+
const salt = randomBytes(32);
|
|
104
|
+
try {
|
|
105
|
+
await handle.writeFile(salt);
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
await handle.close();
|
|
109
|
+
}
|
|
110
|
+
return salt;
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
if (error.code !== "EEXIST")
|
|
114
|
+
throw error;
|
|
115
|
+
}
|
|
116
|
+
const salt = await readFile(path);
|
|
117
|
+
if (salt.length < 16) {
|
|
118
|
+
throw new Error(`Diagnostics salt is unreadable: ${DIAGNOSTICS_LOG_DIR}/${DIAGNOSTICS_SALT_FILE}`);
|
|
119
|
+
}
|
|
120
|
+
return salt;
|
|
121
|
+
}
|
|
122
|
+
async ensureLogDirectory(projectRoot) {
|
|
123
|
+
const directory = join(projectRoot, DIAGNOSTICS_LOG_DIR);
|
|
124
|
+
await mkdir(directory, { recursive: true });
|
|
125
|
+
if (!await isDirectory(directory)) {
|
|
126
|
+
throw new Error(`${DIAGNOSTICS_LOG_DIR} is not a directory`);
|
|
127
|
+
}
|
|
128
|
+
return directory;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ExternalFlowCatalogEntry, ExternalFlowRecord, ExternalFlowRegistry, WriteExternalFlowInput, WriteExternalFlowResult } from "../../ports/external-flow-registry.js";
|
|
2
|
+
export declare class FileSystemExternalFlowRegistry implements ExternalFlowRegistry {
|
|
3
|
+
private readonly builtinFlowsRoot;
|
|
4
|
+
constructor(builtinFlowsRoot: string);
|
|
5
|
+
readonly read: (input: {
|
|
6
|
+
projectRoot: string;
|
|
7
|
+
name: string;
|
|
8
|
+
}) => Promise<ExternalFlowRecord>;
|
|
9
|
+
readonly list: (projectRoot: string) => Promise<readonly ExternalFlowCatalogEntry[]>;
|
|
10
|
+
readonly write: (input: WriteExternalFlowInput) => Promise<WriteExternalFlowResult>;
|
|
11
|
+
private readonly loadFlow;
|
|
12
|
+
private readonly resolveProjectFlowPath;
|
|
13
|
+
private readonly resolveBuiltinFlowPath;
|
|
14
|
+
private readonly readFlowFile;
|
|
15
|
+
private readonly listBuiltinFlows;
|
|
16
|
+
private readonly listProjectFlows;
|
|
17
|
+
}
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import { lstat, mkdir, readdir, readFile, realpath, rename, writeFile } from "node:fs/promises";
|
|
2
|
+
import { isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
3
|
+
import { ExternalFlowSchema } from "../../domain/external-flow.js";
|
|
4
|
+
import { EXTERNAL_FLOWS_DIR } from "../../domain/workspace.js";
|
|
5
|
+
const MAX_EXTERNAL_FLOW_BYTES = 1024 * 1024;
|
|
6
|
+
function contained(root, candidate) {
|
|
7
|
+
const fromRoot = relative(root, candidate);
|
|
8
|
+
return fromRoot === ""
|
|
9
|
+
|| (fromRoot !== ".."
|
|
10
|
+
&& !fromRoot.startsWith(`..${sep}`)
|
|
11
|
+
&& !isAbsolute(fromRoot));
|
|
12
|
+
}
|
|
13
|
+
function flowFileName(name) {
|
|
14
|
+
return `${name}.json`;
|
|
15
|
+
}
|
|
16
|
+
function errorDetails(error) {
|
|
17
|
+
if (error instanceof Error) {
|
|
18
|
+
return {
|
|
19
|
+
code: "EXTERNAL_FLOW_INVALID",
|
|
20
|
+
message: error.message
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
code: "EXTERNAL_FLOW_INVALID",
|
|
25
|
+
message: String(error)
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export class FileSystemExternalFlowRegistry {
|
|
29
|
+
builtinFlowsRoot;
|
|
30
|
+
constructor(builtinFlowsRoot) {
|
|
31
|
+
this.builtinFlowsRoot = builtinFlowsRoot;
|
|
32
|
+
}
|
|
33
|
+
read = async (input) => {
|
|
34
|
+
const loaded = await this.loadFlow(input.projectRoot, input.name);
|
|
35
|
+
return {
|
|
36
|
+
bytes: loaded.bytes,
|
|
37
|
+
flow: loaded.flow,
|
|
38
|
+
source: loaded.source,
|
|
39
|
+
path: loaded.path
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
list = async (projectRoot) => {
|
|
43
|
+
const builtinEntries = await this.listBuiltinFlows();
|
|
44
|
+
const projectEntries = await this.listProjectFlows(projectRoot);
|
|
45
|
+
const projectNames = new Set(projectEntries.map((entry) => entry.name));
|
|
46
|
+
const entries = [
|
|
47
|
+
...projectEntries,
|
|
48
|
+
...builtinEntries.filter((entry) => !projectNames.has(entry.name))
|
|
49
|
+
];
|
|
50
|
+
return entries.sort((left, right) => left.name.localeCompare(right.name));
|
|
51
|
+
};
|
|
52
|
+
write = async (input) => {
|
|
53
|
+
const { projectRoot, name, flow, force } = input;
|
|
54
|
+
const canonicalRoot = await realpath(projectRoot);
|
|
55
|
+
const externalRoot = resolve(canonicalRoot, EXTERNAL_FLOWS_DIR);
|
|
56
|
+
const targetPath = join(externalRoot, flowFileName(name));
|
|
57
|
+
const canonicalExternalRoot = await realpath(externalRoot).catch((error) => {
|
|
58
|
+
if (error instanceof Error
|
|
59
|
+
&& "code" in error
|
|
60
|
+
&& error.code === "ENOENT") {
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
throw error;
|
|
64
|
+
});
|
|
65
|
+
if (canonicalExternalRoot !== undefined
|
|
66
|
+
&& !contained(canonicalRoot, canonicalExternalRoot)) {
|
|
67
|
+
throw new Error("External Flow directory escapes the project root");
|
|
68
|
+
}
|
|
69
|
+
const rootStats = await lstat(externalRoot).catch((error) => {
|
|
70
|
+
if (error instanceof Error
|
|
71
|
+
&& "code" in error
|
|
72
|
+
&& error.code === "ENOENT") {
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
throw error;
|
|
76
|
+
});
|
|
77
|
+
if (rootStats !== undefined && rootStats.isSymbolicLink()) {
|
|
78
|
+
throw new Error("External Flow directory is a symlink");
|
|
79
|
+
}
|
|
80
|
+
const validated = ExternalFlowSchema.parse(flow);
|
|
81
|
+
if (validated.name !== name) {
|
|
82
|
+
throw new Error(`External Flow declares name "${validated.name}", expected "${name}"`);
|
|
83
|
+
}
|
|
84
|
+
let overwritten = false;
|
|
85
|
+
const existingStats = await lstat(targetPath).catch((error) => {
|
|
86
|
+
if (error instanceof Error
|
|
87
|
+
&& "code" in error
|
|
88
|
+
&& error.code === "ENOENT") {
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
throw error;
|
|
92
|
+
});
|
|
93
|
+
if (existingStats !== undefined) {
|
|
94
|
+
if (existingStats.isSymbolicLink()) {
|
|
95
|
+
throw new Error(`External Flow target is a symlink: ${targetPath}`);
|
|
96
|
+
}
|
|
97
|
+
if (!force) {
|
|
98
|
+
throw new Error(`External Flow already exists: ${name}`);
|
|
99
|
+
}
|
|
100
|
+
overwritten = true;
|
|
101
|
+
}
|
|
102
|
+
const parentDir = name.includes("/")
|
|
103
|
+
? join(externalRoot, name.split("/").slice(0, -1).join("/"))
|
|
104
|
+
: externalRoot;
|
|
105
|
+
await mkdir(parentDir, { recursive: true });
|
|
106
|
+
const tempPath = `${targetPath}.tmp`;
|
|
107
|
+
await writeFile(tempPath, `${JSON.stringify(validated, null, 2)}\n`);
|
|
108
|
+
await rename(tempPath, targetPath);
|
|
109
|
+
const relativePath = relative(canonicalRoot, targetPath).replaceAll("\\", "/");
|
|
110
|
+
return { path: relativePath, overwritten };
|
|
111
|
+
};
|
|
112
|
+
loadFlow = async (projectRoot, name) => {
|
|
113
|
+
const projectPath = await this.resolveProjectFlowPath(projectRoot, name);
|
|
114
|
+
if (projectPath !== undefined) {
|
|
115
|
+
return this.readFlowFile(projectPath, name, "project");
|
|
116
|
+
}
|
|
117
|
+
const builtinPath = await this.resolveBuiltinFlowPath(name);
|
|
118
|
+
if (builtinPath !== undefined) {
|
|
119
|
+
return this.readFlowFile(builtinPath, name, "builtin");
|
|
120
|
+
}
|
|
121
|
+
throw new Error(`External Flow not found: ${name}`);
|
|
122
|
+
};
|
|
123
|
+
resolveProjectFlowPath = async (projectRoot, name) => {
|
|
124
|
+
const canonicalRoot = await realpath(projectRoot);
|
|
125
|
+
const externalRoot = resolve(canonicalRoot, EXTERNAL_FLOWS_DIR);
|
|
126
|
+
try {
|
|
127
|
+
const canonicalExternalRoot = await realpath(externalRoot);
|
|
128
|
+
if (!contained(canonicalRoot, canonicalExternalRoot)) {
|
|
129
|
+
throw new Error("External Flow directory escapes the project root");
|
|
130
|
+
}
|
|
131
|
+
const rootStats = await lstat(externalRoot);
|
|
132
|
+
if (!rootStats.isDirectory() || rootStats.isSymbolicLink()) {
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
if (error instanceof Error
|
|
138
|
+
&& "code" in error
|
|
139
|
+
&& error.code === "ENOENT") {
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
throw error;
|
|
143
|
+
}
|
|
144
|
+
const filePath = join(externalRoot, flowFileName(name));
|
|
145
|
+
try {
|
|
146
|
+
const stats = await lstat(filePath);
|
|
147
|
+
if (!stats.isFile() || stats.isSymbolicLink()) {
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
return filePath;
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
if (error instanceof Error
|
|
154
|
+
&& "code" in error
|
|
155
|
+
&& error.code === "ENOENT") {
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
throw error;
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
resolveBuiltinFlowPath = async (name) => {
|
|
162
|
+
const filePath = join(this.builtinFlowsRoot, flowFileName(name));
|
|
163
|
+
try {
|
|
164
|
+
const stats = await lstat(filePath);
|
|
165
|
+
if (!stats.isFile() || stats.isSymbolicLink()) {
|
|
166
|
+
return undefined;
|
|
167
|
+
}
|
|
168
|
+
return filePath;
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
if (error instanceof Error
|
|
172
|
+
&& "code" in error
|
|
173
|
+
&& error.code === "ENOENT") {
|
|
174
|
+
return undefined;
|
|
175
|
+
}
|
|
176
|
+
throw error;
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
readFlowFile = async (filePath, expectedName, source) => {
|
|
180
|
+
const bytes = await readFile(filePath);
|
|
181
|
+
if (bytes.byteLength > MAX_EXTERNAL_FLOW_BYTES) {
|
|
182
|
+
throw new Error(`External Flow exceeds ${String(MAX_EXTERNAL_FLOW_BYTES)} bytes: ${filePath}`);
|
|
183
|
+
}
|
|
184
|
+
let parsed;
|
|
185
|
+
try {
|
|
186
|
+
parsed = JSON.parse(bytes.toString("utf8"));
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
throw new Error(`External Flow is not valid JSON: ${filePath}`, { cause: error });
|
|
190
|
+
}
|
|
191
|
+
const flow = ExternalFlowSchema.parse(parsed);
|
|
192
|
+
if (flow.name !== expectedName) {
|
|
193
|
+
throw new Error(`External Flow at ${filePath} declares name "${flow.name}", expected "${expectedName}"`);
|
|
194
|
+
}
|
|
195
|
+
return { bytes, flow, path: filePath, source };
|
|
196
|
+
};
|
|
197
|
+
listBuiltinFlows = async () => {
|
|
198
|
+
let rootStats;
|
|
199
|
+
try {
|
|
200
|
+
rootStats = await lstat(this.builtinFlowsRoot);
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
if (error instanceof Error
|
|
204
|
+
&& "code" in error
|
|
205
|
+
&& error.code === "ENOENT") {
|
|
206
|
+
return [];
|
|
207
|
+
}
|
|
208
|
+
throw error;
|
|
209
|
+
}
|
|
210
|
+
if (!rootStats.isDirectory() || rootStats.isSymbolicLink()) {
|
|
211
|
+
return [];
|
|
212
|
+
}
|
|
213
|
+
const paths = [];
|
|
214
|
+
const visit = async (directory) => {
|
|
215
|
+
const entries = await readdir(directory, { withFileTypes: true });
|
|
216
|
+
for (const entry of entries) {
|
|
217
|
+
const path = join(directory, entry.name);
|
|
218
|
+
if (entry.isSymbolicLink()) {
|
|
219
|
+
throw new Error(`Built-in External Flow catalog cannot contain symlinks: ${path}`);
|
|
220
|
+
}
|
|
221
|
+
if (entry.isDirectory()) {
|
|
222
|
+
await visit(path);
|
|
223
|
+
}
|
|
224
|
+
else if (entry.isFile() && entry.name.endsWith(".json")) {
|
|
225
|
+
paths.push(path);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
await visit(this.builtinFlowsRoot);
|
|
230
|
+
paths.sort();
|
|
231
|
+
const results = [];
|
|
232
|
+
for (const filePath of paths) {
|
|
233
|
+
const relativePath = relative(this.builtinFlowsRoot, filePath).replaceAll("\\", "/");
|
|
234
|
+
const name = relativePath.slice(0, -".json".length);
|
|
235
|
+
try {
|
|
236
|
+
const loaded = await this.readFlowFile(filePath, name, "builtin");
|
|
237
|
+
results.push({
|
|
238
|
+
name,
|
|
239
|
+
source: "builtin",
|
|
240
|
+
path: filePath,
|
|
241
|
+
status: "valid",
|
|
242
|
+
escapedPackageName: loaded.flow.escapedPackageName,
|
|
243
|
+
stepCount: loaded.flow.steps.length
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
catch (error) {
|
|
247
|
+
results.push({
|
|
248
|
+
name,
|
|
249
|
+
source: "builtin",
|
|
250
|
+
path: filePath,
|
|
251
|
+
status: "invalid",
|
|
252
|
+
failure: errorDetails(error)
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return results;
|
|
257
|
+
};
|
|
258
|
+
listProjectFlows = async (projectRoot) => {
|
|
259
|
+
const canonicalRoot = await realpath(projectRoot);
|
|
260
|
+
const externalRoot = resolve(canonicalRoot, EXTERNAL_FLOWS_DIR);
|
|
261
|
+
try {
|
|
262
|
+
const canonicalExternalRoot = await realpath(externalRoot);
|
|
263
|
+
if (!contained(canonicalRoot, canonicalExternalRoot)) {
|
|
264
|
+
throw new Error("External Flow directory escapes the project root");
|
|
265
|
+
}
|
|
266
|
+
const rootStats = await lstat(externalRoot);
|
|
267
|
+
if (!rootStats.isDirectory() || rootStats.isSymbolicLink()) {
|
|
268
|
+
return [];
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
catch (error) {
|
|
272
|
+
if (error instanceof Error
|
|
273
|
+
&& "code" in error
|
|
274
|
+
&& error.code === "ENOENT") {
|
|
275
|
+
return [];
|
|
276
|
+
}
|
|
277
|
+
throw error;
|
|
278
|
+
}
|
|
279
|
+
const paths = [];
|
|
280
|
+
const visit = async (directory) => {
|
|
281
|
+
const entries = await readdir(directory, { withFileTypes: true });
|
|
282
|
+
for (const entry of entries) {
|
|
283
|
+
const path = join(directory, entry.name);
|
|
284
|
+
if (entry.isSymbolicLink()) {
|
|
285
|
+
throw new Error(`External Flow catalog cannot contain symlinks: ${path}`);
|
|
286
|
+
}
|
|
287
|
+
if (entry.isDirectory()) {
|
|
288
|
+
await visit(path);
|
|
289
|
+
}
|
|
290
|
+
else if (entry.isFile() && entry.name.endsWith(".json")) {
|
|
291
|
+
paths.push(path);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
await visit(externalRoot);
|
|
296
|
+
paths.sort();
|
|
297
|
+
const results = [];
|
|
298
|
+
for (const filePath of paths) {
|
|
299
|
+
const relativePath = relative(canonicalRoot, filePath).replaceAll("\\", "/");
|
|
300
|
+
const name = relativePath
|
|
301
|
+
.slice(`${EXTERNAL_FLOWS_DIR}/`.length, -".json".length);
|
|
302
|
+
try {
|
|
303
|
+
const loaded = await this.readFlowFile(filePath, name, "project");
|
|
304
|
+
results.push({
|
|
305
|
+
name,
|
|
306
|
+
source: "project",
|
|
307
|
+
path: relativePath,
|
|
308
|
+
status: "valid",
|
|
309
|
+
escapedPackageName: loaded.flow.escapedPackageName,
|
|
310
|
+
stepCount: loaded.flow.steps.length
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
catch (error) {
|
|
314
|
+
results.push({
|
|
315
|
+
name,
|
|
316
|
+
source: "project",
|
|
317
|
+
path: relativePath,
|
|
318
|
+
status: "invalid",
|
|
319
|
+
failure: errorDetails(error)
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return results;
|
|
324
|
+
};
|
|
325
|
+
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { type GenerationInFlight, type GenerationSession, type PendingConfirmation } from "../../domain/generation.js";
|
|
1
|
+
import { type GenerationInFlight, type GenerationSession, type PendingConfirmation, type VerificationPhase } from "../../domain/generation.js";
|
|
2
2
|
import { type GenerationSessionStore } from "../../ports/generation-session-store.js";
|
|
3
3
|
export interface FileSystemGenerationSessionStoreOptions {
|
|
4
4
|
lockTimeoutMs?: number;
|
|
5
5
|
lockRetryMs?: number;
|
|
6
6
|
now?: (() => Date) | undefined;
|
|
7
7
|
hooks?: FileSystemGenerationSessionStoreHooks;
|
|
8
|
+
generationRoot?: string | undefined;
|
|
8
9
|
}
|
|
9
10
|
export interface FileSystemGenerationSessionStoreHooks {
|
|
10
11
|
beforeLockStagingWrite?: () => Promise<void> | void;
|
|
@@ -27,6 +28,8 @@ export declare class FileSystemGenerationSessionStore implements GenerationSessi
|
|
|
27
28
|
private readonly options;
|
|
28
29
|
private readonly now;
|
|
29
30
|
private readonly hooks;
|
|
31
|
+
private readonly customGenerationRoot;
|
|
32
|
+
private readonly lock;
|
|
30
33
|
constructor(projectRoot: string, options?: FileSystemGenerationSessionStoreOptions);
|
|
31
34
|
readonly create: (input: GenerationSession) => Promise<void>;
|
|
32
35
|
readonly read: (id: string) => Promise<GenerationSession>;
|
|
@@ -36,14 +39,24 @@ export declare class FileSystemGenerationSessionStore implements GenerationSessi
|
|
|
36
39
|
readonly beginStep: (id: string, expectedRevision: number, inFlightInput: GenerationInFlight, approvedConfirmationInput?: PendingConfirmation) => Promise<GenerationSession>;
|
|
37
40
|
readonly completeStep: (id: string, expectedRevision: number, expectedInFlightInput: GenerationInFlight, input: GenerationSession) => Promise<void>;
|
|
38
41
|
readonly recover: (id: string, expectedRevision: number, input: GenerationSession) => Promise<void>;
|
|
39
|
-
readonly beginVerification: (id: string, expectedRevision: number, attemptId: string
|
|
42
|
+
readonly beginVerification: (id: string, expectedRevision: number, attemptId: string, owner?: {
|
|
43
|
+
pid: number;
|
|
44
|
+
startedAt: string;
|
|
45
|
+
}) => Promise<GenerationSession>;
|
|
46
|
+
readonly updateVerificationPhase: (id: string, attemptId: string, phase: VerificationPhase) => Promise<void>;
|
|
47
|
+
readonly abortVerification: (id: string, expectedRevision: number) => Promise<GenerationSession>;
|
|
40
48
|
readonly completeVerification: (id: string, expectedRevision: number, input: GenerationSession) => Promise<void>;
|
|
41
49
|
readonly failVerification: (id: string, expectedRevision: number, input: GenerationSession) => Promise<void>;
|
|
50
|
+
readonly recoverVerification: (id: string, expectedRevision: number, input: GenerationSession) => Promise<void>;
|
|
51
|
+
readonly reopenVerification: (id: string, expectedRevision: number, input: GenerationSession) => Promise<void>;
|
|
42
52
|
readonly markBundlePublishable: (id: string, expectedRevision: number, input: GenerationSession) => Promise<void>;
|
|
53
|
+
readonly archive: (id: string, expectedRevision: number, input: GenerationSession) => Promise<void>;
|
|
54
|
+
readonly list: () => Promise<readonly GenerationSession[]>;
|
|
43
55
|
readonly writeEvidence: (id: string, relativePath: string, value: unknown) => Promise<void>;
|
|
44
56
|
readonly writeTextEvidence: (id: string, relativePath: string, value: string) => Promise<void>;
|
|
45
57
|
readonly produceEvidence: (id: string, relativePath: string, produce: (temporaryPath: string) => Promise<void>) => Promise<void>;
|
|
46
58
|
readonly readEvidence: (id: string, relativePath: string) => Promise<Buffer>;
|
|
59
|
+
readonly evidenceReference: (id: string, relativePath: string) => Promise<string>;
|
|
47
60
|
readonly listEvidence: (id: string) => Promise<readonly {
|
|
48
61
|
path: string;
|
|
49
62
|
contentBase64: string;
|
|
@@ -54,15 +67,8 @@ export declare class FileSystemGenerationSessionStore implements GenerationSessi
|
|
|
54
67
|
private readonly writeVerificationTransition;
|
|
55
68
|
private readonly activeDirectory;
|
|
56
69
|
private readonly finalDirectory;
|
|
57
|
-
private readonly
|
|
58
|
-
private readonly syncDirectory;
|
|
70
|
+
private readonly withVerifiedEvidence;
|
|
59
71
|
private readonly withLock;
|
|
60
|
-
private readonly
|
|
61
|
-
private readonly tryInstallLock;
|
|
62
|
-
private readonly reapDeadOwnerLock;
|
|
63
|
-
private readonly releaseLock;
|
|
64
|
-
private readonly readLockOwner;
|
|
65
|
-
private readonly fileIdentity;
|
|
66
|
-
private readonly isProcessAlive;
|
|
72
|
+
private readonly syncDirectory;
|
|
67
73
|
private assertPublishable;
|
|
68
74
|
}
|