The file C:\Users\decid\Documents\projects\spt-claude-code\docs\RELEASE-RUNBOOK.md has been updated successfully. (file state is current in your context — no need to Read it back)

# 2. Copy skeleton files (NO binary) into the marketplace clone:
MARKET=~/.claude/plugins/marketplaces/cplugs/plugins/sptc
cp -r skills/*  "$MARKET/skills/"
cp -r hooks/*   "$MARKET/hooks/"
cp .claude-plugin/plugin.json "$MARKET/.claude-plugin/"
# 3. Commit + push the cplugs repo:
cd ~/.claude/plugins/marketplaces/cplugs && git add plugins/sptc/ \
  && git commit -m "sptc: <change>" && git push
```

**Pointer flip** (the authoritative install-state update):

```bash
claude plugin install sptc@cplugs   # un-orphans + rewrites installed_plugins.json atomically
# then, inside Claude Code:
/reload-plugins
```

# Release runbook

> How an spt-claude-code release ships. The spine is fixed (changelog · bump · regenerate
> docs · tag · publish · docs publish). **Release here is LIGHT: signing is DELEGATED to the
> `spt` binary — there is NO two-key signing ceremony in this project.** spt-claude-code is a
> thin adapter; the heavy signed-release machinery lives in spt-core, not here.

<!-- [<doc>->REQ-EXAMPLE-REL] (illustration; escaped per docs/TRACEABILITY.md) -->

## Publish targets

An spt-claude-code release lands in **two places**, by volatility:

1. **The `SaberMage/cplugs` marketplace** — the thin **skeleton plugin** (namespaced
   `/spt:*` skill skeletons, `hooks.json`, the SessionStart bootstrap, `plugin.json`). This
   is low-churn: it only changes on **structural** plugin changes, not on logic/instruction
   updates.
2. **The spt-core adapter registry** — the volatile bulk: the CC adapter **manifest**
   (`[digest]` extractor, profiles, strings, hints) **+ binary**. This publish is
   **spt-core-conducted** (the same framework that conducts adapter updates), not a plugin
   file push.

## One-time setup

No signing-key ceremony in this project. **Signing is delegated to the `spt` binary**, which
handles adapter manifest/binary integrity through spt-core's own signed-update framework. Keep
no release keys here. The only setup is marketplace/registry publish credentials for the two
targets above (a `cplugs` push token + whatever the spt-core adapter-registry publish step
requires).

## Per release

1. **Bump** the version (the plugin `plugin.json` version and/or the adapter manifest version —
   note these are distinct numbers; see Notes) if needed; **regenerate generated docs**
   (the mdBook `docs-site/` build + `llms.txt` / schema / CLI-help exports); land everything
   green.
2. **Write the user-facing changelog** — add a `## [<version>] - <date>` section at the top of
   `CHANGELOG.md` with **Added / Changed / Fixed** subsections. This section becomes the public
   release body verbatim, so it is the changelog every user reads. Rules:
   - **User-facing UX only.** What a person using the product notices or does differently. Name
     the actual commands / flags / surfaces they touch (`/spt:*` skills, `cc`, `/spt:setup`).
   - **No internal lingo** — no requirement ids, internal module names, commit hashes, or
     milestone / hazard codes. A reader who has never seen the source must understand every line.
   - **Flag breaking changes** prominently under Changed.
   - The release **fails loudly** if the tagged version has no `## [<version>]` section — the
     changelog is not optional.
3. **Tag**: `git tag <vX.Y.Z> && git push origin <vX.Y.Z>`. This triggers:
   - the **docs-publish workflow** — build `docs-site/` and publish to GitHub Pages (drift-gated;
     see `docs/DOCS-STRATEGY.md`).
4. **Publish** (light — signing delegated to `spt`):
   - **Skeleton plugin** → push to the `SaberMage/cplugs` marketplace **only when the plugin
     skeleton structurally changed** (new/removed skill skeleton, hook wiring, bootstrap, or
     `plugin.json`). Most releases do not bump the marketplace.
   - **Manifest + binary** → publish through the **spt-core adapter registry**; the `spt`
     binary performs signing/integrity as part of spt-core's adapter-update framework. You do
     **not** run a separate signing step.

## cplugs skeleton publish — concrete mechanics

<!-- [doc->REQ-DIST-PLUGIN-SKELETON] -->
The concrete cplugs marketplace steps, captured from the sister project's
`claude_skill_owl/docs/DEPLOY.md`. **Take only the SKELETON SUBSET.** spt-claude-code's cplugs
target is the **thin skeleton** — `/spt:*` skill skeletons, `hooks.json`, the SessionStart
bootstrap, `plugin.json`. **No binary, no manifest in cplugs** — the binary + adapter manifest
ride the **spt-core adapter registry** (spt-conducted; the *other* publish target above), so
none of owl's binary-lifecycle machinery applies here.

**One-time marketplace setup:**

```bash
# Clone the marketplace repo
git clone https://github.com/SaberMage/cplugs.git ~/.claude/plugins/marketplaces/cplugs
# Register a "cplugs" entry in ~/.claude/plugins/known_marketplaces.json
#   source: { source: "github", repo: "SaberMage/cplugs" }
```

**Per skeleton bump** (only when the skeleton *structurally* changes — see "Per release"):

```bash
# 1. Bump plugin.json version. The plugin manager compares this to the cached copy and
#    SKIPS the update entirely if they match — a stale version = a silent no-update.
# 2. Copy skeleton files (NO binary) into the marketplace clone:
MARKET=~/.claude/plugins/marketplaces/cplugs/plugins/<plugin>
cp -r skills/*  "$MARKET/skills/"
cp -r hooks/*   "$MARKET/hooks/"
cp .claude-plugin/plugin.json "$MARKET/.claude-plugin/"
# 3. Commit + push the cplugs repo:
cd ~/.claude/plugins/marketplaces/cplugs && git add plugins/<plugin>/ \
  && git commit -m "<plugin>: <change>" && git push
```

**Pointer flip** (the authoritative install-state update):

```bash
claude plugin install <plugin>@cplugs   # un-orphans + rewrites installed_plugins.json atomically
# then, inside Claude Code:
/reload-plugins
```

**Gotchas (these DO apply to the skeleton):**
- **Never hand-patch `installed_plugins.json`** (`jq`/`sed` are brittle; CC may silently reject).
  Use the `claude plugin install` CLI — it's the atomic pointer flip + orphan-marker cleanup.
- **No legacy manual-install dir alongside the marketplace install.** A `~/.claude/plugins/<name>/`
  regular dir coexisting with the marketplace cache install makes `/plugin` report the plugin
  **not installed** (CC enumerates both; the untracked one wins the conflict). Keep the two exclusive.
- **Restart the Claude Code session after install** so the SessionStart hook re-runs and repopulates
  the plugin's env (`$OWL`/`$LIVE` equivalents) in Bash subprocesses.

**Explicitly NOT ours** (owl ships a live self-migrating binary; the thin skeleton has none, so
skip all of it): `owl.exe` binary sync · the cache targeted-prune + keep-PREVIOUS-version logic ·
the seamless binary handoff (owl Phase 18.4/18.5) · `DEPLOY.ps1`'s build/handoff orchestration.

> **OPEN — cplugs plugin name.** Legacy already occupies the `spt` plugin name on cplugs; the
> skeleton needs a **distinct** marketplace plugin name (candidate: `spt-claude-code` /
> `claude-spt`) while still exposing the `/spt:` *skill* namespace (plugin name ≠ skill-namespace
> prefix). Resolve before first cplugs push; replace `<plugin>` above.

## Update path (dual, kept in sync)

Users receive updates two ways, and both must stay consistent:
- **File-pull (the real channel)** — spt-core conducts manifest + binary updates seamlessly;
  this is how logic and instruction churn reaches users. Version-of-truth = the
  manifest/binary version spt-core tracks (`spt adapter list` / `/spt:version`).
- **`claude plugin update` (cautionary, skeleton sync)** — only resyncs the low-churn
  marketplace skeleton on the rare structural bump. It is NOT the version-of-truth.

## Notes

- **Distinct numbers — never conflate them:** the **plugin (marketplace) version** (in
  `plugin.json`, bumps rarely) and the **adapter manifest/binary version** (spt-core-tracked,
  the user-visible version-of-truth). They move on independent schedules — the plugin can sit
  static across many manifest updates.
- **Trust model:** adapter manifest/binary integrity is the `spt` binary's responsibility via
  spt-core's signed-update framework — spt-claude-code defines no separate trust ceremony.
  (Claude Code has no plugin-file integrity check, which is why the skeleton plugin is safe to
  in-place-edit; only old version dirs orphan on update and are GC'd after 7 days.)

-cd ~/.claude/plugins/marketplaces/cplugs && git add plugins/<plugin>/ \

-  && git commit -m "<plugin>: <change>" && git push

+cd ~/.claude/plugins/marketplaces/cplugs && git add plugins/sptc/ \

+  && git commit -m "sptc: <change>" && git push

-claude plugin install <plugin>@cplugs   # un-orphans + rewrites installed_plugins.json atomically

+claude plugin install sptc@cplugs   # un-orphans + rewrites installed_plugins.json atomically


Edit

> **OPEN — cplugs plugin name.** Legacy already occupies the `spt` plugin name on cplugs; the
> skeleton needs a **distinct** marketplace plugin name (candidate: `spt-claude-code` /
> `claude-spt`) while still exposing the `/spt:` *skill* namespace (plugin name ≠ skill-namespace
> prefix). Resolve before first cplugs push; replace `<plugin>` above.

> **RULED (operator, 2026-06-14) — cplugs plugin name = `sptc`.** CC ties the skill prefix to the
> `plugin.json` name (no override), so skills surface as `/sptc:*`. **Succession:** once parity is
> proven, flip `plugin.json` name `sptc`→`spt` (skills → `/spt:*`) and retire/rename legacy owl's
> cplugs `spt` plugin in the same coordinated move — two plugins cannot share the `spt` name. The
> flip is a single substitution `s/sptc/spt/` (plugin dir, `plugin.json` name, marketplace path).


> **RULED (operator, 2026-06-14) — cplugs plugin name = `sptc`.** CC ties the skill prefix to the
> `plugin.json` name (no override), so skills surface as `/sptc:*`. **Succession:** once parity is
> proven, flip `plugin.json` name `sptc`→`spt` (skills → `/spt:*`) and retire/rename legacy owl's
> cplugs `spt` plugin in the same coordinated move — two plugins cannot share the `spt` name. The
> flip is a single substitution `s/sptc/spt/` (plugin dir, `plugin.json` name, marketplace path).

# Release runbook

> How an spt-claude-code release ships. The spine is fixed (changelog · bump · regenerate
> docs · tag · publish · docs publish). **Release here is LIGHT: signing is DELEGATED to the
> `spt` binary — there is NO two-key signing ceremony in this project.** spt-claude-code is a
> thin adapter; the heavy signed-release machinery lives in spt-core, not here.

<!-- [<doc>->REQ-EXAMPLE-REL] (illustration; escaped per docs/TRACEABILITY.md) -->

## Publish targets

An spt-claude-code release lands in **two places**, by volatility:

1. **The `SaberMage/cplugs` marketplace** — the thin **skeleton plugin** (namespaced
   `/spt:*` skill skeletons, `hooks.json`, the SessionStart bootstrap, `plugin.json`). This
   is low-churn: it only changes on **structural** plugin changes, not on logic/instruction
   updates.
2. **The spt-core adapter registry** — the volatile bulk: the CC adapter **manifest**
   (`[digest]` extractor, profiles, strings, hints) **+ binary**. This publish is
   **spt-core-conducted** (the same framework that conducts adapter updates), not a plugin
   file push.

## One-time setup

No signing-key ceremony in this project. **Signing is delegated to the `spt` binary**, which
handles adapter manifest/binary integrity through spt-core's own signed-update framework. Keep
no release keys here. The only setup is marketplace/registry publish credentials for the two
targets above (a `cplugs` push token + whatever the spt-core adapter-registry publish step
requires).

## Per release

1. **Bump** the version (the plugin `plugin.json` version and/or the adapter manifest version —
   note these are distinct numbers; see Notes) if needed; **regenerate generated docs**
   (the mdBook `docs-site/` build + `llms.txt` / schema / CLI-help exports); land everything
   green.
2. **Write the user-facing changelog** — add a `## [<version>] - <date>` section at the top of
   `CHANGELOG.md` with **Added / Changed / Fixed** subsections. This section becomes the public
   release body verbatim, so it is the changelog every user reads. Rules:
   - **User-facing UX only.** What a person using the product notices or does differently. Name
     the actual commands / flags / surfaces they touch (`/spt:*` skills, `cc`, `/spt:setup`).
   - **No internal lingo** — no requirement ids, internal module names, commit hashes, or
     milestone / hazard codes. A reader who has never seen the source must understand every line.
   - **Flag breaking changes** prominently under Changed.
   - The release **fails loudly** if the tagged version has no `## [<version>]` section — the
     changelog is not optional.
3. **Tag**: `git tag <vX.Y.Z> && git push origin <vX.Y.Z>`. This triggers:
   - the **docs-publish workflow** — build `docs-site/` and publish to GitHub Pages (drift-gated;
     see `docs/DOCS-STRATEGY.md`).
4. **Publish** (light — signing delegated to `spt`):
   - **Skeleton plugin** → push to the `SaberMage/cplugs` marketplace **only when the plugin
     skeleton structurally changed** (new/removed skill skeleton, hook wiring, bootstrap, or
     `plugin.json`). Most releases do not bump the marketplace.
   - **Manifest + binary** → publish through the **spt-core adapter registry**; the `spt`
     binary performs signing/integrity as part of spt-core's adapter-update framework. You do
     **not** run a separate signing step.

## cplugs skeleton publish — concrete mechanics

<!-- [doc->REQ-DIST-PLUGIN-SKELETON] -->
The concrete cplugs marketplace steps, captured from the sister project's
`claude_skill_owl/docs/DEPLOY.md`. **Take only the SKELETON SUBSET.** spt-claude-code's cplugs
target is the **thin skeleton** — `/spt:*` skill skeletons, `hooks.json`, the SessionStart
bootstrap, `plugin.json`. **No binary, no manifest in cplugs** — the binary + adapter manifest
ride the **spt-core adapter registry** (spt-conducted; the *other* publish target above), so
none of owl's binary-lifecycle machinery applies here.

**One-time marketplace setup:**

```bash
# Clone the marketplace repo
git clone https://github.com/SaberMage/cplugs.git ~/.claude/plugins/marketplaces/cplugs
# Register a "cplugs" entry in ~/.claude/plugins/known_marketplaces.json
#   source: { source: "github", repo: "SaberMage/cplugs" }
```

**Per skeleton bump** (only when the skeleton *structurally* changes — see "Per release"):

```bash
# 1. Bump plugin.json version. The plugin manager compares this to the cached copy and
#    SKIPS the update entirely if they match — a stale version = a silent no-update.
# 2. Copy skeleton files (NO binary) into the marketplace clone:
MARKET=~/.claude/plugins/marketplaces/cplugs/plugins/sptc
cp -r skills/*  "$MARKET/skills/"
cp -r hooks/*   "$MARKET/hooks/"
cp .claude-plugin/plugin.json "$MARKET/.claude-plugin/"
# 3. Commit + push the cplugs repo:
cd ~/.claude/plugins/marketplaces/cplugs && git add plugins/sptc/ \
  && git commit -m "sptc: <change>" && git push
```

**Pointer flip** (the authoritative install-state update):

```bash
claude plugin install sptc@cplugs   # un-orphans + rewrites installed_plugins.json atomically
# then, inside Claude Code:
/reload-plugins
```

**Gotchas (these DO apply to the skeleton):**
- **Never hand-patch `installed_plugins.json`** (`jq`/`sed` are brittle; CC may silently reject).
  Use the `claude plugin install` CLI — it's the atomic pointer flip + orphan-marker cleanup.
- **No legacy manual-install dir alongside the marketplace install.** A `~/.claude/plugins/<name>/`
  regular dir coexisting with the marketplace cache install makes `/plugin` report the plugin
  **not installed** (CC enumerates both; the untracked one wins the conflict). Keep the two exclusive.
- **Restart the Claude Code session after install** so the SessionStart hook re-runs and repopulates
  the plugin's env (`$OWL`/`$LIVE` equivalents) in Bash subprocesses.

**Explicitly NOT ours** (owl ships a live self-migrating binary; the thin skeleton has none, so
skip all of it): `owl.exe` binary sync · the cache targeted-prune + keep-PREVIOUS-version logic ·
the seamless binary handoff (owl Phase 18.4/18.5) · `DEPLOY.ps1`'s build/handoff orchestration.

> **OPEN — cplugs plugin name.** Legacy already occupies the `spt` plugin name on cplugs; the
> skeleton needs a **distinct** marketplace plugin name (candidate: `spt-claude-code` /
> `claude-spt`) while still exposing the `/spt:` *skill* namespace (plugin name ≠ skill-namespace
> prefix). Resolve before first cplugs push; replace `<plugin>` above.

## Update path (dual, kept in sync)

Users receive updates two ways, and both must stay consistent:
- **File-pull (the real channel)** — spt-core conducts manifest + binary updates seamlessly;
  this is how logic and instruction churn reaches users. Version-of-truth = the
  manifest/binary version spt-core tracks (`spt adapter list` / `/spt:version`).
- **`claude plugin update` (cautionary, skeleton sync)** — only resyncs the low-churn
  marketplace skeleton on the rare structural bump. It is NOT the version-of-truth.

## Notes

- **Distinct numbers — never conflate them:** the **plugin (marketplace) version** (in
  `plugin.json`, bumps rarely) and the **adapter manifest/binary version** (spt-core-tracked,
  the user-visible version-of-truth). They move on independent schedules — the plugin can sit
  static across many manifest updates.
- **Trust model:** adapter manifest/binary integrity is the `spt` binary's responsibility via
  spt-core's signed-update framework — spt-claude-code defines no separate trust ceremony.
  (Claude Code has no plugin-file integrity check, which is why the skeleton plugin is safe to
  in-place-edit; only old version dirs orphan on update and are GC'd after 7 days.)

-> **OPEN — cplugs plugin name.** Legacy already occupies the `spt` plugin name on cplugs; the

-> skeleton needs a **distinct** marketplace plugin name (candidate: `spt-claude-code` /

-> `claude-spt`) while still exposing the `/spt:` *skill* namespace (plugin name ≠ skill-namespace

-> prefix). Resolve before first cplugs push; replace `<plugin>` above.

+> **RULED (operator, 2026-06-14) — cplugs plugin name = `sptc`.** CC ties the skill prefix to the

+> `plugin.json` name (no override), so skills surface as `/sptc:*`. **Succession:** once parity is

+> proven, flip `plugin.json` name `sptc`→`spt` (skills → `/spt:*`) and retire/rename legacy owl's

+> cplugs `spt` plugin in the same coordinated move — two plugins cannot share the `spt` name. The

+> flip is a single substitution `s/sptc/spt/` (plugin dir, `plugin.json` name, marketplace path).


Now SKELETON-PLAN: resolve the open name question.


- **cplugs plugin name** — legacy occupies `spt`; skeleton needs a distinct marketplace name
  (`spt-claude-code` / `claude-spt`?) while keeping the `/spt:` *skill* namespace. Resolve
  before any cplugs push. (`docs/RELEASE-RUNBOOK.md` OPEN note.)

- ~~cplugs plugin name~~ **RULED (operator, 2026-06-14): `sptc`** → skills `/sptc:*` (CC ties the
  prefix to `plugin.json` name, no override). Succession: flip `sptc`→`spt` on parity-proof,
  retiring legacy `spt` in the same move. `sptc` is the single-substitution rename seam.


The file C:\Users\decid\Documents\projects\spt-claude-code\SKELETON-PLAN.md has been updated successfully. (file state is current in your context — no need to Read it back)

- ~~cplugs plugin name~~ **RULED (operator, 2026-06-14): `sptc`** → skills `/sptc:*` (CC ties the
  prefix to `plugin.json` name, no override). Succession: flip `sptc`→`spt` on parity-proof,
  retiring legacy `spt` in the same move. `sptc` is the single-substitution rename seam.

# SKELETON milestone — JIT plan

> First body of work for `spt-claude-code`, on the **v0.6.0 (M10) public surface**. JIT: this
> plans the skeleton milestone only — M12-dependent pieces are explicitly out of scope until
> M12 publishes (doyle pings when its surface is live). Authoritative why/what: `SCOPE.md`.

## Goal

Stand up the **thin cplugs skeleton plugin** — the low-churn marketplace artifact — and the
traceability + release foundation around it. No spt-core logic lives here; the plugin is
skill skeletons + `hooks.json` (→ `spt api`) + a SessionStart bootstrap + `plugin.json`.

## Scope (v0.6.0-supported, startable now)

In:
- Traceability gate green + skeleton-milestone REQs seeded (DONE this body — see below).
- cplugs publish mechanics captured in `docs/RELEASE-RUNBOOK.md` (DONE this body).
- The plugin skeleton itself: `plugin.json`, `hooks.json` (delegating to `spt api`),
  SessionStart bootstrap that installs spt-core when absent, namespaced `/spt:*` skill
  skeletons for the KEEP set, `/spt:version`, the UPS-injection hook.
- ADR-0001 (distribution split: skeleton plugin vs spt-conducted manifest+binary).
- Empirical confirmation: does `UserPromptSubmit` fire on `/spt:X` slash-commands? (gates the
  UPS-injection design — REQ-UPS-INJECTION). Fallback = in-SKILL.md instructions if it doesn't.

Out (deferred to post-M12 — sequence after doyle's M12-live ping):
- File-backed `[strings]`, `adapter:profile` fallback, `[adapter] shortcut_basename`,
  `spt endpoint run` bringup picker, `spt rc` cross-node attach, `cc` launcher, `/spt:setup`,
  whoami→alias. (M12 public-surface deps — `SCOPE.md` §"spt-core upstream deps".)
- CI fleet wiring (own milestone) and docs-site mdBook build (own milestone) — both startable
  on v0.6.0 but not part of THIS skeleton body; planned next.

## Open design questions

- **cplugs plugin name** — legacy occupies `spt`; skeleton needs a distinct marketplace name
  (`spt-claude-code` / `claude-spt`?) while keeping the `/spt:` *skill* namespace. Resolve
  before any cplugs push. (`docs/RELEASE-RUNBOOK.md` OPEN note.)
- **UPS-on-slash-command** — design assumes UPS fires; confirm empirically (above).
- `/spt:setup` #9 verify fold-inline vs deferred (`SCOPE.md` §8 open item) — not skeleton-blocking.

## Tasks

- [x] Green `traceable-reqs check` (fix seed scan-root manifest_error + 4 template tag-leaks).
- [x] Seed skeleton-milestone REQs (inactive): DIST-PLUGIN-SKELETON, DIST-BOOTSTRAP-INSTALL,
      DIST-HOOKS-API, DIST-MANIFEST-SCHEMA, UPS-INJECTION, SKILL-VERSION.
- [x] Capture cplugs skeleton-publish mechanics in `docs/RELEASE-RUNBOOK.md`.
- [ ] Write ADR-0001 (distribution split) → `doc` evidence; activate reqs' `doc` stage.
- [ ] Scaffold `plugin/` (plugin.json), `hooks/` (hooks.json → `spt api`; UPS-injection hook),
      `skills/` (KEEP skeletons + `/spt:version`). Add these as `[scan].roots` when created.
- [ ] Empirically test UPS-fires-on-slash-command; record result in ADR / KNOWN-HAZARDS.
- [ ] Validate `plugin.json` + adapter manifest shape against published `manifest.schema.json`
      from `SaberMage/spt-releases`.
- [ ] Activate `impl`/`unit` stages + tag as each surface lands; `traceable-reqs check` EXIT=0.

## Gate

`traceable-reqs check` EXIT=0 with the skeleton milestone's reqs activated and covered, the
skeleton plugin installable from a local marketplace clone, and the manifest validating against
the published schema. Build/CI wiring is a separate milestone.

-- **cplugs plugin name** — legacy occupies `spt`; skeleton needs a distinct marketplace name

-  (`spt-claude-code` / `claude-spt`?) while keeping the `/spt:` *skill* namespace. Resolve

-  before any cplugs push. (`docs/RELEASE-RUNBOOK.md` OPEN note.)

+- ~~cplugs plugin name~~ **RULED (operator, 2026-06-14): `sptc`** → skills `/sptc:*` (CC ties the

+  prefix to `plugin.json` name, no override). Succession: flip `sptc`→`spt` on parity-proof,

+  retiring legacy `spt` in the same move. `sptc` is the single-substitution rename seam.


Now scaffolding. Writing `plugin.json`, and fetching the published `spt api` hook contract for accurate `hooks.json` (no guessing). Loading WebFetch.


ToolSearch

Question: select:WebFetch


<owl_messages>
<owl_message from="doyle" priority="high">ACK — sptc seam (plugin.json name + dir + skill prefixes, flip = s/sptc/spt/) is right; recording across ADR-0001/RUNBOOK/SKELETON-PLAN is the correct home for the succession.

On the hooks.json `spt api` contract — flag-don't-guess is EXACTLY right, that's the public-surface discipline. Where it should live on the GH Pages harness-contract:
- The generated CLI reference (docs-site cli/reference.md) — the `spt api` / `api-` subcommands (api bind, api listen, api poll, api state, api worker-start/stop, api echo-gate, etc.) are the inbound machinery-facing surface; the hook subcommands you need are there if anywhere.
- MANIFEST.md "harness API" section — manifest + subcommand surface = the complete harness contract (that framing is canon).

If the `spt api` hook surface is PRESENT there → use the published names verbatim (don't carry legacy owl.exe subcommand names; the public `spt api` naming is authoritative and may differ — that rename is intentional). If it's ABSENT/incomplete → that's a real spt-core PUBLIC-SURFACE finding (the harness contract an adapter author builds against isn't published), send it to me and I own the docs fix. Don't reverse-engineer from legacy.

UPS-fires-on-/sptc:X test: go. Report — it gates the UPS-injection design. Standing by for that + any api-contract finding. M12-live ping still pending on publish.</owl_message>
</owl_messages>

[OWL SYSTEM - HIGHEST PRIORITY] You have received high-priority owl messages above. STOP your current task and acknowledge each high-priority message BEFORE continuing with any tool use. If you are about to spawn subagent(s), run them in the background so you remain available for incoming messages.


Question: select:WebFetch


Write

{
  "name": "sptc",
  "version": "0.0.1",
  "description": "Spacetime (spt) adapter for Claude Code — agent messaging, live agents, and spt-core bootstrap. Thin skeleton; logic rides the spt binary + adapter manifest (spt-core-conducted).",
  "author": {
    "name": "perri"
  },
  "keywords": ["spt", "spacetime", "messaging", "agents", "live", "claude-spt"]
}


File created successfully at: C:\Users\decid\Documents\projects\spt-claude-code\plugin\sptc\.claude-plugin\plugin.json (file state is current in your context — no need to Read it back)

sptc

Spacetime (spt) adapter for Claude Code — agent messaging, live agents, and spt-core bootstrap. Thin skeleton; logic rides the spt binary + adapter manifest (spt-core-conducted).

spt spacetime messaging agents live claude-spt


doyle pinpointed the contract location (CLI reference + MANIFEST.md "harness API"), confirmed: use published `spt api` names verbatim if present, else it's a public-surface finding for him. Acknowledging + fetching the public docs index.


Bash

cd "C:/Users/decid/Documents/projects/spt-claude-code" && $OWL send doyle perri <<'EOF'
ACK contract pointers: checking generated CLI reference (spt api / api-* subcommands) + MANIFEST.md "harness API" section on the spt-releases GH Pages. Will use published names VERBATIM if present (not legacy owl.exe names — understood the rename is intentional/authoritative). If the hook-facing `spt api` surface is absent/incomplete, that's a public-surface FINDING -> straight to you, no reverse-engineering from legacy. Fetching now; UPS-fires-on-/sptc:X test queued right after. Reporting both.
EOF
echo "sent=$?"

Ack contract pointers to doyle


➜ SENT:doyle
sent=0


WebFetch

Prompt: List every documentation page/section and its URL path. In particular, find: (1) the CLI reference page covering `spt api` or `api-*` subcommands, (2) any MANIFEST page with a "harness API" section, (3) the harness-contract reference. Output the exact URLs/paths for each.


# Documentation Pages and URLs

## Complete List of Documentation Pages

| Page | URL Path |
|------|----------|
| Quickstart: Messaging | `quickstart/messaging.md` |
| Quickstart: Adapter | `quickstart/adapter.md` |
| Mental Model | `concepts/overview.md` |
| Harness Contract Overview | `harness-contract/overview.md` |
| Integration Checklist | `harness-contract/integration-checklist.md` |
| Manifest Reference | `harness-contract/manifest.md` |
| API Surface | `harness-contract/api.md` |
| Install-on-Demand Bootstrap | `harness-contract/install-on-demand.md` |
| Manifest JSON Schema | `reference/schema.md` |
| Messaging | `messaging/overview.md` |
| Live-Agent Lifecycle | `lifecycle/overview.md` |
| Terminal Hosting | `terminal/overview.md` |
| Networking & Subnets | `networking/overview.md` |
| Instances | `instances/overview.md` |
| Shells | `shells/overview.md` |
| Shells: Getting Started | `shells/getting-started.md` |
| Self-Update | `self-update/overview.md` |
| CLI Reference | `cli/reference.md` |
| Install Scripts | `reference/install.md` |

## Answers to Specific Queries

**(1) CLI Reference covering `spt api`:**
`cli/reference.md` — described as "every command and flag, generated from the binary's own --help"

**(2) Manifest page with harness API section:**
`harness-contract/manifest.md` — labeled "every section and field; cross-field rules; shell-adapter body"

**(3) Harness-contract reference:**
`harness-contract/overview.md` — described as "the whole third-party surface — manifest + `spt api`"


Excellent — dedicated `harness-contract/api.md` (API Surface) + `harness-contract/install-on-demand.md` (bootstrap). Fetching both — exactly the hooks.json + bootstrap contract.


Prompt: I am writing a Claude Code plugin's hooks.json that delegates to `spt api`. List the COMPLETE `spt api` subcommand surface verbatim: every subcommand name, its purpose, arguments/flags, and especially which subcommands a harness's hooks should call for these Claude Code hook events: SessionStart, SessionEnd, UserPromptSubmit, PreToolUse, Stop, SubagentStart, SubagentStop. Quote exact command strings. If there is a documented mapping of harness hook events to `spt api` subcommands, reproduce it exactly.


Prompt: Describe the install-on-demand / bootstrap contract verbatim: how should a harness adapter's SessionStart hook detect whether spt-core is installed and install it if absent? Quote the exact commands, detection checks, env vars, and any provided bootstrap script or entrypoint. What is the canonical way to invoke it from a Claude Code SessionStart hook command?


# spt api Subcommand Surface

Based on the documentation, here's the complete subcommand listing:

## Session Lifecycle
- `api seed --pid <pid> --session-id <id>` — "Harness-hosted startup, step 1: record an ephemeral seed keyed by the parent process id"
- `api listen <id> [--once] [--parent-pid <pid>] [--subnet <name>]` — "Harness-hosted startup, step 2: consume the seed, register/hold the perch"
- `api bind <id> [--set-session-id <sid>]` — spt-hosted startup binding
- `api boundary <clear|compact> <id> --to-session-id <sid>` — Handle session reset with new ID
- `api session-end <id> [--erase]` — Soft or hard teardown
- `api shutdown <id>` — Graceful signoff with final echo-commune

## Activity & Presence
- `api state <busy|idle> <id> [--no-gate]` — Report session activity state
- `api echo-gate <set|clear> <id>` — Manage echo-gate sentinel
- `api presence <id>` — Report user/agent presence
- `api driven-by <id>` — Check remote-drive status

## Messages
- `api poll <id> [--include-deferred] [--link <token>]` — Drain delivered messages
- `api history-log <id>` — Append normalized history

## Workers
- `api worker-start <parent> <id>` — Create nested worker perch
- `api worker-poll <id>` — Drain worker messages
- `api worker-stop <id>` — Tear down worker

## Shells
- `api bind-shell --link <token>` — Shell's initial call
- `api emit <id> <payload> --type <type> --link <token>` — Push sensory payload
- `api owner-shutdown <id> --link <token>` — Suspend linked owner

## Introspection
- `api capability` — Print declared hostable_types

**No explicit hook-to-subcommand mapping is documented** for Claude Code events. The document only shows two startup flows (harness-hosted and spt-hosted) but doesn't map SessionStart, UserPromptSubmit, PreToolUse, etc. to specific commands.


# Install-on-Demand Bootstrap Contract

## Detection & Installation Logic

The contract follows this pattern: **check if `spt` is available; if not, run the official installer for your OS.**

### POSIX Shell (Linux/macOS)

```sh
if ! command -v spt >/dev/null 2>&1; then
  echo "spt-core not found - installing..."
  curl -fsSL https://sabermage.github.io/spt-releases/install.sh | sh
  SPT="$HOME/.local/bin/spt"
else
  SPT="spt"
fi
"$SPT" --version
```

The key insight: after install, the current shell hasn't reloaded its PATH, so use the absolute `$HOME/.local/bin/spt` path for the first invocation.

### PowerShell (Windows)

```powershell
if (-not (Get-Command spt -ErrorAction SilentlyContinue)) {
    Write-Output "spt-core not found - installing..."
    irm https://sabermage.github.io/spt-releases/install.ps1 | iex
    $spt = Join-Path $env:LOCALAPPDATA 'spt-core\bin\spt.exe'
} else {
    $spt = 'spt'
}
& $spt --version
```

Windows PATH registration doesn't affect the current process, so the first call must use the absolute path.

## Canonical URLs & Environment Variables

- **POSIX:** `https://sabermage.github.io/spt-releases/install.sh`
- **PowerShell:** `https://sabermage.github.io/spt-releases/install.ps1`

Customization via environment variables (not flags):
- `SPT_INSTALL_VERSION` — specify a release tag
- `SPT_INSTALL_DIR` — override install directory
- `SPT_INSTALL_ASSET_BASE` — local/mirror asset URL
- `SPT_INSTALL_NO_PATH` — skip PATH registration

After initial bootstrap, `spt update` handles signed self-updates automatically.


`spt api` primitive surface is published (seed/listen/bind/boundary/session-end/shutdown/state/poll/worker-*). Bootstrap contract fully published (exact POSIX+PS snippets). **But** api.md documents no hook-event→subcommand mapping for a CC adapter. Before declaring a finding, checking the two adapter-author-facing pages most likely to carry it.


Prompt: Does this page show how a Claude Code (or generic harness) adapter wires its hook events to `spt api` subcommands? Specifically: is there a mapping or example for SessionStart, SessionEnd, UserPromptSubmit, PreToolUse, Stop, SubagentStart, SubagentStop hooks? Quote any hooks.json example, any hook-event-to-subcommand table, and any guidance on delivering messages / injecting additionalContext on UserPromptSubmit or PreToolUse. If there is no such hook-wiring guidance, say so explicitly.


Prompt: Does this integration checklist specify how a harness adapter maps its lifecycle/hook events to `spt api` subcommands? Quote any steps that say "on session start call X", "on user prompt call Y", "on tool use call Z", "on stop/idle call W", "on session end call V". Is there guidance on message delivery timing and injecting additionalContext into the harness on prompt/tool-use hooks? If the hook-event-to-subcommand wiring is not specified, say so explicitly.


# Assessment

This page does **not** provide the hook-wiring guidance you're asking for.

The document shows only two example hooks:

- `"[hooks.SessionStart] fires = "api seed --pid {parent_pid} --session-id {session_id}..."`
- `"[hooks.Idle] fires = "api state idle"`

It explains the general pattern: hooks fire `spt api` commands with placeholder substitution. However, it explicitly lacks:

1. **No mapping table** for SessionEnd, UserPromptSubmit, PreToolUse, Stop, SubagentStart, or SubagentStop
2. **No guidance on message/context injection** beyond a brief note that `"can_inject = true"` surfaces text back into the agent's context
3. **No examples** for additionalContext delivery on specific hooks

The page directs you elsewhere: `"the [harness integration checklist](../harness-contract/integration-checklist.md) — every contract surface grouped by necessity"` and the `"[manifest reference](../harness-contract/manifest.md)"` are cited as complete references, but their content isn't provided here.

For Claude Code adapter specifics, you'd need those downstream documents.


# Hook-to-API Mapping in the Checklist

Yes, the document **does** specify event-to-subcommand wiring. Here are the key mappings:

**Session lifecycle:**
- "harness-hosted: `[hooks.SessionStart] → api seed --pid {parent_pid}` then the session's `api listen <id>`"
- "`api session-end <id>`" or "`api shutdown`" at teardown

**Activity signals:**
- "`[hooks.Idle] → api state idle`" and "`api state busy`"

**Message delivery:**
- Messages arrive via "`[inject]` channels" (hook-based push) or "`api poll <id> --include-deferred`" (pull fallback)

**Context boundaries:**
- "`[hooks.PreCompact/clear] → api boundary <clear|compact>`" for reset survival

**Graceful shutdown:**
- "`api shutdown <id>`" runs "the final echo-commune BEFORE teardown"

**The limitation:** The checklist does **not** detail hook-event-to-command wiring for specific agent operations like tool use or multi-turn prompts. It focuses on session lifecycle, inbound delivery channels, and state transitions rather than granular per-message injection timing. The claude-code example shows broad strokes ("messages arrive over the hook inject channel") but not prompt-by-prompt or tool-by-tool sequencing.


The contract is partially published. To make the finding precise (not a false positive), one more fetch — the manifest hooks section is the crux: it uses a `[hooks.SessionStart]` *manifest* template syntax with `{parent_pid}` placeholders, which is NOT Claude Code's `hooks.json` format. Need to know who materializes the CC hooks.json and who fills the placeholders.


Prompt: Focus on the hooks section of the manifest. Questions: (1) Does the adapter declare hooks in the manifest under a [hooks.*] section, and what is its exact syntax? (2) What placeholders like {parent_pid}, {session_id} are available and WHO substitutes them — spt-core or the harness? (3) Does spt-core generate/materialize the harness's native hook config (e.g. Claude Code hooks.json), or must the adapter author hand-write hooks.json that shells out to `spt api`? (4) Is there a `can_inject` / `[inject]` field and how does an adapter emit additionalContext back into the harness on a prompt hook? Quote the exact manifest fields and any examples verbatim. If any of these is not specified, say so explicitly.


# Hooks Section Analysis

## (1) Declaration Syntax

Yes, hooks are declared under `[hooks.<event>]` sections. The exact syntax:

```toml
[hooks.SessionStart]
fires = "api seed --pid {parent_pid} --session-id {session_id}..."
reads = ["session_id", "parent_pid"]
can_inject = true
```

Each hook requires a `fires` field with an opaque `spt api` command.

## (2) Placeholder Substitution

The document states: **"spt-core runs it with `{key}` substitution placeholders filled."** This means **spt-core performs the substitution**, not the harness. The adapter author declares which keys are available via the `reads` field—spt-core maps input fields into the command template.

## (3) Native Hook Config Generation

The document **does not specify** whether spt-core generates harness-native hook configs. It only states that adapters declare opaque command templates in the manifest; spt-core never parses them. The document is silent on whether adapter authors must hand-write native hook configuration files.

## (4) Injection Mechanism

Yes, `can_inject` exists as a per-hook field:

> "Whether this hook can inject context back to the agent. When `false`, spt-core falls back to its sentinel + relay/poll path instead of expecting injection"

The document also references a separate `[inject]` section specifying **methods** (pty, hook, relay, http) and **activity states** (active, idle), but provides **no technical details** on how adapters actually emit additionalContext back through these channels.


Finding is now airtight. The published contract covers SessionStart/Idle/boundary/SessionEnd/shutdown + the manifest `[hooks.*]` declaration model, but has three real gaps for a CC adapter. This is a public-surface finding — sending to doyle (he owns the docs fix), recording it in-repo, then continuing on the unblocked parts.


Bash

cd "C:/Users/decid/Documents/projects/spt-claude-code" && $OWL send doyle perri <<'EOF'
PUBLIC-SURFACE FINDING (spt-releases GH Pages harness-contract). The `spt api` primitive surface IS published (api.md: seed/listen/bind/boundary/session-end/shutdown/state/poll/worker-*/echo-gate/...), and install-on-demand bootstrap is fully published (exact sh+ps1). But the hook-wiring contract a CC adapter builds against has 3 gaps:

1. HOOK-EVENT -> spt api MAPPING INCOMPLETE. integration-checklist + manifest doc only map: SessionStart->`api seed --pid {parent_pid} --session-id {session_id}` then `api listen <id>`; Idle->`api state idle|busy`; PreCompact/clear->`api boundary clear|compact <id> --to-session-id <sid>`; teardown->`api session-end`/`api shutdown`. MISSING: UserPromptSubmit, PreToolUse, SubagentStart, SubagentStop. (worker-start/stop exist but aren't mapped to the Subagent hook events.)

2. MANIFEST [hooks.*] vs HARNESS-NATIVE hook config UNSPECIFIED. manifest.md declares hooks as `[hooks.<event>] fires="api ..." reads=[...] can_inject=bool`, with spt-core doing {key} substitution from `reads`. It is SILENT on whether spt-core MATERIALIZES the harness-native config (CC hooks.json) from the manifest, OR the adapter author hand-writes hooks.json that shells out to `spt api`. These imply different substitution models: CC passes hook data as JSON on stdin, but the manifest model is spt-core-side {placeholder} fill — so if hooks.json is hand-written, who fills `--pid {parent_pid}`? Unresolved. (Note: this also reframes our SCOPE assumption "plugin ships hooks.json calling spt api" — may instead be manifest-declared + spt-materialized. Need the ruling to know what the sptc plugin actually ships.)

3. INJECTION MECHANISM UNDOCUMENTED. `can_inject` + an `[inject]` section (methods pty/hook/relay/http) exist, but there's NO technical detail on HOW an adapter emits additionalContext back through the hook channel. This blocks (a) general mid-session message delivery on a prompt/tool hook AND (b) our UPS-injection skill-instruction design specifically — I can't validate UPS-injection against the public surface until this is documented.

IMPACT: I can build SessionStart / Stop(idle) / SessionEnd / boundary now. UserPromptSubmit + PreToolUse + Subagent wiring + the whole injection path are blocked on docs. Holding hooks.json (and the UPS-injection design) pending your ruling/docs fix. Recording this in-repo as docs/SPT-CORE-FINDINGS.md.

Proceeding meanwhile on UNBLOCKED work: bootstrap scripts (published verbatim), skill skeletons, plugin.json (name=sptc, done). The UPS-fires-on-/sptc:X test is partly blocked too (the injection HOW is gap #3), but I can still empirically answer the narrower question "does UserPromptSubmit fire at all when the prompt is a /sptc: slash-command" with a trivial echo hook — will run that and report (it's the half of the UPS question that doesn't need the spt injection contract).
EOF
echo "sent=$?"

Send public-surface finding to doyle


Finding sent. Recording it in-repo, then building the unblocked pieces.


Write

# spt-core public-surface findings

> Per the public-surface-only constraint (`AGENTS.md`, `HANDOFF.md` §1): if a capability needed
> to build the adapter is missing/ambiguous in spt-core's **published** surface (`spt-releases`
> releases + GH Pages docs), that is a **finding** — `spt-core`'s published contract has a gap.
> We report it to the spt-core owner (**doyle**) and do **not** reverse-engineer from legacy
> `claude_skill_owl` or reach into spt-core source. This file is the in-repo log of those findings.

| # | Date | Status | Summary |
|---|------|--------|---------|
| F-001 | 2026-06-14 | reported → doyle | Hook-wiring contract for a CC adapter is incomplete (3 sub-gaps) |

---

## F-001 — Harness hook-wiring contract incomplete for a Claude Code adapter

**Reported:** 2026-06-14 to doyle (owl). **Status:** awaiting ruling / docs fix.

**What IS published (sufficient):**
- `harness-contract/api.md` — the full `spt api` primitive surface: `seed`, `listen`, `bind`,
  `boundary`, `session-end`, `shutdown`, `state`, `echo-gate`, `presence`, `driven-by`, `poll`,
  `history-log`, `worker-start/poll/stop`, `bind-shell`, `emit`, `owner-shutdown`, `capability`.
- `harness-contract/install-on-demand.md` — install-on-demand bootstrap, exact `sh` + `ps1`
  snippets, canonical installer URLs, env-var customization. **Fully buildable.**
- `harness-contract/integration-checklist.md` + `manifest.md` map: SessionStart →
  `api seed --pid {parent_pid} --session-id {session_id}` then `api listen <id>`; Idle →
  `api state idle|busy`; PreCompact/clear → `api boundary clear|compact <id> --to-session-id <sid>`;
  teardown → `api session-end <id>` / `api shutdown <id>`.

**The gaps:**

1. **Hook-event → `spt api` mapping is incomplete.** No published mapping for **UserPromptSubmit**,
   **PreToolUse**, **SubagentStart**, **SubagentStop**. (`worker-start/stop` exist but are not tied
   to the Subagent hook events.)
2. **Manifest `[hooks.*]` vs harness-native hook config is unspecified.** `manifest.md` declares
   hooks as `[hooks.<event>] fires="api ..." reads=[...] can_inject=bool`, with spt-core performing
   `{key}` substitution from `reads`. It is **silent** on whether spt-core *materializes* the
   harness-native config (Claude Code `hooks.json`) from the manifest, or the adapter author
   hand-writes `hooks.json` that shells out to `spt api`. These imply different substitution models:
   Claude Code delivers hook data as JSON on **stdin**, but the manifest model is spt-core-side
   `{placeholder}` fill — so if `hooks.json` is hand-written, it is unclear who fills
   `--pid {parent_pid}`. (Also reframes the `SCOPE.md` assumption "the plugin ships a `hooks.json`
   calling `spt api`" — it may instead be manifest-declared + spt-materialized.)
3. **Injection mechanism is undocumented.** `can_inject` + an `[inject]` section (methods
   `pty`/`hook`/`relay`/`http`) exist, but there is **no technical detail** on how an adapter emits
   `additionalContext` back through the hook channel. Blocks (a) general mid-session message
   delivery on a prompt/tool hook, and (b) this project's **UPS-injection** skill-instruction design
   (`docs/adr/0001-*`) specifically — it cannot be validated against the public surface until this
   is documented.

**Impact:** SessionStart / Stop(idle) / SessionEnd / boundary are buildable now. UserPromptSubmit +
PreToolUse + Subagent wiring + the entire injection path are blocked pending docs. `hooks.json` and
the UPS-injection design are held.


File created successfully at: C:\Users\decid\Documents\projects\spt-claude-code\docs\SPT-CORE-FINDINGS.md (file state is current in your context — no need to Read it back)

# spt-core public-surface findings

> Per the public-surface-only constraint (`AGENTS.md`, `HANDOFF.md` §1): if a capability needed
> to build the adapter is missing/ambiguous in spt-core's **published** surface (`spt-releases`
> releases + GH Pages docs), that is a **finding** — `spt-core`'s published contract has a gap.
> We report it to the spt-core owner (**doyle**) and do **not** reverse-engineer from legacy
> `claude_skill_owl` or reach into spt-core source. This file is the in-repo log of those findings.

| # | Date | Status | Summary |
|---|------|--------|---------|
| F-001 | 2026-06-14 | reported → doyle | Hook-wiring contract for a CC adapter is incomplete (3 sub-gaps) |

---

## F-001 — Harness hook-wiring contract incomplete for a Claude Code adapter

**Reported:** 2026-06-14 to doyle (owl). **Status:** awaiting ruling / docs fix.

**What IS published (sufficient):**
- `harness-contract/api.md` — the full `spt api` primitive surface: `seed`, `listen`, `bind`,
  `boundary`, `session-end`, `shutdown`, `state`, `echo-gate`, `presence`, `driven-by`, `poll`,
  `history-log`, `worker-start/poll/stop`, `bind-shell`, `emit`, `owner-shutdown`, `capability`.
- `harness-contract/install-on-demand.md` — install-on-demand bootstrap, exact `sh` + `ps1`
  snippets, canonical installer URLs, env-var customization. **Fully buildable.**
- `harness-contract/integration-checklist.md` + `manifest.md` map: SessionStart →
  `api seed --pid {parent_pid} --session-id {session_id}` then `api listen <id>`; Idle →
  `api state idle|busy`; PreCompact/clear → `api boundary clear|compact <id> --to-session-id <sid>`;
  teardown → `api session-end <id>` / `api shutdown <id>`.

**The gaps:**

1. **Hook-event → `spt api` mapping is incomplete.** No published mapping for **UserPromptSubmit**,
   **PreToolUse**, **SubagentStart**, **SubagentStop**. (`worker-start/stop` exist but are not tied
   to the Subagent hook events.)
2. **Manifest `[hooks.*]` vs harness-native hook config is unspecified.** `manifest.md` declares
   hooks as `[hooks.<event>] fires="api ..." reads=[...] can_inject=bool`, with spt-core performing
   `{key}` substitution from `reads`. It is **silent** on whether spt-core *materializes* the
   harness-native config (Claude Code `hooks.json`) from the manifest, or the adapter author
   hand-writes `hooks.json` that shells out to `spt api`. These imply different substitution models:
   Claude Code delivers hook data as JSON on **stdin**, but the manifest model is spt-core-side
   `{placeholder}` fill — so if `hooks.json` is hand-written, it is unclear who fills
   `--pid {parent_pid}`. (Also reframes the `SCOPE.md` assumption "the plugin ships a `hooks.json`
   calling `spt api`" — it may instead be manifest-declared + spt-materialized.)
3. **Injection mechanism is undocumented.** `can_inject` + an `[inject]` section (methods
   `pty`/`hook`/`relay`/`http`) exist, but there is **no technical detail** on how an adapter emits
   `additionalContext` back through the hook channel. Blocks (a) general mid-session message
   delivery on a prompt/tool hook, and (b) this project's **UPS-injection** skill-instruction design
   (`docs/adr/0001-*`) specifically — it cannot be validated against the public surface until this
   is documented.

**Impact:** SessionStart / Stop(idle) / SessionEnd / boundary are buildable now. UserPromptSubmit +
PreToolUse + Subagent wiring + the entire injection path are blocked pending docs. `hooks.json` and
the UPS-injection design are held.


Writing the bootstrap scripts (published install-on-demand contract verbatim) — impl evidence for REQ-DIST-BOOTSTRAP-INSTALL.


#!/bin/sh
# sptc SessionStart bootstrap (POSIX) — install spt-core on demand if absent.
# Verbatim against the published contract: spt-releases harness-contract/install-on-demand.md.
# The invisible-installer pattern: a user who installs the sptc plugin gets spt-core for free.
# [impl->REQ-DIST-BOOTSTRAP-INSTALL]
set -eu

if ! command -v spt >/dev/null 2>&1; then
  echo "spt-core not found - installing..." >&2
  curl -fsSL https://sabermage.github.io/spt-releases/install.sh | sh
  # PATH is not yet reloaded in this shell — use the absolute install path for the first call.
  SPT="$HOME/.local/bin/spt"
else
  SPT="spt"
fi

"$SPT" --version
# After this initial bootstrap, `spt update` handles signed self-updates automatically.


File created successfully at: C:\Users\decid\Documents\projects\spt-claude-code\plugin\sptc\bootstrap.sh (file state is current in your context — no need to Read it back)

#!/bin/sh
# sptc SessionStart bootstrap (POSIX) — install spt-core on demand if absent.
# Verbatim against the published contract: spt-releases harness-contract/install-on-demand.md.
# The invisible-installer pattern: a user who installs the sptc plugin gets spt-core for free.
# [impl->REQ-DIST-BOOTSTRAP-INSTALL]
set -eu

if ! command -v spt >/dev/null 2>&1; then
  echo "spt-core not found - installing..." >&2
  curl -fsSL https://sabermage.github.io/spt-releases/install.sh | sh
  # PATH is not yet reloaded in this shell — use the absolute install path for the first call.
  SPT="$HOME/.local/bin/spt"
else
  SPT="spt"
fi

"$SPT" --version
# After this initial bootstrap, `spt update` handles signed self-updates automatically.


# sptc SessionStart bootstrap (PowerShell/Windows) — install spt-core on demand if absent.
# Verbatim against the published contract: spt-releases harness-contract/install-on-demand.md.
# The invisible-installer pattern: a user who installs the sptc plugin gets spt-core for free.
# [impl->REQ-DIST-BOOTSTRAP-INSTALL]
if (-not (Get-Command spt -ErrorAction SilentlyContinue)) {
    Write-Output "spt-core not found - installing..."
    irm https://sabermage.github.io/spt-releases/install.ps1 | iex
    # Windows PATH registration doesn't affect the current process — use the absolute path first.
    $spt = Join-Path $env:LOCALAPPDATA 'spt-core\bin\spt.exe'
} else {
    $spt = 'spt'
}

& $spt --version
# After this initial bootstrap, `spt update` handles signed self-updates automatically.


File created successfully at: C:\Users\decid\Documents\projects\spt-claude-code\plugin\sptc\bootstrap.ps1 (file state is current in your context — no need to Read it back)

# sptc SessionStart bootstrap (PowerShell/Windows) — install spt-core on demand if absent.
# Verbatim against the published contract: spt-releases harness-contract/install-on-demand.md.
# The invisible-installer pattern: a user who installs the sptc plugin gets spt-core for free.
# [impl->REQ-DIST-BOOTSTRAP-INSTALL]
if (-not (Get-Command spt -ErrorAction SilentlyContinue)) {
    Write-Output "spt-core not found - installing..."
    irm https://sabermage.github.io/spt-releases/install.ps1 | iex
    # Windows PATH registration doesn't affect the current process — use the absolute path first.
    $spt = Join-Path $env:LOCALAPPDATA 'spt-core\bin\spt.exe'
} else {
    $spt = 'spt'
}

& $spt --version
# After this initial bootstrap, `spt update` handles signed self-updates automatically.


Generating the KEEP-set + `/sptc:version` skill skeletons (thin stubs; operative instructions are adapter-delivered per ADR-0001).


Bash

cd "C:/Users/decid/Documents/projects/spt-claude-code/plugin/sptc" && mkdir -p skills && while IFS='|' read -r nm desc hint purpose; do
  [ -z "$nm" ] && continue
  d="skills/$nm"; mkdir -p "$d"
  {
    printf -- '---\n'
    printf 'name: %s\n' "$nm"
    printf 'description: |\n  %s\n' "$desc"
    [ -n "$hint" ] && printf 'argument-hint: "%s"\n' "$hint"
    printf 'allowed-tools: [Bash]\n'
    printf -- '---\n\n'
    printf '# /sptc:%s\n\n' "$nm"
    printf '> **Skeleton — thin by design.** Operative instructions for this skill are delivered by the\n'
    printf '> `sptc` adapter at invocation time (UserPromptSubmit injection from the adapter `[strings]`;\n'
    printf '> see `docs/adr/0001-distribution-splits-by-volatility.md`). This SKILL.md stays a stub.\n>\n'
    printf '> **Not yet operative.** Wiring is held pending spt-core finding **F-001** (the injection\n'
    printf '> contract — `docs/SPT-CORE-FINDINGS.md`) and the M12 file-backed `[strings]` dependency.\n\n'
    printf '%s\n' "$purpose"
  } > "$d/SKILL.md"
  echo "wrote $d/SKILL.md"
done <<'TABLE'
live|Run a live agent session (Self + Psyche companion). For past sessions, restores a summarized context.|[<id>] [--auto]|Runs a live agent (Self) with its Psyche companion. Maps to spt-hosted bringup + the harness-hosted listen loop.
commune|Send a communal context update to your Psyche.|<<stdin>>|Pushes a context summary to the Psyche so it can produce echo-commune briefs across session boundaries.
ready|Enable inter-agent messaging for the current session.|[<id>]|Registers a perch so this session can receive inter-agent messages.
send|Send a message to another SPT agent.|<target> [--reply-to <sender>]|Delivers a message to another agent; supports reply-to and ring/ask.
signoff|Graceful live-agent shutdown with a final context summary.||Cleanly ends a live session, writing a final summary consumed at teardown.
force-stop|Force-stop an SPT agent — ready or live (with its Psyche).|<id>|Tears down a perch; topology-aware (endpoint shutdown/stop for spt-hosted).
new-alarm|Schedule a one-shot timed alarm delivered as a TIMED PULSE.|<time_spec> -- <message>|Schedules a delayed self-notification delivered when the time arrives.
list-agents|List SPT agents — ready, live, and Psyche processes.||Lists agents/endpoints. Maps to `spt endpoint list`.
version|Report the spt-core-tracked adapter manifest/binary version.||Reports the version-of-truth (adapter manifest/binary tracked by spt-core), not the marketplace skeleton version.
TABLE
echo "--- tree ---"; find . -type f | sort

Generate sptc skill skeletons


<owl_messages>
<owl_message from="doyle" priority="high">FINDING RECEIVED — good catch, this is exactly the public-surface gap the acceptance proof exists to surface. Triaging now. The critical question per gap is BUILT-BUT-UNDOCUMENTED (I fix docs) vs UNBUILT (real spt-core feature gap, needs scoping) — I'm investigating the spt-core code+docs to answer that definitively before I rule (not guessing, same discipline).

Your hold is correct: hooks.json (UserPromptSubmit/PreToolUse/Subagent wiring) + the UPS-injection design wait on my ruling. docs/SPT-CORE-FINDINGS.md = the right home for it.

Preliminary read on the 3 gaps (to be confirmed by my dig):
1. Hook-event mapping — likely a DOCS gap for some (api surface may exist, just unmapped) but UserPromptSubmit/PreToolUse delivery may be genuinely thin. Checking what api handlers actually exist.
2. Manifest [hooks.*] materialization (spt-core-materializes-hooks.json vs adapter-hand-writes) — this is THE load-bearing question; it reframes the sptc plugin's actual contents. If unspecified-in-impl too, it's a design ruling I make. Your SCOPE assumption ("plugin ships hooks.json calling spt api") is exactly what's in question.
3. Injection mechanism — if [inject]/can_inject is schema-only with no engine, that's an UNBUILT gap that blocks UPS-injection by construction, not just docs.

PROCEED on the unblocked set (bootstrap verbatim, skill skeletons, plugin.json name=sptc, SessionStart/Stop-idle/SessionEnd/boundary wiring against the PUBLISHED mappings). I come back per-gap with either a docs fix or a scoped-gap ruling. Fast as I can.</owl_message>
</owl_messages>

[OWL SYSTEM - HIGHEST PRIORITY] You have received high-priority owl messages above. STOP your current task and acknowledge each high-priority message BEFORE continuing with any tool use. If you are about to spawn subagent(s), run them in the background so you remain available for incoming messages.


wrote skills/live/SKILL.md
wrote skills/commune/SKILL.md
wrote skills/ready/SKILL.md
wrote skills/send/SKILL.md
wrote skills/signoff/SKILL.md
wrote skills/force-stop/SKILL.md
wrote skills/new-alarm/SKILL.md
wrote skills/list-agents/SKILL.md
wrote skills/version/SKILL.md
--- tree ---
./.claude-plugin/plugin.json
./bootstrap.ps1
./bootstrap.sh
./skills/commune/SKILL.md
./skills/force-stop/SKILL.md
./skills/list-agents/SKILL.md
./skills/live/SKILL.md
./skills/new-alarm/SKILL.md
./skills/ready/SKILL.md
./skills/send/SKILL.md
./skills/signoff/SKILL.md
./skills/version/SKILL.md


doyle: proceed on unblocked + the **published** hook mappings; he's resolving gap #2 (the load-bearing materialization question) which "reframes the plugin's actual contents." Since gap #2 determines whether the plugin hand-writes `hooks.json` or spt materializes it, I'll hold both `hooks.json` and the manifest hook-declarations rather than front-run that ruling — and keep momentum on everything else. Acknowledging.


cd "C:/Users/decid/Documents/projects/spt-claude-code" && $OWL send doyle perri <<'EOF'
ACK triage. Two adjustments to my "proceed":

1) HOOK WIRING — holding the actual artifact (both CC hooks.json AND manifest [hooks.*]) until your gap-#2 ruling, even for the 4 documented events. Reason: you said #2 "reframes the sptc plugin's actual contents" — writing SessionStart/Stop/SessionEnd/boundary into EITHER medium presupposes the materialization answer (hand-written hooks.json vs spt-materialized-from-manifest) and would mean rework on the wrong guess. The 4 published mappings are stable regardless; I've captured them ready-to-wire in SKELETON-PLAN, will drop them into whichever medium you rule. If you'd rather I commit to the hooks.json-calls-spt-api medium now and adjust later, say so and I'll wire the 4.

2) UPS-fires-on-/sptc:X test — deferring rather than running in THIS live session: install + /reload-plugins on my own active perch risks resetting my env/listener mid-session. I'll run it in a controlled throwaway session (trivial UPS echo hook, observe whether UserPromptSubmit fires on a /sptc: prompt). Queued; reporting the narrow result (the injection HOW stays your gap #3).

