# Phase 34: Version-Change Changelog - Context

**Gathered:** 2026-05-17
**Status:** Ready for planning

<domain>
## Phase Boundary

A single end-of-turn `Stop` hook prompt that surfaces an old→new changelog excerpt to the user the first time the agent finishes a turn after the plugin version has changed underneath them. Scope:

1. **Sentinel + version-mismatch detection** — `$SPT_HOME/last-seen-version.json` (sibling to `owlery/`, NOT inside it) stores the most recently seen version. `hook_idle.rs` (Stop hook) compares `env!("CARGO_PKG_VERSION")` against the sentinel after perch resolution; on mismatch, emits a `<spt-version-changelog>` block via `decision:"block"` + `reason:"..."` and atomically rewrites the sentinel at the same site.
2. **Repo-root `CHANGELOG.md`** — created from git-log history at Plan 01 start in Keep-a-Changelog format (`## [X.Y.Z] - YYYY-MM-DD`). DEPLOY.ps1 `-Bump` appends a stub H2 without committing and refuses to deploy until author fills + commits.
3. **DEPLOY.ps1 sync** — copies repo-root `CHANGELOG.md` into `{Marketplace}/plugin/spt/CHANGELOG.md` alongside the existing binary/skills/hooks/plugin.json sync step. Hook resolves at runtime via `$CLAUDE_PLUGIN_ROOT/CHANGELOG.md`.
4. **Block payload + AskUserQuestion shape** — Rust hook parses CHANGELOG.md once, builds `{old, new, old_date, new_date, step_count}` payload, embeds in block. Claude renders a 4-option AskUserQuestion (`Yes, full changelog` / `Yes, highlights only` / `Remind me later` / `Skip`); on `Remind me later` invokes new `$OWL version-remind` subcommand that rolls the sentinel back to `old`, re-arming the next-Stop emission.
5. **Hook-pivot triggers REQUIREMENTS.md + ROADMAP.md amendment** (Plan 01 first commit, mirrors Phase 31 D-11 / Phase 33 D-03/D-04 pattern) — strikes the SessionStart-entry assumption baked into VERS-02, VERS-04, VERS-06, VERS-08, VERS-09 + ROADMAP.md success criteria 1, 2, 5.

Touches:
- `src/owl/hook_idle.rs` — version-mismatch detection + emission + sentinel atomic write at top of `run()`, after perch resolution and before `set_idle_ready` / `spawn_echo_commune_if_live`. Recursion guard: skip when `OWL_ECHO_COMMUNE` set (mirror line 64-69).
- New `src/owl/version_changelog.rs` (or co-located in `hook_idle.rs`) — sentinel read/write helpers, CHANGELOG.md parser (regex `^## \[(\d+\.\d+\.\d+)\] - (\d{4}-\d{2}-\d{2})$`), payload builder.
- New bin subcommand `owl version-remind <old>` — atomically writes `{"version":"<old>"}` back to the sentinel using existing `atomic_write_string`. ~10-line implementation.
- New repo-root `CHANGELOG.md` — Keep-a-Changelog format, authored by hand at Plan 01 from `git log --oneline --grep='chore: bump'`.
- `docs/DEPLOY.ps1` — (a) new sync step copying `CHANGELOG.md` to marketplace, (b) `-Bump` extension that appends stub H2 to repo-root CHANGELOG.md uncommitted and aborts on unchanged-but-required content.
- `.planning/REQUIREMENTS.md` — amend VERS-02, VERS-04, VERS-06, VERS-08, VERS-09 per decisions D-01..D-09.
- `.planning/ROADMAP.md` Phase 34 — revise success criteria 1, 2, 5 in lockstep with REQUIREMENTS.md.

No new on-disk artifacts beyond the sentinel + CHANGELOG.md. No new info.json fields. No skill surface (no `/spt:changelog` skill — block-body inline instructions drive Claude through the AUQ + remind flow).

