Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions .claude/board/EPIPHANIES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,56 @@
## 2026-09-03 — E-A-CENSUS-IS-A-FUNCTION-OF-ITS-REGEX-SO-GATE-THE-PROPERTY-1 — three numbers, one tree

**Status:** FINDING (all three counts measured on the same tree, same hour).
**Confidence:** High — the divergence is reproducible by swapping one pattern.

**The measurement.** A sibling session's census reported **53 of 208** plans
carrying no D-id and proposed a CI gate so the number could not regrow. Before
building it I re-measured, and got a different answer — twice:

| pattern | untracked / 208 |
|---|---|
| the sibling session's sweep | **53** |
| `supersession_index.py`'s own `DID` | **75** |
| a stricter form requiring a trailing number | **102** |

Same tree, same hour, same population — the `3DGS` family counts 19 under every
pattern, so nobody was measuring a different thing. **The census is a function
of the regex**, and none of the three numbers is wrong; they answer three
slightly different questions and only one of them (the generator's) is the one
the supersession index actually uses.

**The design consequence, which is the point.** A gate asserting *"no more than
N untracked plans"* would have been wrong on the day it landed and would have
frozen whichever regex its author happened to hold. So the gate asserts what
regex choice cannot move: **a plan ADDED in this PR cites at least one D-id.**
That stops the backlog regrowing — the stated goal — without requiring the
backlog to be agreed on, counted, or backfilled, which is a cross-session scope
call nobody has made.

Added-only is deliberate too: gating MODIFIED plans would block whoever next
edits a pre-existing untracked plan, punishing them for a debt they did not
create. A gate that fires on innocent work gets routed around, and a
routed-around gate is worse than none.

**A second finding, from building it.** The obvious way to share the pattern —
`from supersession_index import DID` — is wrong here: that module has **no
`if __name__ == "__main__"` guard**, so importing it runs the entire generator
and prints the index to stdout. Measured; the first version of the checker did
exactly that. Adding a guard would refactor a CI-gated tool for one caller's
convenience, so the pattern is lifted from its source text instead, with a hard
error if that definition is ever renamed or reshaped. A silent fallback to a
local copy would be the drift
`E-A-CITATION-IS-NOT-A-DEPENDENCY-AND-A-FORCED-COPY-NEEDS-A-GATE-1` was written
about — and note the contrast: there the copy was FORCED by a zero-dep boundary
and the remedy was an equivalence test; here nothing forces it, so the remedy is
to not copy at all.

**Verified two-sided on real history, not fixtures.** The three plans actually
added in the last 30 commits all carry D-ids, so the gate passes on recent
legitimate work; the `3DGS` family — the largest untracked group — fails it.
A gate that cannot fire and a gate that fires on everything are the same
non-signal, so both halves were run.

## 2026-09-03 — E-THE-FREE-MITIGATION-WAS-FREE-FOR-TWO-HOURS-1 — the entry's own thesis, applied to the entry

**Status:** FINDING (observed on #1160; supersedes the mitigation half of
Expand Down
9 changes: 9 additions & 0 deletions .claude/board/LATEST_STATE.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
## 2026-09-03 — branch: new plans must carry D-ids (the gate, not the backfill) — INVENTORY DELTA

- ADDED `.claude/tools/plan_dids.py` — fails when a plan file carries no D-id. Reads the pattern out of `supersession_index.py`'s SOURCE rather than importing it: that module has no `__main__` guard, so `import` runs the whole generator and prints the index (measured — the first version did this). Hard-errors if the definition is renamed, rather than falling back to a local copy.
- ADDED `.github/workflows/plan-dids.yml` — runs it on plans ADDED by a PR, modelled on `supersession-index.yml` (same trigger shape, same actionable error text). Added-only by design: gating MODIFIED plans would block whoever next edits a pre-existing untracked plan for a debt they did not create.
- MEASURED, and it is why the gate asserts a PROPERTY and not a COUNT: the same tree gives **53** untracked plans (a sibling session's sweep), **75** (the generator's own `DID` pattern) and **102** (a stricter pattern) out of 208. The `3DGS` family is 19 under all three, so the population is identical — the number is a function of the regex. A "no more than N untracked" gate would have been wrong on arrival.
- VERIFIED two-sided on real history: the 3 plans actually added in the last 30 commits all pass; the `3DGS` family fails. The workflow's own `git diff --diff-filter=A` selector was run against a real range and returns exactly those 3.
- NOT DONE, deliberately: the backfill of the existing untracked plans. The sibling session declined it as a cross-session scope call and said the fix is the gate; this is that gate. Their `probe-r2il-live-regfile-v1` remains theirs to id.
- Epiphany: `E-A-CENSUS-IS-A-FUNCTION-OF-ITS-REGEX-SO-GATE-THE-PROPERTY-1`.

## 2026-09-03 — branch (#1160): both external reviewers are now capped — INVENTORY DELTA

- OBSERVED on #1160: Codex answered a review request with "You have reached your Codex usage limits for code reviews"; CodeRabbit is simultaneously at its org spending cap (85 attempts/7d → 1/hour). **No external reviewer can currently see any PR in this repo.**
Expand Down
103 changes: 103 additions & 0 deletions .claude/tools/plan_dids.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
#!/usr/bin/env python3
"""Every NEWLY ADDED plan must carry at least one D-id.

Why a property and not a count
------------------------------
A census of untracked plans is a function of the regex used to take it. The
same tree, on 2026-09-03, measured **53** (a sibling session's sweep), **75**
(the generator's own pattern) and **102** (a stricter pattern requiring a
trailing number) — identical population, identical `3DGS` family of 19, three
different totals. A gate asserting "no more than N untracked plans" would have
been wrong on the day it landed and would encode whichever regex its author
happened to hold.

So this gate asserts something regex-choice cannot move: *a plan added in this
PR cites at least one D-id.* That stops the backlog regrowing, which is the
stated goal, without requiring the backlog to be agreed on or backfilled — a
cross-session scope call nobody has made.

Why it does not fire on MODIFIED plans
--------------------------------------
Gating modifications would make every edit to a pre-existing untracked plan a
blocked PR, punishing whoever next touches an old file for a debt they did not
create. Added-only is the minimal form that prevents regrowth.

Why the pattern is READ from the generator and not copied
--------------------------------------------------------
`supersession_index.py` owns the D-id pattern; the index's coverage column is
computed with it. A second copy here would agree with it exactly until one was
edited, which is when nobody is comparing them — the drift failure this
workspace ruled on in `E-A-CITATION-IS-NOT-A-DEPENDENCY-AND-A-FORCED-COPY-NEEDS-A-GATE-1`.

`import` was the obvious way to share it and is WRONG here: that module has no
`if __name__ == "__main__"` guard, so importing it runs the whole generator and
prints the index to stdout. (Measured — the first version of this file did
exactly that.) Adding a guard would be a refactor of a CI-gated tool for one
caller's convenience, so instead the pattern is lifted from its source text.
There is exactly one definition and it is still the single source; if that line
is ever renamed or reshaped this raises, which is the correct failure — a silent
fallback to a local copy is the very drift being avoided.
"""

import pathlib
import re
import sys

_GEN = pathlib.Path(__file__).resolve().parent / "supersession_index.py"
_DEF = re.compile(r"^DID\s*=\s*re\.compile\(r'(?P<pat>.*)'\)\s*$", re.M)


def _did_pattern() -> "re.Pattern[str]":
"""The generator's own D-id pattern, read from its source."""
m = _DEF.search(_GEN.read_text(errors="ignore"))
if not m:
raise SystemExit(
f"plan-dids: could not find the `DID = re.compile(r'...')` definition in "
f"{_GEN}. The pattern moved or was renamed. Fix this extractor rather "
f"than copying the pattern here — a second copy is the drift this gate exists to avoid."
)
return re.compile(m.group("pat"))


DID = _did_pattern()


def untracked(paths: list[str]) -> list[str]:
"""Return the subset of `paths` carrying no D-id."""
out = []
for p in paths:
f = pathlib.Path(p)
if not f.is_file():
continue # deleted or renamed away in the same PR
if not DID.search(f.read_text(errors="ignore")):
out.append(p)
return out


def main(argv: list[str]) -> int:
paths = [a for a in argv if a.endswith(".md")]
if not paths:
print("plan-dids: no added plan files in this diff; nothing to check")
return 0

missing = untracked(paths)
for p in paths:
print(f" {'MISSING D-id' if p in missing else 'ok '} {p}")

if not missing:
print(f"plan-dids: {len(paths)} added plan(s), all carry a D-id")
return 0

print()
print("::error::A plan added in this PR carries no D-id.")
print("A plan without D-ids is invisible to every discovery path: STATUS_BOARD")
print("has nothing to hold, and the supersession index's coverage column has")
print("nothing to count. Mint ids for its sections and add the rows, the way")
print("#1155 did retroactively for a sibling plan.")
print()
print(f"Pattern (imported from supersession_index.py): {DID.pattern}")
return 1


if __name__ == "__main__":
raise SystemExit(main(sys.argv[1:]))
43 changes: 43 additions & 0 deletions .github/workflows/plan-dids.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: New plans carry D-ids
on:
pull_request:
paths:
- .claude/plans/**
- .claude/tools/plan_dids.py
- .claude/tools/supersession_index.py
- .github/workflows/plan-dids.yml

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
added-plans-have-dids:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
# The check compares against the PR's merge base, so it needs history.
fetch-depth: 0

- name: Every plan ADDED by this PR must cite a D-id
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
# ADDED only, deliberately. Gating MODIFIED plans would block whoever
# next edits one of the pre-existing untracked plans, punishing them
# for a debt they did not create. Added-only is the minimal form that
# stops the backlog regrowing, which is the whole goal; the existing
# backlog is a separate, cross-session scope call.
mapfile -t added < <(
git diff --diff-filter=A --name-only "$BASE_SHA" "$HEAD_SHA" -- '.claude/plans/*.md'
)
if [ ${#added[@]} -eq 0 ]; then
echo "no plans added by this PR; nothing to check"
exit 0
fi
python3 .claude/tools/plan_dids.py "${added[@]}"
Loading