Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions .claude/rules/comments.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
---
paths:
- "src/**"
- "test/**"
- "scripts/**"
---

# Comments

A code comment has exactly one purpose: to help a future maintainer change
this code correctly, by carrying the constraints the code and tests cannot
carry themselves. A comment addressed to anyone else (a reviewer, a diff
reader, the author's process) does not belong in the source.

A comment may only state a constraint the code cannot show: an external fact
(a library's hidden behavior, a remote API quirk, a verified real-world
payload) or the why of a deliberately surprising choice. Never write a comment
that:

- Explains what the adjacent code plainly shows, or restates a test name, an
assertion, or a log line next to it.
- Tells past-tense history, justifies a change, or talks to a reviewer ("used
to", "previously", "fixed", "split out from", "now", referencing a bug story
or issue number): the next reader sees only the final code.
- Guards a behavior a test could pin: if the constraint is testable and
untested, write the test, not the comment. A comment survives alongside a
test only where the code locally reads as a mistake (error swallowing, odd
ordering); then say why, clearly.
- Pads with redundant contrast ("X, not Y" where Y is merely not-X): state
the constraint once, positively.

No literal em or en dashes in any tracked .ts file, comments and
user-facing strings included (scripts/check-dashes.sh gates the staged
content locally and in CI); use a comma, colon, or parenthetical. A fixture
or quoted payload that genuinely needs the character uses the \u2013 or
\u2014 escape, which the gate does not match.

Every comment must pass the strip test: remove it, and if a competent reader
could recover the content from the code, the names, the contracts of the
functions called, and the tests, it should not exist. A causal veneer
("so ...", "because ...") does not turn narration into a why. A comment sits
ON the line it explains. Show, don't tell: real payloads and verified
behaviors belong in test fixtures, and once a test pins a constraint, a
comment restating it dies. Length is never the criterion: an
unneeded explanation is noise at any length, and a needed one gets however
many words it takes to explain clearly. A long comment on something simple is a
smell that warrants checking whether the content is needed at all, never a
violation by itself. The file's existing comment density
is not a license: judge each comment alone, and prefer a clearer name or a
test over any comment. When a review-refuted finding keeps resurfacing, first
try to make the code read right; an intent pin at the flagged site is the
last resort.
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ jobs:
- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile
- run: bunx oxlint --deny-warnings
# same dash gate as the local hook, for pushes that bypassed it
- run: ./scripts/check-dashes.sh
# db.ts opens /storage/mp4ify.db and blob-store.ts creates /storage/blobs
# at import time, so /storage must exist and be writable
- run: sudo mkdir -p -m 777 /storage
Expand Down
2 changes: 2 additions & 0 deletions check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ else
exit 1
fi

./scripts/check-dashes.sh

# `timeout` runs inside the container, so a hung/non-exiting `bun test` (a leaked
# handle, a runaway loop) self-kills and `--rm` cleans up, instead of leaving an
# orphaned container pinning a CPU. -k force-kills if SIGTERM is ignored.
Expand Down
23 changes: 23 additions & 0 deletions scripts/check-dashes.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/bin/sh
# En/em dashes are banned in .ts source (see .claude/rules/comments.md).
# git grep --cached scans the INDEX, i.e. exactly what a commit would record
# (the worktree can differ both ways), and the '*.ts' pathspec tracks every
# .ts file wherever it lives, so a new top-level dir can't dodge the gate.
# LC_ALL=C pins byte-wise PCRE: \x{2013}-style escapes error under C locales
# while \xe2-style bytes mis-match under UTF-8 mode. The case turns a scan
# ERROR (e.g. a git without PCRE support) into a loud failure, never a
# silent pass.
st=0
dashes=$(LC_ALL=C git grep --cached -nP '\xe2\x80[\x93\x94]' -- '*.ts') || st=$?
case $st in
0)
printf '%s\n' "$dashes"
echo "em/en dash found (use a comma, colon, or parenthetical instead)" >&2
exit 1
;;
1) ;;
*)
echo "em/en dash scan failed to run" >&2
exit 1
;;
esac
Loading