Skip to content

moooon

Motir

Vibe your whole project. Bring an idea — Motir's three AI layers plan it, track it, and ship it, end to end. You're looking at Motir, built in Motir.

  • Vibe Project
  • Open Source
  • AI Agent
  • AI Loop
1
requests
0
upvotes
145
planned
1,361
shipped

Motir · Work items

MOTIR-214Done

4.1.2 `sprintRepository` + `work_item` sprint/rank repo methods (single-op; writes require `tx`)

Estimate: 18m · Depends on: 4.1.1

The data-access leaf for sprints + the backlog rank, per the 4-layer rule (each method one Prisma op; no business logic, no transactions; writes take a required tx: Prisma.TransactionClient; reads used inside a write-guarding transaction take tx + SELECT … FOR UPDATE where a concurrent write could race).

sprintRepository (lib/repositories/sprintRepository.ts): create(data, tx), update(id, data, tx), delete(id, tx), findById(id), findActiveByProject(projectId) (the single state == active row — reads tx + FOR UPDATE in the variant the activation guard uses), listByProject(projectId) (ordered by sequence / state), countByProjectAndState / maxSequenceForProject(projectId) (for the next default name). NO cross-repo calls (repos are leaves).

work_item sprint/rank methods — extend workItemRepository (the entity owns them, not a new repo): setSprint(itemId, sprintId | null, tx) (the association write), setBacklogRank(itemId, rank, tx), and the bounded reads findBacklogPage(projectId, { cursor, limit }, ...) (sprint_id IS NULL, backlog_rank order, limit+1 for the next-cursor) + countBacklog(projectId) + findSprintIssues(sprintId) / countSprintIssues(sprintId) + the findRankNeighbours/boundary reads rankIssue needs (the prev/next backlog_rank around a target). Each is a single Prisma op ($queryRaw only where a grouped/aggregate read needs it).

Empty-input guards (the motir-core-coverage-gate lesson): any method that can be called with an empty id list / null cursor short-circuits with a direct unit test so the per-file branch-coverage gate stays green.

Acceptance criteria

  • sprintRepository exposes the create/update/delete + findById/findActiveByProject/listByProject/count/maxSequence methods; write methods require tx; findActiveByProject has a FOR UPDATE variant for the activation guard path.
  • workItemRepository gains setSprint, setBacklogRank, the cursor-paginated findBacklogPage + countBacklog, findSprintIssues + countSprintIssues, and the rank-neighbour reads — each a single Prisma op; writes require tx.
  • No repository method calls another repository or opens a transaction; aggregates/raw reads use $queryRaw. Empty-input guards are directly unit-tested.
  • pnpm typecheck passes; methods return Prisma rows (mapping is the service's job).

Context refs

  • lib/repositories/boardRepository.ts / workItemRepository.ts — the single-op + required-tx + $queryRaw-aggregate patterns to mirror; where the work_item sprint/rank methods land
  • lib/workItems/positioning.tskeyBetween (the service computes the rank; the repo just persists it)
  • motir-core/CLAUDE.md (repository layer rules; entity-name-wins for method placement) + motir-core-coverage-gate (empty-input-guard tests)