oc_chat
Loading...
Searching...
No Matches
seed_142_todo_narrative.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3# tools/seed_142_todo_narrative.py
4
5# OCT-LINT: disable=tests_exist reason="Covered by oc_chat/tests/registry/seed/test_todo_narrative.py"
6"""
7ROADMAP-TODO deferred-decision narrative -> note records (Plan 142 rework Phase 2).
8
9Purpose
10-------
11The legacy `oc_chat/roadmap/ROADMAP-TODO-v3.38.md` (695 l.) is retired as a render
12OUTPUT (``__init__.RETIRED_OUTPUTS``), but its **deferred-decision narrative** — the
13organizing prose / why-deferred rationale AROUND the already-migrated 53 backlog
14items — had no registry home. Migrate the three deferred-work ``## `` sections
15**verbatim** into ``source/notes/<id>.md`` records (mirroring the Phase-3 framing
16notes), so the living EXTENDED render carries them (no resurrected 4th file):
17
18 ``## Orchestration Registry (M11) — gates, backlog & deferred design questions``
19 · ``## Active deferred items`` · ``## Post-12-week backlog``
20
21The ``## Recently completed`` per-round log (l.370–686) is intentionally **subsumed
22by the changelog** (same disposition as the What's-New narrative);
23the file's title + ``> Appendix`` framing describes the retired 4-file model and is
24not migrated. (All accounted for in ``tools/142_todo_narrative_coverage.md``.)
25
26No contract change: ``Note`` is the pre-existing P1.1 model. Idempotent.
27
28Responsibilities
29----------------
30- ``parse_sections`` (slice the 3 deferred ``## `` sections), ``backfill`` (write
31 the note records), reported ``sections`` for the audit.
32
33Diagnostics
34-----------
35Domain: OC_CHAT.MAIN
36Levels:
37 L2 - seed lifecycle
38 L3 - per-section extraction
39 L4 - per-record emission
40
41Contracts
42---------
43- Deterministic; writes only ``source/notes/<id>.md``; never touches the live
44 legacy TODO; every record validates + re-canonicalises.
45
46Dependencies
47------------
48- oc_diagnostics (structured debug logging via _dbg)
49- oc_chat.registry.regenerate (imported for the contracts bootstrap it runs)
50- option_c_registry_contracts (the Note model and SCHEMA_VERSION for each deferred-decision note record)
51- oc_chat.registry.source.canonical (format_record_text - writes each note's frontmatter and verbatim body)
52"""
53from __future__ import annotations
54
55import re
56import sys
57from dataclasses import dataclass, field
58from pathlib import Path
59
60# Force the WORKTREE-local oc_chat ahead of the editable main-repo install.
61sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
62
63from oc_diagnostics import _dbg
64
65import oc_chat.registry.regenerate # noqa: F401 (contracts bootstrap)
66import option_c_registry_contracts as rc
67
68from oc_chat.registry.source.canonical import format_record_text
69
70_dbg("OC_CHAT.MAIN", "module", "loaded", 4)
71
72#: (heading-prefix, note id, clean title) for the 3 deferred-decision sections.
73#: Each prefix is unique; the slicer stops at the next ``## ``/``# `` heading, so
74#: the ``## Recently completed`` per-round log is excluded (subsumed by changelog).
75_SECTIONS: tuple[tuple[str, str, str], ...] = (
76 ("## Orchestration Registry (M11)", "m11-deferred",
77 "Orchestration Registry (M11) — gates, backlog & deferred design questions"),
78 ("## Active deferred items", "active-deferred",
79 "Active deferred items (still relevant, folded forward, or explicitly deferred)"),
80 ("## Post-12-week backlog", "post-12-week-backlog",
81 # OCT-LINT: disable=no_hardcoded_secrets reason="Roadmap section headings + note ids are not secrets"
82 "Post-12-week backlog (from unified-roadmap-v3 §5)"),
83 ("## Branch / plans / docs hygiene", "branch-plans-docs-hygiene",
84 "Branch / plans / docs hygiene (running list)"),
85)
86_BOUNDARY_RE = re.compile(r"^(## |# |\*End of )", re.MULTILINE)
87_TRAILING_RULE_RE = re.compile(r"\n*-{3,}\s*$")
88
89
90@dataclass
92 notes_written: int = 0
93 sections: dict = field(default_factory=dict) # id -> (title, body)
94
95
96def parse_sections(todo_text: str) -> dict[str, tuple[str, str]]:
97 """Return ``{note_id: (title, verbatim_body)}`` for the 3 deferred sections."""
98 text = todo_text.replace("\r\n", "\n")
99 out: dict[str, tuple[str, str]] = {}
100 for prefix, note_id, title in _SECTIONS:
101 match = re.search(rf"^{re.escape(prefix)}.*$", text, re.MULTILINE)
102 if match is None:
103 continue
104 nxt = _BOUNDARY_RE.search(text, match.end())
105 end = nxt.start() if nxt else len(text)
106 body = text[match.start():end].strip("\n")
107 out[note_id] = (title, _TRAILING_RULE_RE.sub("", body).strip("\n"))
108 return out
109
110
111def backfill(todo_md: Path, source_dir: Path) -> TodoReport:
112 """Write each deferred section to ``source/notes/<id>.md`` (verbatim body)."""
113 func = "backfill"
114 sections = parse_sections(Path(todo_md).read_text(encoding="utf-8"))
115 out_dir = Path(source_dir) / "notes"
116 out_dir.mkdir(parents=True, exist_ok=True)
117 report = TodoReport()
118 for note_id, (title, body) in sections.items():
119 model = rc.Note(schema_version=rc.SCHEMA_VERSION, id=note_id, title=title)
120 (out_dir / f"{note_id}.md").write_text(
121 format_record_text(model, body=body), encoding="utf-8", newline=""
122 )
123 report.notes_written += 1
124 report.sections[note_id] = (title, body)
125 _dbg("OC_CHAT.MAIN", func, f"wrote note {note_id} ({len(body)} chars)", 3)
126 _dbg("OC_CHAT.MAIN", func, f"wrote {report.notes_written} TODO-narrative notes", 2)
127 return report
128
129
130def _main() -> int:
131 func = "_main"
132 repo = Path(__file__).resolve().parents[1]
133 roadmap = repo / "oc_chat" / "roadmap" / "ROADMAP-TODO-v3.38.md"
134 source = repo / "oc_chat" / "roadmap" / "source"
135 report = backfill(roadmap, source)
136 _dbg("OC_CHAT.MAIN", func, f"notes={report.notes_written}", 2)
137 for note_id, (title, body) in report.sections.items():
138 print(f"{note_id}: {title!r} ({len(body)} chars)")
139 print(f"notes_written={report.notes_written}")
140 return 0
141
142
143if __name__ == "__main__":
144 raise SystemExit(_main())
dict[str, tuple[str, str]] parse_sections(str todo_text)
TodoReport backfill(Path todo_md, Path source_dir)