#!/usr/bin/env python3 """SessionStart hook — verify AirContext/ presence and surface a friendly notice. Stage-2 implementation. For now we: - look for AirContext/ in cwd - if absent or invalid, set state.config_missing=true so UserPromptSubmit can block - emit a SessionStart additionalContext message describing AirContext status """ from __future__ import annotations import json import os import sys from pathlib import Path _HERE = Path(__file__).resolve().parent sys.path.insert(0, str(_HERE)) from core.config_loader import validate_config # noqa: E402 from core.state import StateFile # noqa: E402 def _find_project_root(stdin_payload: dict) -> Path: cwd = stdin_payload.get("cwd") or os.environ.get("CLAUDE_PROJECT_DIR") or os.getcwd() return Path(cwd) def main() -> int: raw = sys.stdin.read() or "{}" try: payload = json.loads(raw) except json.JSONDecodeError: payload = {} project = _find_project_root(payload) air = project / "AirContext" # Project hasn't opted in — stay completely silent so AirContext doesn't # pollute every Claude Code session globally. if not air.exists(): return 0 config = air / "config.yaml" state = StateFile(air / "state.json") sid = payload.get("session_id") if sid: state.update(last_session_id=sid) if not config.exists(): state.update(config_missing=True) print(json.dumps({ "hookSpecificOutput": { "hookEventName": "SessionStart", "additionalContext": ( "AirContext: AirContext/ exists but config.yaml is missing. " "Run `/aircontext-init` to (re)install templates." ) } })) return 0 err = validate_config(config) if err: state.update(config_missing=True) print(json.dumps({ "hookSpecificOutput": { "hookEventName": "SessionStart", "additionalContext": f"AirContext config invalid: {err}" } })) return 0 state.update(config_missing=False) # Healthy — stay silent to keep prompt clean. return 0 if __name__ == "__main__": sys.exit(main())