---
phase: quick-260517-6om
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - src/owl/hook_idle.rs
  - src/owl/version_changelog.rs
  - src/cli.rs
  - tests/version_changelog.rs
  - tests/fixtures/version_changelog_block_reference.txt
autonomous: true
requirements:
  - HOTFIX-34-UAT-silent-transport
  - HOTFIX-34-UAT-drop-remind
---

<objective>
**⚠ PARTIAL REVERT 2026-05-20 (commit 49f7598):** the "deferred-spool" subpart
of the transport choice below is N/A. Stop hook now uses `send::deliver_body`
(TCP-first, spool fallback on non-deferred row) instead of
`spool_message_deferred`. Receiver-side urgency reverted in `5f39375`:
version-change owl message surfaces the HIGHEST PRIORITY banner.
The rest of the plan (owl-message envelope replacing the Stop-block JSON,
AUQ 3-option shape, `version-remind` deprecation) is still in force.
See `260517-6om-SUMMARY.md` top-of-file banner for full revert context.

---

Phase 34 UAT hotfix. The current Stop hook (Plan 34-02) emits the
`<spt-version-changelog>` XML as a `decision:"block"` reason, which Claude
Code surfaces as a "Stop hook blocking error" — user-visible noise. Additionally,
the AUQ's "Remind me later" option rolls the sentinel back, causing an
infinite-loop pathology when re-fire happens immediately on the next Stop.

This plan reworks the transport to an owl message delivered to Self's perch
(silent on stdout, surfaced via existing UserPromptSubmit spool drain) and
drops the Remind option from the AUQ entirely. `version-remind` subcommand is
deprecated (hidden from clap help, kept callable for any in-flight invocations).

Bumps v1.10.11 → v1.10.12 via DEPLOY.ps1 -Bump patch.

Purpose: close Phase 34 UAT regression. Output: silent Stop-hook transport,
3-option AUQ, deprecated `version-remind`, deployed plugin v1.10.12.
</objective>

<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/STATE.md
@.planning/phases/34-version-change-changelog/34-CONTEXT.md
@.planning/phases/34-version-change-changelog/34-02-SUMMARY.md
@.planning/phases/34-version-change-changelog/34-03-SUMMARY.md
@src/owl/hook_idle.rs
@src/owl/version_changelog.rs
@src/cli.rs
@tests/version_changelog.rs
@tests/fixtures/version_changelog_block_reference.txt
@CLAUDE.md

<interfaces>
<!-- Existing surfaces this plan reuses. Executor needs these and should NOT re-explore. -->

From src/owl/send.rs:
```rust
// Spool a message to a target perch (no TCP wake; visible to peek_all /
// drain_all / mark_delivered — UserPromptSubmit hook surfaces via
// <owl_messages> injection).
pub fn deliver_body_deferred(target: &str, from: &str, body: &str);
// Direct TCP+spool delivery (wakes listener if connected). Use for live
// inter-agent messaging; NOT what we want for Self-perch hook emission.
pub fn deliver_body(target: &str, from: &str, body: &str);
// Anonymous (no __REPLY_TO__) — used for communes / INIT_SIGNOFF.
pub fn deliver_body_anonymous(target: &str, body: &str);
```

From src/common/spool.rs:
```rust
// Direct spool insert (target perch's spool.db) — bypasses send::deliver_body's
// TCP-first path. spool_message_deferred sets deferred=1 so the poll loop's
// idle-drain skips it; peek_all / drain_all / mark_delivered still see it.
pub fn spool_message(target_id, from_id, body, owlery) -> rusqlite::Result<()>;
pub fn spool_message_deferred(target_id, from_id, body, owlery) -> rusqlite::Result<()>;
```

From src/common/owlery.rs:
```rust
pub fn owlery_dir() -> PathBuf;     // spool DB lives under owlery_dir().join(target_id)
```

From src/owl/version_changelog.rs (current, will be amended in Task 1):
```rust
pub enum VersionPrompt { Emitted, NotEmitted }
pub fn build_block_reason(old, new, old_date, new_date, step_count, changelog_path_resolved) -> String;
pub fn maybe_emit_version_change_block() -> VersionPrompt;   // CURRENTLY println!s decision:block envelope
pub fn run_version_remind(old: &str) -> i32;                  // deprecated in Task 2
```

