← Files Product & App ArchitectARCHIVED FILE
scripts/spec_check.py
2.66 KB · Nov 11, 2026 · 12:42 UTC
"""Small offline specification integrity checker (not an AI reviewer).
Input JSON expected fields: requirements [{id,description,acceptance_tests:[ids]}],
tests [{id}], milestones [{id,depends_on:[ids]}].
Reports missing traceability and dependency cycles. Does not validate semantic quality.
"""
import json
import sys
from pathlib import Path
def inspect_spec(data):
findings=[]
reqs=data.get('requirements',[])
tests=data.get('tests',[])
milestones=data.get('milestones',[])
def duplicates(xs):
seen=set();dup=set()
for x in xs:
if x in seen:dup.add(x)
seen.add(x)
return sorted(dup)
for group,arr in [('requirements',reqs),('tests',tests),('milestones',milestones)]:
ids=[str(item.get('id','')).strip() for item in arr]
for val in duplicates(ids):findings.append({'code':'DUPLICATE_ID','location':group,'id':val})
if any(not i for i in ids):findings.append({'code':'MISSING_ID','location':group})
test_ids={str(x.get('id','')) for x in tests}
for r in reqs:
rid=str(r.get('id',''))
if not str(r.get('description','')).strip():findings.append({'code':'MISSING_DESCRIPTION','requirement':rid})
covered=r.get('acceptance_tests',[])
if not covered:findings.append({'code':'UNTESTED_REQUIREMENT','requirement':rid})
for tid in covered:
if str(tid) not in test_ids:findings.append({'code':'UNKNOWN_TEST','requirement':rid,'test':tid})
ids={str(m.get('id','')) for m in milestones}
deps={str(m.get('id','')):[str(x) for x in m.get('depends_on',[])] for m in milestones}
for k,vals in deps.items():
for d in vals:
if d not in ids:findings.append({'code':'UNKNOWN_DEPENDENCY','milestone':k,'depends_on':d})
if d==k:findings.append({'code':'SELF_DEPENDENCY','milestone':k})
visited=set();active=set();cycles=set()
def visit(n):
if n in active:cycles.add(n);return
if n in visited:return
active.add(n)
for d in deps.get(n,[]):
if d in ids:visit(d)
active.remove(n);visited.add(n)
for n in deps:visit(n)
if cycles:findings.append({'code':'DEPENDENCY_CYCLE','milestones':sorted(cycles)})
return {'ok':not findings,'findings':findings,'requirement_count':len(reqs),'test_count':len(tests),'milestone_count':len(milestones)}
if __name__=='__main__':
if len(sys.argv)!=2:
print('Usage: python scripts/spec_check.py path/to/spec.json',file=sys.stderr)
sys.exit(2)
data=json.loads(Path(sys.argv[1]).read_text(encoding='utf-8'))
report=inspect_spec(data)
print(json.dumps(report,indent=2))
sys.exit(0 if report['ok'] else 1)
SHA-256: afb7e74c6e5fa0a6eb54c661c3f79128fdc37196baa951455b4ecccb8e5ac2df