Conversation
Decision record for the proposed restructure of `docs/BACKLOG.md` from a 6500-line monolith into per-row files under `docs/backlog/<topic>/<row-id>.md`. This ADR is the *decision* — implementation is a separate M/L follow-up that updates every BACKLOG-touching script (audits, generators, indices) to scan the per-row directory instead of parsing the monolith. ## Why a separate fresh branch (not a rebase of #85) Original PR #85 (`land-backlog-per-row-file-batch5`) was based on an older main and accumulated stale CI check_runs (`macos-14`, `ubuntu-22.04`, `Analyze (csharp)`) from a workflow that no longer exists. The current matrix is `macos-26`/`ubuntu-24.04`/etc. Rebase + workflow_dispatch couldn't override the rolled-up stale checks. Fresh branch off current main reproduces the same content (same ADR file, identical 306 lines) and gets clean CI on the current matrix. Per Aaron 2026-04-25 "you can just redo the work if you like and split, it's likely easy to start from master and just remake the code changes." PR #85 will be closed as superseded once this lands.
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Aaron 2026-04-25: 'we can have backlog by swimlane if you think that's better than per file.' Swim-lane (per-domain/per-owner: docs/backlog/security.md, research.md, factory-demo.md, ci.md, governance.md, etc.) is strictly better than per-row on three axes: - Discoverability: ~10-15 swim-lane files vs ~150+ per-row files; each swim-lane is grep-able as a single coherent topic. - Tooling cost: scripts scan a small fixed set of swim-lane files; no dynamic directory walk; index-file generation simpler. - Reordering: tier ordering stays as section headers within a swim-lane file (lighter ceremony than filename-encoded moves). Per-row is strictly better on collision avoidance (filename disambiguates), so retained as a future option if collision rate under swim-lane proves insufficient. ADR title + Status updated to capture both variants. Implementation PR will land swim-lane first; per-row stays as fallback. The 'Alternatives considered' section #5 now describes the swim-lane trade-off matrix in full.
5 tasks
There was a problem hiding this comment.
Pull request overview
Adds a Proposed ADR documenting a plan to restructure docs/BACKLOG.md from a single large file into per-row backlog files to reduce merge conflicts and shared-write churn.
Changes:
- Introduces a new ADR describing the per-row backlog layout, migration approach, and authoring rules.
- Defines proposed directory structure, row frontmatter schema, and index-regeneration approach.
…t is viable Aaron 2026-04-25 caught the overstatement: 'Filename IS the index entry. is that not a benefit we could get with per row file backlog?' Yes — the original ADR's per-row variant encoded priority in the directory path (docs/backlog/p1/...), which created a 'rename on every P3→P1 shift' pattern. But a corrected per-row variant with priority-in-frontmatter (filename = topic+owner-ref, priority in YAML) keeps the filename-IS-index benefit while avoiding the rename ceremony. Add as alternative #6 with a trade-off matrix comparing all three viable approaches (per-row priority-in-path, per-row priority-in- frontmatter, swim-lane). Real trade-off is tooling-investment-now (per-row) vs simpler-grep-now (swim-lane). Maintainer's current lean is still swim-lane for initial implementation, but per-row priority-in-frontmatter is now an active alternative not a rejected one.
Aaron 2026-04-25: '"rename on every priority change" that's fine it would have been an edit anyways'. He's right — file rename and in-place edit are the same cost (one git operation, similarity-tracked). The 'rename ceremony' objection in earlier ADR revisions was non-substantive. Drop it from the trade-off matrix. This further weakens the case for swim-lane over per-row. Net result: both variants are now strictly viable on equal terms. Per-row is collision-avoidance-better; swim-lane is zero-tooling-better. Maintainer no longer expresses a preference between the two. Implementation PR will pick one and migrate.
Aaron 2026-04-25 delegated the final call to Otto. Decision: per-row with priority-in-frontmatter. Reasoning: 1. Pattern consistency — matches every other write-many-edit-rarely surface (memory, ADRs, drain logs, skills); BACKLOG is the holdout with the same access pattern. 2. Filename-IS-index at per-row level (Aaron's framing). 3. Tooling burden bounded (~200 LOC: index script + frontmatter parser); factory has equivalent tooling for memories. 4. Mark-as-done = move-or-delete-file (cleaner than mutating a 1000-line swim-lane file). 5. Strictly better collision avoidance post-R45 EnterWorktree default-flip when parallel branches multiply. Swim-lane retained in the alternatives matrix as documentation of the second-best option; would be acceptable fallback if the per-row tooling investment proves larger than expected.
Markdownlint MD029 caught the alternatives list out of sequence (items 5, 6 came before original 3, 4 because earlier revisions appended new alternatives without reflowing). Reorder to 1-6 sequential. Also drop the duplicated 'status-quo' and 'automated-resolver' items at the bottom — they're now in correct positions in the main list. Same Class A pattern (line-leading numbering after content shifts) caught by markdownlint.
Resolves all 13 copilot review threads on PR #474. The ADR's prior draft was written without first reading the existing substrate — `tools/backlog/generate-index.sh` and `docs/backlog/P[0-3]/` already exist (Otto-181 prior work), and one example row was already migrated. The draft proposed a competing schema and competing scripts. Substantive changes: - **Reframe the ADR.** This is no longer "decide to do per-row" (Otto-181 already decided that). It's "commit to bulk- migrating the remaining ~350 rows into the existing Otto-181 substrate." - **Add an "Existing substrate" section** acknowledging Otto-181 design + `tools/backlog/generate-index.sh` + `docs/backlog/P[0-3]/` + the one example row already migrated. - **Align schema with reality.** The Otto-181 schema is `id: B-<NNNN>`, `priority`, `status`, `title`, `tier`, `effort`, `directive`, `created`, `last_updated`, `composes_with`, `tags`. Drop the `<slug>-<YYYY-MM-DD>` filename + `owner` / `updated` / `scope` field proposals that did not match the parser. - **Fix script references.** The actual script is `tools/backlog/generate-index.sh` (not `regenerate-index.sh`). `lint-index.sh` is owed (Phase 1c) — explicitly noted as a wrapper around `generate-index.sh --check`. - **Update line count.** From "~6k" / "5,957" to "~12,800" (12,781 measured at time of writing). - **Fix markdown formatting.** Renumber the Alternatives section sequentially 1-6. Fix the trade-off matrix rendering (was `||`, now `|`). Reduce the matrix to two columns since "per-row priority-in-path" was rejected. - **"Maintainer's current lean" ambiguity** resolved — the Decision section now states the call concretely (per-row with Otto-181 schema), no longer leaves it to the implementation PR. Threads addressed: - PRRT_kwDOSF9kNM59lW9I — schema mismatch - PRRT_kwDOSF9kNM59lW9O — id semantics inconsistency - PRRT_kwDOSF9kNM59lW9S — index "auto-generated or manual" - PRRT_kwDOSF9kNM59lW9U — `regenerate-index.sh` typo - PRRT_kwDOSF9kNM59lW9V — `lint-index.sh` non-existent - PRRT_kwDOSF9kNM59lW9X — naming/ID scheme misalignment - PRRT_kwDOSF9kNM59lYZK — alternatives numbering 1,2,5,6,3,4 - PRRT_kwDOSF9kNM59lYZN — table extra `|` rendering - PRRT_kwDOSF9kNM59lYZS — `lint-index.sh` cite - PRRT_kwDOSF9kNM59lYZV — line count drift - PRRT_kwDOSF9kNM59lYZZ — "maintainer's lean" ambiguity - PRRT_kwDOSF9kNM59lYZd — script path mismatch - PRRT_kwDOSF9kNM59lYZi — line count factual error
AceHack
added a commit
that referenced
this pull request
Apr 25, 2026
Resolves 6 new copilot threads on PR #474. The prior commit overstated what's currently in tree — claimed P0/P1/P3 directories had files (only P2 has B-0001), claimed new-row.sh + lint-index.sh existed (they don't, owed Phase 1b/1c), claimed the generator parses the full schema (only parses id/status/title), and claimed status values are open|shipped|declined (actual is open|closed). Changes: - **Existing-substrate section** retitled to "Phase 1a prior work" and accurately describes: Phase 1a landed schema+generator; only B-0001 (a placeholder row in P2) exercises the generator on non-empty input; docs/BACKLOG.md is currently still the monolithic authoritative source. - **Generator parses 3 fields** (id/status/title) noted explicitly. The full schema is documented but enforced by convention, not parser. Future Phase 1b/1c may extend. - **status enum** corrected to `open | closed`. "Shipped" / "declined" / "superseded" become row-body prose, not status enum values, since the generator's checkbox is binary. - **Directory-shape comments** updated: P0/P1/P3 are placeholder dirs (currently empty); B-<NNNN>-<slug> files in them are post-migration target. - **DO NOT EDIT label** on docs/BACKLOG.md clarified to apply *post-Phase-2*, not currently. Until Phase 2 ships, docs/BACKLOG.md is hand-edited as today. - **Authoring rules — Close a row** rewrote "Ship a row" section to use `closed` status with reason captured in row body prose (the actual workflow Phase 1a supports). Threads addressed: - PRRT_kwDOSF9kNM59lkNV — DO NOT EDIT vs current state - PRRT_kwDOSF9kNM59lkNX — "already in tree" overstated - PRRT_kwDOSF9kNM59lkNa — new-row.sh + lint-index.sh - PRRT_kwDOSF9kNM59lkNb — schema parser claim - PRRT_kwDOSF9kNM59lkNd — status enum values The thread on PR-description ↔ ADR drift (PRRT_kwDOSF9kNM59lkNS) is a meta-comment about the PR body, not the ADR file; addressed in PR description update + reply.
3 tasks
AceHack
added a commit
that referenced
this pull request
Apr 25, 2026
Aaron 2026-04-25 directive after I gave the honest "soft analogy is substantive but strict Noether-style derivation is open research" answer to his "is there some new conservation law we have exposed?" question: > "backlog ongoing research here to formalize this > conservation law analogously." Three artifacts landed: 1. **`docs/research/otto-287-noether-formalization-2026-04-25.md`** — the research direction. Four-step formalization plan: define cognitive action S = ∫(W - F)dt, identify continuous symmetries, derive Noether currents, do symmetry-breaking analysis. Identifies the three candidate conservation-adjacent structures (constrained- optimization-produces-structure, meta-conservation of rule-form, cognitive-effort redirection). 2. **`docs/backlog/P3/B-0002-otto-287-noether-formalization.md`** — per-row backlog entry following the Otto-181 schema (B-NNNN id, priority/status/title/tier/effort frontmatter, body with acceptance signals + composing rules). First real row after B-0001 placeholder, exercising the substrate post PR #474 ADR. 3. **`docs/BACKLOG.md` P3 section row** — legacy-format pointer at the per-row file + research direction. Until Phase 2 bulk migration runs, the legacy file remains the discoverability surface. Why P3 (not higher): operational substrate (Otto-281..287) works as a practical discipline regardless of whether the formalization succeeds. The formalization is upside, not load-bearing. First milestone is Step 1 (quantification of W and F); anything beyond is incremental research.
3 tasks
AceHack
added a commit
that referenced
this pull request
Apr 25, 2026
The BACKLOG-per-row-file ADR (PR #474, just merged) committed to a Phase 1c lint that enforces docs/BACKLOG.md ↔ docs/backlog/ per-row parity. The ADR named tools/backlog/lint-index.sh as the proposed shape, but there is no pre-commit-hook framework in this repo — the CI surface is the equivalent enforcement point, so this lands as a workflow rather than a separate wrapper script. Mirrors the structure of memory-index-integrity.yml: SHA-pinned checkout, explicit minimum permissions, concurrency group, no user-authored input touched. **Pre-Phase-2 mode** (current state): docs/BACKLOG.md is still the monolithic authoritative file (~12,800 lines) and is hand-edited. The workflow detects this via the absence of the "AUTO-GENERATED" header line emitted by generate-index.sh, and in that mode only verifies the per-row files themselves are well-formed (parseable by the generator). This guards the existing per-row files (B-0001 example, B-0002 Otto-287 Noether) without false-positive-flagging the legacy monolithic file. **Phase 2+ mode** (after bulk migration): the workflow runs generate-index.sh --check, which exits non-zero on any drift between per-row files and the generated index. Becomes load-bearing at that point. Verified locally: - head -5 docs/BACKLOG.md does NOT match AUTO-GENERATED → pre-Phase-2 path taken - generate-index.sh --stdout produces parseable output
AceHack
added a commit
that referenced
this pull request
Apr 25, 2026
* ci(backlog): index-integrity workflow — Phase 1c per BACKLOG ADR The BACKLOG-per-row-file ADR (PR #474, just merged) committed to a Phase 1c lint that enforces docs/BACKLOG.md ↔ docs/backlog/ per-row parity. The ADR named tools/backlog/lint-index.sh as the proposed shape, but there is no pre-commit-hook framework in this repo — the CI surface is the equivalent enforcement point, so this lands as a workflow rather than a separate wrapper script. Mirrors the structure of memory-index-integrity.yml: SHA-pinned checkout, explicit minimum permissions, concurrency group, no user-authored input touched. **Pre-Phase-2 mode** (current state): docs/BACKLOG.md is still the monolithic authoritative file (~12,800 lines) and is hand-edited. The workflow detects this via the absence of the "AUTO-GENERATED" header line emitted by generate-index.sh, and in that mode only verifies the per-row files themselves are well-formed (parseable by the generator). This guards the existing per-row files (B-0001 example, B-0002 Otto-287 Noether) without false-positive-flagging the legacy monolithic file. **Phase 2+ mode** (after bulk migration): the workflow runs generate-index.sh --check, which exits non-zero on any drift between per-row files and the generated index. Becomes load-bearing at that point. Verified locally: - head -5 docs/BACKLOG.md does NOT match AUTO-GENERATED → pre-Phase-2 path taken - generate-index.sh --stdout produces parseable output * ci(backlog): fail-fast on missing files + per-row field validation Three PR #492 review threads addressed: 1. **chatgpt-codex P1 + copilot P1** — sentinel hides missing docs/BACKLOG.md as pre-Phase-2 mode and exits 0, which would let an accidental delete of the authoritative backlog ship green. Added explicit existence preconditions that fail fast on missing docs/BACKLOG.md OR missing/ non-executable tools/backlog/generate-index.sh. 2. **copilot P2** — pre-Phase-2 parseability proxy (`generate-index.sh --stdout > /dev/null`) is too weak. The generator is forgiving: a row with bad frontmatter produces an empty index line, not an error. Replaced with explicit awk-extraction of id/status/title for every B-*.md file and an empty-value check; any row missing all three required fields fails the workflow with a clear per-file message. Belt-and-suspenders: still runs generator end-to-end for structural issues the field-only check might miss. Verified locally with bash: - 2 per-row files enumerated (B-0001 + B-0002) - All 3 required fields extracted cleanly from each - bad_count=0 - generator runs cleanly Net: workflow is now defensive-by-default. Missing files surface as errors immediately; malformed per-row files surface with file-by-file diagnostics. * ci(backlog): scope per-row field extraction to YAML frontmatter only Resolves chatgpt-codex P2 review on PR #492. The earlier extraction (`awk '/^id:/'`, etc.) matched anywhere in the file, so a malformed frontmatter could falsely pass if `id:` / `status:` / `title:` happened to appear later in the body (e.g., in a code block, an example, or a discussion of the schema itself). Now uses an explicit `extract_frontmatter_field` shell function that mirrors `tools/backlog/generate-index.sh`'s `extract_field` state machine: state=0 before the opening `---`, state=1 inside the frontmatter, exit on the closing `---`. Field match only fires when state==1, so body-text matches cannot mask missing frontmatter. Verified locally with bash: - Both real per-row files (B-0001, B-0002) parse cleanly. - A simulated row with `status: open` only in the body (NOT in frontmatter) correctly returns empty for status, triggering the bad_count failure path. Same gate intent (id+status+title required), now with the hole closed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
ADR for restructuring
docs/BACKLOG.mdfrom the current ~12,800-line monolith into per-row files. Decision record + bulk-migration commitment — Phase 2 (the actual content migration) is a separate L follow-up.This ADR builds on Otto-181 Phase 1a substrate (
tools/backlog/generate-index.sh,docs/backlog/P[0-3]/,B-0001example row) which already exists in tree. The ADR's contribution is committing to the bulk-migration of the remaining ~350 rows + naming the owed Phase 1b/1c work (new-row.sh/lint-index.sh).Relationship to PR #85
This is NOT the same content as #85. #85 was a draft with several factual overstatements about the existing tooling. This PR substantially rewrites the ADR after copilot review caught the overstatements:
status: open | closed(the actual enum) notopen|shipped|declinednew-row.shandlint-index.shas OWED (Phase 1b/1c), not "already in tree"docs/BACKLOG.mdis currently the monolithic authoritative — DO NOT EDIT applies post-Phase-2 only#85 will be closed as superseded once this lands.
Why fresh branch instead of rebasing #85
PR #85 had stale CI check_runs (
macos-14,ubuntu-22.04,Analyze (csharp)) from a workflow no longer in CI. Rebase + workflow_dispatch couldn't override the rollup. Per Aaron 2026-04-25: "you can just redo the work if you like and split, it's likely easy to start from master and just remake the code changes." Fresh branch gets clean CI on the current matrix.Test plan