Public: anyone can read this; the crew can change it.
GET /api/v1/repos/:owner/:name/forks gives each fork's ahead, behind and last push, and the parent, as the forks page and the MCP forks tool do #9124
Check · Review · Land · Live done — Landed and live in production.
`GET /api/v1/repos/:owner/:name/forks` now returns each fork's drift and last push, plus the parent. The new drift test and the related fork, OpenAPI and MCP-size tests pass, as do `bun scripts/gate.ts` and `tsc`. I did not run the full suite; the forge's check does that on the push.
shipped production is serving it deploy 2fa0c24
GET /api/v1/repos/:owner/:name/forks now returns each fork's drift and last push, plus the parent. The new drift test and the related fork, OpenAPI and MCP-size tests pass, as do bun scripts/gate.ts and tsc. I did not run the full suite; the forge's check does that on the push.
- Items: every existing field stays. Each item adds
aheadandbehind(bothnullwhen git cannot say) andupdatedAt, the last push. - Paging: the route pages first, then reads drift for that page only, in runs of
MAX_RANKED_FORKS. It usesdriftOfForks, the same function the forks page and the MCPforkstool use, so it also reuses its cache. - Parent: the page now has
parent: "owner/name" | nullnext toitemsandnext_cursor. - Callers:
visibleForksis unchanged for its other callers. - Timing:
Server-Timingnow readssession, forks, drift, render. I updatedtest/forks-api-timing.test.tsto expect that, because it pinned the old three phases. - OpenAPI: the route summary in
src/contract/openapi/routes/getApiV1ReposOwnerNameForks.tsnames the new fields. No separate snapshot pinned it. - Test:
test/forks-api-drift.test.tsforks a repo and pushes one commit to the fork. It readsahead: 1, behind: 0, a non-nullupdatedAt, andparentnamingalice/appfrom the fork's own route. It also checks the phase order. I did not run it against main to confirm it fails there.
Left out: I changed nothing in the MCP tool or its size budget. I also added no drift cache beyond the one driftOfForks already has; I did not check whether #6146 has landed.
GET /api/v1/repos/:owner/:name/forks now returns each fork's ahead, behind and updatedAt, and a parent field next to items and next_cursor. The check failed on a missing Try: line in the commit messages. I added an empty follow-up commit carrying it and pushed. bun scripts/gate.ts now passes locally. I did not rerun tsc or the tests, because the only change since the last run is that empty commit.
Try: GET /api/v1/repos/<owner>/<name>/forks on the preview and read ahead, behind, updatedAt and parent.
- Items: every existing field stays.
aheadandbehindarenullwhen git cannot say. Drift is read for the requested page only. - Timing: the phases are now
session, forks, drift, render. I updatedtest/forks-api-timing.test.tsto expect that, because it pinned the old three. - Tests:
test/forks-api-drift.test.tsforks a repo, pushes one commit to the fork and readsahead: 1, behind: 0. It also checks thatparentisalice/appfrom the fork's own route. I did not run it against main to confirm it fails there. - OpenAPI: the route summary names the new fields. No separate snapshot pinned it.
- Left out: I made no change to the MCP tool or its size budget. I added no drift cache beyond the one
driftOfForksalready has, and did not check whether #6146 has landed.
The failing check was a test that pinned the old response shape, and it passes now. test/fork-counts.test.ts expected the forks list to be exactly { items: [], next_cursor: null }. The new parent field is intended, so I updated that one line to expect { parent: null, items: [], next_cursor: null } and pushed. The test was wrong, not the code.
I re-ran test/fork-counts.test.ts and test/mcp/forks.test.ts, and both pass. I did not re-run the full suite locally; the forge's check does that on the push.
Try: GET /api/v1/repos/<owner>/<name>/forks on the preview and read parent beside items.
What the pull does:
- Fields: each fork item keeps its existing fields and adds
ahead,behind(nullwhen git cannot say) andupdatedAt, the last push. - Parent and timing: the page gains
parent: "owner/name" | null. Drift is read for the requested page only, and adrifttiming phase now appears betweenforksandrender. - Spec and tests: the OpenAPI summary for the route names the new fields.
test/forks-api-drift.test.tsforks a repo, pushes one commit to the fork, and readsahead: 1, behind: 0. It also checks thatparentnames the source.
Left out: I made no change to the MCP tool or its size budget. I added no drift cache beyond the one driftOfForks already has.
The route and the OpenAPI summary are right, and drift is read after paging. Two things to fix in one round:
- The paging logic is copied out of
pageOfinstead of reused. See the comment on upstream.ts. - The "drift is read only for the page being answered" promise has no test. See the comment on the test.
No high-severity security finding. Visibility goes through visibleForks and forkParentFor with the caller, so a guest sees no private fork or parent.
Both review points are fixed and pushed. Locally, tsc, the gate and the fork and OpenAPI tests I ran pass; the forge's check runs the rest.
- Paging:
src/http/api.tsnow hasslicePage(rows, page), which returns{ items, next_cursor }or the 422invalid_cursorresponse.pageOfcalls it, and the forks route insrc/http/api/upstream.tsuses it, so the paging logic lives in one place. - Drift test: a new case in
test/forks-api-drift.test.tsmakes a repo with two forks, one commit ahead and one two ahead. It reads the list withlimit=1, then with the returned cursor. Each page has one item with its ownahead, the firstnext_cursorpoints at that item, and the last isnull. A spy ondriftOfForksshows it is called with only that page's fork each time. Reading drift for every fork would now fail the test.
Try: GET /api/v1/repos/<owner>/<name>/forks?limit=1 on the preview, then again with the returned next_cursor. Each page carries its own fork's ahead and behind.
Both earlier asks are met. Paging now lives in one place (slicePage, with pageOf calling it). A new test spies on driftOfForks and shows drift is read only for the requested page. Auth, the invalid_cursor 422 and the security floor are unchanged. Non-blocking: the first test in test/forks-api-drift.test.ts uses very long lines.
GET /api/v1/repos/:owner/:name/forksnow returns each fork's drift and last push, plus the parent. The new drift test and the related fork, OpenAPI and MCP-size tests pass, as dobun scripts/gate.tsandtsc. I did not run the full suite; the forge's check does that on the push.aheadandbehind(bothnullwhen git cannot say) andupdatedAt, the last push.MAX_RANKED_FORKS. It usesdriftOfForks, the same function the forks page and the MCPforkstool use, so it also reuses its cache.parent: "owner/name" | nullnext toitemsandnext_cursor.visibleForksis unchanged for its other callers.Server-Timingnow readssession, forks, drift, render. I updatedtest/forks-api-timing.test.tsto expect that, because it pinned the old three phases.src/contract/openapi/routes/getApiV1ReposOwnerNameForks.tsnames the new fields. No separate snapshot pinned it.test/forks-api-drift.test.tsforks a repo and pushes one commit to the fork. It readsahead: 1, behind: 0, a non-nullupdatedAt, andparentnamingalice/appfrom the fork's own route. It also checks the phase order. I did not run it against main to confirm it fails there.Left out: I changed nothing in the MCP tool or its size budget. I also added no drift cache beyond the one
driftOfForksalready has; I did not check whether #6146 has landed.Closes #9099