mobile-debug-mcp 0.33.0 → 0.35.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/.agents/skills/mcp-builder/SKILL.md +44 -64
- package/.agents/skills/mcp-builder/references/build-flags.md +44 -43
- package/.agents/skills/mcp-builder/references/diagnostics-schema.md +4 -1
- package/.agents/skills/mcp-builder/references/toolchain-details.md +49 -62
- package/.agents/skills/spec-review/references/spec-review-checklist.md +7 -0
- package/.agents/skills/test-authoring/SKILL.md +7 -6
- package/.agents/skills/test-authoring/references/repo-test-layout.md +3 -1
- package/.agents/skills/test-authoring/references/test-authoring-workflow.md +16 -7
- package/.agents/skills/test-authoring/references/test-quality-checklist.md +2 -1
- package/.github/copilot-instructions.md +14 -36
- package/.github/workflows/ci.yml +23 -2
- package/AGENTS.md +12 -3
- package/CONTRIBUTING.md +79 -0
- package/README.md +158 -124
- package/agent-plugin/.claude-plugin/plugin.json +2 -2
- package/agent-plugin/.codex-plugin/plugin.json +1 -1
- package/agent-plugin/.cursor-plugin/plugin.json +2 -2
- package/agent-plugin/.mcp.json +1 -1
- package/agent-plugin/README.md +20 -6
- package/agent-plugin/mcp.json +1 -1
- package/agent-plugin/plugin.json +1 -1
- package/agent-plugin/skills/mobile-debug-mcp/SKILL.md +113 -4
- package/dist/contracts/execution-controls.js +24 -0
- package/dist/contracts/selector.js +8 -0
- package/dist/contracts/target.js +15 -0
- package/dist/contracts/ui.js +1 -0
- package/dist/debugger/android.js +79 -0
- package/dist/debugger/diagnostics.js +54 -0
- package/dist/debugger/guard.js +30 -0
- package/dist/debugger/protocol.js +33 -0
- package/dist/debugger/sessions.js +304 -0
- package/dist/debugger/worker.js +176 -0
- package/dist/interact/adjustment/adjustment-context.js +41 -0
- package/dist/interact/adjustment/adjustment-gestures.js +55 -0
- package/dist/interact/adjustment/adjustment-plan.js +18 -0
- package/dist/interact/adjustment/adjustment-resolution.js +127 -0
- package/dist/interact/adjustment/adjustment-result.js +19 -0
- package/dist/interact/adjustment/adjustment-semantic.js +64 -0
- package/dist/interact/adjustment/adjustment-verification.js +35 -0
- package/dist/interact/adjustment/adjustment.js +52 -0
- package/dist/interact/adjustment/control-geometry.js +87 -0
- package/dist/interact/adjustment/control-values.js +136 -0
- package/dist/interact/adjustment/slider-labels.js +84 -0
- package/dist/interact/assertions/screen-assertion.js +37 -0
- package/dist/interact/assertions/state-assertion-context.js +35 -0
- package/dist/interact/assertions/state-assertion.js +76 -0
- package/dist/interact/assertions/state-comparison.js +141 -0
- package/dist/interact/assertions/visibility-assertion.js +34 -0
- package/dist/interact/conditional-action.js +12 -7
- package/dist/interact/index.js +65 -3074
- package/dist/interact/input-service.js +1 -0
- package/dist/interact/interaction-trace.js +21 -0
- package/dist/interact/model.js +1 -0
- package/dist/interact/observation.js +1 -0
- package/dist/interact/targeting/candidate-ancestor.js +58 -0
- package/dist/interact/targeting/candidate-result.js +23 -0
- package/dist/interact/targeting/candidate-scoring.js +50 -0
- package/dist/interact/targeting/element-helpers.js +216 -0
- package/dist/interact/targeting/find-element.js +65 -0
- package/dist/interact/targeting/nearby-control.js +56 -0
- package/dist/interact/targeting/resolved-elements.js +21 -0
- package/dist/interact/targeting/tap-target.js +17 -0
- package/dist/interact/targeting/target-scope.js +104 -0
- package/dist/interact/waits/legacy-log-wait.js +38 -0
- package/dist/interact/waits/legacy-wait-observations.js +103 -0
- package/dist/interact/waits/legacy-wait-state.js +34 -0
- package/dist/interact/waits/legacy-wait.js +48 -0
- package/dist/interact/waits/screen-wait.js +49 -0
- package/dist/interact/waits/ui-change-helpers.js +157 -0
- package/dist/interact/waits/ui-change-state.js +18 -0
- package/dist/interact/waits/ui-change-wait.js +77 -0
- package/dist/interact/waits/ui-wait-condition.js +56 -0
- package/dist/interact/waits/ui-wait-state.js +38 -0
- package/dist/interact/waits/ui-wait.js +106 -0
- package/dist/manage/android.js +55 -51
- package/dist/manage/index.js +38 -75
- package/dist/manage/ios/build-types.js +1 -0
- package/dist/manage/ios/build.js +210 -0
- package/dist/manage/ios/project-discovery.js +29 -0
- package/dist/manage/ios/scheme.js +18 -0
- package/dist/manage/ios.js +42 -256
- package/dist/observe/android/context.js +1 -0
- package/dist/observe/android/current-screen.js +55 -0
- package/dist/observe/android/fingerprint.js +13 -0
- package/dist/observe/android/log-stream.js +132 -0
- package/dist/observe/android/logs.js +107 -0
- package/dist/observe/android/screenshot.js +70 -0
- package/dist/observe/android/ui-tree.js +107 -0
- package/dist/observe/android.js +16 -520
- package/dist/observe/evidence.js +122 -76
- package/dist/observe/index.js +16 -263
- package/dist/observe/ios/context.js +16 -0
- package/dist/observe/ios/fingerprint.js +12 -0
- package/dist/observe/ios/log-stream.js +105 -0
- package/dist/observe/ios/logs.js +168 -0
- package/dist/observe/ios/screenshot.js +22 -0
- package/dist/observe/ios/ui-elements.js +241 -0
- package/dist/observe/ios/ui-tree.js +137 -0
- package/dist/observe/ios.js +17 -715
- package/dist/observe/logs/model.js +1 -0
- package/dist/{network → observe/network}/index.js +2 -2
- package/dist/observe/readiness-service.js +13 -2
- package/dist/observe/readiness.js +44 -18
- package/dist/observe/screenshot-encoding.js +41 -0
- package/dist/observe/snapshot-collection.js +79 -0
- package/dist/observe/snapshot-logs.js +30 -0
- package/dist/observe/snapshot-metadata.js +43 -31
- package/dist/observe/snapshot-semantic.js +115 -0
- package/dist/observe/system-trace-android.js +135 -0
- package/dist/observe/system-trace.js +294 -0
- package/dist/runtime/action-results.js +270 -0
- package/dist/runtime/clock.js +4 -0
- package/dist/server/common.js +1 -256
- package/dist/server/definitions/assert.js +162 -0
- package/dist/server/definitions/diagnostic.js +118 -0
- package/dist/server/definitions/input.js +274 -0
- package/dist/server/definitions/manage.js +194 -0
- package/dist/server/definitions/observe.js +149 -0
- package/dist/server/definitions/target.js +138 -0
- package/dist/server/definitions/wait.js +105 -0
- package/dist/server/handlers/action-execution.js +163 -0
- package/dist/server/handlers/correlation.js +23 -0
- package/dist/server/handlers/debugger-handlers.js +48 -0
- package/dist/server/handlers/debugger-response.js +9 -0
- package/dist/server/handlers/diagnostic.js +69 -0
- package/dist/server/handlers/handler-types.js +1 -0
- package/dist/server/handlers/input.js +154 -0
- package/dist/server/handlers/interact.js +113 -0
- package/dist/server/handlers/journey-assertions.js +35 -0
- package/dist/server/handlers/journey.js +147 -0
- package/dist/server/handlers/manage.js +119 -0
- package/dist/server/handlers/observe.js +95 -0
- package/dist/server/handlers/payload.js +4 -0
- package/dist/server/handlers/tool-handlers.js +160 -0
- package/dist/server/schemas/condition-schema.js +2 -0
- package/dist/server/schemas/debugger-schema.js +47 -0
- package/dist/server/{journey-schema.js → schemas/journey-schema.js} +8 -22
- package/dist/server/schemas/system-trace-schema.js +17 -0
- package/dist/server/tool-definitions.js +62 -1134
- package/dist/server-core.js +4 -4
- package/dist/server.js +34 -0
- package/dist/system/android-status.js +101 -0
- package/dist/system/android.js +37 -27
- package/dist/system/index.js +13 -438
- package/dist/system/ios-status.js +132 -0
- package/dist/system/ios.js +11 -8
- package/dist/system/model.js +1 -0
- package/dist/system/probes.js +105 -0
- package/dist/system/status-summary.js +43 -0
- package/dist/system/unsupported-status.js +52 -0
- package/dist/utils/android/commands.js +89 -0
- package/dist/utils/android/gradle.js +139 -0
- package/dist/utils/android/logs.js +103 -0
- package/dist/utils/android/ui-tree.js +233 -0
- package/dist/utils/android/utils.js +6 -564
- package/dist/utils/cli/idb/idb-helper.js +4 -6
- package/dist/utils/cli/idb/install-idb.js +13 -9
- package/dist/utils/cli/ios/preflight-ios.js +42 -38
- package/dist/utils/exec.js +2 -0
- package/dist/utils/ios/utils.js +45 -46
- package/dist/utils/java.js +75 -57
- package/dist/utils/operation-budget.js +2 -0
- package/dist/utils/resolve-device.js +6 -2
- package/dist/utils/ui/index.js +26 -26
- package/docs/CHANGELOG.md +48 -0
- package/docs/README.md +38 -0
- package/docs/benchmarks/README.md +18 -0
- package/docs/benchmarks/interaction-latency/README.md +14 -0
- package/docs/benchmarks/interaction-latency/final.md +4 -1
- package/docs/benchmarks/interaction-latency/ios-validation.md +2 -0
- package/docs/benchmarks/ui-tree-performance/README.md +24 -16
- package/docs/benchmarks/ui-tree-performance/comparison.md +11 -10
- package/docs/code-structure.md +118 -0
- package/docs/specs/001-state-verification/spec.md +16 -14
- package/docs/specs/002-richer-element-identity/spec.md +12 -10
- package/docs/specs/003-wait-and-synchronization-reliability/spec.md +12 -10
- package/docs/specs/004-action-verification-routing/spec.md +3 -3
- package/docs/specs/005-unified-action-execution-and-verification-model/spec.md +2 -0
- package/docs/specs/006-runtime-action-instrumentation-and-binding-layer/spec.md +2 -0
- package/docs/specs/007-actionability-resolution-and-executable-target-selection/spec.md +3 -1
- package/docs/specs/008-adjustable-control-support-and-semantic-value-manipulation/spec.md +3 -4
- package/docs/specs/009-semantic-control-modeling-for-custom-and-composite-controls/spec.md +2 -0
- package/docs/specs/010-verification-stabilization-and-temporal-convergence/spec.md +2 -2
- package/docs/specs/011-recovery-and-replanning-for-failed-or-ambiguous-interaction-flows/spec.md +2 -2
- package/docs/specs/011.1-recovery-contract-types-and-runtime-wiring-spec/spec.md +2 -0
- package/docs/specs/012-action-trace-and-xecution-observability/spec.md +2 -3
- package/docs/specs/013-wait-and-synchronization-reliability/spec.md +19 -19
- package/docs/specs/014-actionability-resolution/spec.md +20 -18
- package/docs/specs/015-adjustable-control-support/spec.md +17 -15
- package/docs/specs/016-environment-tools/spec.md +20 -20
- package/docs/specs/018-interaction-latency-and-batched-journeys/spec.md +2 -0
- package/docs/specs/019-ui-tree-observation-performance/spec.md +3 -1
- package/docs/specs/020-conditional-actions/intent.md +5 -3
- package/docs/specs/020-conditional-actions/plan.md +8 -9
- package/docs/specs/020-conditional-actions/spec.md +4 -2
- package/docs/specs/021-app-readiness-signals/intent.md +4 -2
- package/docs/specs/021-app-readiness-signals/plan.md +7 -6
- package/docs/specs/021-app-readiness-signals/spec.md +3 -1
- package/docs/specs/022-correlated-evidence/intent.md +4 -4
- package/docs/specs/022-correlated-evidence/plan.md +10 -11
- package/docs/specs/022-correlated-evidence/spec.md +3 -1
- package/docs/specs/023-android-system-traces/device-evidence.json +38 -0
- package/docs/specs/023-android-system-traces/emulator-recheck.json +38 -0
- package/docs/specs/023-android-system-traces/intent.md +41 -0
- package/docs/specs/023-android-system-traces/plan.md +115 -0
- package/docs/specs/023-android-system-traces/spec.md +122 -0
- package/docs/specs/024-android-heap-dump-capture/intent.md +41 -0
- package/docs/specs/025-android-runtime-debugger/intent.md +41 -0
- package/docs/specs/025-android-runtime-debugger/plan.md +106 -0
- package/docs/specs/025-android-runtime-debugger/spec.md +183 -0
- package/docs/specs/026-code-structure-and-quality-gates/health-plan.md +88 -0
- package/docs/specs/026-code-structure-and-quality-gates/intent.md +47 -0
- package/docs/specs/026-code-structure-and-quality-gates/plan.md +191 -0
- package/docs/specs/026-code-structure-and-quality-gates/spec.md +138 -0
- package/docs/specs/026-code-structure-and-quality-gates/verification-final.json +200 -0
- package/docs/specs/026-code-structure-and-quality-gates/verification-thin-facade.json +154 -0
- package/docs/specs/026-code-structure-and-quality-gates/verification.json +136 -0
- package/docs/specs/027-debugger-failure-diagnostics/intent.md +43 -0
- package/docs/specs/027-debugger-failure-diagnostics/plan.md +86 -0
- package/docs/specs/027-debugger-failure-diagnostics/spec.md +133 -0
- package/docs/specs/028-compact-debugger-responses/intent.md +41 -0
- package/docs/specs/028-compact-debugger-responses/plan.md +112 -0
- package/docs/specs/028-compact-debugger-responses/spec.md +108 -0
- package/docs/specs/029-kotlin-locals-validation/report.md +119 -0
- package/docs/specs/030-android-coroutine-validation/evidence.json +475 -0
- package/docs/specs/030-android-coroutine-validation/intent.md +47 -0
- package/docs/specs/030-android-coroutine-validation/plan.md +147 -0
- package/docs/specs/030-android-coroutine-validation/report.md +240 -0
- package/docs/specs/030-android-coroutine-validation/spec.md +107 -0
- package/docs/tools/TOOLS.md +31 -16
- package/docs/tools/{evidence.md → correlated-evidence.md} +14 -3
- package/docs/tools/debugger.md +180 -0
- package/docs/tools/interact.md +42 -20
- package/docs/tools/manage.md +25 -44
- package/docs/tools/observe.md +64 -25
- package/docs/tools/system-traces.md +134 -0
- package/docs/tools/system.md +12 -5
- package/package.json +11 -8
- package/scripts/architecture-graph.mjs +69 -0
- package/scripts/check-architecture.mjs +47 -0
- package/scripts/code-quality-baseline.json +1 -0
- package/scripts/code-quality.mjs +161 -0
- package/src/contracts/execution-controls.ts +26 -0
- package/src/contracts/selector.ts +17 -0
- package/src/contracts/target.ts +19 -0
- package/src/contracts/ui.ts +94 -0
- package/src/debugger/android.ts +66 -0
- package/src/debugger/diagnostics.ts +43 -0
- package/src/debugger/guard.ts +31 -0
- package/src/debugger/java/DebuggerWorker.java +166 -0
- package/src/debugger/protocol.ts +37 -0
- package/src/debugger/sessions.ts +327 -0
- package/src/debugger/worker.ts +135 -0
- package/src/interact/adjustment/adjustment-context.ts +83 -0
- package/src/interact/adjustment/adjustment-gestures.ts +48 -0
- package/src/interact/adjustment/adjustment-plan.ts +23 -0
- package/src/interact/adjustment/adjustment-resolution.ts +104 -0
- package/src/interact/adjustment/adjustment-result.ts +34 -0
- package/src/interact/adjustment/adjustment-semantic.ts +53 -0
- package/src/interact/adjustment/adjustment-verification.ts +60 -0
- package/src/interact/adjustment/adjustment.ts +49 -0
- package/src/interact/adjustment/control-geometry.ts +120 -0
- package/src/interact/adjustment/control-values.ts +141 -0
- package/src/interact/adjustment/slider-labels.ts +84 -0
- package/src/interact/assertions/screen-assertion.ts +39 -0
- package/src/interact/assertions/state-assertion-context.ts +48 -0
- package/src/interact/assertions/state-assertion.ts +64 -0
- package/src/interact/assertions/state-comparison.ts +168 -0
- package/src/interact/assertions/visibility-assertion.ts +41 -0
- package/src/interact/conditional-action.ts +18 -12
- package/src/interact/index.ts +76 -3491
- package/src/interact/input-service.ts +11 -0
- package/src/interact/interaction-trace.ts +36 -0
- package/src/interact/model.ts +109 -0
- package/src/interact/observation.ts +7 -0
- package/src/interact/targeting/candidate-ancestor.ts +49 -0
- package/src/interact/targeting/candidate-result.ts +24 -0
- package/src/interact/targeting/candidate-scoring.ts +42 -0
- package/src/interact/targeting/element-helpers.ts +243 -0
- package/src/interact/targeting/find-element.ts +47 -0
- package/src/interact/targeting/nearby-control.ts +46 -0
- package/src/interact/targeting/resolved-elements.ts +20 -0
- package/src/interact/targeting/tap-target.ts +22 -0
- package/src/interact/targeting/target-scope.ts +121 -0
- package/src/interact/waits/legacy-log-wait.ts +30 -0
- package/src/interact/waits/legacy-wait-observations.ts +55 -0
- package/src/interact/waits/legacy-wait-state.ts +48 -0
- package/src/interact/waits/legacy-wait.ts +34 -0
- package/src/interact/waits/screen-wait.ts +37 -0
- package/src/interact/waits/ui-change-helpers.ts +184 -0
- package/src/interact/waits/ui-change-state.ts +26 -0
- package/src/interact/waits/ui-change-wait.ts +59 -0
- package/src/interact/waits/ui-wait-condition.ts +52 -0
- package/src/interact/waits/ui-wait-state.ts +55 -0
- package/src/interact/waits/ui-wait.ts +84 -0
- package/src/manage/android.ts +56 -50
- package/src/manage/index.ts +63 -93
- package/src/manage/ios/build-types.ts +14 -0
- package/src/manage/ios/build.ts +230 -0
- package/src/manage/ios/project-discovery.ts +26 -0
- package/src/manage/ios/scheme.ts +16 -0
- package/src/manage/ios.ts +65 -298
- package/src/observe/android/context.ts +1 -0
- package/src/observe/android/current-screen.ts +63 -0
- package/src/observe/android/fingerprint.ts +13 -0
- package/src/observe/android/log-stream.ts +127 -0
- package/src/observe/android/logs.ts +110 -0
- package/src/observe/android/screenshot.ts +79 -0
- package/src/observe/android/ui-tree.ts +113 -0
- package/src/observe/android.ts +17 -531
- package/src/observe/evidence.ts +130 -71
- package/src/observe/index.ts +24 -286
- package/src/observe/ios/context.ts +22 -0
- package/src/observe/ios/fingerprint.ts +12 -0
- package/src/observe/ios/log-stream.ts +103 -0
- package/src/observe/ios/logs.ts +179 -0
- package/src/observe/ios/screenshot.ts +26 -0
- package/src/observe/ios/ui-elements.ts +266 -0
- package/src/observe/ios/ui-tree.ts +143 -0
- package/src/observe/ios.ts +18 -749
- package/src/observe/logs/model.ts +11 -0
- package/src/{network → observe/network}/index.ts +2 -2
- package/src/observe/readiness-service.ts +10 -3
- package/src/observe/readiness.ts +49 -22
- package/src/observe/screenshot-encoding.ts +32 -0
- package/src/observe/snapshot-collection.ts +69 -0
- package/src/observe/snapshot-logs.ts +26 -0
- package/src/observe/snapshot-metadata.ts +51 -31
- package/src/observe/snapshot-semantic.ts +153 -0
- package/src/observe/system-trace-android.ts +133 -0
- package/src/observe/system-trace.ts +227 -0
- package/src/runtime/action-results.ts +376 -0
- package/src/runtime/clock.ts +9 -0
- package/src/server/common.ts +9 -359
- package/src/server/definitions/assert.ts +164 -0
- package/src/server/definitions/diagnostic.ts +120 -0
- package/src/server/definitions/input.ts +276 -0
- package/src/server/definitions/manage.ts +196 -0
- package/src/server/definitions/observe.ts +151 -0
- package/src/server/definitions/target.ts +140 -0
- package/src/server/definitions/wait.ts +107 -0
- package/src/server/handlers/action-execution.ts +164 -0
- package/src/server/handlers/correlation.ts +20 -0
- package/src/server/handlers/debugger-handlers.ts +39 -0
- package/src/server/handlers/debugger-response.ts +7 -0
- package/src/server/handlers/diagnostic.ts +79 -0
- package/src/server/handlers/handler-types.ts +7 -0
- package/src/server/handlers/input.ts +173 -0
- package/src/server/handlers/interact.ts +135 -0
- package/src/server/handlers/journey-assertions.ts +39 -0
- package/src/server/handlers/journey.ts +132 -0
- package/src/server/handlers/manage.ts +141 -0
- package/src/server/handlers/observe.ts +116 -0
- package/src/server/handlers/payload.ts +36 -0
- package/src/server/handlers/tool-handlers.ts +159 -0
- package/src/server/schemas/condition-schema.ts +2 -0
- package/src/server/schemas/debugger-schema.ts +43 -0
- package/src/server/{journey-schema.ts → schemas/journey-schema.ts} +8 -22
- package/src/server/schemas/system-trace-schema.ts +17 -0
- package/src/server/tool-definitions.ts +69 -1140
- package/src/server-core.ts +4 -4
- package/src/server.ts +25 -1
- package/src/system/android-status.ts +124 -0
- package/src/system/android.ts +33 -21
- package/src/system/index.ts +21 -523
- package/src/system/ios-status.ts +147 -0
- package/src/system/ios.ts +14 -10
- package/src/system/model.ts +41 -0
- package/src/system/probes.ts +114 -0
- package/src/system/status-summary.ts +47 -0
- package/src/system/unsupported-status.ts +55 -0
- package/src/types.ts +16 -106
- package/src/utils/android/commands.ts +81 -0
- package/src/utils/android/gradle.ts +132 -0
- package/src/utils/android/logs.ts +103 -0
- package/src/utils/android/ui-tree.ts +239 -0
- package/src/utils/android/utils.ts +16 -556
- package/src/utils/cli/idb/check-idb.ts +4 -4
- package/src/utils/cli/idb/idb-helper.ts +6 -8
- package/src/utils/cli/idb/install-idb.ts +15 -9
- package/src/utils/cli/ios/preflight-ios.ts +39 -31
- package/src/utils/cli/ios/run-ios-ui-tree-tap.ts +4 -4
- package/src/utils/exec.ts +2 -0
- package/src/utils/ios/utils.ts +57 -61
- package/src/utils/java.ts +71 -51
- package/src/utils/operation-budget.ts +2 -0
- package/src/utils/resolve-device.ts +11 -6
- package/src/utils/ui/index.ts +28 -28
- package/test/device/automated/debugger/coroutine-lifecycle.ts +27 -0
- package/test/device/automated/debugger/coroutine-support.ts +140 -0
- package/test/device/automated/debugger/coroutine.android.test.ts +169 -0
- package/test/device/automated/debugger/kotlin.android.test.ts +127 -0
- package/test/device/automated/debugger/runtime.android.test.ts +219 -0
- package/test/device/automated/interact/interaction.android.test.ts +56 -0
- package/test/device/automated/observe/system-trace.smoke.ts +104 -0
- package/test/device/manual/interact/modul8_selector_tap.manual.ts +1 -1
- package/test/fixtures/contracts/tool-definitions.json +3536 -0
- package/test/fixtures/coroutine-app/MainActivity.kt +100 -0
- package/test/fixtures/coroutine-app/README.md +48 -0
- package/test/fixtures/coroutine-app/build.ts +35 -0
- package/test/fixtures/debugger-app/MainActivity.java +67 -0
- package/test/fixtures/debugger-app/MainActivity.kt +32 -0
- package/test/fixtures/debugger-app/NoLocals.java +7 -0
- package/test/fixtures/debugger-app/README.md +65 -0
- package/test/fixtures/debugger-app/build.ts +39 -0
- package/test/fixtures/latency-journey-app/README.md +2 -0
- package/test/fixtures/readiness-app/README.md +2 -0
- package/test/helpers/perfetto.ts +59 -0
- package/test/unit/debugger/coroutine-lifecycle.test.ts +43 -0
- package/test/unit/debugger/diagnostic-sessions.test.ts +67 -0
- package/test/unit/debugger/diagnostics.test.ts +80 -0
- package/test/unit/debugger/guards.test.ts +50 -0
- package/test/unit/debugger/native.test.ts +33 -0
- package/test/unit/debugger/preparation.test.ts +32 -0
- package/test/unit/debugger/protocol.test.ts +15 -0
- package/test/unit/debugger/sessions.test.ts +83 -0
- package/test/unit/interact/adjustment_service.test.ts +42 -0
- package/test/unit/interact/adjustment_verification.test.ts +35 -0
- package/test/unit/interact/clock_dependencies.test.ts +63 -0
- package/test/unit/interact/element_helpers.test.ts +19 -0
- package/test/unit/interact/legacy_wait_service.test.ts +17 -0
- package/test/unit/interact/state_comparison.test.ts +20 -0
- package/test/unit/interact/target_scope.test.ts +35 -0
- package/test/unit/network/get_network_activity.test.ts +1 -1
- package/test/unit/observe/readiness.test.ts +3 -1
- package/test/unit/observe/screenshot-encoding.test.ts +30 -0
- package/test/unit/observe/snapshot-logs.test.ts +14 -0
- package/test/unit/observe/system-trace-android.test.ts +52 -0
- package/test/unit/observe/system-trace.test.ts +166 -0
- package/test/unit/quality/architecture.test.ts +60 -0
- package/test/unit/quality/code-quality.test.ts +58 -0
- package/test/unit/quality/interaction_boundaries.test.ts +24 -0
- package/test/unit/server/conditional_actions.test.ts +1 -1
- package/test/unit/server/conditional_schema.test.ts +2 -2
- package/test/unit/server/debugger-response.test.ts +63 -0
- package/test/unit/server/system-trace.test.ts +64 -0
- package/test/unit/server/tool_definitions_parity.test.ts +8 -0
- package/dist/server/tool-handlers.js +0 -893
- package/docs/ROADMAP.md +0 -646
- package/docs/benchmarks/ui-tree-performance/bridge-probe.json +0 -29
- package/docs/benchmarks/ui-tree-performance/initial-baseline.json +0 -59560
- package/docs/specs/baseline-spec-v0.md +0 -312
- package/docs/specs/mcp-tooling-spec-v1.md +0 -446
- package/src/server/tool-handlers.ts +0 -923
- /package/dist/{server/condition-schema.js → contracts/condition.js} +0 -0
- /package/dist/server/{readiness-schema.js → schemas/readiness-schema.js} +0 -0
- /package/src/{server/condition-schema.ts → contracts/condition.ts} +0 -0
- /package/src/server/{readiness-schema.ts → schemas/readiness-schema.ts} +0 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { AndroidTraceNative } from '../../../src/observe/system-trace-android.js'
|
|
3
|
+
import type { execCmd } from '../../../src/utils/exec.js'
|
|
4
|
+
|
|
5
|
+
async function run() {
|
|
6
|
+
const calls: Array<{ args: string[]; timeout: number | undefined }> = []
|
|
7
|
+
let failure = '', processes = ''
|
|
8
|
+
const execute: typeof execCmd = async (_cmd, args, options = {}) => {
|
|
9
|
+
calls.push({ args, timeout: options.timeout })
|
|
10
|
+
assert.equal(args[0], '-s'); assert.equal(args[1], 'serial with spaces')
|
|
11
|
+
if (failure && args.join(' ').includes(failure)) return { exitCode: null, stdout: '', stderr: 'ACTION_TIMEOUT' }
|
|
12
|
+
let out = ''
|
|
13
|
+
if (args.includes('--help')) return { exitCode: 1, stdout: '', stderr: 'Usage: perfetto --background-wait --config' }
|
|
14
|
+
if (args.includes('getprop')) out = '36'
|
|
15
|
+
if (args.includes('--list_categories')) out = ' sched - Scheduling\n input - Input\n gfx - Graphics'
|
|
16
|
+
if (args.join(' ').includes('YQ==')) out = 'a'
|
|
17
|
+
if (args.join(' ').includes('for pid')) out = processes
|
|
18
|
+
if (args.includes('stat')) out = '1024'
|
|
19
|
+
return { exitCode: 0, stdout: out, stderr: '' }
|
|
20
|
+
}
|
|
21
|
+
const native = new AndroidTraceNative(execute)
|
|
22
|
+
const remote = '/data/misc/perfetto-traces/mcp-62a51510-c303-4a1b-af0e-f97cc976d740.perfetto-trace'
|
|
23
|
+
assert.equal((await native.capabilities('serial with spaces')).supported, true)
|
|
24
|
+
for (const probe of ['--help', '--query', 'getprop', '--list_categories', 'test -w', 'YQ==']) {
|
|
25
|
+
failure = probe
|
|
26
|
+
assert.equal((await native.capabilities('serial with spaces')).supported, false, probe)
|
|
27
|
+
}
|
|
28
|
+
failure = ''
|
|
29
|
+
await native.start('serial with spaces', remote, 1001, ['sched', 'input'])
|
|
30
|
+
assert.equal(calls.at(-1)!.timeout, 35000)
|
|
31
|
+
assert.ok(calls.at(-1)!.args.join(' ').includes('--background-wait -c -'))
|
|
32
|
+
assert.equal(await native.find('serial with spaces', remote), null)
|
|
33
|
+
processes = '123 456'
|
|
34
|
+
assert.deepEqual(await native.find('serial with spaces', remote), { pid: 123, startTime: '456' })
|
|
35
|
+
assert.ok(calls.at(-1)!.args.join(' ').includes('stat=$(cat "$p/stat" 2>/dev/null) || { [ ! -d "$p" ] && continue; exit 1; }'), 'Unreadable process identity must fail closed')
|
|
36
|
+
processes = '123 456\n999 888'
|
|
37
|
+
await assert.rejects(() => native.find('serial with spaces', remote), /IDENTITY_UNAVAILABLE/)
|
|
38
|
+
const previous = calls.length
|
|
39
|
+
await assert.rejects(() => native.stop('serial with spaces', remote, { pid: 0, startTime: '4' }))
|
|
40
|
+
await assert.rejects(() => native.start('serial with spaces', '/tmp/other; killall perfetto', 1000, []))
|
|
41
|
+
assert.equal(calls.length, previous)
|
|
42
|
+
await native.stop('serial with spaces', remote, { pid: 123, startTime: '456' })
|
|
43
|
+
const signal = calls.at(-1)!.args.join(' ')
|
|
44
|
+
assert.ok(signal.indexOf('awk') < signal.indexOf('kill -TERM'))
|
|
45
|
+
assert.ok(signal.indexOf('grep -Fx') < signal.indexOf('kill -TERM'))
|
|
46
|
+
assert.ok(!signal.includes('killall'))
|
|
47
|
+
failure = 'pull'
|
|
48
|
+
await assert.rejects(() => native.pull('serial with spaces', remote, '/tmp/fixture'), /ACTION_TIMEOUT/)
|
|
49
|
+
assert.equal(calls.at(-1)!.timeout, 30000)
|
|
50
|
+
console.log('Android Perfetto adapter probes, targeting, guards and deadlines: PASS')
|
|
51
|
+
}
|
|
52
|
+
run().catch(error => { console.error(error); process.exitCode = 1 })
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { promises as fs } from 'node:fs'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import { SystemTraceSessions, type TraceClock } from '../../../src/observe/system-trace.js'
|
|
5
|
+
import { TRACE_MAX_BYTES, traceConfig, type TraceNative, type TraceCapabilities, type TraceProcess } from '../../../src/observe/system-trace-android.js'
|
|
6
|
+
|
|
7
|
+
function fixture() {
|
|
8
|
+
let now = 1000000
|
|
9
|
+
let failStart = '', failStop = '', failPull = '', failRemove = '', failFind = ''
|
|
10
|
+
let supported = true, empty = false, oversized = false, stuck = false
|
|
11
|
+
let starts = 0, stops = 0, pulls = 0, removes = 0
|
|
12
|
+
const processes = new Map<string, TraceProcess>()
|
|
13
|
+
const pending = new Map<any, () => void>()
|
|
14
|
+
const time: TraceClock = { wall: () => now, mono: () => now, sleep: async ms => { now += ms },
|
|
15
|
+
schedule(fn) { const id = {} as any; pending.set(id, fn); return id }, clear: id => { pending.delete(id) } }
|
|
16
|
+
const native: TraceNative = {
|
|
17
|
+
async capabilities(device) { return { supported, backend: 'perfetto', device: { platform: 'android', id: device }, prerequisites: {}, reasons: [], enabled_sources: ['sched', 'input'], unavailable_categories: [], limitations: [] } as TraceCapabilities },
|
|
18
|
+
async start(_d, remote) { starts++; processes.set(remote, { pid: starts + 100, startTime: String(starts) }); if (failStart) throw new Error(failStart) },
|
|
19
|
+
async find(_d, remote) { if (failFind) throw new Error(failFind); return processes.get(remote) ?? null },
|
|
20
|
+
async stop(_d, remote) { stops++; if (failStop) throw new Error(failStop); if (!stuck) processes.delete(remote) },
|
|
21
|
+
async pull(_d, _r, local) { pulls++; await fs.writeFile(local, empty ? '' : 'perfetto-fixture'); if (oversized) await fs.truncate(local, TRACE_MAX_BYTES + 1); if (failPull) throw new Error(failPull) },
|
|
22
|
+
async remove() { removes++; if (failRemove) throw new Error(failRemove) }
|
|
23
|
+
}
|
|
24
|
+
const sessions = new SystemTraceSessions(native, time)
|
|
25
|
+
const files: string[] = []
|
|
26
|
+
return { sessions, native, time, processes, pending, files,
|
|
27
|
+
tick(ms: number) { now += ms }, get counts() { return { starts, stops, pulls, removes } },
|
|
28
|
+
flags(v: { start?: string; stop?: string; pull?: string; remove?: string; find?: string; supported?: boolean; empty?: boolean; oversized?: boolean; stuck?: boolean }) {
|
|
29
|
+
failStart = v.start ?? ''; failStop = v.stop ?? ''; failPull = v.pull ?? ''; failRemove = v.remove ?? ''; failFind = v.find ?? ''
|
|
30
|
+
supported = v.supported ?? true; empty = v.empty ?? false; oversized = v.oversized ?? false; stuck = v.stuck ?? false
|
|
31
|
+
},
|
|
32
|
+
async cleanup() { await sessions.shutdown(); await Promise.all(files.map(file => fs.rm(path.dirname(file), { recursive: true, force: true }))) }
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async function run() {
|
|
36
|
+
const f = fixture()
|
|
37
|
+
try {
|
|
38
|
+
const a = await f.sessions.start('a', 1001)
|
|
39
|
+
assert.equal(a.state, 'recording')
|
|
40
|
+
assert.equal(a.capture_completed_at_ms, null)
|
|
41
|
+
await assert.rejects(() => f.sessions.start('a'), /TRACE_BUSY/)
|
|
42
|
+
const b = await f.sessions.start('b')
|
|
43
|
+
const end = await f.sessions.link(a.trace_id, { platform: 'android', id: 'a' }, 'journey-a')
|
|
44
|
+
await assert.rejects(() => f.sessions.link(a.trace_id, { platform: 'android', id: 'b' }, 'wrong'), /TRACE_NOT_FOUND/)
|
|
45
|
+
await assert.rejects(() => f.sessions.link(a.trace_id, { platform: 'ios', id: 'a' }, 'wrong'), /TRACE_NOT_FOUND/)
|
|
46
|
+
const [stop, again] = await Promise.all([f.sessions.control(a.trace_id, 'stop'), f.sessions.control(a.trace_id, 'stop')])
|
|
47
|
+
assert.equal(stop.state, 'completed'); assert.deepEqual(stop, again)
|
|
48
|
+
f.files.push(stop.artifact!.path)
|
|
49
|
+
assert.equal((await end()).coverage, 'partial') // another call stopped capture during the linked journey
|
|
50
|
+
assert.deepEqual(stop.correlation_ids, ['journey-a'])
|
|
51
|
+
assert.equal((await fs.stat(stop.artifact!.path)).mode & 0o777, 0o600)
|
|
52
|
+
assert.equal((await f.sessions.control(a.trace_id, 'cancel')).artifact?.path, stop.artifact?.path)
|
|
53
|
+
assert.equal(f.counts.pulls, 1)
|
|
54
|
+
assert.equal((await f.sessions.control(b.trace_id, 'cancel')).state, 'cancelled')
|
|
55
|
+
assert.equal(f.counts.pulls, 1)
|
|
56
|
+
const expired = await f.sessions.start('c', 1000)
|
|
57
|
+
f.tick(1001)
|
|
58
|
+
const duration = await f.sessions.control(expired.trace_id, 'status')
|
|
59
|
+
assert.equal(duration.completion_reason, 'duration_limit'); f.files.push(duration.artifact!.path)
|
|
60
|
+
f.tick(600001)
|
|
61
|
+
await assert.rejects(() => f.sessions.control(a.trace_id, 'status'), /TRACE_NOT_FOUND/)
|
|
62
|
+
await assert.rejects(() => f.sessions.control('previous-server', 'stop'), /TRACE_NOT_FOUND/)
|
|
63
|
+
} finally { await f.cleanup() }
|
|
64
|
+
|
|
65
|
+
for (const failure of ['start', 'stop', 'pull', 'remove', 'find', 'empty', 'oversized'] as const) {
|
|
66
|
+
const f = fixture()
|
|
67
|
+
try {
|
|
68
|
+
if (failure === 'start') f.flags({ start: 'ACTION_TIMEOUT' })
|
|
69
|
+
const a = await f.sessions.start('a')
|
|
70
|
+
if (failure === 'start') {
|
|
71
|
+
assert.equal(a.reason, 'ACTION_TIMEOUT'); assert.equal(a.state, 'failed'); assert.equal(a.cleanup.status, 'complete')
|
|
72
|
+
assert.equal(f.processes.size, 0); continue
|
|
73
|
+
}
|
|
74
|
+
f.flags(failure === 'empty' || failure === 'oversized' ? { [failure]: true } : { [failure]: 'ACTION_TIMEOUT' })
|
|
75
|
+
const stop = await f.sessions.control(a.trace_id, 'stop')
|
|
76
|
+
assert.equal(stop.state, 'failed', failure); assert.equal(stop.cleanup.status, 'failed', failure)
|
|
77
|
+
if (stop.artifact) f.files.push(stop.artifact.path)
|
|
78
|
+
if (failure === 'pull' || failure === 'empty' || failure === 'oversized') assert.equal(stop.artifact, null)
|
|
79
|
+
f.flags({})
|
|
80
|
+
const retry = await f.sessions.control(a.trace_id, 'stop')
|
|
81
|
+
assert.equal(retry.state, 'completed', failure); assert.equal(retry.cleanup.status, 'complete')
|
|
82
|
+
if (!f.files.includes(retry.artifact!.path)) f.files.push(retry.artifact!.path)
|
|
83
|
+
} finally { await f.cleanup() }
|
|
84
|
+
}
|
|
85
|
+
const reuse = fixture()
|
|
86
|
+
try {
|
|
87
|
+
const a = await reuse.sessions.start('a')
|
|
88
|
+
const [remote, process] = [...reuse.processes.entries()][0]
|
|
89
|
+
reuse.processes.set(remote, { ...process, startTime: '999999' })
|
|
90
|
+
const result = await reuse.sessions.control(a.trace_id, 'stop')
|
|
91
|
+
assert.equal(result.reason, 'TRACE_STOP_FAILED'); assert.equal(reuse.counts.stops, 0)
|
|
92
|
+
reuse.processes.delete(remote)
|
|
93
|
+
assert.equal((await reuse.sessions.control(a.trace_id, 'cancel')).state, 'cancelled')
|
|
94
|
+
} finally { await reuse.cleanup() }
|
|
95
|
+
const cap = fixture()
|
|
96
|
+
try {
|
|
97
|
+
cap.flags({ supported: false })
|
|
98
|
+
assert.equal((await cap.sessions.start('a')).reason, 'TRACE_UNSUPPORTED'); assert.equal(cap.counts.starts, 0)
|
|
99
|
+
cap.flags({})
|
|
100
|
+
await Promise.all(['a', 'b', 'c', 'd'].map(d => cap.sessions.start(d)))
|
|
101
|
+
await assert.rejects(() => cap.sessions.start('e'), /TRACE_BUSY/)
|
|
102
|
+
await cap.sessions.shutdown(); assert.equal(cap.processes.size, 0)
|
|
103
|
+
await assert.rejects(() => cap.sessions.start('e'), /TRACE_BUSY/)
|
|
104
|
+
} finally { await cap.cleanup() }
|
|
105
|
+
const timer = fixture()
|
|
106
|
+
try {
|
|
107
|
+
const a = await timer.sessions.start('timer', 1000)
|
|
108
|
+
timer.tick(1000)
|
|
109
|
+
for (const callback of timer.pending.values()) callback()
|
|
110
|
+
const ended = await timer.sessions.control(a.trace_id, 'status')
|
|
111
|
+
assert.equal(ended.state, 'completed'); assert.equal(ended.completion_reason, 'duration_limit')
|
|
112
|
+
timer.files.push(ended.artifact!.path)
|
|
113
|
+
} finally { await timer.cleanup() }
|
|
114
|
+
const stuck = fixture()
|
|
115
|
+
try {
|
|
116
|
+
const a = await stuck.sessions.start('stuck')
|
|
117
|
+
stuck.flags({ stuck: true })
|
|
118
|
+
const failed = await stuck.sessions.control(a.trace_id, 'stop')
|
|
119
|
+
assert.equal(failed.reason, 'ACTION_TIMEOUT'); assert.equal(failed.artifact, null)
|
|
120
|
+
assert.equal(stuck.counts.pulls, 0)
|
|
121
|
+
stuck.flags({})
|
|
122
|
+
assert.equal((await stuck.sessions.control(a.trace_id, 'cancel')).cleanup.status, 'complete')
|
|
123
|
+
} finally { await stuck.cleanup() }
|
|
124
|
+
const natural = fixture()
|
|
125
|
+
try {
|
|
126
|
+
const a = await natural.sessions.start('natural')
|
|
127
|
+
natural.native.stop = async (_device, remote) => { natural.processes.delete(remote); throw new Error('PROCESS_ALREADY_EXITED') }
|
|
128
|
+
assert.equal((await natural.sessions.control(a.trace_id, 'cancel')).state, 'cancelled')
|
|
129
|
+
} finally { await natural.cleanup() }
|
|
130
|
+
const interrupted = fixture()
|
|
131
|
+
try {
|
|
132
|
+
const a = await interrupted.sessions.start('journey')
|
|
133
|
+
const finish = await interrupted.sessions.link(a.trace_id, { platform: 'android', id: 'journey' }, 'j')
|
|
134
|
+
interrupted.flags({ find: 'DEVICE_DISCONNECTED' })
|
|
135
|
+
assert.equal((await finish()).coverage, 'partial')
|
|
136
|
+
interrupted.flags({})
|
|
137
|
+
} finally { await interrupted.cleanup() }
|
|
138
|
+
const racing = fixture()
|
|
139
|
+
let release!: () => void
|
|
140
|
+
const gate = new Promise<void>(resolve => { release = resolve })
|
|
141
|
+
const originalCapabilities = racing.native.capabilities
|
|
142
|
+
racing.native.capabilities = async device => { await gate; return originalCapabilities(device) }
|
|
143
|
+
const starting = racing.sessions.start('race')
|
|
144
|
+
const closing = racing.sessions.shutdown()
|
|
145
|
+
release()
|
|
146
|
+
assert.equal((await starting).reason, 'TRACE_BUSY')
|
|
147
|
+
assert.deepEqual(await closing, [])
|
|
148
|
+
assert.equal(racing.counts.starts, 0)
|
|
149
|
+
const retention = fixture()
|
|
150
|
+
try {
|
|
151
|
+
const first = await retention.sessions.start('retention')
|
|
152
|
+
await retention.sessions.control(first.trace_id, 'cancel')
|
|
153
|
+
for (let n = 0; n < 100; n++) {
|
|
154
|
+
const next = await retention.sessions.start('retention')
|
|
155
|
+
await retention.sessions.control(next.trace_id, 'cancel')
|
|
156
|
+
}
|
|
157
|
+
await assert.rejects(() => retention.sessions.control(first.trace_id, 'status'), /TRACE_NOT_FOUND/)
|
|
158
|
+
} finally { await retention.cleanup() }
|
|
159
|
+
// Wire bytes decode independently to protect actual proto field numbers/limits.
|
|
160
|
+
const config = traceConfig(1001, ['sched', 'input'])
|
|
161
|
+
assert.ok(config.includes(Buffer.from('linux.ftrace')))
|
|
162
|
+
assert.ok(config.includes(Buffer.from([0x18, 0xe9, 0x07]))) // TraceConfig.duration_ms = 3
|
|
163
|
+
assert.ok(config.includes(Buffer.from([0x50, 0x80, 0x80, 0x80, 0x10]))) // max_file_size_bytes = 10
|
|
164
|
+
console.log('system trace session lifecycle: PASS')
|
|
165
|
+
}
|
|
166
|
+
run().catch(error => { console.error(error); process.exitCode = 1 })
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { mkdtemp, mkdir, writeFile, rm } from 'node:fs/promises'
|
|
3
|
+
import { tmpdir } from 'node:os'
|
|
4
|
+
import path from 'node:path'
|
|
5
|
+
import { spawnSync } from 'node:child_process'
|
|
6
|
+
import { architectureGraph } from '../../../scripts/architecture-graph.mjs'
|
|
7
|
+
import { checkArchitecture } from '../../../scripts/check-architecture.mjs'
|
|
8
|
+
|
|
9
|
+
const root = await mkdtemp(path.join(tmpdir(), 'mcp-architecture-'))
|
|
10
|
+
try {
|
|
11
|
+
const files: Record<string, string> = {
|
|
12
|
+
'package.json': JSON.stringify({ type: 'module' }),
|
|
13
|
+
'tsconfig.json': JSON.stringify({ compilerOptions: {
|
|
14
|
+
module: 'NodeNext', moduleResolution: 'NodeNext', target: 'ES2022',
|
|
15
|
+
paths: { '@server/*': ['./src/server/*'] }
|
|
16
|
+
}, include: ['src/**/*.ts'] }),
|
|
17
|
+
'src/server/entry.ts': 'export const value = 1',
|
|
18
|
+
'src/contracts/bad.ts': "export { value } from '@server/entry.js'",
|
|
19
|
+
'src/interact/entry.ts': "export const load = () => import('../server/entry.js')",
|
|
20
|
+
'src/interact/adjustment/control-values.ts': "import { spawn } from 'node:child_process'; export const native = spawn",
|
|
21
|
+
'src/cycle/a.ts': "export { b } from './b.js'; export const a = 1",
|
|
22
|
+
'src/cycle/b.ts': "export const b = () => import('./a.js')",
|
|
23
|
+
'src/type-cycle/a.ts': "import type { B } from './b.js'; export interface A { b: B }",
|
|
24
|
+
'src/type-cycle/b.ts': "import { type A } from './a.js'; export interface B { a: A }",
|
|
25
|
+
'src/import-equals.ts': "import entry = require('./server/entry.js'); export { entry }",
|
|
26
|
+
'src/require.ts': "export const load = () => require('./server/entry.js')"
|
|
27
|
+
}
|
|
28
|
+
for (const [file, code] of Object.entries(files)) {
|
|
29
|
+
const target = path.join(root, file)
|
|
30
|
+
await mkdir(path.dirname(target), { recursive: true })
|
|
31
|
+
await writeFile(target, code)
|
|
32
|
+
}
|
|
33
|
+
const config = path.join(root, 'tsconfig.json')
|
|
34
|
+
const graph = await architectureGraph(config)
|
|
35
|
+
const edge = (source: string, target: string) => graph.find(item => item.source === source && item.target === target)
|
|
36
|
+
assert(edge('src/contracts/bad.ts', 'src/server/entry.ts'), 'Resolve aliased re-exports to .ts sources')
|
|
37
|
+
assert(edge('src/interact/entry.ts', 'src/server/entry.ts'), 'Resolve literal dynamic .js imports')
|
|
38
|
+
assert(edge('src/require.ts', 'src/server/entry.ts'), 'Resolve literal require calls')
|
|
39
|
+
assert(edge('src/interact/adjustment/control-values.ts', 'node:child_process'), 'Include Node builtins')
|
|
40
|
+
assert.deepEqual(edge('src/type-cycle/b.ts', 'src/type-cycle/a.ts')?.importKinds, ['type'])
|
|
41
|
+
assert(edge('src/import-equals.ts', 'src/server/entry.ts'), 'Resolve import-equals require syntax')
|
|
42
|
+
const rejected = spawnSync(process.execPath, ['scripts/check-architecture.mjs', config], { encoding: 'utf8' })
|
|
43
|
+
assert.equal(rejected.status, 1, rejected.stderr)
|
|
44
|
+
assert(rejected.stderr.includes('Production runtime dependencies must be cycle-free'))
|
|
45
|
+
const results = checkArchitecture(graph)
|
|
46
|
+
assert(results.every(result => result.violations.length > 0), 'Every rule must reject its deliberately broken fixture')
|
|
47
|
+
const cycles = JSON.stringify(results.at(-1)?.violations)
|
|
48
|
+
assert(cycles.includes('src/cycle/a.ts'), 'Catch a cycle through a re-export and dynamic import')
|
|
49
|
+
assert(!cycles.includes('src/type-cycle'), 'Type-only cycles are not runtime cycles')
|
|
50
|
+
assert(checkArchitecture([]).every(result => result.violations.length > 0), 'An empty graph must fail closed')
|
|
51
|
+
|
|
52
|
+
await writeFile(path.join(root, 'src/nonliteral.ts'), 'export const load = (name: string) => import(name)')
|
|
53
|
+
await assert.rejects(architectureGraph(config), /Nonliteral dependency/)
|
|
54
|
+
await rm(path.join(root, 'src/nonliteral.ts'))
|
|
55
|
+
await writeFile(path.join(root, 'src/unresolved.ts'), "import './missing.js'")
|
|
56
|
+
await assert.rejects(architectureGraph(config), /Unresolved dependency/)
|
|
57
|
+
console.log('PASS architecture negative controls: NodeNext, aliases, re-exports, dynamic/require imports, pure boundaries, runtime cycles, type-only cycles, empty and unresolved graphs')
|
|
58
|
+
} finally {
|
|
59
|
+
await rm(root, { recursive: true, force: true })
|
|
60
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { measure, compare } from '../../../scripts/code-quality.mjs'
|
|
3
|
+
|
|
4
|
+
const file = 'src/interact/quality-fixture.ts'
|
|
5
|
+
const inspect = (code: string) => measure(file, code)
|
|
6
|
+
const has = (findings: Awaited<ReturnType<typeof inspect>>, rule: string) => findings.some(f => f.rule === rule)
|
|
7
|
+
const baseline = (findings: Awaited<ReturnType<typeof inspect>>) => findings.map(({ line: _line, ...entry }) => ({ ...entry, reason: 'Test legacy exception' }))
|
|
8
|
+
|
|
9
|
+
async function main() {
|
|
10
|
+
assert.deepEqual(await inspect('export const twice = (value: number) => value * 2'), [])
|
|
11
|
+
const hugeClass = await inspect(`export class Huge {\n${Array.from({ length: 301 }, (_, i) => `field${i} = ${i}`).join('\n')}\n}`)
|
|
12
|
+
assert(has(hugeClass, 'class-lines'))
|
|
13
|
+
const hugeFunction = await inspect(`export function work() {\n${Array.from({ length: 81 }, (_, i) => `console.log(${i})`).join('\n')}\n}`)
|
|
14
|
+
assert(has(hugeFunction, 'max-lines-per-function'))
|
|
15
|
+
assert(has(await inspect(`export const work = () => {\n${'console.log(1)\n'.repeat(81)}}`), 'max-lines-per-function'))
|
|
16
|
+
const hugeFile = await inspect(Array.from({ length: 501 }, (_, i) => `export const value${i} = ${i}`).join('\n'))
|
|
17
|
+
assert(has(hugeFile, 'max-lines'))
|
|
18
|
+
assert(!has(await inspect(`// A comment\n${'\n// comment\n'.repeat(600)}export const x = 1`), 'max-lines'))
|
|
19
|
+
const complex = await inspect(`export function decide(value: number) { ${'if(value) console.log(value);'.repeat(21)} }`)
|
|
20
|
+
assert(has(complex, 'complexity'))
|
|
21
|
+
assert(has(await inspect('export function nested(x: boolean) { if(x){if(x){if(x){if(x){if(x){console.log(x)}}}}} }'), 'max-depth'))
|
|
22
|
+
assert(has(await inspect("import { value } from '../server/tool-handlers.js'"), 'import-boundary'))
|
|
23
|
+
assert(has(await inspect("export { value } from '../server/common.js'"), 'import-boundary'))
|
|
24
|
+
assert(has(await inspect("type Args = import('../server/common.js').ToolCallArgs"), 'import-boundary'))
|
|
25
|
+
assert(has(await inspect("const transport = require('../server/common.js')"), 'import-boundary'))
|
|
26
|
+
assert(has(await inspect('/* eslint complexity: \"off\" */\nexport const value = 1'), 'inline-suppression'))
|
|
27
|
+
assert(!has(await inspect("import type { Value } from '../contracts/value.js'"), 'import-boundary'))
|
|
28
|
+
assert(has(await inspect('// eslint-disable\nexport const x = 1'), 'inline-suppression'))
|
|
29
|
+
assert(has(await inspect('// eslint-disable-next-line complexity\nexport function f() {}'), 'inline-suppression'))
|
|
30
|
+
const any = await inspect('export function legacy(value: any) { return value }')
|
|
31
|
+
assert(has(any, 'explicit-any'))
|
|
32
|
+
assert.deepEqual(compare(any, baseline(any)), [])
|
|
33
|
+
assert(compare(any, []).some(e => e.includes('explicit-any')))
|
|
34
|
+
const grown = await inspect('export function legacy(value: any): any { return value }')
|
|
35
|
+
assert(compare(grown, baseline(any)).some(e => e.includes('explicit-any')))
|
|
36
|
+
const renamed = await inspect('export function replacement(value: any) { return value }')
|
|
37
|
+
assert(compare(renamed, baseline(any)).length >= 2, 'renames do not inherit debt')
|
|
38
|
+
const moved = any.map(f => ({ ...f, file: 'src/interact/moved.ts' }))
|
|
39
|
+
assert(compare(moved, baseline(any)).length >= 2, 'moves do not inherit debt')
|
|
40
|
+
assert(compare([], baseline(any)).some(e => e.includes('obsolete')))
|
|
41
|
+
assert(compare(any, baseline(grown)).some(e => e.includes('Lower baseline')))
|
|
42
|
+
assert(compare(any, [...baseline(any), ...baseline(any)]).some(e => e.includes('Duplicate')))
|
|
43
|
+
assert(compare(any, [{ ...baseline(any)[0], reason: '' }]).some(e => e.includes('Invalid')))
|
|
44
|
+
const collision = await inspect('function same(x: number) {return x} function same(x: number) {return x}')
|
|
45
|
+
assert(has(collision, 'duplicate-symbol'))
|
|
46
|
+
assert(compare(collision, baseline(collision)).some(e => e.includes('duplicate-symbol')), 'identity collisions cannot be baseline exceptions')
|
|
47
|
+
const suppressed = await inspect('// eslint-disable max-lines-per-function\n'+`function huge(){\n${'console.log(1)\n'.repeat(81)}}`)
|
|
48
|
+
assert(has(suppressed, 'max-lines-per-function'), 'inline directives cannot hide metrics')
|
|
49
|
+
const typeDebt = await inspect('type First = any; type Second = number')
|
|
50
|
+
const shiftedTypeDebt = await inspect('type First = number; type Second = any')
|
|
51
|
+
assert(compare(shiftedTypeDebt, baseline(typeDebt)).length >= 2, 'unrelated type declarations cannot offset each other')
|
|
52
|
+
const java = await measure('src/debugger/java/Fixture.java', 'int field;\n'.repeat(501))
|
|
53
|
+
assert(has(java, 'max-lines'))
|
|
54
|
+
assert.deepEqual(await measure('src/debugger/java/Fixture.java', '// comment\n'.repeat(501)), [])
|
|
55
|
+
assert(!has(await inspect('class Accessor { get value() { return 1 } set value(value: number) { console.log(value) } }'), 'duplicate-symbol'))
|
|
56
|
+
console.log('PASS structural metrics, debt ratchet, boundaries and suppression protection')
|
|
57
|
+
}
|
|
58
|
+
main().catch(error => { console.error(error); process.exitCode = 1 })
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { readdir, readFile } from 'node:fs/promises'
|
|
3
|
+
import { measure } from '../../../scripts/code-quality.mjs'
|
|
4
|
+
|
|
5
|
+
// Refactored interaction services must meet defaults even if someone adds a legacy allowance.
|
|
6
|
+
const legacy = new Set(['android.ts', 'ios.ts', 'conditional-action.ts', 'classify.ts'])
|
|
7
|
+
async function sourceFiles(directory = ''): Promise<string[]> {
|
|
8
|
+
const entries = await readdir(`src/interact/${directory}`, { withFileTypes: true })
|
|
9
|
+
const paths: string[] = []
|
|
10
|
+
for (const entry of entries) {
|
|
11
|
+
const relative = directory ? `${directory}/${entry.name}` : entry.name
|
|
12
|
+
if (entry.isDirectory()) paths.push(...await sourceFiles(relative))
|
|
13
|
+
else if (relative.endsWith('.ts') && !legacy.has(relative)) paths.push(relative)
|
|
14
|
+
}
|
|
15
|
+
return paths
|
|
16
|
+
}
|
|
17
|
+
const files = await sourceFiles()
|
|
18
|
+
const baseline = JSON.parse(await readFile('scripts/code-quality-baseline.json', 'utf8')) as Array<{ file: string }>
|
|
19
|
+
for (const file of files) {
|
|
20
|
+
const path = `src/interact/${file}`
|
|
21
|
+
assert.deepEqual(await measure(path, await readFile(path, 'utf8')), [], `${path} must meet default structural and typing limits`)
|
|
22
|
+
assert(!baseline.some(entry => entry.file === path), `${path} cannot inherit legacy exceptions`)
|
|
23
|
+
}
|
|
24
|
+
console.log(`PASS default quality limits and no legacy exceptions across ${files.length} interaction modules`)
|
|
@@ -4,7 +4,7 @@ import { tmpdir } from 'node:os'
|
|
|
4
4
|
import path from 'node:path'
|
|
5
5
|
import { handleToolCall, toolDefinitions } from '../../../src/server-core.js'
|
|
6
6
|
import { ToolsObserve } from '../../../src/observe/index.js'
|
|
7
|
-
import { ToolsNetwork } from '../../../src/network/index.js'
|
|
7
|
+
import { ToolsNetwork } from '../../../src/observe/network/index.js'
|
|
8
8
|
import { deriveSnapshotMetadata, getSnapshotSessionId, resetSnapshotMetadataForTests } from '../../../src/observe/snapshot-metadata.js'
|
|
9
9
|
import { _setDeviceListersForTests, _resetDeviceListersForTests } from '../../../src/utils/resolve-device.js'
|
|
10
10
|
import { runWithBudget } from '../../../src/utils/operation-budget.js'
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import assert from 'node:assert/strict'
|
|
2
2
|
import { AjvJsonSchemaValidator } from '@modelcontextprotocol/sdk/validation/ajv'
|
|
3
3
|
import { toolDefinitions } from '../../../src/server/tool-definitions.js'
|
|
4
|
-
import { conditionalTools, validateExpectedState } from '../../../src/server/condition-schema.js'
|
|
5
|
-
import { journeySchema } from '../../../src/server/journey-schema.js'
|
|
4
|
+
import { conditionalTools, validateExpectedState } from '../../../src/server/schemas/condition-schema.js'
|
|
5
|
+
import { journeySchema } from '../../../src/server/schemas/journey-schema.js'
|
|
6
6
|
|
|
7
7
|
const provider = new AjvJsonSchemaValidator()
|
|
8
8
|
const expectedState = { snapshotSessionId: 'session-fixture', snapshotRevision: 1 }
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { debuggerResponse } from '../../../src/server/handlers/debugger-response.js'
|
|
3
|
+
import { handleDebugger } from '../../../src/server/handlers/debugger-handlers.js'
|
|
4
|
+
import { debuggerContracts, debuggerTools } from '../../../src/server/schemas/debugger-schema.js'
|
|
5
|
+
import { debuggerSessions } from '../../../src/debugger/sessions.js'
|
|
6
|
+
import { DebuggerError } from '../../../src/debugger/worker.js'
|
|
7
|
+
import { diagnostic } from '../../../src/debugger/diagnostics.js'
|
|
8
|
+
|
|
9
|
+
const baseline = ['capabilities', 'attach', 'state']
|
|
10
|
+
const payload = {
|
|
11
|
+
success: false, debug_session_id: 'session', device: { id: 'device' }, target: { pid: 42 }, capabilities: { locals: true },
|
|
12
|
+
state: 'paused', captured_at_ms: 123, reason: null, stop: { stop_id: 'stop', thread_id: 'thread' },
|
|
13
|
+
limitations: ['SHALLOW_LOCALS'], cleanup: { status: 'failed', remaining_resources: ['forward:tcp:123'] },
|
|
14
|
+
error: { code: 'DEBUGGER_CLEANUP_FAILED', retryable: false, diagnostic: diagnostic('cleanup', 'operation_failed') },
|
|
15
|
+
execution_state: 'unknown', result: { frames: [{ frame_id: 'frame' }], truncated: true, availability: 'unknown' },
|
|
16
|
+
breakpoints: [], events: [], next_cursor: 4, overflow: true, timed_out: false, future_field: { preserve: true }
|
|
17
|
+
}
|
|
18
|
+
const expected = structuredClone(payload) as Record<string, unknown>
|
|
19
|
+
for (const key of ['device', 'target', 'capabilities']) delete expected[key]
|
|
20
|
+
const before = structuredClone(payload)
|
|
21
|
+
for (const tool of debuggerTools) {
|
|
22
|
+
const contract = debuggerContracts[tool.name]
|
|
23
|
+
assert.deepEqual(tool.inputSchema.properties.responseMode, { type: 'string', enum: ['compact', 'debug'], default: 'debug' })
|
|
24
|
+
const shape = contract.schema.shape
|
|
25
|
+
assert.equal(shape.responseMode.parse(undefined), 'debug')
|
|
26
|
+
for (const mode of ['compact', 'debug']) assert.equal(shape.responseMode.parse(mode), mode)
|
|
27
|
+
assert.equal(shape.responseMode.safeParse('full').success, false)
|
|
28
|
+
assert.deepEqual(debuggerResponse(contract.command, 'debug', payload), payload)
|
|
29
|
+
assert.deepEqual(debuggerResponse(contract.command, 'compact', payload), baseline.includes(contract.command) ? payload : expected)
|
|
30
|
+
const success = { ...payload, success: true, error: undefined, cleanup: { status: 'pending', remaining_resources: ['jdi_worker'] } }
|
|
31
|
+
assert.deepEqual(debuggerResponse(contract.command, 'compact', success), baseline.includes(contract.command) ? success : { ...expected, success: true, error: undefined, cleanup: success.cleanup })
|
|
32
|
+
const noSession = { ...payload, debug_session_id: null }
|
|
33
|
+
assert.deepEqual(debuggerResponse(contract.command, 'compact', noSession), noSession)
|
|
34
|
+
}
|
|
35
|
+
assert.deepEqual(payload, before, 'projection must not mutate nested or shared objects')
|
|
36
|
+
|
|
37
|
+
// This self-running test has its own process; restore the singleton seam in finally.
|
|
38
|
+
const original = debuggerSessions.command
|
|
39
|
+
try {
|
|
40
|
+
debuggerSessions.command = async (_id, _command, args) => {
|
|
41
|
+
assert.ok(!Object.hasOwn(args!, 'responseMode'), 'presentation input must not reach worker')
|
|
42
|
+
return { ...payload, success: true } as unknown as Awaited<ReturnType<typeof original>>
|
|
43
|
+
}
|
|
44
|
+
for (const mode of [undefined, 'debug', 'compact']) {
|
|
45
|
+
const response = await handleDebugger('get_debugger_locals', { debugSessionId: 'session', stopId: 'stop', frameId: 'frame', ...(mode ? { responseMode: mode } : {}) })
|
|
46
|
+
assert.equal(response.isError, undefined)
|
|
47
|
+
assert.deepEqual(JSON.parse(response.content[0].text), { ...(mode === 'compact' ? expected : payload), success: true })
|
|
48
|
+
}
|
|
49
|
+
for (const code of ['DEBUGGER_STALE_REFERENCE', 'DEBUGGER_NOT_FOUND', 'DEBUGGER_WORKER_FAILED', 'DEBUGGER_TIMEOUT', 'DEBUGGER_CANCELLED']) {
|
|
50
|
+
debuggerSessions.command = async () => { throw new DebuggerError(code, diagnostic('command', 'unknown')) }
|
|
51
|
+
for (const responseMode of ['compact', 'debug']) {
|
|
52
|
+
const response = await handleDebugger('resume_debugger', { debugSessionId: 'absent', stopId: 'old', responseMode })
|
|
53
|
+
const value = JSON.parse(response.content[0].text)
|
|
54
|
+
assert.equal(response.isError, true)
|
|
55
|
+
assert.equal(value.error.code, code)
|
|
56
|
+
assert.equal(Object.hasOwn(value, 'capabilities'), responseMode === 'debug')
|
|
57
|
+
assert.equal(value.debug_session_id, 'absent')
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const invalid = await handleDebugger('detach_debugger', { debugSessionId: 'session', responseMode: 'full' })
|
|
61
|
+
assert.equal(JSON.parse(invalid.content[0].text).error.code, 'INVALID_ARGUMENT')
|
|
62
|
+
} finally { debuggerSessions.command = original }
|
|
63
|
+
console.log('PASS debugger response defaults, exact retention, failures, immutable projection and argument isolation')
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { handleToolCall, toolDefinitions } from '../../../src/server-core.js'
|
|
3
|
+
import { systemTraces } from '../../../src/observe/system-trace.js'
|
|
4
|
+
import { _setDeviceListersForTests, _resetDeviceListersForTests } from '../../../src/utils/resolve-device.js'
|
|
5
|
+
import { AndroidManage } from '../../../src/manage/index.js'
|
|
6
|
+
import { withEvidence, bindSystemTrace, bindEvidenceDevice } from '../../../src/observe/evidence.js'
|
|
7
|
+
|
|
8
|
+
const parse = (r: any) => JSON.parse(r.content[0].text)
|
|
9
|
+
async function run() {
|
|
10
|
+
const originalLink = systemTraces.link, originalStart = systemTraces.start, originalControl = systemTraces.control
|
|
11
|
+
const originalApp = AndroidManage.prototype.startApp
|
|
12
|
+
const id = '62a51510-c303-4a1b-af0e-f97cc976d740'
|
|
13
|
+
let dispatched = 0, starts = 0
|
|
14
|
+
_setDeviceListersForTests({ listAndroidDevices: async () => [{ id: 'trace-device', platform: 'android', osVersion: '16', simulator: true, model: 'fixture' }] })
|
|
15
|
+
try {
|
|
16
|
+
for (const name of ['get_system_trace_capabilities', 'start_system_trace', 'get_system_trace_status', 'stop_system_trace', 'cancel_system_trace']) {
|
|
17
|
+
const tool = toolDefinitions.find(t => t.name === name)
|
|
18
|
+
assert.ok(tool); assert.equal(tool.inputSchema.additionalProperties, false)
|
|
19
|
+
}
|
|
20
|
+
assert.equal(toolDefinitions.find(t => t.name === 'run_journey').inputSchema.properties.systemTraceId.format, 'uuid')
|
|
21
|
+
for (const args of [ {}, { platform: 'android', extra: true }, { platform: 'android', expectedState: {} }, { platform: 'android', durationMs: 0 }, { platform: 'android', durationMs: 1000.5 }, { platform: 'android', durationMs: 120001 } ]) {
|
|
22
|
+
const r = await handleToolCall('start_system_trace', args)
|
|
23
|
+
assert.equal(r.isError, true); assert.equal(parse(r).error.code, 'INVALID_ARGUMENT')
|
|
24
|
+
}
|
|
25
|
+
systemTraces.start = async () => { starts++; throw new Error('must not run') }
|
|
26
|
+
const ios = await handleToolCall('start_system_trace', { platform: 'ios' })
|
|
27
|
+
assert.equal(parse(ios).error.code, 'UNSUPPORTED_PLATFORM'); assert.equal(starts, 0)
|
|
28
|
+
assert.equal(parse(await handleToolCall('get_system_trace_capabilities', { platform: 'ios' })).supported, false)
|
|
29
|
+
for (const name of ['stop_system_trace', 'cancel_system_trace', 'get_system_trace_status']) {
|
|
30
|
+
assert.equal(parse(await handleToolCall(name, { traceId: id, deviceId: 'wrong' })).error.code, 'INVALID_ARGUMENT')
|
|
31
|
+
assert.equal(parse(await handleToolCall(name, { traceId: id })).error.code, 'TRACE_NOT_FOUND')
|
|
32
|
+
}
|
|
33
|
+
AndroidManage.prototype.startApp = async () => { dispatched++; return { appStarted: true } as any }
|
|
34
|
+
const journey = { platform: 'android', deviceId: 'trace-device', systemTraceId: id, steps: [{ id: 'start', type: 'start_app', appId: 'com.android.settings', verificationMode: 'none' }] }
|
|
35
|
+
const rejected = await handleToolCall('run_journey', journey)
|
|
36
|
+
assert.equal(rejected.isError, true); assert.equal(dispatched, 0)
|
|
37
|
+
let correlation = ''
|
|
38
|
+
systemTraces.link = async (trace, device, cid) => {
|
|
39
|
+
assert.equal(trace, id); assert.equal(device.id, 'trace-device'); correlation = cid
|
|
40
|
+
return async () => ({ trace_id: trace, coverage: 'covered', clock_alignment: 'unknown' })
|
|
41
|
+
}
|
|
42
|
+
const result = parse(await handleToolCall('run_journey', journey))
|
|
43
|
+
assert.equal(result.success, true, JSON.stringify(result)); assert.equal(dispatched, 1)
|
|
44
|
+
assert.equal(result.evidence.trace_context.trace_id, id)
|
|
45
|
+
assert.equal(result.evidence.trace_context.supported, true)
|
|
46
|
+
assert.equal(result.evidence.correlation_id, correlation)
|
|
47
|
+
assert.equal(result.steps[0].result.evidence.trace_context.trace_id, id)
|
|
48
|
+
assert.equal(result.steps[0].result.evidence.correlation_id, correlation)
|
|
49
|
+
assert.equal(result.system_trace.coverage, 'covered')
|
|
50
|
+
const plain = parse(await handleToolCall('run_journey', { ...journey, systemTraceId: undefined }))
|
|
51
|
+
assert.deepEqual(plain.evidence.trace_context, { supported: false, trace_id: null, markers: [] })
|
|
52
|
+
const [linked, independent] = await Promise.all([
|
|
53
|
+
withEvidence({ journey: true }, async () => { bindSystemTrace(id); bindEvidenceDevice({ platform: 'android', id: 'trace-device' }); return withEvidence({ action: true }, async () => {}) }),
|
|
54
|
+
withEvidence({ journey: true }, async () => {})
|
|
55
|
+
])
|
|
56
|
+
assert.equal(linked.result.evidence.trace_context.trace_id, id)
|
|
57
|
+
assert.equal(independent.evidence.trace_context.trace_id, null)
|
|
58
|
+
console.log('system trace tool contracts and journey evidence: PASS')
|
|
59
|
+
} finally {
|
|
60
|
+
systemTraces.link = originalLink; systemTraces.start = originalStart; systemTraces.control = originalControl
|
|
61
|
+
AndroidManage.prototype.startApp = originalApp; _resetDeviceListersForTests()
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
run().catch(error => { console.error(error); process.exitCode = 1 })
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { readFile } from 'node:fs/promises'
|
|
3
|
+
import { toolDefinitions } from '../../../src/server/tool-definitions.js'
|
|
4
|
+
|
|
5
|
+
// Captured before structural refactoring: changes require an intentional contract review.
|
|
6
|
+
const baseline = JSON.parse(await readFile(new URL('../../fixtures/contracts/tool-definitions.json', import.meta.url), 'utf8'))
|
|
7
|
+
assert.deepEqual(toolDefinitions, baseline, 'Public tool order, schemas, defaults and descriptions must remain compatible')
|
|
8
|
+
console.log('PASS public tool-definition compatibility baseline')
|