devharness 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +185 -0
- package/build/atomic-write.d.ts +28 -0
- package/build/atomic-write.d.ts.map +1 -0
- package/build/atomic-write.js +126 -0
- package/build/atomic-write.js.map +1 -0
- package/build/cdp-manager.d.ts +524 -0
- package/build/cdp-manager.d.ts.map +1 -0
- package/build/cdp-manager.js +1828 -0
- package/build/cdp-manager.js.map +1 -0
- package/build/cdp-manager.test.d.ts +6 -0
- package/build/cdp-manager.test.d.ts.map +1 -0
- package/build/cdp-manager.test.js +546 -0
- package/build/cdp-manager.test.js.map +1 -0
- package/build/chrome-launcher.d.ts +400 -0
- package/build/chrome-launcher.d.ts.map +1 -0
- package/build/chrome-launcher.js +1071 -0
- package/build/chrome-launcher.js.map +1 -0
- package/build/chrome-launcher.test.d.ts +11 -0
- package/build/chrome-launcher.test.d.ts.map +1 -0
- package/build/chrome-launcher.test.js +204 -0
- package/build/chrome-launcher.test.js.map +1 -0
- package/build/clickable-cache.d.ts +73 -0
- package/build/clickable-cache.d.ts.map +1 -0
- package/build/clickable-cache.js +90 -0
- package/build/clickable-cache.js.map +1 -0
- package/build/command-recorder.d.ts +401 -0
- package/build/command-recorder.d.ts.map +1 -0
- package/build/command-recorder.js +701 -0
- package/build/command-recorder.js.map +1 -0
- package/build/config.d.ts +390 -0
- package/build/config.d.ts.map +1 -0
- package/build/config.js +826 -0
- package/build/config.js.map +1 -0
- package/build/config.test.d.ts +2 -0
- package/build/config.test.d.ts.map +1 -0
- package/build/config.test.js +112 -0
- package/build/config.test.js.map +1 -0
- package/build/connection-manager.d.ts +159 -0
- package/build/connection-manager.d.ts.map +1 -0
- package/build/connection-manager.js +432 -0
- package/build/connection-manager.js.map +1 -0
- package/build/connection-manager.test.d.ts +11 -0
- package/build/connection-manager.test.d.ts.map +1 -0
- package/build/connection-manager.test.js +114 -0
- package/build/connection-manager.test.js.map +1 -0
- package/build/console-monitor.d.ts +150 -0
- package/build/console-monitor.d.ts.map +1 -0
- package/build/console-monitor.js +454 -0
- package/build/console-monitor.js.map +1 -0
- package/build/dashboard/bundle.js +1 -0
- package/build/dashboard/dashboard-client.d.ts +42 -0
- package/build/dashboard/dashboard-client.d.ts.map +1 -0
- package/build/dashboard/dashboard-client.js +184 -0
- package/build/dashboard/dashboard-client.js.map +1 -0
- package/build/dashboard/dashboard-hub.d.ts +50 -0
- package/build/dashboard/dashboard-hub.d.ts.map +1 -0
- package/build/dashboard/dashboard-hub.js +654 -0
- package/build/dashboard/dashboard-hub.js.map +1 -0
- package/build/dashboard/hub-lock.d.ts +16 -0
- package/build/dashboard/hub-lock.d.ts.map +1 -0
- package/build/dashboard/hub-lock.js +77 -0
- package/build/dashboard/hub-lock.js.map +1 -0
- package/build/dashboard/index.d.ts +25 -0
- package/build/dashboard/index.d.ts.map +1 -0
- package/build/dashboard/index.js +77 -0
- package/build/dashboard/index.js.map +1 -0
- package/build/dashboard/types.d.ts +83 -0
- package/build/dashboard/types.d.ts.map +1 -0
- package/build/dashboard/types.js +6 -0
- package/build/dashboard/types.js.map +1 -0
- package/build/debug-logger.d.ts +85 -0
- package/build/debug-logger.d.ts.map +1 -0
- package/build/debug-logger.js +195 -0
- package/build/debug-logger.js.map +1 -0
- package/build/debugger-aware-wrapper.d.ts +37 -0
- package/build/debugger-aware-wrapper.d.ts.map +1 -0
- package/build/debugger-aware-wrapper.js +128 -0
- package/build/debugger-aware-wrapper.js.map +1 -0
- package/build/dom-change-monitor.d.ts +92 -0
- package/build/dom-change-monitor.d.ts.map +1 -0
- package/build/dom-change-monitor.js +437 -0
- package/build/dom-change-monitor.js.map +1 -0
- package/build/element-collector.d.ts +16 -0
- package/build/element-collector.d.ts.map +1 -0
- package/build/element-collector.js +207 -0
- package/build/element-collector.js.map +1 -0
- package/build/error-helpers.d.ts +43 -0
- package/build/error-helpers.d.ts.map +1 -0
- package/build/error-helpers.js +143 -0
- package/build/error-helpers.js.map +1 -0
- package/build/formatters/console-formatter.d.ts +160 -0
- package/build/formatters/console-formatter.d.ts.map +1 -0
- package/build/formatters/console-formatter.js +338 -0
- package/build/formatters/console-formatter.js.map +1 -0
- package/build/helpers/parser-plugins.d.ts +44 -0
- package/build/helpers/parser-plugins.d.ts.map +1 -0
- package/build/helpers/parser-plugins.js +102 -0
- package/build/helpers/parser-plugins.js.map +1 -0
- package/build/helpers/paths.d.ts +79 -0
- package/build/helpers/paths.d.ts.map +1 -0
- package/build/helpers/paths.js +168 -0
- package/build/helpers/paths.js.map +1 -0
- package/build/helpers/process-liveness.d.ts +24 -0
- package/build/helpers/process-liveness.d.ts.map +1 -0
- package/build/helpers/process-liveness.js +36 -0
- package/build/helpers/process-liveness.js.map +1 -0
- package/build/helpers/process-liveness.test.d.ts +2 -0
- package/build/helpers/process-liveness.test.d.ts.map +1 -0
- package/build/helpers/process-liveness.test.js +40 -0
- package/build/helpers/process-liveness.test.js.map +1 -0
- package/build/helpers/sequence-tree.d.ts +27 -0
- package/build/helpers/sequence-tree.d.ts.map +1 -0
- package/build/helpers/sequence-tree.js +80 -0
- package/build/helpers/sequence-tree.js.map +1 -0
- package/build/helpers/sequence-tree.test.d.ts +2 -0
- package/build/helpers/sequence-tree.test.d.ts.map +1 -0
- package/build/helpers/sequence-tree.test.js +122 -0
- package/build/helpers/sequence-tree.test.js.map +1 -0
- package/build/index.d.ts +3 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +2007 -0
- package/build/index.js.map +1 -0
- package/build/interaction-recorder.d.ts +214 -0
- package/build/interaction-recorder.d.ts.map +1 -0
- package/build/interaction-recorder.js +2007 -0
- package/build/interaction-recorder.js.map +1 -0
- package/build/issue-tracker.d.ts +107 -0
- package/build/issue-tracker.d.ts.map +1 -0
- package/build/issue-tracker.js +582 -0
- package/build/issue-tracker.js.map +1 -0
- package/build/issue-tracker.test.d.ts +2 -0
- package/build/issue-tracker.test.d.ts.map +1 -0
- package/build/issue-tracker.test.js +88 -0
- package/build/issue-tracker.test.js.map +1 -0
- package/build/launch-chrome-port.test.d.ts +14 -0
- package/build/launch-chrome-port.test.d.ts.map +1 -0
- package/build/launch-chrome-port.test.js +97 -0
- package/build/launch-chrome-port.test.js.map +1 -0
- package/build/log-processor/orchestrator.d.ts +32 -0
- package/build/log-processor/orchestrator.d.ts.map +1 -0
- package/build/log-processor/orchestrator.js +27 -0
- package/build/log-processor/orchestrator.js.map +1 -0
- package/build/logpoint-execution-tracker.d.ts +57 -0
- package/build/logpoint-execution-tracker.d.ts.map +1 -0
- package/build/logpoint-execution-tracker.js +112 -0
- package/build/logpoint-execution-tracker.js.map +1 -0
- package/build/mcp-supervisor.d.ts +3 -0
- package/build/mcp-supervisor.d.ts.map +1 -0
- package/build/mcp-supervisor.js +211 -0
- package/build/mcp-supervisor.js.map +1 -0
- package/build/messages.d.ts +165 -0
- package/build/messages.d.ts.map +1 -0
- package/build/messages.js +574 -0
- package/build/messages.js.map +1 -0
- package/build/network-monitor.d.ts +172 -0
- package/build/network-monitor.d.ts.map +1 -0
- package/build/network-monitor.js +350 -0
- package/build/network-monitor.js.map +1 -0
- package/build/overlays.d.ts +49 -0
- package/build/overlays.d.ts.map +1 -0
- package/build/overlays.js +409 -0
- package/build/overlays.js.map +1 -0
- package/build/persistent-profiles.test.d.ts +15 -0
- package/build/persistent-profiles.test.d.ts.map +1 -0
- package/build/persistent-profiles.test.js +470 -0
- package/build/persistent-profiles.test.js.map +1 -0
- package/build/port-reserver.d.ts +32 -0
- package/build/port-reserver.d.ts.map +1 -0
- package/build/port-reserver.js +118 -0
- package/build/port-reserver.js.map +1 -0
- package/build/puppeteer-manager.d.ts +47 -0
- package/build/puppeteer-manager.d.ts.map +1 -0
- package/build/puppeteer-manager.js +116 -0
- package/build/puppeteer-manager.js.map +1 -0
- package/build/recording-coverage.test.d.ts +12 -0
- package/build/recording-coverage.test.d.ts.map +1 -0
- package/build/recording-coverage.test.js +72 -0
- package/build/recording-coverage.test.js.map +1 -0
- package/build/reference-validator.d.ts +47 -0
- package/build/reference-validator.d.ts.map +1 -0
- package/build/reference-validator.js +86 -0
- package/build/reference-validator.js.map +1 -0
- package/build/reference-validator.test.d.ts +5 -0
- package/build/reference-validator.test.d.ts.map +1 -0
- package/build/reference-validator.test.js +243 -0
- package/build/reference-validator.test.js.map +1 -0
- package/build/replay-cursor.d.ts +27 -0
- package/build/replay-cursor.d.ts.map +1 -0
- package/build/replay-cursor.js +240 -0
- package/build/replay-cursor.js.map +1 -0
- package/build/runners/docker-compose-runner.d.ts +69 -0
- package/build/runners/docker-compose-runner.d.ts.map +1 -0
- package/build/runners/docker-compose-runner.js +438 -0
- package/build/runners/docker-compose-runner.js.map +1 -0
- package/build/runners/docker-runner.d.ts +65 -0
- package/build/runners/docker-runner.d.ts.map +1 -0
- package/build/runners/docker-runner.js +414 -0
- package/build/runners/docker-runner.js.map +1 -0
- package/build/runners/index.d.ts +19 -0
- package/build/runners/index.d.ts.map +1 -0
- package/build/runners/index.js +35 -0
- package/build/runners/index.js.map +1 -0
- package/build/runners/native-runner.d.ts +95 -0
- package/build/runners/native-runner.d.ts.map +1 -0
- package/build/runners/native-runner.js +370 -0
- package/build/runners/native-runner.js.map +1 -0
- package/build/runners/types.d.ts +147 -0
- package/build/runners/types.d.ts.map +1 -0
- package/build/runners/types.js +25 -0
- package/build/runners/types.js.map +1 -0
- package/build/self-restart.d.ts +12 -0
- package/build/self-restart.d.ts.map +1 -0
- package/build/self-restart.js +41 -0
- package/build/self-restart.js.map +1 -0
- package/build/self-restart.test.d.ts +2 -0
- package/build/self-restart.test.d.ts.map +1 -0
- package/build/self-restart.test.js +34 -0
- package/build/self-restart.test.js.map +1 -0
- package/build/server-claims.d.ts +161 -0
- package/build/server-claims.d.ts.map +1 -0
- package/build/server-claims.js +382 -0
- package/build/server-claims.js.map +1 -0
- package/build/server-claims.test.d.ts +2 -0
- package/build/server-claims.test.d.ts.map +1 -0
- package/build/server-claims.test.js +197 -0
- package/build/server-claims.test.js.map +1 -0
- package/build/server-manager.d.ts +438 -0
- package/build/server-manager.d.ts.map +1 -0
- package/build/server-manager.js +1674 -0
- package/build/server-manager.js.map +1 -0
- package/build/server-manager.test.d.ts +17 -0
- package/build/server-manager.test.d.ts.map +1 -0
- package/build/server-manager.test.js +122 -0
- package/build/server-manager.test.js.map +1 -0
- package/build/server-ownership.test.d.ts +2 -0
- package/build/server-ownership.test.d.ts.map +1 -0
- package/build/server-ownership.test.js +238 -0
- package/build/server-ownership.test.js.map +1 -0
- package/build/server-watcher.d.ts +21 -0
- package/build/server-watcher.d.ts.map +1 -0
- package/build/server-watcher.js +105 -0
- package/build/server-watcher.js.map +1 -0
- package/build/session-detector.d.ts +47 -0
- package/build/session-detector.d.ts.map +1 -0
- package/build/session-detector.js +229 -0
- package/build/session-detector.js.map +1 -0
- package/build/session-detector.test.d.ts +9 -0
- package/build/session-detector.test.d.ts.map +1 -0
- package/build/session-detector.test.js +45 -0
- package/build/session-detector.test.js.map +1 -0
- package/build/sourcemap-handler.d.ts +114 -0
- package/build/sourcemap-handler.d.ts.map +1 -0
- package/build/sourcemap-handler.js +479 -0
- package/build/sourcemap-handler.js.map +1 -0
- package/build/supervisor/child-manager.d.ts +43 -0
- package/build/supervisor/child-manager.d.ts.map +1 -0
- package/build/supervisor/child-manager.js +148 -0
- package/build/supervisor/child-manager.js.map +1 -0
- package/build/supervisor/child-manager.test.d.ts +2 -0
- package/build/supervisor/child-manager.test.d.ts.map +1 -0
- package/build/supervisor/child-manager.test.js +107 -0
- package/build/supervisor/child-manager.test.js.map +1 -0
- package/build/supervisor/client-watcher.d.ts +48 -0
- package/build/supervisor/client-watcher.d.ts.map +1 -0
- package/build/supervisor/client-watcher.js +186 -0
- package/build/supervisor/client-watcher.js.map +1 -0
- package/build/supervisor/client-watcher.test.d.ts +2 -0
- package/build/supervisor/client-watcher.test.d.ts.map +1 -0
- package/build/supervisor/client-watcher.test.js +121 -0
- package/build/supervisor/client-watcher.test.js.map +1 -0
- package/build/supervisor/idle-config.d.ts +30 -0
- package/build/supervisor/idle-config.d.ts.map +1 -0
- package/build/supervisor/idle-config.js +76 -0
- package/build/supervisor/idle-config.js.map +1 -0
- package/build/supervisor/idle-config.test.d.ts +2 -0
- package/build/supervisor/idle-config.test.d.ts.map +1 -0
- package/build/supervisor/idle-config.test.js +119 -0
- package/build/supervisor/idle-config.test.js.map +1 -0
- package/build/supervisor/ndjson-reader.d.ts +47 -0
- package/build/supervisor/ndjson-reader.d.ts.map +1 -0
- package/build/supervisor/ndjson-reader.js +75 -0
- package/build/supervisor/ndjson-reader.js.map +1 -0
- package/build/supervisor/ndjson-reader.test.d.ts +2 -0
- package/build/supervisor/ndjson-reader.test.d.ts.map +1 -0
- package/build/supervisor/ndjson-reader.test.js +92 -0
- package/build/supervisor/ndjson-reader.test.js.map +1 -0
- package/build/supervisor/pidfile.d.ts +11 -0
- package/build/supervisor/pidfile.d.ts.map +1 -0
- package/build/supervisor/pidfile.js +29 -0
- package/build/supervisor/pidfile.js.map +1 -0
- package/build/supervisor/pidfile.test.d.ts +8 -0
- package/build/supervisor/pidfile.test.d.ts.map +1 -0
- package/build/supervisor/pidfile.test.js +46 -0
- package/build/supervisor/pidfile.test.js.map +1 -0
- package/build/supervisor/restart-coordinator.d.ts +73 -0
- package/build/supervisor/restart-coordinator.d.ts.map +1 -0
- package/build/supervisor/restart-coordinator.js +300 -0
- package/build/supervisor/restart-coordinator.js.map +1 -0
- package/build/supervisor/restart-coordinator.test.d.ts +2 -0
- package/build/supervisor/restart-coordinator.test.d.ts.map +1 -0
- package/build/supervisor/restart-coordinator.test.js +434 -0
- package/build/supervisor/restart-coordinator.test.js.map +1 -0
- package/build/test-support/fake-execute-tool-call.d.ts +11 -0
- package/build/test-support/fake-execute-tool-call.d.ts.map +1 -0
- package/build/test-support/fake-execute-tool-call.js +19 -0
- package/build/test-support/fake-execute-tool-call.js.map +1 -0
- package/build/tool-error.d.ts +14 -0
- package/build/tool-error.d.ts.map +1 -0
- package/build/tool-error.js +18 -0
- package/build/tool-error.js.map +1 -0
- package/build/tool-response.d.ts +293 -0
- package/build/tool-response.d.ts.map +1 -0
- package/build/tool-response.js +365 -0
- package/build/tool-response.js.map +1 -0
- package/build/tools/assert-tools.d.ts +62 -0
- package/build/tools/assert-tools.d.ts.map +1 -0
- package/build/tools/assert-tools.js +330 -0
- package/build/tools/assert-tools.js.map +1 -0
- package/build/tools/breakpoint-await-abort.test.d.ts +2 -0
- package/build/tools/breakpoint-await-abort.test.d.ts.map +1 -0
- package/build/tools/breakpoint-await-abort.test.js +124 -0
- package/build/tools/breakpoint-await-abort.test.js.map +1 -0
- package/build/tools/breakpoint-tools.d.ts +99 -0
- package/build/tools/breakpoint-tools.d.ts.map +1 -0
- package/build/tools/breakpoint-tools.js +1197 -0
- package/build/tools/breakpoint-tools.js.map +1 -0
- package/build/tools/config-tools.d.ts +58 -0
- package/build/tools/config-tools.d.ts.map +1 -0
- package/build/tools/config-tools.js +180 -0
- package/build/tools/config-tools.js.map +1 -0
- package/build/tools/connection-routing.test.d.ts +12 -0
- package/build/tools/connection-routing.test.d.ts.map +1 -0
- package/build/tools/connection-routing.test.js +206 -0
- package/build/tools/connection-routing.test.js.map +1 -0
- package/build/tools/console-tools.d.ts +84 -0
- package/build/tools/console-tools.d.ts.map +1 -0
- package/build/tools/console-tools.js +241 -0
- package/build/tools/console-tools.js.map +1 -0
- package/build/tools/content-tools.d.ts +74 -0
- package/build/tools/content-tools.d.ts.map +1 -0
- package/build/tools/content-tools.js +582 -0
- package/build/tools/content-tools.js.map +1 -0
- package/build/tools/dashboard-tools.d.ts +38 -0
- package/build/tools/dashboard-tools.d.ts.map +1 -0
- package/build/tools/dashboard-tools.js +123 -0
- package/build/tools/dashboard-tools.js.map +1 -0
- package/build/tools/dom-tools.d.ts +41 -0
- package/build/tools/dom-tools.d.ts.map +1 -0
- package/build/tools/dom-tools.js +277 -0
- package/build/tools/dom-tools.js.map +1 -0
- package/build/tools/download-tools.d.ts +55 -0
- package/build/tools/download-tools.d.ts.map +1 -0
- package/build/tools/download-tools.js +280 -0
- package/build/tools/download-tools.js.map +1 -0
- package/build/tools/execution-tools.d.ts +40 -0
- package/build/tools/execution-tools.d.ts.map +1 -0
- package/build/tools/execution-tools.js +121 -0
- package/build/tools/execution-tools.js.map +1 -0
- package/build/tools/input-abort.test.d.ts +2 -0
- package/build/tools/input-abort.test.d.ts.map +1 -0
- package/build/tools/input-abort.test.js +227 -0
- package/build/tools/input-abort.test.js.map +1 -0
- package/build/tools/input-ambiguous-selector.test.d.ts +2 -0
- package/build/tools/input-ambiguous-selector.test.d.ts.map +1 -0
- package/build/tools/input-ambiguous-selector.test.js +34 -0
- package/build/tools/input-ambiguous-selector.test.js.map +1 -0
- package/build/tools/input-tools.d.ts +168 -0
- package/build/tools/input-tools.d.ts.map +1 -0
- package/build/tools/input-tools.js +1397 -0
- package/build/tools/input-tools.js.map +1 -0
- package/build/tools/inspection-tools.d.ts +112 -0
- package/build/tools/inspection-tools.d.ts.map +1 -0
- package/build/tools/inspection-tools.js +637 -0
- package/build/tools/inspection-tools.js.map +1 -0
- package/build/tools/interpolation.d.ts +30 -0
- package/build/tools/interpolation.d.ts.map +1 -0
- package/build/tools/interpolation.js +143 -0
- package/build/tools/interpolation.js.map +1 -0
- package/build/tools/issues-abort-hygiene.test.d.ts +2 -0
- package/build/tools/issues-abort-hygiene.test.d.ts.map +1 -0
- package/build/tools/issues-abort-hygiene.test.js +79 -0
- package/build/tools/issues-abort-hygiene.test.js.map +1 -0
- package/build/tools/issues-tools.d.ts +98 -0
- package/build/tools/issues-tools.d.ts.map +1 -0
- package/build/tools/issues-tools.js +821 -0
- package/build/tools/issues-tools.js.map +1 -0
- package/build/tools/issues-tools.test.d.ts +8 -0
- package/build/tools/issues-tools.test.d.ts.map +1 -0
- package/build/tools/issues-tools.test.js +184 -0
- package/build/tools/issues-tools.test.js.map +1 -0
- package/build/tools/modal-tools.d.ts +73 -0
- package/build/tools/modal-tools.d.ts.map +1 -0
- package/build/tools/modal-tools.js +161 -0
- package/build/tools/modal-tools.js.map +1 -0
- package/build/tools/network-tools.d.ts +72 -0
- package/build/tools/network-tools.d.ts.map +1 -0
- package/build/tools/network-tools.js +432 -0
- package/build/tools/network-tools.js.map +1 -0
- package/build/tools/page-tools.d.ts +78 -0
- package/build/tools/page-tools.d.ts.map +1 -0
- package/build/tools/page-tools.js +255 -0
- package/build/tools/page-tools.js.map +1 -0
- package/build/tools/plugin-tools.d.ts +16 -0
- package/build/tools/plugin-tools.d.ts.map +1 -0
- package/build/tools/plugin-tools.js +17 -0
- package/build/tools/plugin-tools.js.map +1 -0
- package/build/tools/replay-abort.test.d.ts +2 -0
- package/build/tools/replay-abort.test.d.ts.map +1 -0
- package/build/tools/replay-abort.test.js +259 -0
- package/build/tools/replay-abort.test.js.map +1 -0
- package/build/tools/replay-add-conditional.test.d.ts +2 -0
- package/build/tools/replay-add-conditional.test.d.ts.map +1 -0
- package/build/tools/replay-add-conditional.test.js +205 -0
- package/build/tools/replay-add-conditional.test.js.map +1 -0
- package/build/tools/replay-background-run.test.d.ts +2 -0
- package/build/tools/replay-background-run.test.d.ts.map +1 -0
- package/build/tools/replay-background-run.test.js +176 -0
- package/build/tools/replay-background-run.test.js.map +1 -0
- package/build/tools/replay-capture-variables.test.d.ts +2 -0
- package/build/tools/replay-capture-variables.test.d.ts.map +1 -0
- package/build/tools/replay-capture-variables.test.js +331 -0
- package/build/tools/replay-capture-variables.test.js.map +1 -0
- package/build/tools/replay-conditional-idb-and-launch.test.d.ts +11 -0
- package/build/tools/replay-conditional-idb-and-launch.test.d.ts.map +1 -0
- package/build/tools/replay-conditional-idb-and-launch.test.js +429 -0
- package/build/tools/replay-conditional-idb-and-launch.test.js.map +1 -0
- package/build/tools/replay-conditional-timeout.test.d.ts +14 -0
- package/build/tools/replay-conditional-timeout.test.d.ts.map +1 -0
- package/build/tools/replay-conditional-timeout.test.js +126 -0
- package/build/tools/replay-conditional-timeout.test.js.map +1 -0
- package/build/tools/replay-declare.test.d.ts +2 -0
- package/build/tools/replay-declare.test.d.ts.map +1 -0
- package/build/tools/replay-declare.test.js +106 -0
- package/build/tools/replay-declare.test.js.map +1 -0
- package/build/tools/replay-declared-browser-cleanup.test.d.ts +2 -0
- package/build/tools/replay-declared-browser-cleanup.test.d.ts.map +1 -0
- package/build/tools/replay-declared-browser-cleanup.test.js +103 -0
- package/build/tools/replay-declared-browser-cleanup.test.js.map +1 -0
- package/build/tools/replay-declared-profile.test.d.ts +2 -0
- package/build/tools/replay-declared-profile.test.d.ts.map +1 -0
- package/build/tools/replay-declared-profile.test.js +116 -0
- package/build/tools/replay-declared-profile.test.js.map +1 -0
- package/build/tools/replay-executor.d.ts +556 -0
- package/build/tools/replay-executor.d.ts.map +1 -0
- package/build/tools/replay-executor.js +2347 -0
- package/build/tools/replay-executor.js.map +1 -0
- package/build/tools/replay-folder-save.test.d.ts +2 -0
- package/build/tools/replay-folder-save.test.d.ts.map +1 -0
- package/build/tools/replay-folder-save.test.js +63 -0
- package/build/tools/replay-folder-save.test.js.map +1 -0
- package/build/tools/replay-formatters.d.ts +148 -0
- package/build/tools/replay-formatters.d.ts.map +1 -0
- package/build/tools/replay-formatters.js +856 -0
- package/build/tools/replay-formatters.js.map +1 -0
- package/build/tools/replay-kill-chrome-on-finish.test.d.ts +2 -0
- package/build/tools/replay-kill-chrome-on-finish.test.d.ts.map +1 -0
- package/build/tools/replay-kill-chrome-on-finish.test.js +175 -0
- package/build/tools/replay-kill-chrome-on-finish.test.js.map +1 -0
- package/build/tools/replay-multi-connection.test.d.ts +2 -0
- package/build/tools/replay-multi-connection.test.d.ts.map +1 -0
- package/build/tools/replay-multi-connection.test.js +547 -0
- package/build/tools/replay-multi-connection.test.js.map +1 -0
- package/build/tools/replay-output-and-record-options.test.d.ts +2 -0
- package/build/tools/replay-output-and-record-options.test.d.ts.map +1 -0
- package/build/tools/replay-output-and-record-options.test.js +240 -0
- package/build/tools/replay-output-and-record-options.test.js.map +1 -0
- package/build/tools/replay-production-error-shape.test.d.ts +7 -0
- package/build/tools/replay-production-error-shape.test.d.ts.map +1 -0
- package/build/tools/replay-production-error-shape.test.js +93 -0
- package/build/tools/replay-production-error-shape.test.js.map +1 -0
- package/build/tools/replay-rebase.test.d.ts +2 -0
- package/build/tools/replay-rebase.test.d.ts.map +1 -0
- package/build/tools/replay-rebase.test.js +58 -0
- package/build/tools/replay-rebase.test.js.map +1 -0
- package/build/tools/replay-retry-classification.test.d.ts +9 -0
- package/build/tools/replay-retry-classification.test.d.ts.map +1 -0
- package/build/tools/replay-retry-classification.test.js +57 -0
- package/build/tools/replay-retry-classification.test.js.map +1 -0
- package/build/tools/replay-run-registry.d.ts +55 -0
- package/build/tools/replay-run-registry.d.ts.map +1 -0
- package/build/tools/replay-run-registry.js +65 -0
- package/build/tools/replay-run-registry.js.map +1 -0
- package/build/tools/replay-sequence-reload.test.d.ts +2 -0
- package/build/tools/replay-sequence-reload.test.d.ts.map +1 -0
- package/build/tools/replay-sequence-reload.test.js +82 -0
- package/build/tools/replay-sequence-reload.test.js.map +1 -0
- package/build/tools/replay-socket-scope.test.d.ts +2 -0
- package/build/tools/replay-socket-scope.test.d.ts.map +1 -0
- package/build/tools/replay-socket-scope.test.js +75 -0
- package/build/tools/replay-socket-scope.test.js.map +1 -0
- package/build/tools/replay-step-connection.test.d.ts +2 -0
- package/build/tools/replay-step-connection.test.d.ts.map +1 -0
- package/build/tools/replay-step-connection.test.js +292 -0
- package/build/tools/replay-step-connection.test.js.map +1 -0
- package/build/tools/replay-step-timeout.test.d.ts +2 -0
- package/build/tools/replay-step-timeout.test.d.ts.map +1 -0
- package/build/tools/replay-step-timeout.test.js +204 -0
- package/build/tools/replay-step-timeout.test.js.map +1 -0
- package/build/tools/replay-tags.test.d.ts +2 -0
- package/build/tools/replay-tags.test.d.ts.map +1 -0
- package/build/tools/replay-tags.test.js +99 -0
- package/build/tools/replay-tags.test.js.map +1 -0
- package/build/tools/replay-teardown-and-foreach.test.d.ts +13 -0
- package/build/tools/replay-teardown-and-foreach.test.d.ts.map +1 -0
- package/build/tools/replay-teardown-and-foreach.test.js +318 -0
- package/build/tools/replay-teardown-and-foreach.test.js.map +1 -0
- package/build/tools/replay-tool-name-validation.test.d.ts +2 -0
- package/build/tools/replay-tool-name-validation.test.d.ts.map +1 -0
- package/build/tools/replay-tool-name-validation.test.js +280 -0
- package/build/tools/replay-tool-name-validation.test.js.map +1 -0
- package/build/tools/replay-tools.d.ts +288 -0
- package/build/tools/replay-tools.d.ts.map +1 -0
- package/build/tools/replay-tools.js +2994 -0
- package/build/tools/replay-tools.js.map +1 -0
- package/build/tools/request-abort.test.d.ts +2 -0
- package/build/tools/request-abort.test.d.ts.map +1 -0
- package/build/tools/request-abort.test.js +130 -0
- package/build/tools/request-abort.test.js.map +1 -0
- package/build/tools/request-tools.d.ts +56 -0
- package/build/tools/request-tools.d.ts.map +1 -0
- package/build/tools/request-tools.js +174 -0
- package/build/tools/request-tools.js.map +1 -0
- package/build/tools/screenshot-tools.d.ts +135 -0
- package/build/tools/screenshot-tools.d.ts.map +1 -0
- package/build/tools/screenshot-tools.js +636 -0
- package/build/tools/screenshot-tools.js.map +1 -0
- package/build/tools/server-tools.d.ts +88 -0
- package/build/tools/server-tools.d.ts.map +1 -0
- package/build/tools/server-tools.js +485 -0
- package/build/tools/server-tools.js.map +1 -0
- package/build/tools/source-tools.d.ts +46 -0
- package/build/tools/source-tools.d.ts.map +1 -0
- package/build/tools/source-tools.js +47 -0
- package/build/tools/source-tools.js.map +1 -0
- package/build/tools/storage-tools.d.ts +141 -0
- package/build/tools/storage-tools.d.ts.map +1 -0
- package/build/tools/storage-tools.js +894 -0
- package/build/tools/storage-tools.js.map +1 -0
- package/build/tools/storage-tools.test.d.ts +13 -0
- package/build/tools/storage-tools.test.d.ts.map +1 -0
- package/build/tools/storage-tools.test.js +707 -0
- package/build/tools/storage-tools.test.js.map +1 -0
- package/build/tools/tab-tools.d.ts +42 -0
- package/build/tools/tab-tools.d.ts.map +1 -0
- package/build/tools/tab-tools.js +296 -0
- package/build/tools/tab-tools.js.map +1 -0
- package/build/tools/wait-tools.d.ts +85 -0
- package/build/tools/wait-tools.d.ts.map +1 -0
- package/build/tools/wait-tools.js +236 -0
- package/build/tools/wait-tools.js.map +1 -0
- package/build/tools/wait-tools.test.d.ts +9 -0
- package/build/tools/wait-tools.test.d.ts.map +1 -0
- package/build/tools/wait-tools.test.js +193 -0
- package/build/tools/wait-tools.test.js.map +1 -0
- package/build/types.d.ts +133 -0
- package/build/types.d.ts.map +1 -0
- package/build/types.js +5 -0
- package/build/types.js.map +1 -0
- package/build/ui-verifier.d.ts +88 -0
- package/build/ui-verifier.d.ts.map +1 -0
- package/build/ui-verifier.js +540 -0
- package/build/ui-verifier.js.map +1 -0
- package/build/utils/abort.d.ts +68 -0
- package/build/utils/abort.d.ts.map +1 -0
- package/build/utils/abort.js +159 -0
- package/build/utils/abort.js.map +1 -0
- package/build/utils/abort.test.d.ts +2 -0
- package/build/utils/abort.test.d.ts.map +1 -0
- package/build/utils/abort.test.js +174 -0
- package/build/utils/abort.test.js.map +1 -0
- package/build/utils/modal-detection-core.d.ts +150 -0
- package/build/utils/modal-detection-core.d.ts.map +1 -0
- package/build/utils/modal-detection-core.js +332 -0
- package/build/utils/modal-detection-core.js.map +1 -0
- package/build/utils/modal-detection-core.test.d.ts +5 -0
- package/build/utils/modal-detection-core.test.d.ts.map +1 -0
- package/build/utils/modal-detection-core.test.js +492 -0
- package/build/utils/modal-detection-core.test.js.map +1 -0
- package/build/utils/modal-detector.d.ts +29 -0
- package/build/utils/modal-detector.d.ts.map +1 -0
- package/build/utils/modal-detector.js +281 -0
- package/build/utils/modal-detector.js.map +1 -0
- package/build/utils/modal-dismissal.d.ts +32 -0
- package/build/utils/modal-dismissal.d.ts.map +1 -0
- package/build/utils/modal-dismissal.js +190 -0
- package/build/utils/modal-dismissal.js.map +1 -0
- package/build/utils/port-check.d.ts +31 -0
- package/build/utils/port-check.d.ts.map +1 -0
- package/build/utils/port-check.js +87 -0
- package/build/utils/port-check.js.map +1 -0
- package/build/utils/port-check.test.d.ts +2 -0
- package/build/utils/port-check.test.d.ts.map +1 -0
- package/build/utils/port-check.test.js +66 -0
- package/build/utils/port-check.test.js.map +1 -0
- package/build/utils/selector-resolver.d.ts +66 -0
- package/build/utils/selector-resolver.d.ts.map +1 -0
- package/build/utils/selector-resolver.js +200 -0
- package/build/utils/selector-resolver.js.map +1 -0
- package/build/validation-helpers.d.ts +41 -0
- package/build/validation-helpers.d.ts.map +1 -0
- package/build/validation-helpers.js +236 -0
- package/build/validation-helpers.js.map +1 -0
- package/build/window-sizing.d.ts +26 -0
- package/build/window-sizing.d.ts.map +1 -0
- package/build/window-sizing.js +87 -0
- package/build/window-sizing.js.map +1 -0
- package/build/window-sizing.test.d.ts +8 -0
- package/build/window-sizing.test.d.ts.map +1 -0
- package/build/window-sizing.test.js +113 -0
- package/build/window-sizing.test.js.map +1 -0
- package/docs/README.md +74 -0
- package/docs/automation.md +562 -0
- package/docs/debugging.md +229 -0
- package/docs/installation.md +133 -0
- package/docs/instructions.md +227 -0
- package/docs/mcp-instructions.md +15 -0
- package/docs/message-system.md +327 -0
- package/docs/messages.md +3480 -0
- package/docs/parser-plugins.md +132 -0
- package/docs/replay.md +1411 -0
- package/docs/troubleshooting.md +272 -0
- package/docs/working_with_llm_notes.md +5 -0
- package/package.json +87 -0
- package/skills/devharness/SKILL.md +195 -0
- package/skills/devharness/references/sequences.md +613 -0
- package/skills/devharness/references/tool-categories.md +99 -0
|
@@ -0,0 +1,701 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command Recorder for capturing and replaying tool invocations
|
|
3
|
+
*
|
|
4
|
+
* Records all tool calls automatically and allows creating named sequences
|
|
5
|
+
* by selecting specific command indices from the history.
|
|
6
|
+
*/
|
|
7
|
+
import { promises as fs, existsSync } from 'fs';
|
|
8
|
+
import { walkSequenceFiles } from './helpers/sequence-tree.js';
|
|
9
|
+
import { ServerFileWatcher } from './server-watcher.js';
|
|
10
|
+
import { join, dirname } from 'path';
|
|
11
|
+
import { debugLog, isHistoryLogEnabled, logToHistoryFile } from './debug-logger.js';
|
|
12
|
+
import { sanitizeReference } from './reference-validator.js';
|
|
13
|
+
import { getOutputPath } from './helpers/paths.js';
|
|
14
|
+
import { atomicWriteFile } from './atomic-write.js';
|
|
15
|
+
import { getIssueSequencesDir, getIssuesBySequenceFile } from './issue-tracker.js';
|
|
16
|
+
export class CommandRecorder {
|
|
17
|
+
history = [];
|
|
18
|
+
sequences = new Map();
|
|
19
|
+
/** Bumped per created sequence: two created in the same millisecond used to
|
|
20
|
+
* share an id, and the second silently evicted the first from the map. */
|
|
21
|
+
sequenceSeq = 0;
|
|
22
|
+
commandCounter = 0;
|
|
23
|
+
maxHistorySize = 1000; // Keep last 1000 commands
|
|
24
|
+
activeSequence = null;
|
|
25
|
+
historyViewedWhilePaused = false;
|
|
26
|
+
/**
|
|
27
|
+
* Where each in-memory sequence came from, for the ones that came from disk.
|
|
28
|
+
* A sequence built from history has no entry and is never touched by the
|
|
29
|
+
* watcher - it exists nowhere else, so there is nothing to reload it from.
|
|
30
|
+
*/
|
|
31
|
+
sequenceSources = new Map();
|
|
32
|
+
sequenceWatcher = null;
|
|
33
|
+
watchedDirs = new Set();
|
|
34
|
+
/**
|
|
35
|
+
* Get the sequences directory for a specific scope
|
|
36
|
+
*/
|
|
37
|
+
getSequencesDir(global = false) {
|
|
38
|
+
return getOutputPath('sequences', { global });
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Watch the sequences directories and reload edited files, the way a managed
|
|
42
|
+
* dev server is restarted when its sources change.
|
|
43
|
+
*
|
|
44
|
+
* Memory used to shadow disk for the lifetime of the session: a sequence
|
|
45
|
+
* loaded once kept running its original version however many times you edited
|
|
46
|
+
* the file, with nothing in the run output to say so. `runAll` reloads the
|
|
47
|
+
* whole tree first, so the same sequence behaved differently depending on how
|
|
48
|
+
* it was invoked - which is how the stale copy stayed invisible.
|
|
49
|
+
*
|
|
50
|
+
* Idempotent, and safe to call before the directories exist: it attaches to
|
|
51
|
+
* whichever are present and callers re-invoke it after a save or load.
|
|
52
|
+
*/
|
|
53
|
+
startSequenceWatch() {
|
|
54
|
+
// The sequences directories, plus wherever loaded files actually live - a
|
|
55
|
+
// sequence can be loaded by absolute path, or out of an issue's own folder.
|
|
56
|
+
const dirs = [
|
|
57
|
+
this.getSequencesDir(false),
|
|
58
|
+
this.getSequencesDir(true),
|
|
59
|
+
...[...this.sequenceSources.values()].map(s => dirname(s.path)),
|
|
60
|
+
].filter((dir, i, all) => all.indexOf(dir) === i && existsSync(dir));
|
|
61
|
+
if (dirs.length === 0)
|
|
62
|
+
return;
|
|
63
|
+
// Nothing new to cover: leave the running watcher alone.
|
|
64
|
+
if (this.sequenceWatcher && dirs.every(dir => this.watchedDirs.has(dir)))
|
|
65
|
+
return;
|
|
66
|
+
this.sequenceWatcher?.stop();
|
|
67
|
+
this.watchedDirs = new Set(dirs);
|
|
68
|
+
this.sequenceWatcher = new ServerFileWatcher({
|
|
69
|
+
paths: dirs,
|
|
70
|
+
// Sequences live UNDER .cdp-tools, which the default exclude list drops -
|
|
71
|
+
// taking every sequence with it.
|
|
72
|
+
excludeDirNames: ['node_modules', '.git'],
|
|
73
|
+
onChange: () => { void this.reloadChangedSequences(); },
|
|
74
|
+
});
|
|
75
|
+
this.sequenceWatcher.start();
|
|
76
|
+
}
|
|
77
|
+
/** Stop watching (tests, shutdown). */
|
|
78
|
+
stopSequenceWatch() {
|
|
79
|
+
this.sequenceWatcher?.stop();
|
|
80
|
+
this.sequenceWatcher = null;
|
|
81
|
+
this.watchedDirs.clear();
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Re-read every disk-backed sequence whose file is newer than the copy in
|
|
85
|
+
* memory. Returns the names actually reloaded.
|
|
86
|
+
*
|
|
87
|
+
* A file that has gone missing or will not parse leaves the in-memory copy
|
|
88
|
+
* alone: a watcher fires mid-write as readily as after one, and dropping a
|
|
89
|
+
* good sequence because it was caught half-written would be worse than the
|
|
90
|
+
* staleness this exists to fix.
|
|
91
|
+
*/
|
|
92
|
+
async reloadChangedSequences(ids) {
|
|
93
|
+
const reloaded = [];
|
|
94
|
+
const scope = ids
|
|
95
|
+
? [...this.sequenceSources].filter(([id]) => ids.includes(id))
|
|
96
|
+
: [...this.sequenceSources];
|
|
97
|
+
for (const [id, source] of scope) {
|
|
98
|
+
let mtimeMs;
|
|
99
|
+
try {
|
|
100
|
+
mtimeMs = (await fs.stat(source.path)).mtimeMs;
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (mtimeMs <= source.mtimeMs)
|
|
106
|
+
continue;
|
|
107
|
+
let parsed;
|
|
108
|
+
try {
|
|
109
|
+
parsed = await this.parseSequenceFile(source.path);
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
this.sequences.delete(id);
|
|
115
|
+
this.sequenceSources.delete(id);
|
|
116
|
+
this.registerLoadedSequence(parsed);
|
|
117
|
+
this.sequenceSources.set(parsed.id, { path: source.path, mtimeMs });
|
|
118
|
+
reloaded.push(parsed.name);
|
|
119
|
+
await debugLog('command-recorder', `Reloaded edited sequence "${parsed.name}" from ${source.path}`);
|
|
120
|
+
}
|
|
121
|
+
return reloaded;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* The current copy of a sequence, re-read first if its file has changed
|
|
125
|
+
* since it was loaded.
|
|
126
|
+
*
|
|
127
|
+
* The watcher debounces (400ms), and an edit followed immediately by a run
|
|
128
|
+
* is the normal rhythm - so a run does not wait for the watcher to catch up,
|
|
129
|
+
* it asks. One stat on the way past.
|
|
130
|
+
*/
|
|
131
|
+
async getFreshSequence(id) {
|
|
132
|
+
if (this.sequenceSources.has(id)) {
|
|
133
|
+
const [reloadedName] = await this.reloadChangedSequences([id]);
|
|
134
|
+
if (reloadedName) {
|
|
135
|
+
return [...this.sequences.values()].find(s => s.name === reloadedName);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return this.sequences.get(id);
|
|
139
|
+
}
|
|
140
|
+
/** Remember which file a sequence came from, so the watcher can refresh it. */
|
|
141
|
+
async trackSequenceSource(sequence, filepath) {
|
|
142
|
+
try {
|
|
143
|
+
this.sequenceSources.set(sequence.id, { path: filepath, mtimeMs: (await fs.stat(filepath)).mtimeMs });
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
// Unreadable stat just means this one is not watched.
|
|
147
|
+
}
|
|
148
|
+
this.startSequenceWatch();
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Set active sequence state (for step-through)
|
|
152
|
+
*/
|
|
153
|
+
setActiveSequence(state) {
|
|
154
|
+
this.activeSequence = state;
|
|
155
|
+
// Reset history viewed flag when sequence state changes
|
|
156
|
+
this.historyViewedWhilePaused = false;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Mark that history was viewed while paused (enables insert)
|
|
160
|
+
*/
|
|
161
|
+
markHistoryViewed() {
|
|
162
|
+
if (this.activeSequence) {
|
|
163
|
+
this.historyViewedWhilePaused = true;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Reset history viewed flag (called when other actions are taken)
|
|
168
|
+
*/
|
|
169
|
+
resetHistoryViewed() {
|
|
170
|
+
this.historyViewedWhilePaused = false;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Check if history was viewed while paused
|
|
174
|
+
*/
|
|
175
|
+
wasHistoryViewed() {
|
|
176
|
+
return this.historyViewedWhilePaused;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Get active sequence state
|
|
180
|
+
*/
|
|
181
|
+
getActiveSequence() {
|
|
182
|
+
return this.activeSequence;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Update current step in active sequence
|
|
186
|
+
*/
|
|
187
|
+
updateActiveSequenceStep(step) {
|
|
188
|
+
if (this.activeSequence) {
|
|
189
|
+
this.activeSequence.currentStep = step;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Get commands recorded since sequence was paused
|
|
194
|
+
*/
|
|
195
|
+
getCommandsSincePause() {
|
|
196
|
+
if (!this.activeSequence)
|
|
197
|
+
return [];
|
|
198
|
+
return this.history.filter(cmd => cmd.index > this.activeSequence.historyIndexAtPause);
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Get current history index (for tracking pause point)
|
|
202
|
+
*/
|
|
203
|
+
getCurrentHistoryIndex() {
|
|
204
|
+
return this.history.length > 0 ? this.history[this.history.length - 1].index : -1;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Record a command (always-on, automatic)
|
|
208
|
+
*/
|
|
209
|
+
async recordCommand(tool, params, options) {
|
|
210
|
+
// Reset history viewed flag when any command is recorded
|
|
211
|
+
// (user must view history again before inserting)
|
|
212
|
+
this.historyViewedWhilePaused = false;
|
|
213
|
+
const paramsClone = JSON.parse(JSON.stringify(params));
|
|
214
|
+
// Keep the connectionReason the call was actually made with (bug-018).
|
|
215
|
+
// It used to be deleted here "to make sequences reusable", which meant a
|
|
216
|
+
// recording that drove two browsers could not be replayed against two
|
|
217
|
+
// browsers - every step fell back to the run-level connection and the
|
|
218
|
+
// sequence silently collapsed into one browser. Reusability is preserved at
|
|
219
|
+
// `create` time instead: a sequence whose steps all share one connection has
|
|
220
|
+
// it hoisted back off the steps (see normalizeStepConnections), so a
|
|
221
|
+
// run-level connectionReason still overrides. Only genuinely
|
|
222
|
+
// multi-connection sequences keep it per-step.
|
|
223
|
+
//
|
|
224
|
+
// Sanitized so a recorded reference always matches the stored connection
|
|
225
|
+
// reference ("Duo Owner Console" -> "duo-owner-console"), which is what
|
|
226
|
+
// connection lookup and the launchChrome `reference` below use.
|
|
227
|
+
if (typeof paramsClone.connectionReason === 'string') {
|
|
228
|
+
paramsClone.connectionReason = sanitizeReference(paramsClone.connectionReason);
|
|
229
|
+
}
|
|
230
|
+
// Sanitize 'reference' param for tools that create connections
|
|
231
|
+
// This ensures recorded sequences use the same reference format as the actual connection
|
|
232
|
+
if (paramsClone.reference && ['launchChrome', 'connectDebugger'].includes(tool)) {
|
|
233
|
+
paramsClone.reference = sanitizeReference(paramsClone.reference);
|
|
234
|
+
}
|
|
235
|
+
// Sanitize 'newReference' for tab rename operations
|
|
236
|
+
if (paramsClone.newReference && tool === 'tab' && paramsClone.action === 'rename') {
|
|
237
|
+
paramsClone.newReference = sanitizeReference(paramsClone.newReference);
|
|
238
|
+
}
|
|
239
|
+
const command = {
|
|
240
|
+
index: this.commandCounter++,
|
|
241
|
+
timestamp: Date.now(),
|
|
242
|
+
tool,
|
|
243
|
+
params: paramsClone,
|
|
244
|
+
...(options?.delay !== undefined && { delay: options.delay }),
|
|
245
|
+
...(options?.comment && { comment: options.comment }),
|
|
246
|
+
};
|
|
247
|
+
this.history.push(command);
|
|
248
|
+
// Trim history if it exceeds max size
|
|
249
|
+
if (this.history.length > this.maxHistorySize) {
|
|
250
|
+
this.history.shift();
|
|
251
|
+
}
|
|
252
|
+
await debugLog('command-recorder', `Recorded #${command.index}: ${tool}`);
|
|
253
|
+
// Write to history.log in replay-compatible format when history logging is enabled
|
|
254
|
+
if (isHistoryLogEnabled()) {
|
|
255
|
+
const historyEntry = { tool, params: paramsClone };
|
|
256
|
+
await logToHistoryFile(JSON.stringify(historyEntry));
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Get command history (most recent first)
|
|
261
|
+
*/
|
|
262
|
+
getHistory(limit = 50) {
|
|
263
|
+
return [...this.history].reverse().slice(0, limit);
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Get a specific command by index
|
|
267
|
+
*/
|
|
268
|
+
getCommand(index) {
|
|
269
|
+
return this.history.find(cmd => cmd.index === index);
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Create a sequence from command indices
|
|
273
|
+
*/
|
|
274
|
+
async createSequence(name, commandIndices, options) {
|
|
275
|
+
// Validate all indices exist and get commands
|
|
276
|
+
const commands = [];
|
|
277
|
+
for (const idx of commandIndices) {
|
|
278
|
+
const cmd = this.getCommand(idx);
|
|
279
|
+
if (!cmd) {
|
|
280
|
+
await debugLog('command-recorder', `Invalid command index: ${idx}`);
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
// Strip index and timestamp for the sequence, but keep delay and comment.
|
|
284
|
+
// params is DEEP-CLONED: the sequence is edited after creation (hoisting a
|
|
285
|
+
// uniform connectionReason off the steps, rebasing URLs), and sharing the
|
|
286
|
+
// object with the history entry made those edits silently rewrite history.
|
|
287
|
+
commands.push({
|
|
288
|
+
tool: cmd.tool,
|
|
289
|
+
params: JSON.parse(JSON.stringify(cmd.params)),
|
|
290
|
+
...(cmd.delay !== undefined && { delay: cmd.delay }),
|
|
291
|
+
...(cmd.comment && { comment: cmd.comment }),
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
// Extract startUrl from commands if not explicitly provided
|
|
295
|
+
let startUrl = options?.startUrl;
|
|
296
|
+
if (!startUrl) {
|
|
297
|
+
// Look for the first navigate goto action
|
|
298
|
+
for (const cmd of commands) {
|
|
299
|
+
if (cmd.tool === 'navigate' && cmd.params.action === 'goto' && cmd.params.url) {
|
|
300
|
+
startUrl = cmd.params.url;
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
const sequence = {
|
|
306
|
+
id: `seq-${Date.now()}-${++this.sequenceSeq}`,
|
|
307
|
+
name,
|
|
308
|
+
...(options?.description && { description: options.description }),
|
|
309
|
+
...(options?.expectedOutcome && { expectedOutcome: options.expectedOutcome }),
|
|
310
|
+
...(startUrl && { startUrl }),
|
|
311
|
+
commands,
|
|
312
|
+
createdAt: Date.now(),
|
|
313
|
+
};
|
|
314
|
+
// Validate before anything destructive happens (see options.validate).
|
|
315
|
+
if (options?.validate && !options.validate(sequence)) {
|
|
316
|
+
await debugLog('command-recorder', `Rejected sequence "${name}" (validation failed) - existing sequence left intact`);
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
// Dedupe by name so re-creating a sequence replaces the old in-memory copy
|
|
320
|
+
// rather than leaving a stale one that loadSequence({name}) would resolve (#75).
|
|
321
|
+
this.removeSequenceByName(name);
|
|
322
|
+
this.sequences.set(sequence.id, sequence);
|
|
323
|
+
await debugLog('command-recorder', `Created sequence "${name}" with ${commands.length} commands${startUrl ? `, startUrl: ${startUrl}` : ''}`);
|
|
324
|
+
return sequence;
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Create a sequence directly from commands (not from history)
|
|
328
|
+
*/
|
|
329
|
+
async createSequenceFromCommands(name, commands, options) {
|
|
330
|
+
// Extract startUrl from commands if not explicitly provided
|
|
331
|
+
let startUrl = options?.startUrl;
|
|
332
|
+
if (!startUrl) {
|
|
333
|
+
for (const cmd of commands) {
|
|
334
|
+
if (cmd.tool === 'navigate' && cmd.params.action === 'goto' && cmd.params.url) {
|
|
335
|
+
startUrl = cmd.params.url;
|
|
336
|
+
break;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
const sequence = {
|
|
341
|
+
id: `seq-${Date.now()}-${++this.sequenceSeq}`,
|
|
342
|
+
name,
|
|
343
|
+
...(options?.description && { description: options.description }),
|
|
344
|
+
...(options?.expectedOutcome && { expectedOutcome: options.expectedOutcome }),
|
|
345
|
+
...(startUrl && { startUrl }),
|
|
346
|
+
commands,
|
|
347
|
+
createdAt: Date.now(),
|
|
348
|
+
};
|
|
349
|
+
// Dedupe by name (see createSequence / #75).
|
|
350
|
+
this.removeSequenceByName(name);
|
|
351
|
+
this.sequences.set(sequence.id, sequence);
|
|
352
|
+
await debugLog('command-recorder', `Created sequence "${name}" from commands with ${commands.length} commands${startUrl ? `, startUrl: ${startUrl}` : ''}`);
|
|
353
|
+
return sequence;
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Check if a sequence with the given name exists
|
|
357
|
+
*/
|
|
358
|
+
sequenceNameExists(name) {
|
|
359
|
+
for (const seq of this.sequences.values()) {
|
|
360
|
+
if (seq.name === name)
|
|
361
|
+
return true;
|
|
362
|
+
}
|
|
363
|
+
return false;
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* List all saved sequences
|
|
367
|
+
*/
|
|
368
|
+
listSequences() {
|
|
369
|
+
return Array.from(this.sequences.values());
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Get a specific sequence by ID
|
|
373
|
+
*/
|
|
374
|
+
getSequence(sequenceId) {
|
|
375
|
+
return this.sequences.get(sequenceId);
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Delete a sequence
|
|
379
|
+
*/
|
|
380
|
+
deleteSequence(sequenceId) {
|
|
381
|
+
return this.sequences.delete(sequenceId);
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Remove ALL in-memory sequences with the given name. Used on create/reload so a
|
|
385
|
+
* name maps to exactly one sequence — otherwise `loadSequence({name})` resolves the
|
|
386
|
+
* oldest insertion-order match and re-created sequences are silently ignored (#75).
|
|
387
|
+
*/
|
|
388
|
+
removeSequenceByName(name) {
|
|
389
|
+
for (const [id, seq] of this.sequences.entries()) {
|
|
390
|
+
if (seq.name === name) {
|
|
391
|
+
this.sequences.delete(id);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Clear all sequences
|
|
397
|
+
*/
|
|
398
|
+
async clearAllSequences() {
|
|
399
|
+
this.sequences.clear();
|
|
400
|
+
await debugLog('command-recorder', 'All sequences cleared');
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Clear command history
|
|
404
|
+
*/
|
|
405
|
+
async clearHistory() {
|
|
406
|
+
this.history = [];
|
|
407
|
+
this.commandCounter = 0;
|
|
408
|
+
await debugLog('command-recorder', 'Command history cleared');
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Get statistics
|
|
412
|
+
*/
|
|
413
|
+
getStats() {
|
|
414
|
+
return {
|
|
415
|
+
historyCount: this.history.length,
|
|
416
|
+
sequenceCount: this.sequences.size,
|
|
417
|
+
oldestCommandIndex: this.history.length > 0 ? this.history[0].index : null,
|
|
418
|
+
newestCommandIndex: this.history.length > 0 ? this.history[this.history.length - 1].index : null,
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Save a sequence to disk
|
|
423
|
+
* @param sequenceId - ID of the sequence to save
|
|
424
|
+
* @param global - If true, save to global ~/.cdp-tools/sequences/, otherwise working directory
|
|
425
|
+
*/
|
|
426
|
+
async saveSequenceToDisk(sequenceId, global = false, overwrite = false) {
|
|
427
|
+
const sequence = this.getSequence(sequenceId);
|
|
428
|
+
if (!sequence) {
|
|
429
|
+
await debugLog('command-recorder', `Sequence ${sequenceId} not found`);
|
|
430
|
+
return null;
|
|
431
|
+
}
|
|
432
|
+
const targetDir = this.getSequencesDir(global);
|
|
433
|
+
try {
|
|
434
|
+
// Sanitize filename - use name directly
|
|
435
|
+
// Note: atomicWriteFile handles directory creation
|
|
436
|
+
const safeFilename = sequence.name.replace(/[^a-z0-9-_]/gi, '-').toLowerCase();
|
|
437
|
+
// Save back into the folder the sequence came from. Writing to the root
|
|
438
|
+
// instead forks it: the foldered original goes stale while a second copy
|
|
439
|
+
// appears at top level, which runAll then runs twice.
|
|
440
|
+
let filename = `${safeFilename}.json`;
|
|
441
|
+
const existing = (await this.listSavedSequencesOnDisk())
|
|
442
|
+
.find(e => e.location === (global ? 'global' : 'working-dir') &&
|
|
443
|
+
(e.id === sequence.id || e.name === sequence.name));
|
|
444
|
+
const existingDir = existing?.filename.includes('/')
|
|
445
|
+
? existing.filename.slice(0, existing.filename.lastIndexOf('/'))
|
|
446
|
+
: '';
|
|
447
|
+
if (existingDir)
|
|
448
|
+
filename = `${existingDir}/${filename}`;
|
|
449
|
+
const filepath = join(targetDir, filename);
|
|
450
|
+
// Check for conflict
|
|
451
|
+
try {
|
|
452
|
+
await fs.access(filepath);
|
|
453
|
+
// File exists
|
|
454
|
+
if (!overwrite) {
|
|
455
|
+
return { success: false, error: 'File already exists', conflict: true, filepath };
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
catch {
|
|
459
|
+
// File doesn't exist, proceed
|
|
460
|
+
}
|
|
461
|
+
// Add usage comment to exported file
|
|
462
|
+
const exportData = {
|
|
463
|
+
_comment: 'CDP Tools replay sequence. Load with: replay({ action: "load", filename: "<this-file>" }), then run with: replay({ action: "run", name: "<this-file>" })',
|
|
464
|
+
...sequence
|
|
465
|
+
};
|
|
466
|
+
await atomicWriteFile(filepath, JSON.stringify(exportData, null, 2));
|
|
467
|
+
// Now disk-backed: watch it, and record THIS write's mtime so the save
|
|
468
|
+
// does not read back as an external edit.
|
|
469
|
+
await this.trackSequenceSource(sequence, filepath);
|
|
470
|
+
await debugLog('command-recorder', `Saved sequence "${sequence.name}" to ${filepath}`);
|
|
471
|
+
return { success: true, filepath };
|
|
472
|
+
}
|
|
473
|
+
catch (error) {
|
|
474
|
+
await debugLog('command-recorder', `Failed to save sequence: ${error}`);
|
|
475
|
+
return { success: false, error: error.message || String(error) };
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Find best matching filename from saved sequences (searches both working dir and global)
|
|
480
|
+
* Supports: exact match, with/without .json, name prefix (returns latest by timestamp)
|
|
481
|
+
* Returns the full path to the matched file
|
|
482
|
+
*/
|
|
483
|
+
async findMatchingFilename(searchTerm) {
|
|
484
|
+
const savedSequences = await this.listSavedSequencesOnDisk();
|
|
485
|
+
if (savedSequences.length === 0)
|
|
486
|
+
return null;
|
|
487
|
+
const term = searchTerm.toLowerCase();
|
|
488
|
+
const termWithoutJson = term.endsWith('.json') ? term.slice(0, -5) : term;
|
|
489
|
+
// `filename` is relative to the sequences root, so it may carry a folder
|
|
490
|
+
// ('spine/spine-01.json'). Match the bare basename too, otherwise moving a
|
|
491
|
+
// file into a folder breaks every existing load call that names it directly.
|
|
492
|
+
const base = (f) => f.toLowerCase().split('/').pop() || f.toLowerCase();
|
|
493
|
+
// 1. Exact filename match (with or without .json, full path or basename)
|
|
494
|
+
const exactMatch = savedSequences.find(s => s.filename.toLowerCase() === term ||
|
|
495
|
+
s.filename.toLowerCase() === termWithoutJson + '.json' ||
|
|
496
|
+
base(s.filename) === term ||
|
|
497
|
+
base(s.filename) === termWithoutJson + '.json');
|
|
498
|
+
if (exactMatch) {
|
|
499
|
+
return { filename: exactMatch.filename, fullPath: exactMatch.fullPath, matchType: 'exact', location: exactMatch.location };
|
|
500
|
+
}
|
|
501
|
+
// 2. Exact name match
|
|
502
|
+
const nameMatch = savedSequences.find(s => s.name.toLowerCase() === termWithoutJson);
|
|
503
|
+
if (nameMatch) {
|
|
504
|
+
return { filename: nameMatch.filename, fullPath: nameMatch.fullPath, matchType: 'name', location: nameMatch.location };
|
|
505
|
+
}
|
|
506
|
+
// 3. Filename starts with search term (gets latest by sorting)
|
|
507
|
+
const prefixMatches = savedSequences.filter(s => s.filename.toLowerCase().startsWith(termWithoutJson) ||
|
|
508
|
+
base(s.filename).startsWith(termWithoutJson));
|
|
509
|
+
if (prefixMatches.length > 0) {
|
|
510
|
+
// Sort by filename descending to get latest timestamp
|
|
511
|
+
prefixMatches.sort((a, b) => b.filename.localeCompare(a.filename));
|
|
512
|
+
return { filename: prefixMatches[0].filename, fullPath: prefixMatches[0].fullPath, matchType: 'prefix', location: prefixMatches[0].location };
|
|
513
|
+
}
|
|
514
|
+
// 4. Name starts with search term
|
|
515
|
+
const namePrefixMatches = savedSequences.filter(s => s.name.toLowerCase().startsWith(termWithoutJson));
|
|
516
|
+
if (namePrefixMatches.length > 0) {
|
|
517
|
+
namePrefixMatches.sort((a, b) => b.filename.localeCompare(a.filename));
|
|
518
|
+
return { filename: namePrefixMatches[0].filename, fullPath: namePrefixMatches[0].fullPath, matchType: 'name-prefix', location: namePrefixMatches[0].location };
|
|
519
|
+
}
|
|
520
|
+
// 5. Name contains search term
|
|
521
|
+
const containsMatches = savedSequences.filter(s => s.name.toLowerCase().includes(termWithoutJson));
|
|
522
|
+
if (containsMatches.length > 0) {
|
|
523
|
+
containsMatches.sort((a, b) => b.filename.localeCompare(a.filename));
|
|
524
|
+
return { filename: containsMatches[0].filename, fullPath: containsMatches[0].fullPath, matchType: 'contains', location: containsMatches[0].location };
|
|
525
|
+
}
|
|
526
|
+
return null;
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* Parse a sequence file from disk WITHOUT touching in-memory state.
|
|
530
|
+
* Split out from registration so a caller can validate the parsed candidate
|
|
531
|
+
* before anything same-named is evicted (see registerLoadedSequence).
|
|
532
|
+
*/
|
|
533
|
+
async parseSequenceFile(filepath) {
|
|
534
|
+
const content = await fs.readFile(filepath, 'utf-8');
|
|
535
|
+
return JSON.parse(content);
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Register a parsed sequence in memory, replacing any same-named copy.
|
|
539
|
+
* Validation (if supplied) runs BEFORE the removal, so a rejected load leaves
|
|
540
|
+
* the pre-existing sequence completely intact instead of deleting the good copy
|
|
541
|
+
* and then rejecting the bad one, leaving the user with neither.
|
|
542
|
+
*/
|
|
543
|
+
registerLoadedSequence(sequence, validate) {
|
|
544
|
+
if (validate && !validate(sequence))
|
|
545
|
+
return false;
|
|
546
|
+
// Remove any existing sequence with the same name (may have different ID)
|
|
547
|
+
this.removeSequenceByName(sequence.name);
|
|
548
|
+
this.sequences.set(sequence.id, sequence);
|
|
549
|
+
return true;
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* Load a sequence from disk (supports fuzzy filename matching)
|
|
553
|
+
*
|
|
554
|
+
* @param options.validate - Called with the parsed candidate BEFORE it replaces any
|
|
555
|
+
* same-named in-memory sequence. Return false to reject: nothing is removed and
|
|
556
|
+
* nothing is stored, and this method returns null.
|
|
557
|
+
*/
|
|
558
|
+
async loadSequenceFromDisk(filename, options) {
|
|
559
|
+
try {
|
|
560
|
+
// Support absolute paths directly
|
|
561
|
+
if (filename.startsWith('/') || filename.includes(':\\')) {
|
|
562
|
+
const sequence = await this.parseSequenceFile(filename);
|
|
563
|
+
if (!this.registerLoadedSequence(sequence, options?.validate)) {
|
|
564
|
+
await debugLog('command-recorder', `Rejected sequence "${sequence.name}" from ${filename} (validation failed) - existing sequence left intact`);
|
|
565
|
+
return null;
|
|
566
|
+
}
|
|
567
|
+
await this.trackSequenceSource(sequence, filename);
|
|
568
|
+
await debugLog('command-recorder', `Loaded sequence "${sequence.name}" from ${filename}`);
|
|
569
|
+
return sequence;
|
|
570
|
+
}
|
|
571
|
+
// Try fuzzy matching for relative filenames
|
|
572
|
+
const match = await this.findMatchingFilename(filename);
|
|
573
|
+
if (!match) {
|
|
574
|
+
await debugLog('command-recorder', `No matching sequence found for "${filename}"`);
|
|
575
|
+
return null;
|
|
576
|
+
}
|
|
577
|
+
const filepath = match.fullPath;
|
|
578
|
+
await debugLog('command-recorder', `Matched "${filename}" to "${match.filename}" (${match.matchType})`);
|
|
579
|
+
const sequence = await this.parseSequenceFile(filepath);
|
|
580
|
+
if (!this.registerLoadedSequence(sequence, options?.validate)) {
|
|
581
|
+
await debugLog('command-recorder', `Rejected sequence "${sequence.name}" from ${filepath} (validation failed) - existing sequence left intact`);
|
|
582
|
+
return null;
|
|
583
|
+
}
|
|
584
|
+
await this.trackSequenceSource(sequence, filepath);
|
|
585
|
+
await debugLog('command-recorder', `Loaded sequence "${sequence.name}" from ${filepath}`);
|
|
586
|
+
return sequence;
|
|
587
|
+
}
|
|
588
|
+
catch (error) {
|
|
589
|
+
await debugLog('command-recorder', `Failed to load sequence from ${filename}: ${error}`);
|
|
590
|
+
return null;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* List saved sequences on disk from a specific directory
|
|
595
|
+
*/
|
|
596
|
+
async listSequencesFromDir(dir, location) {
|
|
597
|
+
const sequences = [];
|
|
598
|
+
try {
|
|
599
|
+
// Read directory - if it doesn't exist, return empty list (no side effects)
|
|
600
|
+
// Walk subdirectories too: a suite is easier to run when it can be grouped
|
|
601
|
+
// into folders (spine/, story/, _helpers/). `filename` stays relative to the
|
|
602
|
+
// sequences root ('spine/spine-01.json'), so it round-trips back through load.
|
|
603
|
+
let jsonFiles;
|
|
604
|
+
try {
|
|
605
|
+
jsonFiles = await walkSequenceFiles(dir);
|
|
606
|
+
}
|
|
607
|
+
catch (err) {
|
|
608
|
+
if (err.code === 'ENOENT')
|
|
609
|
+
return sequences; // Directory doesn't exist yet
|
|
610
|
+
throw err;
|
|
611
|
+
}
|
|
612
|
+
for (const file of jsonFiles) {
|
|
613
|
+
try {
|
|
614
|
+
const filepath = join(dir, file);
|
|
615
|
+
const content = await fs.readFile(filepath, 'utf-8');
|
|
616
|
+
const sequence = JSON.parse(content);
|
|
617
|
+
sequences.push({
|
|
618
|
+
filename: file,
|
|
619
|
+
name: sequence.name,
|
|
620
|
+
id: sequence.id,
|
|
621
|
+
commandCount: sequence.commands?.length || 0,
|
|
622
|
+
location,
|
|
623
|
+
fullPath: filepath,
|
|
624
|
+
...(sequence.description && { description: sequence.description }),
|
|
625
|
+
...(sequence.expectedOutcome && { expectedOutcome: sequence.expectedOutcome }),
|
|
626
|
+
...(sequence.startUrl && { startUrl: sequence.startUrl }),
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
catch (error) {
|
|
630
|
+
await debugLog('command-recorder', `Failed to parse ${file}: ${error}`);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
catch (error) {
|
|
635
|
+
await debugLog('command-recorder', `Failed to list sequences from ${dir}: ${error}`);
|
|
636
|
+
}
|
|
637
|
+
return sequences;
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* List saved sequences on disk (checks both working directory and global)
|
|
641
|
+
*/
|
|
642
|
+
async listSavedSequencesOnDisk() {
|
|
643
|
+
const workingDir = this.getSequencesDir(false);
|
|
644
|
+
const globalDir = this.getSequencesDir(true);
|
|
645
|
+
// Get sequences from both locations
|
|
646
|
+
const workingDirSequences = await this.listSequencesFromDir(workingDir, 'working-dir');
|
|
647
|
+
const globalSequences = await this.listSequencesFromDir(globalDir, 'global');
|
|
648
|
+
// If working dir and global are the same (fallback case), avoid duplicates
|
|
649
|
+
if (workingDir === globalDir) {
|
|
650
|
+
return workingDirSequences;
|
|
651
|
+
}
|
|
652
|
+
return [...workingDirSequences, ...globalSequences];
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* List issue sequences on disk with associated issue metadata
|
|
656
|
+
*/
|
|
657
|
+
async listIssueSequencesOnDisk() {
|
|
658
|
+
const issuesDir = getIssueSequencesDir();
|
|
659
|
+
const sequences = await this.listSequencesFromDir(issuesDir, 'issues');
|
|
660
|
+
// Get issue metadata
|
|
661
|
+
const issueMap = await getIssuesBySequenceFile();
|
|
662
|
+
return sequences.map(seq => {
|
|
663
|
+
const issue = issueMap.get(seq.filename);
|
|
664
|
+
return {
|
|
665
|
+
...seq,
|
|
666
|
+
issueId: issue?.id,
|
|
667
|
+
issueType: issue?.type,
|
|
668
|
+
issueStatus: issue?.status
|
|
669
|
+
};
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* Delete a sequence from disk (supports fuzzy filename matching)
|
|
674
|
+
*/
|
|
675
|
+
async deleteSequenceFromDisk(filename) {
|
|
676
|
+
try {
|
|
677
|
+
// Support absolute paths directly
|
|
678
|
+
if (filename.startsWith('/') || filename.includes(':\\')) {
|
|
679
|
+
await fs.unlink(filename);
|
|
680
|
+
await debugLog('command-recorder', `Deleted sequence file: ${filename}`);
|
|
681
|
+
return true;
|
|
682
|
+
}
|
|
683
|
+
// Try fuzzy matching for relative filenames
|
|
684
|
+
const match = await this.findMatchingFilename(filename);
|
|
685
|
+
if (!match) {
|
|
686
|
+
await debugLog('command-recorder', `No matching sequence found for "${filename}"`);
|
|
687
|
+
return false;
|
|
688
|
+
}
|
|
689
|
+
const filepath = match.fullPath;
|
|
690
|
+
await debugLog('command-recorder', `Matched "${filename}" to "${match.filename}" (${match.matchType})`);
|
|
691
|
+
await fs.unlink(filepath);
|
|
692
|
+
await debugLog('command-recorder', `Deleted sequence file: ${filepath}`);
|
|
693
|
+
return true;
|
|
694
|
+
}
|
|
695
|
+
catch (error) {
|
|
696
|
+
await debugLog('command-recorder', `Failed to delete ${filename}: ${error}`);
|
|
697
|
+
return false;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
//# sourceMappingURL=command-recorder.js.map
|