11.5.4 The adapter module + the IDENTITY reads (`whoami`, `listProjects`) — and the test harness learns `/api/v1`
⚠️ SPLIT 2026-08-07 after three attempts. This card was "the six reads"; it is now the FIRST of three slices.
Each attempt finished the port and ran out of room in the ~40 test conversions the contract change obliges. The sizing was not the real problem — every intermediate state was RED, because porting six methods at once breaks every suite that drives any of them. There was no green increment to stop at.
The fix is to split by METHOD GROUP, so each card ports a few methods AND converts exactly the suites those methods break, and each lands green:
- this card — the adapter module, the test harness's
/api/v1capability,whoami+listProjects- 11.5.21 —
listReady+listSprints- 11.5.22 —
getWorkItem+getWorkItemActivity, and the--jsondecisionThe work is already written:
scratchpad/11.5.4-wip.patch(1171 lines) carries all three slices plus a real bug fix. This card takes its share.
Introduce the adapter module, teach the test harness to serve /api/v1, and port the two IDENTITY reads.
What this card ports
| method | v1 operation |
|---|---|
whoami | GET /api/v1/me + GET /api/v1/workspaces — TWO reads, matched on workspaceId, never on position |
listProjects | GET /api/v1/projects, walked to exhaustion (the view model is a whole list) |
The three foundations it lays
1. packages/cli/src/adapters/reads.ts — the named module 11.5.1 Q4 pins: wire types in, the CLI's own view models out, no generated type visible to a renderer. It arrives with the two identity mappers; the later slices add theirs.
2. MotirClient gains the V1Transport, built in the constructor rather than on connect() — it holds no connection, only a base URL and a bearer, and a read must not require an MCP handshake it no longer uses. MotirClientOptions gains fetchImpl for tests. The two transports COEXIST until 11.5.6.
3. packages/cli/test/helpers/mcpTestServer.ts serves /api/v1 — a V1Script keyed `${METHOD} ${pathTemplate}` with {name} segments, a matcher where a literal beats a placeholder, v1Calls recording, scriptV1(), and a DEFAULT_V1 mirroring DEFAULT_TOOLS' data.
⚠️ The v1 fixtures are written as REAL v1 payloads, never derived from the MCP script. A translation helper would be a second adapter running backwards, and every read test would then assert that the harness and src/adapters/reads.ts agree with each other rather than that either agrees with the server.
⚠️ v1Calls accumulates exactly like calls — every suite that resets one must reset both, or assertions inherit the previous test's requests.
Scope BOUNDARY
The adapter module, the transport wiring, the harness capability, and the two identity methods. It MAY change a command file — but only a line that consumes a view-model field this card changed, and never a human-readable output string. It does NOT port listReady / listSprints (11.5.21) or getWorkItem / getWorkItemActivity (11.5.22). It does NOT touch --json, whose shape is 11.5.22's decision. It does NOT delete the MCP transport (11.5.6).
Acceptance criteria
whoamiandlistProjectscall/api/v1through the transport core; no MCP tool call remains on either path.ProjectSummarydropsidandslug— no file inpackages/cli/srcreads either, and both are deliberately off the v1 project resource (idby §7,slugbecause nothing addresses a project by it). The adapter DROPS them; it does not synthesise them.whoamiresolves its workspace by matchingme.workspaceIdagainst the/workspaceslist — asserted with a list whose FIRST row is not the bound one, so a positional read fails.listProjectswalks every page — asserted against a two-page collection, with the second page's cursor echoed exactly as received.- A READ needs no
connect()— asserted directly, and the existing "refuses to be used before connect()" re-aimed at a method that still speaks MCP. This is the point of the port, not an oversight. motir auth,motir login,motir linkandmotir doctorproduce output identical to the MCP-era implementation for the same data.- The whole
packages/clisuite is GREEN at this commit — no suite is left red for a later slice. - No generated wire type is imported outside
src/adapters/andsrc/transport.ts—tests/cli/generated-api-freshness.test.tsalready enforces this. - The per-file coverage floor (≥90%) holds on every new and modified file.
Context refs
scratchpad/11.5.4-wip.patch— the written work, including this slice.- 11.5.1 Q4 · 11.5.3 — the boundary rule and the transport.
lib/api/v1/identity/schema.ts·projects/schema.ts— the wire shapes.packages/cli/test/clientCore.test.ts·login.test.ts·projectLink.test.ts·commands.interactive.test.ts·doctorCommand.test.ts— the suites this slice converts.- Story: 11.5.