1	# Deploying Spacetime (spt) Plugin
2
3	## Automated Deploy (DEPLOY.ps1)
4
5	For the common case — binary + skills + hooks + marketplace + cache refresh for the version already set in `plugin/spt/.claude-plugin/plugin.json` — use the PowerShell script:
6
7	```powershell
8	# From repo root, after bumping plugin.json version:
9	powershell -ExecutionPolicy Bypass -File docs/DEPLOY.ps1
10
11	# Or let the script bump the version for you:
12	powershell -ExecutionPolicy Bypass -File docs/DEPLOY.ps1 -Bump patch
13	```
14
15	The script automates the build-through-cache-sync sequence and the state refresh (by shelling out to `claude plugin install spt@cplugs`). It **stops before `/reload-plugins`** — you must run that yourself inside Claude Code.
16
17	### What it does (in order)
18
19	1. Reads the authoritative version from `plugin/spt/.claude-plugin/plugin.json`. If `-Bump <patch|minor|major>` is passed, runs the bump sub-sequence: (a) abort gate — refuses to proceed if `CHANGELOG.md` is missing OR still contains a `TODO: changelog entry` marker from a prior bump; (b) rewrites `plugin.json` + `Cargo.toml` to the new version; (c) appends a `TODO: changelog entry` stub H2 to `CHANGELOG.md` for the new version; (d) stages `plugin.json` + `Cargo.toml` + `CHANGELOG.md` and commits as `chore: bump spt plugin to vX.Y.Z` in the spt repo (no push). The bump commit ships with the TODO stub; the user is expected to author a curated `CHANGELOG.md` entry for the new version in a follow-up commit before the next `-Bump`. See "Two-phase changelog authoring" below.
20	2. `cargo build --release` in the repo root.
21	3. Syncs `owl.exe`, `skills/`, `hooks/`, and `plugin.json` to the cplugs marketplace clone.
22	4. Commits + pushes the marketplace (unless skipped).
23	5. Syncs the new version into `~/.claude/plugins/cache/cplugs/spt/<Version>/`, then targeted-prunes older semver directories. The script keeps the NEW version dir, the PREVIOUS version dir (running `owl.exe` processes are still exec'ing from there until they self-migrate via handoff), and the `installPath` leaf dir recorded in `installed_plugins.json` (captured before step 7 rewrites it). Non-semver directories and directories in the keep-set are never touched. Sharing-violation residue (a dir that can't be removed because `owl.exe` still has the `.exe` mapped) is logged as a warning and retried on the next deploy — it does **not** fail the deploy. First-deploy case (no prior `spt@cplugs` entry) skips the prune entirely.
24	6. Cleans up the legacy manual-install dir at `~/.claude/plugins/spt/` if it exists as a regular directory. A manual install coexisting with the marketplace install makes Claude Code report the plugin as **not installed** in `/plugin` (see Troubleshooting below). Symlinked dirs are preserved for the dev-symlink workflow.
25	7. Runs `claude plugin install spt@cplugs` for cache-warm-up and `.orphaned_at` marker cleanup. **This is no longer the version-pointer flip** — the CLI is a no-op when `spt@cplugs` is already installed at any version (it only touches `lastUpdated`, not `version` / `installPath` / `gitCommitSha`). That silent no-op was the failure mode in the 1.9.8 → 1.9.9 deploy that left the pointer pinned to 1.9.8 (see `.planning/debug/26-06-live-skill-regression.md`). If `claude` is not in `PATH`, the script warns and tells you to run the command manually.
26	8. **Explicitly patches `~/.claude/plugins/installed_plugins.json`** to set the `spt@cplugs` entry's `version`, `installPath`, `gitCommitSha`, and `lastUpdated`. Atomic write via temp file + `Move-Item -Force`. This is the authoritative pointer flip — the step 7 CLI call is kept only for its cache-warm-up side effects. Uses `ConvertFrom-Json` / `ConvertTo-Json -Depth 10` (no `JavaScriptSerializer` — its earlier hand-rolled patch was removed for circular-reference crashes; the new patch round-trips through the PSObject graph safely).
27	9. **Re-reads `installed_plugins.json` and asserts `spt@cplugs.version == <Version>`** (and `installPath` matches the new cache dir). Any mismatch writes `ASSERTION FAILED: ...` to stderr and exits the script with status 1; the green "Deploy complete" banner never prints on drift.
28	10. Prints a reminder to run `/reload-plugins` inside Claude Code.
29
30	> The `Write-Step "Step N: ..."` labels in the script body still use their historical numbers (Step 3 for build, Step 4 for marketplace sync, etc. — Step 2 is intentionally absent) so log-grep tooling keeps matching prior runs. The numbered list above is the user-facing sequence; the in-script labels are the log contract.
31
32	### What it does NOT do
33
34	- Bump the plugin version in `plugin.json` unless `-Bump <patch|minor|major>` is passed.
35	- Push the spt repo. Even with `-Bump`, the bump commit lands locally and you push it yourself.
36	- Commit other changes in the spt repo (that's your job).
37	- Edit `~/.claude/settings.json`.
38	- Run `/reload-plugins` (that's a Claude Code slash command).
39	- Kill running `owl.exe` processes. Phase 18.4/18.5 shipped seamless binary handoff — old listeners self-migrate via stdio-relay and Psyche wrappers detach + rehydrate from `wrapper-state.json` once the new version is staged and `installed_plugins.json` flips (step 7 above). Killing them aborts the handoff and strands their session state.
40	- Wipe the entire cache tree. The old "blanket `rm -rf` on `cache/cplugs/spt`" behavior was correct before handoff shipped, but now it yanks the `.exe` out from under running processes mid-migration. The new targeted prune (step 5 above) keeps the PREVIOUS version dir on disk so in-flight handoffs complete.
41
42	### Two-phase changelog authoring (Phase 34 D-08 round 2)
43
44	Under the locked D-08 round 2 design (realigned in quick-260517-n4b post-UAT clarification), `-Bump` enforces a curation gate so each version-change Stop-hook surface ships with a real CHANGELOG entry inside the deployed bundle. The first `-Bump` for any new version aborts and seeds a TODO stub; the second `-Bump` (after the user fills it in and commits) proceeds.
45
46	**First `-Bump` run (no curated entry yet):**
47
48	1. **Abort gate** — refuses to proceed if `CHANGELOG.md` is missing OR contains a `TODO: changelog entry` marker from a prior bump.
49	2. **Curation gate** — checks for `## [<NewVersion>]` H2 header in `CHANGELOG.md`:
50	   - **Absent:** appends `## [<NewVersion>] - YYYY-MM-DD\n\n- TODO: changelog entry` to the working tree (uncommitted), then **EXIT 1** with instructions to fill and commit, then re-run. The current deploy does **not** succeed; no `plugin.json` / `Cargo.toml` mutation occurs.
51	   - **Present + TODO marker:** caught by abort gate above → EXIT 1.
52	   - **Present + curated body:** proceed past the gate.
53
54	**User action between runs:** Edit `CHANGELOG.md`, replace `- TODO: changelog entry` under the newly-stubbed `## [<NewVersion>]` H2 with real bullets in Keep-a-Changelog 1.1.0 categories (Added / Changed / Fixed / Removed / Deprecated / Security). Commit as e.g. `docs(NN): fill <NewVersion> CHANGELOG entry`.
55
56	**Second `-Bump` run (curated entry committed):**
57
58	1. Abort gate passes — no `TODO: changelog entry` marker remains.
59	2. Curation gate passes — `## [<NewVersion>]` H2 present with curated body.
60	3. Rewrite `plugin/spt/.claude-plugin/plugin.json` + `Cargo.toml` to the new version.
61	4. Stage + commit `plugin.json` + `Cargo.toml` (two files) as `chore: bump spt plugin to vX.Y.Z`. CHANGELOG.md is **not** in the bump commit — it landed in the prior curated-entry commit.
62	5. Build, sync to marketplace + cache (including curated `CHANGELOG.md`), refresh `installed_plugins.json`.
63
64	**Pre-authoring shortcut:** A caller who knows the next semver upfront can pre-author and commit the `## [<NewVersion>]` H2 with curated body before running `-Bump`. The curation gate sees the H2 present + curated and proceeds in one shot.
65
66	**Backstop:** If a stub somehow slips into a bump commit (manual flow, prior implementation), the next `-Bump`'s abort gate fires on the `TODO: changelog entry` marker and forces the user to curate before any further deploy.
67
68	### Bucket discipline for stub fill-in (quick-260520-uc2)
69
70	The version-change Stop hook surfaces curated CHANGELOG bullets to the user
71	on the next session boundary after a plugin upgrade. The user sees only the
72	release-facing buckets — implementation noise is filtered out at hook time.
73	The deploying agent (human or LLM) filling the `- TODO: changelog entry`
74	stub MUST follow these rules:
75
76	**Section vocabulary per H2 (`## [vX.Y.Z] - YYYY-MM-DD`):**
77
78	| Section | Use for | Surfaced to user? |
79	|---------|---------|---------------------|
80	| `### Added` | New user-facing capability (a flag, command, message, prompt, hook trigger they will notice) | YES |
81	| `### Changed` | Modified user-facing behavior (different message, different default, different timing) | YES |
82	| `### Fixed` | Bug fix the user actually observed or could observe | YES |
83	| `### Removed` | A user-facing capability that no longer exists | YES |
84	| `### Deprecated` | A capability the user should stop relying on | YES |
85	| `### Security` | Anything with user-relevant security impact | YES |
86	| `### Reverted` | A user-visible behavior was rolled back to a prior shape | YES |
87	| `### BTS` | Anything else: internal refactors, repo plumbing, deploy script changes, doc-string drift, test-only changes, code reverts that don't change user-visible behavior, commit SHAs, file-path callouts | **NO** — filtered out at hook time |
88
89	**UX-language rule for Added / Changed / Fixed (and the other surfaced
90	buckets):**
91	- Phrase as what the user sees, does, or runs. "`/spt:live` now offers an
92	  Other option" — not "added `AuqOption::Other` to `live::auto_pick`".
93	- Do NOT mention: code paths (`src/...`), function or struct names, prompt
94	  XML element names (`<spt-foo>`), commit SHAs, planning doc references
95	  (`Phase 34 D-08`, `quick-260517-n4b`), Rust types, file paths inside
96	  `plugin/spt/`, internal env-var names that are not part of the
97	  documented user surface.
98	- Keep bullets terse. One sentence each is the target; two short sentences
99	  is the ceiling. If you need more, you are probably writing BTS content.
100	- The Stop-hook "Yes, highlights only" path picks 2-5 bullets across the
101	  whole transition; bullets that are themselves short and UX-focused give
102	  it the right material to work with.
103
104	**BTS rule (anything off the UX path):**
105	- Repo-management changes (Cargo manifest tweaks unless they change runtime
106	  behavior, CI config, .gitignore, planning docs).
107	- Deploy-sequence changes (DEPLOY.ps1, DEPLOY.md, marketplace cache logic)
108	  unless the user's deploy workflow changes.
109	- Commit reverts where the reverted code never reached the user's machine.
110	- Doc-string / comment drift fixes.
111	- Test-only additions or framework changes.
112	- Internal refactors with no observable behavior change.
113	- Naming, struct-field, or fn-signature changes inside `src/`.
114
115	**When in doubt, ask: "Would a user typing `/spt:live` notice the
116	difference?"** If no, it's BTS.
117
118	**Examples of correct bucketing** (from real prior bullets, reframed):
119
120	- v1.10.20 "version-change owl message routed via TCP-first deliver_body" →
121	  `### Fixed`: "Version-change announcements arrive in-line with other
122	  high-priority messages instead of waiting for the next poll tick."
123	  (The file-path / fn-name detail moves to `### BTS` as: "Routed
124	  `version_changelog` payload through `deliver_body` like every other owl
125	  message.")
126	- v1.10.20 "WORKING_PERCH_NOTICE TCP-first flip reverted" — no user-visible
127	  symptom changed, so it belongs entirely in `### BTS`, not `### Reverted`.
128	- v1.10.18 the entire "260520-r2f commune-reference write-path fix" — the
129	  user-visible bullet is `### Fixed`: "Fresh `/spt:live` start now delivers
130	  first-commune context to the live agent reliably." Every file/line
131	  citation in the existing entry moves to `### BTS`.
132	- v1.10.17 chunking work — `### Fixed`: "Long poll-listener events no
133	  longer get truncated by the Claude Code Monitor at ~500 characters."
134	  Internal const names and chunk-boundary detail → `### BTS`.
135
136	**Backward compatibility:** Pre-v1.10.21 entries that pre-date this
137	discipline are left as-is. The hook's "skip `### BTS`" filter is a no-op
138	on them (they have no `### BTS` section) and the full-changelog path will
139	surface them verbatim per the existing behavior. The new discipline
140	applies prospectively starting with the next bump.
141
142	### Parameters
143
144	| Parameter | Default | Purpose |
145	|-----------|---------|---------|
146	| `-RepoRoot` | script's parent-of-parent dir | Path to the spt repo root |
147	| `-Marketplace` | `~/.claude/plugins/marketplaces/cplugs` | Local cplugs marketplace clone |
148	| `-PluginDir` | `~/.claude/plugins/spt` | Legacy manual-install dir. Removed by step 6 if present as a regular dir (it would otherwise conflict with the marketplace install). Symlinks at this path are preserved. |
149	| `-Bump` | _(unset)_ | Increment semver: `patch`/`minor`/`major`. Rewrites `plugin.json` and commits in the spt repo (no push). Aborts if `plugin.json` has uncommitted changes. |
150	| `-SkipPush` | off | Commit marketplace locally, don't push. Does not affect the `-Bump` commit (never pushed). |
151	| `-SkipMarketplaceCommit` | off | Skip marketplace commit+push entirely |
152	| `-DryRun` | off | Print every step with `[DRY-RUN]` prefix; no mutations |
153
154	### Example: dev loop without pushing
155
156	```powershell
157	powershell -ExecutionPolicy Bypass -File docs/DEPLOY.ps1 -SkipPush
158	```
159
160	### Example: dry-run to preview
161
162	```powershell
163	powershell -ExecutionPolicy Bypass -File docs/DEPLOY.ps1 -DryRun
164	```
165
166	### Example: bump patch + full deploy
167
168	```powershell
169	powershell -ExecutionPolicy Bypass -File docs/DEPLOY.ps1 -Bump patch
170	```
171
172	### Troubleshooting: plugin shows as "not installed" in `/plugin`
173
174	**Symptom:** `/plugin` reports `spt` as not installed (and refuses to update), even though `installed_plugins.json` has `spt@cplugs` and the plugin clearly works. Commands like `/spt:*` may still function via the marketplace install while the UI misreports state.
175
176	**Cause:** A legacy manual-install directory at `~/.claude/plugins/spt/` (with its own `.claude-plugin/plugin.json`) living alongside the marketplace install at `~/.claude/plugins/cache/cplugs/spt/<version>/`. Claude Code enumerates both, and the unmanaged one — not tracked in `installed_plugins.json` — causes the conflict. Usually a leftover from a pre-marketplace deploy or from running the old "Deploy as Plugin (manual)" flow on top of a marketplace install.
177
178	**Fix:**
179
180	```bash
181	rm -rf ~/.claude/plugins/spt/
182	```
183
184	Then restart Claude Code (or run `/reload-plugins`). DEPLOY.ps1 step 6 now auto-cleans this directory on every marketplace deploy, so re-running the deploy script also resolves it.
185
186	> The manual steps documented in the sections below remain the authoritative spec. The script automates them; if the two ever diverge, the manual steps are correct and the script must be updated to match.
187
188	## Files Overview
189
190	| File | Purpose |
191	|------|---------|
192	| `owl.exe` | Native binary (compiled from `src/`) -- handles all owl and live commands |
193	| `plugin/spt/` | Plugin directory (deployed via marketplace or manually) |
194	| `plugin/spt/.claude-plugin/plugin.json` | Plugin manifest (name: spt) |
195	| `plugin/spt/skills/*/SKILL.md` | Per-command skill files (one directory per /spt: command) |
196	| `plugin/spt/skills/commune/commune.md` | Commune protocol for Self agents |
197	| `plugin/spt/hooks/hooks.json` | SessionStart + SessionEnd hook registrations |
198	| `psyche.md` | Psyche agent prompt template (embedded in binary via `include_str!`) |
199
200	## Deploy via Marketplace (recommended)
201
202	The cplugs private marketplace is the primary way to install spt. It bundles skill files, hooks, and the native binary.
203
204	### Marketplace Setup (one-time)
205
206	Register the cplugs marketplace if not already registered:
207
208	```bash
209	# Clone the marketplace repo
210	git clone https://github.com/SaberMage/cplugs.git ~/.claude/plugins/marketplaces/cplugs
211
212	# Register in ~/.claude/plugins/known_marketplaces.json
213	# Add a "cplugs" entry with source: { source: "github", repo: "SaberMage/cplugs" }
214	```
215
216	### Install from Marketplace
217
218	```
219	/plugin install spt@cplugs
220	```
221
222	### Update Marketplace Plugin Files
223
224	When skill files, hooks, or the binary change in this repo:
225
226	1. **Bump the version** in `plugin/spt/.claude-plugin/plugin.json` (e.g., `1.5.0` → `1.5.1`). The plugin manager compares this version to the cached copy — if they match, it skips the update entirely.
227	2. Build: `cargo build --release`
228	3. **Copy to marketplace** — binary, skills, hooks, and plugin manifest:
229	   ```bash
230	   MARKET=~/.claude/plugins/marketplaces/cplugs/plugins/spt
231	   cp target/release/owl.exe "$MARKET/"
232	   cp -r plugin/spt/skills/* "$MARKET/skills/"
233	   cp -r plugin/spt/hooks/* "$MARKET/hooks/"
234	   cp plugin/spt/.claude-plugin/plugin.json "$MARKET/.claude-plugin/"
235	   ```
236	4. **Commit and push** the marketplace repo:
237	   ```bash
238	   cd ~/.claude/plugins/marketplaces/cplugs
239	   git add plugins/spt/ && git commit -m "spt: <describe change>" && git push
240	   ```
241	5. **Sync the new version into the cache, then (optionally) targeted-prune older versions.** `/plugin install` may not refresh the cache if the plugin is already enabled, so the new version directory must be created explicitly. **Do NOT stop live agents first** — Phase 18.4/18.5 shipped seamless binary handoff, and the handoff fires when `installed_plugins.json` flips in step 6 below. Old listeners self-migrate via stdio-relay and Psyche wrappers detach + rehydrate from `wrapper-state.json`. Killing `owl.exe` aborts that handoff.
242
243	   **Do NOT wipe the entire cache tree.** Running `owl.exe` processes are exec'ing from the PREVIOUS version directory until they migrate. `rm -rf ~/.claude/plugins/cache/cplugs/spt` yanks the `.exe` out from under them mid-handoff. At minimum, keep the PREVIOUS version dir on disk.
244
245	   **Sync all files** (not just the ones you changed) — partial syncs cause subtle mismatches between the binary and skill definitions:
246
247	   ```bash
248	   # Read the version from the plugin manifest — this is the authoritative version number
249	   VERSION=$(node -p "require('./plugin/spt/.claude-plugin/plugin.json').version")
250
251	   # Sync the new version into the cache (do NOT rm -rf the whole spt/ cache tree).
252	   CACHE=~/.claude/plugins/cache/cplugs/spt/$VERSION
253	   mkdir -p "$CACHE/.claude-plugin" "$CACHE/skills" "$CACHE/hooks"
254	   cp target/release/owl.exe "$CACHE/"
255	   cp -r plugin/spt/skills/* "$CACHE/skills/"
256	   cp -r plugin/spt/hooks/* "$CACHE/hooks/"
257	   cp plugin/spt/.claude-plugin/plugin.json "$CACHE/.claude-plugin/"
258
259	   # (Optional cleanup) After the next session start confirms no owl.exe has the
260	   # old .exe mapped, you can remove stale older-version dirs individually:
261	   #   rm -rf ~/.claude/plugins/cache/cplugs/spt/<old-version>
262	   # Keep the PREVIOUS version and the current $VERSION in place. PowerShell
263	   # users should prefer docs/DEPLOY.ps1, which handles this keep-set logic
264	   # automatically and tolerates sharing-violation residue.
265	   ```
266	6. **Refresh plugin state via the Claude Code CLI.** After the manual cache sync in step 5, `installed_plugins.json` still holds the `gitCommitSha` from the previous deploy; on the next session start Claude Code sees a mismatch against cplugs marketplace HEAD, writes `.orphaned_at` into the freshly-deployed cache dir, and refuses to source the SessionStart hook's `$OWL`/`$LIVE` exports — every `/spt:*` skill silently breaks. This is also the trigger for the seamless handoff: flipping `installPath` / `version` in `installed_plugins.json` is what old `owl.exe` listeners detect to initiate their self-migration to the new cache dir. See `.planning/debug/spt-env-vars-empty.md` for the full env-var evidence trail.
267
268	   The one-liner — canonical path, handles `installed_plugins.json` rewrite and orphan-marker cleanup atomically:
269
270	   ```bash
271	   claude plugin install spt@cplugs
272	   ```
273
274	   Equivalent to `/plugin install spt@cplugs` from inside Claude Code. Works identically from a script or CI. Restart the Claude Code session afterwards so the un-orphaned plugin's SessionStart hook runs cleanly and `$OWL`/`$LIVE` populate in Bash subprocesses.
275
276	   **Do not hand-patch `installed_plugins.json`.** Earlier revisions of this doc showed `jq` / `sed` fallbacks; they're brittle (an older DEPLOY.ps1 version crashed on a PowerShell `JavaScriptSerializer` circular-reference error here) and Claude Code may silently reject the result. If the CLI is unavailable, install it — don't edit the manifest by hand.
277	7. Run `/reload-plugins` in Claude Code to pick up skill file changes
278
279	### Enable the Plugin
280
281	Add to `~/.claude/settings.json`:
282
283	```json
284	{
285	  "enabledPlugins": {
286	    "spt@cplugs": true
287	  }
288	}
289	```
290
291	## Build
292
293	```bash
294	cargo build --release
295	```
296
297	The binary is at `target/release/owl.exe` (Windows) or `target/release/owl` (Linux/macOS). It handles both owl and live commands -- no separate binaries needed.
298
299	## Deploy as Plugin (manual, without marketplace)
300
301	> **Warning — do not combine with the marketplace install.** If you have `spt@cplugs` in `installed_plugins.json` (i.e. you installed via `/plugin install spt@cplugs`), do **not** also create `~/.claude/plugins/spt/`. Claude Code will enumerate both and report the marketplace install as **not installed** in `/plugin`, refusing to update it. The two flows are exclusive. See "Troubleshooting: plugin shows as 'not installed'" above.
302
303	If you don't have marketplace access, you can deploy the plugin manually. This replaces the old `~/.claude/skills/owl/` and `~/.claude/skills/live/` directories.
304
305	```bash
306	# Build
307	cargo build --release
308
309	# Deploy plugin directory
310	mkdir -p ~/.claude/plugins/spt
311	cp -r plugin/spt/.claude-plugin ~/.claude/plugins/spt/
312	cp -r plugin/spt/skills ~/.claude/plugins/spt/
313	cp -r plugin/spt/hooks ~/.claude/plugins/spt/
314	# Deploy binary into plugin directory
315	# IMPORTANT: hooks.json uses 'owl.exe' as the canonical binary name on ALL platforms.
316	# Windows:
317	cp target/release/owl.exe ~/.claude/plugins/spt/
318	# Linux/macOS -- copy as owl.exe so hooks.json works without platform detection:
319	cp target/release/owl ~/.claude/plugins/spt/owl.exe
320
321	# Remove old skill directories (clean break per D-08)
322	rm -rf ~/.claude/skills/owl
323	rm -rf ~/.claude/skills/live
324	```
325
326	**Cross-platform note:** The plugin's `hooks.json` references `$CLAUDE_PLUGIN_ROOT/owl.exe` for all hooks (`CLAUDE_PLUGIN_ROOT` is auto-provided by Claude Code's plugin system, pointing to the active plugin version directory). Using `owl.exe` as the canonical binary name on all platforms avoids needing platform-detection logic. On Unix, the binary is simply copied/renamed to `owl.exe` during deployment. Unix has no restriction on `.exe` extensions -- the file runs fine.
327
328	After deploying:
329
330	- The plugin's SessionStart hook auto-injects `$OWL` and `$LIVE` env vars via CLAUDE_ENV_FILE
331	- The plugin's SessionEnd hook runs cleanup-session
332	- No manual settings.json editing needed
333	- `$OWL` and `$LIVE` are set dynamically via CLAUDE_ENV_FILE on each session start, always pointing to the current plugin version's binary
334
335	## Remove Old Skill Directories
336
337	When switching to the plugin (via marketplace or manual), you **must** remove the old skill directories to avoid confusion:
338
339	```bash
340	# Required: remove old skill directories
341	rm -rf ~/.claude/skills/owl
342	rm -rf ~/.claude/skills/live
343	```
344
345	The plugin replaces both old skill directories. Old `/owl` and `/live` skill commands will no longer work. All commands are now under `/spt:` (e.g., `/spt:ready`, `/spt:live`). The binary CLI (`$OWL`, `$LIVE`) is unchanged.
346
347	## Env Var Setup
348
349	The plugin's SessionStart hook automatically writes `$OWL` and `$LIVE` env vars via `CLAUDE_ENV_FILE` on each session start. These point to the current plugin version's binary — no manual configuration needed.
350
351	**If env vars are missing** (e.g., `$OWL: command not found`), restart the Claude Code session — the SessionStart hook will re-run and populate `$OWL` / `$LIVE` from the current active plugin version via `CLAUDE_ENV_FILE`. There is no in-session force-write command; that used to exist as `/spt:env-setup` but was removed because its `settings.json` fallback pinned binary paths and broke upgrades. If restarting doesn't fix it, run `$OWL doctor` to diagnose — something is wrong with the plugin install or the hook.
352
353	**Stale env vars in settings.json:** If you have `OWL`/`LIVE` entries in `~/.claude/settings.json` from a pre-plugin install, remove them. They pin to a specific version and will override the plugin's dynamic env var injection, causing hooks to call the wrong binary after upgrades.
354
355	## Legacy / Standalone Deployment (removed)
356
357	Standalone deployment via `~/.claude/skills/owl/` and `~/.claude/skills/live/` is no longer supported. The root-level `SKILL.md`, `LIVE-SKILL.md`, and `commune.md` files have been removed. All skill definitions live under `plugin/spt/skills/`. Use the plugin system (marketplace or manual) for deployment.
358
359	## Development Workflow
360
361	For plugin development, use `--plugin-dir` to test the plugin from the repo without deploying:
362
363	```bash
364	# Build first
365	cargo build --release
366
367	# Copy binary into plugin dir
368	# Windows:
369	cp target/release/owl.exe plugin/spt/
370	# Linux/macOS:
371	cp target/release/owl plugin/spt/owl.exe
372
373	# Run Claude with the local plugin directory
374	claude --plugin-dir ./plugin/spt
375
376	# Or symlink for persistent development:
377	# ln -s $(pwd)/plugin/spt ~/.claude/plugins/spt
378	```
379
380	Note: When using `--plugin-dir`, the binary must already be built and present in the plugin directory as `owl.exe`.
381
382	## Bundle for Sharing
383
384	```bash
385	cargo build --release
386	# Windows:
387	cp target/release/owl.exe plugin/spt/
388	# Linux/macOS:
389	cp target/release/owl plugin/spt/owl.exe
390	tar czf spt-plugin.tar.gz -C plugin spt/
391	```
392
393	Install from bundle:
394
395	```bash
396	mkdir -p ~/.claude/plugins
397	tar xzf spt-plugin.tar.gz -C ~/.claude/plugins/
398	rm -rf ~/.claude/skills/owl ~/.claude/skills/live
399	```
400
401	Then start a new Claude Code session. The plugin's SessionStart hook will auto-inject `$OWL` and `$LIVE`.
402
403	## Commit Changes
404
405	```bash
406	git add plugin/ src/ psyche.md docs/ Cargo.toml
407	git commit -m "owl: <describe change>"
408	```
409

# Deploying Spacetime (spt) Plugin

## Automated Deploy (DEPLOY.ps1)

For the common case — binary + skills + hooks + marketplace + cache refresh for the version already set in `plugin/spt/.claude-plugin/plugin.json` — use the PowerShell script:

```powershell
# From repo root, after bumping plugin.json version:
powershell -ExecutionPolicy Bypass -File docs/DEPLOY.ps1

# Or let the script bump the version for you:
powershell -ExecutionPolicy Bypass -File docs/DEPLOY.ps1 -Bump patch
```

The script automates the build-through-cache-sync sequence and the state refresh (by shelling out to `claude plugin install spt@cplugs`). It **stops before `/reload-plugins`** — you must run that yourself inside Claude Code.

### What it does (in order)

1. Reads the authoritative version from `plugin/spt/.claude-plugin/plugin.json`. If `-Bump <patch|minor|major>` is passed, runs the bump sub-sequence: (a) abort gate — refuses to proceed if `CHANGELOG.md` is missing OR still contains a `TODO: changelog entry` marker from a prior bump; (b) rewrites `plugin.json` + `Cargo.toml` to the new version; (c) appends a `TODO: changelog entry` stub H2 to `CHANGELOG.md` for the new version; (d) stages `plugin.json` + `Cargo.toml` + `CHANGELOG.md` and commits as `chore: bump spt plugin to vX.Y.Z` in the spt repo (no push). The bump commit ships with the TODO stub; the user is expected to author a curated `CHANGELOG.md` entry for the new version in a follow-up commit before the next `-Bump`. See "Two-phase changelog authoring" below.
2. `cargo build --release` in the repo root.
3. Syncs `owl.exe`, `skills/`, `hooks/`, and `plugin.json` to the cplugs marketplace clone.
4. Commits + pushes the marketplace (unless skipped).
5. Syncs the new version into `~/.claude/plugins/cache/cplugs/spt/<Version>/`, then targeted-prunes older semver directories. The script keeps the NEW version dir, the PREVIOUS version dir (running `owl.exe` processes are still exec'ing from there until they self-migrate via handoff), and the `installPath` leaf dir recorded in `installed_plugins.json` (captured before step 7 rewrites it). Non-semver directories and directories in the keep-set are never touched. Sharing-violation residue (a dir that can't be removed because `owl.exe` still has the `.exe` mapped) is logged as a warning and retried on the next deploy — it does **not** fail the deploy. First-deploy case (no prior `spt@cplugs` entry) skips the prune entirely.
6. Cleans up the legacy manual-install dir at `~/.claude/plugins/spt/` if it exists as a regular directory. A manual install coexisting with the marketplace install makes Claude Code report the plugin as **not installed** in `/plugin` (see Troubleshooting below). Symlinked dirs are preserved for the dev-symlink workflow.
7. Runs `claude plugin install spt@cplugs` for cache-warm-up and `.orphaned_at` marker cleanup. **This is no longer the version-pointer flip** — the CLI is a no-op when `spt@cplugs` is already installed at any version (it only touches `lastUpdated`, not `version` / `installPath` / `gitCommitSha`). That silent no-op was the failure mode in the 1.9.8 → 1.9.9 deploy that left the pointer pinned to 1.9.8 (see `.planning/debug/26-06-live-skill-regression.md`). If `claude` is not in `PATH`, the script warns and tells you to run the command manually.
8. **Explicitly patches `~/.claude/plugins/installed_plugins.json`** to set the `spt@cplugs` entry's `version`, `installPath`, `gitCommitSha`, and `lastUpdated`. Atomic write via temp file + `Move-Item -Force`. This is the authoritative pointer flip — the step 7 CLI call is kept only for its cache-warm-up side effects. Uses `ConvertFrom-Json` / `ConvertTo-Json -Depth 10` (no `JavaScriptSerializer` — its earlier hand-rolled patch was removed for circular-reference crashes; the new patch round-trips through the PSObject graph safely).
9. **Re-reads `installed_plugins.json` and asserts `spt@cplugs.version == <Version>`** (and `installPath` matches the new cache dir). Any mismatch writes `ASSERTION FAILED: ...` to stderr and exits the script with status 1; the green "Deploy complete" banner never prints on drift.
10. Prints a reminder to run `/reload-plugins` inside Claude Code.

> The `Write-Step "Step N: ..."` labels in the script body still use their historical numbers (Step 3 for build, Step 4 for marketplace sync, etc. — Step 2 is intentionally absent) so log-grep tooling keeps matching prior runs. The numbered list above is the user-facing sequence; the in-script labels are the log contract.

### What it does NOT do

- Bump the plugin version in `plugin.json` unless `-Bump <patch|minor|major>` is passed.
- Push the spt repo. Even with `-Bump`, the bump commit lands locally and you push it yourself.
- Commit other changes in the spt repo (that's your job).
- Edit `~/.claude/settings.json`.
- Run `/reload-plugins` (that's a Claude Code slash command).
- Kill running `owl.exe` processes. Phase 18.4/18.5 shipped seamless binary handoff — old listeners self-migrate via stdio-relay and Psyche wrappers detach + rehydrate from `wrapper-state.json` once the new version is staged and `installed_plugins.json` flips (step 7 above). Killing them aborts the handoff and strands their session state.
- Wipe the entire cache tree. The old "blanket `rm -rf` on `cache/cplugs/spt`" behavior was correct before handoff shipped, but now it yanks the `.exe` out from under running processes mid-migration. The new targeted prune (step 5 above) keeps the PREVIOUS version dir on disk so in-flight handoffs complete.

### Two-phase changelog authoring (Phase 34 D-08 round 2)

Under the locked D-08 round 2 design (realigned in quick-260517-n4b post-UAT clarification), `-Bump` enforces a curation gate so each version-change Stop-hook surface ships with a real CHANGELOG entry inside the deployed bundle. The first `-Bump` for any new version aborts and seeds a TODO stub; the second `-Bump` (after the user fills it in and commits) proceeds.

**First `-Bump` run (no curated entry yet):**

1. **Abort gate** — refuses to proceed if `CHANGELOG.md` is missing OR contains a `TODO: changelog entry` marker from a prior bump.
2. **Curation gate** — checks for `## [<NewVersion>]` H2 header in `CHANGELOG.md`:
   - **Absent:** appends `## [<NewVersion>] - YYYY-MM-DD\n\n- TODO: changelog entry` to the working tree (uncommitted), then **EXIT 1** with instructions to fill and commit, then re-run. The current deploy does **not** succeed; no `plugin.json` / `Cargo.toml` mutation occurs.
   - **Present + TODO marker:** caught by abort gate above → EXIT 1.
   - **Present + curated body:** proceed past the gate.

**User action between runs:** Edit `CHANGELOG.md`, replace `- TODO: changelog entry` under the newly-stubbed `## [<NewVersion>]` H2 with real bullets in Keep-a-Changelog 1.1.0 categories (Added / Changed / Fixed / Removed / Deprecated / Security). Commit as e.g. `docs(NN): fill <NewVersion> CHANGELOG entry`.

**Second `-Bump` run (curated entry committed):**

1. Abort gate passes — no `TODO: changelog entry` marker remains.
2. Curation gate passes — `## [<NewVersion>]` H2 present with curated body.
3. Rewrite `plugin/spt/.claude-plugin/plugin.json` + `Cargo.toml` to the new version.
4. Stage + commit `plugin.json` + `Cargo.toml` (two files) as `chore: bump spt plugin to vX.Y.Z`. CHANGELOG.md is **not** in the bump commit — it landed in the prior curated-entry commit.
5. Build, sync to marketplace + cache (including curated `CHANGELOG.md`), refresh `installed_plugins.json`.

**Pre-authoring shortcut:** A caller who knows the next semver upfront can pre-author and commit the `## [<NewVersion>]` H2 with curated body before running `-Bump`. The curation gate sees the H2 present + curated and proceeds in one shot.

**Backstop:** If a stub somehow slips into a bump commit (manual flow, prior implementation), the next `-Bump`'s abort gate fires on the `TODO: changelog entry` marker and forces the user to curate before any further deploy.

### Bucket discipline for stub fill-in (quick-260520-uc2)

The version-change Stop hook surfaces curated CHANGELOG bullets to the user
on the next session boundary after a plugin upgrade. The user sees only the
release-facing buckets — implementation noise is filtered out at hook time.
The deploying agent (human or LLM) filling the `- TODO: changelog entry`
stub MUST follow these rules:

**Section vocabulary per H2 (`## [vX.Y.Z] - YYYY-MM-DD`):**

| Section | Use for | Surfaced to user? |
|---------|---------|---------------------|
| `### Added` | New user-facing capability (a flag, command, message, prompt, hook trigger they will notice) | YES |
| `### Changed` | Modified user-facing behavior (different message, different default, different timing) | YES |
| `### Fixed` | Bug fix the user actually observed or could observe | YES |
| `### Removed` | A user-facing capability that no longer exists | YES |
| `### Deprecated` | A capability the user should stop relying on | YES |
| `### Security` | Anything with user-relevant security impact | YES |
| `### Reverted` | A user-visible behavior was rolled back to a prior shape | YES |
| `### BTS` | Anything else: internal refactors, repo plumbing, deploy script changes, doc-string drift, test-only changes, code reverts that don't change user-visible behavior, commit SHAs, file-path callouts | **NO** — filtered out at hook time |

**UX-language rule for Added / Changed / Fixed (and the other surfaced
buckets):**
- Phrase as what the user sees, does, or runs. "`/spt:live` now offers an
  Other option" — not "added `AuqOption::Other` to `live::auto_pick`".
- Do NOT mention: code paths (`src/...`), function or struct names, prompt
  XML element names (`<spt-foo>`), commit SHAs, planning doc references
  (`Phase 34 D-08`, `quick-260517-n4b`), Rust types, file paths inside
  `plugin/spt/`, internal env-var names that are not part of the
  documented user surface.
- Keep bullets terse. One sentence each is the target; two short sentences
  is the ceiling. If you need more, you are probably writing BTS content.
- The Stop-hook "Yes, highlights only" path picks 2-5 bullets across the
  whole transition; bullets that are themselves short and UX-focused give
  it the right material to work with.

**BTS rule (anything off the UX path):**
- Repo-management changes (Cargo manifest tweaks unless they change runtime
  behavior, CI config, .gitignore, planning docs).
- Deploy-sequence changes (DEPLOY.ps1, DEPLOY.md, marketplace cache logic)
  unless the user's deploy workflow changes.
- Commit reverts where the reverted code never reached the user's machine.
- Doc-string / comment drift fixes.
- Test-only additions or framework changes.
- Internal refactors with no observable behavior change.
- Naming, struct-field, or fn-signature changes inside `src/`.

**When in doubt, ask: "Would a user typing `/spt:live` notice the
difference?"** If no, it's BTS.

**Examples of correct bucketing** (from real prior bullets, reframed):

- v1.10.20 "version-change owl message routed via TCP-first deliver_body" →
  `### Fixed`: "Version-change announcements arrive in-line with other
  high-priority messages instead of waiting for the next poll tick."
  (The file-path / fn-name detail moves to `### BTS` as: "Routed
  `version_changelog` payload through `deliver_body` like every other owl
  message.")
- v1.10.20 "WORKING_PERCH_NOTICE TCP-first flip reverted" — no user-visible
  symptom changed, so it belongs entirely in `### BTS`, not `### Reverted`.
- v1.10.18 the entire "260520-r2f commune-reference write-path fix" — the
  user-visible bullet is `### Fixed`: "Fresh `/spt:live` start now delivers
  first-commune context to the live agent reliably." Every file/line
  citation in the existing entry moves to `### BTS`.
- v1.10.17 chunking work — `### Fixed`: "Long poll-listener events no
  longer get truncated by the Claude Code Monitor at ~500 characters."
  Internal const names and chunk-boundary detail → `### BTS`.

**Backward compatibility:** Pre-v1.10.21 entries that pre-date this
discipline are left as-is. The hook's "skip `### BTS`" filter is a no-op
on them (they have no `### BTS` section) and the full-changelog path will
surface them verbatim per the existing behavior. The new discipline
applies prospectively starting with the next bump.

### Parameters

| Parameter | Default | Purpose |
|-----------|---------|---------|
| `-RepoRoot` | script's parent-of-parent dir | Path to the spt repo root |
| `-Marketplace` | `~/.claude/plugins/marketplaces/cplugs` | Local cplugs marketplace clone |
| `-PluginDir` | `~/.claude/plugins/spt` | Legacy manual-install dir. Removed by step 6 if present as a regular dir (it would otherwise conflict with the marketplace install). Symlinks at this path are preserved. |
| `-Bump` | _(unset)_ | Increment semver: `patch`/`minor`/`major`. Rewrites `plugin.json` and commits in the spt repo (no push). Aborts if `plugin.json` has uncommitted changes. |
| `-SkipPush` | off | Commit marketplace locally, don't push. Does not affect the `-Bump` commit (never pushed). |
| `-SkipMarketplaceCommit` | off | Skip marketplace commit+push entirely |
| `-DryRun` | off | Print every step with `[DRY-RUN]` prefix; no mutations |

### Example: dev loop without pushing

```powershell
powershell -ExecutionPolicy Bypass -File docs/DEPLOY.ps1 -SkipPush
```

### Example: dry-run to preview

```powershell
powershell -ExecutionPolicy Bypass -File docs/DEPLOY.ps1 -DryRun
```

### Example: bump patch + full deploy

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

### Troubleshooting: plugin shows as "not installed" in `/plugin`

**Symptom:** `/plugin` reports `spt` as not installed (and refuses to update), even though `installed_plugins.json` has `spt@cplugs` and the plugin clearly works. Commands like `/spt:*` may still function via the marketplace install while the UI misreports state.

**Cause:** A legacy manual-install directory at `~/.claude/plugins/spt/` (with its own `.claude-plugin/plugin.json`) living alongside the marketplace install at `~/.claude/plugins/cache/cplugs/spt/<version>/`. Claude Code enumerates both, and the unmanaged one — not tracked in `installed_plugins.json` — causes the conflict. Usually a leftover from a pre-marketplace deploy or from running the old "Deploy as Plugin (manual)" flow on top of a marketplace install.

**Fix:**

```bash
rm -rf ~/.claude/plugins/spt/
```

Then restart Claude Code (or run `/reload-plugins`). DEPLOY.ps1 step 6 now auto-cleans this directory on every marketplace deploy, so re-running the deploy script also resolves it.

> The manual steps documented in the sections below remain the authoritative spec. The script automates them; if the two ever diverge, the manual steps are correct and the script must be updated to match.

## Files Overview

| File | Purpose |
|------|---------|
| `owl.exe` | Native binary (compiled from `src/`) -- handles all owl and live commands |
| `plugin/spt/` | Plugin directory (deployed via marketplace or manually) |
| `plugin/spt/.claude-plugin/plugin.json` | Plugin manifest (name: spt) |
| `plugin/spt/skills/*/SKILL.md` | Per-command skill files (one directory per /spt: command) |
| `plugin/spt/skills/commune/commune.md` | Commune protocol for Self agents |
| `plugin/spt/hooks/hooks.json` | SessionStart + SessionEnd hook registrations |
| `psyche.md` | Psyche agent prompt template (embedded in binary via `include_str!`) |

## Deploy via Marketplace (recommended)

The cplugs private marketplace is the primary way to install spt. It bundles skill files, hooks, and the native binary.

### Marketplace Setup (one-time)

Register the cplugs marketplace if not already registered:

```bash
# Clone the marketplace repo
git clone https://github.com/SaberMage/cplugs.git ~/.claude/plugins/marketplaces/cplugs

# Register in ~/.claude/plugins/known_marketplaces.json
# Add a "cplugs" entry with source: { source: "github", repo: "SaberMage/cplugs" }
```

### Install from Marketplace

```
/plugin install spt@cplugs
```

### Update Marketplace Plugin Files

When skill files, hooks, or the binary change in this repo:

1. **Bump the version** in `plugin/spt/.claude-plugin/plugin.json` (e.g., `1.5.0` → `1.5.1`). The plugin manager compares this version to the cached copy — if they match, it skips the update entirely.
2. Build: `cargo build --release`
3. **Copy to marketplace** — binary, skills, hooks, and plugin manifest:
   ```bash
   MARKET=~/.claude/plugins/marketplaces/cplugs/plugins/spt
   cp target/release/owl.exe "$MARKET/"
   cp -r plugin/spt/skills/* "$MARKET/skills/"
   cp -r plugin/spt/hooks/* "$MARKET/hooks/"
   cp plugin/spt/.claude-plugin/plugin.json "$MARKET/.claude-plugin/"
   ```
4. **Commit and push** the marketplace repo:
   ```bash
   cd ~/.claude/plugins/marketplaces/cplugs
   git add plugins/spt/ && git commit -m "spt: <describe change>" && git push
   ```
5. **Sync the new version into the cache, then (optionally) targeted-prune older versions.** `/plugin install` may not refresh the cache if the plugin is already enabled, so the new version directory must be created explicitly. **Do NOT stop live agents first** — Phase 18.4/18.5 shipped seamless binary handoff, and the handoff fires when `installed_plugins.json` flips in step 6 below. Old listeners self-migrate via stdio-relay and Psyche wrappers detach + rehydrate from `wrapper-state.json`. Killing `owl.exe` aborts that handoff.

   **Do NOT wipe the entire cache tree.** Running `owl.exe` processes are exec'ing from the PREVIOUS version directory until they migrate. `rm -rf ~/.claude/plugins/cache/cplugs/spt` yanks the `.exe` out from under them mid-handoff. At minimum, keep the PREVIOUS version dir on disk.

   **Sync all files** (not just the ones you changed) — partial syncs cause subtle mismatches between the binary and skill definitions:

   ```bash
   # Read the version from the plugin manifest — this is the authoritative version number
   VERSION=$(node -p "require('./plugin/spt/.claude-plugin/plugin.json').version")

   # Sync the new version into the cache (do NOT rm -rf the whole spt/ cache tree).
   CACHE=~/.claude/plugins/cache/cplugs/spt/$VERSION
   mkdir -p "$CACHE/.claude-plugin" "$CACHE/skills" "$CACHE/hooks"
   cp target/release/owl.exe "$CACHE/"
   cp -r plugin/spt/skills/* "$CACHE/skills/"
   cp -r plugin/spt/hooks/* "$CACHE/hooks/"
   cp plugin/spt/.claude-plugin/plugin.json "$CACHE/.claude-plugin/"

   # (Optional cleanup) After the next session start confirms no owl.exe has the
   # old .exe mapped, you can remove stale older-version dirs individually:
   #   rm -rf ~/.claude/plugins/cache/cplugs/spt/<old-version>
   # Keep the PREVIOUS version and the current $VERSION in place. PowerShell
   # users should prefer docs/DEPLOY.ps1, which handles this keep-set logic
   # automatically and tolerates sharing-violation residue.
   ```
6. **Refresh plugin state via the Claude Code CLI.** After the manual cache sync in step 5, `installed_plugins.json` still holds the `gitCommitSha` from the previous deploy; on the next session start Claude Code sees a mismatch against cplugs marketplace HEAD, writes `.orphaned_at` into the freshly-deployed cache dir, and refuses to source the SessionStart hook's `$OWL`/`$LIVE` exports — every `/spt:*` skill silently breaks. This is also the trigger for the seamless handoff: flipping `installPath` / `version` in `installed_plugins.json` is what old `owl.exe` listeners detect to initiate their self-migration to the new cache dir. See `.planning/debug/spt-env-vars-empty.md` for the full env-var evidence trail.

   The one-liner — canonical path, handles `installed_plugins.json` rewrite and orphan-marker cleanup atomically:

   ```bash
   claude plugin install spt@cplugs
   ```

   Equivalent to `/plugin install spt@cplugs` from inside Claude Code. Works identically from a script or CI. Restart the Claude Code session afterwards so the un-orphaned plugin's SessionStart hook runs cleanly and `$OWL`/`$LIVE` populate in Bash subprocesses.

   **Do not hand-patch `installed_plugins.json`.** Earlier revisions of this doc showed `jq` / `sed` fallbacks; they're brittle (an older DEPLOY.ps1 version crashed on a PowerShell `JavaScriptSerializer` circular-reference error here) and Claude Code may silently reject the result. If the CLI is unavailable, install it — don't edit the manifest by hand.
7. Run `/reload-plugins` in Claude Code to pick up skill file changes

### Enable the Plugin

Add to `~/.claude/settings.json`:

```json
{
  "enabledPlugins": {
    "spt@cplugs": true
  }
}
```

## Build

```bash
cargo build --release
```

The binary is at `target/release/owl.exe` (Windows) or `target/release/owl` (Linux/macOS). It handles both owl and live commands -- no separate binaries needed.

## Deploy as Plugin (manual, without marketplace)

> **Warning — do not combine with the marketplace install.** If you have `spt@cplugs` in `installed_plugins.json` (i.e. you installed via `/plugin install spt@cplugs`), do **not** also create `~/.claude/plugins/spt/`. Claude Code will enumerate both and report the marketplace install as **not installed** in `/plugin`, refusing to update it. The two flows are exclusive. See "Troubleshooting: plugin shows as 'not installed'" above.

If you don't have marketplace access, you can deploy the plugin manually. This replaces the old `~/.claude/skills/owl/` and `~/.claude/skills/live/` directories.

```bash
# Build
cargo build --release

# Deploy plugin directory
mkdir -p ~/.claude/plugins/spt
cp -r plugin/spt/.claude-plugin ~/.claude/plugins/spt/
cp -r plugin/spt/skills ~/.claude/plugins/spt/
cp -r plugin/spt/hooks ~/.claude/plugins/spt/
# Deploy binary into plugin directory
# IMPORTANT: hooks.json uses 'owl.exe' as the canonical binary name on ALL platforms.
# Windows:
cp target/release/owl.exe ~/.claude/plugins/spt/
# Linux/macOS -- copy as owl.exe so hooks.json works without platform detection:
cp target/release/owl ~/.claude/plugins/spt/owl.exe

# Remove old skill directories (clean break per D-08)
rm -rf ~/.claude/skills/owl
rm -rf ~/.claude/skills/live
```

**Cross-platform note:** The plugin's `hooks.json` references `$CLAUDE_PLUGIN_ROOT/owl.exe` for all hooks (`CLAUDE_PLUGIN_ROOT` is auto-provided by Claude Code's plugin system, pointing to the active plugin version directory). Using `owl.exe` as the canonical binary name on all platforms avoids needing platform-detection logic. On Unix, the binary is simply copied/renamed to `owl.exe` during deployment. Unix has no restriction on `.exe` extensions -- the file runs fine.

After deploying:

- The plugin's SessionStart hook auto-injects `$OWL` and `$LIVE` env vars via CLAUDE_ENV_FILE
- The plugin's SessionEnd hook runs cleanup-session
- No manual settings.json editing needed
- `$OWL` and `$LIVE` are set dynamically via CLAUDE_ENV_FILE on each session start, always pointing to the current plugin version's binary

## Remove Old Skill Directories

When switching to the plugin (via marketplace or manual), you **must** remove the old skill directories to avoid confusion:

```bash
# Required: remove old skill directories
rm -rf ~/.claude/skills/owl
rm -rf ~/.claude/skills/live
```

The plugin replaces both old skill directories. Old `/owl` and `/live` skill commands will no longer work. All commands are now under `/spt:` (e.g., `/spt:ready`, `/spt:live`). The binary CLI (`$OWL`, `$LIVE`) is unchanged.

## Env Var Setup

The plugin's SessionStart hook automatically writes `$OWL` and `$LIVE` env vars via `CLAUDE_ENV_FILE` on each session start. These point to the current plugin version's binary — no manual configuration needed.

**If env vars are missing** (e.g., `$OWL: command not found`), restart the Claude Code session — the SessionStart hook will re-run and populate `$OWL` / `$LIVE` from the current active plugin version via `CLAUDE_ENV_FILE`. There is no in-session force-write command; that used to exist as `/spt:env-setup` but was removed because its `settings.json` fallback pinned binary paths and broke upgrades. If restarting doesn't fix it, run `$OWL doctor` to diagnose — something is wrong with the plugin install or the hook.

**Stale env vars in settings.json:** If you have `OWL`/`LIVE` entries in `~/.claude/settings.json` from a pre-plugin install, remove them. They pin to a specific version and will override the plugin's dynamic env var injection, causing hooks to call the wrong binary after upgrades.

## Legacy / Standalone Deployment (removed)

Standalone deployment via `~/.claude/skills/owl/` and `~/.claude/skills/live/` is no longer supported. The root-level `SKILL.md`, `LIVE-SKILL.md`, and `commune.md` files have been removed. All skill definitions live under `plugin/spt/skills/`. Use the plugin system (marketplace or manual) for deployment.

## Development Workflow

For plugin development, use `--plugin-dir` to test the plugin from the repo without deploying:

```bash
# Build first
cargo build --release

# Copy binary into plugin dir
# Windows:
cp target/release/owl.exe plugin/spt/
# Linux/macOS:
cp target/release/owl plugin/spt/owl.exe

# Run Claude with the local plugin directory
claude --plugin-dir ./plugin/spt

# Or symlink for persistent development:
# ln -s $(pwd)/plugin/spt ~/.claude/plugins/spt
```

Note: When using `--plugin-dir`, the binary must already be built and present in the plugin directory as `owl.exe`.

## Bundle for Sharing

```bash
cargo build --release
# Windows:
cp target/release/owl.exe plugin/spt/
# Linux/macOS:
cp target/release/owl plugin/spt/owl.exe
tar czf spt-plugin.tar.gz -C plugin spt/
```

Install from bundle:

```bash
mkdir -p ~/.claude/plugins
tar xzf spt-plugin.tar.gz -C ~/.claude/plugins/
rm -rf ~/.claude/skills/owl ~/.claude/skills/live
```

Then start a new Claude Code session. The plugin's SessionStart hook will auto-inject `$OWL` and `$LIVE`.

## Commit Changes

```bash
git add plugin/ src/ psyche.md docs/ Cargo.toml
git commit -m "owl: <describe change>"
```


Bash

node -p "require('./plugin/spt/.claude-plugin/plugin.json').version" 2>&1; echo "---changelog head---"; head -40 CHANGELOG.md 2>&1

Current version + changelog head


1.11.20
---changelog head---
# Changelog

All notable changes to the SPT (Spacetime / Sentience Pocket Transacter) plugin are documented in this file.

The format is based on [Keep a Changelog 1.1.0](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Entries authored retroactively from `git log --grep='chore: bump'` at Phase 34 (v1.7.1 milestone).

## [1.11.20] - 2026-05-28

### Fixed
- **`p-<project>` branches on `spt-agent-storage` now actually carry project content.** A project's psyche-sync branch (`p-<project>`) had its ref pushed to origin at seed-init time but never advanced past that initial commit — no commune carrying `<project-context>` ever landed on it, so the branch was visually identical to "absent" on the GitHub web UI. Root cause: `route_two_slice` in `src/owl/echo_commune.rs` wrote the project `<agent>.md` file into `psyches/tracked/projects/<name>/` *before* calling `commit_project_payload`, which pre-populated the target directory and made the downstream `git worktree add` (both primary and `branch already exists` fallback) abort with `'<path>' already exists`. The worktree never materialized, no commit fired, and the error was silently swallowed as `(payload on disk)`. Fix reorders all 3 production write-sites in `echo_commune.rs`: `ensure_project_worktree` runs FIRST, then `fs::write`, then `commit_project_payload`. A defense-in-depth salvage path in `src/common/tracked.rs::ensure_worktree` also auto-repairs already-broken installs — when a target dir is non-empty and lacks `.git` and both `worktree add` attempts fail with `'... already exists'`, contents move to `<wt>.salvage-<unix_ts>/`, `worktree add` retries, salvaged files restore into the now-real worktree. Existing broken installs heal on the next commune carrying `<project-context>`; no manual recovery required.

### BTS
- Quick `260527-s8l`: 3 reorder sites in `src/owl/echo_commune.rs` + new salvage branch in `src/common/tracked.rs::ensure_worktree`. Of the 9 grep-matched call sites surveyed across `echo_commune.rs`, `signoff.rs`, `context.rs`, only the 3 in `echo_commune.rs` were production bugs; the rest are test code or read paths (verified before editing). 2 new regression tests (echo_commune reorder RED→GREEN; ensure_worktree salvage round-trip). `cargo build --release` clean; `cargo test --lib` 951 pass / 0 fail / 5 ignored; `cargo test --test handoff_integration` 19/19; `cargo test --test hook_chain` 7/7. Commits `4866fb9`, `2eb23eb`, debug session at `.planning/debug/p-branches-not-pushed-origin.md`.

## [1.11.19] - 2026-05-27

### Fixed
- **`/psyche-sync-setup` no longer aborts on a re-run or against a stale remote.** Two idempotency gaps in the setup flow (`accept_flow`) could leave cross-machine sync un-enableable after any partial first attempt. (a) If a prior run had already wired an `origin` remote — and because git worktrees share one config, a single agent/project worktree wiring it counts — the setup's `git remote add origin <url>` silently no-op'd, so a stale or wrong backup URL survived and the seeding `git push --all` hit `Repository not found` (404) and exited 1. Origin is now wired authoritatively: each worktree gets `remote add` followed by an unconditional `remote set-url`, so the remote always points at the canonical `https://github.com/<user>/spt-agent-storage.git`. (b) If the GitHub repo already existed (a prior run created it but failed before persisting `state=Enabled`), `gh repo create` exited nonzero with "Name already exists" and the whole flow aborted; that stderr is now treated as idempotent success (checked after the unchanged missing-scope / HTTP 403 browser-fallback path), so re-running setup completes instead of dead-ending. Both git calls remain soft-failed; the `$OWL psyche-sync-setup` exit-code contract is unchanged.

### BTS
- Quick `260527-6ah`: `src/common/sync.rs` `accept_flow` Step 1 (case-insensitive `"already exists"` fall-through to success) + Step 4 (add-then-unconditional-`set-url` per worktree). Two new `#[cfg(unix)]` `fake_gh` regression tests (`accept_flow_repo_exists_is_idempotent`, `accept_flow_corrects_stale_worktree_origin`), excluded from Windows CI per existing convention. `cargo build --release` clean; `cargo test --lib common::sync` 23/23. Diagnosed from a live Windows failure where a leftover `origin=https://github.com/u/spt-agent-storage.git` poisoned setup; the affected runtime was recovered out-of-band (origin `set-url` → correct URL, `push --all`, per-branch upstream, `settings.json sync.state=enabled`) and verified via `$OWL doctor`. Commits `b7668dd`, `e4ceca4`.

## [1.11.18] - 2026-05-27

### Fixed
- **Cross-machine sync offer now actually renders.** The session-start prompt that offers to enable Psyche sync ("Enable now / No, never / Remind me in 12h") never appeared — it was written as passive SessionStart context that Claude receives but does not act on. It now arrives as an active in-session message on the live agent's own channel, so the offer surfaces on `$LIVE start`/`revive` and on a `/clear`|`/compact` boundary (live agents only; plain startups stay silent).
- **`$OWL doctor` no longer shows green for a failing sync.** A per-agent sync row with recorded failures could display PASS once its retry backoff window elapsed. Failing rows now read WARN (failures, no active retry) or FAIL (retry pending or hard stop) and surface the failure reason.

### BTS
- Phase 35 SC1 gap-closure (plan 35-10): new `src/owl/sync_prompt.rs` active-delivery module carrying a self-contained `<instructions>` envelope (mirrors the version-change pattern); both triggers rewired through it; the `.sync-prompt-due` sentinel + `queue_sync_prompt_if_due` passive path removed; always-loaded `<spt-psyche-sync-prompt>` handler added to `live/SKILL.md`; render-path test (`tests/sync_prompt_render.rs`) asserts the spool row + envelope content — the test gap that let the defect ship; operator-UAT wording corrected to the active-delivery model. 5 commits. Cross-machine + in-session render validation remains operator UAT (`35-HUMAN-UAT.md`).

## [1.11.17] - 2026-05-26

### Added
- **Cross-machine context sync (opt-in).** A new `/psyche-sync-setup` flow backs up a live agent's context to a private GitHub repo, so the same agent can resume with its history on another machine. On session start, SPT now offers to enable sync if you haven't set it up yet.
- **`$OWL doctor` sync status.** `doctor` now reports a Sync section — whether cross-machine sync is enabled, the backup remote, and any recent sync failures with their retry timing.

### BTS
- Phase 35 implementation: settings.json sync namespace data layer, `src/common/sync.rs` subprocess facade, Unix detached-spawn helper, UserPromptSubmit async-pull dispatch, post-commit pull-then-push + lazy-worktree remote inheritance wired into `tracked.rs`, plus four `cargo test --test sync_*` integration suites and a two-machine operator UAT runbook. 28 commits across 9 plans / 5 waves.
- Published mid-v1.8-milestone (off the normal milestone-close bump cadence) to provide an installable build for the two-machine operator UAT. Cross-machine sync ships in this build but is not yet validated against real GitHub — that is what the UAT covers.


Current: v1.11.20. CHANGELOG for new version absent — first `-Bump` will stub + abort. Need bump level.


AskUserQuestion

Question: Phase 35.1 bump level (current v1.11.20)?

Choices:
Bug-fix release. Phase 35.1 is a data-loss fix + CR-01/CR-02 hardening — patch semantics fit.
Adds `is_wrapper_alive` + `PHASE35_LIST_ORPHAN_GRACE_SECS` primitives + `doctor --fix orphan-dirs` surface. New user-facing capability could justify minor.
Breaking. `list_filter::collect` signature lost `cleanup_orphans` param. Internal-only — no external consumers — so probably not major.


Your questions have been answered: "Phase 35.1 bump level (current v1.11.20)?"="patch → v1.11.21 (Recommended)". You can now continue with these answers in mind.

Choices:
Bug-fix release. Phase 35.1 is a data-loss fix + CR-01/CR-02 hardening — patch semantics fit.
Adds `is_wrapper_alive` + `PHASE35_LIST_ORPHAN_GRACE_SECS` primitives + `doctor --fix orphan-dirs` surface. New user-facing capability could justify minor.
Breaking. `list_filter::collect` signature lost `cleanup_orphans` param. Internal-only — no external consumers — so probably not major.

Question: Phase 35.1 bump level (current v1.11.20)?


1	# Changelog
2
3	All notable changes to the SPT (Spacetime / Sentience Pocket Transacter) plugin are documented in this file.
4
5	The format is based on [Keep a Changelog 1.1.0](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Entries authored retroactively from `git log --grep='chore: bump'` at Phase 34 (v1.7.1 milestone).
6
7	## [1.11.20] - 2026-05-28
8
9	### Fixed
10	- **`p-<project>` branches on `spt-agent-storage` now actually carry project content.** A project's psyche-sync branch (`p-<project>`) had its ref pushed to origin at seed-init time but never advanced past that initial commit — no commune carrying `<project-context>` ever landed on it, so the branch was visually identical to "absent" on the GitHub web UI. Root cause: `route_two_slice` in `src/owl/echo_commune.rs` wrote the project `<agent>.md` file into `psyches/tracked/projects/<name>/` *before* calling `commit_project_payload`, which pre-populated the target directory and made the downstream `git worktree add` (both primary and `branch already exists` fallback) abort with `'<path>' already exists`. The worktree never materialized, no commit fired, and the error was silently swallowed as `(payload on disk)`. Fix reorders all 3 production write-sites in `echo_commune.rs`: `ensure_project_worktree` runs FIRST, then `fs::write`, then `commit_project_payload`. A defense-in-depth salvage path in `src/common/tracked.rs::ensure_worktree` also auto-repairs already-broken installs — when a target dir is non-empty and lacks `.git` and both `worktree add` attempts fail with `'... already exists'`, contents move to `<wt>.salvage-<unix_ts>/`, `worktree add` retries, salvaged files restore into the now-real worktree. Existing broken installs heal on the next commune carrying `<project-context>`; no manual recovery required.

# Changelog

All notable changes to the SPT (Spacetime / Sentience Pocket Transacter) plugin are documented in this file.

The format is based on [Keep a Changelog 1.1.0](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Entries authored retroactively from `git log --grep='chore: bump'` at Phase 34 (v1.7.1 milestone).

## [1.11.20] - 2026-05-28

### Fixed
- **`p-<project>` branches on `spt-agent-storage` now actually carry project content.** A project's psyche-sync branch (`p-<project>`) had its ref pushed to origin at seed-init time but never advanced past that initial commit — no commune carrying `<project-context>` ever landed on it, so the branch was visually identical to "absent" on the GitHub web UI. Root cause: `route_two_slice` in `src/owl/echo_commune.rs` wrote the project `<agent>.md` file into `psyches/tracked/projects/<name>/` *before* calling `commit_project_payload`, which pre-populated the target directory and made the downstream `git worktree add` (both primary and `branch already exists` fallback) abort with `'<path>' already exists`. The worktree never materialized, no commit fired, and the error was silently swallowed as `(payload on disk)`. Fix reorders all 3 production write-sites in `echo_commune.rs`: `ensure_project_worktree` runs FIRST, then `fs::write`, then `commit_project_payload`. A defense-in-depth salvage path in `src/common/tracked.rs::ensure_worktree` also auto-repairs already-broken installs — when a target dir is non-empty and lacks `.git` and both `worktree add` attempts fail with `'... already exists'`, contents move to `<wt>.salvage-<unix_ts>/`, `worktree add` retries, salvaged files restore into the now-real worktree. Existing broken installs heal on the next commune carrying `<project-context>`; no manual recovery required.


Edit

## [1.11.21] - 2026-05-28

### Fixed
- **`$OWL list` can no longer accidentally delete perch directories.** A read command should never mutate state; before this release, `list` could `rm -rf` an orphan-classified perch dir as a side effect, occasionally taking healthy in-flight psyche perches with it. `list` is now strictly read-only.
- **`$OWL list` no longer mis-classifies in-flight psyche workers as orphans on Windows.** Windows PID recycling could make a live psyche perch's info.json look stale during a poll-rewrite race; `list` now consults wrapper liveness plus a 60-second mtime grace before flagging anything as an orphan.
- **`$OWL doctor --fix` stale-perch sweep no longer demotes healthy live agents.** Previously, the sweep removed the `ready` sentinel from a wrapper-owned psyche perch whose info.json carried a recycled PID, briefly knocking the agent offline. The sweep now skips perches whose wrapper is alive.

### Added
- **`$OWL list` reports orphan-perch count.** When any orphan-perch directories are present, `list` prints a hint suggesting `$OWL doctor --fix` to clean them up. `$LIVE list` does not emit the hint.
- **`$OWL doctor --fix` garbage-collects orphan perch directories.** Nested perch dirs are soft-cleaned (sentinel removed); top-level orphan dirs are hard-deleted. A safety guard refuses to wipe a top-level dir that still hosts non-empty `nested/` children — the cascade-wipe class this release was built to eliminate.

### BTS
- Phase 35.1: 8 production `fs::write(info.json)` sites converted to atomic write (tmp + rename) with a source-order regression test that mechanically prevents reintroduction; new `is_wrapper_alive(self_id)` + `PHASE35_LIST_ORPHAN_GRACE_SECS=60` primitives in `src/common/owlery.rs`; `list_filter::collect` lost its `cleanup_orphans` parameter and gained `orphan_count: usize`; `check_orphan_dirs` walks Layer-1 + Layer-2 with cascade-safe routing; status tag inventory gains `SOFT-CLEANED:id`. 4 plans across 3 waves, 12 code-review fix commits (CR-01 `is_online` no-info short-circuit, CR-02 wrapper-alive bypass parity in `check_stale_perches`, WR-01 drop no-op `Option<Vec>` wrap, WR-03 `check_duplicate_flat_nested` non-empty-nested guard, WR-04 surface `gated_count` in PASS row, WR-05 partial-state stderr detail, WR-06 doc the LIST-NOMUT-01 carve-out, IN-02/IN-04 doc-comment notes, IN-05 reverse `OWL_SESSION_ID` precedence to stdin > env in `hook_subagent_start`). Pre-existing `tests/native_wrapper_state_retry.rs` build error (missing `pulse_psyche` field from pre-35.1 commit `3616ed1`) is not a regression. Code review status: partial — 5 findings skipped per reviewer directive (WR-02 / WR-07 refactor scope, IN-01 / IN-03 / IN-06 cosmetic).

## [1.11.20] - 2026-05-28


The file C:\Users\decid\Documents\projects\claude_skill_owl\CHANGELOG.md has been updated successfully. (file state is current in your context — no need to Read it back)

## [1.11.21] - 2026-05-28

### Fixed
- **`$OWL list` can no longer accidentally delete perch directories.** A read command should never mutate state; before this release, `list` could `rm -rf` an orphan-classified perch dir as a side effect, occasionally taking healthy in-flight psyche perches with it. `list` is now strictly read-only.
- **`$OWL list` no longer mis-classifies in-flight psyche workers as orphans on Windows.** Windows PID recycling could make a live psyche perch's info.json look stale during a poll-rewrite race; `list` now consults wrapper liveness plus a 60-second mtime grace before flagging anything as an orphan.
- **`$OWL doctor --fix` stale-perch sweep no longer demotes healthy live agents.** Previously, the sweep removed the `ready` sentinel from a wrapper-owned psyche perch whose info.json carried a recycled PID, briefly knocking the agent offline. The sweep now skips perches whose wrapper is alive.

### Added
- **`$OWL list` reports orphan-perch count.** When any orphan-perch directories are present, `list` prints a hint suggesting `$OWL doctor --fix` to clean them up. `$LIVE list` does not emit the hint.
- **`$OWL doctor --fix` garbage-collects orphan perch directories.** Nested perch dirs are soft-cleaned (sentinel removed); top-level orphan dirs are hard-deleted. A safety guard refuses to wipe a top-level dir that still hosts non-empty `nested/` children — the cascade-wipe class this release was built to eliminate.

### BTS
- Phase 35.1: 8 production `fs::write(info.json)` sites converted to atomic write (tmp + rename) with a source-order regression test that mechanically prevents reintroduction; new `is_wrapper_alive(self_id)` + `PHASE35_LIST_ORPHAN_GRACE_SECS=60` primitives in `src/common/owlery.rs`; `list_filter::collect` lost its `cleanup_orphans` parameter and gained `orphan_count: usize`; `check_orphan_dirs` walks Layer-1 + Layer-2 with cascade-safe routing; status tag inventory gains `SOFT-CLEANED:id`. 4 plans across 3 waves, 12 code-review fix commits (CR-01 `is_online` no-info short-circuit, CR-02 wrapper-alive bypass parity in `check_stale_perches`, WR-01 drop no-op `Option<Vec>` wrap, WR-03 `check_duplicate_flat_nested` non-empty-nested guard, WR-04 surface `gated_count` in PASS row, WR-05 partial-state stderr detail, WR-06 doc the LIST-NOMUT-01 carve-out, IN-02/IN-04 doc-comment notes, IN-05 reverse `OWL_SESSION_ID` precedence to stdin > env in `hook_subagent_start`). Pre-existing `tests/native_wrapper_state_retry.rs` build error (missing `pulse_psyche` field from pre-35.1 commit `3616ed1`) is not a regression. Code review status: partial — 5 findings skipped per reviewer directive (WR-02 / WR-07 refactor scope, IN-01 / IN-03 / IN-06 cosmetic).

## [1.11.20] - 2026-05-28

The format is based on [Keep a Changelog 1.1.0](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Entries authored retroactively from `git log --grep='chore: bump'` at Phase 34 (v1.7.1 milestone).

+- **`$OWL list` can no longer accidentally delete perch directories.** A read command should never mutate state; before this release, `list` could `rm -rf` an orphan-classified perch dir as a side effect, occasionally taking healthy in-flight psyche perches with it. `list` is now strictly read-only.

+- **`$OWL list` no longer mis-classifies in-flight psyche workers as orphans on Windows.** Windows PID recycling could make a live psyche perch's info.json look stale during a poll-rewrite race; `list` now consults wrapper liveness plus a 60-second mtime grace before flagging anything as an orphan.

+- **`$OWL doctor --fix` stale-perch sweep no longer demotes healthy live agents.** Previously, the sweep removed the `ready` sentinel from a wrapper-owned psyche perch whose info.json carried a recycled PID, briefly knocking the agent offline. The sweep now skips perches whose wrapper is alive.

+- **`$OWL list` reports orphan-perch count.** When any orphan-perch directories are present, `list` prints a hint suggesting `$OWL doctor --fix` to clean them up. `$LIVE list` does not emit the hint.

+- **`$OWL doctor --fix` garbage-collects orphan perch directories.** Nested perch dirs are soft-cleaned (sentinel removed); top-level orphan dirs are hard-deleted. A safety guard refuses to wipe a top-level dir that still hosts non-empty `nested/` children — the cascade-wipe class this release was built to eliminate.

+- Phase 35.1: 8 production `fs::write(info.json)` sites converted to atomic write (tmp + rename) with a source-order regression test that mechanically prevents reintroduction; new `is_wrapper_alive(self_id)` + `PHASE35_LIST_ORPHAN_GRACE_SECS=60` primitives in `src/common/owlery.rs`; `list_filter::collect` lost its `cleanup_orphans` parameter and gained `orphan_count: usize`; `check_orphan_dirs` walks Layer-1 + Layer-2 with cascade-safe routing; status tag inventory gains `SOFT-CLEANED:id`. 4 plans across 3 waves, 12 code-review fix commits (CR-01 `is_online` no-info short-circuit, CR-02 wrapper-alive bypass parity in `check_stale_perches`, WR-01 drop no-op `Option<Vec>` wrap, WR-03 `check_duplicate_flat_nested` non-empty-nested guard, WR-04 surface `gated_count` in PASS row, WR-05 partial-state stderr detail, WR-06 doc the LIST-NOMUT-01 carve-out, IN-02/IN-04 doc-comment notes, IN-05 reverse `OWL_SESSION_ID` precedence to stdin > env in `hook_subagent_start`). Pre-existing `tests/native_wrapper_state_retry.rs` build error (missing `pulse_psyche` field from pre-35.1 commit `3616ed1`) is not a regression. Code review status: partial — 5 findings skipped per reviewer directive (WR-02 / WR-07 refactor scope, IN-01 / IN-03 / IN-06 cosmetic).


Bash

git add CHANGELOG.md && git commit -m "docs(35.1): fill v1.11.21 CHANGELOG entry

Pre-author CHANGELOG entry per DEPLOY.md two-phase changelog gate so the
upcoming -Bump patch run proceeds in one shot.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>" 2>&1 | tail -5

Commit changelog entry


[main 2e62194] docs(35.1): fill v1.11.21 CHANGELOG entry
 1 file changed, 14 insertions(+)


PowerShell

powershell -ExecutionPolicy Bypass -File docs/DEPLOY.ps1 -Bump patch 2>&1 | Select-Object -Last 60

Deploy with patch bump


    |
790 | fn route_project_slot(
    |    ^^^^^^^^^^^^^^^^^^

warning: function `should_fire` is never used
   --> src\live\wrapper\echo_fire.rs:124:15
    |
124 | pub(crate) fn should_fire(sentinel: &Path, window: Duration) -> bool {
    |               ^^^^^^^^^^^

warning: `owl` (lib) generated 9 warnings (run `cargo fix --lib -p owl` to apply 2 suggestions)
    Finished `release` profile [optimized] target(s) in 11.78s
==> Step 4: Sync files to marketplace
    Ensure C:\Users\decid\.claude\plugins\marketplaces\cplugs\plugins\spt\skills exists
    Ensure C:\Users\decid\.claude\plugins\marketplaces\cplugs\plugins\spt\hooks exists
    Ensure C:\Users\decid\.claude\plugins\marketplaces\cplugs\plugins\spt\.claude-plugin exists
    Copy owl.exe -> C:\Users\decid\.claude\plugins\marketplaces\cplugs\plugins\spt
    Copy skills/* -> C:\Users\decid\.claude\plugins\marketplaces\cplugs\plugins\spt\skills
    Copy hooks/* -> C:\Users\decid\.claude\plugins\marketplaces\cplugs\plugins\spt\hooks
    Copy CHANGELOG.md -> C:\Users\decid\.claude\plugins\marketplaces\cplugs\plugins\spt
    Copy plugin.json -> C:\Users\decid\.claude\plugins\marketplaces\cplugs\plugins\spt\.claude-plugin
==> Step 5: Commit + push marketplace
    Committing marketplace changes
[main 8b73099] spt: deploy v1.11.21
 3 files changed, 15 insertions(+), 1 deletion(-)
    Pushing marketplace
To https://github.com/SaberMage/cplugs.git
   2d3cf5e..8b73099  main -> main
    Marketplace HEAD: 8b730999d5e46302c8a069bce98d98f38641ad09
==> Step 6-7: Sync new version + prune old cache
    Prior version: 1.11.20 (installPath leaf: 1.11.20)
    Create C:\Users\decid\.claude\plugins\cache\cplugs\spt\1.11.21/.claude-plugin
    Create C:\Users\decid\.claude\plugins\cache\cplugs\spt\1.11.21/skills
    Create C:\Users\decid\.claude\plugins\cache\cplugs\spt\1.11.21/hooks
    Copy owl.exe -> C:\Users\decid\.claude\plugins\cache\cplugs\spt\1.11.21
    Copy skills/* -> C:\Users\decid\.claude\plugins\cache\cplugs\spt\1.11.21/skills
    Copy hooks/* -> C:\Users\decid\.claude\plugins\cache\cplugs\spt\1.11.21/hooks
    Copy CHANGELOG.md -> C:\Users\decid\.claude\plugins\cache\cplugs\spt\1.11.21
    Copy plugin.json -> C:\Users\decid\.claude\plugins\cache\cplugs\spt\1.11.21/.claude-plugin
    Carry-over .pending-prune-* dirs from prior deploys: .pending-prune-1.11.16-1779850937, .pending-prune-1.11.18-1779914426
    Cleaned carry-over .pending-prune-1.11.16-1779850937
    ! Carry-over .pending-prune-1.11.18-1779914426 still not removable (sharing violation persists; will retry next deploy)
    Keep-set: 1.11.21, 1.11.20
    Prune candidates: 1.11.19
    Renamed 1.11.19 -> .pending-prune-1.11.19-1779931325
    Removed .pending-prune-1.11.19-1779931325
==> Step 8: Clean up rogue manual-install dir
    No manual-install dir at C:\Users\decid\.claude\plugins\spt -- nothing to clean up.
==> Step 9: Refresh plugin state via Claude Code CLI
    Running: claude plugin install spt@cplugs
Installing plugin "spt@cplugs"...✔ Plugin "spt@cplugs" is already installed (scope: user)
==> Step 9b: Patch installed_plugins.json (canonical pointer)
    Updated spt@cplugs[0]: version=1.11.21, installPath=C:\Users\decid\.claude\plugins\cache\cplugs\spt\1.11.21
    installed_plugins.json patched (atomic write via temp + Move-Item -Force).
==> Step 10: Verify installed_plugins.json reflects v1.11.21
    Pointer verified: spt@cplugs version=1.11.21 installPath=C:\Users\decid\.claude\plugins\cache\cplugs\spt\1.11.21

Deploy complete for v1.11.21.
Marketplace HEAD: 8b730999d5e46302c8a069bce98d98f38641ad09
>>> Now run /reload-plugins inside Claude Code to pick up the changes. <<<


Bash

git log --oneline -5; echo "---ahead---"; git rev-list --count origin/main..HEAD 2>&1

Local commits ahead


HANDOFF: trampoline -> C:\Users\decid\.claude\plugins\cache\cplugs\spt\1.11.21\owl.exe


9e1ab78 chore: bump spt plugin to v1.11.21
2e62194 docs(35.1): fill v1.11.21 CHANGELOG entry
9c36c95 docs(phase-35.1): complete phase execution
e226fb2 docs(35.1-fix): update REVIEW.md with fix log
0f4e55a fix(35.1-fix): CR-01 (follow-up) key on info.json existence not strict-serde
---ahead---
56


## ✓ Deploy v1.11.21 Complete

- **Marketplace pushed:** HEAD `8b73099` on `cplugs/main`
- **Cache synced:** `~/.claude/plugins/cache/cplugs/spt/1.11.21/`
- **Pointer flipped:** `installed_plugins.json` → v1.11.21
- **Cache prune:** 1.11.19 removed; 1.11.20 retained (previous, for in-flight handoffs); 1.11.18 pending-prune residue persists (sharing violation, retried next deploy)

**spt repo:** 56 commits ahead of `origin/main` (CHANGELOG fill `2e62194` + bump `9e1ab78` + 54 prior). Not pushed per script contract.

>>> **Run `/reload-plugins` inside Claude Code now to pick up v1.11.21.** <<<

Push spt repo when ready: `git push origin main`.


<task-notification>
<task-id>bp5gjpiwy</task-id>
Monitor event: "« spt event »"
<event><EVENT-PART seq="1/9" id="637b99ac" type="msg" from="version-change">&lt;spt-version-changelog&gt;
  &lt;old&gt;1.11.20&lt;/old&gt;
  &lt;new&gt;1.11.21&lt;/new&gt;
  &lt;old_date&gt;2026-05-28&lt;/old_date&gt;
  &lt;new_date&gt;&lt;/new_date&gt;
  &lt;step_count&gt;1&lt;/step_count&gt;
  &lt;changelog_path&gt;$CLAUDE_PLUGIN_ROOT/CHANGELOG.md&lt;/changelog_path</EVENT-PART>
<EVENT-PART seq="2/9" id="637b99ac">&gt;
  &lt;instructions&gt;
Render an AskUserQuestion to confirm whether the user wants to see the changelog for this SPT plugin update.

Before invoking AskUserQuestion, write one short pre-question line that names the version transition (and dates if both &lt;old_date&gt; and &lt;new_date&gt; are non-empty), e.g. &quot;SPT plugin</EVENT-PART>
<EVENT-PART seq="3/9" id="637b99ac"> updated v1.11.20 → v1.11.21. Print changelog?&quot;.

Then invoke AskUserQuestion with EXACTLY these three options (labels verbatim, do not paraphrase):

  1. &quot;Yes, full changelog&quot; — On selection: read $CLAUDE_PLUGIN_ROOT/CHANGELOG.md and print every H2 section where 1.11.20 &lt; version &lt;= 1.11.21, verbatim markd</EVENT-PART>
<EVENT-PART seq="4/9" id="637b99ac">own. Preserve the original H2 lines, bullet structure, and the user-facing H3 subsections (Added / Changed / Fixed / Removed / Deprecated / Security / Reverted). OMIT any &quot;### BTS&quot; subsection entirely — those are behind-the-scenes notes the user does not want to see. If a version has only a &quot;### BTS&quot; subsection with no us</EVENT-PART>
<EVENT-PART seq="5/9" id="637b99ac">er-facing content, render the H2 header and date alone with a single line &quot;(no user-facing changes this release)&quot;. Render in the order they appear in the file (newest first). Do not summarize, do not paraphrase the bullets you do show.

  2. &quot;Yes, highlights only&quot; — On selection: read $CLAUDE_PLUGIN_ROOT/CHANGELOG.m</EVENT-PART>
<EVENT-PART seq="6/9" id="637b99ac">d and synthesize 2-5 highlight bullets across the H2 sections where 1.11.20 &lt; version &lt;= 1.11.21. Draw ONLY from the &quot;### Added&quot;, &quot;### Changed&quot;, &quot;### Fixed&quot;, &quot;### Removed&quot;, &quot;### Deprecated&quot;, &quot;### Security&quot;, and &quot;### Reverted&quot; subsections. Do NOT draw from &quot;### BTS</EVENT-PART>
<EVENT-PART seq="7/9" id="637b99ac">&quot; subsections — those are behind-the-scenes notes and must never appear in highlights. Pick the most impactful user-facing items across the range. Append exactly this pointer at the end: &quot;See $CLAUDE_PLUGIN_ROOT/CHANGELOG.md for full detail.&quot;

  3. &quot;Skip&quot; — On selection: take no action. Acknowledge briefly (&</EVENT-PART>
<EVENT-PART seq="8/9" id="637b99ac">quot;OK, skipping.&quot;) and continue with the user's prior intent.

If the user provides freeform input via AskUserQuestion's &quot;Other&quot; option, treat all continuation phrases (later / not now / i
...(truncated)</event>
</task-notification>


CAVEMAN MODE ACTIVE (full). Drop articles/filler/pleasantries/hedging. Fragments OK. Code/commits/security: write normal.


<owl_reachability_notice>
You have an active owl perch. When spawning subagents or doing work, use run_in_background: true so you stay reachable for incoming messages.
</owl_reachability_notice>


SPT plugin updated v1.11.20 → v1.11.21. Print changelog?


AskUserQuestion

Question: SPT plugin updated v1.11.20 → v1.11.21. Print changelog?

Choices:
Print every H2 section in range, verbatim. BTS subsections omitted.
2-5 synthesized highlight bullets across user-facing buckets.
No action. Continue with prior intent.


Your questions have been answered: "SPT plugin updated v1.11.20 → v1.11.21. Print changelog?"="Yes, highlights only". You can now continue with these answers in mind.

Choices:
Print every H2 section in range, verbatim. BTS subsections omitted.
2-5 synthesized highlight bullets across user-facing buckets.
No action. Continue with prior intent.

Question: SPT plugin updated v1.11.20 → v1.11.21. Print changelog?


## Highlights — v1.11.21

- **`$OWL list` is now strictly read-only.** It can no longer accidentally delete perch directories — a race had occasionally taken healthy in-flight psyche perches with it.
- **`$OWL list` no longer mis-classifies in-flight psyche workers as orphans on Windows.** Adds a wrapper-liveness + 60s grace check before flagging anything.
- **`$OWL list` reports orphan-perch count when present** with a hint to run `$OWL doctor --fix`.
- **`$OWL doctor --fix` now garbage-collects orphan perch directories** — nested perches soft-cleaned, top-level dirs hard-deleted, with a safety guard that refuses to wipe a top-level dir hosting non-empty `nested/` children.
- **`$OWL doctor --fix` stale-perch sweep no longer demotes healthy live agents** whose info.json carries a recycled Windows PID.

See $CLAUDE_PLUGIN_ROOT/CHANGELOG.md for full detail.


/reload-plugins


<local-command-caveat>Caveat: The messages below were generated by the user while running local commands. DO NOT respond to these messages or otherwise consider them in your response unless the user explicitly asks you to.</local-command-caveat>


<command-name>/reload-plugins</command-name>
            <command-message>reload-plugins</command-message>
            <command-args></command-args>


<local-command-stdout>Reloaded: 6 plugins · 0 skills · 39 agents · 9 hooks · 1 plugin MCP server · 1 plugin LSP server</local-command-stdout>