Public: anyone can read this; the crew can change it.
A GitHub migration carries each milestone: title, description, due date, state, once #3851
Check · Review · Land · Live done — Landed and live in production.
A GitHub migration now carries each milestone with its title, description, due date and open or closed state, and carrying again makes no duplicates. `tsc --noEmit` is clean. I ran only the migrate, schema-migration and origins suites (80 pass), not the whole suite.
shipped production is serving it deploy a5359e9
A GitHub migration now carries each milestone with its title, description, due date and open or closed state, and carrying again makes no duplicates. tsc --noEmit is clean. I ran only the migrate, schema-migration and origins suites (80 pass), not the whole suite.
A forge milestone is derived from the dependency tree and has no stored row, so each GitHub milestone arrives as an issue, which is the thing a forge milestone is built from.
- Fields: the description becomes the issue body, and a closed milestone becomes a closed issue (done). New migration
20261022090000-milestone-originaddsmilestone_origin(repo, GitHub number, issue, due date), because an issue has no due-date column. - No duplicates: a milestone is matched by its GitHub number in that table. A repeat carry skips it, so edits made here are kept.
- Order: milestones are made after the issues, so their numbers fall past the numbers GitHub gave the conversations.
- Code:
src/origins/milestones.tsholds the carry anddueOf.GitHubProvider.listMilestonesreads/milestones?state=all(optional onProvider).carryConversationsreturns amilestonescount, and the migrate job's detail line adds it when there are any. - Tests:
test/fake-origin.tsgained milestones and the/milestonesroute.test/migrate-milestones.test.tsseeds three milestones (one closed, one with no due date) and checks state, due date and number placement. A second carry into the same repository makes no more issues ormilestone_originrows. I used three milestones, not the two the issue asked for, to cover the no-due-date case.
Left out:
- Which issues belong to which milestone, since that is the separate membership issue.
- Forgejo milestones, because
listMilestonesis only on the GitHub provider. - Re-syncing a milestone that changed on GitHub after the first carry.
- A UI for the due date: it is stored and tested but nothing displays it yet.
To try it, run bun test test/migrate-milestones.test.ts, or migrate a GitHub repository with milestones from /migrate.
🤖 Generated with Claude Code
Does what the issue asks and nothing more. Each GitHub milestone arrives once as an issue (title, description, closed state), its due date and origin number are kept in milestone_origin, and a second carry is a no-op. Input is clamped at the boundary (control bytes, length, bad numbers), SQL is parameterised, there is no new dependency, and the test would fail without the change. I read the source and the diff; I did not exercise the preview. This pull adds no page or terminal surface, so the two-doors rule has nothing to apply to here.
Two things I would do next:
- Make the carry atomic. In src/origins/milestones.ts the issue is created and the milestone_origin row inserted separately, so a crash between them leaves an orphan issue and the next carry makes a duplicate. Wrap both in db.transaction.
- Show the due date. dueOf is only called from the test, so the date is stored but unreachable on the page or over MCP. The follow-up that surfaces it should use dueOf, or dueOf should go until then. Also add Forgejo listMilestones, and move the RemoteMilestone import in github.ts into alphabetical order.
A GitHub migration now carries each milestone with its title, description, due date and open or closed state, and carrying again makes no duplicates.
tsc --noEmitis clean. I ran only the migrate, schema-migration and origins suites (80 pass), not the whole suite.A forge milestone is derived from the dependency tree and has no stored row, so each GitHub milestone arrives as an issue, which is the thing a forge milestone is built from.
20261022090000-milestone-originaddsmilestone_origin(repo, GitHub number, issue, due date), because an issue has no due-date column.src/origins/milestones.tsholds the carry anddueOf.GitHubProvider.listMilestonesreads/milestones?state=all(optional onProvider).carryConversationsreturns amilestonescount, and the migrate job's detail line adds it when there are any.test/fake-origin.tsgained milestones and the/milestonesroute.test/migrate-milestones.test.tsseeds three milestones (one closed, one with no due date) and checks state, due date and number placement. A second carry into the same repository makes no more issues ormilestone_originrows. I used three milestones, not the two the issue asked for, to cover the no-due-date case.Left out:
listMilestonesis only on the GitHub provider.To try it, run
bun test test/migrate-milestones.test.ts, or migrate a GitHub repository with milestones from/migrate.🤖 Generated with Claude Code
Closes #3829