COMMITTED this body shortly: bootstrap.sh/.ps1 (published verbatim, impl-tagged), 9 skill skeletons (KEEP set + /sptc:version, thin stubs), plugin.json name=sptc, ADR-0001, docs/SPT-CORE-FINDINGS.md (F-001), name-ruling recorded across ADR/runbook/plan. traceable-reqs: activated BOOTSTRAP-INSTALL=[doc,impl] + 3 reqs=[doc], gate green. Ping when committed + standing by for your per-gap rulings + M12-live.
EOF
echo "sent=$?"

Ack doyle, state hold-on-wiring rationale


➜ SENT:doyle
sent=0


Now updating the registry — add `plugin` scan root, activate the covered reqs.


Edit

roots = ["docs", "docs-site", "CONTEXT.md", "SCOPE.md", "AGENTS.md", "HANDOFF.md"]

roots = ["plugin", "docs", "docs-site", "CONTEXT.md", "SCOPE.md", "AGENTS.md", "HANDOFF.md"]


The file C:\Users\decid\Documents\projects\spt-claude-code\traceable-reqs.toml has been updated successfully. (file state is current in your context — no need to Read it back)

# traceable-reqs manifest for spt-claude-code — SEED.
#
# Authoritative requirement registry: every requirement and every KNOWN-HAZARDS
# invariant lives here as a REQ-* id (see docs/TRACEABILITY.md for the full contract).
#
# SEED YOUR REQs FROM SCOPE.md "Parity-trim" — the DROP / TRANSFORM / KEEP / ADD buckets are
# the parity reference. Mint a REQ-* per surface you port/transform/add as you START each
# milestone (NOT up front). The registry below is intentionally EMPTY (commented shapes only).
#
# ACQUIRE the CLI (it is a released binary, not a crate dependency):
#   gh release download --repo BigscreenVR/traceable-reqs \
#     --pattern '*<your-platform>' --output traceable-reqs   # then chmod +x
# (see INSTANTIATE.md for per-platform patterns + CI wiring.) Validate this seed against
# the installed CLI before relying on it: `traceable-reqs check`.
#
# THE FOUR-STAGE CONTRACT: every requirement is traced from
#   doc  (prose/design)  ->  impl (production code)  ->  unit (unit test)  ->  int (cross-process/node)
# Tag evidence in the SAME commit, on/above the real evidence:
#   // [impl->REQ-FOO]   // [unit->REQ-FOO]   <!-- [doc->REQ-FOO] -->
#
# ACTIVATION MODEL: every requirement starts inactive (`required_stages = []`) so `check`
# stays green pre-code. A milestone ACTIVATES its requirements by setting their real
# `required_stages` when work on them begins. Deferred items stay [] until promoted.
# Never declare the full end-state stage list up front — activate incrementally as evidence
# lands (add "int" at the final integrating wave).

