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

11.5.23 `nextReady` off MCP — pick from the RANKED ready set, and route from the dispatch prompt

The LAST method still speaking MCP. MotirClient.nextReady calls the next_ready tool; nothing else in packages/cli/src does, so this card is what makes 11.5.6's deletion possible.

There is no next_ready endpoint on /api/v1, and there should not be one. The ready collection already answers the question: lib/api/v1/ready/schema.ts documents that rows arrive in the dispatch rank (type asc, priority desc, key asc) and that items[0] is what an agent should take next. "Next ready" is a client-side pick from a ranked page, not an operation.

Where targetRepo comes from — SEED FIRST, THEN RESOLVE

targetRepo is per card and can differ on every dispatch, so it belongs with the card's instruction payload — the "what to do" — not on a collection row. That is Amendment 10 Q2's position and it stands: the ready row does NOT gain the field, and the loop reads it from GET /api/v1/work-items/{key}/dispatch-prompt.

⚠️ The obvious ordering is circular, and the fix is to invert it. The loop cannot resolve the checkout before the prompt read (it needs targetRepo), and it cannot seed the prompt after (repos.ensure creates the session branch and the seed is repo?.branch ?? null). So:

  1. Read the prompt WITH the run's branch as the seed. The prompt is a pure READ — it never claims the item and never moves its status — so seeding before knowing whether a checkout exists has no side effect and costs nothing.
  2. resolveDispatchTarget(dir, config, dispatch.targetRepo)repos.ensure(target), which creates the session branch on origin.
  3. A checkout exists → the prompt already reads correctly. ONE read, no regression on the common path.
  4. No checkout → the prompt's lineage instruction is wrong for an item that will ship as its own pull request, so re-read seedless and hand the agent that text instead. A second read, on a path that was already the exceptional one.

What to build

  1. nextReady fetches a ready PAGE through the transport and returns the first row not held out, following nextCursor when a whole page is excluded. kinds rides as the collection's own repeated filter.
  2. Exclusion by KEY, and the id goes. MOTIR-2338 already made the persisted list key-based and then translated keys back to row ids for the tool; with the pick client-side there is nothing to translate. DispatchItem.id and claimNextNotExcluded's extra round trip both go away.
  3. The seed-first ordering above, in commands/auto.ts and commands/batch.ts.
  4. DispatchItem sheds id and targetRepo, and carries inheritedSessionBranch — Amendment 15's readiness qualifier (MOTIR-2400), which is what classifySnapshotItem reads to refuse a lineage item in motir batch.
  5. listReadyForDispatch — the whole ranked set for motir batch's snapshot, one page walk where it used to be one next_ready per item with a growing exclusion list.

Scope BOUNDARY

nextReady, DispatchItem, and the pick/route ordering in the three dispatch commands. It does NOT add a field to the ready row or change any endpoint. It does NOT touch render.ts. It does NOT delete the MCP transport or the SDK (11.5.6, which this unblocks). It does NOT change any output string: motir run, motir auto and motir batch print byte-identically.

Acceptance criteria

  • nextReady calls GET /api/v1/projects/{projectKey}/ready through the transport; no MCP tool call remains anywhere in packages/cli/src.
  • A run holding out every row of the first page follows nextCursor and picks from the second — asserted, because a client-side pick that stops at one page silently drains a run early.
  • The pick honours the SERVER's rank: items[0] of the first page, unexcluded, is what comes back. Asserted against a page whose rank matches no field the client could sort on.
  • Exclusion is by KEY end to end; no row id is sent, read or persisted, and claimNextNotExcluded's translating round trip is gone.
  • An item WITH a checkout makes exactly ONE dispatch-prompt request, asserted on the request count — the seed-first ordering exists to keep the common path at one read, and an implementation that reads seedless-then-seeded passes every output assertion while doubling the calls.
  • An item with NO checkout re-reads seedless, and the agent receives the per_item_pr text rather than an instruction to integrate into a branch that does not exist.
  • Reading a prompt still neither claims the item nor moves its status — asserted by reading the item's status before and after, because this card now reads it before deciding anything.
  • motir run, motir auto and motir batch produce byte-identical output for the same data, asserted by the existing suites with their assertion strings untouched.
  • The per-file coverage floor (≥90%) holds on every new and modified file.

Context refs

  • lib/api/v1/ready/schema.ts — the dispatch rank as contract, and why targetRepo is not on the row.
  • ADR docs/decisions/public-api-conventions.md Amendment 10 Q2 (the prompt is where targetRepo lives) and Amendment 15 (the row's inheritedSessionBranch).
  • packages/cli/src/mcpClient.tsnextReady, DispatchItem.
  • packages/cli/src/commands/dispatch.ts (claimNextNotExcluded) · auto.ts (repos.ensure, resolveDispatchTarget, the seed) · batch.ts.
  • MOTIR-2338 — made the exclusion list key-based; this removes the id translation it had to add.
  • MOTIR-2400 — shipped inheritedSessionBranch, without which motir batch's snapshot could not refuse a lineage item.
  • Story: MOTIR-1855.