Development record
hand-maintained index drift: intents/README.md corpus listings have drifted from the filesystem; commands/README.md lists three of the seven plugin verb files; internal/README.md still describes core as two capabilities with adapter/scanner as a planned seam; the repo README Layout omits skills/ from the plugin surface. Detector (per adr-5 derive-dont-store): hand enumerations of sibling files are generated or deleted — a lint flags a README that enumerates directory contents by hand, or the enumeration is emitted by tooling with a drift test. Acceptance corpus: the four stale indexes above.
hand-maintained index drift: intents/README.md corpus listings have drifted from the filesystem; commands/README.md lists three of the seven plugin verb files; internal/README.md still describes core as two capabilities with adapter/scanner as a planned seam; the repo README Layout omits skills/ from the plugin surface. Detector (per adr-5 derive-dont-store): hand enumerations of sibling files are generated or deleted — a lint flags a README that enumerates directory contents by hand, or the enumeration is emitted by tooling with a drift test. Acceptance corpus: the four stale indexes above.
Frontmatter
| id | iss-38 |
|---|---|
| type | issue |
| lifecycle | resolved |
| severity | major |
.abcd/work/issues/resolved/iss-38-hand-maintained-index-drift.md
open on GitHub ↗ · commit history ↗
mentions14
- adr-5Brief is the current state; no version label, no archive directory
- dcn-26080200000000032026-08-02 — iss-38 (v0.5.0 item D): hand-maintained indexes are either DELETED or GATED, never corrected and left hand-kept, because a corrected list drifts again on the next commit and nobody notices the second time either (adr-5, derive don't store). The ledger named four stale indexes; three were real and one does not hold. (1) `intents/README.md` transcribed four lifecycle directories as ASCII trees and had fallen sixteen drafts, one promotion (itd-73, listed as a draft while living in `planned/`), two disciplines and two superseded intents behind, with a `shipped/` section still calling the directory empty over thirteen shipped intents — deleted, because the enumeration carried nothing the directory does not carry better, and what a filesystem cannot state (the kind taxonomy, the lifecycle contract, the bundle history) stays. (2) `commands/README.md` (fourteen verbs named, eighteen files present) and (3) `internal/README.md` (core described as "identity/version and the read-only status snapshot" while its own later bullets described twenty packages, and `adapter/scanner` filed under planned seams although it ships with its own pattern set, redactor and tests) keep short, high-value enumerations — so those ship WITH the detector rather than as a promise to remember. The new `index_drift` rule holds a marked region (`<!-- index: <id> -->`) to the directory it enumerates in one of two modes: `exact`, where listing and directory must agree both ways, and `absent`, where every listed path must still be missing — the planned-seams shape, whose drift is a seam that shipped. Scoping is an explicit marker plus a configured entry pattern rather than a markdown-list parser: one generic rule, no bespoke parser per README, and unrelated prose beside a list cannot fire it. It fails closed three ways (region gone while the config entry remains; region parsing to zero entries; malformed spec), so half-deleting an index is a finding rather than a silent disarm. (4) The fourth instance — "the repo README Layout omits `skills/` from the plugin surface" — was investigated and does NOT hold: there is no `skills/` directory, abcd ships zero skills deliberately (the three workflows once shaped as skills became commands, closing iss-61; `brief/05-internals/08-skills.md` states it), so the Layout section is correct by omission and was left untouched. Inventing a `skills/` reference to satisfy a finding would have written a falsehood into the record to close a ticket — the same disposition iss-37 reached on its one non-holding instance. CHANGELOG: an entry, unlike iss-34's test-only pass, and impact `additive` rather than `internal` — the rule lives in the shipped lint engine that `abcd docs lint` runs, so any abcd-managed repo gains it by declaring its own pairs in `.abcd/docs-lint.json`.
- dcn-26080200000000042026-08-02 — iss-39: record-lint gains `record_schema`, the first rule that reasons ACROSS the record's stores (ADRs, intents, specs, the issue ledger) rather than inside one, and the `superseded/` exemption narrows for the intent tree: both rules that read it (`intent_lifecycle`, `intent_impact_valid`) now run there too, while the spec-store rules (`spec_lifecycle`, `spec_id_unique`) still honour it as before and the cross-store `record_schema` never consulted it at all. Four invariants, one scan: a cross-reference field names a record the corpus has; a supersession is declared from BOTH sides; a filename and the id inside it agree; and every lifecycle directory is enumerated, so an undeclared bucket is a finding rather than a state no rule reads. Scope is deliberate on two axes. (1) Cross-references are checked in the machine-readable frontmatter fields (`supersedes`, `related_adrs`, `related_intents`, `builds_on`, `blocked_by`), not in bare prose: a frontmatter handle is a claim that the record exists and is a live input, whereas prose legitimately narrates ids that do not resolve ("itd-38's id was released, not reserved"; a test fixture's `itd-9999`; adr-29's account of what ADR-6 settled) — arming prose would have cost ten false positives against one true one, the same false-positive budget that scopes GL002. (2) A handle resolves either to a file or to a `supersedes:` declaration elsewhere, because the ADR lifecycle PRUNES a superseded record and keeps the trace in its successor — that is what distinguishes adr-4/adr-8/adr-14..18 (pruned, accounted for) from adr-6 (cited, never accounted for). The exemption narrowing is the root cause of the corpus defect it exposes: `intent_lifecycle` is a schema rule, but the `superseded/` bucket exempted its own records from the one rule that checks a supersession is recorded, so itd-47 and itd-49 sat there for months with a prose blockquote and no `superseded_by` field. Being historical excuses a record from how it is WRITTEN, never from being well-formed. `superseded_by` also widens from `^itd-\d+` to `^(itd|adr)-\d+`: an ADR that redecides the question an intent rested on retires that intent as surely as a successor intent does, and refusing to spell that forced those two records to say nothing at all. Corpus repairs in the same change: adr-6 is dropped from the two `related_adrs` lists and the two brief pages that cited it re-point at adr-29 (which records the two-stage redaction model and states in its own body that adr-6's decision stands — adr-29 does not supersede it, so claiming it does would have written a falsehood to clear a finding); adr-12 and adr-32 gain the bidirectional pair the ledger-location redecision always implied, adr-12's status moving to `superseded` and the index row with it; itd-47/itd-49 gain `superseded_by: adr-22`/`adr-26` with `kind_at_supersession`, and those ADRs name them back; and the pre-existing one-way links itd-32→itd-31 and itd-31→itd-48 gain their reverse halves. CHANGELOG impact `additive`, matching iss-38's disposition on the same axis: the rule ships in the lint engine `abcd docs lint` runs, and any abcd-managed repo gains it by declaring its own `record_stores`.
- dcn-26080300000000022026-08-03 — iss-42, CORRECTION to the entry immediately above and three fixes from a fresh adversarial correctness review. (1) That entry claimed two banned_tokens entries made both stalenesses "non-silent on reintroduction". The `phantom-research-spikes` half did not: its pattern was the literal string `research/spikes`, which never occurs in the prose it was meant to catch — the record map spells the row as two separate backtick spans (`notes/` ... `spikes/`), so restoring the exact original stale line left record-lint at zero blockers. The claim was written from reading the pattern instead of reproducing the defect, which is the failure the detector-first rule exists to prevent, and the lesson is that a ban's acceptance test is the original line, not the pattern's plausibility. The fix is not a wider regex: ADR-30 and the dated go-rebuild plan carry the same prose shape legitimately, and no pattern separates the living index from the two historical records without an escape they must not be edited to carry. So the row is GATED instead, per iss-38's "delete or gate, never correct and leave hand-kept" — an `index_drift` region over the row holds it to `research/`'s actual subdirectories, which catches every spelling of a phantom child AND a real child the row omits, and fails closed if the region is deleted. (2) `abcd ideate record` wrote its dated verdict record to `research/` root, making the shipped binary the standing producer of the very convention violation this item cleared out of that directory by hand; `ResearchRelDir` now points at `research/notes/`. spc-18's rationale ("killed ideas are research outcomes, and the research directory is where a future session looks") argues for the research area, not its root, and a verdict record is by spc-18's own words a dated research note — so `notes/` is where the record's convention already put it. The CLI surface test asserted the path through the constant, so it would have followed the constant anywhere; it now asserts the literal directory. (3) The rewritten trust caveat over-claimed `surface_coverage`: the rule reads `commands_dir` and `skills_dir` only, while the crosscheck manifest's Direction B enumerates five surface kinds, so the agent, hook, and CLI-verb surfaces are periodic-pass-only. A caveat whose entire job is saying how much is machine-enforced must not overstate it — the same phantom-enforcement class iss-40 flagged — and the wording now names the two surfaces the structural check actually covers.
- dcn-26082100000000012026-08-21 — Bug-hunt round 4, branch bughunt-b/round-4. Baseline (make preflight
- iss-18106-lint.md section 1 (the lint-code namespace) is a catalogue of a predecessor project's Python-era implementation — the `IL001`–`RC007` code families and the class names they cite (`IntentLinter`, `BundleLinter`, `PromptLinter`, `SpecLinter`) describe code this repo does not carry. Re-filed here from iss-37 (2026-08-03 maintainer disposition), which is re-scoped to its three doc-claim instances.
- iss-296The hand-maintained ADR index table (.abcd/development/decisions/adrs/README.md) ends at adr-42 and omits committed records adr-43 and adr-44; the record-lint index_drift rule registers four indexes but not the ADR index, so this largest hand index is ungated. Same file: .abcd/work/rulesets/README.md says 'seven required status checks' where main-protection.json now lists eight (external-review added)
- iss-299brief/README.md's 05-internals directory-tree comment enumerates ten chapters including a nonexistent 'in-session dispatch' (that is planned intent itd-2, not a chapter); the directory holds nine files and its own index has nine rows
- iss-317development README conventions sentence says issues graduate rather than a ledger contradicting accepted adr-32 and the live work-tier ledger
- iss-319development README record-map table omits specs release and release-gate and carries a stale personas-json migration promise
- iss-332.abcd/development/README.md conventions line says issues graduate into intents/ or principles/ rather than a ledger, but the repo runs on a committed issue ledger at .abcd/work/issues/ (adr-32); the map of the durable record denies the store it governs, in the file iss-42 ruled must be current
- iss-351the three-disciplines roster itd-1 itd-5 itd-37 is hand-enumerated at six sites while intents/disciplines/ holds six active disciplines, dropping itd-79 itd-81 itd-84 from the mental model, scope, roadmap dashboard and phase-0 acceptance
- iss-2608211144555085The ADR index (.abcd/development/decisions/adrs/README.md) ends at the adr-44 row while adr-45 is accepted (2026-08-20) and cited as settled law by six live records (the invariants brief, itd-114, spc-33, .abcd/work/issues/README.md, two open issues, DECISIONS.md). 37 ADR files vs 36 index rows — exactly one missing. The index is the declared hand-maintained roll-call (adrs/README.md) and is not among index_drift's four registered indexes, so it is structurally ungated. Third recurrence of this class in the same table (after resolved iss-296 and iss-374), which iss-38's decision (hand-kept indexes must be deleted or gated) predicts.
- iss-2608220750029986adrs/README.md index omits the accepted adr-47 and adr-48 rows (site-build session) — fourth recurrence of the unindexed-ADR class after adr-45 (iss-38 wants these indexes gated); rows added in the same change that records this