Skip to content

ci: generate the README index, and check that ordered-list markers ascend - #30

Merged
mmcky merged 3 commits into
mainfrom
claude/qep-index-and-checks
Sep 9, 2026
Merged

ci: generate the README index, and check that ordered-list markers ascend#30
mmcky merged 3 commits into
mainfrom
claude/qep-index-and-checks

Conversation

@quantecon-services

Copy link
Copy Markdown
Collaborator

Two mechanical changes and the AGENTS.md text that follows from them. Ruled 2026-09-08; discussion on #26, items 2 and 4. No QEP text changes, so no version question — QEP-1's own Automation section wants a matching edit and that lands with the v4 amendment carrying #26's other items.

1. The README index is generated

stamp.mjs previously synced the Type and Version columns of rows that already existed. It now rebuilds the whole table from each QEP's frontmatter, ordered by number (renderIndex/buildRow in qeps.mjs, which read column positions from the table header so a reordered or extended table needs no code change). check.mjs no longer requires a PR to carry its own row and no longer enforces Type/Status/Version parity — a stale table is a warning, not a failure.

Why. The table is a merge-conflict magnet: four open QEP PRs contend for rows in one table, and #18 is currently unmergeable on that single line against QEP-3's row.

What this does and does not fix. A branch that already carries a row will still conflict textually — strip the row. But a mis-resolved index conflict is now self-healing, because the post-merge regeneration restores the table from frontmatter whatever the resolution did to it. So a QEP that merges out of numeric order slots into position automatically, and gaps while drafts are open need no later hand-insertion. That last point is the answer to "what if QEP-6 merges before QEP-5".

2. Ordered-list markers must ascend in source

Markdown renumbers an ordered list on render, so a source list reading 1., 2., 2., 3., 4. displays as 1–5 while every external "clause N" citation silently shifts by one. QEP-6's Adoption section is exactly that, and it survived a twelve-amendment review (#19), a field report and four PR comments.

Fenced blocks are exempt; blank lines do not end a run; a more-indented line is treated as an item's continuation and a deeper list as independent.

3. AGENTS.md follows, in four places

  • The index section now says the table is generated and must not be hand-edited, with the gap and conflict behaviour spelled out.
  • Drafting and Accepting stop telling authors to write a row — status lives in two places in the document, not three.
  • A new section: cite a section by its heading, not its number. Inserting §2 into QEP-6 mid-draft renumbered §2–§7 to §3–§8 and forced a correction onto a ruling that had already cited them; the qe skills, the dashboard's tracker contract and several tracking issues all cite these.
  • Accepting also records that a merged QEP may not carry status: Draft — QEP-3 did, for two months (QEP-3 is merged to main as Draft — a state the lifecycle does not admit, and #25 waits on it #27, being fixed in QEP-3: Accepted #29).

Verification

Each claim was tested rather than reasoned about:

Test Result
Generator against the current index reproduces it byte-for-byte — a no-op on correct input
check.mjs with QEP-6's file present and no row for it passes, with the warning — the relaxation works
Monotonic check against the real qep-0006-project-trackers.md reports line 364 against line 349 — catches the live defect
stamp.mjs on a correct tree nothing to stamp or sync
stamp.mjs after deleting QEP-3's row and flipping QEP-2's Status to Draft both repaired; tree returns to the committed content exactly

node .github/scripts/check.mjsQEP checks passed.
node .github/scripts/check-labels.mjsLabel appendix checks passed (1 companion file(s)).

Suggested landing order

Before #18's QEP-6 repairs — it makes that PR's one-line dirty conflict disappear rather than needing a both-rows resolution. The check.mjs condition refusing status: Draft on main is deliberately not here: it must land after #29, or it fails on QEP-3 itself.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Hwm5shrGXmaq4r3Lp9kS33


Generated by Claude Code

…cend

Two mechanical changes and the AGENTS.md text that follows from them. Ruled
2026-09-08; discussion on #26 items 2 and 4.

THE INDEX IS NOW GENERATED. stamp.mjs previously synced the Type and Version
columns of rows that already existed; it now rebuilds the whole table from each
QEP's frontmatter, ordered by number (renderIndex/buildRow in qeps.mjs, which
read column positions from the table header so a reordered table needs no code
change). check.mjs no longer requires a PR to carry its own row and no longer
enforces Type/Status/Version parity — a stale table is a warning, not a
failure.

