149 lines
4.3 KiB
Python
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())
|