[scan]
# Every evidence location. A missing root makes evidence SILENTLY vanish from the trace —
# audit this whenever a new evidence dir appears. ADD `plugin`, `skills`, `hooks`, `tests`
# the moment those dirs are created (skeleton milestone, body #2) — until they exist a
# nonexistent root is a hard manifest_error, so only list what is on disk now.
roots = ["docs", "docs-site", "CONTEXT.md", "SCOPE.md", "AGENTS.md", "HANDOFF.md"]

[policy]
# Default policy applied at activation: doc + impl + unit. Networking/lifecycle/cross-node
# reqs add "int". Until a req is activated, its per-req `required_stages = []` keeps it out
# of the coverage gate.
required_stages = []

# ───────────────────────────── Requirements ─────────────────────────────────
# Add requirements as you scope milestones, seeded from SCOPE.md "Parity-trim". Shape:
#
# [[requirements]]
# id = "REQ-EXAMPLE-1"
# title = "One-line statement of the requirement"
# required_stages = []  # activate (e.g. ["doc","impl","unit"]) when the milestone starts
#
# KNOWN-HAZARDS invariants are first-class requirements — one REQ-HAZARD-* per invariant,
# each requiring at least "unit" (add "int" where cross-process/cross-node):
#
# [[requirements]]
# id = "REQ-HAZARD-EXAMPLE"
# title = "The invariant, stated so a test can prove it"
# required_stages = []

