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-236Done

4.4.3 Backend — `completeSprint` flow + carry-over (transition + move unfinished to backlog / planned sprint, one tx)

Estimate: 28m · Depends on: 4.4.2, 4.1.4

The complete half of the lifecycle service — closes an active sprint and carries its unfinished work somewhere, composing Story 4.1's transition guard + association writes + the Epic-3 done-category set.

sprintsService.completeSprint(sprintId, { carryOverTo }) where carryOverTo is 'backlog' (default) or { sprintId: <planned sprint id> } (one method = one prisma.$transaction):

  • Load the sprint; assert it is active and compose assertSprintTransition(active, complete).
  • Determine the unfinished set: the sprint's issues whose workflow status is NOT in a category = 'done' terminal status — resolve the project's done-category status keys via the shipped workflowsService.getTerminalStatusKeys(projectId) (Epic 3) and select the sprint issues whose status key is outside that set. The DONE issues STAY on the completed sprint (the historical record); only the unfinished ones move.
  • Carry-over move (bounded batch, one tx — the 4.2.2 bulk shape): for 'backlog', moveToBacklog each unfinished issue (4.1.4 — they re-appear in backlog_rank order); for { sprintId }, validate the target is a planned sprint in the same project (throw InvalidCarryOverTargetError / reuse the 4.1.4 same-project guard otherwise) and assignToSprint each unfinished issue into it. The whole carry-over is ONE transaction (partial failure rolls back — never a half-moved set), recording a 1.4.6 revision per move.
  • Close: set completedAt = now, flip state to complete (freeing the project's one-active slot so the next sprint can start). Return the completed SprintDto + the report payload (or let the UI fetch getSprintReport, 4.4.4).
  • A sprint with no unfinished issues completes with an empty carry-over (no-op move). Enforce the finding-#26 workspaceId gate.

Typed errors: add SprintNotCompletableError (not active), InvalidCarryOverTargetError (target not a same-project planned sprint) to lib/sprints/errors.ts. Route: POST /api/sprints/[id]/complete — HTTP-only, one service call + error mapping (422 state, 409/422 target).

Acceptance criteria

  • completeSprint composes assertSprintTransition(active→complete), computes the unfinished set via getTerminalStatusKeys (non-done-category issues), moves them in ONE transaction — to the backlog (moveToBacklog, rank order restored) or into a same-project PLANNED sprint (assignToSprint, same-project guarded) — leaves done issues on the sprint, sets completedAt, flips state to complete, and records a revision per move.
  • A carry-over target that is cross-project or not planned throws InvalidCarryOverTargetError; completing a non-active sprint throws SprintNotCompletableError; a sprint with no incomplete issues completes with a no-op carry-over; the freed one-active slot lets a new sprint start.
  • The carry-over is a bounded batch (one tx, rollback on partial failure — not N round-trips); new typed errors live in lib/sprints/errors.ts; POST /api/sprints/[id]/complete is HTTP-only; the finding-#26 workspaceId gate covers it.
  • pnpm test:coverage keeps the changed service file ≥90% branch/fn/line (the coverage gate).

Context refs

  • Story 4.4.2 (startSprint + the extended sprintsService/errors) — the layer this completes; the pure assertSprintTransition to compose
  • lib/repositories/workItemRepository.ts setSprint / lib/services moveToBacklog + assignToSprint (Story 4.1.4) — the association MOVES the carry-over drives; the same-project guard to reuse; the 4.2.2 bulk-tx shape to mirror for the bounded batch
  • lib/services/workflowsService.ts getTerminalStatusKeys (Epic 3) — the category = 'done' set that defines "unfinished"
  • the 1.4.6 workItemRevisionsService — the audit-trail write to reuse per move; motir-core/CLAUDE.md (4-layer); finding #26 + #57 (bounded batch); motir-core-coverage-gate