diff --git a/.specify/memory/constitution.md b/.specify/memory/constitution.md index d5837fc..c5096df 100644 --- a/.specify/memory/constitution.md +++ b/.specify/memory/constitution.md @@ -1,13 +1,28 @@ + + # Slopsmith Constitution > Slopsmith is a self-hosted, single-user web app for browsing, playing, and -> practicing interactive music notation, built around its own open `.sloppak` +practicing interactive music notation, built around its own open `.sloppak` > chart format (charts imported from Guitar Pro / MusicXML or authored in the > built-in editor). This constitution captures the non-negotiable principles > that govern its core (`server.py`, `lib/`, `static/`) and that all in-tree > plugins (`plugins//`) inherit by default. It is a *retrospective* > document — the codebase came first, the principles below were distilled from -> `CLAUDE.md`, `README.md`, and the shape of the existing implementation. +> `AGENTS.md`, `README.md`, and the shape of the existing implementation. ## Core Principles @@ -69,28 +84,45 @@ features extend `app.js` and the existing globals (`window.playSong`, layout invariants (`#player` flex-column, `#highway` flex:1, `#player-controls` at the bottom) MUST be preserved. -### III. Plugins Are the Extension Point — Isolated by `load_sibling` +### III. Plugins Are the Extension Point — Capability-Declared and Isolated Functionality that is not part of the irreducible "browse + play charts" loop ships as a plugin under `plugins//`, not as core code. Each plugin is its own directory (typically a separate git repo), discovered at startup via `plugin.json`, and free to add nav links, screens, -settings panels, and `/api/plugins//...` routes. Plugins MUST -isolate their backend Python imports via `context["load_sibling"]` so -two plugins shipping a generic `extractor.py` / `util.py` / `client.py` -do not collide in `sys.modules`. +settings panels, and `/api/plugins//...` routes. Slopsmith-facing +plugin behavior is capability-declared by default: manifests describe +the domains a plugin owns, provides, requests, observes, validates, or +contributes to before any runtime script hydrates. Runtime handlers, +legacy wrappers, and private globals are implementation details or +compatibility bridges, never the primary integration contract. Plugins +MUST isolate their backend Python imports via `context["load_sibling"]` +so two plugins shipping a generic `extractor.py` / `util.py` / +`client.py` do not collide in `sys.modules`. **Non-negotiable rules** - Generic features (practice journal, setlist, metronome, tone player, tab view, MIDI control, stem mixing, editors, etc.) belong in a plugin repo, not in `lib/` or `server.py`. +- Plugins with Slopsmith-facing behavior MUST declare + `standards: ["capability-pipelines.v1"]` plus redaction-safe + `capabilities`, `ui`, or `ui_contributions` metadata for the behavior + they expose. Metadata-only or transitional manifests that omit + capability participation MUST document why no current domain applies. +- Runtime participants, event listeners, wrapper hooks, timers, DOM + roots, diagnostics contributors, and media nodes MUST be idempotent + across repeated script hydration. If a wrapper or private global is + still required, it MUST be documented as a compatibility bridge or a + missing capability-domain gap. - Plugin backend modules MUST use `context["load_sibling"]("name")` for sibling imports. Bare `import sibling` works during transition but triggers a startup warning when a name collides. - Plugins MUST register routes under `/api/plugins//...`, - use `window.slopsmith.emit/on` for cross-plugin communication, and - prefix their `localStorage` keys with their plugin id. + use capability commands/events when an active domain exists, and + prefix their `localStorage` keys with their plugin id. General + `window.slopsmith.emit/on` events remain acceptable for host events or + domains that have not yet been promoted. - Plugins inherit this constitution and may layer additional rules in their own `CLAUDE.md`, but MUST NOT relax core principles (e.g. a plugin cannot require a frontend framework in core). @@ -207,11 +239,12 @@ no `..`, no absolute paths). `flex:1`; `#player-controls` sits at the bottom. Hiding the highway collapses the layout — use `margin-top: auto` on controls if you need to hide it. -- **Plugin load order**: alphabetical by directory name. The - `playSong` wrapper chain runs outermost-first (last-loaded wrapper - runs first). Plugins MUST tolerate dependent globals being absent - at load time and check at runtime - (`typeof window.X === 'function'`). +- **Plugin load order**: alphabetical by directory name. Scripts MUST + tolerate dependent globals being absent at load time and check at + runtime (`typeof window.X === 'function'`). Load order and the + `playSong` wrapper chain are implementation details, not integration + contracts; plugins declare roles, ownership, compatibility, and UI + contributions through capability metadata. ## Development Workflow @@ -225,8 +258,9 @@ no `..`, no absolute paths). After pushing a fix, the CodeRabbit loop (`feedback_coderabbit_review.md`) runs to silence. - **Testing**: `pytest` for backend (`requirements-test.txt`), - Playwright for browser interactions (`tests/browser/`), CI runs both - on every push/PR to `main`. + Playwright for browser interactions (`tests/browser/`), and plugin + manifest schema validation for capability metadata. CI runs these on + every relevant push/PR to `main`. - **CHANGELOG**: every PR updates `[Unreleased]`. Releases rename `[Unreleased]` to `[X.Y.Z] - YYYY-MM-DD` (the VERSION bump itself is automated). @@ -246,12 +280,13 @@ no `..`, no absolute paths). explicit constitutional amendment in this file. - Amendments require: (a) a PR that updates this file alongside the code change, (b) an entry in `CHANGELOG.md` under "Migration notes" - if user-visible, and (c) a corresponding update to `CLAUDE.md` so - AI agents and humans see the same source of truth. + if user-visible, and (c) a corresponding update to `AGENTS.md` and any + agent-specific imports or instructions so AI agents and humans see the + same source of truth. - The principles are listed in priority order. When two principles conflict (e.g. "vanilla frontend" vs. a plugin that wants to ship React), the lower-numbered principle wins by default; the higher-numbered principle's escape hatch is to live in a plugin with its own bundled assets. -**Version**: 1.1.0 | **Ratified**: 2026-05-09 | **Last Amended**: 2026-06-01 +**Version**: 1.2.0 | **Ratified**: 2026-05-09 | **Last Amended**: 2026-06-03 diff --git a/.specify/templates/plan-template.md b/.specify/templates/plan-template.md index 8d5e68d..635747f 100644 --- a/.specify/templates/plan-template.md +++ b/.specify/templates/plan-template.md @@ -31,7 +31,29 @@ *GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* -[Gates determined based on constitution file] +Answer each gate with PASS / FAIL / N/A and a short justification: + +- **Self-hosted Docker path**: Does the feature keep `DLC_DIR` and + `CONFIG_DIR` as the only required runtime inputs, with any new + dependency installable in the existing Docker image? +- **Vanilla/source-served frontend**: Does core remain plain JS and + committed CSS, with no framework, runtime CDN/JIT, bundler, or + serve-path build step? If plugin CSS is needed, does it ship through + `styles`? +- **Plugin and capability boundary**: If the feature is outside browse + + play CDLC, is it plugin-shaped? Does every Slopsmith-facing plugin + behavior declare `capability-pipelines.v1` metadata (`capabilities`, + `ui`, or `ui_contributions`) and use `load_sibling` for backend + siblings? +- **CDLC compatibility**: Are PSARC scanning, sloppak manifests, + arrangement IDs, and highway WebSocket messages kept backward- + compatible, or is a migration note planned? +- **Pure/testable core**: Are new `lib/` helpers flat-importable, + side-effect-light, and covered where practical? +- **Observability and diagnostics**: Does backend output use logging, + and do diagnostics remain redacted and versioned? +- **Settings/versioning**: Are settings imports/export paths safe, + additive/idempotent, and compatible with the `VERSION` workflow? ## Project Structure diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d2eb62..614bf18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **`highway.getPhrases()` and `highway.getMastery()` public plugin API** — exposes phrase timing windows (`[{ index, start_time, end_time, max_difficulty }]`) and the current mastery slider value (`0..1`) as documented, stable plugin API. Both values were already in memory and reachable via internal names; this surfaces them with intent so plugins can implement section-aware logic (e.g. tracking accuracy per phrase, suppressing difficulty changes during a hard solo) without reaching into undocumented internals. Returns `null` when the song has no phrase data (GP imports, single-difficulty charts). Pair with the existing `hasPhraseData()` to gate phrase-aware code paths. - **Tailwind freshness guard + wider plugin scan.** A new `tailwind-fresh` CI job (`.github/workflows/tests.yml`) rebuilds `static/tailwind.min.css` with the pinned `tailwindcss@3.4.19` and hard-fails on any diff, so the committed prebuilt stylesheet can no longer silently lag source (after PR #411 removed the runtime Play CDN, a stale file shipped unstyled elements with no guard). The `tailwind.config.js` plugin content glob is widened to `./plugins/**/*.{js,html}`, which also scans non-`screen.js` plugin JS (e.g. `plugins/app_tour_*/script.js`) that was previously invisible to the build. Regenerating under the wider glob is a no-op for runtime behaviour — it only adds classes that were already used in source. Groundwork for the plugin `styles` capability (constitution 1.1.0, Principle II): runtime-installed plugins ship their own compiled CSS rather than relying on core's build-time scan. - **Plugin capability pipelines** — adds the first versioned capability coordination layer for plugin authors and support tooling. `/api/plugins` now exposes validated capability declarations, validation warnings, unsupported-version metadata, UI/runtime domain declarations, and compatibility shim summaries for legacy `nav` / `screen` / `settings` / `routes` / visualization surfaces. The browser runtime now tracks manifest participants separately from live handlers, explicit dispatch outcomes (`no-owner`, `no-handler`, `unsupported-command`, `incompatible-version`), claim lifecycle cleanup, manual override precedence, deterministic ownership conflicts, multi-provider ordering, shim hit counts, and a redaction-safe diagnostics snapshot capped at 64 KB. A bundled Capability Inspector plugin shows the live graph, and new docs cover the manifest schema, recipes, safety matrix, lifecycle cleanup, and diagnostics contract. +- **Constitution 1.2.0 codifies capability-first plugins.** Principle III now makes `capability-pipelines.v1` manifest declarations the default Slopsmith-facing plugin contract, treats wrappers/private globals as compatibility bridges or capability gaps, and adds a concrete spec-kit Constitution Check gate for plugin capability metadata. - **Audio graph/session capability slice** — promotes `audio-mix`, `audio-input`, `audio-monitoring`, and coordinated `stems` diagnostics into the capability runtime. The new audio session host records song route/fader state, redaction-safe input sources, monitoring lifecycle outcomes, stem automation claims/overrides/orphans, and compatibility bridge hits for legacy faders, song volume, Stems master volume, 3D Highway analyser taps, audio startup barriers, and input source handoffs. `core.audio.session` coordinates `stems` without replacing the Stems plugin as the owner of actual stem playback/state. - **Audio-mix control plane** — makes `audio-mix` the player mixer source of truth. Core now exposes `list-faders`, `get-fader-value`, `set-fader-value`, `inspect-route`, and `inspect-analyser` through the capability runtime, routes native and compatibility-backed fader provider operations with a 2-second timeout, reports committed values back to the mixer UI, suppresses matching legacy faders when a native participant owns the same logical control, and expands audio-session diagnostics/Capability Inspector rendering for fader availability, source modes, bridge hits, route/analyser summaries, and timeout failures. - **Audio-input control plane** — makes `audio-input` the redaction-safe source of truth for instrument input discovery and lifecycle. Core now exposes `list-sources`, `select-source`, `open-source`, and `close-source` through the capability runtime, persists selected logical sources, keeps inspect/list/select prompt-free, routes provider `source.open`/`source.close` operations with bounded outcomes, shares compatible open sessions across requesters, suppresses compatibility-backed duplicate sources when a native provider owns the same logical key, and expands audio-session diagnostics/Capability Inspector rendering for selected input, open sessions, bridge hits, storage status, and permission/device failures without exposing raw device labels or live audio handles.