</domain>

<decisions>
## Implementation Decisions

### Hook Pivot (architectural foundation)

- **D-01: Stop hook (`hook_idle.rs`), not SessionStart.** Surfacing the version-change prompt at end-of-turn lands the AskUserQuestion exactly when the agent is between tasks. Rationale: a SessionStart or UserPromptSubmit prompt risks the user issuing "go do X" → walking away → AskUserQuestion blocking the agent → returning many minutes later to find zero progress. Stop hook guarantees the question fires after the agent's current work is complete and before any new turn starts. This invalidates VERS-02's "On SessionStart (in `plugin_session_start.rs`)" wording — amendment scope below.

- **D-02: Predicate = `owl_id` resolves in `hook_idle.rs::run()`.** Piggybacks on the existing perch-resolution gate at hook_idle.rs:25-32. If no perch matches `OWL_SESSION_ID` or `parent_pid`, hook early-returns and the version-change check never runs — exactly the "spt is in use" semantic the user requested (`spt listen` / `spt live` / `spt revive` all create perches; live-agent sessions are perches). No new predicate code; no new info.json field.

- **D-03: Recursion guard reuses `OWL_ECHO_COMMUNE` env-var skip** (hook_idle.rs:64-69 pattern). Haiku echo-commune subprocesses MUST NOT see a version-change block. New version-changelog code MUST run after the OWL_ECHO_COMMUNE check or be itself guarded equivalently.

### Block Emission + Sentinel Write Site

- **D-04: Block emission via `decision:"block"` + `reason:"<spt-version-changelog>...</spt-version-changelog>"`** at the top of `hook_idle.rs::run()`, immediately after perch resolution and the OWL_ECHO_COMMUNE guard, before `set_idle_ready` / `spawn_echo_commune_if_live`. Block content is the rendered XML payload (D-05). Stop hook's `decision:"block"` + `reason` schema is the natural injection channel for Stop (which has no `hookSpecificOutput.additionalContext` per the existing hook_idle.rs:1-5 comment) — research flag: confirm Stop's `reason` text reaches Claude as injected context on re-engagement, not as a silent abort. See Research Flags below.

- **D-05: Sentinel write site = same `run()` top, BEFORE returning the block.** Atomic via existing `atomic_write_string` (src/common/owlery.rs:248). Default behavior: writes the NEW version. Re-fire on next Stop only happens if the user picks `Remind me later`, which calls `$OWL version-remind <old>` to roll the sentinel back. This inverts the "ack on yes/skip" model: the hook commits sentinel pre-emission; only the explicit remind path rolls back. Rationale (per user): most-used paths (Yes-full / Yes-highlights / Skip) have zero UX bloat; the less-common Remind path pays the round-trip.

- **D-06: Fire-once cadence = once per version transition.** With D-05 sentinel pre-write in place, the next Stop in the same session sees `sentinel == CARGO_PKG_VERSION` and emits nothing. Matches VERS-05's "does not re-fire next session" intent — also doesn't re-fire next Stop in same session.

### Block Payload + AskUserQuestion Shape

- **D-07: Payload shape = `{old, new, old_date, new_date, step_count}`.** Embedded JSON-ish inside the `<spt-version-changelog>` block. Dates parsed from CHANGELOG.md H2 lines (Keep-a-Changelog `## [X.Y.Z] - YYYY-MM-DD` regex). `step_count` = number of H2 sections between `old` (exclusive) and `new` (inclusive). Surfaces in the AUQ question text (`SPT plugin updated v{old} → v{new} ({step_count} version{s}). Print changelog?`); does NOT change the option set.