# ── Milestone: SKELETON (thin cplugs plugin) — minted at kickoff, per-milestone (not
# the whole registry up front). All inactive (required_stages = []) so `check` stays
# green until body #2 lands evidence and flips each to ["doc"] / ["doc","impl"] etc.
# Seeded from SCOPE.md "Parity-trim" (Distribution + ADD buckets).

[[requirements]]
id = "REQ-DIST-PLUGIN-SKELETON"
title = "Marketplace artifact on cplugs is a thin skeleton: namespaced /spt:* skill stubs + hooks.json + plugin.json, no embedded logic"
required_stages = []

[[requirements]]
id = "REQ-DIST-BOOTSTRAP-INSTALL"
title = "SessionStart bootstrap installs spt-core when absent (invisible-installer pattern)"
required_stages = []

[[requirements]]
id = "REQ-DIST-HOOKS-API"
title = "hooks.json delegates to `spt api` rather than carrying adapter logic"
required_stages = []

[[requirements]]
id = "REQ-DIST-MANIFEST-SCHEMA"
title = "CC adapter manifest validates against spt-core's published manifest.schema.json (from spt-releases)"
required_stages = []

[[requirements]]
id = "REQ-UPS-INJECTION"
title = "UserPromptSubmit hook detects /spt:X and injects X's real instructions as additionalContext (SKILL.md files stay skeletons); must be empirically confirmed UPS fires on slash-commands"
required_stages = []

