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

11.3.9 `GET /api/v1/projects/{projectKey}/ready` — the ready set in DISPATCH rank, with each row's dependency edges

The endpoint that makes external agent orchestration possible without MCP: "what can I pick up right now, and what does finishing it unblock?" — the reason 11.3 is worth shipping to an integrator at all.

⚠️ Readiness is COMPUTED, and this route does not compute it

An item is ready when it is a childless leaf, in a todo status, with every is_blocked_by blocker terminal AND every ANCESTOR ready — the parent-ready cascade. workItemsService.listReady implements exactly that, top-down by layer (collectReadyLeaves), and returns rows already sorted by the dispatch rank (type asc, priority desc, key asc): leaf-most kinds first, priority breaking the type tie, key breaking the final tie.

A flat "all its own blockers are done" check is a different and wrong answer, and it is the answer a route that re-derives readiness would give. So: call the service. Do not filter, re-sort, re-rank or post-process its result; the endpoint's whole value is that an agent loop and the product's own /ready page can never disagree about what is ready. Do not import from lib/mcp/ to get there either — the MCP tool is a reference SHAPE, not a dependency; the two transports align through the service.

Note what the ORDER is worth: it is the dispatch rank, so items[0] is what an agent should take next. Paging it by any other key silently destroys that, which is why 11.3.2's service-positioned cursor exists — the v1 cursor here wraps the shipped (kind, priority, key) seek-after position.

The dependency-edge block is a second, BATCHED read

Each row carries dependencies: { blockedBy, blocks }{ key, title, status } per edge, key being the MOTIR-<n> identifier — from workItemsService.getDependencyEdgesForItems, which resolves a whole page in two queries and is TOTAL (a row with no edges gets two empty arrays, never undefined). This is what lets a client see downstream impact without N follow-up calls, and it is why the block lives at the transport rather than on ReadyItemDto: widening the DTO would ship an edge payload to the /ready page, which renders dependency state its own way. 11.3.1 Q4 records that a bounded, constant-count page projection is a permitted second service call — never a per-row read.

For a ready row blockedBy is terminal by definition (that is what makes it ready); blocks is the payload that matters — what this item unblocks, i.e. why it is worth doing first.

What to build

  • lib/api/v1/ready/schema.ts — the ready-row response schema + field-by-field mapper, including the edge block. Decide deliberately what of ReadyItemDto is public: descriptionExcerpt is a ~200-char plain-text excerpt, while descriptionMd is populated only for manual rows (the shipped payload-size split) — shipping a field that is null for most rows for a reason no client can see is worse than omitting it. type / executor / kind / priority are closed vocabularies and take the compile-time totality guards.
  • GET /api/v1/projects/{projectKey}/readyread scope, cursor-paged, with the shipped optional filters (kinds, priority, assigneeId including its tri-state unassigned bucket) exposed or deliberately not.

Reads only. The claim/dispatch operation (claimNextReady) is a work_items:write status flip behind the CI-credit gate and is a deliberate non-goal of this story; nothing here defers work to it.

Acceptance criteria

  • The endpoint exists, declares scope: 'read', and returns the declared schema's output.
  • The response set and ORDER equal workItemsService.listReady's for the same project and filters — asserted by calling both and comparing, not by re-implementing the expectation.
  • A fixture where the parent-ready cascade excludes an item whose OWN sibling blockers are all done is asserted explicitly: the item must be ABSENT. This is the exact case a flat blocker check gets wrong, and it is the reason this endpoint may not be re-derived.
  • Every row carries dependencies: { blockedBy, blocks } with the shipped key names, both arrays always present, and key as the MOTIR-<n> identifier — including a row with no edges, which gets two empty arrays rather than a missing field.
  • The edge projection is two queries for the whole page, not one per row — asserted by query count or by a spy on the batched reader, since an N+1 here is invisible until a 100-row page.
  • Paging preserves the dispatch rank across page boundaries; limit defaults to 50 and clamps to 100 even though the underlying read allows 200.
  • A malformed cursor is a 422 (the shipped InvalidReadyCursorError maps deliberately, rather than escaping as a 500); a cursor past the tail is an empty 200 page.
  • An empty ready set is 200 with empty items, never a 404; an unknown or cross-workspace projectKey is a 404.
  • No route re-derives readiness, and no v1 route imports from lib/mcp/ — both asserted as guards, not by review.
  • Unit tests ship with the route; every new file holds the ≥90% per-file coverage floor.
  • ONE PR against motir-core.

Context refs

  • lib/services/workItemsService.tslistReady (and its documented ready predicate + cascade), getDependencyEdgesForItems (the two-query batched projection, and its totality guarantee).
  • lib/workItems/readyFilter.tsReadyListFilter, the dispatch sort order, encodeReadyCursor / decodeReadyCursor / InvalidReadyCursorError, READY_DEFAULT_LIMIT / READY_MAX_LIMIT.
  • lib/dto/ready.tsReadyItemDto, and the recorded 7.0.3 split explaining why descriptionMd is manual-rows-only.
  • lib/mcp/tools/listReady.ts · lib/mcp/dependencyEdges.ts — the reference shape and the recorded reason the edge block lives at the transport. Read, do not import.
  • lib/dto/workItems.tsWorkItemDependencyEdgesDto / WorkItemEdgeSummaryDto, the edge entry shape.
  • The readiness cascade rule (parent-ready + sibling blockers) — plan-rules.md § Ordering follows the dependency arrow.
  • Blockers: 11.3.1 (Q1 cursor + Q4 the projection call), 11.3.2 (the cursor primitive). Edge projection shipped by 7.9.0f. Parent story: 11.3.