From 0f3a11ab59a6e87d19f0a3f3bf04061db6281305 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 26 Aug 2026 04:08:48 +0000 Subject: [PATCH 1/4] QEP-6: Project Tracker Structure and Order (Draft) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds QEP-6 as a Draft: the project-tracking unit (one tracker issue, Project issue type, native sub-issues one level down), positional order with plan semantics, dependencies for real constraints, descriptive phase milestones, the fixed status stamp, and the body's no-mirror discipline with a single Next: resume line. Adds the README index row (standard / Draft / –). Discussion: https://github.com/QuantEcon/qeps/issues/15 Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_013WNYd4EW71564zVcW81yx7 --- README.md | 1 + qeps/qep-0006-project-trackers.md | 269 ++++++++++++++++++++++++++++++ 2 files changed, 270 insertions(+) create mode 100644 qeps/qep-0006-project-trackers.md diff --git a/README.md b/README.md index 17c03fb..b69e746 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ need a QEP. |-----|-------|------|--------|---------| | [QEP-1](qeps/qep-0001-purpose-and-process.md) | QEP Purpose and Process | process | Accepted | v2 | | [QEP-2](qeps/qep-0002-standard-github-labels.md) | Standard GitHub Label Set and Labelling Policy | standard | Accepted | – | +| [QEP-6](qeps/qep-0006-project-trackers.md) | Project Tracker Structure and Order | standard | Draft | – | QEPs that set an ongoing rule are **maintained in place**: a substantive amendment bumps the QEP's `version` (shown above) under the same review process, rather than superseding diff --git a/qeps/qep-0006-project-trackers.md b/qeps/qep-0006-project-trackers.md new file mode 100644 index 0000000..32eba42 --- /dev/null +++ b/qeps/qep-0006-project-trackers.md @@ -0,0 +1,269 @@ +--- +qep: 6 +title: Project Tracker Structure and Order +author: "@mmcky" +status: Draft +type: standard +created: 2026-08-26 +discussion: https://github.com/QuantEcon/qeps/issues/15 +--- + +# QEP-6: Project Tracker Structure and Order + +| | | +| ------------ | -------------------------------------------------------------------- | +| **QEP** | 6 | +| **Title** | Project Tracker Structure and Order | +| **Author** | @mmcky | +| **Status** | Draft | +| **Type** | standard | +| **Created** | 2026-08-26 | +| **Discussion** | [QuantEcon/qeps#15](https://github.com/QuantEcon/qeps/issues/15) | + +## Summary + +This QEP defines the org's unit of project tracking: a **Project** is one tracker +issue, typed with the native `Project` issue type, whose work items are its +**native sub-issues** one level down. Every structural fact lives in exactly one +native carrier — membership in sub-issue edges, **order in list position**, +constraints in dependencies, grouping in milestones, freshness in one fixed +status stamp — and **sequence is never encoded in names**. The tracker body +carries what structure cannot: goal, current state, phase intent, gates, and +rationale, and it never restates what the platform already holds. + +## Motivation + +The practice exists; the convention doesn't. A 2026-08-23 audit of 99 +tracker-shaped open issues org-wide found 13 title conventions (the commonest +covering 23%), 92% with no issue type, 60% tracking work in body checkboxes or +prose rather than sub-issues, status stamps in three forms, and zero use of +native dependencies. Coordination issues also have no honest label under QEP-2 +([#11](https://github.com/QuantEcon/qeps/issues/11)), so "untyped = untriaged" +is currently unreliable. + +Two things force the decision now. First, trackers have a **machine consumer**: +the projects dashboard's collector reads every registered tracker nightly, and +three producer classes write them (people, the `qe` skills, and future +maintenance agents). Second, **ordering has arrived from the field**: the first +work plan reached for milestones to express sequence +(QuantEcon/textstrata#8), and a `wp{issue}-stage{n}` milestone naming +convention was proposed to generalise it (QuantEcon/skills#55). Milestones +group but do not order; the review of that first instance found the plan's own +phases and the milestone axis cutting across each other, with the real +dependencies left in prose. + +The root problem is structural: a sequence token in an issue title or a +milestone name **welds order to identity**. Every re-plan must then either +rename the work (breaking every prior reference to it) or let the names lie +against the visible list. Names are also the one carrier the machine consumer +cannot use: the dashboard's tracker contract deliberately **never reads title +conventions or labels**, and its redaction rules strip titles and milestone +names from private repositories (12 of the 28 registered trackers). Position, +edges, types, dates, and counts survive everywhere names do not — and each is a +single-writer fact: re-ordering a list is one gesture, renaming a token family +is one edit per issue. + +## Proposal + +### 1. The unit + +A **Project is one tracker issue**. It carries the org-level **`Project` issue +type** — a native issue type, not a label, so QEP-2's label set is untouched +and "untyped = untriaged" stays reliable. Issue types are org metadata rather +than repository content, so the type survives even where a private repository's +content is redacted. This resolves the gap reported in +[#11](https://github.com/QuantEcon/qeps/issues/11). + +Its **work items are its direct native sub-issues** — which may live in any +repository — and **progress is measured on direct children only, never +deeper**. Membership lives in the sub-issue edges: body checkboxes, plan +tables, and legacy tasklists are never work items. A tracker with no +sub-issues has **unmeasured** progress (never 0% — "no sub-issues" and +"nothing done yet" are different facts). + +### 2. Order is positional + +The sub-issue list is kept in **plan order**: **position is the order of the +plan**, and the **topmost open item is next**. The list is a plan, not a +queue — completed items keep their place, so the list stays readable as the +plan with progress visible in it. A new item is placed on arrival (it appends +by default; move it into its phase), and phase groups stay contiguous. + +**Re-ordering moves items and never renames them** — by drag in the UI or by +the sub-issue reprioritize API (`PATCH …/issues/{parent}/sub_issues/priority`; +GraphQL `reprioritizeSubIssue`); the two write the same fact. **Sequence is +never encoded in issue titles or milestone names** (no `W3 —`, no `Phase 2:` +prefixes, no `stage{n}` tokens). Issue numbers are the stable handles; titles +are pure descriptions. + +Order is an **operational property, not a published field** of the unit: a +consumer that publishes a child list should preserve list order (order is an +attribute of the membership edge and is observable exactly where membership +is), but no consumer may require, parse, or infer sequence from names. + +### 3. Constraints are dependencies + +A genuine sequencing constraint between work items is expressed as a **native +dependency** (blocked-by), never as prose. A dependency states a +**constraint** (a partial order), not a presentation order; position states +presentation. Dependencies are **optional** — most order is soft preference, +and edges are reserved for constraints that are real. Phase-level gates +("nothing in Phase 2 starts before Phase 1's exit criterion") are stated in +the body, not encoded as pairwise edges. + +### 4. Phases are milestones (optional) + +Where a tracker is large enough to want grouping, **milestones group work +items into phases**. Milestone names are **descriptive** ("Phase 1 — +Foundations"), never sequence tokens: a milestone is a single object that +issues *reference*, so renaming a phase is one edit that propagates +everywhere — the single-update-point property no title convention has. Phase +order shows as contiguity in the list; phase *meaning* (intent, exit +criterion, gate) lives in the body's phase table. + +Two platform limits are accepted rather than worked around: an issue takes +one milestone, so phases must partition the work; and milestones are +per-repository, so **cross-repo work items fall back to the body's phase +table** — a same-named milestone in another repository is a copy, and copies +diverge. + +### 5. The status stamp + +The tracker body carries **exactly one machine-read element**: the status +stamp, in one fixed form. + +- **Canonical**: a `## Where we stand (verified YYYY-MM-DD)` heading. +- **Accepted fallback**: a `> **Updated YYYY-MM-DD.**` banner line. + +The string `verified YYYY-MM-DD` must not appear anywhere else in the body — +free-text near-misses are the known failure mode of stamp parsing. Everything +else in the body is for people; no other body text is machine-read. + +### 6. The body + +The body opens with a **one-sentence goal**, then the **stamp section at the +top**. The stamp section may open with a single **`**Next:**` line** — a link +to one work item plus one line of pickup context (branch, failing thing, +where to resume). This is the body's **only** statement of what is next: it +is a dated claim inside the one section whose contract is +dated-claims-re-verified-on-update, and if it ever disagrees with the list, +**the list is the authority** and the line is stale prose awaiting its next +stamp. Trackers that do not run working sessions omit the line. + +The body carries what structure cannot: the goal and definition of done, the +where-we-stand narrative, phase intents and exit criteria, gates, sequencing +rationale, and scope boundaries ("what does not change"). The body **never +mirrors structure**: + +| Never in the body | It already lives in | +|---|---| +| Work-item rosters or checkbox work lists | the sub-issue list (membership, order, state) | +| Milestone→issue tables | the milestone (one click, always live) | +| Hand-written progress counts or percentages | native sub-issue progress | +| Pairwise dependency prose for edges that exist | the dependency edges | +| Any "next"/"currently on" claim outside the stamp's `Next:` line | list position | + +Work items are referenced from prose by number/URL (rendered live by GitHub), +never by copied titles. An informative body skeleton is given in Appendix A. + +### 7. Scope + +This QEP governs **project tracker issues** — the unit the projects registry +registers. The **programme layer** (programme → project → items) is +deliberately outside it. The surrounding *practice* — tracker vs period-plan +genres, session ledgers, succession, revision-log comment discipline — is +maintained in the org's `qe` skills, which cite this QEP as the authority on +the unit's structure. + +## Alternatives considered + +- **Sequence tokens in titles or milestone names** (`W0–W6`, `wp{n}-stage{m}`, + `Phase 2:` prefixes). Rejected: welds order to identity, so every re-order + renames k issues (breaking every prior "see W3" in immutable comment + history) or leaves the tokens lying against the visible list. Unreadable by + the machine consumer by contract, and stripped by redaction for private + repositories. Field evidence: token families harden into names on first + use, which is the tell that the real need is *handles* (issue numbers) and + *grouping* (milestones), not order. +- **The body's plan table as the order authority.** Rejected: the native list + renders in position order on the same page, so a table that enumerates a + second order is a mirror in visible conflict with structure, and the + discipline's own rule — claims re-verified, never carried forward — argues + against maintaining mirrors. The table's job is meaning (phases, gates, + rationale), not enumeration. +- **A `tracking` type label** (as floated in + [#11](https://github.com/QuantEcon/qeps/issues/11)). Rejected in favour of + the native `Project` issue type: QEP-2's one-type-per-issue label rule stays + untouched, and the type survives redaction where labels are never read. +- **GitHub Projects (v2) as the ordering surface.** Rejected as authority: + item order there is per-view, on a separate permission surface, in a second + system the collector does not read. Fine as a *lens* over the same issues; + never the source of truth. +- **Queue semantics for the list** (done sinks, next floats to top). + Rejected: destroys the list's readability as a plan, fragments phase + contiguity, and makes closing an item cost a move. Under plan semantics the + routine gesture (closing) costs nothing and moves always mean re-planning. +- **A standalone resume box above the body.** Rejected: a second dated prose + register beside the stamp section — two update points that diverge, and a + stylistic near-miss with the banner stamp form. Folded into the stamp + section as the single `Next:` line instead. + +## Adoption + +Obligations on the three producer classes and the one consumer: + +1. **New trackers** conform at creation: `Project` type, sub-issue work items, + stamp, plan-ordered list, order-free names. +2. **Registered existing trackers** are brought into this layout by the + conform tooling (QuantEcon/skills#49); the collector reports compliance + nightly. Adoption of the ordering rules is greenfield: at the 2026-08-23 + baseline only 3 of 28 registered trackers carry any milestone (all + descriptive) and none use dependencies, so nothing is renamed and no + history is rewritten. +3. **The `qe` skills** (`workplan-*`) operationalise the convention: create + sub-issues in plan position, reprioritize on re-plan, and refresh the + stamp section, `Next:` line, and list position as one atomic update. Their + lint/conform pass may verify mechanically: stamp form present and unique, + no sequence tokens in child titles or milestone names, no checkbox work + lists, at most one `Next:` line and only in the stamp section, phases + contiguous. +4. **The projects dashboard** treats the tracker's list order as the + published child order (its tracker contract currently re-sorts children by + issue number; that rule is amended to preserve list order — a one-row + contract change plus a sort removal, verified once against a deliberate + re-order on a public tracker). Order remains unpublished as a field; + dependencies continue to publish as counts. + +## Appendix A (informative): tracker body skeleton + +```markdown +*Goal: one sentence on what done looks like.* + +## Where we stand (verified YYYY-MM-DD) + +**Next:** Owner/repo#N — one line of pickup context. + +Narrative of state: recent movement, in flight, blockers. + +## Plan + +Work items and their order: the sub-issue list (top = next; completed items +keep their place). Constraints between items: native dependencies. Phases: +the milestones below. + +| Phase | Intent | Exit criterion | +|---|---|---| +| [Phase 1 — Name](milestone-url) | … | … | + +**Gates:** … + +**Sequencing rationale:** why this order — only what the list cannot say. + +## Out of scope / what does not change + +… +``` + +Free sections (Findings, Premises, Notes) may follow — the skeleton is a +floor, not a ceiling. Revision history lives in comments, per the practice +conventions maintained in the `qe` skills. From 1a0f063b195185075bd48a3286e91116186fcd49 Mon Sep 17 00:00:00 2001 From: Matt McKay Date: Thu, 27 Aug 2026 13:01:49 +1000 Subject: [PATCH 2/4] Apply the field-test amendment set from the QEP-6 field report Folds in the twelve amendments consolidated on the field report thread (see QuantEcon/qeps#19) plus two edits from its producer-tooling finding: - Scope the standard to GitHub.com (s7) - Ban purely numeric milestone names, verified live (s4) - Membership criterion: closing a work item advances the definition of done; unparented issues and unparented trackers are normal (s1) - Direct children homogeneous in kind; progress is a snapshot, not a time series (s1) - Dropped items leave the sub-issue list - percent_completed counts not_planned closures as completed (s2) - Gates: mostly phase-level in practice; a whole-project gate also carries the tracker-to-tracker dependency edge (verified live on a throwaway pair); stated once, in the project that waits (s3) - Parked-ness is derived from open blockers, never carried (s3) - Stamp refresh is a read-modify-write with a lossless read; sanitising surfaces get append-only treatment (s5) - Named replacements for the body bans; Related work section (s6) - Markdown template plus post-creation type read-back (Adoption 1) - Re-parenting guard on the conform tooling (Adoption 2) - QEP-6 authoritative over the dashboard contract during handover (Adoption 3) - Drop "untyped = untriaged" as a load-bearing justification (s1) Co-Authored-By: Claude Fable 5 --- qeps/qep-0006-project-trackers.md | 146 ++++++++++++++++++++++++++---- 1 file changed, 126 insertions(+), 20 deletions(-) diff --git a/qeps/qep-0006-project-trackers.md b/qeps/qep-0006-project-trackers.md index 32eba42..aeaa320 100644 --- a/qeps/qep-0006-project-trackers.md +++ b/qeps/qep-0006-project-trackers.md @@ -68,11 +68,10 @@ is one edit per issue. ### 1. The unit A **Project is one tracker issue**. It carries the org-level **`Project` issue -type** — a native issue type, not a label, so QEP-2's label set is untouched -and "untyped = untriaged" stays reliable. Issue types are org metadata rather -than repository content, so the type survives even where a private repository's -content is redacted. This resolves the gap reported in -[#11](https://github.com/QuantEcon/qeps/issues/11). +type** — a native issue type, not a label, so QEP-2's label set is untouched. +Issue types are org metadata rather than repository content, so the type +survives even where a private repository's content is redacted. This resolves +the gap reported in [#11](https://github.com/QuantEcon/qeps/issues/11). Its **work items are its direct native sub-issues** — which may live in any repository — and **progress is measured on direct children only, never @@ -81,6 +80,22 @@ tables, and legacy tasklists are never work items. A tracker with no sub-issues has **unmeasured** progress (never 0% — "no sub-issues" and "nothing done yet" are different facts). +An issue is a **work item** of a project if closing it advances the project's +definition of done. If the definition of done can be met without it, it is not +a member — however much it shares a repository, a theme, or an owner. **If the +goal must be widened to justify an item's membership, the item is not a +member.** Not every issue belongs to a project — an unparented issue is the +normal case, not a gap to be filled — and an unparented project tracker is +likewise normal: not every project answers to a programme. + +A tracker's direct children should be **homogeneous in kind** — all leaf work +items, or all project trackers. Progress is a count over direct children, so +mixing a multi-item project with leaf issues weights them equally and makes +the percentage meaningless. The percentage is also a **snapshot over current +direct children, not a time series**: membership changes — items dropped, +items moved to another project, a tracker split — move the number without +work occurring, so progress is never compared across a membership change. + ### 2. Order is positional The sub-issue list is kept in **plan order**: **position is the order of the @@ -89,6 +104,16 @@ queue — completed items keep their place, so the list stays readable as the plan with progress visible in it. A new item is placed on arrival (it appends by default; move it into its phase), and phase groups stay contiguous. +Dropping is different from completing. An item **dropped** from the plan — +closed `not_planned`, or superseded — is **removed from the sub-issue list**: +membership is the plan, and work no longer in the plan is not a member. The +issue itself persists with its own `state_reason` (`completed`, `not_planned`, +or `duplicate` with `duplicate_of`) and its cross-references intact — only +the membership edge goes. This is mechanical as well as semantic: +`sub_issues_summary.completed` counts every closed child regardless of +`state_reason`, so a dropped item left in place inflates the tracker's +reported progress. + **Re-ordering moves items and never renames them** — by drag in the UI or by the sub-issue reprioritize API (`PATCH …/issues/{parent}/sub_issues/priority`; GraphQL `reprioritizeSubIssue`); the two write the same fact. **Sequence is @@ -107,19 +132,40 @@ A genuine sequencing constraint between work items is expressed as a **native dependency** (blocked-by), never as prose. A dependency states a **constraint** (a partial order), not a presentation order; position states presentation. Dependencies are **optional** — most order is soft preference, -and edges are reserved for constraints that are real. Phase-level gates -("nothing in Phase 2 starts before Phase 1's exit criterion") are stated in -the body, not encoded as pairwise edges. +and edges are reserved for constraints that are real. Dependencies are +independent of the parent hierarchy, so an edge may cross project boundaries. + +**Gates** cover phase-level and project-level constraints. Most cross-project +gates are **phase-level** in practice — one phase of A waits on one phase of +B while the rest of both proceeds — and a phase-level gate ("nothing in +Phase 2 starts before Phase 1's exit criterion") is stated in the body, not +encoded as pairwise edges, because no object represents a phase. Where a +project genuinely waits on another project **in its entirety**, that gate +must also carry the native dependency edge between the two tracker issues, +with the body carrying only the rationale. State the constraint at the +granularity that is actually true: a tracker-to-tracker edge asserts that +*nothing* in the blocked project may start, and parks it wholesale in any +consumer deriving parked-ness from blockers. A gate is stated **once**, in +the body of the project that must wait; the other project points at it (see +§6, Related work) rather than restating it. + +Whether an item is **parked** is derived, not carried: an open item with an +open blocker is parked. Position states where an item sits in the plan; +dependencies state whether it can start. Where a gate is a decision rather +than an issue, create the issue for the decision rather than describing the +park in prose. ### 4. Phases are milestones (optional) Where a tracker is large enough to want grouping, **milestones group work items into phases**. Milestone names are **descriptive** ("Phase 1 — -Foundations"), never sequence tokens: a milestone is a single object that -issues *reference*, so renaming a phase is one edit that propagates -everywhere — the single-update-point property no title convention has. Phase -order shows as contiguity in the list; phase *meaning* (intent, exit -criterion, gate) lives in the body's phase table. +Foundations"), never sequence tokens, and never purely numeric (`gh issue +list --milestone` parses its argument as a number first, so a milestone +titled "2026" is looked up as milestone *number* 2026 and fails). A milestone +is a single object that issues *reference*, so renaming a phase is one edit +that propagates everywhere — the single-update-point property no title +convention has. Phase order shows as contiguity in the list; phase *meaning* +(intent, exit criterion, gate) lives in the body's phase table. Two platform limits are accepted rather than worked around: an issue takes one milestone, so phases must partition the work; and milestones are @@ -139,6 +185,19 @@ The string `verified YYYY-MM-DD` must not appear anywhere else in the body — free-text near-misses are the known failure mode of stamp parsing. Everything else in the body is for people; no other body text is machine-read. +Body writes are **whole-replace**: GitHub offers no partial update and no +compare-and-swap, so a stamp refresh is a **read-modify-write within a +single turn**, preserving all body content outside the stamp section — +concurrent writers otherwise silently discard each other's edits. The read +half must be **lossless**: read the body from a surface that returns its +source (the REST issues endpoint returns raw markdown). Some agent surfaces +return a sanitised rendering instead — HTML entities substituted, and +anything tag-shaped, such as a `` in a code snippet, silently +dropped — and writing such a read back destroys content while appearing to +preserve it. Where only a sanitising surface is available, treat the body as +append-only and record corrections as comments. This applies to every +programmatic body write, not only the stamp. + ### 6. The body The body opens with a **one-sentence goal**, then the **stamp section at the @@ -163,6 +222,21 @@ mirrors structure**: | Pairwise dependency prose for edges that exist | the dependency edges | | Any "next"/"currently on" claim outside the stamp's `Next:` line | list position | +The bans have named replacements: the body's **phase table carries phase +intent and exit criteria, never membership** — the question a roster answers +(which items are in which phase) is already answered by the sub-issue list +and the milestones. A ban with a named replacement survives contact with +producers; a ban alone gets worked around. + +A **Related work** section (optional) names sibling **projects** — one line +each on how they relate: informs, spawned by, shares an engine, gated by +(pointing at the body that states the gate). GitHub records only that a +mention happened; a deliberate relationship between projects is an assertion +with no native carrier, which is what the body is for. Entries name +projects, never work items — an entry may cite item numbers as the specifics +of a project-level relationship, but an entry whose subject is a work item +is the sub-issue list restated or dependency prose, both banned above. + Work items are referenced from prose by number/URL (rendered live by GitHub), never by copied titles. An informative body skeleton is given in Appendix A. @@ -175,6 +249,10 @@ genres, session ledgers, succession, revision-log comment discipline — is maintained in the org's `qe` skills, which cite this QEP as the authority on the unit's structure. +The QEP is scoped to **GitHub.com**: the sub-issue reordering endpoint is +absent from GitHub Enterprise Server API descriptions through 3.18, so the +ordering rule has no verified mechanism there. + ## Alternatives considered - **Sequence tokens in titles or milestone names** (`W0–W6`, `wp{n}-stage{m}`, @@ -213,20 +291,42 @@ the unit's structure. Obligations on the three producer classes and the one consumer: 1. **New trackers** conform at creation: `Project` type, sub-issue work items, - stamp, plan-ordered list, order-free names. + stamp, plan-ordered list, order-free names. A tracker template is a + **markdown template**, never an issue form: forms render field labels as + `###` headings and cannot emit the stamp heading or its dynamic date, + while a markdown template passes the body through verbatim and can set + `type:`. Templates cannot set milestone, parent, dependencies, or order — + those are post-creation for every producer. Creation is never trusted to + carry the type: after creating a tracker, assert the type by read-back + (`gh issue view N --json issueType`) and set it explicitly if absent — + non-interactive `gh issue create` does not apply templates at all, and + the REST `type` parameter is silently dropped for callers without push + access; both fail silently. Assert against the issue itself, not an org + type enumeration (`GET /orgs/{org}/issue-types` is 403 for app identities + where the repo-scoped read succeeds), and key automation on + `issue.type.name` — webhook payloads carry no template identifier. 2. **Registered existing trackers** are brought into this layout by the conform tooling (QuantEcon/skills#49); the collector reports compliance - nightly. Adoption of the ordering rules is greenfield: at the 2026-08-23 - baseline only 3 of 28 registered trackers carry any milestone (all - descriptive) and none use dependencies, so nothing is renamed and no - history is rewritten. + nightly. Claiming a work item that already has a parent **detaches it + from that parent**: sub-issue membership is single-parent, and both + `gh issue edit --add-sub-issue` and `--parent` set `replace_parent` + unconditionally, with no opt-out. The conform tooling must read an item's + existing parent before linking, and re-link that parent or record the + detachment — a programme tracker silently emptied this way reports its + remaining closed children as complete. Adoption of the ordering rules is + greenfield: at the 2026-08-23 baseline only 3 of 28 registered trackers + carry any milestone (all descriptive) and none use dependencies, so + nothing is renamed and no history is rewritten. 3. **The `qe` skills** (`workplan-*`) operationalise the convention: create sub-issues in plan position, reprioritize on re-plan, and refresh the stamp section, `Next:` line, and list position as one atomic update. Their lint/conform pass may verify mechanically: stamp form present and unique, no sequence tokens in child titles or milestone names, no checkbox work lists, at most one `Next:` line and only in the stamp section, phases - contiguous. + contiguous. The skills currently cite the dashboard's tracker contract, + which permits body constructs this QEP forbids; until that contract's + planned handover to this QEP, this QEP is authoritative for tracker + structure wherever the two disagree. 4. **The projects dashboard** treats the tracker's list order as the published child order (its tracker contract currently re-sorts children by issue number; that rule is amended to preserve list order — a one-row @@ -255,10 +355,16 @@ the milestones below. |---|---|---| | [Phase 1 — Name](milestone-url) | … | … | -**Gates:** … +**Gates:** … (phase-level gates live here; a whole-project gate also carries +a tracker-to-tracker dependency edge, with only the rationale stated here, +once, in the project that waits) **Sequencing rationale:** why this order — only what the list cannot say. +## Related work + +- [Project name](tracker-url) — one line on the relationship. + ## Out of scope / what does not change … From e61b1dac6840780edafef3b5b66c7bebe1ee23b1 Mon Sep 17 00:00:00 2001 From: Matt McKay Date: Fri, 28 Aug 2026 15:11:34 +1000 Subject: [PATCH 3/4] =?UTF-8?q?QEP-6:=20add=20the=20issue-type=20axis=20?= =?UTF-8?q?=E2=80=94=20three=20structural=20roles,=20content=20stays=20on?= =?UTF-8?q?=20labels?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Types carry structural role (Project / Task / Decision, each with its own meaning of "closed"); QEP-2 labels carry content, and the axes compose. Adoption gains the one-time reversible reshape of the org type set (Bug/Feature disabled, Task re-described, Decision created). Co-Authored-By: Claude Fable 5 --- qeps/qep-0006-project-trackers.md | 82 +++++++++++++++++++++++++------ 1 file changed, 67 insertions(+), 15 deletions(-) diff --git a/qeps/qep-0006-project-trackers.md b/qeps/qep-0006-project-trackers.md index aeaa320..e4fe8ff 100644 --- a/qeps/qep-0006-project-trackers.md +++ b/qeps/qep-0006-project-trackers.md @@ -25,9 +25,10 @@ discussion: https://github.com/QuantEcon/qeps/issues/15 This QEP defines the org's unit of project tracking: a **Project** is one tracker issue, typed with the native `Project` issue type, whose work items are its **native sub-issues** one level down. Every structural fact lives in exactly one -native carrier — membership in sub-issue edges, **order in list position**, -constraints in dependencies, grouping in milestones, freshness in one fixed -status stamp — and **sequence is never encoded in names**. The tracker body +native carrier — kind in issue types, membership in sub-issue edges, **order +in list position**, constraints in dependencies, grouping in milestones, +freshness in one fixed status stamp — and **sequence is never encoded in +names**. The tracker body carries what structure cannot: goal, current state, phase intent, gates, and rationale, and it never restates what the platform already holds. @@ -96,7 +97,45 @@ direct children, not a time series**: membership changes — items dropped, items moved to another project, a tracker split — move the number without work occurring, so progress is never compared across a membership change. -### 2. Order is positional +### 2. Issue types + +Native issue types carry **structural role**: what kind of object an issue is +in the tracking system. The axis is orthogonal to QEP-2's labels, which +classify **content** at triage regardless of project membership — the two +compose rather than compete (a work item whose content is a bug is `Task` +plus the `bug` label, never a `Bug` type). Types are org metadata: the +machine consumer branches on them, and they survive redaction where labels +are never read. + +The org type set is three structural roles, each with its own meaning of +"closed": + +| Type | Role | Closing means | +|---|---|---| +| `Project` | a tracker (§1) | the definition of done is met | +| `Task` | a leaf work item | the work shipped | +| `Decision` | a decision point in a plan | the choice is recorded | + +A **`Decision`** is open while the choice is pending, closed `completed` when +decided — with the choice recorded in the issue — and closed `not_planned` +when mooted. Work that cannot start before a choice is made is blocked-by +the decision (§4), so "parked on a decision" is derived like any other +parked-ness, and a consumer can render a project's open decisions as the +distinct objects they are. A `Decision` may still carry QEP-2's `discuss` +label: the label states what the conversation is; the type states the +object's role in the plan. + +`Project` is required on trackers and `Decision` on decision points; `Task` +on leaf work items is recommended, not required — an untyped issue remains +the normal pre-triage state. Hierarchy level is never encoded in type: +sub-issue edges carry level, so a tracker whose children are trackers is +still a `Project`. + +A type is admitted to the set only when it names a structural role with its +own meaning of "closed" that a machine consumer renders or branches on. +Content classification is never a type — that axis belongs to QEP-2. + +### 3. Order is positional The sub-issue list is kept in **plan order**: **position is the order of the plan**, and the **topmost open item is next**. The list is a plan, not a @@ -126,7 +165,7 @@ consumer that publishes a child list should preserve list order (order is an attribute of the membership edge and is observable exactly where membership is), but no consumer may require, parse, or infer sequence from names. -### 3. Constraints are dependencies +### 4. Constraints are dependencies A genuine sequencing constraint between work items is expressed as a **native dependency** (blocked-by), never as prose. A dependency states a @@ -147,15 +186,15 @@ granularity that is actually true: a tracker-to-tracker edge asserts that *nothing* in the blocked project may start, and parks it wholesale in any consumer deriving parked-ness from blockers. A gate is stated **once**, in the body of the project that must wait; the other project points at it (see -§6, Related work) rather than restating it. +§7, Related work) rather than restating it. Whether an item is **parked** is derived, not carried: an open item with an open blocker is parked. Position states where an item sits in the plan; dependencies state whether it can start. Where a gate is a decision rather -than an issue, create the issue for the decision rather than describing the -park in prose. +than an issue, create a `Decision`-typed issue for it (§2) rather than +describing the park in prose. -### 4. Phases are milestones (optional) +### 5. Phases are milestones (optional) Where a tracker is large enough to want grouping, **milestones group work items into phases**. Milestone names are **descriptive** ("Phase 1 — @@ -173,7 +212,7 @@ per-repository, so **cross-repo work items fall back to the body's phase table** — a same-named milestone in another repository is a copy, and copies diverge. -### 5. The status stamp +### 6. The status stamp The tracker body carries **exactly one machine-read element**: the status stamp, in one fixed form. @@ -198,7 +237,7 @@ preserve it. Where only a sanitising surface is available, treat the body as append-only and record corrections as comments. This applies to every programmatic body write, not only the stamp. -### 6. The body +### 7. The body The body opens with a **one-sentence goal**, then the **stamp section at the top**. The stamp section may open with a single **`**Next:**` line** — a link @@ -240,7 +279,7 @@ is the sub-issue list restated or dependency prose, both banned above. Work items are referenced from prose by number/URL (rendered live by GitHub), never by copied titles. An informative body skeleton is given in Appendix A. -### 7. Scope +### 8. Scope This QEP governs **project tracker issues** — the unit the projects registry registers. The **programme layer** (programme → project → items) is @@ -273,6 +312,11 @@ ordering rule has no verified mechanism there. [#11](https://github.com/QuantEcon/qeps/issues/11)). Rejected in favour of the native `Project` issue type: QEP-2's one-type-per-issue label rule stays untouched, and the type survives redaction where labels are never read. +- **Content-classifying issue types** (`Bug` and `Feature`, GitHub's default + set). Rejected: content classification is QEP-2's label axis, and a second + carrier for the same fact lets the two disagree — an issue typed `Bug` and + labelled `enhancement`, or typed and unlabelled and so invisible to every + QEP-2 query. The type axis is reserved for structural roles (§2). - **GitHub Projects (v2) as the ordering surface.** Rejected as authority: item order there is per-view, on a separate permission surface, in a second system the collector does not read. Fine as a *lens* over the same issues; @@ -288,9 +332,17 @@ ordering rule has no verified mechanism there. ## Adoption -Obligations on the three producer classes and the one consumer: - -1. **New trackers** conform at creation: `Project` type, sub-issue work items, +One org-level precondition, then obligations on the three producer classes +and the one consumer: + +1. **The org issue-type set** is reshaped once to the three roles of §2, by + four reversible calls to the issue-types API: `Bug` and `Feature` are + **disabled, not deleted** — disabling hides a type from every picker but + retains existing assignments latent, and is undone by one call — `Task`'s + description is edited to name the work-item role, and `Decision` is + created. Because the change is reversible it ships at the start of the + field test, not at acceptance. +2. **New trackers** conform at creation: `Project` type, sub-issue work items, stamp, plan-ordered list, order-free names. A tracker template is a **markdown template**, never an issue form: forms render field labels as `###` headings and cannot emit the stamp heading or its dynamic date, From 361be8c71bd2e866705a4f7ad98e43e6b332b160 Mon Sep 17 00:00:00 2001 From: Matt McKay Date: Fri, 28 Aug 2026 15:28:00 +1000 Subject: [PATCH 4/4] =?UTF-8?q?QEP-6:=20record=20the=20two-tier=20ruling?= =?UTF-8?q?=20=E2=80=94=20a=20programme=20is=20registry=20grouping,=20not?= =?UTF-8?q?=20a=20tier?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Records the outcome of the #24 discussion in one scope sentence: the grammar has two normative tiers and no third; a programme is a named collection of projects maintained by the projects registry. Co-Authored-By: Claude Fable 5 --- qeps/qep-0006-project-trackers.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/qeps/qep-0006-project-trackers.md b/qeps/qep-0006-project-trackers.md index e4fe8ff..446276c 100644 --- a/qeps/qep-0006-project-trackers.md +++ b/qeps/qep-0006-project-trackers.md @@ -282,8 +282,12 @@ never by copied titles. An informative body skeleton is given in Appendix A. ### 8. Scope This QEP governs **project tracker issues** — the unit the projects registry -registers. The **programme layer** (programme → project → items) is -deliberately outside it. The surrounding *practice* — tracker vs period-plan +registers. The grammar has **two normative tiers — project and work item — +and no third**: a *programme* is a named collection of projects, a grouping +the projects registry maintains, with no structural duties of its own. No +producer or consumer reads programme membership as structure, an unparented +tracker remains the normal case (§1), and hierarchy level never enters the +type set (§2). The surrounding *practice* — tracker vs period-plan genres, session ledgers, succession, revision-log comment discipline — is maintained in the org's `qe` skills, which cite this QEP as the authority on the unit's structure.