[[requirements]]
id = "REQ-SKILL-VERSION"
title = "/spt:version reports the spt-core-tracked manifest/binary version (the version-of-truth, not the marketplace skeleton version)"
required_stages = []

-roots = ["docs", "docs-site", "CONTEXT.md", "SCOPE.md", "AGENTS.md", "HANDOFF.md"]

+roots = ["plugin", "docs", "docs-site", "CONTEXT.md", "SCOPE.md", "AGENTS.md", "HANDOFF.md"]


[[requirements]]
id = "REQ-DIST-PLUGIN-SKELETON"
title = "Marketplace artifact on cplugs is a thin skeleton: namespaced /spt:* skill stubs + hooks.json + plugin.json, no embedded logic"
required_stages = []

[[requirements]]
id = "REQ-DIST-PLUGIN-SKELETON"
title = "Marketplace artifact on cplugs is a thin skeleton: namespaced /sptc:* skill stubs + hooks.json + plugin.json, no embedded logic"
required_stages = ["doc"]  # doc: ADR-0001. impl held until hooks.json lands (gap-F-001 #2).


[[requirements]]
id = "REQ-DIST-PLUGIN-SKELETON"
title = "Marketplace artifact on cplugs is a thin skeleton: namespaced /sptc:* skill stubs + hooks.json + plugin.json, no embedded logic"
required_stages = ["doc"]  # doc: ADR-0001. impl held until hooks.json lands (gap-F-001 #2).

# traceable-reqs manifest for spt-claude-code — SEED.
#
# Authoritative requirement registry: every requirement and every KNOWN-HAZARDS
# invariant lives here as a REQ-* id (see docs/TRACEABILITY.md for the full contract).
#
# SEED YOUR REQs FROM SCOPE.md "Parity-trim" — the DROP / TRANSFORM / KEEP / ADD buckets are
# the parity reference. Mint a REQ-* per surface you port/transform/add as you START each
# milestone (NOT up front). The registry below is intentionally EMPTY (commented shapes only).
#
# ACQUIRE the CLI (it is a released binary, not a crate dependency):
#   gh release download --repo BigscreenVR/traceable-reqs \
#     --pattern '*<your-platform>' --output traceable-reqs   # then chmod +x
# (see INSTANTIATE.md for per-platform patterns + CI wiring.) Validate this seed against
# the installed CLI before relying on it: `traceable-reqs check`.
#
# THE FOUR-STAGE CONTRACT: every requirement is traced from
#   doc  (prose/design)  ->  impl (production code)  ->  unit (unit test)  ->  int (cross-process/node)
# Tag evidence in the SAME commit, on/above the real evidence:
#   // [impl->REQ-FOO]   // [unit->REQ-FOO]   <!-- [doc->REQ-FOO] -->
#
# ACTIVATION MODEL: every requirement starts inactive (`required_stages = []`) so `check`
# stays green pre-code. A milestone ACTIVATES its requirements by setting their real
# `required_stages` when work on them begins. Deferred items stay [] until promoted.
# Never declare the full end-state stage list up front — activate incrementally as evidence
# lands (add "int" at the final integrating wave).

[scan]
# Every evidence location. A missing root makes evidence SILENTLY vanish from the trace —
# audit this whenever a new evidence dir appears. ADD `plugin`, `skills`, `hooks`, `tests`
# the moment those dirs are created (skeleton milestone, body #2) — until they exist a
# nonexistent root is a hard manifest_error, so only list what is on disk now.
roots = ["plugin", "docs", "docs-site", "CONTEXT.md", "SCOPE.md", "AGENTS.md", "HANDOFF.md"]

[policy]
# Default policy applied at activation: doc + impl + unit. Networking/lifecycle/cross-node
# reqs add "int". Until a req is activated, its per-req `required_stages = []` keeps it out
# of the coverage gate.
required_stages = []

# ───────────────────────────── Requirements ─────────────────────────────────
# Add requirements as you scope milestones, seeded from SCOPE.md "Parity-trim". Shape:
#
# [[requirements]]
# id = "REQ-EXAMPLE-1"
# title = "One-line statement of the requirement"
# required_stages = []  # activate (e.g. ["doc","impl","unit"]) when the milestone starts
#
# KNOWN-HAZARDS invariants are first-class requirements — one REQ-HAZARD-* per invariant,
# each requiring at least "unit" (add "int" where cross-process/cross-node):
#
# [[requirements]]
# id = "REQ-HAZARD-EXAMPLE"
# title = "The invariant, stated so a test can prove it"
# required_stages = []

# ── Milestone: SKELETON (thin cplugs plugin) — minted at kickoff, per-milestone (not
# the whole registry up front). All inactive (required_stages = []) so `check` stays
# green until body #2 lands evidence and flips each to ["doc"] / ["doc","impl"] etc.
# Seeded from SCOPE.md "Parity-trim" (Distribution + ADD buckets).

[[requirements]]
id = "REQ-DIST-PLUGIN-SKELETON"
title = "Marketplace artifact on cplugs is a thin skeleton: namespaced /spt:* skill stubs + hooks.json + plugin.json, no embedded logic"
required_stages = []

[[requirements]]
id = "REQ-DIST-BOOTSTRAP-INSTALL"
title = "SessionStart bootstrap installs spt-core when absent (invisible-installer pattern)"
required_stages = []

[[requirements]]
id = "REQ-DIST-HOOKS-API"
title = "hooks.json delegates to `spt api` rather than carrying adapter logic"
required_stages = []

[[requirements]]
id = "REQ-DIST-MANIFEST-SCHEMA"
title = "CC adapter manifest validates against spt-core's published manifest.schema.json (from spt-releases)"
required_stages = []

