From ff7c6c76d15fc77acc860c6ef660b2f401af9c5b Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 13 Aug 2026 02:40:19 +0000 Subject: [PATCH 1/4] Point the README at the canonical repo and current product name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The install snippet cloned got-feedback/feedback-plugin-sectionmap, which is not where this repo lives — anyone following it landed on the wrong remote. Also updates the pre-rename "Slopsmith" naming and the /path/to/slopsmith/plugins example. The clone target directory stays `section_map`: it has to match plugin.json's id exactly or the Host skips the plugin at discovery. No version bump — nothing outside the README changed. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01B4NYejYW2dW7dNtYh7srDy --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d16e2fc..84a88c0 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# Slopsmith Plugin: Section Map +# feedBack Plugin: Section Map -A plugin for [Slopsmith](https://github.com/got-feedback/feedback) that shows a minimap bar of the full song structure at the top of the player. Click any section to jump to it. +A plugin for [feedBack](https://github.com/get-flashbacks/feedBack) that shows a minimap bar of the full song structure at the top of the player. Click any section to jump to it. ## Features @@ -13,8 +13,8 @@ A plugin for [Slopsmith](https://github.com/got-feedback/feedback) that shows a ## Installation ```bash -cd /path/to/slopsmith/plugins -git clone https://github.com/got-feedback/feedback-plugin-sectionmap.git section_map +cd /path/to/feedBack/plugins +git clone https://github.com/get-flashbacks/feedBack-plugin-sectionmap.git section_map docker compose restart ``` From a39d53b5395a9b2f54ba3e6676fa0d8497fa5ba3 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 13 Aug 2026 02:51:16 +0000 Subject: [PATCH 2/4] Add targeted plugin-spec compliance checks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This repo's ci.yml calls the shared reusable-ci.yml, which runs the test suites and nothing else. The org's original-repo plugins run their own custom-checks.yml instead, so enforcement was split down the middle: forks got tests but no spec checks, originals got spec checks but not always tests. A cross-repo audit found defects on both sides of that line. Three targeted jobs rather than the originals' full sixteen — version-bumped-on-change, changelog-updated, and idempotent-top-level-guard. These are the ones that map to defects actually found in these repos: versions and changelogs going stale while functional source shipped, and screen.js re-executing on reload without a guard. Jobs self-skip where they do not apply. The right home for these is got-feedback/.github's reusable-ci.yml, so every repo picks them up from one reference instead of four copies. The file says so, and says to delete it when that lands. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01B4NYejYW2dW7dNtYh7srDy --- .github/workflows/compliance.yml | 140 +++++++++++++++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 .github/workflows/compliance.yml diff --git a/.github/workflows/compliance.yml b/.github/workflows/compliance.yml new file mode 100644 index 0000000..4ee33e2 --- /dev/null +++ b/.github/workflows/compliance.yml @@ -0,0 +1,140 @@ +name: compliance + +# A targeted subset of the plugin-spec checks that the org's original-repo +# plugins run via their own custom-checks.yml. This repo's ci.yml calls the +# shared reusable-ci.yml, which runs the test suites and nothing else — so +# before this file, fork-derived plugins got tests but no spec enforcement +# while the originals got enforcement but (in one case) no tests. +# +# Deliberately not the full 16-job set. These three are the ones that map to +# defects a cross-repo audit actually found in these repos: versions and +# changelogs going stale while functional source shipped, and screen.js +# re-executing on plugin reload without a guard. Jobs self-skip when they +# don't apply (no CHANGELOG.md, no screen.js). +# +# The right long-term home for these is got-feedback/.github's +# reusable-ci.yml, so every repo picks them up from one reference. Delete +# this file when that lands. + +on: + pull_request: + branches: [main] + push: + branches: [main] + +permissions: + contents: read + +env: + PLUGIN_DIR: "." + +jobs: + version-bumped-on-change: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: plugin.json version must bump when functional source changes + run: | + set -eu + BASE="${{ github.event.pull_request.base.sha }}" + if [ -z "$BASE" ]; then + BASE="${{ github.event.before }}" + fi + if [ -z "$BASE" ] || ! git cat-file -e "$BASE" 2>/dev/null; then + echo "No usable base commit to diff against — skipping" + exit 0 + fi + DIR="$PLUGIN_DIR" + CHANGED=$(git diff --name-only "$BASE" HEAD -- "$DIR" || true) + FUNCTIONAL=$(echo "$CHANGED" | grep -E '\.(py|js|html)$' | grep -vE '(^|/)tests?/' | grep -v 'plugin\.json$' || true) + if [ -z "$FUNCTIONAL" ]; then + echo "No functional source changed — skipping" + exit 0 + fi + MANIFEST_CHANGED=$(git diff --name-only "$BASE" HEAD -- "$DIR/plugin.json" || true) + if [ -z "$MANIFEST_CHANGED" ]; then + echo "::error file=$DIR/plugin.json::functional source changed ($FUNCTIONAL) but plugin.json was not — bump the version" + exit 1 + fi + OLD_VERSION=$(git show "$BASE:$DIR/plugin.json" 2>/dev/null | python3 -c "import json,sys; print(json.load(sys.stdin).get('version',''))" || echo "") + NEW_VERSION=$(python3 -c "import json; print(json.load(open('$DIR/plugin.json')).get('version',''))") + if [ "$OLD_VERSION" = "$NEW_VERSION" ]; then + echo "::error file=$DIR/plugin.json::plugin.json changed but version is still $NEW_VERSION — bump it" + exit 1 + fi + echo "OK: version bumped $OLD_VERSION -> $NEW_VERSION" + + idempotent-top-level-guard: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Top-level listeners/timers must be inside a reload guard + run: | + python3 - <<'PY' + import os, re, sys + + plugin_dir = os.environ["PLUGIN_DIR"] + js_path = os.path.join(plugin_dir, "screen.js") + if not os.path.isfile(js_path): + print("No screen.js — skipping") + raise SystemExit(0) + + src = open(js_path, encoding="utf-8").read() + has_hotpath = bool(re.search(r'(window\.addEventListener|document\.addEventListener|setInterval)\s*\(', src)) + # Guard naming varies across the org (__xInstalled, __xHooksInstalled, + # __feedBackDynamicDifficulty, __ddCardBadgeRegistered, ...) — no + # single suffix covers them, so just require *some* window-level __ + # flag rather than pattern-matching a specific naming style. + # + # Both access forms count. section_map, the org's reference guard, + # hoists the name into a const and uses window[HOOK_KEY]; matching + # only the dotted form flags it as unguarded when it is not. + dotted = re.search(r'window\.__\w+', src) + bracket_literal = re.search(r'window\s*\[\s*[\'"]__\w+[\'"]\s*\]', src) + bracket_const = ( + re.search(r'window\s*\[\s*[A-Za-z_$][\w$]*\s*\]', src) + and re.search(r'[\'"]__\w+[\'"]', src) + ) + has_guard = bool(dotted or bracket_literal or bracket_const) + + if has_hotpath and not has_guard: + print(f"::error file={js_path}::found addEventListener/setInterval but no window.__*Installed-style reload guard — screen.js can re-execute on plugin reload") + sys.exit(1) + print("OK: no unguarded top-level listeners detected (or none present)") + PY + + changelog-updated: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: CHANGELOG.md Unreleased section must be updated for functional changes + run: | + set -eu + if [ ! -f CHANGELOG.md ]; then + echo "No CHANGELOG.md in this repo — skipping" + exit 0 + fi + BASE="${{ github.event.pull_request.base.sha }}" + if [ -z "$BASE" ]; then + BASE="${{ github.event.before }}" + fi + if [ -z "$BASE" ] || ! git cat-file -e "$BASE" 2>/dev/null; then + echo "No usable base commit to diff against — skipping" + exit 0 + fi + FUNCTIONAL=$(git diff --name-only "$BASE" HEAD -- "$PLUGIN_DIR" | grep -E '\.(py|js|html)$' | grep -vE '(^|/)tests?/' || true) + if [ -z "$FUNCTIONAL" ]; then + echo "No functional source changed — skipping" + exit 0 + fi + CHANGELOG_DIFF=$(git diff --name-only "$BASE" HEAD -- CHANGELOG.md || true) + if [ -z "$CHANGELOG_DIFF" ]; then + echo "::error file=CHANGELOG.md::functional source changed but CHANGELOG.md was not updated" + exit 1 + fi + echo "OK: CHANGELOG.md was updated" + From e52997b5e9f1b5eb67c63d5ccea11cca5c8fe7fa Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 13 Aug 2026 03:05:31 +0000 Subject: [PATCH 3/4] docs(readme): link the canonical app repo; harden compliance checks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review feedback on PR #8. The app link is the canonical got-feedBack/feedBack, not this fork. The original link (got-feedback/feedback) already resolved there via GitHub's org-casing normalisation, so retargeting it at get-flashbacks was a regression, not a fix. The plugin's own clone URL stays on get-flashbacks, which is where this repo actually lives. Compliance workflow, same fixes as the sibling repos: diff against the merge base rather than the base tip — demonstrated in this repo, where base a65c7d3 differs from merge-base 738997b, so a stale branch could be failed for a version bump it does not owe. Also require the [Unreleased] section itself to change, count .css as functional source, and record the guard job's known limitation: it proves a guard exists somewhere in the file, not that every hotpath sits inside one. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01B4NYejYW2dW7dNtYh7srDy --- .github/workflows/compliance.yml | 32 ++++++++++++++++++++++++++------ README.md | 2 +- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/.github/workflows/compliance.yml b/.github/workflows/compliance.yml index 4ee33e2..e989d39 100644 --- a/.github/workflows/compliance.yml +++ b/.github/workflows/compliance.yml @@ -46,9 +46,14 @@ jobs: echo "No usable base commit to diff against — skipping" exit 0 fi + # Diff against the MERGE BASE, not the base tip. On pull_request, + # github.event.pull_request.base.sha is main's live tip, so a + # two-dot diff also reports commits that landed on main after this + # branch forked — failing the PR for a bump it does not owe. + BASE=$(git merge-base "$BASE" HEAD || echo "$BASE") DIR="$PLUGIN_DIR" CHANGED=$(git diff --name-only "$BASE" HEAD -- "$DIR" || true) - FUNCTIONAL=$(echo "$CHANGED" | grep -E '\.(py|js|html)$' | grep -vE '(^|/)tests?/' | grep -v 'plugin\.json$' || true) + FUNCTIONAL=$(echo "$CHANGED" | grep -E '\.(py|js|html|css)$' | grep -vE '(^|/)tests?/' | grep -v 'plugin\.json$' || true) if [ -z "$FUNCTIONAL" ]; then echo "No functional source changed — skipping" exit 0 @@ -99,6 +104,10 @@ jobs: ) has_guard = bool(dotted or bracket_literal or bracket_const) + # Known limitation: this proves a guard EXISTS somewhere in the + # file, not that every hotpath sits inside one. A new unguarded + # listener added alongside an existing guard still passes. It is a + # cross-repo floor, not proof of idempotency. if has_hotpath and not has_guard: print(f"::error file={js_path}::found addEventListener/setInterval but no window.__*Installed-style reload guard — screen.js can re-execute on plugin reload") sys.exit(1) @@ -126,15 +135,26 @@ jobs: echo "No usable base commit to diff against — skipping" exit 0 fi - FUNCTIONAL=$(git diff --name-only "$BASE" HEAD -- "$PLUGIN_DIR" | grep -E '\.(py|js|html)$' | grep -vE '(^|/)tests?/' || true) + # Diff against the MERGE BASE, not the base tip. On pull_request, + # github.event.pull_request.base.sha is main's live tip, so a + # two-dot diff also reports commits that landed on main after this + # branch forked — failing the PR for a bump it does not owe. + BASE=$(git merge-base "$BASE" HEAD || echo "$BASE") + FUNCTIONAL=$(git diff --name-only "$BASE" HEAD -- "$PLUGIN_DIR" | grep -E '\.(py|js|html|css)$' | grep -vE '(^|/)tests?/' || true) if [ -z "$FUNCTIONAL" ]; then echo "No functional source changed — skipping" exit 0 fi - CHANGELOG_DIFF=$(git diff --name-only "$BASE" HEAD -- CHANGELOG.md || true) - if [ -z "$CHANGELOG_DIFF" ]; then - echo "::error file=CHANGELOG.md::functional source changed but CHANGELOG.md was not updated" + # Touching the file is not enough — editing an already-released + # section while [Unreleased] stays put is the exact slip this is + # meant to catch. Compare the [Unreleased] section itself. + unreleased() { + git show "$1:CHANGELOG.md" 2>/dev/null \ + | awk '/^## \[Unreleased\]/{f=1;next} /^## \[/{f=0} f' + } + if [ "$(unreleased "$BASE")" = "$(unreleased HEAD)" ]; then + echo "::error file=CHANGELOG.md::functional source changed but CHANGELOG.md's [Unreleased] section did not (a release cut that empties it also counts as a change)" exit 1 fi - echo "OK: CHANGELOG.md was updated" + echo "OK: CHANGELOG.md [Unreleased] changed" diff --git a/README.md b/README.md index 84a88c0..b704a0c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # feedBack Plugin: Section Map -A plugin for [feedBack](https://github.com/get-flashbacks/feedBack) that shows a minimap bar of the full song structure at the top of the player. Click any section to jump to it. +A plugin for [feedBack](https://github.com/got-feedBack/feedBack) that shows a minimap bar of the full song structure at the top of the player. Click any section to jump to it. ## Features From 8c7f7910d3b9c0ab3168c00d194b96452aeb88db Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 13 Aug 2026 03:09:34 +0000 Subject: [PATCH 4/4] docs(readme): install from the canonical repo, not this fork MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The install snippet cloned this fork. That makes the plugin fork-specific — anyone following the README ends up tracking get-flashbacks rather than the repo the plugin actually comes from, and the plugin stops being useful to its upstream. got-feedBack/feedBack-plugin-sectionmap is the original; this checkout is a fork of it. Point the clone URL there, matching the app link that was already corrected to got-feedBack/feedBack. The original URL (got-feedback/feedback-plugin-sectionmap) resolved to the canonical repo via GitHub's org-casing normalisation, so retargeting it at get-flashbacks was the regression this undoes; the casing is now explicit rather than relying on that normalisation. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01B4NYejYW2dW7dNtYh7srDy --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b704a0c..d8b95bb 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ A plugin for [feedBack](https://github.com/got-feedBack/feedBack) that shows a m ```bash cd /path/to/feedBack/plugins -git clone https://github.com/get-flashbacks/feedBack-plugin-sectionmap.git section_map +git clone https://github.com/got-feedBack/feedBack-plugin-sectionmap.git section_map docker compose restart ```