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

11.5.16 A v1 client cannot COUNT a filtered set — the work-item collection reports no total

MotirClient.searchWorkItems returns { items, total, nextCursor }, and total is load-bearing at four call sites. GET /api/v1/projects/{projectKey}/work-items declares body: { kind: 'page' } — the PLAIN envelope, which carries no totalCount. So 11.5.4 cannot port that method, and this is the gap its own STOP rule names.

Why no client-side derivation exists

Two of the four sites ask for one row in order to read the count:

  • packages/cli/src/commands/read.ts:96–105searchWorkItems({ filter: inFlightFilter(), limit: 1 }), then inFlightCount: search.total. The file's own comment says why: "search_work_items returns the matching total directly, so one call suffices."
  • packages/cli/src/commands/doctor.ts:79–80searchWorkItems({ projectKey, limit: 1 }), then total: page.total.

Counting those by paging would turn one request into as many as the match set is wide — a behaviour change motir status and motir doctor would both feel. The other two: packages/cli/src/commands/plan.ts:163 (page.total > 0) and packages/cli/src/session.ts:128 (a full walk, the one site items.length could serve).

Why it is a DECISION and not a mapping

lib/api/v1/openapi/envelopes.ts states the constraint deliberately: "TWO envelopes, NOT one with an optional totalCount … either promises a total or it does not." And the totals that exist today are byproducts — planning/operations.ts:37–45 records that the backlog and a sprint's members return the ranked envelope because both underlying reads already compute totalCount.

This read does not. The MCP tool gets its total from an OFFSET-paged search (lib/mcp/tools/searchWorkItems.ts:207result.total / result.page / result.pageSize); the v1 collection is a KEYSET read over (createdAt, id). They are different service reads, and the keyset one has no count to hand back. So serving a total here means paying for a COUNT that nothing currently runs — on a collection whose whole design note argues against promising one it cannot cheaply keep.

What has to be settled

  1. Does the collection become ranked, or does counting get its own answer? A ?count= / HEAD / a separate summary read all keep the paging envelope honest and let the count be requested only when wanted. Making the collection ranked is simpler and taxes every page.
  2. If a count is served, what does it cost and is it bounded? The filter is an arbitrary FilterAST; a COUNT under one has no row cap. Whether it is exact, capped ("999+"), or estimated is part of the answer, not a detail after it.
  3. Is total on the CLI's SearchPage the right shape to preserve at all, or should the two count-only call sites move to whatever this decision creates and stop pretending to be searches? They ask for limit: 1 and discard the row — that is a count, spelled as a search.
  4. Which contract version step this is. A new field on a response is additive under §8; a new operation is too. Either way contractVersion.ts moves and the CLI regenerates.

Scope BOUNDARY

Settles the question and ships the server side of it, the way 11.5.13 did. It does NOT port searchWorkItems or touch a CLI command — that is 11.5.4's, which this unblocks.

Acceptance criteria

  • The ADR gains an amendment answering all four questions above, with the cost of a filtered COUNT measured rather than assumed.
  • A v1 client can learn how many work items match a filter in ONE request, without paging the match set.
  • contractVersion.ts moves, with a changelog line.
  • The generated CLI artifacts are regenerated and committed; both freshness guards pass.
  • A test drives the real route and asserts the count against a known fixture set, including a filtered subset and an empty one.
  • The per-file coverage floor (≥90%) holds on every new and modified file.

Context refs

  • lib/api/v1/openapi/envelopes.ts — the two-envelope rule this must not quietly break.
  • lib/api/v1/planning/operations.ts:37–45 — why the existing ranked collections are ranked.
  • app/api/v1/projects/[projectKey]/work-items/route.ts — the keyset read with no count.
  • lib/mcp/tools/searchWorkItems.ts:207,240 — where the MCP tool's total actually comes from.
  • packages/cli/src/commands/read.ts:96 · commands/doctor.ts:79 · commands/plan.ts:163 · session.ts:128 — the four consumers.
  • docs/decisions/cli-v1-client.md — the ADR to amend.
  • Story: 11.5.