[[requirements]]
id = "REQ-UPS-INJECTION"
title = "UserPromptSubmit hook detects /spt:X and injects X's real instructions as additionalContext (SKILL.md files stay skeletons); must be empirically confirmed UPS fires on slash-commands"
required_stages = []

[[requirements]]
id = "REQ-SKILL-VERSION"
title = "/spt:version reports the spt-core-tracked manifest/binary version (the version-of-truth, not the marketplace skeleton version)"
required_stages = []

-title = "Marketplace artifact on cplugs is a thin skeleton: namespaced /spt:* skill stubs + hooks.json + plugin.json, no embedded logic"

+title = "Marketplace artifact on cplugs is a thin skeleton: namespaced /sptc:* skill stubs + hooks.json + plugin.json, no embedded logic"

+required_stages = ["doc"]  # doc: ADR-0001. impl held until hooks.json lands (gap-F-001 #2).


id = "REQ-DIST-BOOTSTRAP-INSTALL"
title = "SessionStart bootstrap installs spt-core when absent (invisible-installer pattern)"
required_stages = []

id = "REQ-DIST-BOOTSTRAP-INSTALL"
title = "SessionStart bootstrap installs spt-core when absent (invisible-installer pattern)"
required_stages = ["doc", "impl"]  # doc: ADR-0001. impl: plugin/sptc/bootstrap.{sh,ps1}.


id = "REQ-DIST-BOOTSTRAP-INSTALL"
title = "SessionStart bootstrap installs spt-core when absent (invisible-installer pattern)"
required_stages = ["doc", "impl"]  # doc: ADR-0001. impl: plugin/sptc/bootstrap.{sh,ps1}.

# traceable-reqs manifest for spt-claude-code — SEED.
#
# Authoritative requirement registry: every requirement and every KNOWN-HAZARDS
# invariant lives here as a REQ-* id (see docs/TRACEABILITY.md for the full contract).
#
# SEED YOUR REQs FROM SCOPE.md "Parity-trim" — the DROP / TRANSFORM / KEEP / ADD buckets are
# the parity reference. Mint a REQ-* per surface you port/transform/add as you START each
# milestone (NOT up front). The registry below is intentionally EMPTY (commented shapes only).
#
# ACQUIRE the CLI (it is a released binary, not a crate dependency):
#   gh release download --repo BigscreenVR/traceable-reqs \
#     --pattern '*<your-platform>' --output traceable-reqs   # then chmod +x
# (see INSTANTIATE.md for per-platform patterns + CI wiring.) Validate this seed against
# the installed CLI before relying on it: `traceable-reqs check`.
#
# THE FOUR-STAGE CONTRACT: every requirement is traced from
#   doc  (prose/design)  ->  impl (production code)  ->  unit (unit test)  ->  int (cross-process/node)
# Tag evidence in the SAME commit, on/above the real evidence:
#   // [impl->REQ-FOO]   // [unit->REQ-FOO]   <!-- [doc->REQ-FOO] -->
#
# ACTIVATION MODEL: every requirement starts inactive (`required_stages = []`) so `check`
# stays green pre-code. A milestone ACTIVATES its requirements by setting their real
# `required_stages` when work on them begins. Deferred items stay [] until promoted.
# Never declare the full end-state stage list up front — activate incrementally as evidence
# lands (add "int" at the final integrating wave).

[scan]
# Every evidence location. A missing root makes evidence SILENTLY vanish from the trace —
# audit this whenever a new evidence dir appears. ADD `plugin`, `skills`, `hooks`, `tests`
# the moment those dirs are created (skeleton milestone, body #2) — until they exist a
# nonexistent root is a hard manifest_error, so only list what is on disk now.
roots = ["plugin", "docs", "docs-site", "CONTEXT.md", "SCOPE.md", "AGENTS.md", "HANDOFF.md"]

[policy]
# Default policy applied at activation: doc + impl + unit. Networking/lifecycle/cross-node
# reqs add "int". Until a req is activated, its per-req `required_stages = []` keeps it out
# of the coverage gate.
required_stages = []

# ───────────────────────────── Requirements ─────────────────────────────────
# Add requirements as you scope milestones, seeded from SCOPE.md "Parity-trim". Shape:
#
# [[requirements]]
# id = "REQ-EXAMPLE-1"
# title = "One-line statement of the requirement"
# required_stages = []  # activate (e.g. ["doc","impl","unit"]) when the milestone starts
#
# KNOWN-HAZARDS invariants are first-class requirements — one REQ-HAZARD-* per invariant,
# each requiring at least "unit" (add "int" where cross-process/cross-node):
#
# [[requirements]]
# id = "REQ-HAZARD-EXAMPLE"
# title = "The invariant, stated so a test can prove it"
# required_stages = []

# ── Milestone: SKELETON (thin cplugs plugin) — minted at kickoff, per-milestone (not
# the whole registry up front). All inactive (required_stages = []) so `check` stays
# green until body #2 lands evidence and flips each to ["doc"] / ["doc","impl"] etc.
# Seeded from SCOPE.md "Parity-trim" (Distribution + ADD buckets).

[[requirements]]
id = "REQ-DIST-PLUGIN-SKELETON"
title = "Marketplace artifact on cplugs is a thin skeleton: namespaced /sptc:* skill stubs + hooks.json + plugin.json, no embedded logic"
required_stages = ["doc"]  # doc: ADR-0001. impl held until hooks.json lands (gap-F-001 #2).

[[requirements]]
id = "REQ-DIST-BOOTSTRAP-INSTALL"
title = "SessionStart bootstrap installs spt-core when absent (invisible-installer pattern)"
required_stages = []

[[requirements]]
id = "REQ-DIST-HOOKS-API"
title = "hooks.json delegates to `spt api` rather than carrying adapter logic"
required_stages = []

[[requirements]]
id = "REQ-DIST-MANIFEST-SCHEMA"
title = "CC adapter manifest validates against spt-core's published manifest.schema.json (from spt-releases)"
required_stages = []

[[requirements]]
id = "REQ-UPS-INJECTION"
title = "UserPromptSubmit hook detects /spt:X and injects X's real instructions as additionalContext (SKILL.md files stay skeletons); must be empirically confirmed UPS fires on slash-commands"
required_stages = []

[[requirements]]
id = "REQ-SKILL-VERSION"
title = "/spt:version reports the spt-core-tracked manifest/binary version (the version-of-truth, not the marketplace skeleton version)"
required_stages = []

+required_stages = ["doc", "impl"]  # doc: ADR-0001. impl: plugin/sptc/bootstrap.{sh,ps1}.


id = "REQ-DIST-HOOKS-API"
title = "hooks.json delegates to `spt api` rather than carrying adapter logic"
required_stages = []

id = "REQ-DIST-HOOKS-API"
title = "hooks.json delegates to `spt api` rather than carrying adapter logic"
required_stages = ["doc"]  # doc: ADR-0001. impl held until gap-F-001 #2 (hooks.json vs spt-materialized) ruled.


id = "REQ-DIST-HOOKS-API"
title = "hooks.json delegates to `spt api` rather than carrying adapter logic"
required_stages = ["doc"]  # doc: ADR-0001. impl held until gap-F-001 #2 (hooks.json vs spt-materialized) ruled.

# traceable-reqs manifest for spt-claude-code — SEED.
#
# Authoritative requirement registry: every requirement and every KNOWN-HAZARDS
# invariant lives here as a REQ-* id (see docs/TRACEABILITY.md for the full contract).
#
# SEED YOUR REQs FROM SCOPE.md "Parity-trim" — the DROP / TRANSFORM / KEEP / ADD buckets are
# the parity reference. Mint a REQ-* per surface you port/transform/add as you START each
# milestone (NOT up front). The registry below is intentionally EMPTY (commented shapes only).
#
# ACQUIRE the CLI (it is a released binary, not a crate dependency):
#   gh release download --repo BigscreenVR/traceable-reqs \
#     --pattern '*<your-platform>' --output traceable-reqs   # then chmod +x
# (see INSTANTIATE.md for per-platform patterns + CI wiring.) Validate this seed against
# the installed CLI before relying on it: `traceable-reqs check`.
#
# THE FOUR-STAGE CONTRACT: every requirement is traced from
#   doc  (prose/design)  ->  impl (production code)  ->  unit (unit test)  ->  int (cross-process/node)
# Tag evidence in the SAME commit, on/above the real evidence:
#   // [impl->REQ-FOO]   // [unit->REQ-FOO]   <!-- [doc->REQ-FOO] -->
#
# ACTIVATION MODEL: every requirement starts inactive (`required_stages = []`) so `check`
# stays green pre-code. A milestone ACTIVATES its requirements by setting their real
# `required_stages` when work on them begins. Deferred items stay [] until promoted.
# Never declare the full end-state stage list up front — activate incrementally as evidence
# lands (add "int" at the final integrating wave).

[scan]
# Every evidence location. A missing root makes evidence SILENTLY vanish from the trace —
# audit this whenever a new evidence dir appears. ADD `plugin`, `skills`, `hooks`, `tests`
# the moment those dirs are created (skeleton milestone, body #2) — until they exist a
# nonexistent root is a hard manifest_error, so only list what is on disk now.
roots = ["plugin", "docs", "docs-site", "CONTEXT.md", "SCOPE.md", "AGENTS.md", "HANDOFF.md"]

[policy]
# Default policy applied at activation: doc + impl + unit. Networking/lifecycle/cross-node
# reqs add "int". Until a req is activated, its per-req `required_stages = []` keeps it out
# of the coverage gate.
required_stages = []

# ───────────────────────────── Requirements ─────────────────────────────────
# Add requirements as you scope milestones, seeded from SCOPE.md "Parity-trim". Shape:
#
# [[requirements]]
# id = "REQ-EXAMPLE-1"
# title = "One-line statement of the requirement"
# required_stages = []  # activate (e.g. ["doc","impl","unit"]) when the milestone starts
#
# KNOWN-HAZARDS invariants are first-class requirements — one REQ-HAZARD-* per invariant,
# each requiring at least "unit" (add "int" where cross-process/cross-node):
#
# [[requirements]]
# id = "REQ-HAZARD-EXAMPLE"
# title = "The invariant, stated so a test can prove it"
# required_stages = []

# ── Milestone: SKELETON (thin cplugs plugin) — minted at kickoff, per-milestone (not
# the whole registry up front). All inactive (required_stages = []) so `check` stays
# green until body #2 lands evidence and flips each to ["doc"] / ["doc","impl"] etc.
# Seeded from SCOPE.md "Parity-trim" (Distribution + ADD buckets).

[[requirements]]
id = "REQ-DIST-PLUGIN-SKELETON"
title = "Marketplace artifact on cplugs is a thin skeleton: namespaced /sptc:* skill stubs + hooks.json + plugin.json, no embedded logic"
required_stages = ["doc"]  # doc: ADR-0001. impl held until hooks.json lands (gap-F-001 #2).

[[requirements]]
id = "REQ-DIST-BOOTSTRAP-INSTALL"
title = "SessionStart bootstrap installs spt-core when absent (invisible-installer pattern)"
required_stages = ["doc", "impl"]  # doc: ADR-0001. impl: plugin/sptc/bootstrap.{sh,ps1}.

[[requirements]]
id = "REQ-DIST-HOOKS-API"
title = "hooks.json delegates to `spt api` rather than carrying adapter logic"
required_stages = []

[[requirements]]
id = "REQ-DIST-MANIFEST-SCHEMA"
title = "CC adapter manifest validates against spt-core's published manifest.schema.json (from spt-releases)"
required_stages = []

[[requirements]]
id = "REQ-UPS-INJECTION"
title = "UserPromptSubmit hook detects /spt:X and injects X's real instructions as additionalContext (SKILL.md files stay skeletons); must be empirically confirmed UPS fires on slash-commands"
required_stages = []

[[requirements]]
id = "REQ-SKILL-VERSION"
title = "/spt:version reports the spt-core-tracked manifest/binary version (the version-of-truth, not the marketplace skeleton version)"
required_stages = []

+required_stages = ["doc"]  # doc: ADR-0001. impl held until gap-F-001 #2 (hooks.json vs spt-materialized) ruled.