The table was a merge-conflict magnet: four open QEP PRs contend for rows in
one table, and #18 is unmergeable on that single line against QEP-3's row.
A branch that still carries a row will conflict textually — strip the row —
but a mis-resolved conflict is now self-healing, because the post-merge
regeneration restores the table from frontmatter whatever the resolution did.
A QEP that merges out of numeric order slots into position automatically, so
gaps while drafts are open are normal and need no later hand-insertion.

Verified: the generator reproduces the current index byte-for-byte, and with
QEP-6's file present but no row for it, check.mjs passes with the warning
rather than failing.

ORDERED-LIST MARKERS MUST ASCEND IN SOURCE. Markdown renumbers an ordered list
on render, so a source list reading 1., 2., 2., 3., 4. displays as 1-5 while
every external "clause N" citation silently shifts by one. QEP-6's Adoption
section is exactly that, and it survived a twelve-amendment review, a field
report and four PR comments. Fenced blocks are exempt; blank lines do not end a
run; a more-indented line is a continuation and a deeper list is independent.
Verified against the real file: it reports qep-0006 line 364 against line 349.

AGENTS.md follows in four places: the index section now says the table is
generated and must not be hand-edited, the drafting and accepting sections stop
telling authors to write a row (status lives in two places in the document, not
three), a new section says to cite a section by its heading rather than its
number, and the CI section is corrected. The accepting section also records
that a merged QEP may not carry status Draft.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hwm5shrGXmaq4r3Lp9kS33
quantecon-services pushed a commit that referenced this pull request Sep 8, 2026
…tream amendments

Folds three ruled changes into this amendment and drops one it originally
proposed, so v3 lands as one coherent round instead of a v3 that a v4 would
immediately partly delete. Ruled 2026-09-08; discussion on #26, #9 and #22.

DROPPED FROM THIS AMENDMENT: the one-week default comment window. The ruling
went further than shortening it.

THE DECISION DEADLINE GOES. Step 3 of "How a QEP is decided" is struck and the
acceptance trigger becomes "no objection is outstanding" rather than a date —
lazy consensus with no clock, so a QEP nobody objects to still lands without
needing the lead, and nothing is accepted by the mere passage of time. Roles,
the Summary's "clear deadline" promise, and the README's Proposing steps all
follow.

The evidence is recorded under Alternatives considered: on 2026-09-08 none of
the four open QEP pull requests had a live deadline — #8 had never set one in
47 days, #13's and #23's had passed, #18's was deliberately deferred — while
nothing in CI ever read a PR description. The field was required by this
document and checked by nothing. Enforcing it in CI was considered and
declined: it would have turned three open PRs red for a rule they predated.

THE README INDEX IS GENERATED, post-merge from frontmatter and ordered by
number, so a PR carries no row of its own and two QEP PRs cannot collide on one
line of one table. The mechanism shipped ahead of this amendment in #30,
together with the ordered-list check; Automation now names both, and a stale
index is a warning rather than a failure. Numbering states what already
happened informally: a number is reserved when its draft PR opens, released if
that PR closes unmerged, so index gaps are normal while drafts are open.

A DOWNSTREAM QEP CARRIES ITS UPSTREAM AMENDMENTS — #9's sentence, widened by
the case it did not anticipate. Where the superseded standard is not itself a
QEP, the Adoption section must name the contract, the obligation and a date by
which the handover completes: QEP-6 asserts precedence over an external tracker
contract with no date on it, and the qe skills depend on that sentence at
runtime. Closes #9.

Also brings the branch up to date with main, which had moved on by QEP-3's
merge (this branch predated it, so it carried neither the file nor its index
row).

Verified green under both the pre-#30 checks on this branch and #30's new ones,
including the ordered-list rule over the rewritten Adoption entry.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hwm5shrGXmaq4r3Lp9kS33
quantecon-services pushed a commit that referenced this pull request Sep 8, 2026
…tion 1-5

The Draft repairs ruled on 2026-09-08, from the two field reports on this PR
and a review of all four dashboard projects. The comment window has not opened
and there are no reviews on this PR, so every edit here is a free Draft edit:
no version, no window reset.

SECTION 4, THE PHASE GATE. The clause that sent phase-level gates to the body
"because no object represents a phase" is what produced six uncarried
orderings across two conformant trackers. The dependency graph is
many-to-many, which is enough: the first item of the waiting phase is blocked
by every item of the phase it waits on. Linear in the upstream phase rather
than a cross-product, it publishes how many upstream items are still open, and
it clears when the PHASE closes rather than when the upstream tracker closes.

The alternatives were checked against the live graph rather than reasoned
about. A tracker-to-tracker edge over-parks: section 4 says it asserts nothing
in the blocked project may start, and status-projects#89's Decide phase is
being worked now. A single tail edge under-carries: of #69's five Build items,
#76 and #77 both report blocking 0 and the five have no chain between them, so
closing the tail implies nothing about the rest.

SECTION 4, THE ITEM EDGE. Made explicit that an item-level constraint is a
native edge whether or not the two items share a project (FR-2's first half) —
a producer read the gate paragraph's "cross-project" as covering item edges and
wrote three of them as prose.

SECTION 4, THE DECISION GATE. Where a phase's exit criterion is a single
Decision closing, the gate is a decision gate: the object exists, so each item
that waits carries the edge to it (FR-1). Both producers read section 2 and
section 4 as pointing different ways here and both chose the body.

SECTION 4, THE BACK-POINTER IS STRUCK. Blocked-by and blocking are two ends of
one edge — verified live, #73 reports blocking 1 while #94 reports blocked_by 3
— so the waited-on project needs no back-pointer to stay in step. No qe skill
writes a dependency edge at all, so the obligation had zero producers and a
free platform inverse: it will be missed every time, and was. A Related work
line stays available prose in section 7, not an obligation.

SECTION 1. Decision children are compatible with either homogeneous kind, a
closed-completed Decision counts toward progress like any child, and a
consumer may render decisions apart (FR-3). Two of eleven and two of nine
children on the reviewed trackers are decisions, and the Themes programme
tracker carries seven beside four project trackers.

SECTION 8. "No producer or consumer reads programme membership as structure"
stops being true the day the dashboard's programme read ships, so it now
separates the producer duty from a consumer's observation: observing a front
door creates no duty on any tracker and no third tier.

APPENDIX A. Its Gates line restated both struck rules verbatim, and it is the
text producers copy — so it changes in the same commit, as the field report
asked.

ADOPTION IS RENUMBERED 1-5. The source read 1., 2., 2., 3., 4.; Markdown
renumbers on render, so the published document showed 1-5 while every external
"clause N" citation after the first was off by one. Clause 4 also gains FR-4's
obligation: re-stamp the section in the same turn as any change to the list,
because the phase table is an undated claim outside the one section whose
contract is that its claims are re-verified on update, and it went stale within
a day of a re-plan.

FRONTMATTER gains related: [2] with the matching header-table row, which
check.mjs has always enforced as a pair and which this QEP — the one that
reasons most explicitly about another — did not carry.

THE README INDEX ROW IS REMOVED rather than merged. This branch predated
QEP-3's merge and both inserted a row after QEP-2's, which is what made the PR
unmergeable. Under the generated-index rule the row is derived from
frontmatter, so stripping it returns README to its base content and the merge
with main is clean. Note the consequence: until #30 lands, this branch's own
check.mjs still requires a row per QEP file, so CI here is red until then.
That ordering is deliberate.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hwm5shrGXmaq4r3Lp9kS33
@mmcky
mmcky requested a balanced review from Copilot September 9, 2026 00:27

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

It changes merge-gating CI logic and the post-merge auto-commit-to-main flow (relaxing a hard parity failure to a warning and adding a new hard-failing check), so the enforcement-policy change warrants human sign-off.

Pull request overview

This PR reworks the QEP CI tooling so the README index table is fully generated from each QEP's frontmatter (rather than hand-maintained and parity-checked), and adds a new check that ordered-list markers ascend in source. It fits the repo's goal of removing the README index as a merge-conflict magnet across concurrently-open QEP PRs, and of preventing Markdown ordered-list renumbering from silently shifting external "clause N" citations. The accompanying AGENTS.md edits document the new "generated index / do not hand-edit" rule, drop the instruction to add a README row, and add a "cite sections by name, not number" convention.

Changes:

  • qeps.mjs: adds buildRow/renderIndex (and extends readIndex to track table bounds and all column indices) so the whole index body is rebuilt from frontmatter, ordered by number, with column order read from the header.
  • stamp.mjs and check.mjs: stamp.mjs now regenerates the entire index post-merge; check.mjs downgrades index staleness from a hard failure to a warning and adds the new ordered-list ascending check.
  • AGENTS.md: updates the index/accepting/drafting/CI sections to match, and adds the section-citation guidance.
File summaries
File Description
.github/scripts/qeps.mjs Adds buildRow/renderIndex and extends readIndex with start/end/header and full column map to drive index regeneration.
.github/scripts/stamp.mjs Replaces per-column Type/Version sync with full index regeneration from frontmatter, ordered by number.
.github/scripts/check.mjs Removes the hard README parity check (now a warning) and adds the ordered-list ascending-marker check.
AGENTS.md Documents the generated index, removes "add a README row" guidance, and adds the cite-by-name convention.
Review details
  • Files reviewed: 4/4 changed files
  • Comments generated: 0
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

…ator

Four fixes from review of this PR.

**A fence no longer ends the lists it is nested inside.** `runs.clear()` on
every fence meant an INDENTED fence — which Markdown treats as a continuation
of its list item, and which AGENTS.md in this PR calls exempt — reset the
surrounding run, so a marker repeated across it went unreported. That is the
exact defect class the check exists for. The reset is now scoped by the
fence's own indent, the same rule already applied to every other non-blank
line. Verified against five cases: a plain repeat, a repeat after an indented
fence, a repeat across an indented fence (previously missed), correct
numbering across an indented fence, and two independent lists separated by a
top-level fence, which must stay clean because a non-indented fence really
does end the list.

**`buildRow` no longer drops trailing columns.** Width came from the columns
the function knows about, so an unknown column between two known ones was
emitted empty as documented, but one at the END of the table fell off the row
and its content was destroyed. Width now comes from the header's own count.

**The stale-index warning is a PR annotation.** Parity is a warning rather
than a failure now, and a line in the raw log is a signal nobody reads on a
green check.

**Duplicate and missing QEP numbers are errors.** The index is generated from
these: a missing number drops a QEP out of the table silently, a duplicate
emits two rows under one heading, and neither surfaces anywhere else. QEP-1
expects colliding proposals to be adjusted at merge — this is what tells the
author there is a collision.

The generator still reproduces the committed README byte-for-byte.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@mmcky

mmcky commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Four fixes from review

Pushed in 3df12ad. The generator still reproduces the committed README.md byte-for-byte, and node .github/scripts/check.mjs passes on the tree.

1. A fence no longer ends the lists it is nested inside. runs.clear() fired on every fence, but an indented fence is a continuation of its list item — which is what this PR's own AGENTS.md text means by "fenced code blocks are exempt". The effect was that a marker repeated across such a fence went unreported: the exact defect class the check exists for.

2. two

   ```
   code
   ```

2. repeat      <- renders as 3; citations shift; was NOT caught

The reset is now scoped by the fence's own indent, the same rule already applied to every other non-blank line. Verified against five cases with the real check.mjs:

Case Want Before After
Plain repeat 1. 2. 2. error error error
Repeat after an indented fence error error error
Repeat across an indented fence error clean error
Correct numbering across an indented fence clean clean clean
Two independent lists split by a top-level fence clean clean clean

The last case is why the fix is indent-scoped rather than a blanket removal of the reset: a non-indented fence really does end a list in CommonMark, and the second list legitimately restarts.

2. buildRow no longer drops trailing columns. Width came from the columns the function knows about, so its comment held for an unknown column between two known ones but not for one at the end — that cell fell off the row and its content was destroyed. Reproduced by adding a Discussion column: rows came back with five cells against a six-column header. Width now comes from idx.header.length.

3. The stale-index warning is a PR annotation (::warning file=README.md::). Parity is a warning rather than a failure now, and a line in raw log output is a signal nobody reads on a green check.

4. Duplicate and missing QEP numbers are errors. The index is generated from these, so a missing qep: drops a QEP out of the table silently and a duplicate emits two rows under one heading — neither surfaces anywhere else. QEP-1 expects colliding proposals to be "adjusted at merge"; this is what tells the author there is a collision to adjust. Nothing in the tree trips either check today.

Number 4 is the one addition that goes beyond fixing what was here — say the word if you would rather it landed separately.

…ated

The note still told readers the Type/Version columns are "kept in sync by
CI" — the behaviour this PR replaces with whole-table regeneration from
frontmatter. AGENTS.md was updated in four places for that change and the
README's own description of the same mechanism was left behind, so main
would have carried a stale account of the thing this PR ships.

The sentence about `Version` reading `–` is deliberately left alone: it is
still true today, and it is QEP-1 v3 (#23) that changes it, so that half
belongs in the PR that makes it false.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
mmcky added a commit that referenced this pull request Sep 9, 2026
The note said `Version` reads `–` until a QEP is first amended — the v1–v2
rule this amendment replaces. Under v3 every merged QEP carries a version
from the moment it lands, so the column shows `v{N}` from `v0` up and a `–`
means only that the stamp has not reached that QEP yet, which QEP-1's
Adoption section commits to closing.

Stated as what the column means rather than hedged with when the automation
arrives: the pointer to Adoption carries the gap, as it does for the rest of
this amendment.

The note's other stale half — "the Type/Version columns are kept in sync by
CI", which whole-table regeneration replaces — is fixed in #30, the PR that
makes it false.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@mmcky

mmcky commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Also: the README index note

a68ebc4. AGENTS.md was updated in four places for the generated index, but the README's own description of the same mechanism — "the Type/Version columns are kept in sync by CI" — was left behind, so main would have carried a stale account of what this PR ships. It now says the table is generated from frontmatter and must not be hand-edited.

Surfaced by a suppressed Copilot comment on #23, which spotted the neighbouring half of the same sentence. That half — Version reads until a QEP is first amended — is QEP-1 v3's to change and is fixed in #23. The two edits are on separate source lines and compose cleanly.

@mmcky
mmcky merged commit 56eebce into main Sep 9, 2026
1 check passed
@mmcky
mmcky deleted the claude/qep-index-and-checks branch September 9, 2026 03:39
mmcky added a commit that referenced this pull request Sep 9, 2026
Takes #30's generated index and the reworked checks. The README index note
conflicted: both PRs rewrote that sentence, #30 replacing "kept in sync by
CI" with the generated-table description and this branch replacing the
`Version` clause with v3's rule. The resolution keeps both halves — the
table is generated from frontmatter, and `Version` shows `v{N}` from `v0`
up with `–` meaning the stamp has not reached that QEP yet.

I had said the two edits sat on separate source lines and would compose
without a conflict; they overlapped on the line they share.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
mmcky added a commit that referenced this pull request Sep 10, 2026
…ex (#23)

**Substantive amendment: QEP-1 v2 → v3.** Discussions: #22 (stamping),
#26 (the deadline and the index).

## What this amendment changes

**1. Stamp `version` and `version-hash` from v0.** Every QEP that
records an outcome carries both fields from the merge that records it. A
Draft carries neither; the post-merge stamp writes `version: 0` and the
hash uniformly across Accepted, Rejected and Withdrawn, and the hash
moves on every later merged change, editorial included. `version-hash`
is stated plainly to be a historical anchor, not a file checksum. The
implicit-v0 design it replaces is recorded under *Alternatives
considered*.

**2. The decision deadline is struck; acceptance needs one other Core
Maintainer.** Step 3 of *How a QEP is decided* becomes: once the PR has
been announced and no objection is outstanding, a Core Maintainer other
than the author accepts the QEP with an approving review. Lazy consensus
with no clock, and nobody accepts their own QEP. The same rule governs
substantive amendments; editorial changes need no second reader. *Roles*
now says who a Core Maintainer is (admin rights on this repository). The
evidence for dropping the deadline is on #26; the QEP keeps the
principle.

**3. The README index is generated** post-merge from each QEP's
frontmatter, ordered by number, so a PR carries no row of its own.
*Numbering* states the reservation rule that makes index gaps normal.
The mechanism landed in #30; *Automation* names it, together with the
ordered-list check.

## Also in this PR

`qeps/template.md` and `AGENTS.md` follow the new version rule, so
neither describes the implicit-v0 semantics after this merges. The
README's process steps and index note follow QEP-1.

## What follows acceptance

Tracked on #22: the stamp action writes `version: 0` where missing,
already-merged QEPs past Draft are backfilled, the pull-request check's
new-QEP rule inverts, and the README index and theme pill show `v0`.

Per convention `version-hash` is removed for CI to re-stamp at merge.
Under change 2 this PR wants an approving review from a Core Maintainer
other than its author before it merges.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants