from __future__ import annotations
import argparse
import json
import sys
from pathlib import Path
SHARED_FIELDS = {
"session_id": "<SESSION_ID>",
"transcript_path": "<TRANSCRIPT_PATH>",
"cwd": "<REPO_ROOT>",
"permission_mode": "default",
}
EVENT_FIXTURES: dict[str, dict] = {
"SessionStart": {
**SHARED_FIELDS,
"hook_event_name": "SessionStart",
"source": "startup",
},
"UserPromptSubmit": {
**SHARED_FIELDS,
"hook_event_name": "UserPromptSubmit",
"prompt": "<USER_PROMPT_BODY>",
},
"PreCompact": {
**SHARED_FIELDS,
"hook_event_name": "PreCompact",
"trigger": "auto",
"custom_instructions": "<USER_PROMPT_BODY>",
},
"Stop": {
**SHARED_FIELDS,
"hook_event_name": "Stop",
"stop_hook_active": True,
},
"SessionEnd": {
**SHARED_FIELDS,
"hook_event_name": "SessionEnd",
"reason": "prompt_input_exit",
},
}
def _per_event_readme() -> str:
return """# Claude Code live evidence
**Status:** Deterministic-from-protocol fixtures (pre-v1 degradation).
These fixtures were generated by
`scripts/build-deterministic-claude-fixtures.py` based on Anthropic's
published Claude Code hook protocol documentation. They are NOT
recordings of a live Claude session — but their shape is what a real
recording would produce after `scripts/sanitize-live-evidence.py`.
## How to upgrade to live evidence
1. Run the operator recapture procedure documented in
`docs/playbooks/capture-live-adapter-evidence.md`.
2. The rig's sanitizer overwrites these files in place under
`<event>/<n>.json`.
3. Re-run `make verify` — the live-evidence comparison test in
`tests/live_evidence_claude.rs` re-runs against the captured
fixtures and confirms or rejects each manifest claim.
## v1 freeze gate
Per #30's acceptance criteria: this issue closes with the
deterministic fixtures and the verification harness, **plus a
documented pre-v1 degradation**: at least one operator recapture run
must occur before v1 freeze to confirm the deterministic fixtures
match real Claude behavior on the v1 freeze candidate harness
versions.
"""
def main(argv: list[str]) -> int:
parser = argparse.ArgumentParser(
description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter
)
parser.add_argument(
"--output-dir",
type=Path,
default=Path(__file__).resolve().parent.parent / "tests" / "fixtures" / "live",
help="Root output directory (default: tests/fixtures/live).",
)
args = parser.parse_args(argv)
claude_root: Path = args.output_dir / "claude"
claude_root.mkdir(parents=True, exist_ok=True)
(claude_root / "README.md").write_text(_per_event_readme())
for event, body in EVENT_FIXTURES.items():
event_dir = claude_root / event
event_dir.mkdir(parents=True, exist_ok=True)
(event_dir / "0.json").write_text(json.dumps(body, indent=2, sort_keys=True) + "\n")
print(
f"build-deterministic-claude-fixtures: wrote {len(EVENT_FIXTURES)} fixtures "
f"under {claude_root}",
file=sys.stderr,
)
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))