feedBack/AGENTS.md

13 KiB

AGENTS.md

Project orientation for AI coding assistants (Cursor, GitHub Copilot, OpenAI Codex, Aider, Claude Code, Cline, Continue, Cody, Devin, …) and human contributors.

Slopsmith is a self-hosted web app for browsing, playing, and practicing interactive music notation, built around its own open .sloppak chart format. Charts come from importing Guitar Pro (GP5/GP8) or MusicXML, or from authoring in the built-in editor. It runs as a Docker container with a FastAPI backend (server.py), vanilla JavaScript frontend (static/), shared Python libraries (lib/), and an extensive plugin system (plugins/). No frontend frameworks — plain JS, HTML, Tailwind CSS. AGPL-3.0-only.

This file is the canonical orientation. Tool-specific automation (Claude skills/subagents/rules, Copilot instructions, etc.) lives in .claude/ and .github/copilot-instructions.md; both point back here. For plugin work, start at docs/PLUGIN_AUTHORING.md.

Architecture quick reference

server.py              FastAPI app — library API, WebSocket highway, plugin loading
main.py                Programmatic uvicorn entrypoint — installs structlog before boot
logging_setup.py       Structured logging + correlation IDs (LOG_LEVEL/LOG_FORMAT/LOG_FILE)
static/
  app.js               Main frontend — screens, library views, player, plugin loader
  highway.js           Canvas note highway renderer (createHighway factory)
  diagnostics.js       window.slopsmith.diagnostics namespace (loaded first in <head>)
  index.html           Single-page app shell
lib/
  song.py              Core data models (Note, Chord, Arrangement, Song)
  sloppak.py           Sloppak format support
  sloppak_convert.py   Import conversion + Demucs stem split
  loosefolder.py       Loose-folder XML chart support
  audio.py             WEM/OGG/MP3 audio handling
  retune.py            Pitch-shifting logic
  tunings.py           Tuning name/offset utilities
  gp2rs.py             Guitar Pro to arrangement XML conversion
  gp2midi.py           Guitar Pro to MIDI
plugins/
  __init__.py          Plugin discovery, loading, requirements install, load_sibling
  <plugin>/            Each plugin is its own directory (often a git submodule)
schema/
  plugin.schema.json   JSON Schema for plugin.json (validated in CI)
docs/                  Plugin contracts + format specs (see Plugin authoring below)
tests/                 pytest + tests/js/ (node --test) + tests/browser/ (Playwright)
specs/                 Active spec-kit features (specs/001-slopsmith-platform/...)

Running the app

Canonical dev path is Docker Compose — docker-compose.yml live-mounts static/, server.py, lib/, plugins/, and VERSION into the container, so frontend edits are visible on refresh and backend edits trigger uvicorn auto-reload.

docker compose up                                     # build + run on :8000
DLC_PATH=/path/to/dlc docker compose up               # override default Steam DLC path

For host-side runs (tests, scripts, no Docker), the programmatic entry point is main.py:

python main.py                                        # HOST=0.0.0.0 PORT=8000 default
HOST=127.0.0.1 PORT=8001 python main.py

main.py installs the structlog pipeline via logging_setup.configure_logging() before uvicorn boots and passes log_config=None so uvicorn's dictConfig never overwrites it. Do not invoke uvicorn server:app directly during development — early lifecycle log lines will bypass the structured pipeline and correlation IDs.

Logging env vars (read by logging_setup):

  • LOG_LEVELDEBUG | INFO | WARNING | ERROR (default INFO)
  • LOG_FORMATjson | text (default text — coloured console)
  • LOG_FILE — optional path for a persistent log file (e.g. /config/slopsmith.log)

Plugin backend code receives a pre-configured context["log"] logger namespaced to slopsmith.plugin.<id> — never use print(). See docs/plugin-logging.md.

Testing

pytest                              # All Python tests
pytest tests/test_plugins.py -v     # Specific file
pytest -k "load_sibling" -v         # Pattern match

