Skip to content

docs: ADR draft — BACKLOG-per-row-file restructure (fresh branch, supersedes #85)#474

Merged
AceHack merged 7 commits intomainfrom
docs/backlog-per-row-file-adr-fresh
Apr 25, 2026
Merged

docs: ADR draft — BACKLOG-per-row-file restructure (fresh branch, supersedes #85)#474
AceHack merged 7 commits intomainfrom
docs/backlog-per-row-file-adr-fresh

Conversation

@AceHack
Copy link
Copy Markdown
Member

@AceHack AceHack commented Apr 25, 2026

Summary

ADR for restructuring docs/BACKLOG.md from 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-0001 example 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:

  • Aligns with the actual Otto-181 schema (id/status/title parser-validated; rest convention-enforced)
  • Uses status: open | closed (the actual enum) not open|shipped|declined
  • Marks new-row.sh and lint-index.sh as OWED (Phase 1b/1c), not "already in tree"
  • Updates line count from "~6k" to "~12,800" (12,781 measured)
  • Acknowledges docs/BACKLOG.md is currently the monolithic authoritative — DO NOT EDIT applies post-Phase-2 only
  • Status of this ADR shifted from Proposed (Round 44 batch 5 of 6: BACKLOG-per-row-file restructure ADR (draft) #85) to Accepted (here) per Aaron's 2026-04-25 delegation: "i'll leaf it up to you if you want per row for backlog... it's your ownership so you make the finial decision."

#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

  • Aligned with actual Otto-181 schema and existing tooling
  • Open questions converted to Otto-283 "decided + revisit-if" entries (don't bottleneck Aaron)
  • Fresh CI on current matrix
  • ADR is decision-only — separate PR will land Phase 2 bulk migration + Phase 1b/1c tooling

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.
Copilot AI review requested due to automatic review settings April 25, 2026 12:37
@AceHack AceHack enabled auto-merge (squash) April 25, 2026 12:37
@chatgpt-codex-connector
Copy link
Copy Markdown

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.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

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.

Comment thread docs/DECISIONS/2026-04-22-backlog-per-row-file-restructure.md
Comment thread docs/DECISIONS/2026-04-22-backlog-per-row-file-restructure.md Outdated
Comment thread docs/DECISIONS/2026-04-22-backlog-per-row-file-restructure.md Outdated
Comment thread docs/DECISIONS/2026-04-22-backlog-per-row-file-restructure.md Outdated
Comment thread docs/DECISIONS/2026-04-22-backlog-per-row-file-restructure.md
Comment thread docs/DECISIONS/2026-04-22-backlog-per-row-file-restructure.md Outdated
AceHack added 2 commits April 25, 2026 08:42
…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.
Copilot AI review requested due to automatic review settings April 25, 2026 12:42
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.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 7 comments.

Comment thread docs/DECISIONS/2026-04-22-backlog-per-row-file-restructure.md Outdated
Comment thread docs/DECISIONS/2026-04-22-backlog-per-row-file-restructure.md Outdated
Comment thread docs/DECISIONS/2026-04-22-backlog-per-row-file-restructure.md Outdated
Comment thread docs/DECISIONS/2026-04-22-backlog-per-row-file-restructure.md
Comment thread docs/DECISIONS/2026-04-22-backlog-per-row-file-restructure.md Outdated
Comment thread docs/DECISIONS/2026-04-22-backlog-per-row-file-restructure.md Outdated
Comment thread docs/DECISIONS/2026-04-22-backlog-per-row-file-restructure.md
AceHack added 2 commits April 25, 2026 08:49
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
Copilot AI review requested due to automatic review settings April 25, 2026 13:39
@AceHack AceHack merged commit 861c71a into main Apr 25, 2026
15 checks passed
@AceHack AceHack deleted the docs/backlog-per-row-file-adr-fresh branch April 25, 2026 13:41
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 6 comments.

Comment thread docs/DECISIONS/2026-04-22-backlog-per-row-file-restructure.md
Comment thread docs/DECISIONS/2026-04-22-backlog-per-row-file-restructure.md
Comment thread docs/DECISIONS/2026-04-22-backlog-per-row-file-restructure.md
Comment thread docs/DECISIONS/2026-04-22-backlog-per-row-file-restructure.md
Comment thread docs/DECISIONS/2026-04-22-backlog-per-row-file-restructure.md
Comment thread docs/DECISIONS/2026-04-22-backlog-per-row-file-restructure.md
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.
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.
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.
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.

2 participants