diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100644 index 0000000..1ebdad0 --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,13 @@ +#!/bin/sh +# Sporel API-Doc-Convention pre-commit hook. +SPOREL_EXE="${SPOREL_EXE:-$(command -v Sporel.exe 2>/dev/null || command -v sporel 2>/dev/null)}" +if [ -z "$SPOREL_EXE" ]; then + echo "INFO: Sporel.exe not on PATH. Skipping lint." >&2 + exit 0 +fi +"$SPOREL_EXE" --lint="$(pwd)" --fix +RC=$? +if git diff --cached --name-only | grep -qx 'README.md'; then + git add README.md +fi +exit $RC diff --git a/README.md b/README.md index 7d0f5f4..40e9c89 100644 --- a/README.md +++ b/README.md @@ -1,48 +1,89 @@ -# sporel-module-lib-core.command-test +# lib-core.command-test -P.3.7 reference test-module — validates `lib-core.command` API contracts -via the `engine.test.*` assertion surface. +Test-lib for `lib-core.command` (Tests-as-Libs per ADR-0037). -- Module-ID: `lib-core.command-test` -- Version: `0.1.0` -- Kind: `test-module` (headless; runs init() once + exits) -- Spec: `meta/docs/superpowers/specs/2026-05-14-p3-7-lib-command-design.md` +**Version:** 0.1.0 +**Lib-ID:** lib-core.command-test +**Requires:** lib-core.command v>=0.1.0, lib-core.input v>=0.4.0, lib-core.camera v>=0.3.0 +**Tags:** test, command -## Run +## Topology + + +```mermaid +graph LR + this["lib-core.command-test"] + lib_core_command["lib-core.command"] + this --> lib_core_command + lib_core_input["lib-core.input"] + this --> lib_core_input + lib_core_camera["lib-core.camera"] + this --> lib_core_camera +``` + + +## Coverage Statement + +Per Fault-Injection-Audit-Convention (2026-05-16): + +| Section | Assertion-Count | Coverage | +|---|---|---| +| Register / Unregister | 3 | `register` handle, `count_registered` after register/unregister | +| Move-Issue + State-Query | 3 | `move_to`, `current` returns move-data, `is_moving` true after issue, false for idle | +| State-Query (idle) | 2 | `current` returns nil for idle, `is_moving` false for unknown handle | +| Update-step | 2 | `update` moves 10px in 0.1s @ 100 px/s, y unchanged for horizontal move | +| Arrival-snap | 2 | Snap to target.x when step >= dist, `current` cleared after arrival | +| Cancel + cancel_all | 5 | `cancel(h2)` clears h2 only, `cancel_all` clears remaining; cancel_all clears 2-unit batch | +| Speed-Setter | 2 | `speed` from register, `set_speed` updates | +| Arrive-Threshold + Enabled | 3 | Default threshold = 2, `set_arrive_threshold` writes, `enabled` toggle | +| Error-Handling | 2 | `register` with non-function errors, `move_to` with unknown handle errors | +| **TOTAL** | **24** | | + +### Tested-Surface +- `command.register`, `command.unregister`, `command.count_registered` +- `command.move_to`, `command.current`, `command.is_moving` +- `command.cancel`, `command.cancel_all` +- `command.speed`, `command.set_speed` +- `command.arrive_threshold`, `command.set_arrive_threshold` +- `command.enabled`, `command.set_enabled` +- `command.update` (step + arrival-snap) + +### Fault-Injection Audit (2026-05-16) + +Verified via deliberate-mutation in fixture-mirror; mutations reverted after observation. + +#### Caught Mutations + +| # | Mutation | Caught by | +|---|---|---| +| M1 | `M.move_to` prefixed with `do return end` (complete no-op) | 6 assertions fail: `current returns move-command data`, `is_moving true after move_to`, `update moves 10px`, `arrival snap to target.x`, `cancel doesn't affect h3`, `move_to with unknown handle errors` | +| M2 | `M.is_moving` prefixed with `do return false end` (always false) | `is_moving true after move_to` fails | + +#### Uncaught Mutations (Gaps) — Cheap-Fill Applied + +| # | Original Gap | Fill | Re-Inject Verifies | +|---|---|---|---| +| M3 | `M.cancel_all` rewritten to break after first entry | Added 2-unit batch scenario; asserts h2 + h3 both nil after `cancel_all` | ✓ | + +### Structurally Untestable +- None identified at headless level — entire surface exercisable via mocked-unit closure-state helper. + +### Deferred +- Multi-frame integration with `lib-core.player_control` consumer flow (covered indirectly by player_control-test). + +## Running + +Via `--test-libs=lib-core.command-test` (Smoke P0-S-batch Stage): ```bash -Sporel.exe --test-libs=lib-core.command-test +Sporel.exe --libs-dir= --test-libs=lib-core.command-test ``` Output is TAP version 13 on stdout; exit-code = failure-count (0 = all pass). -## Coverage Statements (2026-05-16 Fault-Injection Audit) +## References -Verified via deliberate-mutation in fixture-mirror; mutations reverted after observation. - -### Caught Mutations - -| # | Mutation | Caught by | -|---|---|---| -| M1 | `M.move_to` prefixed with `do return end` (complete no-op) | 6 assertions fail: "current returns move-command data" (ok 4), "is_moving true after move_to" (ok 5), "update moves 10px" (ok 9), "arrival snap to target.x" (ok 11), "cancel doesn't affect h3" (ok 14), "move_to with unknown handle errors" (ok 23) | -| M2 | `M.is_moving` prefixed with `do return false end` (always false) | Assertion "is_moving true after move_to" (ok 5) | - -### Uncaught Mutations (Gaps) - -| # | Mutation | Why uncaught | Severity | -|---|---|---|---| -| M3 | `M.cancel_all` rewritten to loop-and-break after first entry (only cancels one command) | Existing test cancels h2 manually before calling `cancel_all`, leaving only h3 active — single-entry cancel_all succeeds trivially | Medium | - -**Resolution per Gap:** -- Gap M3: Cheap-fill applied — added a second `cancel_all` scenario where both h2 and h3 are active simultaneously; asserts both are nil after `cancel_all` (commit see below). - -### Cheap-Gap-Fills Applied - -| Gap | New Assertion | Re-Inject Verifies | -|---|---|---| -| M3 | `cancel_all clears h2 of 2-unit batch` — `current(h2) == nil` after issuing move to both then cancel_all | ✓ | -| M3 | `cancel_all clears h3 of 2-unit batch` — `current(h3) == nil` after same cancel_all | ✓ | - -### Tested Production-Lib Surface - -`lib-core.command` v0.1.0 — Tests cover: `register`, `unregister`, `count_registered`, `move_to`, `current`, `is_moving`, `cancel`, `cancel_all`, `speed`, `set_speed`, `arrive_threshold`, `set_arrive_threshold`, `enabled`, `set_enabled`, `update` (step + arrival-snap). +- ADR-0037 (Tests-as-Libs Convention) +- ADR-0038 (API-Doc-Convention) +- Production-Lib: `~/Projects/Sporel/sporel-libs/lib-core/command/README.md` +- Spec: `meta/docs/superpowers/specs/2026-05-14-p3-7-lib-command-design.md` diff --git a/scripts/install-hooks.sh b/scripts/install-hooks.sh new file mode 100644 index 0000000..2ff6e41 --- /dev/null +++ b/scripts/install-hooks.sh @@ -0,0 +1,3 @@ +#!/bin/sh +git config core.hooksPath .githooks +echo "Hooks activated."