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

11.5.22 The DETAIL reads over `/api/v1` — `getWorkItem`, the activity stream, and what `--json` emits

The third and last read slice 11.5.4 was split into — the sharpest reshape, plus the one user-visible decision the port forces.

methodv1 operation
getWorkItemGET /api/v1/work-items/{key}
getWorkItemActivityGET /api/v1/work-items/{key}/activity — ONE operation serves all three views, so a cursor stays scoped to the view that issued it

⚠️ THE DECISION: --json emits the v1 RESOURCE

The port makes "--json output is unchanged" unsatisfiable, and the reason is worth stating plainly: under MCP the view model WAS the tool payload — the mirror was structural — so "unchanged" cost nothing. Under v1 the payload is a different shape, so --json changes whichever way it goes.

Decided: --json emits the v1 body VERBATIM.

The view model is deliberately LOSSY — mcpClient.ts says heavier fields are omitted "because --json prints the tool payload itself, so nothing is lost by omitting them here." --json is the escape hatch that makes that narrowing safe. Emitting the view model instead would point the escape hatch at the narrowed thing and remove labels, components, commentCount, createdAt, reporterId, dueDate and the provenance fields from every script that reads it — a capability removal wearing a shape change.

Emitting the v1 body also makes the flag DOCUMENTABLE: "motir show --json returns the /api/v1 work-item resource" is a promise with an OpenAPI document behind it, versioned and additive-only under §8. And motir show KEY --json then agrees byte for byte with a direct curl of the same resource, which is the story's whole thesis made observable.

The adapter boundary is NOT violated. ADR Q4 exists so RENDERERS do not become a second consumer of the wire contract; --json is a pass-through, not a renderer, and it needs BYTES rather than a type. So the client exposes the raw body typed as unknown, the command JSON.stringifys it, and no generated type is IMPORTED outside src/adapters/ and src/transport.ts — the freshness test's rule is untouched.

One consequence, named: --json loses item.id. §7 keeps the internal cuid off the wire, and this is the same deliberate loss Amendment 10 Q3 already recorded when the exclusion list moved to keys.

This decision is recorded as an ADR amendment by this card, because it is a public-behaviour promise rather than an implementation detail.

The DETAIL reshape

  • ancestorKeysancestors, narrowed to { identifier }: render.ts:747 reads .identifier alone, so the alternative is inventing a kind/title/status the wire never sent.
  • links (five groups) → the three arrays the CLI declares. linkId leaves the view model — nothing read it, and v1 does not publish it.
  • readiness.blockedByAncestorKey + …Title → one { identifier, title }. The two are null together on the wire, so the object is null or complete.
  • WorkItemDetail.item.id and .parent are DROPPED — 11.5.19 removed the last reader of the first, and the second never had one.

The ACTIVITY reshape

  • The CLI's ActivityValue / ActivityPart stay LOOSER than the wire's closed unions, deliberately: the CLI ships on its own release train and meets newer servers, so activityValueText's default branch must stay REACHABLE. A faithful re-narrowing turns an unfamiliar value into a crash.
  • The wire's workItemKey becomes identifier — what render.ts:850 reads.
  • totalComments / totalChanges come from 11.5.18; the footer derives "44 more comments" as total − shown, which page counts cannot supply.
  • order is not on the wire and does not need to be — it is what the CLI itself requested (default asc), so echoing it states no fact the server did not.

Scope BOUNDARY

These two methods, the --json decision and its ADR amendment, and the suites they break. It does NOT re-open the identity or collection reads. It does NOT change render.ts, any flag, or any HUMAN-readable output string — --json is the one deliberate exception, decided above. It does NOT delete the MCP transport (11.5.6).

Acceptance criteria

  • Both methods call /api/v1 through the transport core; no MCP tool call remains on either path.
  • motir show produces HUMAN output identical to the MCP-era implementation for the same data — header, readiness, lineage, the WAVE view, every edge group and the body.
  • motir show --json emits the v1 work-item resource byte for byte, and --activity --json the v1 activity page — asserted by comparing against the exact body the server returned, not against a hand-written expectation.
  • The ADR carries the amendment recording this, including why the view model was rejected and why the adapter boundary is not violated.
  • No generated wire type is IMPORTED outside src/adapters/ and src/transport.ts; the raw body reaches the command as unknown.
  • An activity value or part kind this build has never seen renders generically and does not throw — asserted with an invented type.
  • A cursor issued for one VIEW is never sent to another.
  • The whole packages/cli suite is GREEN at this commit.
  • The per-file coverage floor (≥90%) holds on every new and modified file.

Context refs

  • scratchpad/11.5.4-wip.patch — this slice's mappers and converted fixtures are already written there.
  • 11.5.4 — the adapter module and the harness's /api/v1 capability.
  • 11.5.18 — the activity totals, and the extend mechanism whose Ajv defect the patch also fixes.
  • lib/api/v1/workItems/schema.ts · workLoop/schema.ts — the wire shapes.
  • packages/cli/src/render.ts:565,747,850 · assignChildWaves (570) — the readers the narrowings are measured against.
  • packages/cli/test/read.test.ts (the show / activity blocks) · dispatchCommand.test.ts — the suites this slice converts.
  • Story: 11.5.