npm run test:js                     # Node-native JS plugin-API contract tests (tests/js/)
npm run install:playwright          # One-time: install Chromium for Playwright
npm test                            # Playwright browser tests (tests/browser/)

Pytest config in pyproject.toml sets pythonpath = [".", "lib"] and testpaths = ["tests"]. CI runs pytest on every push/PR to main (Python 3.12). See docs/testing-plugins.md for fixtures (isolate_logging, reset_plugin_state) and Playwright patterns.

Git workflow

  • Never push directly to main — always create a feature branch and open a PR.
  • DCO sign-off is mandatory. git commit -s appends Signed-off-by:. Forgot? git commit --amend -s. See CONTRIBUTING.md.
  • Upstream remote — set upstream to the canonical Slopsmith repository; origin is your fork.
  • Plugins are gitlinks — each plugin in plugins/ is typically its own git repo (submodule or clone). Branch switches on the main repo can clobber plugin directories. Use git update-index --assume-unchanged for plugin dirs if needed.
  • Commit style — short imperative subject line, blank line, then body explaining why. Conventional-commit prefixes (feat(scope):, fix(scope):, chore:, docs:) are conventional in the log but not enforced.

Versioning

  • VERSION (repo root) — single source of truth; plain semver string. Bind-mounted into the container and copied by the Dockerfile so it's always available at /app/VERSION.
  • GET /api/version — returns {"version", "source_url", "license_url"}. source_url is overridable via APP_SOURCE_URL (default https://github.com/byrongamatos/slopsmith); license_url falls back to source_url + "/blob/main/LICENSE" and is overridable via APP_LICENSE_URL. Both must be http(s); non-http(s) values are rejected.
  • Auto-sync.github/workflows/sync-version.yml rewrites VERSION via a repository_dispatch (desktop-released) from slopsmith-desktop's release job. As an explicit automation-only exception to "Never push directly to main", the sync job commits straight to main as github-actions[bot]. Humans still go through PRs.
  • CHANGELOG.mdKeep a Changelog format. Update [Unreleased] on each PR; release renames it.

Song formats

Slopsmith supports two:

  • Legacy archive (encrypted, read-only) — fast metadata scan via lib/sloppak.py; full unpack for playback. Audio via vgmstream-cli + ffmpeg.
  • Sloppak (open format) — hand-editable, two interchangeable forms: .sloppak zip or .sloppak/ directory. Preferred for new features. Full spec: docs/sloppak-spec.md. Key code: lib/sloppak.py, lib/sloppak_convert.py, lib/song.py.

Frontend conventions

  • No frameworks — vanilla JS, fetch API, DOM manipulation
  • Plugin integration — prefer documented capability domains, provider APIs, and redaction-safe UI contributions over private globals
  • StoragelocalStorage for all user preferences, prefixed with plugin id
  • Styling — Tailwind utility classes; dark theme (bg-dark-600, text-gray-300, accent #4080e0, gold #e8c040)
  • Naming — camelCase for JS, kebab-case for CSS, snake_case for plugin IDs
  • Player layout#player is display:flex; flex-direction:column; position:fixed; inset:0. #highway is flex:1. Hiding the highway collapses the layout — use margin-top: auto on controls if you must hide it.

Backend conventions

  • Framework — FastAPI + uvicorn (boot via main.py)
  • Imports — flat imports from lib/ (no __init__.py): from song import Song
  • Database — SQLite via MetadataDB class with threading.Lock
  • WebSocket — JSON frames, try/except WebSocketDisconnect. Protocol: docs/websocket-protocol.md
  • Error handling — graceful fallbacks (audio conversion errors don't crash the song; missing art returns a placeholder)
  • Type hints — used sparingly (Path | None, dict, list)
  • Docstrings — minimal; code is self-documenting

Plugin authoring — see docs/PLUGIN_AUTHORING.md

Plugins are the primary extension point. Each lives in plugins/<name>/ with a plugin.json manifest that declares its capability-pipelines.v1 participation. Curated plugins must be AGPL-3.0 or AGPL-compatible — see CONTRIBUTING.md for the allowlist. Manifest is validated in CI against schema/plugin.schema.json, including capability metadata.

Topic Doc
Manifest reference (plugin.json fields) docs/plugin-manifest.md
Capability declarations (standards, capabilities, ui) docs/plugin-manifest.md#capabilities
Visualization contracts docs/plugin-visualization-contracts.md
Plugin styles (styles: "assets/plugin.css") docs/plugin-styles.md
Backend context["log"] logging docs/plugin-logging.md
Diagnostics opt-in (export bundle) docs/plugin-diagnostics.md
Multi-file backends (load_sibling) docs/plugin-sibling-imports.md
WebSocket highway protocol docs/websocket-protocol.md
Testing plugins (pytest + Playwright) docs/testing-plugins.md
Diagnostics bundle layout docs/diagnostics-bundle-spec.md
Sloppak format spec docs/sloppak-spec.md
Tuning the note_detect plugin docs/note-detect-tuning.md

First-hour pitfalls (read these before your first PR)

  1. load_sibling for cross-file backend plugins. Bare from extractor import X in routes.py collides across plugins because Python caches by module name in sys.modules. Use context["load_sibling"]("extractor") — gets a per-plugin namespaced module. Full explanation: docs/plugin-sibling-imports.md.

  2. Capability declarations are the integration map. Plugin behavior should be visible in standards, capabilities, and ui metadata before runtime code hydrates. If the domain you need is missing, document it as a capability gap instead of adding another private global contract.

  3. Highway flex layout. #highway has flex:1 in the player. Hiding it with display:none removes the flex child and #player-controls floats to the top. If you must hide the highway, add margin-top: auto to the controls div.

  4. Plugin gitlinks bite on branch switches. Plugins are separate git repos cloned into plugins/. git checkout / git clean on the main repo can delete or clobber them. Use git update-index --assume-unchanged plugins/<id> if needed.

  5. DCO sign-off is mandatory. Every commit needs Signed-off-by:. Add via git commit -s (or git commit --amend -s if you forgot). PRs without DCO won't merge.

Verification (run before claiming done)

pytest -q                         # backend tests
npm run test:js                   # JS plugin-API contract tests
npm test                          # Playwright browser tests (slow; defers to CI)

If you touched plugins/*/plugin.json or schema/:

python -c "import json,glob,jsonschema; s=json.load(open('schema/plugin.schema.json')); [jsonschema.validate(json.load(open(p)), s) for p in sorted(glob.glob('plugins/*/plugin.json'))]"

House rules

  • AGPL-3.0-only. Inbound contributions are inbound under the same terms. Don't paste from incompatible sources.
  • DCO sign-off mandatory on every commit (git commit -s).
  • No frontend frameworks. Vanilla JS, fetch API, Tailwind classes. Don't add React/Vue/Svelte.
  • Backend logging. Plugin routes.py must use context["log"], never print(). See docs/plugin-logging.md.
  • Plugin Python imports. Multi-file backends use context["load_sibling"]("<module>"), not bare from <module> import. See docs/plugin-sibling-imports.md.
  • Capability metadata. Plugin integrations declare standards: ["capability-pipelines.v1"] plus redaction-safe capabilities/ui metadata as the primary integration contract.
  • Spec-kit owns .specify/ and specs/. Don't modify those without explicit instruction; the /speckit-* skills own that surface.

Tool-specific surfaces (optional reading)

  • CLAUDE.md — points back here. Exists for Claude Code's auto-loading convention. Claude-specific automation lives in .claude/ (skills, subagents, rules, settings).
  • .github/copilot-instructions.md — points back here. Exists for GitHub Copilot's native instructions format.
  • No .cursorrules — Cursor reads AGENTS.md natively. Cursor-specific rules, if ever needed, would go under .cursor/rules/.