From src/owl/hook_idle.rs::run() (current call site, will be amended in Task 1):
- stdin parsed via hook_output::parse_hook_stdin → stdin.session_id
- session_id = OWL_SESSION_ID env > stdin.session_id
- owl_id resolved via find_perch_cached(&session_id) or find_perch_by_parent_pid()
- maybe_emit_version_change_block() called; on Emitted return early (skips set_idle_ready + spawn_echo_commune)

From src/live/wrapper/mod.rs:
```rust
// Predicate that classifies INIT_SIGNOFF wire envelopes. Returns true if the
// body lowercased contains <event type="init_signoff" — we MUST NOT compose
// a version-change body that matches this shape (regression 30c0144).
pub(crate) fn is_init_signoff_envelope(msg: &str) -> bool;
```
</interfaces>
</context>

<tasks>

<task type="auto">
  <name>Task 1: Silent owl-message transport + drop Remind option (Changes 1, 2)</name>
  <files>src/owl/hook_idle.rs, src/owl/version_changelog.rs</files>
  <action>
Rework the Stop-hook version-change emission so it sends an owl message to
Self's perch instead of writing a `decision:"block"` JSON envelope to stdout.
Simultaneously drop the "Remind me later" AUQ option (infinite-loop risk
confirmed in Phase 34 UAT).

CHANGE A — `src/owl/version_changelog.rs`:

