#!/usr/bin/env python3 from __future__ import annotations import argparse from common import ( BACKLOG_PATH, ARCHIVE_PATH, IMPLEMENTATION_PATH, PROOF_PATH, RESEARCH_ACTIVE_PATH, append_archive_line, backlog_entry_map, git_commit, remove_backlog_ids, save_text, slugify, today_iso, ) def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser(description="Open a new implementation or research turn.") parser.add_argument("--lane", required=True, choices=("implementation", "research")) parser.add_argument("--title", required=True, help="Turn title.") parser.add_argument("--summary", required=True, help="One-line proof or charter summary.") parser.add_argument( "--pick", action="append", default=[], help="Backlog ID to pull into the turn. Repeat as needed.", ) parser.add_argument( "--commit", action="store_true", help="Commit the planning change automatically.", ) parser.add_argument( "--force", action="store_true", help="Replace an already-open turn instead of refusing.", ) return parser.parse_args() def render_selected(ids: list[str], items: dict[str, str]) -> str: if not ids: return "- none selected" return "\n".join(f"- [{item_id}] {items[item_id]}" for item_id in ids) def open_implementation_turn(title: str, summary: str, selected: str) -> None: opened = today_iso() save_text( PROOF_PATH, f"""# Implementation Proof: {title} Status: open Opened: {opened} ## Hypothesis {summary} ## Scope {selected} ## Non-goals - unchanged from `THESIS.md` unless the user approves otherwise ## Definition of done - current turn implementation is validated with direct evidence - remaining fakes are listed plainly """, ) save_text( IMPLEMENTATION_PATH, f"""# Implementation Turn: {title} Status: open Opened: {opened} ## Goal {summary} ## Selected backlog items {selected} ## Notes - Fill in the concrete implementation plan before coding if the live plan no longer matches the turn. """, ) def open_research_turn(title: str, summary: str, selected: str) -> None: opened = today_iso() save_text( RESEARCH_ACTIVE_PATH, f"""# Research Turn: {title} Status: open Opened: {opened} ## Charter {summary} ## Selected backlog items {selected} ## Hypothesis TBD by the approved research turn. ## Dataset or source of truth TBD by the approved research turn. ## Metrics TBD by the approved research turn. ## Assumptions TBD by the approved research turn. ## Falsification condition TBD by the approved research turn. """, ) def main() -> None: args = parse_args() if args.lane == "implementation": if "Status: open" in PROOF_PATH.read_text(encoding="utf-8") and not args.force: raise SystemExit("implementation turn already open; close it first or pass --force") else: if "Status: open" in RESEARCH_ACTIVE_PATH.read_text(encoding="utf-8") and not args.force: raise SystemExit("research turn already open; close it first or pass --force") entries = backlog_entry_map() missing = [item_id for item_id in args.pick if item_id not in entries] if missing: raise SystemExit(f"unknown backlog IDs: {', '.join(missing)}") selected = render_selected(args.pick, entries) if args.lane == "implementation": open_implementation_turn(args.title, args.summary, selected) else: open_research_turn(args.title, args.summary, selected) if args.pick: remove_backlog_ids(args.pick) append_archive_line( "planning", ( f"- {today_iso()}: opened {args.lane} turn `{slugify(args.title)}` " f"from backlog items {', '.join(args.pick) if args.pick else 'none'}." ), ) if args.commit: commit_paths = [BACKLOG_PATH, ARCHIVE_PATH] if args.lane == "implementation": commit_paths.extend([PROOF_PATH, IMPLEMENTATION_PATH]) else: commit_paths.append(RESEARCH_ACTIVE_PATH) git_commit( f"""Open {args.lane} turn: {args.title} Proof: Establish the approved {args.lane} turn and move selected backlog items into active scope. Assumptions: The selected backlog items are the approved scope for this turn. Still fake: Opening a turn changes planning state only; the work itself is not implemented yet.""", paths=commit_paths, ) if __name__ == "__main__": main()