From 4f9c8df95871687990d9c173ce0d1b530fa1f104 Mon Sep 17 00:00:00 2001 From: Izzy Katt Date: Tue, 29 Sep 2026 14:05:07 -0400 Subject: [PATCH] Revert the toolchain: back to the repository as it was before the flake MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restores every file to its state at f481e36 — the commit before "nix: pin the whole toolchain and mechanise the maintenance it guards" (#14). This undoes #14 through #22: the flake, the pinned browser, the CDP watch/verify tooling, the Claude Code setup, and the docs written around them. WHAT THIS DOES NOT TOUCH, and it is the whole reason a revert is safe here: neither userscript changed at any point in that range. thumbwall.user.js and leolist-listings-only.user.js are byte-identical at f481e36 and at main (e6804e698a52 and 4d9db4aa33bd). thumbwall stays at 5.0.0, leolist at 1.65.0. No published behaviour changes and no script work is lost. CI still gates. Both required checks are defined in the restored lint.yml under the same names branch protection asks for — "eslint + meta" and "node --check" — and scripts/meta-lint.mjs predates the range, so `npm run lint` remains the publish gate it always was. Removed: flake.nix, flake.lock, nix/mermaid-ascii.nix, .envrc, .claude/, CLAUDE.md, .github/workflows/nix.yml, the markdownlint configs and _typos.toml. Restored: .gitignore, eslint.config.mjs, lint.yml, CHANGELOG.md, CONTRIBUTING.md and README.md to their pre-flake content. A revert rather than a force-push: main is public and carries 22 merged pull requests, and rewriting that history would break every clone and branch taken from it. The tree is what matters and the tree is exact. --- .claude/.markdownlint.jsonc | 10 - .claude/commands/newscript.md | 41 -- .claude/commands/publish.md | 53 --- .claude/hooks/post-edit.mjs | 70 --- .claude/hooks/session-start.mjs | 66 --- .claude/hooks/stop-gate.mjs | 71 --- .claude/rules/flake-and-nix.md | 48 -- .claude/rules/listings.md | 42 -- .claude/settings.json | 96 ---- .envrc | 25 - .github/.markdownlint.jsonc | 10 - .github/workflows/lint.yml | 5 +- .github/workflows/nix.yml | 43 -- .gitignore | 18 +- .markdownlint-cli2.jsonc | 8 - .markdownlint.jsonc | 36 -- CHANGELOG.md | 346 +++----------- CLAUDE.md | 448 ----------------- CONTRIBUTING.md | 129 +---- README.md | 26 - _typos.toml | 28 -- eslint.config.mjs | 21 +- flake.lock | 27 -- flake.nix | 819 -------------------------------- listings/.markdownlint.jsonc | 18 - nix/mermaid-ascii.nix | 39 -- 26 files changed, 91 insertions(+), 2452 deletions(-) delete mode 100644 .claude/.markdownlint.jsonc delete mode 100644 .claude/commands/newscript.md delete mode 100644 .claude/commands/publish.md delete mode 100755 .claude/hooks/post-edit.mjs delete mode 100755 .claude/hooks/session-start.mjs delete mode 100755 .claude/hooks/stop-gate.mjs delete mode 100644 .claude/rules/flake-and-nix.md delete mode 100644 .claude/rules/listings.md delete mode 100644 .claude/settings.json delete mode 100644 .envrc delete mode 100644 .github/.markdownlint.jsonc delete mode 100644 .github/workflows/nix.yml delete mode 100644 .markdownlint-cli2.jsonc delete mode 100644 .markdownlint.jsonc delete mode 100644 CLAUDE.md delete mode 100644 _typos.toml delete mode 100644 flake.lock delete mode 100644 flake.nix delete mode 100644 listings/.markdownlint.jsonc delete mode 100644 nix/mermaid-ascii.nix diff --git a/.claude/.markdownlint.jsonc b/.claude/.markdownlint.jsonc deleted file mode 100644 index cb89efc..0000000 --- a/.claude/.markdownlint.jsonc +++ /dev/null @@ -1,10 +0,0 @@ -// Overrides for Claude Code's own project files. `extends` is load-bearing: -// without it this file REPLACES the root rule set rather than adding to it. -{ - "extends": "../.markdownlint.jsonc", - - // A slash command and a rule file are PROMPT BODIES, not documents. Their - // frontmatter carries the title, so a top-level heading inside would - // duplicate it. - "MD041": false -} diff --git a/.claude/commands/newscript.md b/.claude/commands/newscript.md deleted file mode 100644 index 2c80814..0000000 --- a/.claude/commands/newscript.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -description: Start a new userscript the right way — measure the live page first, then scaffold. -argument-hint: -allowed-tools: Bash, Read, Edit, Write, Glob, Grep ---- - -Start a new install unit named `$1` targeting `$2`. - -**Measure before you scaffold.** A selector that was not verified against the -live page is a guess, and guesses rot silently. - -1. **Survey the live page with every userscript disabled** — otherwise you are - measuring your own output. Follow **CLAUDE.md § Measuring the live page**: it - carries the exact `page-lab` invocations, the `--target-id` trap, and why - `selector-verify.mjs`'s `GENERATED` verdict is what enforces the - no-generated-class-names rule. Opening an adult site in the operator's - browser is **their** action — ask for the tab. - Record for each anchor: the selector, the **exact node count** it matched, - and the date. - -2. **Reject any anchor that is a generated class name, a localised - `aria-label`, or a tool-generated id.** Anchor on ARIA roles, `href` values - and data attributes instead. - -3. **Scaffold.** `nix run .#new-script -- $1` - This writes `$1.user.js` with the required metadata keys and the teardown - contract already wired, plus a `listings/$1.md` stub. It passes all three - gates as generated. - -4. **Fill in the header comment with the measurements from step 1**, then the - `@name`, `@description` and `@match` for `$2`. Decide Greasy Fork vs Sleazy - Fork now — an adult-site script uploaded to Greasy Fork gets rejected or - relocated. - -5. **Confirm the failure mode.** Break your own selector on purpose and check - the page renders **stock**, not mangled. That is the contract; a script that - mangles a page on a miss is a defect. - -6. `nix run .#lint`, add the README catalogue row, add a `CHANGELOG.md` entry - under `## [Unreleased]`, and exercise the site's primary actions in a - browser before opening anything. diff --git a/.claude/commands/publish.md b/.claude/commands/publish.md deleted file mode 100644 index e61a1bc..0000000 --- a/.claude/commands/publish.md +++ /dev/null @@ -1,53 +0,0 @@ ---- -description: Take a script from "changed" to "posted on Sleazy Fork", with every gate in order. -argument-hint: [patch|minor|major|x.y.z] -allowed-tools: Bash, Read, Edit, Glob, Grep ---- - -Publish `$1` at bump level `${2:-patch}`. - -Do these in order and **stop at the first failure** — report what failed and -what you need, rather than working around it. - -1. **Confirm the change is real and measured.** `git diff` the script. If a - selector changed, the header comment must carry the new measurement and its - date. A comment asserting behaviour the code does not perform is a defect — - fix the comment in this same change, not later. - -2. **Bump the version.** - `nix run .#bump -- $1 ${2:-patch}` - Never hand-edit the `@version` line; the command preserves the metadata - block's alignment and refuses anything that does not clear `origin/main`. - -3. **Write the CHANGELOG entry** under `## $1`, newest first, in Keep a - Changelog categories (`Added`/`Changed`/`Fixed`/`Removed`/`Security`). - Explain **why**, and include the measurement. "What" is in the diff. - -4. **Update `listings/$1.md`** if the behaviour changed. It is the Sleazy Fork - body, and an undisclosed behaviour is the most common reason a script is - taken down. Render any diagram with `nix run .#diagram`. - -5. **Run the gates.** `nix run .#lint`, then - `nix run .#publish-check -- $1`. Every line must read `ok`. - - Then check it on a real page: - `nix run .#verify -- --expect ` - That drives a real wheel event, so lazy content is measured rather than - assumed absent. - -6. **Exercise the site's primary actions** in a browser before you call it - done. Geometry proves a control is *present*, never that it *works* — and - it must work under a **trusted** event (CDP `Input.dispatchMouseEvent`, not - `el.click()`). CLAUDE.md § Measuring the live page has the tooling and the - injection traps; there are no acceptance specs in this repo yet, so this - step is manual and you must actually do it. - -7. **Branch, commit, push.** Subject `$1: what changed`; the body explains why - and carries the measurement. Never add AI attribution. Push the branch — - the **izzykatt-ci** App opens the pull request from that commit message and - squash-merges it once the checks are green. - -8. **Report what is left for a human**: posting the code and the listing body - on Sleazy Fork (select **Markdown** before pasting the body — a missed click - publishes raw `##` and fences), and adding the listing link to the README - catalogue row if this is a first publish. diff --git a/.claude/hooks/post-edit.mjs b/.claude/hooks/post-edit.mjs deleted file mode 100755 index 335ff1e..0000000 --- a/.claude/hooks/post-edit.mjs +++ /dev/null @@ -1,70 +0,0 @@ -#!/usr/bin/env node -// PostToolUse(Write|Edit). Two jobs, both cheap enough to run on every write: -// -// *.user.js -> parse it and run the Greasy Fork publish lint on THAT FILE. -// A userscript is copied verbatim into the browser, so a syntax -// error is a script that silently never runs. Catching it at the -// edit is worth far more than catching it in CI. -// *.nix -> git add it. A flake evaluates the git tree, so an unstaged new -// file is invisible to `nix flake check`. -// -// Exit 2 feeds stderr back to Claude to fix. Anything else never blocks. - -import { execFileSync } from 'node:child_process'; -import { existsSync } from 'node:fs'; -import { relative, isAbsolute } from 'node:path'; - -const root = process.env.CLAUDE_PROJECT_DIR || process.cwd(); - -let input = ''; -process.stdin.setEncoding('utf8'); -for await (const chunk of process.stdin) input += chunk; - -let file; -try { - file = JSON.parse(input)?.tool_input?.file_path; -} catch { - process.exit(0); -} -if (!file) process.exit(0); - -const rel = isAbsolute(file) ? relative(root, file) : file; -if (rel.startsWith('..')) process.exit(0); // outside the project - -const run = (cmd, args) => - execFileSync(cmd, args, { cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }); - -if (rel.endsWith('.nix')) { - try { - run('git', ['add', '--', rel]); - } catch { - /* not a git checkout, or the file is ignored — neither is worth blocking */ - } - process.exit(0); -} - -if (!rel.endsWith('.user.js')) process.exit(0); - -const problems = []; -try { - run('node', ['--check', rel]); -} catch (e) { - problems.push(`${rel} does not parse — it would silently never run:\n${e.stderr || e.message}`); -} - -if (existsSync(`${root}/scripts/meta-lint.mjs`)) { - try { - run('node', ['scripts/meta-lint.mjs', rel]); - } catch (e) { - problems.push(`${rel} fails the Greasy Fork publish lint:\n${e.stdout || ''}${e.stderr || ''}`); - } -} - -if (problems.length) { - console.error(problems.join('\n\n')); - process.exit(2); -} - -// There is no live-injection loop to revive here. A change reaches the browser -// only through a real Violentmonkey install, which is the operator's action — -// see CONTRIBUTING.md "Testing a change". diff --git a/.claude/hooks/session-start.mjs b/.claude/hooks/session-start.mjs deleted file mode 100755 index 82ca567..0000000 --- a/.claude/hooks/session-start.mjs +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env node -// SessionStart digest. Three things that are invisible until they bite: -// -// 1. whether ./node_modules is the flake-pinned tree or an npm install, -// 2. untracked .nix files (a flake evaluates the GIT TREE, so they are -// invisible to `nix flake check` and the result is silently stale), -// 3. each script's @version against origin/main — the difference between -// "ready to publish" and "a re-install would be a silent no-op". -// -// Never fails the session: every probe is wrapped, and the worst case is a -// shorter digest. - -import { execFileSync } from 'node:child_process'; -import { lstatSync, readlinkSync, readdirSync, readFileSync } from 'node:fs'; - -const root = process.env.CLAUDE_PROJECT_DIR || process.cwd(); -const out = []; - -const git = (args) => - execFileSync('git', args, { cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim(); - -const versionOf = (text) => /^\/\/\s*@version\s+(\S+)/m.exec(text)?.[1] ?? null; - -try { - const st = lstatSync(`${root}/node_modules`); - if (st.isSymbolicLink()) { - out.push( - readlinkSync(`${root}/node_modules`).includes('/nix/store/') - ? 'deps: node_modules is the flake-pinned tree.' - : 'deps: node_modules is a symlink OUTSIDE the Nix store.', - ); - } else { - out.push('deps: node_modules is an npm install — `nix run .#deps` pins it instead.'); - } -} catch { - out.push('deps: node_modules is ABSENT — run `nix run .#deps` (never `npm install`).'); -} - -try { - const untracked = git(['ls-files', '--others', '--exclude-standard', '--', '*.nix']); - if (untracked) { - out.push(`WARNING: untracked .nix files are INVISIBLE to nix flake check — git add them:\n ${untracked.split('\n').join('\n ')}`); - } -} catch { - /* not a git checkout; nothing to say */ -} - -try { - const scripts = readdirSync(root).filter((f) => f.endsWith('.user.js')).sort(); - for (const f of scripts) { - const head = versionOf(readFileSync(`${root}/${f}`, 'utf8')); - let base = null; - try { - base = versionOf(git(['show', `origin/main:${f}`])); - } catch { - /* new script, or no origin/main locally */ - } - if (head && base && head === base) out.push(`${f}: @version ${head} (same as origin/main — bump before publishing).`); - else if (head && base) out.push(`${f}: @version ${head}, origin/main has ${base}.`); - else if (head) out.push(`${f}: @version ${head} (not on origin/main yet).`); - } -} catch { - /* unreadable checkout; nothing to say */ -} - -if (out.length) console.log(out.join('\n')); diff --git a/.claude/hooks/stop-gate.mjs b/.claude/hooks/stop-gate.mjs deleted file mode 100755 index 97b4765..0000000 --- a/.claude/hooks/stop-gate.mjs +++ /dev/null @@ -1,71 +0,0 @@ -#!/usr/bin/env node -// Stop gate. Refuses to end a turn that leaves a changed userscript failing the -// two gates CI requires. The point is that "done" and "lints" mean the same -// thing here — a red gate discovered by CI costs a round trip, and a red gate -// discovered after publishing costs an install channel. -// -// Runs ONLY when a .user.js actually changed, so an unrelated turn pays -// nothing. `stop_hook_active` short-circuits the re-entry loop. - -import { execFileSync } from 'node:child_process'; -import { existsSync } from 'node:fs'; - -const root = process.env.CLAUDE_PROJECT_DIR || process.cwd(); - -let input = ''; -process.stdin.setEncoding('utf8'); -for await (const chunk of process.stdin) input += chunk; - -try { - if (JSON.parse(input)?.stop_hook_active) process.exit(0); -} catch { - /* no payload; carry on */ -} - -const run = (cmd, args) => - execFileSync(cmd, args, { cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }); - -let changed = []; -try { - changed = run('git', ['status', '--porcelain', '--', '*.user.js']) - .split('\n') - .map((l) => l.slice(3).trim()) - .filter(Boolean); -} catch { - process.exit(0); -} -if (changed.length === 0) process.exit(0); - -const problems = []; -for (const f of changed) { - if (!existsSync(`${root}/${f}`)) continue; // deleted - try { - run('node', ['--check', f]); - } catch (e) { - problems.push(`${f} does not parse:\n${e.stderr || e.message}`); - } -} - -try { - run('node', ['scripts/meta-lint.mjs']); -} catch (e) { - problems.push(`meta-lint failed:\n${e.stdout || ''}${e.stderr || ''}`); -} - -const eslint = `${root}/node_modules/.bin/eslint`; -if (existsSync(eslint)) { - try { - run(eslint, changed); - } catch (e) { - problems.push(`eslint failed:\n${e.stdout || ''}${e.stderr || ''}`); - } -} else { - problems.push('node_modules is absent, so ESLint did not run. `nix run .#deps` installs it offline.'); -} - -if (problems.length) { - console.error( - `A changed userscript does not pass the gates CI requires. Fix these, then stop:\n\n${problems.join('\n\n')}`, - ); - process.exit(2); -} diff --git a/.claude/rules/flake-and-nix.md b/.claude/rules/flake-and-nix.md deleted file mode 100644 index c2b618a..0000000 --- a/.claude/rules/flake-and-nix.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -description: Nix flake evaluation reads the git tree — stage before you evaluate. -paths: - - "flake.nix" - - "flake.lock" - - "nix/**/*.nix" ---- - -# Working on the flake - -## Stage before you evaluate - -A flake evaluates the **git tree**, not the working directory. A new file that -has not been `git add`ed is **invisible** to the evaluator, which surfaces as a -confusing `path does not exist` or, worse, a silently stale evaluation. - -```bash -git add -A && nix flake check -``` - -The `PostToolUse` hook stages `.nix` writes as a safety net. Do not rely on it -alone when authoring several files at once. - -## What each output is for - -- `checks.*` — every gate that runs **offline**, in the sandbox. Adding one here - means CI gets it for free via `.github/workflows/nix.yml`. -- `packages.*` / `apps.*` — the maintenance commands. Each is a - `writeShellApplication`, so **shellcheck runs at build time**: a broken - command fails `nix flake check`, not someone's afternoon. -- `devShells.default` — the CLIs. Add one only after `nix search nixpkgs ` - confirms it is packaged. - -Prefer adding a command to the flake over documenting a manual procedure in a -README. The command is checked; the README is not. - -## Two traps already paid for - -- **`SC2329` is excluded on purpose.** Every command shares one prelude and uses - only part of it; shellcheck cannot see the other callers. -- **`actionlint` carries two `-ignore` patterns** for - `actions/create-github-app-token`. Its bundled action metadata predates - `client-id` auth. Drop them when a newer actionlint stops reporting them. - -## Formatting - -`nix fmt .` uses `nixfmt-tree`. `checks.formatting` fails on anything -unformatted, so run it before pushing. diff --git a/.claude/rules/listings.md b/.claude/rules/listings.md deleted file mode 100644 index 43dafec..0000000 --- a/.claude/rules/listings.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -description: Listing bodies are pasted into Sleazy Fork's form — write and render them accordingly. -paths: - - "listings/**" ---- - -# Listing copy - -`listings/