Files

149 lines
4.3 KiB
Python

#!/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())