Skip to content

chore(discord-bot): gate JS on ESLint + Prettier, matching the ruff pair - #192

Merged
qiuethan merged 2 commits into
stagingfrom
chore/bot-lint-format
Aug 9, 2026
Merged

chore(discord-bot): gate JS on ESLint + Prettier, matching the ruff pair#192
qiuethan merged 2 commits into
stagingfrom
chore/bot-lint-format

Conversation

@qiuethan

@qiuethan qiuethan commented Aug 9, 2026

Copy link
Copy Markdown
Collaborator

What this changes

The discord-bot now has lint and format enforcement, gated in CI, mirroring the ruff check + ruff format --check pair every Python service already carries.

  • ESLint 9 (flat config, eslint.config.js) for correctness only.
  • Prettier (.prettierrc.json) for formatting, printWidth: 100 to match ruff's line-length = 100.
  • eslint-config-prettier last in the config array, so ESLint never argues with Prettier about style.
  • New scripts: lint, lint:fix, format, format:check.
  • node-test runs npm run lint and npm run format:check after npm test. No new job — the ten required status checks are unchanged.

Why

The bot was the only CODEOWNERS zone with no style enforcement at all: ~80 JS files, no eslint, no prettier, no lint script, and a CI job that ran node --test and nothing else. Every Python service is fully gated. With rotating student maintainers, that asymmetry is exactly where style drifts.

Found during an onboarding-readiness sweep of the repo.

Zone

discord-bot — plus a 7-line step addition to .github/workflows/ci.yml and the doc updates below. It spans zones only because enforcement is meaningless without the CI hook; splitting them would merge a linter nobody runs.

About the diff size

~3,300 lines, and all but 5 of them are the one-time reformat.

This is verifiable rather than a promise. Running Prettier over staging and diffing the result against this branch yields exactly five changes, all of them the unused variables ESLint flagged:

File Change
src/linkService.js linkByEmail destructured discordHandle and never used it (its sibling confirmAndLink does)
test/teamService.test.js TeamNotFound imported, never used
test/meetingClient.test.js assigned openStream result never read — call retained
test/recorder.test.js (x2) assigned startedRecorder result never read — await retained

Nothing else differs. Per AGENTS.md a formatting-only diff is fine on its own; this is that, plus five named dead-variable removals.

ignoreRestSiblings is enabled so the existing omit idiom (const { flags, ...editable } = dpayload) remains legal — that pattern is deliberate, not dead code.

How to verify

cd discord-bot
npm ci
npm test
npm run lint          # clean
npm run format:check  # clean

To confirm the reformat is semantically inert, reproduce the check above:

git archive staging discord-bot | tar -x -C /tmp/base
cp discord-bot/.prettierrc.json discord-bot/.prettierignore /tmp/base/discord-bot/
cd /tmp/base/discord-bot && npx prettier --write "src/**/*.js" "test/**/*.js" "scripts/**/*.js"
diff -ru /tmp/base/discord-bot/src <repo>/discord-bot/src   # only linkService.js
diff -ru /tmp/base/discord-bot/test <repo>/discord-bot/test # only the three test files

npm test is 33/35 on my machine — identical to the pre-change baseline. Both failures are Node 18 only (node:test exports no mock; fastify needs diagnostics.tracingChannel) and pass on CI's Node 20. A follow-up PR enforces the Node floor so this stops being a trap.

Docs updated

  • AGENTS.md — bot pre-push command; the lint-gating note now covers JS
  • docs/DEVELOPMENT.md — lint step in "Make your first contribution"
  • docs/DEPLOYMENT-HISTORY.mdnode-test job description
  • discord-bot/docs/CONTRIBUTING.md — pre-push checklist

🤖 Generated with Claude Code

The bot was the one zone with no style enforcement. Every Python service is
gated on `ruff check` and `ruff format --check`; `node-test` ran only
`node --test`, leaving ~80 JS files unlinted. With rotating maintainers that
asymmetry drifts.

Adds ESLint 9 (flat config) for correctness and Prettier for formatting, with
eslint-config-prettier last so the two can't disagree. `printWidth` is 100 to
match the `line-length = 100` the Python services already set for ruff. Both
tools are scoped to `*.js` on purpose — the Markdown in this repo is
hand-wrapped and Prettier must not reflow it.

`node-test` now runs `npm run lint` and `npm run format:check` after the suite.
No new CI job, so the ten required checks are unchanged.

