From e7ca364c1d329bc6ab0abe0f5e88a97fb163d89f Mon Sep 17 00:00:00 2001 From: Axel Meyer Date: Sat, 16 May 2026 16:49:41 +0200 Subject: [PATCH] docs: migrate README to API-Doc-Convention (S2-cascade.libs test-lib) Restructured per ADR-0038 with Coverage-Statement-Block per Fault- Injection-Audit-Convention. Topology auto-populated. Pre-commit hook installed. Co-Authored-By: Claude Opus 4.7 (1M context) --- .githooks/pre-commit | 13 +++++ README.md | 119 ++++++++++++++++++++++++++------------- scripts/install-hooks.sh | 3 + 3 files changed, 97 insertions(+), 38 deletions(-) create mode 100644 .githooks/pre-commit create mode 100644 scripts/install-hooks.sh 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 e4fa0ea..bd5c21e 100644 --- a/README.md +++ b/README.md @@ -1,48 +1,91 @@ -# sporel-module-lib-core.selection-test +# lib-core.selection-test -P.3.6 reference test-module — validates `lib-core.selection` API contracts -via the `engine.test.*` assertion surface. +Test-lib for `lib-core.selection` (Tests-as-Libs per ADR-0037). -- Module-ID: `lib-core.selection-test` -- Version: `0.1.0` -- Kind: `test-module` (headless; runs init() once + exits) -- Spec: `meta/docs/superpowers/specs/2026-05-14-p3-6-lib-selection-design.md` +**Version:** 0.1.0 +**Lib-ID:** lib-core.selection-test +**Requires:** lib-core.selection v>=0.1.0, lib-core.input v>=0.4.0, lib-core.camera v>=0.3.0 +**Tags:** test, selection -## Run +## Topology + + +```mermaid +graph LR + this["lib-core.selection-test"] + lib_core_selection["lib-core.selection"] + this --> lib_core_selection + 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 | Handle positivity, `count_registered` after register/unregister | +| Set-Query (initial empty) | 2 | `is_empty()` true, `count() == 0` | +| Set-Mutation | 5 | `add`/`contains`, `count==1`, `remove`/`!contains`, `toggle` add/remove, `clear` -> empty | +| Unregister cascade | 1 | `unregister` removes from selection | +| Drag-Threshold | 2 | Default = 4, `set_drag_threshold` writes | +| Drag-State + box_rect | 2 | Initial `drag_state == "idle"`, `box_rect()` nil while idle | +| Enabled-Flag | 2 | Default true, `set_enabled(false)` writes | +| Visual-Setters | 2 | `set_drag_box_color` writes RGBA, `set_highlight_color` writes RGBA | +| Error-Handling | 3 | `set_drag_threshold(-1)` errors, `add(unregistered)` errors, `toggle(unregistered)` errors | +| **TOTAL** | **22** | | + +### Tested-Surface +- `selection.register`, `selection.unregister`, `selection.count_registered` +- `selection.is_empty`, `selection.count`, `selection.contains` +- `selection.add`, `selection.remove`, `selection.toggle`, `selection.clear` +- `selection.drag_threshold`, `selection.set_drag_threshold` +- `selection.drag_state`, `selection.box_rect` +- `selection.enabled`, `selection.set_enabled` +- `selection.set_drag_box_color`, `selection.drag_box_color` +- `selection.set_highlight_color`, `selection.highlight_color` + +### Fault-Injection Audit (2026-05-16) + +Verified via deliberate-mutation in fixture-mirror; mutations reverted after observation. + +#### Caught Mutations + +| # | Mutation | Caught by | +|---|---|---| +| M1 | `M.contains` prefixed with `do return false end` (always false) | Assertions `add → contains` + `toggle adds when absent` fail (2 failures) | +| M2 | `M.count` prefixed with `do return 0 end` (always zero) | Assertion `count = 1 after add` fails (expected 1, got 0) | + +#### Uncaught Mutations (Gaps) — Cheap-Fill Applied + +| # | Original Gap | Fill | Re-Inject Verifies | +|---|---|---|---| +| M3 | `M.add` unregistered-handle guard removed | Added `pcall(add(99999))` + `pcall(toggle(99999))` error assertions | ✓ | + +### Structurally Untestable +- Mouse-drag selection-box interaction — requires real mouse events; headless `drag_state` stays `idle`. +- Render output of highlight + drag-box visuals (verified manually). + +### Deferred +- Integration test with simulated mouse events (Future-Slice B). + +## Running + +Via `--test-libs=lib-core.selection-test` (Smoke P0-S-batch Stage): ```bash -Sporel.exe --test-libs=lib-core.selection-test +Sporel.exe --libs-dir= --test-libs=lib-core.selection-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.contains` prefixed with `do return false end` (always false) | Assertions "add → contains" (ok 6) and "toggle adds when absent" (ok 9) — 2 failures | -| M2 | `M.count` prefixed with `do return 0 end` (always zero) | Assertion "count = 1 after add" (ok 7) — expected 1 got 0 | - -### Uncaught Mutations (Gaps) - -| # | Mutation | Why uncaught | Severity | -|---|---|---|---| -| M3 | `M.add` unregistered-handle guard removed (any handle accepted silently) | No test calls `add(unregistered_handle)` and expects an error | Medium | - -**Resolution per Gap:** -- Gap M3: Cheap-fill applied — two `pcall` error assertions added for `add(99999)` and `toggle(99999)` (commit see below). - -### Cheap-Gap-Fills Applied - -| Gap | New Assertion | Re-Inject Verifies | -|---|---|---| -| M3 | `add(unregistered) errors` — `pcall(function() selection.add(99999) end)` must return false | ✓ | -| M3 | `toggle(unregistered) errors` — `pcall(function() selection.toggle(99999) end)` must return false | ✓ | - -### Tested Production-Lib Surface - -`lib-core.selection` v0.1.0 — Tests cover: `register`, `unregister`, `count_registered`, `is_empty`, `count`, `contains`, `add`, `remove`, `toggle`, `clear`, `drag_threshold`, `set_drag_threshold`, `drag_state`, `box_rect`, `enabled`, `set_enabled`, `set_drag_box_color`, `drag_box_color`, `set_highlight_color`, `highlight_color`. +- ADR-0037 (Tests-as-Libs Convention) +- ADR-0038 (API-Doc-Convention) +- Production-Lib: `~/Projects/Sporel/sporel-libs/lib-core/selection/README.md` +- Spec: `meta/docs/superpowers/specs/2026-05-14-p3-6-lib-selection-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."