Skip to content

chore(ai): move versioned-writer checklist from CLAUDE.md to a skill - #294

Merged
mattrace-gloo merged 2 commits into
mrace/feature/271-verse-audio-conflict-detectionfrom
kasey/chore/versioned-resource-writers-skill
Aug 28, 2026
Merged

chore(ai): move versioned-writer checklist from CLAUDE.md to a skill#294
mattrace-gloo merged 2 commits into
mrace/feature/271-verse-audio-conflict-detectionfrom
kasey/chore/versioned-resource-writers-skill

Conversation

@kaseywright

Copy link
Copy Markdown
Contributor

Targets mrace/feature/271-verse-audio-conflict-detection so it can merge into #281 before that lands on main.

Why

#281 adds CLAUDE.md at 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:

  • The Cursor rule is gated — globs: src/**/*.{service,repository,route}.ts, alwaysApply: false. It loads only when you touch those files.
  • Root CLAUDE.md has no equivalent gate. It enters context for every task in this repo, including ones nowhere near a version token.

The CLAUDE.md copy 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

  • Removed CLAUDE.md.
  • Added .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.
  • Content mirrors the Cursor rule verbatim, since that version is the better of the two.
  • Added a reciprocal pointer in the Cursor rule so the two don't drift silently.

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:

  1. 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.md as a team decision instead of in an AI-context file. Left verbatim here.

  2. Concurrency rule 3 may be too absolute. "Every state-mutating write bumps the token" — markConflictPreservingActive mutates conflictStatus and 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

@coderabbitai

coderabbitai Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: fcf88caa-1125-4191-8807-6d942932b4af

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@mattrace-gloo mattrace-gloo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@kaseywright
kaseywright force-pushed the kasey/chore/versioned-resource-writers-skill branch from 5624b4c to 43baea3 Compare August 28, 2026 16:45
@kaseywright

Copy link
Copy Markdown
Contributor Author

Both done, thanks for the quick turnaround.

Rebased onto c2c090d and re-copied the rule into the skill — it now carries the reworded rule 3, the two new rules (5: clearing a conflict is its own operation; 6: sweeps that delete are CAS too), and the Present + malformed → 400 row. The two files match again apart from frontmatter and the reciprocal pointers.

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 bible_id divergence from the last review round on #281 (chapter-assignments.repository.ts:448 — the rollup filters on bt.bible_id while findForVerse and listByChapter don't) is the one item with no response yet in c2c090d. Not urgent, and not this PR's business — just flagging so it doesn't get lost.

@mattrace-gloo

Copy link
Copy Markdown
Contributor

Thanks for turning this around so fast, and sorry for moving the base under you mid-review.

I diffed the two files at 43baea3 and they line up: identical bodies, with the only differences being the Cursor frontmatter vs the skill's name/description and the two pointers. Reworded rule 3, new rules 5 and 6, and the Present + malformed → 400 row all made it across.

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 c2c090d is no longer the branch tip — I pushed 4a5eef9 after you rebased, a CLAUDE.md-only doc fix, so it lands as a delete/modify conflict against the file this PR removes. The resolution is just "take the delete": everything that commit added to CLAUDE.md (the four token states and resolve-only) is already in the skill body, and the "Review triage" section it left behind is exactly the part we just agreed to cut. Since I caused it, I'm happy to push the rebase to your branch — say the word, or grab it yourself if you'd rather keep the branch yours.

On bible_id — good catch, and you're right that it's the one thing still unanswered. I walked it again: findForVerse matches on projectUnitId / bookId / chapterNumber with no bible predicate, while the rollup's bible_texts join requires bible_texts.bibleId = chapter_assignments.bibleId, so a unit whose assignment points at a different bible than its texts resolves in one and vanishes from the other. Real inconsistency, agreed it isn't this PR's business or #281's. I'll get it filed as its own item so it stops living in review threads.

@kaseywright

Copy link
Copy Markdown
Contributor Author

@mattrace-gloo That all sounds great. Feel free to merge this PR into yours as you see fit.

kaseywright added 2 commits August 28, 2026 11:40
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
@mattrace-gloo
mattrace-gloo force-pushed the kasey/chore/versioned-resource-writers-skill branch from 43baea3 to 8bbb0dd Compare August 28, 2026 18:41
@mattrace-gloo

mattrace-gloo commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Went ahead and pushed the rebase since the conflict was mine — hope that's alright, and shout if you'd rather have driven it.

8bbb0dd, replayed onto 4a5eef9 with your two commits and authorship intact. CLAUDE.md resolved as "take the delete"; the three files are byte-identical to what you had at 43baea3, and the diff against the base tip is exactly the three this PR touches. Pre-push gate ran clean (format, lint, typecheck, 525 tests). GitHub now shows it mergeable.

@mattrace-gloo
mattrace-gloo merged commit a50502e into mrace/feature/271-verse-audio-conflict-detection Aug 28, 2026
1 check passed
@github-actions
github-actions Bot deleted the kasey/chore/versioned-resource-writers-skill branch August 28, 2026 18:42
mattrace-gloo added a commit that referenced this pull request Sep 3, 2026
* 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>
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.

2 participants