You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
How to keep specs consistent and up-to-date with spec-kit? #620
With spec-kit, each feature defines its own spec. This works well at first, but over time and with the multiplication of features, some descriptions become outdated or are enriched by new features.
Example
Feature 001 – User Login defines that a user can log in with email + password.
A few months later, feature 009 – Two-Factor Authentication enhances the login by adding an extra step (OTP code).
In this case, the original spec of feature 001 becomes partially outdated or needs to be complemented by feature 009.
Discussion
Is there a recommended approach to consolidate specs over time?
Should the original feature be modified, or is it better to keep the historical record and add links to subsequent evolutions?
Are there patterns used by the community to manage the evolution and consistency of specs as new features are added?
Idea: Would it be useful to introduce a /close command (or equivalent) to mark a feature as deprecated and automatically update a centralized project documentation?
Reacted by Josh Perfetto, JaysonAlbert, Ernest, gwallan, Ruka Sakurai, Martin Chovanec, Liron Cohen, Dário, Mykola Grybyk, ruckfull and 2 more
I think the up-to-date centralized documentation could be considered an integral part of the spec constitution, not just a supporting tool.
This would ensure that as features evolve, the “official” documentation reflects the current state and remains consistent with the history of specs.
I have a similar concern. I think it's essential that specs are maintained/curated/refactored to maintain a single, logically organized source of truth that both humans and AI can refer to. An implementation path could be to do a git commit on the spec update(s), and then use that commit to kick-off planning of a migration plan to implement the spec change.
Doing that, your example might be extended as:
Spec 001 - Initial, Minimal Product is implemented, which amongst other things defines email + password login
A few months later, Spec 009 - Authentication is implemented, which more thoroughly considers authentication requirements and states that both email + password and OAuth login should be supported. The mention of email + password is removed from 001 in the same commit that adds 009. A migration plan + implementation is kicked off.
A few months later, Spec 009 - Authentication is amended, to incorporate the new two-factor auth feature. A migration plan + implementation is kicked off.
What if SpecKit could compact multiple implemented features into a single “milestone” spec — kind of like how LLMs summarize long conversations?
After several /specify → /plan → /tasks → implement cycles, we could run /speckit.compact to:
Synthesize all completed specs into a concise milestone summary
Capture shared patterns, architecture, and lessons
Detect drift between intent and implementation
Optionally trigger a light refactor phase to align the codebase with the consolidated spec
Basically: milestones not as planning containers, but as post-implementation compaction points — compressing implementation history into reusable context and keeping both specs and code coherent as the system evolves.
Reacted by Josh Perfetto, Gideon Catz, Mbertu, deoncg, Viet Vu, 𖤬𝔫𝔡𝔶, Lestan, dhc-bianyunpeng, Ernest, Liron Cohen and 7 more
This is indeed a key point!
It is important to have an up-to-date, consolidated specification describing the overall project.
So I'm joining the question :-)
I also ran into this very early in my testing of this tool. The generated output after the spec is implmented quickly becomes out of date due the the fine tuning changes applied to get the feature ready for actual use. In a perfect world there would be some background job that can detect when feature deviate from the original spec and suggests changes to bring it up to date. I envision this could also be used to create specs for existing features that are missing specs within your project.
I once considered not merging the specs directory into the main branch each time a branch is merged, so as to ensure that the development branch retains its own archival information from the development process.
This issue has been automatically marked as stale because it has not had any activity for 150 days. It will be closed in 30 days if no further activity occurs.
The 001-login / 009-2FA example is exactly the case I hit, and I ended up splitting it into two problems that turned out to need different tools.
"What did we already decide?" — context from earlier specs never reaching the current one. I published spec-kit-memory for that (community catalog id memory): it hooks before_specify / before_plan and recalls prior specs and decisions from a configurable memory backend before you write the new spec.
"Which earlier promise did this new spec just invalidate?" — the sharper half of your question, and the one that actually bit me. A merged spec in my project made a closed per-intent allowlist its central safety control. A later spec made that intent set page-derived and unbounded. The first spec was not wrong — an assumption underneath it moved, and nobody wrote the amendment. Both merged separately, each passing its own gate. /speckit-analyze could not see it, because it reads one feature directory: spec.md, plan.md, tasks.md, constitution. The hole survived three review rounds before a concrete behavioural question walked straight into it.
So I wrote spec-kit-coherence. It hooks after_analyze and diffs the feature against its merged siblings — where "merged" is read from git ls-tree <base-ref>, so a spec that only exists in your working tree is a peer under construction, not a promise already made.
Two halves on purpose:
A deterministic script enumerates siblings from declared links only — spec numbers cited in the prose, # Implements: headers in files the branch changed, contracts declaring themselves a delta over another spec. Never "specs that feel related", which is how this degenerates into a second architecture review. It also flags the two defects that need no judgment: a cited spec with no directory, and a delta contract with no base contract to be a delta of.
A judgment pass then classifies each sibling invariant HOLDS / SILENTLY BROKEN / AMENDED — where AMENDED requires the amendment to actually live in the sibling, not merely in the new spec's prose. An amendment written only in the changing document has not been made.
On your example it would surface 001's "log in with email + password" as a closed-world promise sitting against 009's added step, and say plainly that the artefact to amend is 001 — and that this is a governance change to 001's promises, not a code edit.
Answering @dan-s-github's June question directly: this is that extension, such as it is. Fair warning that it is new — source is public, but no release is cut yet and it is not in the community catalog, so it is a specify extension add --dev <path> install for now.
This issue has been automatically marked as stale because it has had no activity for 60 days. It will be closed in 30 days unless there is a substantive update. If the problem still applies, please provide updated context such as whether it is still reproducible, the affected version or environment, its current impact, and any new reproduction details. A comment that only states that the issue is "still relevant" does not give maintainers enough information to reconsider it.
We measured how big this problem is on a real repo.
We took frank-fs/frank: 17 features in specs/, one Spec Kit template. Then we folded all 17 into one backlog, oldest first. At each feature we asked: is this story new, does it revise an earlier one, or is it a duplicate?
70 user stories as written became 45. 10 were rewritten by later features, 14 were the same story written again, 1 was dropped. Five features added no new stories at all, they only changed old ones.
One story was touched by four features: 001 → 010 → 014 → 015, all about API compatibility. To know what it says today, you read four spec.md files in order.
And 20 times a later requirement looked like it replaces an earlier one, but no spec says so. One requirement, "all HTML through Hox h()", was put in doubt three times.
So per-feature specs work like a change log, not a spec of the system. The 001-login / 009-2FA case is the normal case, not an edge case.
I've hit the same wall, and I think the answer is not "consolidate the specs into one" — it's "detect the drift mechanically, and keep the history".
Reason: merging 001 into 009 destroys the only record of why 001 existed. What's actually missing is a way to know, without reading everything, which specs no longer describe current behaviour.
So I built a zero-dependency checker and ran it against a reproduction of the exact example in this issue (001 login + 009 2FA). Real output, trimmed in three documented ways to keep it readable: the per-finding evidence JSON and re-check command are omitted, and findings 3–6 have their repeated claim wording abbreviated with …. Everything else is verbatim. (The CLI strings are Chinese because that's the language I work in — I've glossed each line.)
规格漂移判定 · demo # spec drift report · demo
spec 2 份 | 测试文件 1 个 | 判定 8 条 # 2 specs | 1 test file | 8 findings
沿用的 spec 清单: # specs read
[001] 001-user-login status=Implemented FR=3 SC=2 story=1
[009] 009-two-factor status=Draft FR=4 SC=2 story=1
🔴 1. [D1] 009-two-factor
1 处需求仍标记 NEEDS CLARIFICATION,但 spec 未被阻挡
# 1 requirement still marked NEEDS CLARIFICATION, and the spec is not blocked
🔴 2. [D3] FR-001 001-user-login
FR-001 在任何测试文件里都找不到足够的特征词锚点(2 个非通用词中仅 0 个出现,需 ≥2)
# no test file has enough distinguishing terms: 0 of 2 appeared, needs >= 2
🔴 3. [D3] FR-003 001-user-login
FR-003 …(6 个非通用词中仅 0 个出现,需 ≥2) # 0 of 6 appeared
🔴 4. [D3] FR-001 009-two-factor
FR-001 …(4 个非通用词中仅 0 个出现,需 ≥2) # 0 of 4 appeared
🔴 5. [D3] FR-003 009-two-factor
FR-003 …(7 个非通用词中仅 0 个出现,需 ≥2) # 0 of 7 appeared
🔴 6. [D3] FR-004 009-two-factor
FR-004 …(3 个非通用词中仅 1 个出现,需 ≥2) # 1 of 3 appeared — the 2-term rule firing
🟡 7. [D2] 009-two-factor
Status 仍是「Draft」,但 4/4 条 FR 的关键词已在实现代码里出现
# status is still Draft, but the terms of all 4 FRs already appear in the implementation
🟡 8. [D4] FR-002 vs FR-002 001-user-login ← 009-two-factor
001-user-login 的 FR-002 与更晚的 009-two-factor 的 FR-002 关键词重叠 50%,
且 001-user-login 未引用 009 → 疑似已被取代但未标注
# FR-002 of 001 shares 50% of its terms with FR-002 of the later 009, and 001 never
# references 009 → suspected superseded, but not marked
汇总:high=6 mid=2 low=0 # summary: high=6 mid=2 low=0
说明:本工具只报「可复核的信号」,不做语义判断。D4 是候选而非结论,需人工确认。
# note: this tool only reports re-checkable signals; it makes no semantic judgement.
# D4 is a candidate, never a verdict — a human confirms it.
--json emits the same findings as structured data — rule id, spec, FR id, evidence, and the exact re-check command. The prose fields inside it stay in Chinese; only the structure is language-neutral. A --lang en mode is the obvious next step and I haven't built it.
That last one (D4) is the actual question you asked: "is 001's spec now outdated because of 009?" The tool doesn't answer it — it points at the pair and quotes both texts, and a human decides.
What I learned building it: all three obvious approaches fail
1. Don't grep the test files. Comments and docstrings poison the result. My own test file had a trailing comment — # nothing below covers: sign in with email and password — and that comment alone made FR-001 look "covered". Strip comments and docstrings first; only count what's in the assertion region.
2. Drop the vocabulary every spec shares.email and password appear in every FR of both specs. If a test mentions "email" once (for a format validation test), every FR suddenly looks anchored. Cut terms whose document frequency exceeds ~40% before matching. This one change moved the result from 3 findings to 7.
3. One matching word is not a match. A test named test_email_validation_rejects_missing_at contains "rejects" — which "matched" an entirely unrelated FR-004 about rejecting a reused OTP. Require at least two distinct terms to co-occur.
The generalisable point: "the vocabulary overlaps" and "the requirement is covered" are different claims, and grep can't tell them apart without these three filters.
What it deliberately does not do
No semantic judgement. D4 is a candidate list, not a verdict.
No LLM call. It runs in CI in under a second.
Doesn't modify your files. Every finding carries the exact command to re-check it yourself.
The part that makes it useful day-to-day
python spec_drift.py --root . --json # machine-readable
python spec_drift.py --root . --fail-on D3 # exit 1 if any FR lost its test anchor
--fail-on D3 turns "specs should stay consistent" from a wish into a CI gate — which is the thing that actually stops drift, rather than detecting it after the fact.
(One detail I got wrong the first time: my CI ran that gate with continue-on-error: true, which means it could never fail. A green badge that proves nothing. It now asserts the opposite — the gate must exit 1 on the broken fixture and exit 0 on the clean one. If a gate can't go red, it isn't a gate.)
I ran it on real projects before posting this — and the first version was unusable
Rather than tune thresholds on my own two-spec toy, I cloned three public repos that actually use spec-kit and ran the checker on their real specs:
repo
specs
test files
findings (first run)
after fix
thooams/uolt
35
103
406
31
XEBIA-ACE/New_Prompt_UMS
23
27
19
19
sardylan/live-qso-map
18
76
12
11
406 findings on one repo is not a strict checker, it's a broken one. Cause: D4 compares every pair of specs, so 35 specs produce ~600 pairs — and specs from the same project are consecutive iterations that share vocabulary by construction. Three fixes brought it to 31: threshold 34% → 45%, keep only the strongest overlap per spec pair, and cap the list at 20 with an explicit "N more folded" line.
The other two repos are where the signal held up:
New_Prompt_UMS: 17 D1 findings. 17 requirements still marked NEEDS CLARIFICATION, in specs whose status is no longer Draft. That's not a heuristic artifact — those are unresolved decisions shipped as if they were settled.
live-qso-map: 10 D2 findings. Status says Draft, but the FRs' distinctive terms are already in the implementation. That's "spec lags reality", the exact inverse of the problem in this issue.
Then I scanned the ecosystem, and found something bigger than drift
I pulled every specs/*/spec.md from 57 public repos that use spec-kit. Not a sample of
convenient repos — everything code search could find:
projects
57
specs
825
functional requirements
9,864
specs still marked Draft
364 (44%)
unresolved [NEEDS CLARIFICATION: ...]
239 across 25 projects (44%)
cross-spec overlaps ≥45%
2,300 pairs across 24 projects
The number that reframes this issue isn't the overlap count. It's that 44% of specs are
still Draft.
If almost half the specs in the wild never reach any terminal state, then "is this spec
still accurate?" is not a question the data can answer — because no spec was ever marked
as accurate in the first place. There is no "done" to drift away from.
That's why I think the useful fix isn't consolidation and isn't a /compact command.
It's giving a spec a lifecycle it can end: Draft → Active → Superseded-by #NNN.
Once a superseded spec points at its successor, D4-style detection becomes unnecessary —
you just read the pointer. Detection is only needed because nobody wrote the pointer down.
Concretely, three small changes would make this problem tractable:
A Superseded-by field in the spec header. One line. Costs nothing, and turns a
latent inconsistency into a queryable edge.
Status that can be Active. Right now the only states are Draft and whatever
people invent; 44% never leave Draft, which makes status carry no information.
A gate that refuses [NEEDS CLARIFICATION] in a spec whose status is not Draft.
239 instances of exactly that exist today across 25 projects.
The checker I built detects the symptom. Those three changes remove the cause. I'd rather
argue about the three changes than about my thresholds.
What I still don't know
Whether the 31 remaining findings in uolt are true positives. I only verified that the count became readable, not that each one is correct. Checking that needs someone who knows the project.
So if this is worth pursuing at all: pick one spec pair from your own output and I'll walk through why it fired. That's the fastest way to find out whether the heuristic deserves to exist — much faster than me tuning it in the dark.
Known limits
D4 is lexical, not semantic. Two requirements can share terms and still be independent. Treat output as a reading list.
Non-English specs are approximated. No tokenizer; CJK runs are sliced into 2–4 character chunks. Good enough to be useful, not linguistically correct.
SC (success criteria) are not checked. They're usually not test-anchored by construction, so they need a different signal.
Thresholds will misfire on unusual vocabulary. 40% / 2-term / 45% are calibrated on 5 projects total (2 synthetic, 3 real). That is not a lot.
git clone https://github.com/chenhz01/spec-drift &&cd spec-drift
python selfcheck.py # 14 assertions: must fire on demo/, must stay silent on clean/
python spec_drift.py --root demo # reproduces the output above
demo/ is the 001-login + 009-2FA case, clean/ is a zero-defect fixture. The self-check
asserts both directions — a checker that only ever says "looks fine" is as untrustworthy
as one that only ever complains.
I'm not proposing this as the solution — just as a concrete starting point that others can re-run and argue with, which seemed more useful than a sixteenth "+1, we need this" on a thread that's been open for 13 months.
Activity
elebescond commentedon Sep 27, 2025
Following up on my own question:
I think the up-to-date centralized documentation could be considered an integral part of the spec constitution, not just a supporting tool.
This would ensure that as features evolve, the “official” documentation reflects the current state and remains consistent with the history of specs.
jperfetto commentedon Sep 29, 2025
I have a similar concern. I think it's essential that specs are maintained/curated/refactored to maintain a single, logically organized source of truth that both humans and AI can refer to. An implementation path could be to do a git commit on the spec update(s), and then use that commit to kick-off planning of a migration plan to implement the spec change.
Doing that, your example might be extended as:
michelegirini commentedon Oct 18, 2025
What if SpecKit could compact multiple implemented features into a single “milestone” spec — kind of like how LLMs summarize long conversations?
After several /specify → /plan → /tasks → implement cycles, we could run /speckit.compact to:
Synthesize all completed specs into a concise milestone summary
Capture shared patterns, architecture, and lessons
Detect drift between intent and implementation
Optionally trigger a light refactor phase to align the codebase with the consolidated spec
Basically: milestones not as planning containers, but as post-implementation compaction points — compressing implementation history into reusable context and keeping both specs and code coherent as the system evolves.
gideoncatz commentedon Oct 20, 2025
This is indeed a key point!
It is important to have an up-to-date, consolidated specification describing the overall project.
So I'm joining the question :-)
timgaunt commentedon Nov 5, 2025
I agree, it would be great if there was a centralised/consolidated spec that keeps track of the entire current definition
amerzad commentedon Nov 11, 2025
That is exactly the killer feature of Openspec.
tjohnson4 commentedon Nov 15, 2025
I also ran into this very early in my testing of this tool. The generated output after the spec is implmented quickly becomes out of date due the the fine tuning changes applied to get the feature ready for actual use. In a perfect world there would be some background job that can detect when feature deviate from the original spec and suggests changes to bring it up to date. I envision this could also be used to create specs for existing features that are missing specs within your project.
gwallan commentedon Dec 30, 2025
I once considered not merging the specs directory into the main branch each time a branch is merged, so as to ensure that the development branch retains its own archival information from the development process.
github-actions commentedon May 30, 2026
This issue has been automatically marked as stale because it has not had any activity for 150 days. It will be closed in 30 days if no further activity occurs.
dan-s-github commentedon Jun 1, 2026
just came across this FR while investigating the same problems, wonder if there is an extension available that would solve this
dmarcelino commentedon Jun 30, 2026
I have the same concern. There's also a discussion about this here: #152
zaytsevand commentedon Aug 12, 2026
The 001-login / 009-2FA example is exactly the case I hit, and I ended up splitting it into two problems that turned out to need different tools.
"What did we already decide?" — context from earlier specs never reaching the current one. I published spec-kit-memory for that (community catalog id
memory): it hooksbefore_specify/before_planand recalls prior specs and decisions from a configurable memory backend before you write the new spec."Which earlier promise did this new spec just invalidate?" — the sharper half of your question, and the one that actually bit me. A merged spec in my project made a closed per-intent allowlist its central safety control. A later spec made that intent set page-derived and unbounded. The first spec was not wrong — an assumption underneath it moved, and nobody wrote the amendment. Both merged separately, each passing its own gate.
/speckit-analyzecould not see it, because it reads one feature directory:spec.md,plan.md,tasks.md, constitution. The hole survived three review rounds before a concrete behavioural question walked straight into it.So I wrote spec-kit-coherence. It hooks
after_analyzeand diffs the feature against its merged siblings — where "merged" is read fromgit ls-tree <base-ref>, so a spec that only exists in your working tree is a peer under construction, not a promise already made.Two halves on purpose:
# Implements:headers in files the branch changed, contracts declaring themselves a delta over another spec. Never "specs that feel related", which is how this degenerates into a second architecture review. It also flags the two defects that need no judgment: a cited spec with no directory, and a delta contract with no base contract to be a delta of.HOLDS/SILENTLY BROKEN/AMENDED— whereAMENDEDrequires the amendment to actually live in the sibling, not merely in the new spec's prose. An amendment written only in the changing document has not been made.On your example it would surface 001's "log in with email + password" as a closed-world promise sitting against 009's added step, and say plainly that the artefact to amend is 001 — and that this is a governance change to 001's promises, not a code edit.
Answering @dan-s-github's June question directly: this is that extension, such as it is. Fair warning that it is new — source is public, but no release is cut yet and it is not in the community catalog, so it is a
specify extension add --dev <path>install for now.mnriem commentedon Aug 12, 2026
As indicated above you can deliver an extension and/or preset to deliver this above and beyond the core SDD process
github-actions commentedon Oct 13, 2026
This issue has been automatically marked as stale because it has had no activity for 60 days. It will be closed in 30 days unless there is a substantive update. If the problem still applies, please provide updated context such as whether it is still reproducible, the affected version or environment, its current impact, and any new reproduction details. A comment that only states that the issue is "still relevant" does not give maintainers enough information to reconsider it.
Khunjua commentedon Oct 20, 2026
We measured how big this problem is on a real repo.
We took frank-fs/frank: 17 features in specs/, one Spec Kit template. Then we folded all 17 into one backlog, oldest first. At each feature we asked: is this story new, does it revise an earlier one, or is it a duplicate?
70 user stories as written became 45. 10 were rewritten by later features, 14 were the same story written again, 1 was dropped. Five features added no new stories at all, they only changed old ones.
One story was touched by four features: 001 → 010 → 014 → 015, all about API compatibility. To know what it says today, you read four spec.md files in order.
And 20 times a later requirement looked like it replaces an earlier one, but no spec says so. One requirement, "all HTML through Hox h()", was put in doubt three times.
So per-feature specs work like a change log, not a spec of the system. The 001-login / 009-2FA case is the normal case, not an edge case.
chenhz01 commentedon Oct 29, 2026
I've hit the same wall, and I think the answer is not "consolidate the specs into one" — it's "detect the drift mechanically, and keep the history".
Reason: merging 001 into 009 destroys the only record of why 001 existed. What's actually missing is a way to know, without reading everything, which specs no longer describe current behaviour.
So I built a zero-dependency checker and ran it against a reproduction of the exact example in this issue (001 login + 009 2FA). Real output, trimmed in three documented ways to keep it readable: the per-finding evidence JSON and re-check command are omitted, and findings 3–6 have their repeated claim wording abbreviated with
…. Everything else is verbatim. (The CLI strings are Chinese because that's the language I work in — I've glossed each line.)--jsonemits the same findings as structured data — rule id, spec, FR id, evidence, and the exact re-check command. The prose fields inside it stay in Chinese; only the structure is language-neutral. A--lang enmode is the obvious next step and I haven't built it.That last one (D4) is the actual question you asked: "is 001's spec now outdated because of 009?" The tool doesn't answer it — it points at the pair and quotes both texts, and a human decides.
What I learned building it: all three obvious approaches fail
1. Don't grep the test files. Comments and docstrings poison the result. My own test file had a trailing comment —
# nothing below covers: sign in with email and password— and that comment alone made FR-001 look "covered". Strip comments and docstrings first; only count what's in the assertion region.2. Drop the vocabulary every spec shares.
emailandpasswordappear in every FR of both specs. If a test mentions "email" once (for a format validation test), every FR suddenly looks anchored. Cut terms whose document frequency exceeds ~40% before matching. This one change moved the result from 3 findings to 7.3. One matching word is not a match. A test named
test_email_validation_rejects_missing_atcontains "rejects" — which "matched" an entirely unrelated FR-004 about rejecting a reused OTP. Require at least two distinct terms to co-occur.The generalisable point: "the vocabulary overlaps" and "the requirement is covered" are different claims, and grep can't tell them apart without these three filters.
What it deliberately does not do
The part that makes it useful day-to-day
--fail-on D3turns "specs should stay consistent" from a wish into a CI gate — which is the thing that actually stops drift, rather than detecting it after the fact.(One detail I got wrong the first time: my CI ran that gate with
continue-on-error: true, which means it could never fail. A green badge that proves nothing. It now asserts the opposite — the gate must exit 1 on the broken fixture and exit 0 on the clean one. If a gate can't go red, it isn't a gate.)I ran it on real projects before posting this — and the first version was unusable
Rather than tune thresholds on my own two-spec toy, I cloned three public repos that actually use spec-kit and ran the checker on their real specs:
thooams/uoltXEBIA-ACE/New_Prompt_UMSsardylan/live-qso-map406 findings on one repo is not a strict checker, it's a broken one. Cause: D4 compares every pair of specs, so 35 specs produce ~600 pairs — and specs from the same project are consecutive iterations that share vocabulary by construction. Three fixes brought it to 31: threshold 34% → 45%, keep only the strongest overlap per spec pair, and cap the list at 20 with an explicit "N more folded" line.
The other two repos are where the signal held up:
NEEDS CLARIFICATION, in specs whose status is no longer Draft. That's not a heuristic artifact — those are unresolved decisions shipped as if they were settled.Draft, but the FRs' distinctive terms are already in the implementation. That's "spec lags reality", the exact inverse of the problem in this issue.Then I scanned the ecosystem, and found something bigger than drift
I pulled every
specs/*/spec.mdfrom 57 public repos that use spec-kit. Not a sample ofconvenient repos — everything code search could find:
Draft[NEEDS CLARIFICATION: ...]The number that reframes this issue isn't the overlap count. It's that 44% of specs are
still
Draft.If almost half the specs in the wild never reach any terminal state, then "is this spec
still accurate?" is not a question the data can answer — because no spec was ever marked
as accurate in the first place. There is no "done" to drift away from.
That's why I think the useful fix isn't consolidation and isn't a
/compactcommand.It's giving a spec a lifecycle it can end:
Draft → Active → Superseded-by #NNN.Once a superseded spec points at its successor, D4-style detection becomes unnecessary —
you just read the pointer. Detection is only needed because nobody wrote the pointer down.
Concretely, three small changes would make this problem tractable:
Superseded-byfield in the spec header. One line. Costs nothing, and turns alatent inconsistency into a queryable edge.
Statusthat can beActive. Right now the only states areDraftand whateverpeople invent; 44% never leave
Draft, which makes status carry no information.[NEEDS CLARIFICATION]in a spec whose status is notDraft.239 instances of exactly that exist today across 25 projects.
The checker I built detects the symptom. Those three changes remove the cause. I'd rather
argue about the three changes than about my thresholds.
What I still don't know
Whether the 31 remaining findings in
uoltare true positives. I only verified that the count became readable, not that each one is correct. Checking that needs someone who knows the project.So if this is worth pursuing at all: pick one spec pair from your own output and I'll walk through why it fired. That's the fastest way to find out whether the heuristic deserves to exist — much faster than me tuning it in the dark.
Known limits
Repo + run instructions: https://github.com/chenhz01/spec-drift
demo/is the 001-login + 009-2FA case,clean/is a zero-defect fixture. The self-checkasserts both directions — a checker that only ever says "looks fine" is as untrustworthy
as one that only ever complains.
I'm not proposing this as the solution — just as a concrete starting point that others can re-run and argue with, which seemed more useful than a sixteenth "+1, we need this" on a thread that's been open for 13 months.