Updated project files and configuration
Some checks failed
WHOOSH CI / speclint (push) Has been cancelled
WHOOSH CI / contracts (push) Has been cancelled

- Added/updated .gitignore file
- Fixed remote URL configuration
- Updated project structure and files

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Claude Code
2025-09-17 22:51:50 +10:00
parent e5555ae277
commit afccc94998
19 changed files with 3376 additions and 2352 deletions

141
scripts/speclint_check.py Normal file
View File

@@ -0,0 +1,141 @@
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import sys
from dataclasses import dataclass
from pathlib import Path
from typing import Iterable, List, Optional
import re
ALLOWED_PROJ = {
"CHORUS",
"COOEE",
"DHT",
"SHHH",
"KACHING",
"HMMM",
"UCXL",
"SLURP",
"RUSTLE",
"WHOOSH",
"BUBBLE",
}
ALLOWED_CAT = {"REQ", "INT", "SEC", "OBS", "PER", "COMP"}
REQ_PATTERN = re.compile(r"REQ:\s*([A-Z]+)-([A-Z]+)-(\d{3})")
UCXL_PATTERN = re.compile(r"UCXL:\s*ucxl://")
SKIP_DIRS = {
".git",
"node_modules",
"dist",
"build",
"venv",
"__pycache__",
".venv",
"target",
}
@dataclass
class Finding:
path: Path
line_no: int
severity: str
message: str
line: str
def iter_files(paths: Iterable[Path]) -> Iterable[Path]:
for p in paths:
if p.is_dir():
for sub in p.rglob("*"):
if sub.is_dir():
if sub.name in SKIP_DIRS:
continue
continue
yield sub
elif p.is_file():
yield p
def validate_file(path: Path, require_ucxl: bool, max_distance: int) -> List[Finding]:
findings: List[Finding] = []
try:
text = path.read_text(errors="ignore")
except Exception as e:
findings.append(Finding(path, 0, "warn", f"unable to read file: {e}", line=""))
return findings
lines = text.splitlines()
for idx, line in enumerate(lines, start=1):
m = REQ_PATTERN.search(line)
if not m:
continue
proj, cat, _ = m.group(1), m.group(2), m.group(3)
if proj not in ALLOWED_PROJ:
findings.append(Finding(path, idx, "error", f"unknown PROJ '{proj}' in ID", line=line))
if cat not in ALLOWED_CAT:
findings.append(Finding(path, idx, "error", f"unknown CAT '{cat}' in ID", line=line))
if require_ucxl:
start = max(1, idx - max_distance)
end = min(len(lines), idx + max_distance)
window = lines[start - 1 : end]
if not any(UCXL_PATTERN.search(l) for l in window):
findings.append(
Finding(
path,
idx,
"error",
f"missing nearby UCXL backlink (±{max_distance} lines)",
line=line,
)
)
return findings
def cmd_check(args: argparse.Namespace) -> int:
paths = [Path(p) for p in args.paths]
all_findings: List[Finding] = []
for f in iter_files(paths):
try:
if f.stat().st_size > 3_000_000:
continue
except Exception:
pass
all_findings.extend(
validate_file(f, require_ucxl=args.require_ucxl, max_distance=args.max_distance)
)
if all_findings:
for fd in all_findings:
print(f"{fd.path}:{fd.line_no}: {fd.severity}: {fd.message}")
if fd.line:
print(f" {fd.line.strip()}")
return 1
return 0
def build_argparser() -> argparse.ArgumentParser:
p = argparse.ArgumentParser(prog="speclint-check", description="Suite 2.0.0 traceability linter (WHOOSH local)")
sub = p.add_subparsers(dest="cmd", required=True)
c = sub.add_parser("check", help="validate requirement IDs and UCXL backlinks")
c.add_argument("paths", nargs="+", help="files or directories to scan")
c.add_argument("--require-ucxl", action="store_true", help="require nearby UCXL backlink")
c.add_argument("--max-distance", type=int, default=5, help="line distance for UCXL proximity check")
c.set_defaults(func=cmd_check)
return p
def main(argv: Optional[list[str]] = None) -> int:
try:
args = build_argparser().parse_args(argv)
return args.func(args)
except Exception as e:
print(f"speclint-check: internal error: {e}", file=sys.stderr)
return 2
if __name__ == "__main__":
sys.exit(main())