import json import sys from datetime import date from pathlib import Path OUTPUT_RELATIVE_PATH = ( Path("ccra_review_bundle") / "round-05_init_selector-calibration-policy" / "00_ROUND05_NEW_SESSION_CONTEXT.md" ) REQUIRED_SOURCE_FILES = [ "reports/Round04_closeout_note_2026-06-18.md", "reports/Round04_blind_routing_evaluation_report_2026-06-18.md", "reports/Round04_1_post_patch_routing_verification_report_2026-06-18.md", "selector/round04_blind_inputs.json", "selector/selector_rules.json", "selector/selector_calibration_inputs.json", "tests/qpi.regression.json", "tests/intellectual_archaeology.regression.json", "tests/regression_cases.json", "docs/DECISIONS.md", "docs/WORKFLOW.md", ] SECTION_TITLES = [ "Project State", "Round 04 / 04.1 Closure Summary", "Round 05: Selector Calibration Policy Review", "Round 05 Core Questions", "Files To Read in New Conversation", "Round 05 Non-Goals", "Suggested Round 05 Deliverables", "Missing Files", ] MODEL_FILES = { "QPI": Path("models") / "qpi.model.json", "Intellectual Archaeology": Path("models") / "intellectual_archaeology.model.json", } REGRESSION_FILES = { "QPI": Path("tests") / "qpi.regression.json", "Intellectual Archaeology": Path("tests") / "intellectual_archaeology.regression.json", "Aggregate": Path("tests") / "regression_cases.json", } def read_json(path): return json.loads(path.read_text(encoding="utf-8")) def collect_missing_files(root): return [ rel_path for rel_path in REQUIRED_SOURCE_FILES if not (root / Path(rel_path)).is_file() ] def collect_model_states(root): states = [] for display_name, rel_path in MODEL_FILES.items(): path = root / rel_path if not path.is_file(): states.append({ "display_name": display_name, "status": "missing", "stability_level": "missing", "regression_status": "missing", }) continue data = read_json(path) states.append({ "display_name": display_name, "status": data.get("status", "unknown"), "stability_level": data.get("stability_profile", {}).get("stability_level", "unknown"), "regression_status": data.get("regression_status", "unknown"), }) return states def collect_regression_counts(root): counts = {} for display_name, rel_path in REGRESSION_FILES.items(): path = root / rel_path if not path.is_file(): counts[display_name] = "missing" continue data = read_json(path) counts[display_name] = len(data.get("regression_cases", [])) return counts def collect_round04_evidence(root): closeout = root / "reports" / "Round04_closeout_note_2026-06-18.md" post_patch = root / "reports" / "Round04_1_post_patch_routing_verification_report_2026-06-18.md" evidence = { "closeout_note_present": closeout.is_file(), "post_patch_report_present": post_patch.is_file(), "targeted_cases_checked": "unknown", "targeted_failures": "unknown", "behavior_changes_recorded": "unknown", "r04_bi_022_recorded": False, } if post_patch.is_file(): text = post_patch.read_text(encoding="utf-8") evidence["targeted_cases_checked"] = extract_report_value(text, "Targeted cases checked") evidence["targeted_failures"] = extract_report_value(text, "Targeted failures") evidence["behavior_changes_recorded"] = extract_report_value(text, "Behavior changes recorded") evidence["r04_bi_022_recorded"] = "R04-BI-022" in text return evidence def extract_report_value(text, label): prefix = f"{label}: " for line in text.splitlines(): if line.startswith(prefix): return line[len(prefix):].strip() return "unknown" def build_markdown_list(items): return "\n".join(f"- {item}" for item in items) def build_file_readiness_lines(root): lines = [] for rel_path in REQUIRED_SOURCE_FILES: status = "present" if (root / Path(rel_path)).is_file() else "missing" lines.append(f"- `{rel_path}` ({status})") return lines def build_context_document(root): missing_files = collect_missing_files(root) model_states = collect_model_states(root) regression_counts = collect_regression_counts(root) evidence = collect_round04_evidence(root) model_state_lines = [ f"- {item['display_name']}: status `{item['status']}`, " f"stability_level `{item['stability_level']}`, " f"regression_status `{item['regression_status']}`" for item in model_states ] regression_lines = [ f"- {name}: {count}" for name, count in regression_counts.items() ] missing_lines = ["- None"] if not missing_files else [f"- `{path}`" for path in missing_files] lines = [ "# Round 05 New Session Context", "", f"Generated: {date.today().isoformat()}", "", "Generated by `python scripts/init_round05_context.py`.", "", "This file is a read-only startup context for a new Round 05 conversation. The script does not call an LLM and does not modify selector rules, model cards, model specs, or regression cases.", "", "## Project State", "", "- Current phase: `model_library_mvp`.", "- Current goal: file-first cognitive model library MVP.", "- Current sample models: QPI and Intellectual Archaeology.", "- Current selector: rule-based, backed by `selector/selector_rules.json`.", "- No LLM selector, RAG, vector database, frontend, backend, or service layer is in scope.", "", "Current model lifecycle state:", "", *model_state_lines, "", "Current regression case counts:", "", *regression_lines, "", "## Round 04 / 04.1 Closure Summary", "", "- Round 04 was a blind routing evaluation.", "- The original Round 04 blind routing report is preserved and was not rewritten.", "- Round 04.1 was a selector no-call, hard exclusion, and depth-limiting patch plus post-patch verification.", "- Round 04.1 is not a second blind test.", "- Four targeted cases passed post-patch verification.", f"- Targeted cases checked: {evidence['targeted_cases_checked']}.", f"- Targeted failures: {evidence['targeted_failures']}.", f"- Behavior changes recorded: {evidence['behavior_changes_recorded']}.", "- No model lifecycle upgrade follows from Round 04 / 04.1.", "- No LLM selector is introduced.", "- QPI and Intellectual Archaeology remain `draft / B / pending`.", "", "Round 04.1 targeted fixes:", "", "- `R04-BI-002`: translation payload must not trigger QPI.", "- `R04-BI-024`: explicit IA refusal must hard-exclude Intellectual Archaeology while allowing lightweight QPI.", "- `R04-BI-035`: depth-limiting plus dominant-scarcity judgment should allow QPI.", "- `R04-BI-036`: depth-limiting plus information / path / consensus scarcity judgment should allow QPI.", "", "`R04-BI-022` closeout note:", "", "- Original result: hard no-call.", "- After patch: QPI selected, IA rejected.", "- Judgment: accepted collateral behavior change from the depth-limiting QPI override.", "- This is not treated as a new failure.", "- It is already represented in QPI regression as `case_qpi_round04_depth_limited_assumption_scarcity_001`.", "- Round 05 should discuss whether it remains regression, also enters calibration, or becomes calibration-only policy material later.", "- Do not open a new patch round only for `R04-BI-022`.", "", "## Round 05: Selector Calibration Policy Review", "", "Round 05 should review selector calibration policy before changing rules.", "", "Round 05 should not directly modify selector rules unless Owner / CCRA explicitly approves a follow-up Round 05.1 patch.", "", "Round 05 should not continue treating Round 04.1 as an unfinished patch round. The policy review starts from the accepted Round 04 / 04.1 closure evidence.", "", "## Round 05 Core Questions", "", "- Should governance / responsibility / consensus inputs more easily enter QPI?", "- For capacity / resource / path inputs, which should remain no-call and which should enter QPI?", "- Should likely Intellectual Archaeology natural-language inputs allow direct IA entry, or must they be QPI-first?", "- When the user says \"QPI already judged...\", should the selector still select QPI together with IA, or select IA only?", "- How should ambiguous low-context inputs be handled?", "- Is a before/after behavior diff checker needed to list every non-target behavior change automatically?", "- Should `R04-BI-022` stay in regression, move into calibration discussion, or both?", "", "## Files To Read in New Conversation", "", *build_file_readiness_lines(root), "", "## Round 05 Non-Goals", "", "- Do not add a third model.", "- Do not upgrade QPI.", "- Do not upgrade Intellectual Archaeology.", "- Do not introduce an LLM selector.", "- Do not introduce RAG, vector database, frontend, or backend.", "- Do not expand all organization problems into QPI recall.", "- Do not convert the full Round 04 blind pool directly into regression.", "", "## Suggested Round 05 Deliverables", "", "- `01_ROUND05_SCOPE_AND_POLICY_QUESTIONS.md`", "- `02_ROUND04_BEHAVIOR_DIFF_FOR_CALIBRATION.md`", "- `03_CANDIDATE_CALIBRATION_CASES.md`", "- `04_CANDIDATE_REGRESSION_CASES.md`", "- `05_CCRA_REVIEW_QUESTIONS_ROUND05.md`", "", "## Missing Files", "", *missing_lines, "", ] summary = { "output_path": str(OUTPUT_RELATIVE_PATH).replace("\\", "/"), "success": True, "missing_files": missing_files, "sections": SECTION_TITLES, } return "\n".join(lines), summary def write_context_document(root): document, summary = build_context_document(root) output_path = root / OUTPUT_RELATIVE_PATH output_path.parent.mkdir(parents=True, exist_ok=True) output_path.write_text(document, encoding="utf-8") return output_path, summary def main(): root = Path(__file__).resolve().parents[1] output_path, summary = write_context_document(root) print(f"Generated file: {output_path}") print("Success: yes") if summary["missing_files"]: print("Missing files:") for path in summary["missing_files"]: print(f"- {path}") else: print("Missing files: none") print("Main sections:") for section in summary["sections"]: print(f"- {section}") print("Selector rules modified: no") return 0 if __name__ == "__main__": sys.exit(main())