# Agent rules for the `linker` repo (reddit → spotify/bandcamp) A Firefox extension + a tiny Node resolver that turn artist/band names in reddit comments into Spotify/Bandcamp links. This file is the repo-local doctrine; the work is tracked in the **towl** work-item store under project **`reddit-spotify-linker`**. ## Stack & layout - `service/` — zero-dependency Node resolver (Node 18+, built-in `fetch`/`http`). `resolver.js` = the thin HTTP server that wires the real Spotify search + ollama classify into resolve-core; `resolve-core.js` = the pure resolve orchestration (`resolveAll`/`resolveOne`/`pickDirect`/`cacheKey`, taking injected search/classify fns — unit-testable without network); `lib.js` = pure, side-effect-free logic (the exact-name match precision lever + link shaping) so it's unit-testable; `llm.js` = pure ollama prompt/parse helpers (the optional local-LLM gate, HTTP-only — no in-process model runtime); `lib.test.js` + `llm.test.js` + `resolver.test.js` = `node:test`. The only music API is Spotify (MusicBrainz removed). The resolver confirms **both artists and albums**: one `type=artist,album` Spotify search per candidate, exact case-insensitive name match against either; each result carries `kind: "artist" | "album"`. - `extension/` — Firefox **MV2** content script (`content.js` + `extract.js` + `manifest.json` + `styles.css`). No build step. `extract.js` is the pure candidate extractor (Title-Case runs + cue phrases for artists; quoted strings + album cue phrases for albums) — node-testable via `extract.test.cjs`. Album links render italicised (`.rsl-album`). - `scripts/check.sh` — the local check gate. ## How work lands (IMPORTANT — this is NOT the towl repo) `linker` is hosted at **git.yapplesauce.com/paul/linker**, a **plain repo**: there is **no PR pipeline and no branch protection**. You **push to `main` directly** (via the user's PAT). Do **not** use `towl-orchestrate` / worktree+PR here. 1. Make a small, reversible change. 2. Run the gate: `bash scripts/check.sh` (must print `ALL CHECKS PASSED`). 3. Commit citing the towl ref (e.g. `(task/odesli-direct-links)`), push `main`. 4. Stamp the SHA on the towl item: `stamp_commit { ref, sha, repo: "linker" }`. 5. Close the towl item. **NEVER touch the `towl` or `stickball` repos from work scoped to this project.** ## Testing (zero-dep) `node --test` over `service/*.test.js` exercises `lib.js` (norm + the exact-name match + link builders), `llm.js` (the classifier prompt/parse), and `resolve-core.js` (the album/artist orchestration — kindHint tie-break, kind-aware cache key, allowFallback gating — driven with injected fake search/classify fns, no network). Keep pure logic in `lib.js`/`resolve-core.js` (not `resolver.js`, which has the server side effect) so it stays testable without opening a port. `extension/extract.test.cjs` covers the pure extractor (artist/cue/album candidates) and must be extended with each detection change. **Add/extend a test with every logic change.** The extension's in-browser behavior is **not** headlessly testable in this environment — validate `content.js` with `node --check` and a documented manual check on a real reddit thread (`about:debugging` → load temporary add-on). ## Code stewards (towl plans under objective/linker-stewardship) Standing, language-appropriate stewards (adapted from towl's SCOUT→FILE doctrine; run them like any other steward — e.g. via a `/loop`). Each files findings into towl and fixes small things directly. - **quality** (`plan/steward-linker-quality`) — correctness + regressions; **owns and maintains `scripts/check.sh` and the `node:test` suite**. - **design** (`plan/steward-linker-design`) — detection precision/recall, the injected-link UX, README accuracy, reddit-DOM drift. - **security** (`plan/steward-linker-security`) — DOM injection (build nodes via `createElement`/`textContent`, **never `innerHTML`**; http(s) hrefs only), minimal extension permissions, bounded external API calls (no SSRF), secrets never logged/committed (`.env` is gitignored). ## Standing decisions - Firefox **MV2** stays — Mozilla has no deprecation timeline; do not migrate to MV3 speculatively. - **Optional local LLM is the recommended better-matching path** (human override, feedback 2026-06-18 — supersedes the old "no ML/NER" rule). The resolver may call ollama over HTTP (`OLLAMA_URL` enables it, `OLLAMA_MODEL` defaults `qwen2.5:3b`) to classify which candidates are real artists/albums. It stays **zero-NPM-dep**: HTTP only, no in-process model runtime. The heuristic + Spotify exact-match is the zero-dep default when no LLM is configured. - Detection precision lever (zero-dep default): a candidate links **only** when Spotify returns an **artist or album** whose name matches it case-insensitively. A confirmed name links to its Spotify artist/album page if available, else a " bandcamp" Google fallback. Album candidates are over-generated from precise signals only (quoted strings + album cue phrases) because album free-text is noisier than artist names — the Spotify exact-match (and/or LLM `kind=album`) is the filter.