- **D-08: AskUserQuestion 4-option shape:** `Yes, full changelog` / `Yes, highlights only` / `Remind me later` / `Skip`.
  - `Yes, full changelog` — Claude prints ALL H2 sections from CHANGELOG.md where `old < v ≤ new`, verbatim markdown, concatenated. Single-step and multi-step rendered identically (overrides VERS-06's "never full prose for each interim version" constraint — see amendment scope D-09).
  - `Yes, highlights only` — Claude's discretion: extracts the most impactful feature adds + fixes from the same range, prose-style. No fixed rubric; agent uses judgment. Pointer to `$CLAUDE_PLUGIN_ROOT/CHANGELOG.md` appended for users who want full detail.
  - `Remind me later` — Claude invokes `$OWL version-remind <old version>` (e.g. `$OWL version-remind 1.10.9`), passing the `old` value from the block payload as a literal positional arg. `$OWL` is the env-var-injected owl-binary path (matches existing `$OWL deliver` / `$LIVE pick-spec` conventions across `/spt:*` skills). The subcommand rewrites `$SPT_HOME/last-seen-version.json` to `{"version":"<old version>"}` so the next Stop hook re-detects mismatch and re-fires the block. No body rendered. **The block body MUST surface the exact `$OWL version-remind <old>` invocation string verbatim** so Claude has no ambiguity about which arg to pass — Claude cannot infer `<old>` from anything other than the block payload it just received.
  - `Skip` — no action. Sentinel already updated by hook at run-entry; no re-fire.

### CHANGELOG.md Genesis + Maintenance

- **D-09: Repo-root `CHANGELOG.md` authored one-shot at Plan 01 start.** Plan 01 step 1 (after the REQUIREMENTS.md/ROADMAP.md amendment commit): author CHANGELOG.md by hand using `git log --oneline --grep='chore: bump'` as the version-bump skeleton. Curated prose per version, Keep-a-Changelog format. One commit; never auto-regenerated. No new `owl changelog backfill` subcommand — out of scope.

- **D-10: Forward maintenance = DEPLOY.ps1 `-Bump` stub injection + commit gate.** When `-Bump patch|minor|major` runs, DEPLOY.ps1 appends a stub `## [NEW_VERSION] - YYYY-MM-DD\n\n- TODO: changelog entry\n` to repo-root CHANGELOG.md **without committing**. Outputs an explicit notice that the user must fill + commit before deploy will succeed. On the next `-Bump` invocation: if CHANGELOG.md still contains the TODO marker for `NEW_VERSION` (or is otherwise unchanged from prior commit), DEPLOY aborts with the same notice. Combines stub-injection (option 1) with the "abort if unchanged" gate (option 2) — guaranteed CHANGELOG.md update before any bump can ship.

- **D-11: DEPLOY.ps1 sync target = `{Marketplace}/plugin/spt/CHANGELOG.md`** (sibling to `plugin.json`, NOT inside `.claude-plugin/`). Resolves at hook time as `$CLAUDE_PLUGIN_ROOT/CHANGELOG.md`. Add to DEPLOY.ps1's sync step alongside `owl.exe` + `skills/` + `hooks/` + `plugin.json`. Mirrors `psyche.md` placement convention (plugin root, not subdir).

### REQUIREMENTS.md + ROADMAP.md Amendment Scope (Plan 01 first commit)

- **D-12: Amend VERS-02, VERS-04, VERS-06, VERS-08, VERS-09 + ROADMAP.md success criteria 1, 2, 5.** Mirrors Phase 31 D-11 and Phase 33 D-03/D-04 amendment-commit pattern.
  - **VERS-02:** "On SessionStart (in `plugin_session_start.rs`)" → "On Stop hook (in `src/owl/hook_idle.rs`), gated on active perch resolution via existing `find_perch_by_session` / `find_perch_by_parent_pid`. Hook compares `env!("CARGO_PKG_VERSION")` (compiled-in) against `last-seen-version.json`. NEVER reads runtime `plugin.json`." (compiled-in source clause preserved)
  - **VERS-04:** yes/no/dismiss → "AskUserQuestion with four options: `Yes, full changelog` / `Yes, highlights only` / `Remind me later` / `Skip`. Question text includes `step_count` when > 1."
  - **VERS-06:** drop "never full prose for each interim version" constraint. New wording: "Multi-version skip surfaces the same 4-option AskUserQuestion as single-step; user-chosen `Yes, full changelog` renders all interim H2 sections verbatim, `Yes, highlights only` defers to agent discretion. The CHANGELOG.md pointer is appended to the highlights body."
  - **VERS-08:** AUTO-03 (SessionStart) and VERS-04 (Stop) fire on different hooks; coexistence is enforced by hook-separation, not allow-list overlap. New wording: "Version-change prompt (VERS-04) fires from Stop hook and SessionStart auto-pick (AUTO-03 from Phase 33) fires from SessionStart hook. The two never share a hook invocation; no allow-list reconciliation required."
  - **VERS-09:** "single write site at the top-level SessionStart entry... no in-handoff trampoline writes" → "Single write site at the top of `hook_idle.rs::run()` after perch resolution + OWL_ECHO_COMMUNE guard. Atomic via existing `atomic_write_string`. Binary handoff (Phase 18.4/18.5) does not race because Stop hooks do not fire during in-flight binary swap (Stop fires between turns, handoff happens between processes)."
  - **ROADMAP.md SC1:** "On a fresh install, SessionStart silently writes..." → "...on first Stop hook fire in any spt-bearing session, sentinel silently writes..."
  - **ROADMAP.md SC2:** "next SessionStart emits..." → "next Stop hook fire in an spt-bearing session emits..."
  - **ROADMAP.md SC5:** drop coexistence language → "VERS-04 fires from Stop; AUTO-03 fires from SessionStart; collision impossible by hook separation."
  - Plan 01 begins with this amendment commit before any Rust code lands.

### Claude's Discretion

- **Stop hook `decision:"block"` + `reason` injection semantics** — Confirm via Claude Code hooks reference (`~/.claude/reference_docs/claude-code-hooks.md`) that the `reason` text in a blocked Stop response reaches Claude as injected context on the next turn re-engagement. The hook_idle.rs:1-5 comment notes Stop "has no hookSpecificOutput schema" — but `decision:"block"` is a separate output shape that may carry user-visible context. Flag for research phase. If `decision:"block"` does NOT propagate text to Claude usefully, fall back: emit via PreToolUse (next tool call) or revisit UserPromptSubmit despite the earlier "walk-away" concern.
- **`<spt-version-changelog>` block exact XML shape** — child elements vs JSON-as-text payload vs hybrid. Locked decisions: must carry `old`, `new`, `old_date`, `new_date`, `step_count`. Open: also embed full CHANGELOG.md excerpt for `old < v ≤ new`, or have Claude read `$CLAUDE_PLUGIN_ROOT/CHANGELOG.md` at AUQ-yes time? D-05 sentinel-pre-write removes the "block must be self-contained" pressure; either is workable. Planner / researcher decides — leaning toward Claude reads at yes-time so block payload stays small.
- **`version-remind` subcommand exact signature** — Locked: `$OWL version-remind <old version>` (bare positional arg, e.g. `$OWL version-remind 1.10.9`). Block-body MUST surface this exact invocation string with the `old` value substituted so Claude passes it verbatim; the subcommand cannot infer the rollback target from any other source.
- **CHANGELOG.md parser regex tightness** — Keep-a-Changelog allows `## [Unreleased]` and link references (`[1.10.10]: <url>`). The phase 34 parser SHOULD reject `[Unreleased]` (no date, not a real release) and ignore link reference lines (not H2 markers — they're `[ref]:` form). Add to RESEARCH.md test corpus.
- **Highlights extraction policy** — D-08 says "Claude's discretion". Skill-body wording (or block-body wording, since no skill is planned) should give Claude enough framing: "Pick 2-5 most impactful feature adds and fixes across the range; prefer items that change user-facing behavior; skip purely-internal refactors and test-only changes". Final wording in Plan 02 or wherever the block-body authoring lives.
- **Plan split** — likely 3 plans: (1) REQUIREMENTS.md/ROADMAP.md amendment + CHANGELOG.md genesis + DEPLOY.ps1 sync/stub, (2) Rust hook + sentinel + parser + `version-remind` subcommand + tests, (3) block-body Claude instructions + integration test on a synthesized version transition. Final boundary at planner's discretion.
- **Tests** — version-mismatch detection (single-step, multi-step, first-install silent, sentinel-equal-no-emit, OWL_ECHO_COMMUNE guard, no-perch guard); CHANGELOG.md parser (Keep-a-Changelog happy path, `[Unreleased]` reject, malformed H2 graceful); `version-remind` rollback round-trip; DEPLOY.ps1 stub injection + abort-on-unchanged.

</decisions>

<canonical_refs>
## Canonical References

**Downstream agents MUST read these before planning or implementing.**

### Phase requirements + planning artifacts
- `.planning/REQUIREMENTS.md` VERS-01..VERS-09 — 9 requirements. **VERS-02, VERS-04, VERS-06, VERS-08, VERS-09 to be amended per D-12.** Plan 01 begins with the amendment commit.
- `.planning/ROADMAP.md` Phase 34 — goal + 6 success criteria. **Success criteria 1, 2, 5 to be revised in lockstep with REQUIREMENTS.md amendment.** Success criteria 3, 4, 6 unchanged.

### Cross-references to prior phase decisions
- `.planning/phases/33-fresh-start-commune-auto-resume/33-CONTEXT.md` — Phase 33 D-11 amendment-commit pattern (Plan 01 mirrors this for D-12). AUTO-03 SessionStart emission pattern (`emit_auto_pick` at plugin_session_start.rs:139-155) — informational; Phase 34 does NOT emit from SessionStart but the Pattern S2 envelope shape is reusable for any future SessionStart additions. Phase 33 D-04 / FRESH-06 rewording precedent for VERS-08's coexistence-by-hook-separation rewording.
- `.planning/phases/31-picker-correctness/31-CONTEXT.md` D-11 — doc-amendment-first plan structure (Plan 01 amends REQUIREMENTS.md + ROADMAP.md before any code).
- `.planning/phases/32-list-overhaul-skill-hint-audit/32-CONTEXT.md` — DEPLOY.ps1 multi-file sync pattern (informational; Phase 34 extends the sync step).

### Stop hook surface (D-01..D-06)
- `src/owl/hook_idle.rs:14-43` — `run()` entry. Version-change emission site is AFTER perch resolution (lines 25-32) and AFTER the OWL_ECHO_COMMUNE guard (mirror lines 64-69 — guard MUST run before any version-change work), BEFORE `set_idle_ready` (line 39) and `spawn_echo_commune_if_live` (line 42).
- `src/owl/hook_idle.rs:1-5` — comment asserts "Stop hooks have no hookSpecificOutput schema". This is research-relevant: Phase 34 uses `decision:"block"` + `reason` instead, which is a different output channel. Confirm semantics against `~/.claude/reference_docs/claude-code-hooks.md` during research.
- `src/owl/hook_idle.rs:64-69` — `OWL_ECHO_COMMUNE` recursion guard. Reuse pattern verbatim for the version-change code path.

### Claude Code hooks reference (D-04 research target)
- `~/.claude/reference_docs/claude-code-hooks.md` — Stop hook output schema. Confirm `decision:"block"` + `reason` propagates `reason` text to Claude as re-engagement context (not as a silent abort).

### Atomic write + sentinel path (VERS-01, D-05)
- `src/common/owlery.rs:248` — `atomic_write_string(path, body)` (tmp + rename). Use verbatim for both Stop-hook sentinel write (D-05) and `version-remind` rollback write.
- `src/common/owlery.rs:18-32` — `spt_home()` resolves `$SPT_HOME` env override or platform default. Sentinel lives at `spt_home().join("last-seen-version.json")` — sibling to `spt_home().join("owlery")` per VERS-01.

### Compiled-in version source (VERS-02)
- `src/owl/poll.rs:75`, `src/owl/setup.rs:35`, `src/live/stop.rs:199` — existing call sites for `env!("CARGO_PKG_VERSION")`. Pattern for the Phase 34 hook: same `env!` macro at compile time; no runtime `plugin.json` read.

### CHANGELOG.md format + parser surface (D-08, D-09, D-11)
- `https://keepachangelog.com/en/1.1.0/` — Keep-a-Changelog v1.1.0 spec. Reference for `## [X.Y.Z] - YYYY-MM-DD` H2 shape. Phase 34 parser regex: `^## \[(\d+\.\d+\.\d+)\] - (\d{4}-\d{2}-\d{2})$`. Reject `[Unreleased]`; ignore link reference lines.
- `psyche.md` (repo root, embedded via `include_str!`) — placement precedent for repo-root markdown files synced to plugin meta dir. Phase 34's CHANGELOG.md follows this placement convention but is NOT embedded (D-11: runtime-read from `$CLAUDE_PLUGIN_ROOT/CHANGELOG.md`).

### DEPLOY.ps1 surfaces (D-10, D-11)
- `docs/DEPLOY.ps1:154-176` — plugin.json version resolution + `-Bump` entry. Stub-injection logic (D-10) hooks in alongside the existing Cargo.toml sync at lines 224-269.
- `docs/DEPLOY.ps1:171-292` — `-Bump` block. New CHANGELOG.md stub-append + unchanged-abort gate slots in after the Cargo.toml/plugin.json bump but before commit (lines 277-287).
- `docs/DEPLOY.ps1` sync step (post-build) — exact lines vary; CHANGELOG.md sync target is `{Marketplace}/plugin/spt/CHANGELOG.md` per D-11.

### Existing version-text precedent
- `src/owl/poll.rs:75` — `READY:{id} (spt v{})` format. Informational only; Phase 34 prompt text uses `v{old} → v{new}` shape from D-07/D-08.

### AskUserQuestion native "Other" field
- AskUserQuestion automatically provides an "Other" free-text option. Phase 34 D-08's 4-option set explicitly chooses NOT to use "Other" because Remind / Skip already cover the non-print branches. If a user provides freeform input, Claude should map continuation phrases ("not now", "later", "skip for now") to `Remind me later` and dismissal phrases ("ignore", "stop asking") to `Skip` — matches Phase 33 D-08 / advisor mode pattern.

</canonical_refs>

<code_context>
## Existing Code Insights

### Reusable Assets
- **`atomic_write_string`** (src/common/owlery.rs:248) — tmp + rename atomic write. Used verbatim for sentinel writes from both Stop hook (D-05) and `version-remind` subcommand.
- **`spt_home()` + `owlery_dir()`** (src/common/owlery.rs:18-32, 65) — already resolve `$SPT_HOME` / platform default. Sentinel path = `spt_home().join("last-seen-version.json")`. No new path helper needed.
- **`hook_output::parse_hook_stdin` + `find_perch_by_session` + `find_perch_by_parent_pid` + `find_perch_cached`** — perch-resolution path already proven across `hook_idle.rs`, `hook_prompt.rs`, `hook_check.rs`. Phase 34 reuses verbatim.
- **`env!("CARGO_PKG_VERSION")`** — compile-time version macro already used in 3 existing call sites (poll.rs, setup.rs, stop.rs). Identical usage for Phase 34's hook check.
- **`OWL_ECHO_COMMUNE` recursion guard** (hook_idle.rs:64-69) — copy/paste pattern for the version-change code path. Haiku subprocesses MUST NOT see a version-change block.
- **AskUserQuestion native "Other"** — Phase 34 deliberately ignores; 4-option set is complete.
- **DEPLOY.ps1 `-Bump` Cargo.toml/plugin.json sync** (DEPLOY.ps1:224-269) — atomic-rewrite-then-verify pattern. CHANGELOG.md stub-injection (D-10) follows the same regex-replace-then-verify shape.

### Established Patterns
- **Doc-amendment-first plan structure** (Phase 31 Plan 01, Phase 32 Plan 01, Phase 33 Plan 01): when a phase's discovered design contradicts a requirement-as-written, Plan 01's first commit amends REQUIREMENTS.md + ROADMAP.md before any code. Phase 34 follows this for D-12.
- **Pattern S2: hookSpecificOutput envelope** (plugin_session_start.rs:139-155 `emit_auto_pick`) — informational only for Phase 34; Stop uses `decision:"block"` + `reason` instead. Confirmation needed during research.
- **`include_str!` precedent for repo-root markdown** (psyche.md): Phase 34 chooses NOT to follow this for CHANGELOG.md (per D-11: runtime-read from `$CLAUDE_PLUGIN_ROOT/CHANGELOG.md` instead). Rationale: changelog grows monotonically; binary should not bloat with history.
- **Sync-then-verify in DEPLOY.ps1** (DEPLOY.ps1:256-273): each file write verifies via re-read. CHANGELOG.md sync follows the same pattern.
- **Schema additivity** (Phase 26 PLAN-OVERVIEW Q4, Phase 31 D-12, Phase 33): no new info.json fields; no new on-disk artifacts beyond the sentinel; the `version-remind` subcommand is purely additive.

### Integration Points
- **`src/owl/hook_idle.rs::run()`** — version-change emission lands between perch resolution (lines 25-32) and `set_idle_ready` (line 39). OWL_ECHO_COMMUNE guard MUST precede any new logic.
- **`docs/DEPLOY.ps1` `-Bump` flow** — CHANGELOG.md stub-injection + unchanged-abort gate inserts after Cargo.toml verification (line 238), before the regex-replace block (line 247).
- **`docs/DEPLOY.ps1` sync step** — `{Marketplace}/plugin/spt/CHANGELOG.md` copy slots alongside the existing binary/skills/hooks/plugin.json sync.
- **`Cargo.toml` `[[bin]]` table** — new `version-remind` subcommand attaches as a `clap` subcommand on the existing `owl` binary; no new binary target.
- **`tests/`** — version-change detection tests live in a new `tests/version_changelog.rs` integration test file (mirrors `tests/skill_hints.rs` from Phase 32 regression-guard pattern). Unit tests for the CHANGELOG.md parser co-locate with `src/owl/version_changelog.rs` (or wherever the parser lands).

</code_context>

<specifics>
## Specific Ideas

- **Sentinel JSON shape (locked):** `{"version":"X.Y.Z"}`. Single field, no timestamp, no plugin name. Minimal for `serde_json::from_str` round-trip. Path: `$SPT_HOME/last-seen-version.json` (sibling to `owlery/` per VERS-01).
- **CHANGELOG.md H2 regex (locked):** `^## \[(\d+\.\d+\.\d+)\] - (\d{4}-\d{2}-\d{2})$`. Case-sensitive. Reject `## [Unreleased]` (no date matched). Ignore link reference lines (start with `[` not `## [`).
- **Prompt question text template:**
  - Single-step: `SPT plugin updated v{old} → v{new}. Print changelog?`
  - Multi-step: `SPT plugin updated v{old} → v{new} ({step_count} versions). Print changelog?`
  - Dates surface in the AUQ description / Claude's pre-question framing, not in the question line itself.
- **4-option labels (locked, verbatim per D-08):**
  - `Yes, full changelog`
  - `Yes, highlights only`
  - `Remind me later`
  - `Skip`
- **Highlights body framing (suggested wording for block-body or skill-body):** "Pick 2-5 most impactful feature adds and fixes across the range; prefer items that change user-facing behavior; skip purely-internal refactors and test-only changes. Append `See $CLAUDE_PLUGIN_ROOT/CHANGELOG.md for full detail.`"
- **`version-remind` subcommand signature (locked):** `$OWL version-remind <old version>` — bare positional arg (e.g. `$OWL version-remind 1.10.9`). Writes `{"version":"<old version>"}` to `$SPT_HOME/last-seen-version.json` via `atomic_write_string`. Exits 0 on success, 1 on missing arg or write failure. The `<old version>` arg is REQUIRED — the subcommand has no way to infer it; the block-body MUST instruct Claude to pass the literal `old` value from the block payload it received. `$OWL` is the env-var-injected owl-binary path per existing `/spt:*` skill conventions; subcommand also callable directly via `owl version-remind <old version>` for tests / scripted invocation.
- **DEPLOY.ps1 stub format (locked):** Appended block at end of repo-root CHANGELOG.md:
  ```markdown
  ## [{NEW_VERSION}] - {YYYY-MM-DD}

  - TODO: changelog entry
  ```
  Date = bump-time `Get-Date -Format 'yyyy-MM-dd'`. The literal `TODO: changelog entry` marker is what the unchanged-abort gate scans for on the next `-Bump` invocation.
- **DEPLOY.ps1 unchanged-abort message (suggested):** `ERROR: CHANGELOG.md contains 'TODO: changelog entry' for v{NEW_VERSION}. Fill in the entry and commit before re-running -Bump.`

</specifics>

<deferred>
## Deferred Ideas

- **`owl changelog backfill` subcommand** — would auto-generate CHANGELOG.md from `git log --grep='chore: bump'`. Rejected for Phase 34 per D-09 (one-shot manual author at Plan 01). Re-evaluate if a future fresh-fork bootstrap is needed.
- **`/spt:changelog` skill** — would own AUQ + render + remind flow as a skill body. Rejected for Phase 34 per overall scope (no new skill surface). Block-body inline instructions to Claude are sufficient. Re-evaluate if the inline-instructions pattern becomes brittle or if other phases want to compose with the changelog flow.
- **Sentinel with reminder flag** — alternative to `version-remind`-rolls-back: sentinel could carry `{"version":"X.Y.Z","reminder":bool}` and version-remind sets `reminder:true` while keeping the new version. Rejected for Phase 34 to keep sentinel shape minimal (D-07). Could reconsider if a future requirement needs richer sentinel metadata.
- **Telemetry for AUQ option distribution** — log how often each of the 4 options is picked. Useful for tuning highlights framing. Out of scope; no telemetry infrastructure in spt today.
- **`Yes, highlights only` rubric formalization** — current decision is "Claude's discretion". A future phase could specify an exact rubric (e.g., "include all `## Added`, `## Fixed` H3 subsections; exclude `## Changed (internal)`"). Requires CHANGELOG.md to use H3 subsections per Keep-a-Changelog v1.1.0 — out of scope for Phase 34's minimal H2-per-version format.
- **Block payload embedded full excerpt vs Claude-reads-at-yes-time** — D-04 / Claude's Discretion flagged this as planner choice. If planner picks embed-in-block, Phase 34 hook also parses the H2 sections during the hook run. Defer to RESEARCH.md.
- **PreToolUse fallback if Stop's `decision:"block"` + `reason` doesn't propagate text to Claude** — flagged as research blocker in D-04 / Claude's Discretion. If confirmed broken, escalate during planning.
- **UserPromptSubmit revisit** — the "walk-away mid-task" rationale (D-01) is the reason we pivoted from UserPromptSubmit to Stop. If a future requirement needs the prompt to land at session-start time (e.g., a critical security update notice), UserPromptSubmit becomes viable again with a different UX framing. Not in Phase 34.

</deferred>

---

*Phase: 34-version-change-changelog*
*Context gathered: 2026-05-17*