title = "UserPromptSubmit hook detects /spt:X and injects X's real instructions as additionalContext (SKILL.md files stay skeletons); must be empirically confirmed UPS fires on slash-commands"
required_stages = []

title = "UserPromptSubmit hook detects /sptc:X and injects X's real instructions as additionalContext (SKILL.md files stay skeletons); must be empirically confirmed UPS fires on slash-commands"
required_stages = ["doc"]  # doc: ADR-0001. impl held until gap-F-001 #3 (injection contract) ruled + UPS-fires test.


<owl_messages>
<owl_message from="doyle" priority="high">Both adjustments = right calls, affirmed:

1) HOLD the hook artifact (don't commit to EITHER medium yet) — your instinct is correct. Gap #2 decides whether the sptc plugin SHIPS a hand-written hooks.json or just a manifest [hooks.*] that spt-core materializes; writing the 4 mappings into the wrong medium = rework. Capturing them ready-to-wire in SKELETON-PLAN is exactly right. Do NOT commit to hooks.json-shells-spt-api on a guess — my gap-#2 ruling resolves the medium, then you drop the 4 (and the blocked events) into the ruled shape in one pass. My triage dig is running now; ruling inbound.

2) UPS test in a throwaway session — correct + responsible. Never /reload-plugins on your own live perch (resets env/listener mid-session = you'd drop off the loop). Controlled throwaway w/ a trivial UPS echo hook is the right rig. Report the narrow result (does UserPromptSubmit fire on a /sptc: prompt); the injection HOW stays my gap #3.

Commit the unblocked body (bootstrap verbatim, skill skeletons, plugin.json=sptc, ADR-0001, SPT-CORE-FINDINGS F-001, name-ruling). traceable activation (BOOTSTRAP-INSTALL=[doc,impl] + 3 [doc]) = good incremental shape. Ping when committed. Per-gap rulings coming from my dig.</owl_message>
</owl_messages>

[OWL SYSTEM - HIGHEST PRIORITY] You have received high-priority owl messages above. STOP your current task and acknowledge each high-priority message BEFORE continuing with any tool use. If you are about to spawn subagent(s), run them in the background so you remain available for incoming messages.


title = "UserPromptSubmit hook detects /sptc:X and injects X's real instructions as additionalContext (SKILL.md files stay skeletons); must be empirically confirmed UPS fires on slash-commands"
required_stages = ["doc"]  # doc: ADR-0001. impl held until gap-F-001 #3 (injection contract) ruled + UPS-fires test.

# traceable-reqs manifest for spt-claude-code — SEED.
#
# Authoritative requirement registry: every requirement and every KNOWN-HAZARDS
# invariant lives here as a REQ-* id (see docs/TRACEABILITY.md for the full contract).
#
# SEED YOUR REQs FROM SCOPE.md "Parity-trim" — the DROP / TRANSFORM / KEEP / ADD buckets are
# the parity reference. Mint a REQ-* per surface you port/transform/add as you START each
# milestone (NOT up front). The registry below is intentionally EMPTY (commented shapes only).
#
# ACQUIRE the CLI (it is a released binary, not a crate dependency):
#   gh release download --repo BigscreenVR/traceable-reqs \
#     --pattern '*<your-platform>' --output traceable-reqs   # then chmod +x
# (see INSTANTIATE.md for per-platform patterns + CI wiring.) Validate this seed against
# the installed CLI before relying on it: `traceable-reqs check`.
#
# THE FOUR-STAGE CONTRACT: every requirement is traced from
#   doc  (prose/design)  ->  impl (production code)  ->  unit (unit test)  ->  int (cross-process/node)
# Tag evidence in the SAME commit, on/above the real evidence:
#   // [impl->REQ-FOO]   // [unit->REQ-FOO]   <!-- [doc->REQ-FOO] -->
#
# ACTIVATION MODEL: every requirement starts inactive (`required_stages = []`) so `check`
# stays green pre-code. A milestone ACTIVATES its requirements by setting their real
# `required_stages` when work on them begins. Deferred items stay [] until promoted.
# Never declare the full end-state stage list up front — activate incrementally as evidence
# lands (add "int" at the final integrating wave).

[scan]
# Every evidence location. A missing root makes evidence SILENTLY vanish from the trace —
# audit this whenever a new evidence dir appears. ADD `plugin`, `skills`, `hooks`, `tests`
# the moment those dirs are created (skeleton milestone, body #2) — until they exist a
# nonexistent root is a hard manifest_error, so only list what is on disk now.
roots = ["plugin", "docs", "docs-site", "CONTEXT.md", "SCOPE.md", "AGENTS.md", "HANDOFF.md"]

[policy]
# Default policy applied at activation: doc + impl + unit. Networking/lifecycle/cross-node
# reqs add "int". Until a req is activated, its per-req `required_stages = []` keeps it out
# of the coverage gate.
required_stages = []

# ───────────────────────────── Requirements ─────────────────────────────────
# Add requirements as you scope milestones, seeded from SCOPE.md "Parity-trim". Shape:
#
# [[requirements]]
# id = "REQ-EXAMPLE-1"
# title = "One-line statement of the requirement"
# required_stages = []  # activate (e.g. ["doc","impl","unit"]) when the milestone starts
#
# KNOWN-HAZARDS invariants are first-class requirements — one REQ-HAZARD-* per invariant,
# each requiring at least "unit" (add "int" where cross-process/cross-node):
#
# [[requirements]]
# id = "REQ-HAZARD-EXAMPLE"
# title = "The invariant, stated so a test can prove it"
# required_stages = []

# ── Milestone: SKELETON (thin cplugs plugin) — minted at kickoff, per-milestone (not
# the whole registry up front). All inactive (required_stages = []) so `check` stays
# green until body #2 lands evidence and flips each to ["doc"] / ["doc","impl"] etc.
# Seeded from SCOPE.md "Parity-trim" (Distribution + ADD buckets).

[[requirements]]
id = "REQ-DIST-PLUGIN-SKELETON"
title = "Marketplace artifact on cplugs is a thin skeleton: namespaced /sptc:* skill stubs + hooks.json + plugin.json, no embedded logic"
required_stages = ["doc"]  # doc: ADR-0001. impl held until hooks.json lands (gap-F-001 #2).

[[requirements]]
id = "REQ-DIST-BOOTSTRAP-INSTALL"
title = "SessionStart bootstrap installs spt-core when absent (invisible-installer pattern)"
required_stages = ["doc", "impl"]  # doc: ADR-0001. impl: plugin/sptc/bootstrap.{sh,ps1}.

[[requirements]]
id = "REQ-DIST-HOOKS-API"
title = "hooks.json delegates to `spt api` rather than carrying adapter logic"
required_stages = ["doc"]  # doc: ADR-0001. impl held until gap-F-001 #2 (hooks.json vs spt-materialized) ruled.

[[requirements]]
id = "REQ-DIST-MANIFEST-SCHEMA"
title = "CC adapter manifest validates against spt-core's published manifest.schema.json (from spt-releases)"
required_stages = []

[[requirements]]
id = "REQ-UPS-INJECTION"
title = "UserPromptSubmit hook detects /spt:X and injects X's real instructions as additionalContext (SKILL.md files stay skeletons); must be empirically confirmed UPS fires on slash-commands"
required_stages = []

[[requirements]]
id = "REQ-SKILL-VERSION"
title = "/spt:version reports the spt-core-tracked manifest/binary version (the version-of-truth, not the marketplace skeleton version)"
required_stages = []

-title = "UserPromptSubmit hook detects /spt:X and injects X's real instructions as additionalContext (SKILL.md files stay skeletons); must be empirically confirmed UPS fires on slash-commands"

+title = "UserPromptSubmit hook detects /sptc:X and injects X's real instructions as additionalContext (SKILL.md files stay skeletons); must be empirically confirmed UPS fires on slash-commands"

+required_stages = ["doc"]  # doc: ADR-0001. impl held until gap-F-001 #3 (injection contract) ruled + UPS-fires test.


doyle affirmed both holds. Updating the plan to capture progress + the ready-to-wire mappings, then committing.


## Tasks

- [x] Green `traceable-reqs check` (fix seed scan-root manifest_error + 4 template tag-leaks).
- [x] Seed skeleton-milestone REQs (inactive): DIST-PLUGIN-SKELETON, DIST-BOOTSTRAP-INSTALL,
      DIST-HOOKS-API, DIST-MANIFEST-SCHEMA, UPS-INJECTION, SKILL-VERSION.
- [x] Capture cplugs skeleton-publish mechanics in `docs/RELEASE-RUNBOOK.md`.
- [ ] Write ADR-0001 (distribution split) → `doc` evidence; activate reqs' `doc` stage.
- [ ] Scaffold `plugin/` (plugin.json), `hooks/` (hooks.json → `spt api`; UPS-injection hook),
      `skills/` (KEEP skeletons + `/spt:version`). Add these as `[scan].roots` when created.
- [ ] Empirically test UPS-fires-on-slash-command; record result in ADR / KNOWN-HAZARDS.
- [ ] Validate `plugin.json` + adapter manifest shape against published `manifest.schema.json`
      from `SaberMage/spt-releases`.
- [ ] Activate `impl`/`unit` stages + tag as each surface lands; `traceable-reqs check` EXIT=0.

## Gate

## Tasks

- [x] Green `traceable-reqs check` (fix seed scan-root manifest_error + 4 template tag-leaks).
- [x] Seed skeleton-milestone REQs: DIST-PLUGIN-SKELETON, DIST-BOOTSTRAP-INSTALL, DIST-HOOKS-API,
      DIST-MANIFEST-SCHEMA, UPS-INJECTION, SKILL-VERSION.
- [x] Capture cplugs skeleton-publish mechanics in `docs/RELEASE-RUNBOOK.md`.
- [x] Write ADR-0001 (distribution split) → `doc` evidence (PLUGIN-SKELETON, HOOKS-API,
      BOOTSTRAP-INSTALL, UPS-INJECTION activated to `doc`).
- [x] `plugin/sptc/plugin.json` (name=sptc), `plugin/sptc/bootstrap.{sh,ps1}` (published
      install-on-demand verbatim; BOOTSTRAP-INSTALL → `doc,impl`), 9 skill skeletons (KEEP set +
      `/sptc:version`, thin stubs). Added `plugin` as a `[scan].root`.
- [x] Record spt-core public-surface finding **F-001** in `docs/SPT-CORE-FINDINGS.md`; reported.
- [ ] **BLOCKED on F-001 ruling:** the hook artifact. Held in BOTH media (CC `hooks.json` AND
      manifest `[hooks.*]`) pending gap-#2 (does the plugin hand-write `hooks.json`, or does
      spt-core materialize it from the manifest?). The 4 published mappings are ready (below).
- [ ] **BLOCKED on F-001 #3:** UPS-injection impl (the additionalContext channel HOW).
- [ ] UPS-fires-on-`/sptc:X` empirical test — run in a **throwaway** session (never
      `/reload-plugins` on the live perch); trivial UPS echo hook; report the narrow fire/no-fire.
- [ ] `/sptc:version` operative body + adapter manifest → validate against published
      `manifest.schema.json` (`reference/schema.md`); activate MANIFEST-SCHEMA, SKILL-VERSION.
- [ ] Tag `impl`/`unit` + activate as each surface lands; `traceable-reqs check` EXIT=0.

## Published hook→`spt api` mappings (ready-to-wire once F-001 #2 rules the medium)

From the published harness-contract (`api.md` / `integration-checklist.md` / `manifest.md`):

| Claude Code hook | `spt api` invocation (published) |
|---|---|
| SessionStart | `api seed --pid {parent_pid} --session-id {session_id}` then `api listen <id>` |
| Stop / Idle | `api state idle` (and `api state busy` on activity) |
| PreCompact / clear | `api boundary <clear\|compact> <id> --to-session-id <sid>` |
| SessionEnd | `api session-end <id>` (or `api shutdown <id>` for graceful final echo-commune) |
| UserPromptSubmit | **gap F-001 #1/#3** — not published |
| PreToolUse | **gap F-001 #1/#3** — not published |
| SubagentStart / SubagentStop | `api worker-start/worker-stop` exist but **not mapped** (gap F-001 #1) |

## Gate