docs(spec-loop): add spec-gap staleness verification spec - #926
Conversation
Generated-by: Claude (claude-opus-5)
potiuk
left a comment
There was a problem hiding this comment.
The spec itself is good — clear problem statement, crisp acceptance criteria, and the "evidence must be a call site, current file content, or a counted value; the presence of a name is not admissible" rule is exactly the right shape for something a model has to apply consistently. One correction wanted before it lands, and one hunk that has since become redundant.
source: claims an implementation that does not exist
The frontmatter reads:
Implemented by tools/spec-loop/PROMPT_plan.md, tools/spec-loop/PROMPT_update.md, and new checks in tools/spec-validator.
None of that exists yet. I checked the tree:
- no
validate_gap_predicatesorvalidate_cross_spec_countsanywhere undertools/ - neither
PROMPT_plan.mdnorPROMPT_update.mdmentions an evidence rule
status: proposed is correct and says so. The source: line contradicts it, and it does so in the exact tense the shipped specs use — the convention across the tree is:
| status | wording |
|---|---|
experimental (code exists) |
"Implemented in tools/gmail/, tools/jira/, …" |
proposed (code does not) |
maintainer-education.md: "Referenced but not yet delivered by docs/rfcs/RFC-AI-0004.md" |
So a reader — or the plan beat, which reads these fields — sees a shipped-code phrasing on unshipped work.
This is worth fixing rather than waving through, because it is precisely the failure this spec exists to prevent. The document opens with "Stops a ## Known gaps bullet outliving the gap it describes" and its own source: asserts an implementation that has not been written. Same drift, one field up.
source: >
Observed drift: nine "Known gaps" bullets across eight specs described
capabilities that had already shipped, and the plan beat queued work
from them. To be implemented by tools/spec-loop/PROMPT_plan.md,
tools/spec-loop/PROMPT_update.md, and new checks in
tools/spec-validator.
For what it is worth, the observed drift is real and I ran into it from the other side today: #1034 corrected one of exactly those bullets — "Release audit record schema is prose-only" — where the schema and its eval fixtures had shipped months earlier. So the motivating case is well grounded.
The uv.lock hunk is now redundant
The uv.lock change here is byte-identical to the one in #965 — same eight mypy specifier lines, same blob hashes. #965 has now merged, so this hunk is already on main; rebasing will make it disappear and leave this PR as the single spec file it wants to be.
Worth doing before merge simply so the PR says what it is: uv.lock +8/-8 on a spec-only change is the kind of thing that makes a future reader wonder what a lockfile has to do with spec staleness.
This review was drafted by an AI-assisted tool and
confirmed by an Apache Magpie maintainer. The findings
below are observations, not blockers; an Apache Magpie
maintainer — a real person — will take the next look at the
PR. If you think a finding is mis-applied, please reply on
the PR and a maintainer will weigh in.More on how Apache Magpie handles maintainer review:
CONTRIBUTING.md § Opening a pull request.
Two review fixes on the spec text. The uv.lock change in the parent commit is left alone: it corrects genuine stale-lock drift (the lock at the base commit had 26 mypy specifiers at >=2.1.0 against 18 in the pyprojects; regenerating brings both to 18/9). - Correct acceptance criterion 7. The discrepancy is three-way, not five-versus-six: triage-mode.md claims five skills, repo-health-family.md claims six, and seven carry family: repo-health in frontmatter. The seventh, audit-finding-fix, is in neither prose list — plausibly deliberate, since it fixes findings rather than producing them. The check must report all three numbers and name the disputed skill rather than presume frontmatter is authoritative. - Note that validate_cross_spec_counts compares against the live count independently, so the case where every spec agrees and all are stale is caught too. Agreement reads as confirmation, which makes it the more dangerous shape. - Record the landing order in Known gaps. The check is HARD and criterion 7 requires it to fire on a violation present in the tree today, so landing it before the counts are reconciled turns main red. Only the SOFT advisory is safe to land first. Generated-by: Claude Code (Opus 5)
potiuk
left a comment
There was a problem hiding this comment.
Approving with two fixups pushed to the branch.
Spec text. Acceptance criterion 7 called the repo-health discrepancy "five-versus-six". It is three-way: triage-mode.md claims five, repo-health-family.md claims six, and seven skills carry family: repo-health. The seventh, audit-finding-fix, appears in neither prose list — plausibly deliberate, since it fixes findings rather than producing them. So the check has to report all three numbers and name the disputed skill rather than presume frontmatter is authoritative; a HARD check that presumes an answer gets argued with instead of fixed. Also noted that comparing against the live count catches the case where every spec agrees and all are stale, which is the more dangerous shape since agreement reads as confirmation.
Landing order, recorded in Known gaps. validate_cross_spec_counts is HARD and criterion 7 requires it to fire on a violation present in the tree today, so landing it before the counts are reconciled turns main red. Only the SOFT advisory is safe to land first.
On uv.lock — I was wrong and have left your change alone. I first read it as churn in a spec-only PR and reverted it; the pre-commit hook regenerated it and the numbers explain why. At your base commit the lock had 26 mypy specifiers at >=2.1.0 against 18 in the pyprojects; regenerating brings both to 18/9. It was correcting genuine stale-lock drift, not adding noise.
The refuted-by-measurement note in Out of scope is the best part of this — recording that git-recency scoring does not separate the populations, with the numbers, is exactly what stops it being re-proposed.
Summary
Adds
tools/spec-loop/specs/spec-gap-staleness.md, aproposedspec forstopping a
## Known gapsbullet from outliving the gap it describes.Spec-only. Nothing is implemented by this PR.
Why now
Nine Known-gap bullets across eight specs currently describe capabilities
that have already shipped. Examples verified against
main:adapters.mdsays adapter authoring smoke validation is missing;validate_adapter_authoringis called at__init__.py:3710.privacy-llm-gate.mdsays branch-name confidentiality validation ismissing;
validate_branch_name_confidentialityis called twice.issue-management-family.mdandreviewer-routing.mdboth say thereviewer-routingrow is missing fromdocs/modes.md; it is atdocs/modes.md:85.release-management-lifecycle.mdsays the audit record schema isprose-only;
skills/release-audit-report/audit-record-schema.mdexists.This matters beyond tidiness: the plan beat reads Known gaps to select
work, so a stale bullet sends the loop at finished work. There is also a
straight contradiction between
triage-mode.md(repo-health is "afive-skill family") and
repo-health-family.md("six skills deep").What the spec proposes
may only become a work item if the beat cites a call site, current
file content, or a counted value. The presence of a name is
explicitly not admissible, since a function defined but never called
satisfies a name match while the gap stays open.
gap-closes-when:predicates on gap bullets, with athree-verb vocabulary (
symbol-called,path-exists,skill-count) that covers all nine observed cases. A satisfied ormalformed predicate is HARD; an unannotated bullet is SOFT, so
nothing breaks on the day this lands.
validate_cross_spec_counts, HARD. Counted claims checkedagainst live
family:frontmatter, with cross-spec disagreement aviolation regardless of which side is right.
Bullets whose gap is inherently narrative declare
gap-closes-when: none (narrative). That is the correct annotation forthe eleven "experimental, no adopter pilot has run" bullets, whose truth
depends on facts outside the repository and which should never be
machine-checked.
Reviewer questions
validate_cross_spec_countsis proposed HARD and the unannotated-bulletadvisory SOFT. SOFT is what allowed nine bullets to accumulate, so
there is a case for making it HARD, at the cost of a blocking backfill
across all 25 specs.
Generated-by: Claude (claude-opus-5)
Type of change
.claude/skills/<name>/) — eval fixtures updated belowtools/<system>/*.md)tools/*/withpyproject.toml)docs/,README.md,CONTRIBUTING.md)projects/_template/)prek, workflows, validators)Test plan
prek run --all-filespassesuv run pytest/ruff check/mypypasses(
PYTHONPATH=tools/skill-evals/src python3 -m skill_evals.runner tools/skill-evals/evals/<skill>/)(a regression test for the bug fixed / the behaviour added — see CONTRIBUTING.md)