#!/usr/bin/env python3 from __future__ import annotations import argparse import hashlib import json import sys from pathlib import Path from typing import Any _ROOT_DIR = Path(__file__).resolve().parents[1] if str(_ROOT_DIR) not in sys.path: sys.path.insert(0, str(_ROOT_DIR)) from scripts.packlib import ( # noqa: E402 ROOT, PackError, load_plan, load_state, packet_map, relative, select_next_packet, ) BINDING_FILES = [ "AGENTS.md", "PROJECT_CONTRACT.md", "docs/02-security/SECURITY_INVARIANTS.md", "CURRENT_STATE.md", ] def choose_packet(packet_id: str | None) -> dict[str, Any]: plan = load_plan() packets = packet_map(plan) if packet_id: if packet_id not in packets: raise PackError(f"unknown packet: {packet_id}") return packets[packet_id] selection = select_next_packet(plan, load_state()) if selection.packet is None: raise PackError(selection.reason) return selection.packet def main() -> int: parser = argparse.ArgumentParser( description="Render minimal, deterministic context for GPT-5.6 Luna." ) parser.add_argument( "--packet", help="explicit packet id; default selects the next ready packet" ) parser.add_argument("--output", help="output path; default .luna/context.md") parser.add_argument("--stdout", action="store_true", help="also print the rendered context") parser.add_argument( "--max-chars", type=int, default=500_000, help="fail rather than silently truncate beyond this size", ) args = parser.parse_args() try: packet = choose_packet(args.packet) except PackError as exc: print(f"ERROR: {exc}", file=sys.stderr) return 2 packet_id = packet["id"] requested = [ *BINDING_FILES, f"implementation/work-packets/{packet_id}.md", *packet.get("read", []), ] ordered: list[str] = [] seen: set[str] = set() for item in requested: if item not in seen: ordered.append(item) seen.add(item) sections: list[str] = [] source_hashes: dict[str, str] = {} for item in ordered: path = ROOT / item if not path.is_file(): print(f"ERROR: context source missing: {item}", file=sys.stderr) return 2 content = path.read_text(encoding="utf-8") source_hashes[item] = hashlib.sha256(content.encode("utf-8")).hexdigest() sections.append(f"\n\n--- BEGIN {item} ---\n\n{content.rstrip()}\n\n--- END {item} ---") plan_entry = json.dumps(packet, indent=2, ensure_ascii=False) header = f"""# ITWorx MCP Hub — Luna context bundle **Active packet:** `{packet_id}` — {packet.get("title")} **Recommended effort:** `{packet.get("effort")}` **Purpose:** Disposable minimal context. Source files remain authoritative. ## Immediate instruction Execute the active packet completely according to `AGENTS.md`: implementation, tests, adversarial review, evidence, state update, focused commit, and automatic continuation to the next ready packet. Do not ask routine questions and do not load unrelated design files without a concrete need. ## Machine-readable packet entry ```json {plan_entry} ``` ## Included source hashes ```json {json.dumps(source_hashes, indent=2)} ``` """ rendered = header + "".join(sections) + "\n" if len(rendered) > args.max_chars: print( f"ERROR: context is {len(rendered)} characters, above " f"--max-chars={args.max_chars}; narrow the packet read list " "instead of truncating", file=sys.stderr, ) return 3 output = Path(args.output) if args.output else ROOT / ".luna" / "context.md" if not output.is_absolute(): output = ROOT / output output.parent.mkdir(parents=True, exist_ok=True) output.write_text(rendered, encoding="utf-8") packet_copy = output.parent / f"context-{packet_id}.md" if packet_copy != output: packet_copy.write_text(rendered, encoding="utf-8") print( f"Rendered {relative(output)} for {packet_id}: " f"{len(ordered)} files, {len(rendered)} characters" ) if args.stdout: print(rendered) return 0 if __name__ == "__main__": sys.exit(main())