Repository navigation
Idea: Convention for use of milestones when building work plans #55
Description
Adopt wp{issue#}-stage{n} milestone naming for work-plan stages
Proposal: when a work plan divides its work into stages, give each stage a GitHub milestone named after the work-plan issue that owns it — wp{issue#}-stage{n}, e.g. wp8-stage1 for stage 1 of the plan tracked in issue #8. Adopted ad hoc in QuantEcon/textstrata on 2026-08-25; filing it here to decide whether it becomes the documented convention for all work plans.
Why name the milestone after the plan
Milestones are a flat, repo-global namespace with no parent field, so a bare Stage 1 is ambiguous the second a repo has two work plans, and there is nothing in the milestone itself that says which plan it belongs to. Embedding the issue number makes the milestone self-describing and gives the convention four practical properties:
- Ownership is readable from the name. Anyone landing on the milestone list can tell
wp8-stage1belongs to the plan in TRACKING: work plan 2026-07-22 — rubric v2 quick-wins, merge + email, skill wiring toward the #654 re-test #8, without opening anything. - No label sprawl. QEP-2 keeps the label set deliberately minimal, and
stage-1/stage-2/stage-3labels would sit outside it. Milestones carry percent-complete and an issue list natively, which is exactly what a phased plan wants, so the structure lands without touching the label vocabulary. - Retirement is unambiguous. When the plan closes, its
wp8-*milestones close with it. A genericStage 2would linger and invite a later, unrelated plan to reuse it with different meaning. - It is the only native grouping axis left when sub-issues are taken. GitHub allows one parent per issue. In the textstrata case the roadmap issues were already native sub-issues of a cross-repo programme tracker (QuantEcon/project-translation#49), so claiming them as sub-issues of the local plan was not possible without breaking that hierarchy. Milestones grouped them inside the repo while the cross-repo parentage stayed intact — a situation any repo whose work belongs to a programme tracker will hit.
First instance
QuantEcon/textstrata#8 is a phased work plan produced from a repository audit. Its stage milestones were created and assigned on 2026-08-25:
| Milestone | Issues |
|---|---|
wp8-stage1 |
#1, #2, #5, #6 |
wp8-stage2 |
#3 |
wp8-stage3 |
#7 |
The tracker issue itself was left unmilestoned, since it spans all three stages — worth confirming as part of the convention rather than leaving to taste.
Where it would be documented
| File | Section | Change |
|---|---|---|
qe/skills/workplan-project/SKILL.md |
step 4 (draft the work package) | The drafting rules cover labels per QEP-2 but say nothing about milestones. Add the naming rule, and that the plan table's phases are what the milestones mirror. |
qe/skills/workplan-project/SKILL.md |
step 5 (file it) | The creation order does not create or assign milestones. Add both steps, after the sub-issues exist. |
qe/skills/workplan/SKILL.md |
"The convention" | Records observed practice pending a QEP. If this is adopted, it belongs in that list alongside the untyped-plan-issues rule. |
| the planned work-plan QEP | — | qe/skills/workplan/SKILL.md notes that formalising the convention as a QEP is planned; if that is where naming rules should live, this issue is an input to it rather than a skill edit. |
Tooling note
Milestones must exist before anything can be assigned to one, and creating them is not covered by every agent surface: in the textstrata run the available tooling could assign a milestone by number but could neither create nor list milestones, so a human created the three by hand and the numbers had to be looked up before assignment. gh can do it directly — gh api repos/{owner}/{repo}/milestones -f title=wp8-stage1 — so if this convention is adopted, workplan-project step 5 should create the milestones itself rather than leaving a manual gap in an otherwise scripted flow.
Open questions
stageorphase? Tracking: sequenced work plan for the Stage 1–3 roadmap textstrata#8's own plan table is organised by "Phase 0–5" while its milestones track the programme's Stage 1–3 — the two divisions are real and distinct there, but a convention should say which one the milestone mirrors, or allow either token.- Cross-repo plans. Milestones are per-repo. When a plan in repo A drives work in repo B,
wp8-stage1in repo B names an issue number that does not exist there. Options: accept the ambiguity, or qualify the token. - Does the tracker carry a milestone? Left unmilestoned in the first instance; needs a stated rule.
- Separator and padding.
wp8-stage1as used, versuswp-8-stage-1or zero-padded forms. Worth fixing so milestone lists sort predictably once a repo has several plans.
Activity
mmcky commentedon Aug 25, 2026
Review of the first instance
I reviewed QuantEcon/textstrata#8 and its three milestones against this proposal. The first instance is a good argument for the membership half of the convention, and it quietly demonstrates that the ordering half is still missing.
The milestones carry the programme's axis, not the plan's
QuantEcon/textstrata#8's body is explicit that the milestones were "taken from each issue's own text — the shared footer marks all of them as part of #49's programme Stage 1". But the plan's own sequencing is Phase 0–5, and the two axes cut across each other:
wp8-stage1wp8-stage2wp8-stage3So
wp8-stage1is four issues spanning three phases in no stated order. The milestone answers "whose programme stage is this?" and says nothing about "what comes first?".Which means finding F4 is only half-done
F4 reads: "the Stage 1/2/3 structure and the inter-issue dependencies (#2 → #1, #6 → #1) lived only in prose", marked ✅ Done. The milestones encoded the structure. The dependencies are still prose — they live in the plan's Sequencing paragraph and nowhere machine-readable. Milestones group; they do not order. That is the gap I think this issue should be widened to cover.
The name has two owners
wp8names the repo-local sequencing companion;stage1names QuantEcon/project-translation#49's division. This issue files "cross-repo plans" as a hypothetical open question — it is live in the very first instance. Ruling the other way does not rescue it: if the milestone must mirror the plan's phases, textstrata's becomewp8-phase0…wp8-phase5, at which point QuantEcon/textstrata#3 has no home and phase 2 is a milestone of one. The phase axis is too fine for milestones; the stage axis is the right grain and it is not the plan's.The
descriptionfield is empty on all three milestonesThat field is where "Stage 1 of QuantEcon/project-translation#49, sequenced by QuantEcon/textstrata#8" belongs, and it undercuts the "ownership must be readable from the name" argument — a description holds a working link, a name holds eight characters. Using both beats overloading the name, and it makes the
wp{n}-prefix optional rather than load-bearing.Three separable things
Conflating these is what makes the open questions hard to settle:
On "stage or phase"
From the projects-dashboard side there is a hard collision.
stageis already the declared lifecycle column of a project — idea, proposed, active, steady, parked, done, dropped — and the public pipeline view is built on it. A milestone tokenstage1meaning "phase 1 of a plan" would render next tostage: activemeaning a pipeline column, on the same pages. I would preferphase, which also matches QuantEcon/textstrata#8's own plan table.What a milestone convention would actually reach
Worth being concrete about the machine consumer, because the answer is "nothing", twice over. Links below are into QuantEcon/status-projects, which is private — they will 404 for non-members; the behaviour they describe is visible on the public dashboard.
A child's milestone is unread by construction. The collector's tracker query reads
milestone { title dueOn progressPercentage }on the tracker issue, while the child page carries onlynumber title state repository(collector/collect.py#L249-L254). Stage milestones live on children. So under the convention exactly as drafted — which deliberately leaves the tracker unmilestoned because it spans all stages — the one field the dashboard reads is guaranteed empty, and the fields that carry the convention are never fetched.And a milestone name does not survive redaction anyway. For a tracker in a private repository the published milestone is stripped to its due date and progress percent; the title is dropped outright. That is not an oversight, it is the contract: a milestone title is repository content. So even if the convention put
wp8-stage1on the tracker itself, the name would be unreadable for every private project. Of the 28 projects on the public dashboard, 12 are in that class.Adoption would be greenfield, which is the good news. Only 3 of the 28 registered trackers carry a milestone at all today —
actions#110("Maintenance audit 2026-07"),QuantEcon.py#925("JupyterLite / WASM browser support"), and one on a private tracker whose title is redacted to a bare 38%. None is stage-shaped, so a convention would not be fighting existing practice. But note what those three show: the useful thing about a milestone in the wild has been its prose description of a body of work, not a structural token.Ordering: numbered titles, and the primitive nobody uses
The proposal @mmcky raised is that the milestone groups and a number in the issue title orders within it, degrading to plain numbered titles in the many repos that use no milestones. Two things in its favour, and two caveats.
In favour: an issue title is the one signal that survives every consumer today. Child titles are fetched and are rendered in the project page's sub-issue list, so numbered titles would show up on the dashboard with no collector change at all — against the three separate changes reading child milestones would need. It also needs no push access: in the textstrata run the agent tooling could assign a milestone but could neither create nor list one, so a human created all three by hand.
Caveat one: it does not solve visibility. A private tracker publishes no child list at all — not even its public children, because the parent-to-child edge is itself a fact held in the private repository. So numbered titles are invisible for the same 12 of 28 projects, for a different reason than milestone names are. Numbering addresses the tooling problem, not the privacy one. Better stated now than discovered later.
Caveat two: GitHub already has a native ordering primitive the org does not use. Issue dependencies express
#2 → #1and#6 → #1exactly, need no naming convention, and are already read nightly by the collector —blockedByplusissueDependenciesSummary, published as a count that survives redaction. The 2026-08-23 org audit quoted in QuantEcon/qeps#15 measured 0 of 99 tracker-shaped issues using them.These are not competing. Dependencies express constraint (a partial order); numbering expresses presentation (a total order). Tiles need the second — a DAG does not tell you what to render third.
One more thing the ordering rule has to survive
Child order on the dashboard today is "ascending by issue number within a repository", and the stated reason is not meaning but determinism: a truncated list says "showing the first N", so N must be of a stated order. Issue number is creation order, which is an accidental proxy for planning order and stops being one the moment a step is inserted late. That is exactly the case sparse numbering (10, 20, 30) is for, and it is also why a numbering convention needs a stated tie-break for unnumbered issues rather than leaving them to fall wherever.
Suggested shape
descriptionnaming the owning plan by link, and the axis stated as the plan's own division rather than left to taste.One hygiene note that this comment had to observe and is worth folding into the convention: bare
#Nreferences in a cross-repo discussion resolve against the reading repo. Every reference above is fully qualified for that reason — which is exactly F4's sibling finding F3 in the textstrata audit, and a good candidate for a stated rule rather than a per-audit catch.The QEP-scope half of this is posted at QuantEcon/qeps#15.