import io
import re

R = r"C:\Users\decid\Documents\projects\spt-claude-code"

# ---------------------------------------------------------------- 1. brief: marker goes at the TOP
bp = R + r"\adapter\strings\briefs\live-ops.md"
b = io.open(bp, encoding="utf-8").read()
old = """  commune WITH the wake marker, not a plain commune.** Embed the literal `!!wake!!` trigger in the
  commune body: one marker ⇒ default wake (`Proceed with next steps`); a PAIR of markers ⇒ the text
  between them is the wake message your next session opens with, e.g. `!!wake!! Resume T2c: wire the
  branch. !!wake!!`."""
new = """  commune WITH the wake marker, not a plain commune.** Embed the literal `!!wake!!` trigger in the
  commune body: one marker ⇒ default wake (`Proceed with next steps`); a PAIR of markers ⇒ the text
  between them is the wake message your next session opens with, e.g. `!!wake!! Resume T2c: wire the
  branch. !!wake!!`. **Put it in the first few lines, not at the end** — spt files the commune away
  within a few seconds, and what the adapter can read back after that is capped at the first ~16KB.
  A marker past that cut cannot be seen, so a long commune that trails its marker resets nothing."""
assert b.count(old) == 1
io.open(bp, "w", encoding="utf-8", newline="\n").write(b.replace(old, new))

# ---------------------------------------------------------------- 2. the live-agent skill string
lp = R + r"\adapter\strings\skills\live-normal.md"
l = io.open(lp, encoding="utf-8").read()
old_l = """the operator asks you to **commune across**, embed `!!wake!!` in the commune body — one marker ⇒
default wake; a PAIR ⇒ the text between them is your custom wake directive (the message your next
session opens with)."""
new_l = """the operator asks you to **commune across**, embed `!!wake!!` near the TOP of the commune body — one
marker ⇒ default wake; a PAIR ⇒ the text between them is your custom wake directive (the message your
next session opens with). Near the top matters: only the first ~16KB is readable back once spt has
filed the commune, so a trailing marker in a long commune resets nothing."""
assert l.count(old_l) == 1
io.open(lp, "w", encoding="utf-8", newline="\n").write(l.replace(old_l, new_l))

# ---------------------------------------------------------------- 3. the refusal names the real fix
hp = R + r"\tools\claude-spt\src\hook.rs"
h = io.open(hp, encoding="utf-8").read()
old_h = """            // REFUSE, LOUDLY, RATHER THAN GUESS. The payload is capped at the 16KB class and the
            // wake marker conventionally sits at the END of a commune — exactly what a cap cuts.
            // Arming anyway would clear a session on a commune that never asked for it; skipping
            // silently would drop a handoff. So: never clear on a guess, and say so. These frames
            // carry no `digest_seq` to follow (28 observed, none truncated, none carrying one), so
            // there is no recovery path adapter-side; that gap is filed upstream.
            env.log(&format!(
                "claude-spt hook: commune frame for {id} is TRUNCATED ({} bytes visible) and carries no digest_seq to follow — cannot tell whether it is an across-commune, so NO boundary is armed. If this was a commune-across, re-issue it or clear manually",
                payload.len()
            ));"""
new_h = """            // REFUSE, LOUDLY, RATHER THAN GUESS — and the refusal names a fix the author can act
            // on. The payload is capped at the 16KB class, HEAD-KEPT, so a marker inside the first
            // 16KB is always visible and only a trailing one is lost. Arming blind would clear a
            // session on a commune that never asked for it; skipping silently would drop a handoff.
            //
            // There is no recovery path once the body is cut: `digest_seq` has NEVER been emitted
            // for ANY io kind (doyle, measured in core rather than read off the page — the api.md
            // example showing one describes a reserved, unset pointer), and an ingested commune
            // writes no digest entry at all, so after ingest the body lives only in the tracked
            // mind. The durable fix is ours and needs no core change: the wake marker is
            // claude-spt's own convention — core parses it nowhere — so the briefs now tell agents
            // to put it near the TOP of the commune, where the cap cannot reach it.
            env.log(&format!(
                "claude-spt hook: commune frame for {id} is TRUNCATED ({} bytes visible, head kept) — the wake marker, if any, sits past the cut, so NO boundary is armed rather than one guessed. Put the marker in the commune's first few lines and this cannot recur; for THIS commune, re-issue it or clear manually",
                payload.len()
            ));"""
assert h.count(old_h) == 1
io.open(hp, "w", encoding="utf-8", newline="\n").write(h.replace(old_h, new_h))

# ---------------------------------------------------------------- 4. version + history
mp = R + r"\adapter\claude-spt.toml"
m = io.open(mp, encoding="utf-8").read()
assert m.count('version = "0.38.0"') == 1
hist = """# 0.38.1 (2026-09-06): CLOSES 0.38.0's KNOWN LIMITATION WITH NO CORE CHANGE. doyle measured the two
# facts that decide it (in core, not off the page): the frame payload is HEAD-truncated, and
# `digest_seq` has NEVER been emitted for any io kind — the api.md example showing one describes a
# reserved, unset pointer, and an ingested commune writes no digest entry at all, so a cut body is
# unrecoverable. But the wake marker is OUR convention (core parses it nowhere, by ruling), so where
# it sits is ours to move: the briefs now tell agents to put `!!wake!!` in the commune's first few
# lines, where a 16KB head-keeping cap cannot reach it. A >16KB across-commune then arms correctly
# from the frame we already have. The truncation refusal now names that fix instead of only naming
# the manual recovery. Text-only + one log line; floor unchanged 0.66.0, skeleton unchanged 0.1.18.
version = "0.38.1\""""
io.open(mp, "w", encoding="utf-8", newline="\n").write(m.replace('version = "0.38.0"', hist, 1))

# ---------------------------------------------------------------- 5. changelog
cp = R + r"\CHANGELOG.md"
c = io.open(cp, encoding="utf-8").read()
anchor = "## [0.38.0] - 2026-09-06"
assert c.count(anchor) == 1
entry = """## [0.38.1] - 2026-09-06

> Requires spt-core **v0.66.0 or newer** (unchanged). Update with `spt adapter update claude-spt`. Plugin skeleton **0.1.18** (unchanged — the revised briefing text ships with the adapter).

### Fixed
- **The size limit noted in 0.38.0 no longer costs you a reset.** Agents are now briefed to put the wake marker in the commune's first few lines rather than at the end, which keeps it inside the part that stays readable after spacetime files the commune away. A long commune-across now resets reliably. If a commune does arrive with its marker past that point, the adapter still refuses to guess, and its log now tells the author how to avoid it rather than only how to recover.

"""
c = c.replace(anchor, entry + anchor, 1)
io.open(cp, "w", encoding="utf-8", newline="\n").write(c)
heads = re.findall(r"^## \[(\d+\.\d+\.\d+)\]", c, re.M)
assert heads[:3] == ["0.38.1", "0.38.0", "0.37.0"], heads[:5]
print("ok, headings:", heads[:4])
