anchors.py
python
sha256:a78e7e5a8740e03315f325d19edeb3aa1b306b3337d04abbaa9a9e0f3bbeb7a1
docs: MuseHub-first before ISR #74 — staging solidify NEXT
Human
13 hours ago
| 1 | """Named anchor markers for templated section replacement (§4).""" |
| 2 | |
| 3 | from __future__ import annotations |
| 4 | |
| 5 | import re |
| 6 | |
| 7 | ANCHOR_OPEN = "<!-- overseer:anchor:{name} -->" |
| 8 | ANCHOR_CLOSE = "<!-- /overseer:anchor:{name} -->" |
| 9 | |
| 10 | |
| 11 | def anchor_open(name: str) -> str: |
| 12 | return ANCHOR_OPEN.format(name=name) |
| 13 | |
| 14 | |
| 15 | def anchor_close(name: str) -> str: |
| 16 | return ANCHOR_CLOSE.format(name=name) |
| 17 | |
| 18 | |
| 19 | HANDOVER_ANCHORS = frozenset( |
| 20 | { |
| 21 | "vcs-table", |
| 22 | "done-recently", |
| 23 | "verified-snapshot", |
| 24 | "change-log", |
| 25 | "next-session", |
| 26 | "paste-ready-prompt", |
| 27 | } |
| 28 | ) |
| 29 | |
| 30 | ROADMAP_ANCHORS = frozenset( |
| 31 | { |
| 32 | "build-queue", |
| 33 | "next-step-glance", |
| 34 | } |
| 35 | ) |
| 36 | |
| 37 | |
| 38 | def replace_anchor_block(text: str, name: str, new_body: str) -> str: |
| 39 | """Replace content between named anchors; insert anchors if the block is missing.""" |
| 40 | open_marker = anchor_open(name) |
| 41 | close_marker = anchor_close(name) |
| 42 | body = new_body.strip("\n") |
| 43 | block = f"{open_marker}\n{body}\n{close_marker}" |
| 44 | |
| 45 | pattern = re.compile( |
| 46 | re.escape(open_marker) + r"\n.*?\n" + re.escape(close_marker), |
| 47 | re.DOTALL, |
| 48 | ) |
| 49 | if pattern.search(text): |
| 50 | return pattern.sub(block, text, count=1) |
| 51 | |
| 52 | return _insert_anchor_fallback(text, name, block) |
| 53 | |
| 54 | |
| 55 | def _insert_anchor_fallback(text: str, name: str, block: str) -> str: |
| 56 | """Insert or replace anchor block near a known heading when markers are absent. |
| 57 | |
| 58 | ``next-session`` / ``paste-ready-prompt`` use region bounds (§GSP.5.3) so nested |
| 59 | paste-inside-NEXT dogfood is not swallowed on first regen. |
| 60 | """ |
| 61 | if name == "next-session": |
| 62 | replaced = _replace_next_session_region(text, block) |
| 63 | if replaced is not None: |
| 64 | return replaced |
| 65 | if name == "paste-ready-prompt": |
| 66 | replaced = _replace_paste_ready_region(text, block) |
| 67 | if replaced is not None: |
| 68 | return replaced |
| 69 | |
| 70 | heading_map = { |
| 71 | "vcs-table": "## VCS (verified", |
| 72 | "done-recently": "### What just landed", |
| 73 | "verified-snapshot": "## Verified snapshot", |
| 74 | "change-log": "## Change log", |
| 75 | "next-session": "## NEXT SESSION", |
| 76 | "paste-ready-prompt": "### Paste-ready prompt", |
| 77 | "build-queue": "## Build queue", |
| 78 | "next-step-glance": "## Next step at a glance", |
| 79 | } |
| 80 | heading = heading_map.get(name) |
| 81 | if heading and heading in text: |
| 82 | return text.replace(heading, block + "\n\n" + heading, 1) |
| 83 | return text.rstrip() + "\n\n" + block + "\n" |
| 84 | |
| 85 | |
| 86 | def _replace_next_session_region(text: str, block: str) -> str | None: |
| 87 | """Replace from ``## NEXT SESSION`` through line before paste heading or snapshot ``---``.""" |
| 88 | lines = text.splitlines(keepends=True) |
| 89 | start: int | None = None |
| 90 | end: int | None = None |
| 91 | for index, line in enumerate(lines): |
| 92 | if start is None and line.startswith("## NEXT SESSION"): |
| 93 | start = index |
| 94 | continue |
| 95 | if start is None: |
| 96 | continue |
| 97 | stripped = line.strip() |
| 98 | if stripped.startswith("### Paste-ready prompt"): |
| 99 | end = index |
| 100 | break |
| 101 | if stripped == "---": |
| 102 | # Prefer the --- that precedes ## Verified snapshot when no paste heading. |
| 103 | ahead = "".join(lines[index + 1 : index + 6]) |
| 104 | if "## Verified snapshot" in ahead or "<!-- overseer:anchor:verified-snapshot" in ahead: |
| 105 | end = index |
| 106 | break |
| 107 | if start is None: |
| 108 | return None |
| 109 | if end is None: |
| 110 | end = len(lines) |
| 111 | prefix = "".join(lines[:start]) |
| 112 | suffix = "".join(lines[end:]) |
| 113 | body = block if block.endswith("\n") else block + "\n" |
| 114 | return prefix + body + ("\n" if suffix and not body.endswith("\n\n") else "") + suffix |
| 115 | |
| 116 | |
| 117 | def _replace_paste_ready_region(text: str, block: str) -> str | None: |
| 118 | """Replace from ``### Paste-ready prompt`` through the first fenced code block close.""" |
| 119 | lines = text.splitlines(keepends=True) |
| 120 | start: int | None = None |
| 121 | fence_open = False |
| 122 | end: int | None = None |
| 123 | for index, line in enumerate(lines): |
| 124 | if start is None: |
| 125 | if line.strip().startswith("### Paste-ready prompt"): |
| 126 | start = index |
| 127 | continue |
| 128 | stripped = line.strip() |
| 129 | if not fence_open and stripped.startswith("```"): |
| 130 | fence_open = True |
| 131 | continue |
| 132 | if fence_open and stripped.startswith("```"): |
| 133 | end = index + 1 |
| 134 | break |
| 135 | if start is None or end is None: |
| 136 | return None |
| 137 | prefix = "".join(lines[:start]) |
| 138 | suffix = "".join(lines[end:]) |
| 139 | body = block if block.endswith("\n") else block + "\n" |
| 140 | return prefix + body + suffix |
File History
2 commits
sha256:a78e7e5a8740e03315f325d19edeb3aa1b306b3337d04abbaa9a9e0f3bbeb7a1
docs: MuseHub-first before ISR #74 — staging solidify NEXT
Human
13 hours ago
sha256:4671b7f787ddbe63ced31c895b688c77ab495653b65a730b423329f26b3c1439
feat: K1-P1 complete — agent provenance, build-verification…
Sonnet 4.6
patch
52 days ago