mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-09-12 09:38:32 +00:00
fix: address PR #332 review feedback
Addresses 12 of 13 review comments from Copilot and CodeRabbit on PR #332. One comment (no-manifests in validate-plugins.yml) is declined and answered inline; the rest are applied here. Substantive fixes: - .github/workflows/validate-plugins.yml — add --noconftest to the schema-tests step. tests/conftest.py imports structlog at module level, but the CI job only installs requirements-test.txt (pytest/httpx/jsonschema), so pytest collection would fail at conftest import. The schema tests don't use shared fixtures, so skipping conftest is safe and avoids dragging the full runtime requirements into a 2 KB validation job. (Copilot) - schema/plugin.schema.json — tighten the server_files regex on both settings.server_files and diagnostics.server_files to match the runtime _validate_relpath rules in plugins/__init__.py. The previous regex only blocked absolute paths, drive letters, backslashes, and "..". The runtime also rejects "//", "./", "/./", and leading-dotfile segments. Schema-valid manifests are now also load-time-valid. Verified the regex against 12 cases: the 3 in-tree manifests still validate. (Copilot) - .claude/skills/plugin-validate/SKILL.md — add a per-iteration plugin_ok flag so we no longer print "OK <path>" after an earlier FAIL in the same manifest. Schema-pass + id-mismatch previously produced both FAIL and OK lines for one plugin. (CodeRabbit) - docs/websocket-protocol.md — clarify song_info.tuning array length is source-dependent (typically 6 guitar, 4 bass, but extended-range GP imports can be 7/8/5/6). Recommend highway.getStringCount() for the authoritative count. Line 30 already said this; the table row on line 12 was the stale half. (CodeRabbit) Trivial fixes: - .claude/rules/plugin-author.md — "wants included" -> "wants to include" in the settings.server_files rule. (CodeRabbit) - Markdown MD040 — add `text` language tags to 7 bare-fence code blocks across AGENTS.md, docs/PLUGIN_AUTHORING.md, docs/testing-plugins.md, docs/plugin-logging.md, .claude/README.md, .claude/agents/slopsmith-reviewer.md, and .claude/skills/plugin-validate/SKILL.md (two fences). (CodeRabbit) Declined: - .github/workflows/validate-plugins.yml no-manifests -> exit 0 (CodeRabbit suggested exit 1). Plugins in this repo are in-tree, not submodules (no .gitmodules, git submodule status empty), and the workflow has a path filter on plugins/**/plugin.json so it only runs when a manifest actually changes. Exit 0 is correct. Answered inline on the PR. Verification: pytest tests/test_plugin_schema.py -v --noconftest # 8 passed 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'))]" # ok — all 3 in-tree manifests validate against tightened schema Signed-off-by: Miguel_LZPF <mgcdreamer@gmail.com>
This commit is contained in:
+1
-1
@@ -4,7 +4,7 @@ This directory holds [Claude Code](https://claude.ai/code) artifacts: skills, ru
|
|||||||
|
|
||||||
## Layout
|
## Layout
|
||||||
|
|
||||||
```
|
```text
|
||||||
.claude/
|
.claude/
|
||||||
├── agents/ Subagents (invoked via @<name>)
|
├── agents/ Subagents (invoked via @<name>)
|
||||||
│ └── slopsmith-reviewer.md Plugin-aware code review
|
│ └── slopsmith-reviewer.md Plugin-aware code review
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ Run each item; structure the output as `PASS` / `FAIL` / `N/A` with file:line ci
|
|||||||
|
|
||||||
## Output format
|
## Output format
|
||||||
|
|
||||||
```
|
```text
|
||||||
plugin-review: <plugin_id>
|
plugin-review: <plugin_id>
|
||||||
=========================
|
=========================
|
||||||
1. manifest validates PASS
|
1. manifest validates PASS
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ These rules apply only when editing files under `plugins/**`. They encode the co
|
|||||||
## State and config
|
## State and config
|
||||||
|
|
||||||
- **`localStorage` keys must be prefixed with the plugin id** to avoid collisions.
|
- **`localStorage` keys must be prefixed with the plugin id** to avoid collisions.
|
||||||
- **`settings.server_files` declares config-dir paths the plugin wants included in the Settings export/import flow.** Relpaths only — no `..`, no abs paths, no backslashes. See [`docs/plugin-manifest.md`](../../docs/plugin-manifest.md).
|
- **`settings.server_files` declares config-dir paths the plugin wants to include in the Settings export/import flow.** Relpaths only — no `..`, no abs paths, no backslashes. See [`docs/plugin-manifest.md`](../../docs/plugin-manifest.md).
|
||||||
- **`diagnostics.server_files` / `diagnostics.callable`** declares what enters the Export Diagnostics bundle. Keep payloads under 100 KB and don't include secrets. See [`docs/plugin-diagnostics.md`](../../docs/plugin-diagnostics.md).
|
- **`diagnostics.server_files` / `diagnostics.callable`** declares what enters the Export Diagnostics bundle. Keep payloads under 100 KB and don't include secrets. See [`docs/plugin-diagnostics.md`](../../docs/plugin-diagnostics.md).
|
||||||
|
|
||||||
## Visualization specifics
|
## Visualization specifics
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ for path in sorted(glob.glob('plugins/*/plugin.json')):
|
|||||||
plugin_dir = pathlib.Path(path).parent
|
plugin_dir = pathlib.Path(path).parent
|
||||||
plugin_id = plugin_dir.name
|
plugin_id = plugin_dir.name
|
||||||
m = json.load(open(path))
|
m = json.load(open(path))
|
||||||
|
plugin_ok = True # per-iteration flag so we don't print OK after a later FAIL
|
||||||
# 1. Schema
|
# 1. Schema
|
||||||
try:
|
try:
|
||||||
jsonschema.validate(m, schema)
|
jsonschema.validate(m, schema)
|
||||||
@@ -42,16 +43,19 @@ for path in sorted(glob.glob('plugins/*/plugin.json')):
|
|||||||
if m['id'] != plugin_id:
|
if m['id'] != plugin_id:
|
||||||
print(f"FAIL {path}: id={m['id']!r} but directory is {plugin_id!r}")
|
print(f"FAIL {path}: id={m['id']!r} but directory is {plugin_id!r}")
|
||||||
ok = False
|
ok = False
|
||||||
|
plugin_ok = False
|
||||||
# 3. Declared files exist
|
# 3. Declared files exist
|
||||||
for field in ('script', 'routes', 'tour'):
|
for field in ('script', 'routes', 'tour'):
|
||||||
if field in m and not (plugin_dir / m[field]).exists():
|
if field in m and not (plugin_dir / m[field]).exists():
|
||||||
print(f"FAIL {path}: {field}={m[field]!r} but file missing")
|
print(f"FAIL {path}: {field}={m[field]!r} but file missing")
|
||||||
ok = False
|
ok = False
|
||||||
|
plugin_ok = False
|
||||||
if 'settings' in m and 'html' in m['settings']:
|
if 'settings' in m and 'html' in m['settings']:
|
||||||
h = m['settings']['html']
|
h = m['settings']['html']
|
||||||
if not (plugin_dir / h).exists():
|
if not (plugin_dir / h).exists():
|
||||||
print(f"FAIL {path}: settings.html={h!r} but file missing")
|
print(f"FAIL {path}: settings.html={h!r} but file missing")
|
||||||
ok = False
|
ok = False
|
||||||
|
plugin_ok = False
|
||||||
for field in ('settings', 'diagnostics'):
|
for field in ('settings', 'diagnostics'):
|
||||||
if field in m and 'server_files' in m[field]:
|
if field in m and 'server_files' in m[field]:
|
||||||
for relpath in m[field]['server_files']:
|
for relpath in m[field]['server_files']:
|
||||||
@@ -61,7 +65,9 @@ for path in sorted(glob.glob('plugins/*/plugin.json')):
|
|||||||
if '..' in relpath or relpath.startswith('/') or '\\' in relpath:
|
if '..' in relpath or relpath.startswith('/') or '\\' in relpath:
|
||||||
print(f"FAIL {path}: {field}.server_files contains unsafe path {relpath!r}")
|
print(f"FAIL {path}: {field}.server_files contains unsafe path {relpath!r}")
|
||||||
ok = False
|
ok = False
|
||||||
print(f"OK {path}")
|
plugin_ok = False
|
||||||
|
if plugin_ok:
|
||||||
|
print(f"OK {path}")
|
||||||
sys.exit(0 if ok else 1)
|
sys.exit(0 if ok else 1)
|
||||||
PY
|
PY
|
||||||
```
|
```
|
||||||
@@ -84,13 +90,13 @@ pytest tests/test_plugin_schema.py::test_schema_license_enum_subset_of_contribut
|
|||||||
|
|
||||||
Use the format from the script: `OK <path>` per validated manifest, `FAIL <path>: <reason>` per failure. Add a one-line summary:
|
Use the format from the script: `OK <path>` per validated manifest, `FAIL <path>: <reason>` per failure. Add a one-line summary:
|
||||||
|
|
||||||
```
|
```text
|
||||||
Result: 3/3 plugins valid (OK app_tour_library, app_tour_settings, highway_3d)
|
Result: 3/3 plugins valid (OK app_tour_library, app_tour_settings, highway_3d)
|
||||||
```
|
```
|
||||||
|
|
||||||
or
|
or
|
||||||
|
|
||||||
```
|
```text
|
||||||
Result: 2/3 plugins valid; 1 FAIL (see above)
|
Result: 2/3 plugins valid; 1 FAIL (see above)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -67,4 +67,8 @@ jobs:
|
|||||||
PY
|
PY
|
||||||
|
|
||||||
- name: Run schema sanity tests
|
- name: Run schema sanity tests
|
||||||
run: pytest tests/test_plugin_schema.py -v
|
# --noconftest skips tests/conftest.py, which imports structlog
|
||||||
|
# (not in requirements-test.txt). The schema tests don't use
|
||||||
|
# shared fixtures, so this is safe and avoids dragging the full
|
||||||
|
# runtime requirements into a 2 KB schema-validation job.
|
||||||
|
run: pytest tests/test_plugin_schema.py -v --noconftest
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ This file is the canonical orientation. Tool-specific automation (Claude skills/
|
|||||||
|
|
||||||
## Architecture quick reference
|
## Architecture quick reference
|
||||||
|
|
||||||
```
|
```text
|
||||||
server.py FastAPI app — library API, WebSocket highway, plugin loading
|
server.py FastAPI app — library API, WebSocket highway, plugin loading
|
||||||
main.py Programmatic uvicorn entrypoint — installs structlog before boot
|
main.py Programmatic uvicorn entrypoint — installs structlog before boot
|
||||||
logging_setup.py Structured logging + correlation IDs (LOG_LEVEL/LOG_FORMAT/LOG_FILE)
|
logging_setup.py Structured logging + correlation IDs (LOG_LEVEL/LOG_FORMAT/LOG_FILE)
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ This guide is the entry point. Each topic below has a dedicated doc — read wha
|
|||||||
|
|
||||||
## Quickstart
|
## Quickstart
|
||||||
|
|
||||||
```
|
```text
|
||||||
plugins/my_plugin/
|
plugins/my_plugin/
|
||||||
├── plugin.json Manifest (required) — see docs/plugin-manifest.md
|
├── plugin.json Manifest (required) — see docs/plugin-manifest.md
|
||||||
├── screen.html Optional — markup mounted at #plugin-my_plugin
|
├── screen.html Optional — markup mounted at #plugin-my_plugin
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ That logger inherits from the root `slopsmith` logger, which is configured by `l
|
|||||||
|
|
||||||
Look for your plugin's logger name in the console output:
|
Look for your plugin's logger name in the console output:
|
||||||
|
|
||||||
```
|
```text
|
||||||
INFO slopsmith.plugin.my_plugin: plugin ready
|
INFO slopsmith.plugin.my_plugin: plugin ready
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ Slopsmith has three test surfaces — Python unit/integration tests (pytest), JS
|
|||||||
|
|
||||||
## Test layout
|
## Test layout
|
||||||
|
|
||||||
```
|
```text
|
||||||
tests/
|
tests/
|
||||||
├── conftest.py Shared pytest fixtures (isolate_logging)
|
├── conftest.py Shared pytest fixtures (isolate_logging)
|
||||||
├── test_plugins.py Plugin loader + load_sibling + collision tests (includes reset_plugin_state)
|
├── test_plugins.py Plugin loader + load_sibling + collision tests (includes reset_plugin_state)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ Each connection receives the following JSON frames, roughly in this order:
|
|||||||
| Message | Shape | Description |
|
| Message | Shape | Description |
|
||||||
|---------|-------|-------------|
|
|---------|-------|-------------|
|
||||||
| `loading` | `{ type: 'loading', stage }` | Status/progress message during extraction or conversion. |
|
| `loading` | `{ type: 'loading', stage }` | Status/progress message during extraction or conversion. |
|
||||||
| `song_info` | `{ type, title, artist, arrangement, arrangement_index, arrangements, duration, tuning, capo, format, audio_url, audio_error, stems }` | Song metadata. `arrangements` is the full list for the switcher. `audio_url` is `null` when audio is unavailable, in which case `audio_error` is non-null; otherwise `audio_error` is `null`. `stems` is always present — an empty array for non-sloppak songs or sloppak songs with no split stems. `tuning` is an array (6 for guitar, 4 for bass). |
|
| `song_info` | `{ type, title, artist, arrangement, arrangement_index, arrangements, duration, tuning, capo, format, audio_url, audio_error, stems }` | Song metadata. `arrangements` is the full list for the switcher. `audio_url` is `null` when audio is unavailable, in which case `audio_error` is non-null; otherwise `audio_error` is `null`. `stems` is always present — an empty array for non-sloppak songs or sloppak songs with no split stems. `tuning` is an array whose length depends on the source arrangement (typically 6 for guitar, 4 for bass, but extended-range GP imports can be 7/8 for guitar or 5/6 for bass — use `highway.getStringCount()` for the authoritative count). |
|
||||||
| `beats` | `{ type, data: [{ time, measure }] }` | Beat timestamps with measure numbers. |
|
| `beats` | `{ type, data: [{ time, measure }] }` | Beat timestamps with measure numbers. |
|
||||||
| `sections` | `{ type, data: [{ time, name }] }` | Named sections (Intro, Verse, Chorus, etc.). |
|
| `sections` | `{ type, data: [{ time, name }] }` | Named sections (Intro, Verse, Chorus, etc.). |
|
||||||
| `anchors` | `{ type, data: [{ time, fret, width }] }` | Fret zoom anchors. |
|
| `anchors` | `{ type, data: [{ time, fret, width }] }` | Fret zoom anchors. |
|
||||||
|
|||||||
@@ -92,8 +92,8 @@
|
|||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"not": { "pattern": "^/|^[a-zA-Z]:|\\\\|(^|/)\\.\\.(/|$)" },
|
"not": { "pattern": "^/|^[a-zA-Z]:|\\\\|(^|/)\\.\\.(/|$)|//|(^|/)\\.(/|$)|^\\." },
|
||||||
"description": "Relpath under context['config_dir']. No abs paths, no '..', no backslashes. Trailing '/' denotes a directory (recurse)."
|
"description": "Relpath under context['config_dir']. No abs paths, no '..', no '//', no './', no leading dotfiles, no backslashes. Mirrors the runtime _validate_relpath rules in plugins/__init__.py so a schema-valid manifest is also load-time-valid. Trailing '/' denotes a directory (recurse)."
|
||||||
},
|
},
|
||||||
"uniqueItems": true,
|
"uniqueItems": true,
|
||||||
"description": "Opt-in for Settings export/import (slopsmith#113). Files included in user-triggered backups. See docs/plugin-manifest.md."
|
"description": "Opt-in for Settings export/import (slopsmith#113). Files included in user-triggered backups. See docs/plugin-manifest.md."
|
||||||
@@ -108,10 +108,10 @@
|
|||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"not": { "pattern": "^/|^[a-zA-Z]:|\\\\|(^|/)\\.\\.(/|$)" }
|
"not": { "pattern": "^/|^[a-zA-Z]:|\\\\|(^|/)\\.\\.(/|$)|//|(^|/)\\.(/|$)|^\\." }
|
||||||
},
|
},
|
||||||
"uniqueItems": true,
|
"uniqueItems": true,
|
||||||
"description": "Files copied verbatim into plugins/<id>/<relpath> inside the diagnostics bundle."
|
"description": "Files copied verbatim into plugins/<id>/<relpath> inside the diagnostics bundle. Path rules match the settings.server_files pattern (no abs paths, no '..', no '//', no './', no leading dotfiles, no backslashes)."
|
||||||
},
|
},
|
||||||
"callable": {
|
"callable": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|||||||
Reference in New Issue
Block a user