her / tools /phase0_check.py
geekwrestler's picture
Squash history (purge pre-scrub demo session blobs)
5f43c7d
#!/usr/bin/env python3
"""Phase 0 gate — deterministic pre-flight, NO model, NO parser logic.
Reproduces the Build-Plan Phase 0 gate (docs/BUILD-PLAN.md):
- the fixture's line count must be 1316
- print the distinct top-level `type` counts
And confirms the Phase-0 capability check: we can enumerate ~/.claude/projects
and read a .jsonl (TRACE-CONTRACT §1).
This is a pre-flight only. Turn/token/provenance numbers belong to Phase 1+.
Run: python3 tools/phase0_check.py
"""
from __future__ import annotations
import json
import sys
from collections import Counter
from pathlib import Path
REPO = Path(__file__).resolve().parent.parent
FIXTURE = REPO / "fixtures" / "sample-session.jsonl"
EXPECTED_LINES = 1316 # TRACE-CONTRACT §6 — ground truth
def type_counts(path: Path) -> tuple[int, int, Counter]:
"""Return (parsed_lines, parse_failures, Counter of top-level `type`)."""
counts: Counter = Counter()
parsed = 0
failures = 0
with path.open(encoding="utf-8") as fh:
for line in fh:
line = line.strip()
if not line:
continue
parsed += 1
try:
obj = json.loads(line)
except json.JSONDecodeError:
failures += 1
continue
counts[obj.get("type", "<none>")] += 1
return parsed, failures, counts
def enumerate_projects() -> tuple[int, int]:
"""Phase-0 capability check: can we walk ~/.claude/projects and see sessions?"""
root = Path.home() / ".claude" / "projects"
if not root.is_dir():
return 0, 0
dirs = [p for p in root.iterdir() if p.is_dir()]
sessions = list(root.glob("**/*.jsonl"))
return len(dirs), len(sessions)
def main() -> int:
if not FIXTURE.exists():
print(f"FAIL — fixture missing: {FIXTURE}")
return 1
parsed, failures, counts = type_counts(FIXTURE)
print("Her · हेर — Phase 0 pre-flight")
print("=" * 52)
print(f"fixture : {FIXTURE}")
print(f"line count : {parsed} (expected {EXPECTED_LINES})")
print(f"json parse errors: {failures}")
print("-" * 52)
print("top-level `type` counts:")
width = max(len(k) for k in counts) if counts else 0
for name, n in sorted(counts.items(), key=lambda kv: (-kv[1], kv[0])):
print(f" {name:<{width}} {n}")
print(f" {'TOTAL':<{width}} {sum(counts.values())}")
print("-" * 52)
n_dirs, n_sessions = enumerate_projects()
print(f"~/.claude/projects: {n_dirs} project dirs, {n_sessions} sessions readable")
print("=" * 52)
ok = (parsed == EXPECTED_LINES) and (failures == 0)
print("GATE:", "PASS ✓" if ok else "FAIL ✗")
return 0 if ok else 1
if __name__ == "__main__":
sys.exit(main())