The bulk of this diff is the one-time reformat. It is formatting-only: running
Prettier over staging and diffing against this branch yields exactly the five
unused-variable removals ESLint flagged, and nothing else --

  - src/linkService.js        linkByEmail destructured an unused discordHandle
  - test/teamService.test.js  TeamNotFound imported, never used
  - test/meetingClient.test.js / test/recorder.test.js (x2)
                              assigned call results never read; the calls stay

`ignoreRestSiblings` is on, so the existing `const { flags, ...editable }` omit
idiom stays legal.

Verified: npm test 33/35 -- identical to the pre-change baseline on this
machine. The two failures are Node 18 only (`node:test` has no `mock` export;
fastify needs `diagnostics.tracingChannel`) and pass on CI's Node 20.
…gnore

Two follow-ups from reviewing the config rather than trusting it.

1. The browser block didn't do what it claimed. Flat config MERGES
   languageOptions.globals across every block whose `files` match, so layering
   a browser block over a `**/*.js` node block gave src/web/public/ both sets.
   Playground code could reference process, Buffer or __dirname and lint clean,
   then break in the page.

   Probed with deliberate errors, before -> after:

     process.env in a browser file   not flagged -> error
     document.title in a node file   error       -> error
     typo (documnt) in browser file  error       -> error

   Fixed by splitting rules from globals and giving the node block an
   `ignores: ['src/web/public/**']` so the two environments can't overlap.
   Real codebase still lints and format-checks clean.

2. Added .git-blame-ignore-revs. The reformat touched 60 files, and without
   this `git blame` there answers "who ran Prettier" instead of "who wrote
   this" -- the wrong answer in a repo whose whole point is surviving turnover.

   The file ships with the mechanism and instructions but no SHA yet, on
   purpose: this repo squash-merges, so the branch SHA won't exist on staging
   after merge. It carries a TODO to append the squashed SHA in a follow-up.
@qiuethan

qiuethan commented Aug 9, 2026

Copy link
Copy Markdown
Collaborator Author

Review follow-up: fixed a real bug in the config

Reviewing this instead of trusting it turned up a defect worth catching before merge.

The browser block didn't do what it claimed

ESLint flat config merges languageOptions.globals across every block whose files match. Layering a browser block on top of a **/*.js node block therefore gave src/web/public/ both sets — so playground code could reference process, Buffer, or __dirname, lint clean, and break in the page.

Probed with deliberate errors:

Probe Before After
process.env in a browser file not flagged ❌ error ✓
document.title in a node file error ✓ error ✓
documnt.title typo in browser file error ✓ error ✓

Fixed by splitting the rules block from the globals blocks and giving the node one ignores: ['src/web/public/**'], so the two environments can't overlap. Real codebase still passes eslint . and prettier --check clean.

Small blast radius — 2 files, and genuine typos were always caught — but the config asserted an isolation it didn't provide.

.git-blame-ignore-revs

The reformat touches 60 files, so git blame there would answer "who ran Prettier" rather than "who wrote this" — the wrong answer in a repo whose premise is surviving turnover.

One thing needs doing after merge. This repo squash-merges (checked: recent non-dependabot merges have a single parent and a (#N) suffix), so ed153cc won't exist on staging afterward and a SHA listed now would silently match nothing. The file ships with the mechanism, the git config blame.ignoreRevsFile instructions, and a TODOappend the squashed SHA in a follow-up commit once this lands. Happy to do that immediately after merge.

Zone

Now also touches repo root, since .git-blame-ignore-revs has to live there for GitHub to pick it up.

@qiuethan
qiuethan merged commit c970bc9 into staging Aug 9, 2026
11 checks passed
@qiuethan
qiuethan deleted the chore/bot-lint-format branch August 9, 2026 20:05
qiuethan added a commit that referenced this pull request Aug 9, 2026
Follow-up to #192, which could not carry its own SHA: this repo squash-merges,
so the commit did not exist until it landed on staging. It is c970bc9.

Measured effect on the worst-hit files -- lines git blame credited to the
formatter, before -> after:

  src/commands/team.js          55 of 188  ->  1
  src/adapters/discord.js       40 of 591  ->  0
  src/commands/doc.js           30 of 128  ->  1
  src/web/public/app.js         23 of 304  ->  0
  src/meeting/meetingSurface.js 22 of 259  ->  0

Nearly a third of team.js blamed on a formatting pass. Now it points at whoever
wrote the line.

GitHub applies this automatically in its blame view. Locally it is opt-in once
per clone:

    git config blame.ignoreRevsFile .git-blame-ignore-revs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant