chore(ai): move versioned-writer checklist from CLAUDE.md to a skill - #294
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
mattrace-gloo
left a comment
There was a problem hiding this comment.
Approving — the reasoning is right and I'd have got this wrong on my own.
The gating asymmetry is the part I'd missed: I wrote the Cursor rule with globs + alwaysApply: false and then dropped the same content into a root CLAUDE.md without noticing I'd thrown the gating away in the process. A skill is the correct analogue. Agreed too that two copies is legitimate duplication rather than drift waiting to happen — neither tool reads the other's format, and you've added the reciprocal pointer.
Your two judgement calls
1. "AI review hygiene" placement — move it, and not to CONTRIBUTING.md in this PR.
You're right that it's process guidance rather than repo knowledge, and right to be uneasy that it arrived in the PR it would govern the review of. I'd add a third objection: I wrote it partly as a reaction to one review round, which is a bad sample size to codify from.
Suggest cutting the section from both copies here and proposing it separately as a team decision, where people can disagree with the blocking/non-blocking split without it being bundled into an audio feature. The remaining three sections are all repo knowledge and belong exactly where you've put them.
2. Concurrency rule 3 — agreed, and I've narrowed it.
markConflictPreservingActive is deliberate: it flags a conflict without changing what the recording points at, so it invalidates nobody's token and has nothing to bump. The rule as written was contradicted by its own source code, which is a good way to get it ignored.
I've reworded it on the base branch (c2c090d) to scope it to paths that advance state, with the conflict-flag case named as the explicit exception. Same commit adds two rules that came out of the latest review round:
- Clearing a conflict is its own operation — if ordinary writes can also clear it, the client that caused the conflict settles it by retrying with the token the conflict response handed back. That was a live bug in #281.
- Sweeps that delete are CAS too — re-check "still unreferenced" inside the deleting statement under a lock on the parent row, and delete rows before bytes so a concurrent re-upload can't have its revived object collected. Also a live bug in #281.
What that means for this PR
Sorry — that edit lands your SKILL.md out of sync before it merges. Since the whole point is the two staying mirrored, could you rebase and re-copy? If you'd rather not, I'm equally happy to take the rebase myself and merge, or to merge this first and mirror the rule changes on top. Your call, and thanks for opening it against the branch rather than main — that saved a round trip.
On your earlier point about disparate vendor usage: agreed that Cursor + Claude gets the 80/20, and the offer to wire this into a PR gate stands whenever the team wants it.
5624b4c to
43baea3
Compare
|
Both done, thanks for the quick turnaround. Rebased onto Cut "AI review hygiene" from both copies. Your third objection is the one that convinced me — generalising from a single review round is the weakest part, and it's the part that would age worst. Left the other three sections in place. I'd also rather not be the one to propose the replacement: the blocking/non-blocking split is a call about how your team wants reviews to land, and I've been on one side of exactly one such review here. Happy to write it up as a strawman if that's useful, but it should be your team's decision, not a byproduct of this PR. Also added the skill's own pointer back at the Cursor rule, so the sync obligation is stated in both directions rather than just one. Reciprocal pointers are still the only thing keeping these in step, which works while it's two files and one checklist. If it grows, the sturdier answer is generating one from the other, or a CI check that diffs the shared body — the offer to wire that up stands. One note while you're here: the |
|
Thanks for turning this around so fast, and sorry for moving the base under you mid-review. I diffed the two files at On cutting "AI review hygiene" — agreed, and I think you're right about who should propose the replacement. Asking the reviewer to draft the blocking/non-blocking split for reviews they're a party to is an awkward position to put you in, and I shouldn't have. I'll raise it with the team as its own conversation; if we want a starting point after that I'll come ask, but the framing should come from us. On the sync mechanism — reciprocal pointers feel like the right amount of machinery for two files and one checklist, and stating the obligation in both directions is a real improvement over the one-way version I had. If a second or third checklist shows up, generating one from the other (or your CI diff check) becomes the obviously correct answer and I'll take you up on it then. One mechanical thing left, and it's mine, not the rebase's. GitHub is showing this as conflicting because On |
|
@mattrace-gloo That all sounds great. Feel free to merge this PR into yours as you see fit. |
CLAUDE.md at the repo root loads into every session in this repo,
regardless of what the task touches. The Cursor rule covering the same
material is already gated (`globs: src/**/*.{service,repository,route}.ts`,
`alwaysApply: false`), so the two copies had asymmetric load behaviour —
and the CLAUDE.md copy had lost the rationale behind each rule.
Replace CLAUDE.md with a Claude Code skill, which is the direct analogue
of the Cursor rule's on-demand loading: it is selected by description
when the work matches, and costs nothing otherwise.
Content mirrors `.cursor/rules/versioned-resource-writers.mdc` verbatim,
since that version keeps the "why" for each item. Adds a reciprocal
pointer to the Cursor rule so the two do not drift silently.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011FGa3gAfArR7GpjC8yBRuh
Rebased onto c2c090d, which reworded concurrency rule 3 and added rules 5 and 6. Re-copied the Cursor rule into the skill so the two match again. Also removes the "AI review hygiene" section from both copies, per review: it is process guidance rather than repo knowledge, it arrived in the PR whose review it would govern, and it generalises from a single review round. Worth proposing separately as a team decision, where the blocking/non-blocking split can be argued on its own merits. The three remaining sections are repo knowledge and stay. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011FGa3gAfArR7GpjC8yBRuh
43baea3 to
8bbb0dd
Compare
|
Went ahead and pushed the rebase since the conflict was mine — hope that's alright, and shout if you'd rather have driven it.
|
a50502e
into
mrace/feature/271-verse-audio-conflict-detection
* feat(verse-audio): version tokens and conflict detection for offline takes Stop last-writer-wins on verse audio uploads by versioning units, retaining conflicting takes, exposing chapter hasConflict rollups, and adding PM resolve. * fix(verse-audio): tighten conflict detection concurrency and chapter rollup CAS version updates, idempotent take inserts, insert-only unit create, and bible_id scoping on hasConflict so concurrent uploads cannot clobber state. * fix(verse-audio): address conflict-detection review findings Treat omitted baseVersionToken as replace for legacy clients, close the first-upload activeTake race, CAS resolveConflict, clear null storage ids, and promote matching non-active takes on intentional revert. * fix(verse-audio): batch storage lookups and skip false first-upload conflicts Derive hash-keyed download URLs for modern takes and batch legacy storage_objects lookups via getByIds. Treat identical-byte first-upload races as idempotent when the reloaded take is already active. * fix(verse-audio): address follow-up review on token parsing and legacy conflict Treat empty baseVersionToken form fields as absent, preserve conflict status for legacy clients without a token, bump version on first-upload link CAS, and add shared Cursor/Claude checklists for versioned writers. * style(verse-audio): apply prettier wrapping from pre-push hook * style: prettier-format drizzle journal and 0027 snapshot * fix(verse-audio): prune superseded takes on the reclaim sweep Hash-keyed blobs are per-take; drop non-active takes on clean units after the existing reclaim grace so re-records do not pin R2 objects forever. Conflicted units keep every take until resolve. * fix(verse-audio): split type-only import to satisfy lint Refs FLU-271 * fix(verse-audio): make conflicts resolve-only and the take prune non-destructive The reclaim sweep listed non-active takes, then deleted the rows and their blobs unconditionally. A take promoted between those steps was deleted out from under its recording (active_take_id is ON DELETE set null) and its bytes removed, and a concurrent re-upload of the same bytes had its revived storage_objects row stamped deleted. The prune now locks the parent recordings, re-checks "not active, still clean" inside the deleting statement, and leaves every object to the grace-guarded orphan pass. Uploads no longer clear conflictStatus. A stale base hands the losing client the current token, so it could settle the contest in its own favour just by retrying — PM adjudication through /resolve is now the only way a conflict is cleared. Also: an empty baseVersionToken form field is read as "legacy client" instead of being rejected (the multipart body is validated, contrary to the earlier reading that the schema was inert), take retention gets its own AUDIO_TAKE_RETENTION_MS separate from the one-hour blob grace, and a prune failure no longer starves the orphan sweep. Refs #271 * fix(verse-audio): revalidate orphan reclamation under lock Prevent the reclaim sweep from deleting bytes after an upload creates a live reference by locking and rechecking each candidate immediately before deletion. Also preserve the take-retention window during prune races and cover the upload CAS-miss path. * docs: align Claude version-token contract with implementation Document all four token states and make explicit that only conflict resolution can clear an existing verse-audio conflict. * chore(ai): move versioned-writer checklist from CLAUDE.md to a skill (#294) * chore(ai): move versioned-writer checklist from CLAUDE.md to a skill CLAUDE.md at the repo root loads into every session in this repo, regardless of what the task touches. The Cursor rule covering the same material is already gated (`globs: src/**/*.{service,repository,route}.ts`, `alwaysApply: false`), so the two copies had asymmetric load behaviour — and the CLAUDE.md copy had lost the rationale behind each rule. Replace CLAUDE.md with a Claude Code skill, which is the direct analogue of the Cursor rule's on-demand loading: it is selected by description when the work matches, and costs nothing otherwise. Content mirrors `.cursor/rules/versioned-resource-writers.mdc` verbatim, since that version keeps the "why" for each item. Adds a reciprocal pointer to the Cursor rule so the two do not drift silently. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011FGa3gAfArR7GpjC8yBRuh * chore(ai): re-mirror the skill and drop the review-hygiene section Rebased onto c2c090d, which reworded concurrency rule 3 and added rules 5 and 6. Re-copied the Cursor rule into the skill so the two match again. Also removes the "AI review hygiene" section from both copies, per review: it is process guidance rather than repo knowledge, it arrived in the PR whose review it would govern, and it generalises from a single review round. Worth proposing separately as a team decision, where the blocking/non-blocking split can be argued on its own merits. The three remaining sections are repo knowledge and stay. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011FGa3gAfArR7GpjC8yBRuh --------- Co-authored-by: kaseywright <kasey.wright@biblionexus.org> * fix(verse-audio): close remaining review races Revalidate orphan references after locking, keep Bible-scoped conflict views consistent, and surface cleanup races as retryable conflicts. * fix(verse-audio): preserve resolve against stale duplicate uploads Stop reopen-after-resolve on known non-active takes, transactionalize first upload, narrow 23503 mapping to retryable storage FKs, and remove the dead unguarded updateRecordingState writer. * test(chapter-assignments): strengthen hasConflict bible rollup coverage Extract the EXISTS subquery for inspection and assert bible/book/chapter scoping plus per-row rollup behavior across assignment Bibles. * feat(verse-audio): include currentVersionToken on 409 conflict responses Lets mobile retry CAS/cleanup races without a full GET by reloading the live token on upload and resolve 409s. * style: fix lint and prettier issues from currentVersionToken change CI validate failed on import order in verse-audio.route.ts and table formatting in the versioned-writer skill. --------- Co-authored-by: Kasey Wright <kasey@kaseywright.com> Co-authored-by: kaseywright <kasey.wright@biblionexus.org>
Targets
mrace/feature/271-verse-audio-conflict-detectionso it can merge into #281 before that lands onmain.Why
#281 adds
CLAUDE.mdat the repo root (the repo's first) alongside.cursor/rules/versioned-resource-writers.mdc. Both carry the same versioned-writer checklist, but they load very differently:globs: src/**/*.{service,repository,route}.ts,alwaysApply: false. It loads only when you touch those files.CLAUDE.mdhas no equivalent gate. It enters context for every task in this repo, including ones nowhere near a version token.The
CLAUDE.mdcopy had also lost the rationale: compare "Every state-mutating write bumps the version token" against the Cursor rule's version, which explains why (a concurrent existing-unit writer that read the old token can still win its CAS and silently demote the first uploader). The reasoning is the part that makes the rule usable.What changed
CLAUDE.md..claude/skills/versioned-resource-writers/SKILL.md— the direct analogue of the Cursor rule's on-demand loading. A skill is selected by its description when the work matches, and costs nothing otherwise.Two copies remain, but that duplication is now legitimate — Cursor cannot read a Claude skill, and vice versa. Each tool gets a scoped, on-demand copy in its own format.
Two things for you to decide
Both carried over as-is rather than changed unilaterally:
The "AI review hygiene" section. It tells a reviewer what to treat as blocking vs. non-blocking. That's process guidance rather than repo knowledge, and it arrived in the PR it would govern the review of. Worth considering whether it belongs in
CONTRIBUTING.mdas a team decision instead of in an AI-context file. Left verbatim here.Concurrency rule 3 may be too absolute. "Every state-mutating write bumps the token" —
markConflictPreservingActivemutatesconflictStatusand does not bump. That looks deliberate and fine, but it means the rule as written is already contradicted by the code it was extracted from. Might be worth narrowing it to link/advance paths.Notes
No source changes — docs/config only. The checklist content itself is unchanged from what you wrote.
🤖 Generated with Claude Code
https://claude.ai/code/session_011FGa3gAfArR7GpjC8yBRuh