# 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>"
```
