mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-09-13 00:48:33 +00:00
feat: add cross-tool orientation, CI schema validation, and Claude Code surfaces
Adds the contributor- and AI-tool-facing infrastructure on top of the
modular docs from the previous commit. Lands AGENTS.md as the canonical
cross-tool orientation (read natively by Cursor, Copilot, Codex, Aider,
Cline, Continue, Cody, Devin, Replit Agent, and Claude Code), flips
CLAUDE.md to a 22-line pointer that uses Claude Code's @-import to
inline AGENTS.md, wires up plugin.json validation in CI, and adds the
Claude-specific automation surfaces under .claude/.
Cross-tool orientation:
AGENTS.md (178 lines) — single source of truth: architecture, running
the app, testing, git workflow, versioning, song formats, frontend
and backend conventions, plugin authoring index, first-hour
pitfalls, verification, house rules.
CLAUDE.md (22 lines) — Claude Code memory file. Uses @AGENTS.md
import (recursion depth 5) so the canonical content is inlined
without duplication. Lists .claude/ surfaces.
.github/copilot-instructions.md — Copilot custom instructions
format; points at AGENTS.md and docs/PLUGIN_AUTHORING.md.
.cursorrules — not added. Cursor reads AGENTS.md natively in 2026
and .cursorrules is legacy.
Contribution hygiene (.github/):
PULL_REQUEST_TEMPLATE.md — summary, linked issue, test plan, DCO
and conventional-commit reminders. No AI-disclosure section.
ISSUE_TEMPLATE/bug.yml — version, deployment, OS, plugins enabled,
repro, logs (linked to docs/diagnostics-bundle-spec.md for
redaction guidance).
ISSUE_TEMPLATE/feature.yml — problem, proposed, alternatives,
surface, plugin-author impact, license check.
ISSUE_TEMPLATE/config.yml — disables blank issues; redirects
plugin issues to plugin repos and security to the private
advisory flow.
CI:
.github/workflows/validate-plugins.yml — runs on changes to
plugins/*, schema/, CONTRIBUTING.md, the test file, or the
workflow itself. Installs jsonschema and pytest, validates every
plugins/*/plugin.json against schema/plugin.schema.json, and runs
the license-allowlist subset check.
tests/test_plugin_schema.py — 8 parametrized tests: schema is
well-formed, the 3 in-tree manifests validate, manifest id
matches its parent directory name, schema license enum is a
subset of CONTRIBUTING's curated allowlist.
requirements-test.txt — append jsonschema>=4.0.
.github/workflows/sync-version.yml — comment retargeted to
AGENTS.md "Versioning" section.
Claude Code surfaces (.claude/):
README.md — layout explanation. Spec-kit owns skills/speckit-*;
repo-specific skills sit alongside. Hooks off by default;
settings.json carries a commented opt-in example.
skills/plugin-scaffold/SKILL.md — generates a plugin skeleton for
type in {visualization, overlay, settings-only, routes-only}.
skills/plugin-validate/SKILL.md — local pre-push check: validates
plugin.json against schema, asserts declared files exist,
enforces license allowlist.
rules/plugin-author.md — globs scoped to plugins/**. Encodes the
contracts from docs/PLUGIN_AUTHORING.md so AI suggestions don't
drift from them (manifest required, context[\"log\"] over print,
load_sibling over bare imports, playSong await discipline,
settings.server_files conventions).
agents/slopsmith-reviewer.md — plugin-aware reviewer subagent;
invoke with @slopsmith-reviewer. 12-item checklist mirrors the
rule and the schema.
settings.json — empty hooks block plus a commented PostToolUse
example for opt-in plugin.json validation on save.
Inbound-ref updates (files we own):
README.md — \"AI Agent Guide\" points at AGENTS.md and notes
.claude/ and copilot-instructions are tool-specific.
CONTRIBUTING.md — \"Plugin System in CLAUDE.md\" -> docs/ and
schema/; \"Git Workflow\" -> AGENTS.md#git-workflow.
docs/sloppak-spec.md — plugin-system table cell -> docs/.
Out of scope (intentionally untouched):
plugins/highway_3d/README.md (gitlink — plugin owns its docs).
.specify/memory/constitution.md and other spec-kit artefacts
(spec-kit owns that surface; CLAUDE.md still resolves
transitively via the @-import).
Verification:
pytest -q # backend + schema tests pass
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'))]\"
Signed-off-by: Miguel_LZPF <mgcdreamer@gmail.com>
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
# 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 Rocksmith 2014 Custom DLC. 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/`](.claude/) and [`.github/copilot-instructions.md`](.github/copilot-instructions.md); both point back here. For plugin work, start at [`docs/PLUGIN_AUTHORING.md`](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)
|
||||
psarc.py PSARC archive reading and extraction
|
||||
sloppak.py Sloppak format support
|
||||
sloppak_convert.py PSARC → sloppak conversion + Demucs stem split
|
||||
audio.py WEM/OGG/MP3 audio handling
|
||||
retune.py Pitch-shifting logic
|
||||
tunings.py Tuning name/offset utilities
|
||||
gp2rs.py Guitar Pro to Rocksmith 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.
|
||||
|
||||
```bash
|
||||
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`:
|
||||
|
||||
```bash
|
||||
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_LEVEL` — `DEBUG | INFO | WARNING | ERROR` (default `INFO`)
|
||||
- `LOG_FORMAT` — `json | 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`](docs/plugin-logging.md).
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
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`](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`](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.md`** — [Keep a Changelog](https://keepachangelog.com/) format. Update `[Unreleased]` on each PR; release renames it.
|
||||
|
||||
## Song formats
|
||||
|
||||
Slopsmith supports two:
|
||||
|
||||
- **PSARC** (Rocksmith native) — encrypted archive. Read-only. Fast metadata scan via `lib/psarc.py` (`read_psarc_entries`); full unpack via `unpack_psarc()` 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`](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
|
||||
- **Globals** — `highway`, `audio`, `playSong()`, `showScreen()`, `createHighway()`, `window.slopsmith`
|
||||
- **Storage** — `localStorage` 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`](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`](docs/PLUGIN_AUTHORING.md)
|
||||
|
||||
Plugins are the primary extension point. Each lives in `plugins/<name>/` with a `plugin.json` manifest. Curated plugins must be AGPL-3.0 or AGPL-compatible — see [`CONTRIBUTING.md`](CONTRIBUTING.md) for the allowlist. Manifest is validated in CI against [`schema/plugin.schema.json`](schema/plugin.schema.json).
|
||||
|
||||
Topic | Doc
|
||||
--- | ---
|
||||
Manifest reference (`plugin.json` fields) | [`docs/plugin-manifest.md`](docs/plugin-manifest.md)
|
||||
Visualization (setRenderer / overlay / note-state) | [`docs/plugin-visualization-contracts.md`](docs/plugin-visualization-contracts.md)
|
||||
Audio mixer fader registration | [`docs/plugin-audio-mixer.md`](docs/plugin-audio-mixer.md)
|
||||
Backend `context["log"]` logging | [`docs/plugin-logging.md`](docs/plugin-logging.md)
|
||||
Diagnostics opt-in (export bundle) | [`docs/plugin-diagnostics.md`](docs/plugin-diagnostics.md)
|
||||
Keyboard shortcuts (`registerShortcut`) | [`docs/plugin-keyboard-shortcuts.md`](docs/plugin-keyboard-shortcuts.md)
|
||||
Multi-file backends (`load_sibling`) | [`docs/plugin-sibling-imports.md`](docs/plugin-sibling-imports.md)
|
||||
WebSocket highway protocol | [`docs/websocket-protocol.md`](docs/websocket-protocol.md)
|
||||
Testing plugins (pytest + Playwright) | [`docs/testing-plugins.md`](docs/testing-plugins.md)
|
||||
Diagnostics bundle layout | [`docs/diagnostics-bundle-spec.md`](docs/diagnostics-bundle-spec.md)
|
||||
Sloppak format spec | [`docs/sloppak-spec.md`](docs/sloppak-spec.md)
|
||||
Tuning the note_detect plugin | [`docs/note-detect-tuning.md`](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`](docs/plugin-sibling-imports.md).
|
||||
|
||||
2. **`playSong` wrapper race condition.** Plugins commonly wrap `window.playSong`. Wrappers chain outermost-first (last-loaded runs first). If an inner wrapper does `await import(CDN)`, it yields to the event loop and WebSocket messages (`song_info`, `ready`) can arrive before outer wrappers finish setup. Use `getSongInfo()` as a fallback, not `_onReady` alone.
|
||||
|
||||
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)
|
||||
|
||||
```bash
|
||||
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/`:
|
||||
|
||||
```bash
|
||||
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`](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`](docs/plugin-sibling-imports.md).
|
||||
- **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`](CLAUDE.md) — points back here. Exists for Claude Code's auto-loading convention. Claude-specific *automation* lives in [`.claude/`](.claude/) (skills, subagents, rules, settings).
|
||||
- [`.github/copilot-instructions.md`](.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/`.
|
||||
Reference in New Issue
Block a user