oc_chat
Loading...
Searching...
No Matches
notes.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3# oc_chat/registry/render/roadmap/notes.py
4
5# OCT-LINT: disable=tests_exist reason="Covered by oc_chat/tests/registry/seed/test_framing_notes.py"
6"""
7Source-backed EXTENDED framing-note sections (Plan 142 P3 Part 2, §3.5).
8
9Purpose
10-------
11Render the roadmap-level **framing sections** — Sources Referenced, Reconciliation
12notes, Execution Order, Out Of Scope, Git Workflow & Version Convention — from the
13``source/notes/<id>.md`` records (``kind: note``, EXTENDED-only, §3.5), mirroring the
14changelog-framing pattern: each note's **body is the verbatim legacy section**
15(its own ``## `` heading + content), emitted as-is. NOT read from the store (a
16note is never a ``roadmap_items_v3`` row); NO prose hard-coded in the renderer.
17The lean ``ROADMAP.md`` does not carry these — framing is EXTENDED-only.
18
19Responsibilities
20----------------
21- ``load_note_records(source_dir)`` — scan + load the ``kind: note`` records.
22- ``render_note_sections(records)`` — emit each verbatim body in render order.
23
24Diagnostics
25-----------
26Domain: OC_CHAT.MAIN
27Levels:
28 L2 - render lifecycle
29 L3 - per-note counts
30 L4 - per-section assembly
31
32Contracts
33---------
34- Deterministic: identical source -> identical bytes; renderer stores no prose.
35- Reads no store table (a note is source-only).
36
37Dependencies
38------------
39- oc_diagnostics (structured debug logging via _dbg)
40- oc_chat.registry.source.scanner (source tree scan for note records)
41- oc_chat.registry.source.canonical (loads each note record into its model and body)
42"""
43from __future__ import annotations
44
45from pathlib import Path
46
47from oc_diagnostics import _dbg
48
49from oc_chat.registry.source.canonical import load_record
50from oc_chat.registry.source.scanner import scan_source_tree
51
52_dbg("OC_CHAT.MAIN", "module", "loaded", 4)
53
54#: Render order for the framing notes (legacy reading order). Any note id not
55#: listed sorts after these, by id — so a future note still renders (never dropped).
56_NOTE_ORDER: tuple[str, ...] = (
57 "versioning-policy",
58 "sources-referenced",
59 "reconciliation-notes",
60 "execution-order",
61 "out-of-scope",
62 "git-workflow",
63 # ROADMAP-TODO deferred-decision narrative (rework Phase 2).
64 "m11-deferred",
65 "active-deferred",
66 "post-12-week-backlog",
67 "branch-plans-docs-hygiene",
68)
69
70
71def load_note_records(source_dir: Path) -> list[tuple[object, str]]:
72 """Return ``[(Note, body)]`` for every ``source/notes/<id>.md`` record."""
73 out: list[tuple[object, str]] = []
74 for scanned in scan_source_tree(Path(source_dir)):
75 if scanned.kind == "note":
76 model, body = load_record(scanned)
77 out.append((model, body))
78 return out
79
80
81def _order_key(model: object) -> tuple[int, str]:
82 note_id = str(getattr(model, "id"))
83 rank = _NOTE_ORDER.index(note_id) if note_id in _NOTE_ORDER else len(_NOTE_ORDER)
84 return (rank, note_id)
85
86
87def render_note_sections(records: list[tuple[object, str]]) -> list[str]:
88 """Emit each framing note's verbatim body (its own ``## `` heading), in order."""
89 func = "render_note_sections"
90 ordered = sorted(records, key=lambda pair: _order_key(pair[0]))
91 lines: list[str] = []
92 for _model, body in ordered:
93 text = body.strip()
94 if not text:
95 continue
96 if lines:
97 lines.append("")
98 lines.extend(text.split("\n"))
99 _dbg("OC_CHAT.MAIN", func, f"{len(ordered)} framing-note sections", 2)
100 return lines
101
102
103__all__ = ["load_note_records", "render_note_sections"]
tuple[int, str] _order_key(object model)
Definition notes.py:81
list[str] render_note_sections(list[tuple[object, str]] records)
Definition notes.py:87
list[tuple[object, str]] load_note_records(Path source_dir)
Definition notes.py:71