extract.py python
137 lines 3.8 KB
Raw
sha256:0e9549ec7b463911bc08b7d586dc320b1ac9b1f5c943ee7e3865dcc6cb0f6f83 chore(governance): sync handover+roadmap to 84db8c8 (drift:… Human 9 hours ago
1 """Extract and format the paste-ready fence for ``ok next`` (§ONS.5)."""
2
3 from __future__ import annotations
4
5 from dataclasses import dataclass
6 from pathlib import Path
7
8 from tools.governance_hygiene.next_regen import extract_paste_fence_body
9
10 # Exact heading — one space before the Unicode em dash (§ONS.5.3).
11 CURRENT_NEXT_HEADING = "## CURRENT NEXT — paste this"
12
13 PASTE_HEADING = "### Paste-ready prompt"
14
15 REASON_HANDOVER_MISSING = "handover_missing"
16 REASON_HANDOVER_UNREADABLE = "handover_unreadable"
17 REASON_HEADING_MISSING = "heading_missing"
18 REASON_FENCE_MISSING = "fence_missing"
19 REASON_FENCE_EMPTY = "fence_empty"
20 REASON_MODEL_MISSING = "model_missing"
21
22 FAIL_CLOSED_REASONS = frozenset(
23 {
24 REASON_HANDOVER_MISSING,
25 REASON_HANDOVER_UNREADABLE,
26 REASON_HEADING_MISSING,
27 REASON_FENCE_MISSING,
28 REASON_FENCE_EMPTY,
29 REASON_MODEL_MISSING,
30 }
31 )
32
33
34 @dataclass(frozen=True)
35 class CurrentNextResult:
36 """Successful extract of the paste-ready fence body."""
37
38 path: str
39 lane: str | None
40 fence: str
41 heading: str = CURRENT_NEXT_HEADING
42
43
44 @dataclass(frozen=True)
45 class CurrentNextError:
46 """Fail-closed extract outcome (§ONS.5.7)."""
47
48 reason: str
49 detail: str
50 path: str | None = None
51 lane: str | None = None
52
53 @property
54 def message(self) -> str:
55 return f"next: {self.reason} — {self.detail}"
56
57
58 def extract_current_next(
59 handover_path: Path,
60 *,
61 repo_relative_path: str,
62 lane: str | None,
63 ) -> CurrentNextResult | CurrentNextError:
64 """Read ``handover_path`` and return the paste fence or a closed reason.
65
66 Order of fail-closed reasons is frozen (§ONS.5.7). Does not invent a fence
67 from roadmap, chat memory, or planned regen bytes.
68 """
69 if not handover_path.exists():
70 return CurrentNextError(
71 reason=REASON_HANDOVER_MISSING,
72 detail=f"handover not found: {repo_relative_path}",
73 path=repo_relative_path,
74 lane=lane,
75 )
76
77 try:
78 text = handover_path.read_text(encoding="utf-8")
79 except (OSError, UnicodeDecodeError) as exc:
80 return CurrentNextError(
81 reason=REASON_HANDOVER_UNREADABLE,
82 detail=f"cannot read handover as UTF-8: {exc}",
83 path=repo_relative_path,
84 lane=lane,
85 )
86
87 # Heading check before fence extract so a stray ``` elsewhere cannot win.
88 if PASTE_HEADING not in text:
89 return CurrentNextError(
90 reason=REASON_HEADING_MISSING,
91 detail="missing '### Paste-ready prompt' heading (KH1 H7)",
92 path=repo_relative_path,
93 lane=lane,
94 )
95
96 body = extract_paste_fence_body(text)
97 if body is None:
98 return CurrentNextError(
99 reason=REASON_FENCE_MISSING,
100 detail="paste-ready heading present but no fenced body",
101 path=repo_relative_path,
102 lane=lane,
103 )
104
105 if not body.strip():
106 return CurrentNextError(
107 reason=REASON_FENCE_EMPTY,
108 detail="paste-ready fence body is empty",
109 path=repo_relative_path,
110 lane=lane,
111 )
112
113 if "Model:" not in body:
114 return CurrentNextError(
115 reason=REASON_MODEL_MISSING,
116 detail="paste-ready fence body lacks 'Model:' (KH1 H8)",
117 path=repo_relative_path,
118 lane=lane,
119 )
120
121 return CurrentNextResult(
122 path=repo_relative_path,
123 lane=lane,
124 fence=body,
125 heading=CURRENT_NEXT_HEADING,
126 )
127
128
129 def format_current_next(result: CurrentNextResult) -> str:
130 """Return human stdout bytes for a successful extract (§ONS.5.4).
131
132 Trailing newline on the last line is included.
133 """
134 body = result.fence
135 if not body.endswith("\n"):
136 body = body + "\n"
137 return f"{CURRENT_NEXT_HEADING}\n\n```text\n{body}```\n"
File History 1 commit
sha256:6abcf1fa82a7a621ccbc945f19acdba5bc0db54569599404a1452fb4a096a199 fix(ISR): default require_independent_second_reviewer to require Human minor 9 hours ago