1. `build_block_reason(...)` modifications:
   - Drop the `<invocation_for_remind>` XML element from BOTH the resolved
     and degraded `instructions` branches AND from the final XML scaffold
     (the trailing `format!("<spt-version-changelog>...")` block at lines ~391-403).
   - Update the doc-comment listing element ordering to:
     `<old>` → `<new>` → `<old_date>` → `<new_date>` → `<step_count>` →
     `<changelog_path>` → `<instructions>`  (no `<invocation_for_remind>`).
   - In `instructions` (BOTH resolved + degraded branches): remove option 3
     ("Remind me later") and renumber Skip from 4 → 3. AUQ now has THREE options:
     1. "Yes, full changelog"
     2. "Yes, highlights only"
     3. "Skip"
   - Adjust the "Other" freeform mapping clause: drop the "not now"/"later"/
     "skip for now" → "Remind me later" mapping; replace with "all freeform
     continuation phrases (later / not now / ignore / stop asking / no) → Skip".
     Single mapping line in both branches.
   - REMOVE the `old` placeholder substitution from the
     `<invocation_for_remind>` arg position — but note `{old}` is still
     substituted via existing `format!` inside instructions text (e.g.
     "read $CLAUDE_PLUGIN_ROOT/CHANGELOG.md and print every H2 section where
     {old} < version <= {new}"). Those `{old}` / `{new}` substitutions stay.
   - Verify no test seam (`print_canonical_block_reason_for_reference`) breaks —
     it just prints the new payload.

2. Rework `maybe_emit_version_change_block` (around line 425) — KEY CHANGE:
   the function no longer writes to stdout. Rename it to make the new
   contract explicit and side-effect-only on the spool:
   - Keep the same name `maybe_emit_version_change_block` so callers don't
     churn, BUT change the signature to:
     `pub fn maybe_emit_version_change_block(target_owl_id: &str) -> VersionPrompt`
   - It still returns `VersionPrompt::Emitted` vs `NotEmitted` (the caller in
     hook_idle.rs still wants to know whether to short-circuit
     `spawn_echo_commune_if_live`).
   - On the Emitted path, INSTEAD OF the current `println!(...)` of the
     `{"decision":"block","reason":...}` envelope:
     a. Build the full `<spt-version-changelog>...</spt-version-changelog>`
        XML body via `build_block_reason(...)` (already in scope).
     b. Call `crate::common::spool::spool_message_deferred(target_owl_id,
        "version-change", &body, &crate::common::owlery::owlery_dir())`.
        Use `from_id = "version-change"` (informational pseudo-sender;
        matches the project convention for hook-originated messages).
        Use the `_deferred` variant so the running listener is NOT woken —
        the UserPromptSubmit hook drain will surface it on the next user
        turn (Phase 18.1 D-02 pattern; see CLAUDE.md "info priority for
        informational owl messages").
     c. On `spool_message_deferred` Err: log to stderr (`eprintln!`) one
        line — `version-change: failed to enqueue owl message: <err>` — and
        return `VersionPrompt::NotEmitted` (sentinel was already pre-written,
        so we never re-fire; the message is lost in this rare path,
        acceptable — surfacing on stderr is the diagnostic).
     d. On Ok: return `VersionPrompt::Emitted`.
   - INIT_SIGNOFF regression guard (commit 30c0144): the XML body starts
     with `<spt-version-changelog>` — the lowercased substring
     `<event type="init_signoff"` cannot appear inside it. Add a debug-only
     assertion at the bottom of the function before returning Emitted:
     `debug_assert!(!crate::live::wrapper::is_init_signoff_envelope(&body),
       "version-change body must not match INIT_SIGNOFF envelope shape (regression 30c0144)")`.
     This is a defense-in-depth tripwire; release builds drop it.

3. Update the unit tests in `src/owl/version_changelog.rs::tests`:
   - `build_block_reason_contains_all_required_elements`: REMOVE the
     `<invocation_for_remind>` assertion line.
   - `build_block_reason_single_step_resolved_branch_substitutes_correctly`:
     REMOVE the `<invocation_for_remind>`, `"Remind me later"`, and
     `$OWL version-remind 1.10.9` assertions. Keep the rest.
   - `build_block_reason_multi_step_substitutes_step_count`: REMOVE the
     `$OWL version-remind 1.10.5` assertion. Keep step_count assertions.
   - `build_block_reason_unresolved_changelog_uses_degraded_branch`: REMOVE
     the `"Remind me later"` and `$OWL version-remind 1.10.9` assertions.
     Keep the "not available" / "Do NOT attempt to read" markers.
   - `build_block_reason_unresolved_branch_does_not_instruct_read_for_yes_options`:
     no changes (still valid).
   - `build_block_reason_element_ordering`: REMOVE the `p_invoke` lookup
     and the `assert!(p_changelog < p_invoke)` / `assert!(p_invoke < p_instructions)`
     ordering checks. Replace with a single `assert!(p_changelog < p_instructions)`.
   - `build_block_reason_invocation_for_remind_is_verbatim_dollar_owl`:
     DELETE the entire test (the element no longer exists).
   - `version_prompt_compile_time_return_type_check`: update the `_take_fn`
     signature — `fn(&str) -> VersionPrompt` now takes a `&str` arg.
   - `current_version` / `is_plain_semver` / sentinel tests: no changes.
   - `run_version_remind_*` tests: no changes (subcommand still works,
     just deprecated; tests stay green as a regression guard for any
     in-flight invocations).

CHANGE B — `src/owl/hook_idle.rs`:

1. Update the call site at lines ~65-75 inside `run()`:
   - The local `owl_id: String` is already resolved by line 45 (the
     `match owl_id { Some(id) => id, None => return }` arm).
   - Change the match arm to pass it:
     ```
     match version_changelog::maybe_emit_version_change_block(&owl_id) {
         VersionPrompt::Emitted => return,
         VersionPrompt::NotEmitted => { /* fall through */ }
     }
     ```
   - Doc-comment refresh: update the file-level comment block at lines 7-17
     (`Phase 34 (D-04): also emits...`) to reflect the new transport:
     "Phase 34 + hotfix-260517-6om: on version mismatch the hook enqueues a
     `<spt-version-changelog>` owl message to Self's perch via
     `spool_message_deferred` instead of returning a `decision:"block"`
     envelope (which surfaced as a user-visible 'Stop hook blocking error').
     The deferred spool row is drained on the next UserPromptSubmit hook
     via the universal spool drain (Phase 18.1 D-02). On Emitted, run()
     still returns early — set_idle_ready + spawn_echo_commune_if_live
     are skipped (the Stop hook is now silent, but the early-return
     preserves the existing ordering invariant)."

Commit message (atomic, after build + green tests):
`fix(34): emit version-change as owl message instead of Stop block`

(Note: Change 2 — drop Remind option — is folded into this same commit per
the scope spec's "atomic commits per scope group" rule. Both changes touch
build_block_reason's two branches; splitting them mid-function is worse.)

A single commit covering BOTH Change 1 + Change 2 is correct here.
  </action>
  <verify>
    <automated>cargo build --release 2>&amp;1 | tail -20 &amp;&amp; cargo test --lib version_changelog 2>&amp;1 | tail -30</automated>
  </verify>
  <done>
- `cargo build --release` succeeds with zero warnings related to these files.
- `cargo test --lib version_changelog` all unit tests pass (after the
  Remind/invocation_for_remind assertion deletions).
- `grep -n 'invocation_for_remind' src/owl/version_changelog.rs` returns 0 hits.
- `grep -n 'Remind me later' src/owl/version_changelog.rs` returns 0 hits.
- `grep -n 'decision":"block"' src/owl/version_changelog.rs` returns 0 hits
  (no more JSON envelope construction).
- `grep -n 'spool_message_deferred' src/owl/version_changelog.rs` returns 1+ hits.
- `maybe_emit_version_change_block` signature now takes `&str` and the call
  site in `hook_idle.rs::run()` passes `&owl_id`.
- Atomic commit landed with message `fix(34): emit version-change as owl
  message instead of Stop block`.
  </done>
</task>

<task type="auto">
  <name>Task 2: Deprecate version-remind + regenerate integration tests + fixture (Changes 3, 4, 5)</name>
  <files>src/cli.rs, tests/version_changelog.rs, tests/fixtures/version_changelog_block_reference.txt</files>
  <action>
Hide the `version-remind` clap subcommand, rewrite the integration tests to
match the new silent-transport contract, and regenerate the canonical
reference fixture.

CHANGE A — `src/cli.rs`:

1. Find the `VersionRemind` variant at line 158-164. Modify the
   `#[command(...)]` attribute:
   - From: `#[command(name = "version-remind")]`
   - To:   `#[command(name = "version-remind", hide = true)]`
2. Update the doc-comment above the variant (line 155-157) to read:
   ```
   /// DEPRECATED (hotfix-260517-6om): 'Remind me later' AUQ option removed
   /// in v1.10.12 due to infinite-loop risk (sentinel rollback → next Stop
   /// fires immediately → loop). Subcommand retained for any in-flight
   /// invocations from older block payloads; do NOT advertise. Future
   /// cleanup: full removal in a later milestone.
   ```
3. In `src/owl/mod.rs` at line 132-137 (the `VersionRemind { old }` dispatch
   arm), no functional change is required — `run_version_remind` still does
   the same atomic sentinel write. Add a single inline comment above the
   arm: `// DEPRECATED — hidden from --help per hotfix-260517-6om; arm kept
   for backward compat with any in-flight invocations.`
4. The two unit tests at cli.rs:425-440 (`version_remind` parse round-trip,
   missing-arg rejection) stay — they're regression guards for the hidden
   subcommand.

CHANGE B — `tests/version_changelog.rs`:

This file has 13 integration tests. Most need surgical edits to drop the
`decision:"block"` assertion and replace it with an owl-message-on-spool
assertion. Common pattern across tests 2, 3, 11, 13:

OLD invariant (DELETE):
```rust
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("\"decision\":\"block\""), ...);
assert!(stdout.contains("<old>1.10.9</old>"), ...);
```

NEW invariant (ADD):
```rust
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.trim().is_empty(),
    "Stop hook must produce no stdout (transport changed to spool); got: {}", stdout);
// Verify owl message was enqueued to Self's perch spool.
let owl_id = "test-owl";  // (or "reference-perch" / "test-live" per test)
let spool_path = tmp.path().join("owlery").join(owl_id).join("spool.db");
assert!(spool_path.exists(), "spool DB must exist at {}", spool_path.display());
let conn = rusqlite::Connection::open(&spool_path).unwrap();
let body: String = conn
    .query_row("SELECT body FROM messages WHERE from_id = 'version-change' ORDER BY id DESC LIMIT 1",
               [], |r| r.get(0))
    .expect("version-change message must be in spool");
assert!(body.contains("<spt-version-changelog>"), "got: {}", body);
assert!(body.contains("<old>1.10.9</old>"), "got: {}", body);
// (move other XML-substring asserts here from the old stdout assertions)
```

Add `rusqlite` to `[dev-dependencies]` ONLY IF not already pulled in
transitively — the crate's main `[dependencies]` already has `rusqlite =
{ version = "...", features = ["bundled"] }`, so `cargo test` already links
it. Use a plain `use rusqlite;` import at top of `tests/version_changelog.rs`.

Per-test changes:

1. `first_install_silent_writes_sentinel` (test 1) — already asserts empty
   stdout; add an assertion that the spool DB either does NOT exist OR
   contains zero `version-change` rows (first-install MUST stay silent on
   the spool too). The existing sentinel-write assertion stays.

2. `single_step_mismatch_emits_block` (test 2) — replace decision-block
   stdout assertions with the spool-message helper pattern above. Required
   spool-body substrings: `<spt-version-changelog>`, `<old>1.10.9</old>`,
   `<new>1.10.10</new>`, `<step_count>1</step_count>`. Sentinel-rewrite
   assertion unchanged.

3. `multi_step_mismatch_emits_block_with_step_count` (test 3) — same swap.
   Required spool-body substring: `<step_count>5</step_count>`.

4. `sentinel_equal_no_emit` (test 4) — already asserts no decision-block;
   tighten to also assert no spool row was inserted for `from_id =
   'version-change'`.

5. `owl_echo_commune_guard_skips_emission` (test 5) — same tightening:
   no decision-block AND no spool row.

6. `no_perch_skips_emission` (test 6) — same tightening. With no perch
   resolved, `maybe_emit_version_change_block` is never called, so no
   spool DB / no row.

7. `stop_hook_active_skips_emission` (test 7) — same tightening.

8. `version_remind_rollback_round_trip` (test 8) — UNCHANGED. The
   subcommand still works (deprecated, not removed).

9. `version_remind_missing_arg_exits_nonzero` (test 9) — UNCHANGED.

10. `version_remind_non_semver_exits_1` (test 10) — UNCHANGED.

11. `malformed_sentinel_treated_as_first_install` (test 11) — both
    sub-cases A and B: replace decision-block-stdout assertions with
    "stdout empty AND no version-change row in spool". The garbage-not-in-
    stdout assertion stays as `assert!(!stdout.contains("$(echo pwned)"))`;
    add the parallel `assert!(!body_if_any.contains("$(echo pwned)"))`
    just in case a future bug routes garbage into the spool.

12. `echo_commune_does_not_spawn_when_version_block_fires` (test 12) —
    primary invariant (`.more-done` MUST NOT exist) stays; replace the
    decision-block-stdout assertion (a) with the spool-row assertion.
    Sentinel-rewrite assertion (c) unchanged.

13. `block_reason_matches_reference_invariants` (test 13) — REWORK:
    - Source the body from the spool, not from stdout JSON envelope.
    - Drop the `serde_json::from_str(stdout.trim())` envelope parse and
      the `envelope["decision"] == "block"` assertion.
    - Pull body via the spool helper pattern.
    - DROP these invariants from the substring list: `"Remind me later"`,
      `$OWL version-remind 1.10.9` (both occurrences),
      `<invocation_for_remind>$OWL version-remind 1.10.9</invocation_for_remind>`.
    - KEEP all other invariants: `<spt-version-changelog>`,
      `</spt-version-changelog>`, `<old>1.10.9</old>`, `<new>1.10.10</new>`,
      `<step_count>1</step_count>`,
      `<changelog_path>$CLAUDE_PLUGIN_ROOT/CHANGELOG.md</changelog_path>`,
      `"Yes, full changelog"`, `"Yes, highlights only"`, `"Skip"`,
      `read $CLAUDE_PLUGIN_ROOT/CHANGELOG.md`.
    - Placeholder-leak guard (REVIEW-FIX #12) — unchanged.
    - Reference-file diff-check at the tail — leave the eprintln! warning
      path intact; the regenerated fixture in CHANGE C below will match
      on first regenerate.

ADD a new test (test 14): `stop_hook_produces_no_stdout_on_mismatch`:
```rust
#[test]
fn stop_hook_produces_no_stdout_on_mismatch() {
    let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
    let tmp = tempfile::tempdir().unwrap();
    let _sth = SptHomeGuard::set(tmp.path(), tmp.path(), Some("1.10.10"));
    write_live_perch(tmp.path(), "test-owl");
    write_changelog_fixture(tmp.path());
    write_sentinel_fixture(tmp.path(), "1.10.9");

    let output = base_hook_idle_cmd(tmp.path(), "1.10.10")
        .write_stdin(r#"{"session_id":"test-session"}"#)
        .output()
        .expect("owl hook-idle subprocess");

    assert!(output.status.success());
    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(stdout.trim().is_empty(),
        "Stop hook must produce empty stdout on mismatch (hotfix-260517-6om transport); got: {}",
        stdout);
    // Sentinel updated → no re-fire next Stop.
    let sentinel = read_sentinel(tmp.path()).unwrap();
    assert!(sentinel.contains("1.10.10"));
}
```

ADD a new test (test 15): `owl_message_lands_on_self_perch_spool`:
```rust
#[test]
fn owl_message_lands_on_self_perch_spool() {
    let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
    let tmp = tempfile::tempdir().unwrap();
    let _sth = SptHomeGuard::set(tmp.path(), tmp.path(), Some("1.10.10"));
    write_live_perch(tmp.path(), "test-owl");
    write_changelog_fixture(tmp.path());
    write_sentinel_fixture(tmp.path(), "1.10.9");

    let output = base_hook_idle_cmd(tmp.path(), "1.10.10")
        .write_stdin(r#"{"session_id":"test-session"}"#)
        .output()
        .expect("owl hook-idle subprocess");

    assert!(output.status.success());

    let spool_path = tmp.path().join("owlery").join("test-owl").join("spool.db");
    assert!(spool_path.exists(),
        "spool DB must exist at {} (transport regression — hotfix-260517-6om)",
        spool_path.display());

    let conn = rusqlite::Connection::open(&spool_path).unwrap();
    let body: String = conn
        .query_row(
            "SELECT body FROM messages WHERE from_id = 'version-change' ORDER BY id DESC LIMIT 1",
            [], |r| r.get(0))
        .expect("version-change message must be in spool");

    assert!(body.contains("<spt-version-changelog>"), "got: {}", body);
    assert!(body.contains("<old>1.10.9</old>"), "got: {}", body);
    assert!(body.contains("<new>1.10.10</new>"), "got: {}", body);
    // INIT_SIGNOFF false-positive regression guard (commit 30c0144):
    assert!(!body.to_ascii_lowercase().contains("<event type=\"init_signoff\""),
        "version-change body must not match INIT_SIGNOFF envelope shape: {}", body);
}
```

CHANGE C — `tests/fixtures/version_changelog_block_reference.txt`:

Regenerate the canonical fixture via the existing `#[ignore]` helper test.
Run after CHANGE A + B compile clean:
```
cargo test --lib version_changelog::tests::print_canonical_block_reason_for_reference -- --ignored --nocapture
```
Pipe the output between `---REFERENCE-BEGIN---` and `---REFERENCE-END---`
into `tests/fixtures/version_changelog_block_reference.txt`. New fixture
reflects 3-option AUQ (no Remind), no `<invocation_for_remind>` element.

Recompute SHA-256 of the new fixture and append a note to the Phase 34-02
SUMMARY appendix (or to this plan's SUMMARY at output-time) — defer
SHA-update to summary-writing step.

Suggested commits (atomic per scope group):
- `feat(34): drop "Remind me later" AUQ option (infinite-loop risk)` —
  if you split the build_block_reason instructions edit into its own commit,
  AND Task 1 only covered the transport rewrite. (Task 1 spec above
  already folds both into one commit; if Task 1 followed that, this commit
  message is NOT used.) Otherwise:
- `chore(34): deprecate version-remind subcommand` — CHANGE A only
  (cli.rs hide=true + doc-comment).
- `test(34): rewrite integration tests for spool transport + new fixture` —
  CHANGE B + C combined (tests + regenerated fixture).

The split into two commits (cli deprecation + tests/fixture) is preferred
since they touch disjoint files. Final commit count for this task: 2.
  </action>
  <verify>
    <automated>cargo test --test version_changelog 2>&amp;1 | tail -30 &amp;&amp; grep -c "Remind me later" tests/version_changelog.rs tests/fixtures/version_changelog_block_reference.txt</automated>
  </verify>
  <done>
- `cargo test --test version_changelog` all integration tests green
  (≥14 tests pass — 12 original surgical-edited + 2 new + 1 reference test,
  minus the deleted ones if any).
- `cargo test --lib version_changelog` still green after CHANGE A unit-test
  edits from Task 1.
- `grep -v '^//' tests/version_changelog.rs | grep -c 'Remind me later'`
  returns 0 (comment-stripped; matches gate-hygiene rule).
- `grep -v '^//' tests/version_changelog.rs | grep -c 'invocation_for_remind'`
  returns 0.
- `grep -v '^#' tests/fixtures/version_changelog_block_reference.txt | grep -c 'Remind me later'`
  returns 0.
- `grep -v '^#' tests/fixtures/version_changelog_block_reference.txt | grep -c 'invocation_for_remind'`
  returns 0.
- `owl --help` no longer lists `version-remind` (hidden) — manual one-line
  verify via `cargo run --release -- --help | grep -c version-remind` returns 0.
- `owl version-remind 1.2.3 --help` still works (clap parses the hidden
  subcommand) — verify with `cargo run --release -- version-remind --help`
  exits 0.
- Two atomic commits landed (deprecation + tests/fixture).
  </done>
</task>

<task type="auto">
  <name>Task 3: Deploy v1.10.11 → v1.10.12 (Change 6)</name>
  <files>plugin/spt/.claude-plugin/plugin.json, Cargo.toml, Cargo.lock, CHANGELOG.md</files>
  <action>
After Tasks 1 + 2 are committed and all `cargo test` is green, run the
project's canonical deploy script:

```
powershell -ExecutionPolicy Bypass -File docs/DEPLOY.ps1 -Bump patch
```

This will:
1. Bump `plugin/spt/.claude-plugin/plugin.json` 1.10.11 → 1.10.12.
2. Bump `Cargo.toml` [package] version 1.10.11 → 1.10.12.
3. Append a stub `## [1.10.12] - <today>` entry to repo-root `CHANGELOG.md`
   (Phase 34-01 D-10 abort-on-unchanged gate is now active — you MUST fill
   in the entry before the script will commit). When the script aborts
   with `ERROR: CHANGELOG.md contains 'TODO: changelog entry' for v1.10.12`,
   edit `CHANGELOG.md`: replace the `- TODO: changelog entry` line with
   the curated entry below, then re-run the same DEPLOY command.

CHANGELOG.md entry to fill in for `## [1.10.12]`:
```markdown
### Fixed
- Version-change Stop hook no longer surfaces as a "Stop hook blocking
  error". The `<spt-version-changelog>` payload now enqueues as an owl
  message on Self's perch and surfaces silently via the existing
  UserPromptSubmit spool drain.

### Changed
- AskUserQuestion for version-change updates reduced from 4 options to 3.
  "Remind me later" removed — it rolled the sentinel back, causing the
  next Stop hook to re-fire immediately (infinite-loop pathology
  confirmed during Phase 34 UAT).
- `owl version-remind <old>` subcommand hidden from `--help`. Still
  callable for any in-flight invocations from older block payloads.
```

4. Build release binary (cargo build --release).
5. Sync `target/release/owl.exe` + `plugin/spt/skills/` + `plugin/spt/hooks/` +
   `plugin/spt/CHANGELOG.md` + updated `plugin.json` to the marketplace clone
   `cplugs/spt/1.10.12/`.
6. Patch `installed_plugins.json` pointer to 1.10.12.
7. Run `claude plugin install spt@cplugs`.

The script STOPS before `/reload-plugins` per project convention. Do NOT
run `/reload-plugins` from the executor — that is a user action per
CLAUDE.md and docs/DEPLOY.md.

If the script reports any sharing-violation warning on prior cache
versions (`1.10.11`, `1.10.10`, etc.) marked `.pending-prune-*`, that is
non-fatal per Phase 27-07 precedent — note in SUMMARY but do not retry.

Atomic commit will be created by the script (subject:
`chore: bump 1.10.11 → 1.10.12`, plus a follow-up commit for
`Cargo.lock` if the script does not stage it).

After deploy completes, verify the deployed binary version:
```
& "$env:LOCALAPPDATA\..\..\.claude\plugins\cache\cplugs\spt\1.10.12\owl.exe" --version
```
Expected: `owl 1.10.12`.

If `gh` is available and the user wants the commits pushed, defer to user;
do NOT push automatically.
  </action>
  <verify>
    <automated>cargo build --release 2>&amp;1 | tail -5 &amp;&amp; cargo test --workspace 2>&amp;1 | tail -10</automated>
  </verify>
  <done>
- DEPLOY.ps1 -Bump patch ran clean to completion (no abort on `TODO:
  changelog entry` after CHANGELOG.md filled in; no fatal sharing
  violation).
- `plugin/spt/.claude-plugin/plugin.json` version is `1.10.12`.
- `Cargo.toml` [package] version is `1.10.12`.
- `Cargo.lock` synced (a follow-up commit may exist per DEPLOY.ps1 contract).
- `CHANGELOG.md` has the curated `## [1.10.12]` entry committed (not the
  TODO stub).
- Marketplace cache `~/.claude/plugins/cache/cplugs/spt/1.10.12/owl.exe`
  reports `owl 1.10.12` on `--version`.
- `installed_plugins.json` pointer flipped to 1.10.12.
- `claude plugin install spt@cplugs` succeeded (the script's last step
  before stopping).
- `/reload-plugins` NOT run by executor (user action).
- `cargo test --workspace` still green after the version bump
  (no test references the literal 1.10.11 string — verified via grep in
  Task 2).
  </done>
</task>

</tasks>

<verification>
End-to-end gates:

1. **Build** — `cargo build --release` succeeds, zero new warnings.
2. **Tests** — `cargo test --workspace` green (lib + integration);
   specifically the version-change suite shows: stdout-empty assertion fires,
   spool-row-exists assertion fires, INIT_SIGNOFF disjointness asserted.
3. **Transport regression guard** — grep `tests/version_changelog.rs` for
   `decision":"block"` returns 0 matches outside comments.
4. **Remind option excision** — grep `src/owl/version_changelog.rs`,
   `tests/version_changelog.rs`, `tests/fixtures/version_changelog_block_reference.txt`
   for `Remind me later` / `invocation_for_remind` / `version-remind` (in
   non-test-context lines) returns 0 hits except the deprecated cli.rs
   subcommand registration and src/owl/mod.rs dispatch arm.
5. **CLI hide gate** — `cargo run --release -- --help` does NOT list
   `version-remind` in the subcommand table.
6. **Deploy** — DEPLOY.ps1 -Bump patch landed; installed plugin reports
   1.10.12; no fatal errors; no `/reload-plugins` triggered by executor.
</verification>

<success_criteria>
- Stop hook produces empty stdout on version mismatch (no
  `decision:"block"` envelope).
- Spool DB at `$SPT_HOME/owlery/<self-owl>/spool.db` contains a row with
  `from_id = 'version-change'` and a body matching
  `<spt-version-changelog>...</spt-version-changelog>`.
- That body contains exactly 3 AUQ options (verbatim labels: `"Yes, full
  changelog"`, `"Yes, highlights only"`, `"Skip"`) — no `"Remind me later"`,
  no `$OWL version-remind` instruction text, no `<invocation_for_remind>`
  element.
- `owl --help` does not list `version-remind`.
- INIT_SIGNOFF false-positive regression (commit 30c0144) NOT
  re-introduced — `is_init_signoff_envelope(body)` returns false for the
  new payload (asserted in test 15 + debug_assert in source).
- Plugin deployed at 1.10.12; marketplace cache + `installed_plugins.json`
  pointer updated; `claude plugin install spt@cplugs` succeeded.
- Three atomic commits land for source changes (Task 1: fix transport;
  Task 2a: deprecate cli; Task 2b: tests + fixture). DEPLOY.ps1 lands
  its own bump commit (Task 3).
</success_criteria>

<output>
Create `.planning/quick/260517-6om-version-change-owl-transport-hotfix/260517-6om-SUMMARY.md`
when done. Include:
- One-line outcome summary
- Per-task commit SHAs
- New SHA-256 of `tests/fixtures/version_changelog_block_reference.txt`
- Notes on any sharing-violation warnings from DEPLOY.ps1
- Reminder that user must run `/reload-plugins` post-deploy
</output>
