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

## Consequences

<!-- [doc->REQ-DIST-MANIFEST-SCHEMA] -->
- The marketplace plugin can sit static across many manifest/binary updates → the
  version-of-truth is the spt-core-tracked manifest/binary version (`spt adapter list` /
  `/spt:version`), **not** the marketplace `plugin.json` version. The CC adapter manifest
  (`adapter/claude-spt.toml`) is authored against spt-core's **published** `manifest.schema.json`
  (vendored at `adapter/manifest.schema.json`) and CI-gated against it (`ci/manifest/`).

<!-- [doc->REQ-DIST-PLUGIN-SKELETON] -->
<!-- [doc->REQ-DIST-HOOKS-API] -->
<!-- [doc->REQ-DIST-BOOTSTRAP-INSTALL] -->
<!-- [doc->REQ-UPS-INJECTION] -->
# Distribution splits by volatility: a thin skeleton plugin, an spt-conducted manifest + binary

## Status

accepted (2026-06-14)

## Context

`spt-claude-code` is the `claude-spt` adapter — simultaneously spt-core's v1 acceptance proof,
its first casual-end-user entrypoint (a Claude Code plugin on `SaberMage/cplugs`), and an
invisible spt-core installer. It must ship as a CC plugin, yet almost all real logic now lives
inside the `spt` binary and the CC adapter manifest, both **conducted by spt-core**, not by the
plugin author.

Forces:

1. **Two very different churn rates.** Skill *instructions*, the `[digest]` extractor, profiles,
   strings, and hints change often. The plugin's *structure* (which skills exist, hook wiring,
   bootstrap) changes rarely. Coupling them to one release cadence means either over-bumping the
   marketplace or letting logic rot.
2. **Claude Code has no plugin-file integrity check.** In-place edits to an installed plugin do
   not orphan it; only *old version directories* orphan on a version bump (GC'd after 7 days).
   So low-churn structure is safe to ship via the marketplace, and high-churn logic does *not*
   need to ride the marketplace at all.
3. **The skill `/spt:` namespace must be preserved** (legacy parity). User-scope skills under
   `~/.claude/skills` cannot namespace (they surface as bare `/live`, collision-prone), so skills
   must stay **plugin-provided**.
4. **Users install mid-session**, so the plugin cannot assume a SessionStart fire has installed
   spt-core; the bootstrap and a `/spt:setup` skill must cover the gap.

## Decision

Split the adapter across **three layers by volatility** (see `SCOPE.md` §"Distribution
architecture"):

| Layer | Contents | Home | Churn |
|---|---|---|---|
| **Skeleton plugin** | namespaced `/spt:*` skill *skeletons*, `hooks.json`, SessionStart bootstrap, `plugin.json` | `SaberMage/cplugs` | low |
| **spt binary** | all logic | spt-core domain (signed, peer-propagated) | high |
| **CC adapter manifest** | `[digest]` extractor, profiles, strings, hints | spt-core adapter registry (**not** plugin files) | medium |

Concretely:

- **The marketplace plugin is a true thin skeleton.** It carries skill *skeletons* (frontmatter
  + a stub body), `hooks.json`, a SessionStart bootstrap, and `plugin.json` — **no binary, no
  manifest, no embedded logic.** [`REQ-DIST-PLUGIN-SKELETON`]
- **`hooks.json` delegates to `spt api`** rather than carrying adapter behavior. The hook commands
  invoke the spt binary's published `api` surface; the plugin ships no binary of its own (contrast
  legacy, which shipped `owl.exe` and called it directly). [`REQ-DIST-HOOKS-API`]
- **A SessionStart bootstrap installs spt-core when absent** — the invisible-installer pattern.
  A user who installs the plugin gets spt-core for free. [`REQ-DIST-BOOTSTRAP-INSTALL`]
- **Skill instructions are delivered by UPS-injection.** A `UserPromptSubmit` hook detects `/spt:X`
  in the prompt and injects X's real instructions as `additionalContext`, sourced from the adapter
  `[strings]` (file-backed, so the manifest doesn't bloat). SKILL.md files stay skeletons; the
  volatile instruction text lives in spt-core-conducted strings. [`REQ-UPS-INJECTION`]
- **The "bulk" install/update is manifest + binary**, conducted by spt-core's own framework (the
  file-pull channel), not a marketplace bump. The marketplace plugin rarely needs a version bump
  and never orphans an active install.

### Rejected alternatives

- **Global user-scope skills** (`~/.claude/skills`) — lose the `/spt:` namespace (bare `/live`,
  collision-prone). Rejected.
- **fetch-stub skills** (SKILL.md fetches its real body at runtime) — VETOED by the operator:
  adds terminal noise, latency, and an extra tool call per invocation. UPS-injection chosen
  instead. (`SCOPE.md` §"Skill-instruction delivery".)
- **A fat plugin shipping the binary** (legacy `owl.exe` model) — couples high-churn logic to the
  marketplace cadence and forces the binary/handoff/cache-prune machinery the thin skeleton has no
  need for. Rejected; logic rides the spt binary + adapter registry.

### Open / to-confirm

- **UPS-fires-on-slash-command is an empirical assumption.** The design assumes
  `UserPromptSubmit` fires when the prompt is a `/spt:X` slash-command. This is undocumented and
  must be confirmed at build time; if it does **not** fire, the fallback is in-SKILL.md
  instructions. (Tracked under `REQ-UPS-INJECTION`; see `SKELETON-PLAN.md`.)
- **File-backed `[strings]` is an M12 spt-core dependency** — until M12 publishes, instruction
  bodies cannot be externalized; skeleton SKILL.md files may carry interim inline instructions.

## Consequences

- The marketplace plugin can sit static across many manifest/binary updates → the
  version-of-truth is the spt-core-tracked manifest/binary version (`spt adapter list` /
  `/spt:version`), **not** the marketplace `plugin.json` version. These are distinct numbers on
  independent schedules (see `docs/RELEASE-RUNBOOK.md`).
- New obligation: a dual update path kept in sync — file-pull (real) + a cautionary
  `claude plugin update` (skeleton sync only).
- Forces the cplugs **plugin-name** question, now **ruled (operator, 2026-06-14)**: because CC
  invokes skills as `/<plugin>:<skill>` (the skill prefix is hard-tied to the `plugin.json` name —
  no override; confirmed against the official plugin docs), the marketplace plugin is named
  **`sptc`** and skills surface as **`/sptc:*`** for now. **Succession plan:** once `spt-claude-code`
  proves parity, flip `plugin.json` name `sptc`→`spt` (skills become `/spt:*`) and retire/rename
  legacy owl's cplugs `spt` plugin in the same coordinated move (two plugins cannot share the
  `spt` name). `sptc` is the single-substitution seam — the flip is `s/sptc/spt/`.
- Mints no new REQs beyond those already seeded for the skeleton milestone; this ADR is the
  `doc`-stage evidence for `REQ-DIST-PLUGIN-SKELETON`, `REQ-DIST-HOOKS-API`,
  `REQ-DIST-BOOTSTRAP-INSTALL`, and `REQ-UPS-INJECTION`.

-  `/spt:version`), **not** the marketplace `plugin.json` version. These are distinct numbers on

+  `/spt:version`), **not** the marketplace `plugin.json` version. The CC adapter manifest

+  (`adapter/claude-spt.toml`) is authored against spt-core's **published** `manifest.schema.json`

+  (vendored at `adapter/manifest.schema.json`) and CI-gated against it (`ci/manifest/`). These are distinct numbers on


All wired. Run the validator, unit test, then full gate suite.


Bash

cd /c/Users/decid/Documents/projects/spt-claude-code
echo "=== check-manifest.sh ==="; sh ci/manifest/check-manifest.sh; echo "rc=$?"
echo "=== unit test ==="; sh tests/manifest-schema.sh; echo "rc=$?"


=== check-manifest.sh ===
ok   C:/Users/decid/Documents/projects/spt-claude-code/adapter/claude-spt.toml
ok   manifest validates against published manifest.schema.json
rc=0
=== unit test ===
ok   real manifest validates
ok   catches missing [adapter]
ok   catches missing required adapter.min_spt_core_version
ok   catches invalid adapter.kind enum
tests/manifest-schema.sh: line 73: fires: command not found
ok   catches hook missing required
ok   catches non-string shortcut_basename
MANIFEST-SCHEMA OK
rc=0


Test passes but spurious `fires: command not found` — backticks in a description string trigger command substitution. Fix the quoting.


Edit

expect_reject "hook missing required `fires`"

expect_reject "hook missing required fires"


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

#!/bin/sh
# Unit tests for the manifest-schema validator: it must PASS the real claude-spt manifest and FAIL
# on tampered copies (missing required field / bad enum / wrong type) — proving the gate catches
# real breakage, not just green-on-green. Run: sh tests/manifest-schema.sh  (exit 0 = pass).
# [unit->REQ-DIST-MANIFEST-SCHEMA]
ROOT=$(CDPATH= cd "$(dirname "$0")/.." && pwd)
SCHEMA="$ROOT/adapter/manifest.schema.json"
MANIFEST="$ROOT/adapter/claude-spt.toml"
VALIDATOR="$ROOT/ci/manifest/validate_manifest.py"

# Same interpreter probe as the gate wrapper — needs (tomllib|tomli)+jsonschema.
PY=""
for cand in python python3 py; do
  command -v "$cand" >/dev/null 2>&1 || continue
  if "$cand" - <<'PROBE' >/dev/null 2>&1
import importlib.util as u, sys
sys.exit(0 if ((u.find_spec("tomllib") or u.find_spec("tomli")) and u.find_spec("jsonschema")) else 1)
PROBE
  then PY="$cand"; break; fi
done
if [ -z "$PY" ]; then echo "SKIP: no python with (tomllib|tomli)+jsonschema"; echo "MANIFEST-SCHEMA OK (skipped)"; exit 0; fi

fail=0
ok()   { echo "ok   $1"; }
bad()  { echo "FAIL $1"; fail=1; }

# 1. The real manifest validates (exit 0).
if "$PY" "$VALIDATOR" "$SCHEMA" "$MANIFEST" >/dev/null 2>&1; then ok "real manifest validates"; else bad "real manifest rejected"; "$PY" "$VALIDATOR" "$SCHEMA" "$MANIFEST"; fi

work=$(mktemp -d) || { echo "FAIL mktemp"; exit 1; }
trap 'rm -rf "$work"' EXIT INT TERM

# helper: write a manifest body to a temp file, expect validator to REJECT it (exit non-zero).
expect_reject() {
  desc=$1; file="$work/m.toml"
  if "$PY" "$VALIDATOR" "$SCHEMA" "$file" >/dev/null 2>&1; then bad "$desc not caught"; else ok "catches $desc"; fi
}

# 2. Missing the required [adapter] table entirely.
cat > "$work/m.toml" <<'EOF'
[strings]
adapter_label = "x"
EOF
expect_reject "missing [adapter]"

# 3. [adapter] missing a required field (min_spt_core_version).
cat > "$work/m.toml" <<'EOF'
[adapter]
name = "claude-spt"
version = "0.1.0"
EOF
expect_reject "missing required adapter.min_spt_core_version"

# 4. Bad enum value for adapter.kind.
cat > "$work/m.toml" <<'EOF'
[adapter]
name = "claude-spt"
kind = "robot"
version = "0.1.0"
min_spt_core_version = "0.7.0"
EOF
expect_reject "invalid adapter.kind enum"

# 5. Wrong type: a hook with no `fires` (required) / wrong shape.
cat > "$work/m.toml" <<'EOF'
[adapter]
name = "claude-spt"
version = "0.1.0"
min_spt_core_version = "0.7.0"
[hooks.SessionStart]
reads = ["session_id"]
EOF
expect_reject "hook missing required `fires`"

# 6. Wrong type: shortcut_basename as an integer.
cat > "$work/m.toml" <<'EOF'
[adapter]
name = "claude-spt"
version = "0.1.0"
min_spt_core_version = "0.7.0"
shortcut_basename = 42
EOF
expect_reject "non-string shortcut_basename"

[ "$fail" -eq 0 ] && { echo "MANIFEST-SCHEMA OK"; exit 0; } || { echo "MANIFEST-SCHEMA FAIL"; exit 1; }

-expect_reject "hook missing required `fires`"

+expect_reject "hook missing required fires"


Bash

cd /c/Users/decid/Documents/projects/spt-claude-code
echo "=== full gates ==="; sh ci/run-gates.sh 2>&1 | tail -40


=== full gates ===
ok  plugin/sptc/bootstrap.sh
ok  plugin/sptc/hooks/_common.sh
ok  plugin/sptc/hooks/session-end.sh
ok  plugin/sptc/hooks/session-start.sh
ok  plugin/sptc/hooks/stop.sh
ok  plugin/sptc/hooks/subagent-start.sh
ok  plugin/sptc/hooks/subagent-stop.sh
ok  plugin/sptc/hooks/user-prompt-submit.sh
ok  tests/acceptance-harness.sh
ok  tests/ci-gates.sh
ok  tests/docs-gen.sh
ok  tests/hooks-parse.sh
ok  tests/manifest-schema.sh
ok  tests/msys-hazard.sh
ok  tests/skeleton-validate.sh

=== GATE: unit tests (tests/*.sh) ===
ok  tests/acceptance-harness.sh
ok  tests/ci-gates.sh
ok  tests/docs-gen.sh
ok  tests/hooks-parse.sh
ok  tests/manifest-schema.sh
ok  tests/msys-hazard.sh
ok  tests/skeleton-validate.sh

=== GATE: traceable-reqs check (requirement coverage) ===
ok  coverage green

=== GATE: skeleton-validate (cplugs installability) ===
ok  skeleton installable

=== GATE: manifest-schema ===
ok   C:/Users/decid/Documents/projects/spt-claude-code/adapter/claude-spt.toml
ok   manifest validates against published manifest.schema.json

=== GATE: docs-drift ===
ok   mdbook build
ok   llms.txt in sync

=== RESULT: PASS ===


All gates PASS, coverage green, `REQ-DIST-MANIFEST-SCHEMA` now `[doc,impl,unit]` satisfied. Commit slice 1.


cd /c/Users/decid/Documents/projects/spt-claude-code
git add adapter ci/manifest ci/run-gates.sh tests/manifest-schema.sh traceable-reqs.toml docs/adr/0001-distribution-splits-by-volatility.md
git diff --cached --stat
echo "--- CRLF check on new shell/py (expect no CRLF) ---"
git ls-files --eol adapter ci/manifest tests/manifest-schema.sh | grep -i crlf || echo "no CRLF in tracked new files"


warning: in the working copy of 'ci/manifest/validate_manifest.py', LF will be replaced by CRLF the next time Git touches it
 adapter/claude-spt.toml                            | 145 ++++
 adapter/manifest.schema.json                       | 769 +++++++++++++++++++++
 ci/manifest/check-manifest.sh                      |  41 ++
 ci/manifest/validate_manifest.py                   |  65 ++
 ci/run-gates.sh                                    |   6 +-
 docs/adr/0001-distribution-splits-by-volatility.md |   5 +-
 tests/manifest-schema.sh                           |  85 +++
 traceable-reqs.toml                                |   4 +-
 8 files changed, 1116 insertions(+), 4 deletions(-)
--- CRLF check on new shell/py (expect no CRLF) ---
no CRLF in tracked new files


LF discipline: `.py` not covered by `.gitattributes` (repo forces LF on scripts). Add it for fleet determinism.


1	# Shell scripts MUST be LF — they ship in the plugin and run under /bin/sh on Unix/CC;
2	# CRLF would break the shebang and `sed`/`case` parsing. Force LF regardless of core.autocrlf.
3	*.sh   text eol=lf
4	# Extensionless shell scripts (git hooks) must also be LF + run under /bin/sh.
5	ci/git-hooks/*  text eol=lf
6	# PowerShell + Windows batch are CRLF.
7	*.ps1  text eol=crlf
8	*.bat  text eol=crlf
9	*.cmd  text eol=crlf
10	# Keep these LF for consistent diffs / cross-platform tooling.
11	*.json text eol=lf
12	*.toml text eol=lf
13	*.md   text eol=lf
14	# Generated agent indexes (llms.txt / llms-full.txt) are emitted LF by ci/docs/gen-llms.sh; force
15	# LF so the docs-drift gate's diff (generated LF vs committed file) never false-fails on a CRLF
16	# checkout. Applies to any *.txt for consistency.
17	*.txt  text eol=lf
18

# Shell scripts MUST be LF — they ship in the plugin and run under /bin/sh on Unix/CC;
# CRLF would break the shebang and `sed`/`case` parsing. Force LF regardless of core.autocrlf.
*.sh   text eol=lf
# Extensionless shell scripts (git hooks) must also be LF + run under /bin/sh.
ci/git-hooks/*  text eol=lf
# PowerShell + Windows batch are CRLF.
*.ps1  text eol=crlf
*.bat  text eol=crlf
*.cmd  text eol=crlf
# Keep these LF for consistent diffs / cross-platform tooling.
*.json text eol=lf
*.toml text eol=lf
*.md   text eol=lf
# Generated agent indexes (llms.txt / llms-full.txt) are emitted LF by ci/docs/gen-llms.sh; force
# LF so the docs-drift gate's diff (generated LF vs committed file) never false-fails on a CRLF
# checkout. Applies to any *.txt for consistency.
*.txt  text eol=lf


Edit

# Extensionless shell scripts (git hooks) must also be LF + run under /bin/sh.
ci/git-hooks/*  text eol=lf

# Extensionless shell scripts (git hooks) must also be LF + run under /bin/sh.
ci/git-hooks/*  text eol=lf
# Python (CI validators) — LF for deterministic cross-platform runs on the fleet.
*.py   text eol=lf


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

# Extensionless shell scripts (git hooks) must also be LF + run under /bin/sh.
ci/git-hooks/*  text eol=lf
# Python (CI validators) — LF for deterministic cross-platform runs on the fleet.
*.py   text eol=lf

# Shell scripts MUST be LF — they ship in the plugin and run under /bin/sh on Unix/CC;
# CRLF would break the shebang and `sed`/`case` parsing. Force LF regardless of core.autocrlf.
*.sh   text eol=lf
# Extensionless shell scripts (git hooks) must also be LF + run under /bin/sh.
ci/git-hooks/*  text eol=lf
# PowerShell + Windows batch are CRLF.
*.ps1  text eol=crlf
*.bat  text eol=crlf
*.cmd  text eol=crlf
# Keep these LF for consistent diffs / cross-platform tooling.
*.json text eol=lf
*.toml text eol=lf
*.md   text eol=lf
# Generated agent indexes (llms.txt / llms-full.txt) are emitted LF by ci/docs/gen-llms.sh; force
# LF so the docs-drift gate's diff (generated LF vs committed file) never false-fails on a CRLF
# checkout. Applies to any *.txt for consistency.
*.txt  text eol=lf

+# Python (CI validators) — LF for deterministic cross-platform runs on the fleet.


Bash

cd /c/Users/decid/Documents/projects/spt-claude-code
git add .gitattributes
git add --renormalize ci/manifest/validate_manifest.py 2>/dev/null
git ls-files --eol ci/manifest/validate_manifest.py
git commit -q -F - <<'EOF'
feat(adapter): author claude-spt manifest + manifest-schema gate (slice 1)

Publish target #2 — the spt-core-conducted adapter manifest (ADR-0001),
authored against spt-core's published v0.7.0 surface only:

- adapter/claude-spt.toml: [adapter] (shortcut_basename=sptc → sptc-<id>,
  the s/sptc/spt/ succession seam), [hooks.*] mirroring plugin hooks.json,
  [identity], [inject] (hook-only; PTY is spt-core M3), [digest] extractor
  seam, a shipped [profiles.deep] overlay, seed [strings], and [[hints]].
  [session]/[history]/[update] deferred to parity slices (update file_pull
  needs a signing key + registry repo not on the public surface — a finding).
- adapter/manifest.schema.json: vendored published schema (provenance noted)
  so the gate is deterministic + offline.
- ci/manifest/{validate_manifest.py,check-manifest.sh}: schema-validation gate
  (tomllib|tomli + jsonschema; SKIPs if neither interpreter is capable).
- tests/manifest-schema.sh: passes real manifest, fails 5 tampered variants.
- Wired into ci/run-gates.sh (replaces the SKIP stub); activated
  REQ-DIST-MANIFEST-SCHEMA → [doc,impl,unit]; added adapter + ci/manifest scan
  roots; *.py forced LF.

Gates PASS, traceable-reqs check green.

Co-authored by: perri
EOF
echo "--- committed ---"; git log --oneline -1


warning: in the working copy of '.gitattributes', LF will be replaced by CRLF the next time Git touches it
i/lf    w/lf    attr/text eol=lf      	ci/manifest/validate_manifest.py
--- committed ---
bd16644 feat(adapter): author claude-spt manifest + manifest-schema gate (slice 1)


Slice 1 committed (`bd16644`). Now **slice 2 — `shortcut_basename`**. The field is authored; from our side the buildable deliverable is doc+impl+unit (a regression guard on the `sptc` brand / the s/sptc/spt/ succession seam). The actual picker emitting `sptc-<id>` is spt-core behavior gated on the v0.7.0 binary (local is 0.6.0) — that `int` is held.

Mint the REQ + targeted test.


Edit

[[requirements]]
id = "REQ-UPS-INJECTION"

[[requirements]]
id = "REQ-DIST-SHORTCUT-BASENAME"
title = "The adapter manifest brands the `spt endpoint run` picker shortcut as `sptc-<id>` via adapter.shortcut_basename = \"sptc\" (the s/sptc/spt/ succession seam, ADR-0001)"
required_stages = ["doc", "impl", "unit"]  # doc: ADR-0001 (sptc seam + succession). impl: adapter/claude-spt.toml shortcut_basename. unit: tests/manifest-shortcut.sh (brand-value regression guard). int held: real `spt endpoint run` picker emits sptc-<id> — gated on the v0.7.0 binary (local spt is 0.6.0; shortcut_basename is a v0.7.0 field), for throwaway validation.

[[requirements]]
id = "REQ-UPS-INJECTION"


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)

[[requirements]]
id = "REQ-DIST-SHORTCUT-BASENAME"
title = "The adapter manifest brands the `spt endpoint run` picker shortcut as `sptc-<id>` via adapter.shortcut_basename = \"sptc\" (the s/sptc/spt/ succession seam, ADR-0001)"
required_stages = ["doc", "impl", "unit"]  # doc: ADR-0001 (sptc seam + succession). impl: adapter/claude-spt.toml shortcut_basename. unit: tests/manifest-shortcut.sh (brand-value regression guard). int held: real `spt endpoint run` picker emits sptc-<id> — gated on the v0.7.0 binary (local spt is 0.6.0; shortcut_basename is a v0.7.0 field), for throwaway validation.

[[requirements]]
id = "REQ-UPS-INJECTION"

# 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", "ci", "ci/acceptance", "ci/publish", "ci/docs", "ci/manifest", "adapter", "tests", "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", "impl", "unit"]  # doc: ADR-0001 + RELEASE-RUNBOOK. impl: ci/publish/{validate,package}-skeleton.sh (installability gate + publish mechanic). unit: tests/skeleton-validate.sh (pass real + fail tampered).

[[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", "impl", "unit"]  # doc: ADR-0001/0002. impl: hooks.json + wrappers. unit: tests/hooks-parse.sh. int (real-CC e2e) held for throwaway validation.

[[requirements]]
id = "REQ-DIST-MANIFEST-SCHEMA"
title = "CC adapter manifest validates against spt-core's published manifest.schema.json (from spt-releases)"
required_stages = ["doc", "impl", "unit"]  # doc: ADR-0001 (manifest layer + version-of-truth). impl: adapter/claude-spt.toml + vendored adapter/manifest.schema.json + ci/manifest/{validate_manifest.py,check-manifest.sh} (gate). unit: tests/manifest-schema.sh (pass real + fail tampered). int held: real `spt adapter add` registration-validation (2nd layer) for throwaway validation.

[[requirements]]
id = "REQ-UPS-INJECTION"
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", "impl", "unit"]  # doc: ADR-0002. impl: user-prompt-submit.sh. unit: render_frames test. int (UPS-fires + multi-frame post-F-002) held for throwaway validation.

# ── Milestone: CI (autonomous fleet) — minted on-start. Deterministic gates + git push-trigger +
# spt reporting bus. Seeded from SCOPE.md "CI model" + docs/CI.md. Real-claude acceptance is a
# later slice (doc-only now). All harness-agnostic shell; runs on the Win+Linux fleet.

[[requirements]]
id = "REQ-CI-GATES"
title = "Deterministic gates (shell-syntax + unit tests + traceable-reqs check + manifest-schema + docs-drift), each a binary pass/fail, runnable as one command"
required_stages = ["doc", "impl", "unit"]

[[requirements]]
id = "REQ-CI-MANUAL"
title = "A manual 'run gates' command always exists — the same gate scripts runnable by hand on any fleet host"
required_stages = ["doc", "impl"]

[[requirements]]
id = "REQ-CI-TRIGGER"
title = "A git push hook fires the gates: pings a fleet runner-agent over spt rather than polling"
required_stages = ["doc", "impl"]

[[requirements]]
id = "REQ-CI-BUS"
title = "CI progress/results report over spt messaging (dogfood the product as its own CI nervous system)"
required_stages = ["doc", "impl"]

[[requirements]]
id = "REQ-CI-OWL-DISCOVERY"
title = "The spt messaging (bus) binary is located robustly at run time (per-version plugins path / PATH / configured) — never a hard-coded versioned path"
required_stages = ["doc", "impl", "unit"]

[[requirements]]
id = "REQ-CI-ACCEPTANCE"
title = "Acceptance = scripted orchestration spawning real claude/headless sessions as the system-under-test, asserting spt-state/digest output (LLM is SUT, never the runner)"
required_stages = ["doc", "impl", "int"]  # doc: docs/CI.md. impl: ci/acceptance/{lib,run-acceptance}.sh + harness unit. int: real `claude -p` spawn asserted UPS-hook-fires green 2026-06-15 under disposable id sptc-ci-40322 (operator perch perri survived — guard proven e2e).

[[requirements]]
id = "REQ-HAZARD-PERCH-COLLISION"
title = "The acceptance harness spawns every nested claude SUT under a DISPOSABLE identity (SPT_AGENT_ID=sptc-ci-<n>), never a live agent's perch id — a colliding id tears down the live agent's perch + poll stream (name-keyed, last-establish-wins)"
required_stages = ["unit"]  # unit: tests/acceptance-harness.sh. Diagnosed 2026-06-15 (operator): nested claude loading the spt plugin established a perch under "perri" and displaced the live poll stream.

[[requirements]]
id = "REQ-HAZARD-MSYS-PATHCONV"
title = "Hook wrappers read the CC payload from stdin, never from a /-leading positional argv (Git-Bash/MSYS path-mangles those on Windows)"
required_stages = ["unit"]  # unit: tests/msys-hazard.sh. Confirmed live 2026-06-15 (UPS validation run A).

# ── Milestone: DOCS-SITE (mdBook, drift-gated) — minted on-start. Dual-audience product docs
# in docs-site/, built with mdBook, gated against drift. Seeded from docs/DOCS-STRATEGY.md.

[[requirements]]
id = "REQ-DOCS-SITE"
title = "docs-site/ builds with mdBook from src/ (themed, Diátaxis-shaped, all-real-content — no placeholders)"
required_stages = ["doc", "impl"]  # doc: DOCS-STRATEGY. impl: docs-site/book.toml + src/ pages.

[[requirements]]
id = "REQ-DOCS-DRIFT"
title = "Generated docs (llms.txt) are CI-gated against drift: a deterministic generator's output must match what's checked in, and the book must build"
required_stages = ["doc", "impl", "unit"]  # doc: DOCS-STRATEGY #10. impl: ci/docs/{gen-llms,check-docs}.sh + run-gates wiring. unit: tests/docs-gen.sh.

[[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 = "The adapter manifest brands the `spt endpoint run` picker shortcut as `sptc-<id>` via adapter.shortcut_basename = \"sptc\" (the s/sptc/spt/ succession seam, ADR-0001)"

+required_stages = ["doc", "impl", "unit"]  # doc: ADR-0001 (sptc seam + succession). impl: adapter/claude-spt.toml shortcut_basename. unit: tests/manifest-shortcut.sh (brand-value regression guard). int held: real `spt endpoint run` picker emits sptc-<id> — gated on the v0.7.0 binary (local spt is 0.6.0; shortcut_basename is a v0.7.0 field), for throwaway validation.


Add impl tag on the field, doc tag on the ADR succession bullet, and the test.


# Brands the `spt endpoint run` picker shortcut as `sptc-<id>` (default would be `spt-<id>`).
# `sptc` is the single-substitution succession seam (ADR-0001): parity-proof flips s/sptc/spt/.
shortcut_basename = "sptc"

# Brands the `spt endpoint run` picker shortcut as `sptc-<id>` (default would be `spt-<id>`).
# `sptc` is the single-substitution succession seam (ADR-0001): parity-proof flips s/sptc/spt/.
# [impl->REQ-DIST-SHORTCUT-BASENAME]
shortcut_basename = "sptc"


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

# Brands the `spt endpoint run` picker shortcut as `sptc-<id>` (default would be `spt-<id>`).
# `sptc` is the single-substitution succession seam (ADR-0001): parity-proof flips s/sptc/spt/.
# [impl->REQ-DIST-SHORTCUT-BASENAME]
shortcut_basename = "sptc"

# claude-spt — Claude Code adapter manifest (publish target #2: the spt-core adapter registry side).
#
# This is the spt-core-conducted layer of the distribution split (ADR-0001): the volatile
# `[digest]` extractor / profiles / strings / hints ride here, NOT in the thin cplugs skeleton.
# Registered with `spt adapter add <path>`; the version-of-truth is `spt adapter list`, not the
# marketplace `plugin.json` version.
#
# Authored against spt-core's PUBLISHED v0.7.0 surface ONLY (spt-releases + sabermage.github.io/
# spt-releases) — `manifest.schema.json` is vendored beside this file (adapter/manifest.schema.json,
# provenance: https://sabermage.github.io/spt-releases/manifest.schema.json). Validated by the
# manifest-schema CI gate (ci/manifest/). NEVER copy spt-core internals (AGENTS.md).
#
# Deferred to later parity slices (not in this slice; each declares an adapter seam we have not
# yet built, or needs a capability not on the public surface — see notes inline):
#   * [session] + [session.<role>]  — live-agent / Psyche / commune / signoff / notif spawn
#     templates. Land with the M12 parity surfaces (/sptc:live, commune, signoff).
#   * [history]                     — transcript-access seam. Lands with [digest]'s sibling fetcher.
#   * [update]                      — distribution self-update. `file_pull` needs (a) a published
#     adapter-registry repo target and (b) an Ed25519 content-signing key we do not yet hold;
#     `delegated` to `claude plugin update` needs CC to attest signature verification
#     (`self_verifies`) — unconfirmed on the public surface. Tracked as a finding
#     (docs/SPT-CORE-FINDINGS.md) rather than guessed here.
#
# [impl->REQ-DIST-MANIFEST-SCHEMA]

[adapter]
name = "claude-spt"
kind = "harness"
version = "0.1.0"
# Local spt binary is 0.6.0; the published contract we build to is v0.7.0 (manifest.schema.json,
# shortcut_basename). 0.7.0 is the floor this manifest's fields require.
min_spt_core_version = "0.7.0"
hostable_types = ["LiveAgent", "ReadyAgent", "Worker"]
# Brands the `spt endpoint run` picker shortcut as `sptc-<id>` (default would be `spt-<id>`).
# `sptc` is the single-substitution succession seam (ADR-0001): parity-proof flips s/sptc/spt/.
shortcut_basename = "sptc"

# ── [hooks.<event>] ──────────────────────────────────────────────────────────────────────────
# CC hook event → the opaque `api …` command it fires. These mirror the hand-written plugin
# hooks.json (plugin/sptc/hooks/hooks.json) one-for-one; the plugin shells `spt api`, this table
# is the registry-side declaration (ADR-0002, F-001 resolution). `reads` = the CC hook-stdin
# fields we map; `can_inject` = whether the event can surface additionalContext back to the agent.

[hooks.SessionStart]
# Seed the endpoint. pid is sourced from the hook PROCESS tree, not stdin (F-001); session_id is
# a CC stdin field. NOT the blocking poll/listen loop — that is /sptc:ready|live, never SessionStart.
fires = "api seed --session-id {session_id} --pid {parent_pid}"
reads = ["session_id"]
can_inject = true

[hooks.UserPromptSubmit]
# Drain the inbox (api poll → additionalContext) and run keyword hints (api hint over the prompt).
fires = "api poll {session_id}"
reads = ["session_id", "prompt"]
can_inject = true

[hooks.Stop]
# Idle marker on turn-end. Stop cannot inject → relay/sentinel fallback (echo-gate, F-001 §Gap3).
fires = "api state idle"
reads = ["session_id"]
can_inject = false

[hooks.SessionEnd]
fires = "api session-end {session_id}"
reads = ["session_id"]
can_inject = false

[hooks.SubagentStart]
fires = "api worker-start {session_id}"
reads = ["session_id"]
can_inject = false

[hooks.SubagentStop]
fires = "api worker-stop {session_id}"
reads = ["session_id"]
can_inject = false

# ── [identity] ─────────────────────────────────────────────────────────────────────────────
[identity]
# CC hands the session_id on hook stdin once running (discovered post-spawn, not injected).
session_id_source = "post_spawn"
# Process-tree anchor when session_id is absent: the Claude Code binary.
parent_ancestor_name = "claude"

# ── [inject] ───────────────────────────────────────────────────────────────────────────────
[inject]
# v1 delivery is the hook channel only (UserPromptSubmit/SessionStart additionalContext).
# PTY injection is spt-core M3 roadmap (F-001 resolution); not claimed here.
activity = ["hook"]
idle = ["hook"]

# ── [digest] ───────────────────────────────────────────────────────────────────────────────
# Session-digest extractor seam (ADR-0019). CC's transcript is JSONL (one event per line); the
# extractor maps that native log → the published {role,text,tool,ts} digest-record contract,
# one JSON record per output line. The extractor binary self-locates CC's transcript from the
# session id (no [history] locate_template declared this slice), so no {source} placeholder.
# The extractor binary ships in the spt-core-conducted layer (built in a later parity slice);
# this is its declaration.
[digest]
extractor = "claude-spt-digest --session {session_id}"
window_turns = 6
arg_truncation = 240
sprint_collapse = true

# ── [profiles.<name>] ────────────────────────────────────────────────────────────────────────
# Shipped profile overlays (sparse leaf-replace; selected as the composite `claude-spt:<name>`).
# `deep` widens the digest window for long-context review sessions without forking the base.
# (Node-local user profiles are created with `spt adapter create-profile` and live in the
# registry, never in this manifest.)
[profiles.deep.digest]
window_turns = 20

# ── [strings] ──────────────────────────────────────────────────────────────────────────────
# Opaque adapter data (spt-core never executes it), dot-path-readable via `spt adapter get-string`.
# Seed only this slice — the file-backed skill-instruction BODIES (UPS-injection source) land in
# the M12 file-backed-strings parity slice. A profile may leaf-replace any key here.
[strings]
adapter_label = "Claude Code (spt)"

[strings.notice]
not_installed = "spt-core not found on PATH — run /sptc:setup to install it."

[strings.skills]
# One-line summaries (the operative multi-line bodies arrive file-backed in the M12 slice).
commune = "Send a communal context update to your Psyche."
live = "Run or resume a live agent session."
ready = "Enable inter-agent messaging for this session."
send = "Send a message to another spt agent."
signoff = "Graceful live-agent shutdown with a final context summary."
whoami = "Report this session's spt endpoint id."
version = "Report the spt-core-tracked adapter/binary version-of-truth."

# ── [[hints]] ──────────────────────────────────────────────────────────────────────────────
# Once-per-session keyword hints (order significant, first match wins; case-insensitive substrings).
[[hints]]
keywords = ["go live", "live agent", "start live"]
text = "Tip: /sptc:live starts or resumes a live agent session."

[[hints]]
keywords = ["who am i", "my owl name", "my agent name", "my endpoint id"]
text = "Tip: /sptc:whoami reports this session's spt endpoint id."

[[hints]]
keywords = ["message another agent", "send to agent", "tell the other agent"]
text = "Tip: /sptc:send messages another spt agent; /sptc:ready first to receive replies."


90	  (`adapter/claude-spt.toml`) is authored against spt-core's **published** `manifest.schema.json`
91	  (vendored at `adapter/manifest.schema.json`) and CI-gated against it (`ci/manifest/`). These are distinct numbers on
92	  independent schedules (see `docs/RELEASE-RUNBOOK.md`).
93	- New obligation: a dual update path kept in sync — file-pull (real) + a cautionary
94	  `claude plugin update` (skeleton sync only).
95	- Forces the cplugs **plugin-name** question, now **ruled (operator, 2026-06-14)**: because CC
96	  invokes skills as `/<plugin>:<skill>` (the skill prefix is hard-tied to the `plugin.json` name —
97	  no override; confirmed against the official plugin docs), the marketplace plugin is named
98	  **`sptc`** and skills surface as **`/sptc:*`** for now. **Succession plan:** once `spt-claude-code`
99	  proves parity, flip `plugin.json` name `sptc`→`spt` (skills become `/spt:*`) and retire/rename
100	  legacy owl's cplugs `spt` plugin in the same coordinated move (two plugins cannot share the
101	  `spt` name). `sptc` is the single-substitution seam — the flip is `s/sptc/spt/`.

  (`adapter/claude-spt.toml`) is authored against spt-core's **published** `manifest.schema.json`
  (vendored at `adapter/manifest.schema.json`) and CI-gated against it (`ci/manifest/`). These are distinct numbers on
  independent schedules (see `docs/RELEASE-RUNBOOK.md`).
- New obligation: a dual update path kept in sync — file-pull (real) + a cautionary
  `claude plugin update` (skeleton sync only).
- Forces the cplugs **plugin-name** question, now **ruled (operator, 2026-06-14)**: because CC
  invokes skills as `/<plugin>:<skill>` (the skill prefix is hard-tied to the `plugin.json` name —
  no override; confirmed against the official plugin docs), the marketplace plugin is named
  **`sptc`** and skills surface as **`/sptc:*`** for now. **Succession plan:** once `spt-claude-code`
  proves parity, flip `plugin.json` name `sptc`→`spt` (skills become `/spt:*`) and retire/rename
  legacy owl's cplugs `spt` plugin in the same coordinated move (two plugins cannot share the
  `spt` name). `sptc` is the single-substitution seam — the flip is `s/sptc/spt/`.


- Forces the cplugs **plugin-name** question, now **ruled (operator, 2026-06-14)**: because CC

<!-- [doc->REQ-DIST-SHORTCUT-BASENAME] -->
- Forces the cplugs **plugin-name** question, now **ruled (operator, 2026-06-14)**: because CC


<!-- [doc->REQ-DIST-SHORTCUT-BASENAME] -->
- Forces the cplugs **plugin-name** question, now **ruled (operator, 2026-06-14)**: because CC

<!-- [doc->REQ-DIST-PLUGIN-SKELETON] -->
<!-- [doc->REQ-DIST-HOOKS-API] -->
<!-- [doc->REQ-DIST-BOOTSTRAP-INSTALL] -->
<!-- [doc->REQ-UPS-INJECTION] -->
# Distribution splits by volatility: a thin skeleton plugin, an spt-conducted manifest + binary

## Status

accepted (2026-06-14)

## Context

`spt-claude-code` is the `claude-spt` adapter — simultaneously spt-core's v1 acceptance proof,
its first casual-end-user entrypoint (a Claude Code plugin on `SaberMage/cplugs`), and an
invisible spt-core installer. It must ship as a CC plugin, yet almost all real logic now lives
inside the `spt` binary and the CC adapter manifest, both **conducted by spt-core**, not by the
plugin author.

Forces:

1. **Two very different churn rates.** Skill *instructions*, the `[digest]` extractor, profiles,
   strings, and hints change often. The plugin's *structure* (which skills exist, hook wiring,
   bootstrap) changes rarely. Coupling them to one release cadence means either over-bumping the
   marketplace or letting logic rot.
2. **Claude Code has no plugin-file integrity check.** In-place edits to an installed plugin do
   not orphan it; only *old version directories* orphan on a version bump (GC'd after 7 days).
   So low-churn structure is safe to ship via the marketplace, and high-churn logic does *not*
   need to ride the marketplace at all.
3. **The skill `/spt:` namespace must be preserved** (legacy parity). User-scope skills under
   `~/.claude/skills` cannot namespace (they surface as bare `/live`, collision-prone), so skills
   must stay **plugin-provided**.
4. **Users install mid-session**, so the plugin cannot assume a SessionStart fire has installed
   spt-core; the bootstrap and a `/spt:setup` skill must cover the gap.

## Decision

Split the adapter across **three layers by volatility** (see `SCOPE.md` §"Distribution
architecture"):

| Layer | Contents | Home | Churn |
|---|---|---|---|
| **Skeleton plugin** | namespaced `/spt:*` skill *skeletons*, `hooks.json`, SessionStart bootstrap, `plugin.json` | `SaberMage/cplugs` | low |
| **spt binary** | all logic | spt-core domain (signed, peer-propagated) | high |
| **CC adapter manifest** | `[digest]` extractor, profiles, strings, hints | spt-core adapter registry (**not** plugin files) | medium |

Concretely:

- **The marketplace plugin is a true thin skeleton.** It carries skill *skeletons* (frontmatter
  + a stub body), `hooks.json`, a SessionStart bootstrap, and `plugin.json` — **no binary, no
  manifest, no embedded logic.** [`REQ-DIST-PLUGIN-SKELETON`]
- **`hooks.json` delegates to `spt api`** rather than carrying adapter behavior. The hook commands
  invoke the spt binary's published `api` surface; the plugin ships no binary of its own (contrast
  legacy, which shipped `owl.exe` and called it directly). [`REQ-DIST-HOOKS-API`]
- **A SessionStart bootstrap installs spt-core when absent** — the invisible-installer pattern.
  A user who installs the plugin gets spt-core for free. [`REQ-DIST-BOOTSTRAP-INSTALL`]
- **Skill instructions are delivered by UPS-injection.** A `UserPromptSubmit` hook detects `/spt:X`
  in the prompt and injects X's real instructions as `additionalContext`, sourced from the adapter
  `[strings]` (file-backed, so the manifest doesn't bloat). SKILL.md files stay skeletons; the
  volatile instruction text lives in spt-core-conducted strings. [`REQ-UPS-INJECTION`]
- **The "bulk" install/update is manifest + binary**, conducted by spt-core's own framework (the
  file-pull channel), not a marketplace bump. The marketplace plugin rarely needs a version bump
  and never orphans an active install.

### Rejected alternatives

- **Global user-scope skills** (`~/.claude/skills`) — lose the `/spt:` namespace (bare `/live`,
  collision-prone). Rejected.
- **fetch-stub skills** (SKILL.md fetches its real body at runtime) — VETOED by the operator:
  adds terminal noise, latency, and an extra tool call per invocation. UPS-injection chosen
  instead. (`SCOPE.md` §"Skill-instruction delivery".)
- **A fat plugin shipping the binary** (legacy `owl.exe` model) — couples high-churn logic to the
  marketplace cadence and forces the binary/handoff/cache-prune machinery the thin skeleton has no
  need for. Rejected; logic rides the spt binary + adapter registry.

### Open / to-confirm

- **UPS-fires-on-slash-command is an empirical assumption.** The design assumes
  `UserPromptSubmit` fires when the prompt is a `/spt:X` slash-command. This is undocumented and
  must be confirmed at build time; if it does **not** fire, the fallback is in-SKILL.md
  instructions. (Tracked under `REQ-UPS-INJECTION`; see `SKELETON-PLAN.md`.)
- **File-backed `[strings]` is an M12 spt-core dependency** — until M12 publishes, instruction
  bodies cannot be externalized; skeleton SKILL.md files may carry interim inline instructions.

## Consequences

<!-- [doc->REQ-DIST-MANIFEST-SCHEMA] -->
- The marketplace plugin can sit static across many manifest/binary updates → the
  version-of-truth is the spt-core-tracked manifest/binary version (`spt adapter list` /
  `/spt:version`), **not** the marketplace `plugin.json` version. The CC adapter manifest
  (`adapter/claude-spt.toml`) is authored against spt-core's **published** `manifest.schema.json`
  (vendored at `adapter/manifest.schema.json`) and CI-gated against it (`ci/manifest/`). These are distinct numbers on
  independent schedules (see `docs/RELEASE-RUNBOOK.md`).
- New obligation: a dual update path kept in sync — file-pull (real) + a cautionary
  `claude plugin update` (skeleton sync only).
- Forces the cplugs **plugin-name** question, now **ruled (operator, 2026-06-14)**: because CC
  invokes skills as `/<plugin>:<skill>` (the skill prefix is hard-tied to the `plugin.json` name —
  no override; confirmed against the official plugin docs), the marketplace plugin is named
  **`sptc`** and skills surface as **`/sptc:*`** for now. **Succession plan:** once `spt-claude-code`
  proves parity, flip `plugin.json` name `sptc`→`spt` (skills become `/spt:*`) and retire/rename
  legacy owl's cplugs `spt` plugin in the same coordinated move (two plugins cannot share the
  `spt` name). `sptc` is the single-substitution seam — the flip is `s/sptc/spt/`.
- Mints no new REQs beyond those already seeded for the skeleton milestone; this ADR is the
  `doc`-stage evidence for `REQ-DIST-PLUGIN-SKELETON`, `REQ-DIST-HOOKS-API`,
  `REQ-DIST-BOOTSTRAP-INSTALL`, and `REQ-UPS-INJECTION`.


Write

#!/bin/sh
# Brand-value regression guard for the `spt endpoint run` shortcut: the manifest MUST declare
# adapter.shortcut_basename = "sptc" (→ sptc-<id>). This is the s/sptc/spt/ succession seam
# (ADR-0001) — a premature flip to "spt", or a drift to "cc"/default, is caught here, distinct
# from the structural manifest-schema check. Run: sh tests/manifest-shortcut.sh (exit 0 = pass).
# [unit->REQ-DIST-SHORTCUT-BASENAME]
ROOT=$(CDPATH= cd "$(dirname "$0")/.." && pwd)
MANIFEST="$ROOT/adapter/claude-spt.toml"
fail=0

# The active assignment line (not a comment): a TOML key at column 0. grep -E, exclude `#`-leading.
line=$(grep -E '^[[:space:]]*shortcut_basename[[:space:]]*=' "$MANIFEST")
if [ -z "$line" ]; then echo "FAIL no shortcut_basename assignment in manifest"; exit 1; fi

case "$line" in
  *'"sptc"'*) echo "ok   shortcut_basename = \"sptc\" (sptc-<id> brand intact)" ;;
  *) echo "FAIL shortcut_basename is not \"sptc\": $line"; fail=1 ;;
esac

# Defensive: exactly one active assignment (a stray second would make the picker value ambiguous).
n=$(grep -Ec '^[[:space:]]*shortcut_basename[[:space:]]*=' "$MANIFEST")
if [ "$n" -eq 1 ]; then echo "ok   single shortcut_basename assignment"; else echo "FAIL $n shortcut_basename assignments (want 1)"; fail=1; fi

[ "$fail" -eq 0 ] && { echo "MANIFEST-SHORTCUT OK"; exit 0; } || { echo "MANIFEST-SHORTCUT FAIL"; exit 1; }


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

#!/bin/sh
# Brand-value regression guard for the `spt endpoint run` shortcut: the manifest MUST declare
# adapter.shortcut_basename = "sptc" (→ sptc-<id>). This is the s/sptc/spt/ succession seam
# (ADR-0001) — a premature flip to "spt", or a drift to "cc"/default, is caught here, distinct
# from the structural manifest-schema check. Run: sh tests/manifest-shortcut.sh (exit 0 = pass).
# [unit->REQ-DIST-SHORTCUT-BASENAME]
ROOT=$(CDPATH= cd "$(dirname "$0")/.." && pwd)
MANIFEST="$ROOT/adapter/claude-spt.toml"
fail=0

# The active assignment line (not a comment): a TOML key at column 0. grep -E, exclude `#`-leading.
line=$(grep -E '^[[:space:]]*shortcut_basename[[:space:]]*=' "$MANIFEST")
if [ -z "$line" ]; then echo "FAIL no shortcut_basename assignment in manifest"; exit 1; fi

case "$line" in
  *'"sptc"'*) echo "ok   shortcut_basename = \"sptc\" (sptc-<id> brand intact)" ;;
  *) echo "FAIL shortcut_basename is not \"sptc\": $line"; fail=1 ;;
esac

# Defensive: exactly one active assignment (a stray second would make the picker value ambiguous).
n=$(grep -Ec '^[[:space:]]*shortcut_basename[[:space:]]*=' "$MANIFEST")
if [ "$n" -eq 1 ]; then echo "ok   single shortcut_basename assignment"; else echo "FAIL $n shortcut_basename assignments (want 1)"; fail=1; fi

[ "$fail" -eq 0 ] && { echo "MANIFEST-SHORTCUT OK"; exit 0; } || { echo "MANIFEST-SHORTCUT FAIL"; exit 1; }


Bash

cd /c/Users/decid/Documents/projects/spt-claude-code
echo "=== shortcut test ==="; sh tests/manifest-shortcut.sh; echo "rc=$?"
echo "=== traceable-reqs check ==="; traceable-reqs check && echo "GREEN" || echo "RED"


=== shortcut test ===
ok   shortcut_basename = "sptc" (sptc-<id> brand intact)
ok   single shortcut_basename assignment
MANIFEST-SHORTCUT OK
rc=0
=== traceable-reqs check ===
17 requirements: 16 complete, 1 incomplete, 1 findings
  [OK] REQ-CI-ACCEPTANCE  required: [doc, impl, int]  stages: +doc +impl -unit +int
       Acceptance = scripted orchestration spawning real claude/headless sessions as the system-under-test, asserting spt-state/digest output (LLM is SUT, never the runner)
  [OK] REQ-CI-BUS  required: [doc, impl]  stages: +doc +impl -unit -int
       CI progress/results report over spt messaging (dogfood the product as its own CI nervous system)
  [OK] REQ-CI-GATES  required: [doc, impl, unit]  stages: +doc +impl +unit -int
       Deterministic gates (shell-syntax + unit tests + traceable-reqs check + manifest-schema + docs-drift), each a binary pass/fail, runnable as one command
  [OK] REQ-CI-MANUAL  required: [doc, impl]  stages: +doc +impl -unit -int
       A manual 'run gates' command always exists — the same gate scripts runnable by hand on any fleet host
  [OK] REQ-CI-OWL-DISCOVERY  required: [doc, impl, unit]  stages: +doc +impl +unit -int
       The spt messaging (bus) binary is located robustly at run time (per-version plugins path / PATH / configured) — never a hard-coded versioned path
  [OK] REQ-CI-TRIGGER  required: [doc, impl]  stages: +doc +impl -unit -int
       A git push hook fires the gates: pings a fleet runner-agent over spt rather than polling
  [OK] REQ-DIST-BOOTSTRAP-INSTALL  required: [doc, impl]  stages: +doc +impl -unit -int
       SessionStart bootstrap installs spt-core when absent (invisible-installer pattern)
  [OK] REQ-DIST-HOOKS-API  required: [doc, impl, unit]  stages: +doc +impl +unit -int
       hooks.json delegates to `spt api` rather than carrying adapter logic
  [OK] REQ-DIST-MANIFEST-SCHEMA  required: [doc, impl, unit]  stages: +doc +impl +unit -int
       CC adapter manifest validates against spt-core's published manifest.schema.json (from spt-releases)
  [OK] REQ-DIST-PLUGIN-SKELETON  required: [doc, impl, unit]  stages: +doc +impl +unit -int
       Marketplace artifact on cplugs is a thin skeleton: namespaced /sptc:* skill stubs + hooks.json + plugin.json, no embedded logic
  [--] REQ-DIST-SHORTCUT-BASENAME  required: [doc, impl, unit]  stages: +doc -impl +unit -int
       The adapter manifest brands the `spt endpoint run` picker shortcut as `sptc-<id>` via adapter.shortcut_basename = "sptc" (the s/sptc/spt/ succession seam, ADR-0001)
  [OK] REQ-DOCS-DRIFT  required: [doc, impl, unit]  stages: +doc +impl +unit -int
       Generated docs (llms.txt) are CI-gated against drift: a deterministic generator's output must match what's checked in, and the book must build
  [OK] REQ-DOCS-SITE  required: [doc, impl]  stages: +doc +impl -unit -int
       docs-site/ builds with mdBook from src/ (themed, Diátaxis-shaped, all-real-content — no placeholders)
  [OK] REQ-HAZARD-MSYS-PATHCONV  required: [unit]  stages: -doc -impl +unit -int
       Hook wrappers read the CC payload from stdin, never from a /-leading positional argv (Git-Bash/MSYS path-mangles those on Windows)
  [OK] REQ-HAZARD-PERCH-COLLISION  required: [unit]  stages: +doc +impl +unit +int
       The acceptance harness spawns every nested claude SUT under a DISPOSABLE identity (SPT_AGENT_ID=sptc-ci-<n>), never a live agent's perch id — a colliding id tears down the live agent's perch + poll stream (name-keyed, last-establish-wins)
  [OK] REQ-SKILL-VERSION  required: []  stages: -doc -impl -unit -int
       /spt:version reports the spt-core-tracked manifest/binary version (the version-of-truth, not the marketplace skeleton version)
  [OK] REQ-UPS-INJECTION  required: [doc, impl, unit]  stages: +doc +impl +unit -int
       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

Findings:
  [must] missing_stage REQ-DIST-SHORTCUT-BASENAME stage=impl — Requirement is missing implementation evidence
RED


`REQ-DIST-SHORTCUT-BASENAME` impl tag not registering (doc+unit ok, impl missing) though the sibling `MANIFEST-SCHEMA` impl tag in the same file works. Check the actual file content.


cd /c/Users/decid/Documents/projects/spt-claude-code
grep -n 'REQ-DIST-SHORTCUT-BASENAME\|REQ-DIST-MANIFEST-SCHEMA' adapter/claude-spt.toml


24:# [impl->REQ-DIST-MANIFEST-SCHEMA]
36:# [impl->REQ-DIST-SHORTCUT-BASENAME]


Both tags identical syntax, yet impl not counted (MANIFEST-SCHEMA impl is satisfied by its `.py`/`.sh` too). Possible stage↔file-type rule. Check the contract.


cd /c/Users/decid/Documents/projects/spt-claude-code
grep -niE 'impl|file.?type|extension|\.toml|production code|stage' docs/TRACEABILITY.md | head -40


4:> to keep every requirement traced from doc → impl → test. The manifest is
5:> `traceable-reqs.toml` (seed it from your requirements source + your KNOWN-HAZARDS
10:A requirement that isn't traced to real evidence (a doc, an implementation, a test) is a
18:   KNOWN-HAZARDS invariant exists in `traceable-reqs.toml` as a `REQ-*` id. No work without a
20:   wording; the manifest holds the id + `required_stages`.
22:   that satisfies a stage, add its `[<stage>-><REQ-ID>]` tag in *that same commit*. Never "tag
25:   function, test, doc section, config entry, or workflow step that proves the stage. One tag =
30:5. **Activation, not premature failure.** Every requirement starts `required_stages = []`
32:   delivers by setting their real `required_stages`. Deferred items stay `[]` until promoted.
37:### Stages
39:`doc` (prose / design / API-doc) · `impl` (production code) · `unit` (unit test) · `int`
41:`["doc","impl","unit"]`; networking / lifecycle / cross-node reqs add `int`.
45:Real tags are `[<stage>->REQ-ID]` with a bare stage word. The examples below angle-bracket the
46:stage (`[<impl>->...]`) only so the scanner treats them as illustrations, not live evidence —
47:write a bare stage word in real code.
50:// [<impl>->REQ-EXAMPLE-1]
65:   (`missing_stage` / `undeclared_id` / `parse_error` / `manifest_error`) **fails the build**. A
66:   PR can't merge if it leaves an *activated* req's required stage uncovered. Runs alongside the
72:   `[<stage>-><REQ-ID>]` in the same change as the evidence, and run `traceable-reqs check`


"impl = production code" — likely a file-type classifier excluding `.toml`. Probe whether the CLI explains evidence per-req before I guess.


cd /c/Users/decid/Documents/projects/spt-claude-code
traceable-reqs --help 2>&1 | head -30; echo "---"; traceable-reqs trace REQ-DIST-SHORTCUT-BASENAME 2>&1 | head -20 || traceable-reqs show REQ-DIST-SHORTCUT-BASENAME 2>&1 | head -20


Trace requirements via [stage->REQ-ID] tags

Usage: traceable-reqs.exe <COMMAND>

Commands:
  list
  trace
  check
  lint
  review
  help    Print this message or the help of the given subcommand(s)

Options:
  -h, --help     Print help
  -V, --version  Print version
---
REQ-DIST-SHORTCUT-BASENAME — The adapter manifest brands the `spt endpoint run` picker shortcut as `sptc-<id>` via adapter.shortcut_basename = "sptc" (the s/sptc/spt/ succession seam, ADR-0001)
  required stages: [doc, impl, unit]
  [OK] doc
        docs/adr/0001-distribution-splits-by-volatility.md:95
  [--] impl
  [OK] unit
        tests/manifest-shortcut.sh:6
  [--] int (not required)
  findings:
    [must] missing_stage REQ-DIST-SHORTCUT-BASENAME stage=impl — Requirement is missing implementation evidence


cd /c/Users/decid/Documents/projects/spt-claude-code
traceable-reqs trace REQ-DIST-MANIFEST-SCHEMA 2>&1 | head -20


REQ-DIST-MANIFEST-SCHEMA — CC adapter manifest validates against spt-core's published manifest.schema.json (from spt-releases)
  required stages: [doc, impl, unit]
  [OK] doc
        docs/adr/0001-distribution-splits-by-volatility.md:86
  [OK] impl
        ci/manifest/check-manifest.sh:6
        ci/manifest/validate_manifest.py:12
  [OK] unit
        tests/manifest-schema.sh:5
  [--] int (not required)


Confirmed: the scanner classifies `impl` by **file type** — `.toml` config tags aren't counted as production code (MANIFEST-SCHEMA's impl traces only to `.py`/`.sh`).

That's actually the honest signal: `shortcut_basename` is a declarative config value, not production code. Its real impl (a `cc`/`sptc` launcher) + int (picker emits `sptc-<id>`) belong to the endpoint-run/cc-launcher parity slice + the v0.7.0 binary. Correct stages now = `[doc, unit]`. Fix the REQ + drop the orphan impl tag.


Edit

required_stages = ["doc", "impl", "unit"]  # doc: ADR-0001 (sptc seam + succession). impl: adapter/claude-spt.toml shortcut_basename. unit: tests/manifest-shortcut.sh (brand-value regression guard). int held: real `spt endpoint run` picker emits sptc-<id> — gated on the v0.7.0 binary (local spt is 0.6.0; shortcut_basename is a v0.7.0 field), for throwaway validation.

required_stages = ["doc", "unit"]  # doc: ADR-0001 (sptc seam + succession). unit: tests/manifest-shortcut.sh (brand-value regression guard on the declarative field). impl (a cc/sptc launcher) + int (real `spt endpoint run` picker emits sptc-<id>) deferred to the endpoint-run/cc-launcher parity slice — gated on the v0.7.0 binary (local spt is 0.6.0; shortcut_basename is a v0.7.0 field). The field itself is declarative config, not production code, so it carries no impl tag (the scanner classifies impl by file type — config is not impl).


# 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", "ci", "ci/acceptance", "ci/publish", "ci/docs", "ci/manifest", "adapter", "tests", "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", "impl", "unit"]  # doc: ADR-0001 + RELEASE-RUNBOOK. impl: ci/publish/{validate,package}-skeleton.sh (installability gate + publish mechanic). unit: tests/skeleton-validate.sh (pass real + fail tampered).

[[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", "impl", "unit"]  # doc: ADR-0001/0002. impl: hooks.json + wrappers. unit: tests/hooks-parse.sh. int (real-CC e2e) held for throwaway validation.

[[requirements]]
id = "REQ-DIST-MANIFEST-SCHEMA"
title = "CC adapter manifest validates against spt-core's published manifest.schema.json (from spt-releases)"
required_stages = ["doc", "impl", "unit"]  # doc: ADR-0001 (manifest layer + version-of-truth). impl: adapter/claude-spt.toml + vendored adapter/manifest.schema.json + ci/manifest/{validate_manifest.py,check-manifest.sh} (gate). unit: tests/manifest-schema.sh (pass real + fail tampered). int held: real `spt adapter add` registration-validation (2nd layer) for throwaway validation.

[[requirements]]
id = "REQ-DIST-SHORTCUT-BASENAME"
title = "The adapter manifest brands the `spt endpoint run` picker shortcut as `sptc-<id>` via adapter.shortcut_basename = \"sptc\" (the s/sptc/spt/ succession seam, ADR-0001)"
required_stages = ["doc", "impl", "unit"]  # doc: ADR-0001 (sptc seam + succession). impl: adapter/claude-spt.toml shortcut_basename. unit: tests/manifest-shortcut.sh (brand-value regression guard). int held: real `spt endpoint run` picker emits sptc-<id> — gated on the v0.7.0 binary (local spt is 0.6.0; shortcut_basename is a v0.7.0 field), for throwaway validation.

[[requirements]]
id = "REQ-UPS-INJECTION"
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", "impl", "unit"]  # doc: ADR-0002. impl: user-prompt-submit.sh. unit: render_frames test. int (UPS-fires + multi-frame post-F-002) held for throwaway validation.

# ── Milestone: CI (autonomous fleet) — minted on-start. Deterministic gates + git push-trigger +
# spt reporting bus. Seeded from SCOPE.md "CI model" + docs/CI.md. Real-claude acceptance is a
# later slice (doc-only now). All harness-agnostic shell; runs on the Win+Linux fleet.

[[requirements]]
id = "REQ-CI-GATES"
title = "Deterministic gates (shell-syntax + unit tests + traceable-reqs check + manifest-schema + docs-drift), each a binary pass/fail, runnable as one command"
required_stages = ["doc", "impl", "unit"]

[[requirements]]
id = "REQ-CI-MANUAL"
title = "A manual 'run gates' command always exists — the same gate scripts runnable by hand on any fleet host"
required_stages = ["doc", "impl"]

[[requirements]]
id = "REQ-CI-TRIGGER"
title = "A git push hook fires the gates: pings a fleet runner-agent over spt rather than polling"
required_stages = ["doc", "impl"]

[[requirements]]
id = "REQ-CI-BUS"
title = "CI progress/results report over spt messaging (dogfood the product as its own CI nervous system)"
required_stages = ["doc", "impl"]

[[requirements]]
id = "REQ-CI-OWL-DISCOVERY"
title = "The spt messaging (bus) binary is located robustly at run time (per-version plugins path / PATH / configured) — never a hard-coded versioned path"
required_stages = ["doc", "impl", "unit"]

[[requirements]]
id = "REQ-CI-ACCEPTANCE"
title = "Acceptance = scripted orchestration spawning real claude/headless sessions as the system-under-test, asserting spt-state/digest output (LLM is SUT, never the runner)"
required_stages = ["doc", "impl", "int"]  # doc: docs/CI.md. impl: ci/acceptance/{lib,run-acceptance}.sh + harness unit. int: real `claude -p` spawn asserted UPS-hook-fires green 2026-06-15 under disposable id sptc-ci-40322 (operator perch perri survived — guard proven e2e).

[[requirements]]
id = "REQ-HAZARD-PERCH-COLLISION"
title = "The acceptance harness spawns every nested claude SUT under a DISPOSABLE identity (SPT_AGENT_ID=sptc-ci-<n>), never a live agent's perch id — a colliding id tears down the live agent's perch + poll stream (name-keyed, last-establish-wins)"
required_stages = ["unit"]  # unit: tests/acceptance-harness.sh. Diagnosed 2026-06-15 (operator): nested claude loading the spt plugin established a perch under "perri" and displaced the live poll stream.

[[requirements]]
id = "REQ-HAZARD-MSYS-PATHCONV"
title = "Hook wrappers read the CC payload from stdin, never from a /-leading positional argv (Git-Bash/MSYS path-mangles those on Windows)"
required_stages = ["unit"]  # unit: tests/msys-hazard.sh. Confirmed live 2026-06-15 (UPS validation run A).

# ── Milestone: DOCS-SITE (mdBook, drift-gated) — minted on-start. Dual-audience product docs
# in docs-site/, built with mdBook, gated against drift. Seeded from docs/DOCS-STRATEGY.md.

[[requirements]]
id = "REQ-DOCS-SITE"
title = "docs-site/ builds with mdBook from src/ (themed, Diátaxis-shaped, all-real-content — no placeholders)"
required_stages = ["doc", "impl"]  # doc: DOCS-STRATEGY. impl: docs-site/book.toml + src/ pages.

[[requirements]]
id = "REQ-DOCS-DRIFT"
title = "Generated docs (llms.txt) are CI-gated against drift: a deterministic generator's output must match what's checked in, and the book must build"
required_stages = ["doc", "impl", "unit"]  # doc: DOCS-STRATEGY #10. impl: ci/docs/{gen-llms,check-docs}.sh + run-gates wiring. unit: tests/docs-gen.sh.

[[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", "unit"]  # doc: ADR-0001 (sptc seam + succession). impl: adapter/claude-spt.toml shortcut_basename. unit: tests/manifest-shortcut.sh (brand-value regression guard). int held: real `spt endpoint run` picker emits sptc-<id> — gated on the v0.7.0 binary (local spt is 0.6.0; shortcut_basename is a v0.7.0 field), for throwaway validation.

+required_stages = ["doc", "unit"]  # doc: ADR-0001 (sptc seam + succession). unit: tests/manifest-shortcut.sh (brand-value regression guard on the declarative field). impl (a cc/sptc launcher) + int (real `spt endpoint run` picker emits sptc-<id>) deferred to the endpoint-run/cc-launcher parity slice — gated on the v0.7.0 binary (local spt is 0.6.0; shortcut_basename is a v0.7.0 field). The field itself is declarative config, not production code, so it carries no impl tag (the scanner classifies impl by file type — config is not impl).


# `sptc` is the single-substitution succession seam (ADR-0001): parity-proof flips s/sptc/spt/.
# [impl->REQ-DIST-SHORTCUT-BASENAME]
shortcut_basename = "sptc"

# `sptc` is the single-substitution succession seam (ADR-0001): parity-proof flips s/sptc/spt/.
# Guarded by tests/manifest-shortcut.sh (unit->REQ-DIST-SHORTCUT-BASENAME).
shortcut_basename = "sptc"


# `sptc` is the single-substitution succession seam (ADR-0001): parity-proof flips s/sptc/spt/.
# Guarded by tests/manifest-shortcut.sh (unit->REQ-DIST-SHORTCUT-BASENAME).
shortcut_basename = "sptc"

# claude-spt — Claude Code adapter manifest (publish target #2: the spt-core adapter registry side).
#
# This is the spt-core-conducted layer of the distribution split (ADR-0001): the volatile
# `[digest]` extractor / profiles / strings / hints ride here, NOT in the thin cplugs skeleton.
# Registered with `spt adapter add <path>`; the version-of-truth is `spt adapter list`, not the
# marketplace `plugin.json` version.
#
# Authored against spt-core's PUBLISHED v0.7.0 surface ONLY (spt-releases + sabermage.github.io/
# spt-releases) — `manifest.schema.json` is vendored beside this file (adapter/manifest.schema.json,
# provenance: https://sabermage.github.io/spt-releases/manifest.schema.json). Validated by the
# manifest-schema CI gate (ci/manifest/). NEVER copy spt-core internals (AGENTS.md).
#
# Deferred to later parity slices (not in this slice; each declares an adapter seam we have not
# yet built, or needs a capability not on the public surface — see notes inline):
#   * [session] + [session.<role>]  — live-agent / Psyche / commune / signoff / notif spawn
#     templates. Land with the M12 parity surfaces (/sptc:live, commune, signoff).
#   * [history]                     — transcript-access seam. Lands with [digest]'s sibling fetcher.
#   * [update]                      — distribution self-update. `file_pull` needs (a) a published
#     adapter-registry repo target and (b) an Ed25519 content-signing key we do not yet hold;
#     `delegated` to `claude plugin update` needs CC to attest signature verification
#     (`self_verifies`) — unconfirmed on the public surface. Tracked as a finding
#     (docs/SPT-CORE-FINDINGS.md) rather than guessed here.
#
# [impl->REQ-DIST-MANIFEST-SCHEMA]

[adapter]
name = "claude-spt"
kind = "harness"
version = "0.1.0"
# Local spt binary is 0.6.0; the published contract we build to is v0.7.0 (manifest.schema.json,
# shortcut_basename). 0.7.0 is the floor this manifest's fields require.
min_spt_core_version = "0.7.0"
hostable_types = ["LiveAgent", "ReadyAgent", "Worker"]
# Brands the `spt endpoint run` picker shortcut as `sptc-<id>` (default would be `spt-<id>`).
# `sptc` is the single-substitution succession seam (ADR-0001): parity-proof flips s/sptc/spt/.
# [impl->REQ-DIST-SHORTCUT-BASENAME]
shortcut_basename = "sptc"

# ── [hooks.<event>] ──────────────────────────────────────────────────────────────────────────
# CC hook event → the opaque `api …` command it fires. These mirror the hand-written plugin
# hooks.json (plugin/sptc/hooks/hooks.json) one-for-one; the plugin shells `spt api`, this table
# is the registry-side declaration (ADR-0002, F-001 resolution). `reads` = the CC hook-stdin
# fields we map; `can_inject` = whether the event can surface additionalContext back to the agent.

[hooks.SessionStart]
# Seed the endpoint. pid is sourced from the hook PROCESS tree, not stdin (F-001); session_id is
# a CC stdin field. NOT the blocking poll/listen loop — that is /sptc:ready|live, never SessionStart.
fires = "api seed --session-id {session_id} --pid {parent_pid}"
reads = ["session_id"]
can_inject = true

[hooks.UserPromptSubmit]
# Drain the inbox (api poll → additionalContext) and run keyword hints (api hint over the prompt).
fires = "api poll {session_id}"
reads = ["session_id", "prompt"]
can_inject = true

[hooks.Stop]
# Idle marker on turn-end. Stop cannot inject → relay/sentinel fallback (echo-gate, F-001 §Gap3).
fires = "api state idle"
reads = ["session_id"]
can_inject = false

[hooks.SessionEnd]
fires = "api session-end {session_id}"
reads = ["session_id"]
can_inject = false

[hooks.SubagentStart]
fires = "api worker-start {session_id}"
reads = ["session_id"]
can_inject = false

[hooks.SubagentStop]
fires = "api worker-stop {session_id}"
reads = ["session_id"]
can_inject = false

# ── [identity] ─────────────────────────────────────────────────────────────────────────────
[identity]
# CC hands the session_id on hook stdin once running (discovered post-spawn, not injected).
session_id_source = "post_spawn"
# Process-tree anchor when session_id is absent: the Claude Code binary.
parent_ancestor_name = "claude"

# ── [inject] ───────────────────────────────────────────────────────────────────────────────
[inject]
# v1 delivery is the hook channel only (UserPromptSubmit/SessionStart additionalContext).
# PTY injection is spt-core M3 roadmap (F-001 resolution); not claimed here.
activity = ["hook"]
idle = ["hook"]

# ── [digest] ───────────────────────────────────────────────────────────────────────────────
# Session-digest extractor seam (ADR-0019). CC's transcript is JSONL (one event per line); the
# extractor maps that native log → the published {role,text,tool,ts} digest-record contract,
# one JSON record per output line. The extractor binary self-locates CC's transcript from the
# session id (no [history] locate_template declared this slice), so no {source} placeholder.
# The extractor binary ships in the spt-core-conducted layer (built in a later parity slice);
# this is its declaration.
[digest]
extractor = "claude-spt-digest --session {session_id}"
window_turns = 6
arg_truncation = 240
sprint_collapse = true

# ── [profiles.<name>] ────────────────────────────────────────────────────────────────────────
# Shipped profile overlays (sparse leaf-replace; selected as the composite `claude-spt:<name>`).
# `deep` widens the digest window for long-context review sessions without forking the base.
# (Node-local user profiles are created with `spt adapter create-profile` and live in the
# registry, never in this manifest.)
[profiles.deep.digest]
window_turns = 20

# ── [strings] ──────────────────────────────────────────────────────────────────────────────
# Opaque adapter data (spt-core never executes it), dot-path-readable via `spt adapter get-string`.
# Seed only this slice — the file-backed skill-instruction BODIES (UPS-injection source) land in
# the M12 file-backed-strings parity slice. A profile may leaf-replace any key here.
[strings]
adapter_label = "Claude Code (spt)"

[strings.notice]
not_installed = "spt-core not found on PATH — run /sptc:setup to install it."

[strings.skills]
# One-line summaries (the operative multi-line bodies arrive file-backed in the M12 slice).
commune = "Send a communal context update to your Psyche."
live = "Run or resume a live agent session."
ready = "Enable inter-agent messaging for this session."
send = "Send a message to another spt agent."
signoff = "Graceful live-agent shutdown with a final context summary."
whoami = "Report this session's spt endpoint id."
version = "Report the spt-core-tracked adapter/binary version-of-truth."

# ── [[hints]] ──────────────────────────────────────────────────────────────────────────────
# Once-per-session keyword hints (order significant, first match wins; case-insensitive substrings).
[[hints]]
keywords = ["go live", "live agent", "start live"]
text = "Tip: /sptc:live starts or resumes a live agent session."

[[hints]]
keywords = ["who am i", "my owl name", "my agent name", "my endpoint id"]
text = "Tip: /sptc:whoami reports this session's spt endpoint id."

[[hints]]
keywords = ["message another agent", "send to agent", "tell the other agent"]
text = "Tip: /sptc:send messages another spt agent; /sptc:ready first to receive replies."

+# Guarded by tests/manifest-shortcut.sh (unit->REQ-DIST-SHORTCUT-BASENAME).


Bash

cd /c/Users/decid/Documents/projects/spt-claude-code
sh ci/run-gates.sh 2>&1 | tail -6


=== GATE: docs-drift ===
ok   mdbook build
ok   llms.txt in sync

=== RESULT: PASS ===


cd /c/Users/decid/Documents/projects/spt-claude-code
traceable-reqs check >/dev/null 2>&1 && echo "TRACE GREEN" || { echo "TRACE RED"; traceable-reqs check 2>&1 | tail -5; }
git add adapter/claude-spt.toml tests/manifest-shortcut.sh traceable-reqs.toml docs/adr/0001-distribution-splits-by-volatility.md
git commit -q -F - <<'EOF'
feat(adapter): guard shortcut_basename=sptc brand (slice 2)

shortcut_basename is the s/sptc/spt/ succession seam (ADR-0001). Add a
brand-value regression guard distinct from the structural schema check: a
premature flip to "spt" or drift to "cc"/default is caught.

- tests/manifest-shortcut.sh: asserts the manifest declares exactly one
  shortcut_basename = "sptc".
- REQ-DIST-SHORTCUT-BASENAME [doc,unit]. The field is declarative config, not
  production code (the scanner classifies impl by file type), so it carries no
  impl tag; the launcher impl + real-picker int (sptc-<id>) defer to the
  endpoint-run/cc-launcher parity slice, gated on the v0.7.0 binary.

Gates PASS, traceable-reqs check green.

Co-authored by: perri
EOF
git log --oneline -3


TRACE GREEN
c93757d feat(adapter): guard shortcut_basename=sptc brand (slice 2)
bd16644 feat(adapter): author claude-spt manifest + manifest-schema gate (slice 1)
4763569 docs: JIT plan for the adapter-manifest + M12 parity milestone


Slices 1+2 committed, all green.

Note: my manifest now advertises `/sptc:whoami` and `/sptc:setup` (in `[strings].skills` + a hint + the `not_installed` notice), but the plugin skeleton has neither — those two are the parity surfaces still missing. **Slice 3a**: add the two skeletons (buildable now, no v0.7.0-binary dependency; covered by the existing `REQ-DIST-PLUGIN-SKELETON` + skeleton-validate gate). Check the validator's skeleton requirements first.


1	#!/bin/sh
2	# Validate the sptc cplugs SKELETON is a coherent, installable artifact carrying ONLY the skeleton
3	# subset — no runtime state, no binary, no adapter manifest (those ride the spt-core registry, never
4	# cplugs; see docs/RELEASE-RUNBOOK.md). Deterministic, binary pass/fail. [impl->REQ-DIST-PLUGIN-SKELETON]
5	#
6	# Usage: validate-skeleton.sh [plugin-dir]   (default: <repo>/plugin/sptc). Exit 0 = installable.
7	set -u
8	HERE=$(CDPATH= cd "$(dirname "$0")" && pwd)
9	ROOT=$(CDPATH= cd "$HERE/../.." && pwd)
10	PLUGIN="${1:-$ROOT/plugin/sptc}"
11	rc=0
12	bad() { printf 'FAIL: %s\n' "$1"; rc=1; }
13	ok()  { printf 'ok   %s\n' "$1"; }
14
15	[ -d "$PLUGIN" ] || { echo "FATAL: no plugin dir at $PLUGIN"; exit 2; }
16
17	# A minimal JSON well-formedness check without a hard jq dependency: prefer jq, else python, else
18	# a balanced-brace heuristic. Returns 0 if $1 parses / looks well-formed.
19	json_ok() {
20	  if command -v jq >/dev/null 2>&1; then jq -e . "$1" >/dev/null 2>&1; return $?; fi
21	  if command -v python3 >/dev/null 2>&1; then python3 -c 'import json,sys; json.load(open(sys.argv[1]))' "$1" >/dev/null 2>&1; return $?; fi
22	  # Heuristic fallback: non-empty, starts with { , balanced braces.
23	  head -c1 "$1" | grep -q '{' || return 1
24	  awk '{n+=gsub(/{/,"{"); n-=gsub(/}/,"}")} END{exit (n==0)?0:1}' "$1"
25	}
26	# Extract a flat top-level "key":"value" string (same shape _common.sh uses).
27	jval() { sed -n "s/.*\"$2\"[[:space:]]*:[[:space:]]*\"\([^\"]*\)\".*/\1/p" "$1" | head -n1; }
28
29	# ── plugin.json ──────────────────────────────────────────────────────────────
30	PJ="$PLUGIN/.claude-plugin/plugin.json"
31	if [ -f "$PJ" ] && json_ok "$PJ"; then ok "plugin.json valid JSON"; else bad "plugin.json missing/invalid ($PJ)"; fi
32	if [ -f "$PJ" ]; then
33	  [ "$(jval "$PJ" name)" = "sptc" ] && ok "plugin.json name=sptc" || bad "plugin.json name != sptc (got '$(jval "$PJ" name)')"
34	  [ -n "$(jval "$PJ" version)" ] && ok "plugin.json has version" || bad "plugin.json missing version"
35	  [ -n "$(jval "$PJ" description)" ] && ok "plugin.json has description" || bad "plugin.json missing description"
36	fi
37
38	# ── hooks.json + every referenced wrapper exists ─────────────────────────────
39	HJ="$PLUGIN/hooks/hooks.json"
40	if [ -f "$HJ" ] && json_ok "$HJ"; then
41	  ok "hooks.json valid JSON"
42	  # Pull each hooks/<name>.sh token referenced in commands; assert the file exists.
43	  refs=$(grep -o 'hooks/[A-Za-z0-9_-]*\.sh' "$HJ" | sort -u)
44	  for r in $refs; do
45	    if [ -f "$PLUGIN/$r" ]; then ok "hook wrapper present: $r"; else bad "hooks.json references missing wrapper: $r"; fi
46	  done
47	else
48	  bad "hooks.json missing/invalid ($HJ)"
49	fi
50
51	# ── every skill dir has a SKILL.md ───────────────────────────────────────────
52	if [ -d "$PLUGIN/skills" ]; then
53	  for d in "$PLUGIN"/skills/*/; do
54	    [ -e "$d" ] || continue
55	    if [ -f "${d}SKILL.md" ]; then ok "skill ok: $(basename "$d")"; else bad "skill missing SKILL.md: $(basename "$d")"; fi
56	  done
57	else
58	  bad "no skills/ dir"
59	fi
60
61	# ── skeleton-subset invariant: NO runtime state / binary / manifest in the PUBLISHED surface ──
62	# [impl->REQ-DIST-PLUGIN-SKELETON]
63	# Scan only the subset that actually gets published (what package-skeleton.sh copies) — never the
64	# whole plugin dir, so gitignored dev-tree runtime noise (e.g. a live perch's `.claude/`, which the
65	# packager never copies) doesn't false-fail the gate. A leak here means a non-skeleton file landed
66	# INSIDE a published path (skills/, hooks/, plugin.json, bootstrap) — that ships, so it must fail.
67	SUBSET_PATHS="$PLUGIN/.claude-plugin/plugin.json $PLUGIN/hooks $PLUGIN/skills $PLUGIN/bootstrap.sh $PLUGIN/bootstrap.ps1"
68	scan=""
69	for p in $SUBSET_PATHS; do [ -e "$p" ] && scan="$scan $p"; done
70	leaks=$(find $scan -type f \( \
71	     -name 'LIVE_AGENT_IDS.json' \
72	  -o -name '*-commune.md' \
73	  -o -name '*-signoff.md' \
74	  -o -name 'cc' -o -name 'cc-*' -o -name 'cc.bat' -o -name 'cc.sh' \
75	  -o -name '*.exe' -o -name '*.bin' -o -name '*.dll' \
76	  -o -name 'manifest.json' \) 2>/dev/null)
77	if [ -z "$leaks" ]; then
78	  ok "published surface clean (no runtime/binary/manifest leak)"
79	else
80	  printf 'FAIL: non-skeleton files in the published surface (runtime state / binary / manifest belong in the spt-core registry, not cplugs):\n%s\n' "$leaks"
81	  rc=1
82	fi
83
84	printf '\n=== SKELETON: %s ===\n' "$([ "$rc" -eq 0 ] && echo INSTALLABLE || echo INVALID)"
85	exit "$rc"
86

#!/bin/sh
# Validate the sptc cplugs SKELETON is a coherent, installable artifact carrying ONLY the skeleton
# subset — no runtime state, no binary, no adapter manifest (those ride the spt-core registry, never
# cplugs; see docs/RELEASE-RUNBOOK.md). Deterministic, binary pass/fail. [impl->REQ-DIST-PLUGIN-SKELETON]
#
# Usage: validate-skeleton.sh [plugin-dir]   (default: <repo>/plugin/sptc). Exit 0 = installable.
set -u
HERE=$(CDPATH= cd "$(dirname "$0")" && pwd)
ROOT=$(CDPATH= cd "$HERE/../.." && pwd)
PLUGIN="${1:-$ROOT/plugin/sptc}"
rc=0
bad() { printf 'FAIL: %s\n' "$1"; rc=1; }
ok()  { printf 'ok   %s\n' "$1"; }

[ -d "$PLUGIN" ] || { echo "FATAL: no plugin dir at $PLUGIN"; exit 2; }

# A minimal JSON well-formedness check without a hard jq dependency: prefer jq, else python, else
# a balanced-brace heuristic. Returns 0 if $1 parses / looks well-formed.
json_ok() {
  if command -v jq >/dev/null 2>&1; then jq -e . "$1" >/dev/null 2>&1; return $?; fi
  if command -v python3 >/dev/null 2>&1; then python3 -c 'import json,sys; json.load(open(sys.argv[1]))' "$1" >/dev/null 2>&1; return $?; fi
  # Heuristic fallback: non-empty, starts with { , balanced braces.
  head -c1 "$1" | grep -q '{' || return 1
  awk '{n+=gsub(/{/,"{"); n-=gsub(/}/,"}")} END{exit (n==0)?0:1}' "$1"
}
# Extract a flat top-level "key":"value" string (same shape _common.sh uses).
jval() { sed -n "s/.*\"$2\"[[:space:]]*:[[:space:]]*\"\([^\"]*\)\".*/\1/p" "$1" | head -n1; }

# ── plugin.json ──────────────────────────────────────────────────────────────
PJ="$PLUGIN/.claude-plugin/plugin.json"
if [ -f "$PJ" ] && json_ok "$PJ"; then ok "plugin.json valid JSON"; else bad "plugin.json missing/invalid ($PJ)"; fi
if [ -f "$PJ" ]; then
  [ "$(jval "$PJ" name)" = "sptc" ] && ok "plugin.json name=sptc" || bad "plugin.json name != sptc (got '$(jval "$PJ" name)')"
  [ -n "$(jval "$PJ" version)" ] && ok "plugin.json has version" || bad "plugin.json missing version"
  [ -n "$(jval "$PJ" description)" ] && ok "plugin.json has description" || bad "plugin.json missing description"
fi

# ── hooks.json + every referenced wrapper exists ─────────────────────────────
HJ="$PLUGIN/hooks/hooks.json"
if [ -f "$HJ" ] && json_ok "$HJ"; then
  ok "hooks.json valid JSON"
  # Pull each hooks/<name>.sh token referenced in commands; assert the file exists.
  refs=$(grep -o 'hooks/[A-Za-z0-9_-]*\.sh' "$HJ" | sort -u)
  for r in $refs; do
    if [ -f "$PLUGIN/$r" ]; then ok "hook wrapper present: $r"; else bad "hooks.json references missing wrapper: $r"; fi
  done
else
  bad "hooks.json missing/invalid ($HJ)"
fi

# ── every skill dir has a SKILL.md ───────────────────────────────────────────
if [ -d "$PLUGIN/skills" ]; then
  for d in "$PLUGIN"/skills/*/; do
    [ -e "$d" ] || continue
    if [ -f "${d}SKILL.md" ]; then ok "skill ok: $(basename "$d")"; else bad "skill missing SKILL.md: $(basename "$d")"; fi
  done
else
  bad "no skills/ dir"
fi

# ── skeleton-subset invariant: NO runtime state / binary / manifest in the PUBLISHED surface ──
# [impl->REQ-DIST-PLUGIN-SKELETON]
# Scan only the subset that actually gets published (what package-skeleton.sh copies) — never the
# whole plugin dir, so gitignored dev-tree runtime noise (e.g. a live perch's `.claude/`, which the
# packager never copies) doesn't false-fail the gate. A leak here means a non-skeleton file landed
# INSIDE a published path (skills/, hooks/, plugin.json, bootstrap) — that ships, so it must fail.
SUBSET_PATHS="$PLUGIN/.claude-plugin/plugin.json $PLUGIN/hooks $PLUGIN/skills $PLUGIN/bootstrap.sh $PLUGIN/bootstrap.ps1"
scan=""
for p in $SUBSET_PATHS; do [ -e "$p" ] && scan="$scan $p"; done
leaks=$(find $scan -type f \( \
     -name 'LIVE_AGENT_IDS.json' \
  -o -name '*-commune.md' \
  -o -name '*-signoff.md' \
  -o -name 'cc' -o -name 'cc-*' -o -name 'cc.bat' -o -name 'cc.sh' \
  -o -name '*.exe' -o -name '*.bin' -o -name '*.dll' \
  -o -name 'manifest.json' \) 2>/dev/null)
if [ -z "$leaks" ]; then
  ok "published surface clean (no runtime/binary/manifest leak)"
else
  printf 'FAIL: non-skeleton files in the published surface (runtime state / binary / manifest belong in the spt-core registry, not cplugs):\n%s\n' "$leaks"
  rc=1
fi

printf '\n=== SKELETON: %s ===\n' "$([ "$rc" -eq 0 ] && echo INSTALLABLE || echo INVALID)"
exit "$rc"


Validator just requires a `SKILL.md` per skill dir. Add `whoami` + `setup` skeletons matching the existing pattern.


Write

---
name: whoami
description: |
  Report this session's spt endpoint id — "which agent am I?".
allowed-tools: [Bash]
---

# /sptc:whoami

> **Skeleton — thin by design.** Operative instructions for this skill are delivered by the
> `sptc` adapter at invocation time (UserPromptSubmit injection from the adapter `[strings]`;
> see `docs/adr/0001-distribution-splits-by-volatility.md`). This SKILL.md stays a stub.
>
> **Not yet operative.** Wiring is held pending spt-core finding **F-001** (the injection
> contract — `docs/SPT-CORE-FINDINGS.md`) and the M12 file-backed `[strings]` dependency.

Resolves the current session to its spt endpoint id (`spt whoami`) so the agent can address
replies and discover its own perch identity.


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

---
name: whoami
description: |
  Report this session's spt endpoint id — "which agent am I?".
allowed-tools: [Bash]
---

# /sptc:whoami

> **Skeleton — thin by design.** Operative instructions for this skill are delivered by the
> `sptc` adapter at invocation time (UserPromptSubmit injection from the adapter `[strings]`;
> see `docs/adr/0001-distribution-splits-by-volatility.md`). This SKILL.md stays a stub.
>
> **Not yet operative.** Wiring is held pending spt-core finding **F-001** (the injection
> contract — `docs/SPT-CORE-FINDINGS.md`) and the M12 file-backed `[strings]` dependency.

Resolves the current session to its spt endpoint id (`spt whoami`) so the agent can address
replies and discover its own perch identity.


---
name: setup
description: |
  Install or repair spt-core for this Claude Code session (mid-session installer).
allowed-tools: [Bash]
---

# /sptc:setup

> **Skeleton — thin by design.** Operative instructions for this skill are delivered by the
> `sptc` adapter at invocation time (UserPromptSubmit injection from the adapter `[strings]`;
> see `docs/adr/0001-distribution-splits-by-volatility.md`). This SKILL.md stays a stub.
>
> **Not yet operative.** Wiring is held pending spt-core finding **F-001** (the injection
> contract — `docs/SPT-CORE-FINDINGS.md`) and the M12 file-backed `[strings]` dependency.

Covers the mid-session install gap (ADR-0001): a user who installs the plugin mid-session has not
had a SessionStart bootstrap fire, so `/sptc:setup` runs the same invisible-installer bootstrap to
fetch + verify spt-core on demand.


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

---
name: setup
description: |
  Install or repair spt-core for this Claude Code session (mid-session installer).
allowed-tools: [Bash]
---

# /sptc:setup

> **Skeleton — thin by design.** Operative instructions for this skill are delivered by the
> `sptc` adapter at invocation time (UserPromptSubmit injection from the adapter `[strings]`;
> see `docs/adr/0001-distribution-splits-by-volatility.md`). This SKILL.md stays a stub.
>
> **Not yet operative.** Wiring is held pending spt-core finding **F-001** (the injection
> contract — `docs/SPT-CORE-FINDINGS.md`) and the M12 file-backed `[strings]` dependency.

Covers the mid-session install gap (ADR-0001): a user who installs the plugin mid-session has not
had a SessionStart bootstrap fire, so `/sptc:setup` runs the same invisible-installer bootstrap to
fetch + verify spt-core on demand.


Bash

cd /c/Users/decid/Documents/projects/spt-claude-code
sh ci/run-gates.sh 2>&1 | grep -iE 'skill ok: (whoami|setup)|skeleton|RESULT|FAIL'


ok  ci/publish/package-skeleton.sh
ok  ci/publish/validate-skeleton.sh
ok  tests/skeleton-validate.sh
ok  tests/skeleton-validate.sh
=== GATE: skeleton-validate (cplugs installability) ===
ok  skeleton installable
=== RESULT: PASS ===


Confirm the two new skills register, then commit.


cd /c/Users/decid/Documents/projects/spt-claude-code
sh ci/publish/validate-skeleton.sh 2>&1 | grep -E 'skill ok'
echo "--- trace check ---"; traceable-reqs check >/dev/null 2>&1 && echo GREEN || echo RED
git add plugin/sptc/skills/whoami plugin/sptc/skills/setup
git commit -q -F - <<'EOF'
feat(plugin): add /sptc:whoami + /sptc:setup skill skeletons (slice 3a)

The manifest (slice 1) advertises /sptc:whoami and /sptc:setup (in
[strings].skills, a hint, and the not_installed notice), but the cplugs
skeleton had neither. Add both as thin skeletons matching the established
pattern (frontmatter + stub body; operative instructions arrive via
UPS-injection from the adapter [strings], held pending F-001 + M12).

- whoami: resolves the session to its spt endpoint id (spt whoami).
- setup: mid-session invisible-installer (the SessionStart-bootstrap gap, ADR-0001).

Covered by REQ-DIST-PLUGIN-SKELETON + the skeleton-validate gate. Gates PASS,
traceable-reqs check green.

Co-authored by: perri
EOF
git log --oneline -1


ok   skill ok: commune
ok   skill ok: force-stop
ok   skill ok: list-agents
ok   skill ok: live
ok   skill ok: new-alarm
ok   skill ok: ready
ok   skill ok: send
ok   skill ok: setup
ok   skill ok: signoff
ok   skill ok: version
ok   skill ok: whoami
--- trace check ---
GREEN
8cd8372 feat(plugin): add /sptc:whoami + /sptc:setup skill skeletons (slice 3a)


All 11 skills validate. Three atomic slices shipped + green. Update the JIT plan so remaining work is sharp, then report.


Edit

## Slices (sequence; each its own commit + green gate)

1. **Adapter manifest authoring** — write the `claude-spt` adapter manifest (`[digest]` extractor,
   profiles, strings, hints, `shortcut_basename=sptc`). Add a **manifest-schema gate** to
   `ci/run-gates.sh` (currently SKIP stub): validate the manifest against the published
   v0.7.0 `manifest.schema.json`. Activate **REQ-DIST-MANIFEST-SCHEMA** → `[doc,impl,unit]`.
2. **shortcut_basename** — wire the `sptc-<id>` shortcut emission; test it. (Mint REQ if needed.)
3. **M12 parity surfaces** (seed REQs per surface as each starts — activate-don't-pre-fail):
   file-backed `[strings]` (skill bodies via UPS-injection, lifts the SKILL.md-skeleton bodies),
   `spt endpoint run` bringup, `cc` launcher, `/sptc:setup`, whoami-alias.

## Slices (sequence; each its own commit + green gate)

1. ✅ **DONE (bd16644)** — **Adapter manifest authoring**. `adapter/claude-spt.toml` ([adapter]+
   shortcut_basename, [hooks.*], [identity], [inject], [digest], [profiles.deep], [strings],
   [[hints]]); vendored `adapter/manifest.schema.json`; manifest-schema gate
   (`ci/manifest/{validate_manifest.py,check-manifest.sh}`, py tomllib|tomli + jsonschema) wired
   into `run-gates.sh`. **REQ-DIST-MANIFEST-SCHEMA** → `[doc,impl,unit]` (int held: real
   `spt adapter add` registration-validation = 2nd layer). Deferred in-manifest with notes:
   `[session]`/roles, `[history]`, `[update]` (file_pull needs a signing key + registry repo not
   on the public surface — a finding).
2. ✅ **DONE (c93757d)** — **shortcut_basename** guard. `tests/manifest-shortcut.sh` asserts the
   `sptc` brand (succession seam). **REQ-DIST-SHORTCUT-BASENAME** → `[doc,unit]` (the field is
   declarative config, not impl — scanner classifies impl by file type; launcher impl + real-picker
   int deferred to endpoint-run/cc-launcher slice, gated on the v0.7.0 binary).
3. ⏳ **M12 parity surfaces** (seed REQs per surface as each starts — activate-don't-pre-fail):
   - ✅ **DONE (8cd8372)** — `/sptc:whoami` + `/sptc:setup` skill skeletons (the manifest
     advertised both; skeletons now exist, covered by REQ-DIST-PLUGIN-SKELETON + skeleton-validate).
   - ⏳ **file-backed `[strings]`** — lift the SKILL.md-skeleton bodies into adapter `[strings]`
     (UPS-injection source). Authoring buildable now; the **int** (the binary actually serves
     file-backed strings) needs the **v0.7.0 binary locally** (have 0.6.0).
   - ⏳ **`spt endpoint run` bringup + `cc`/`sptc` launcher** — needs the **v0.7.0 binary** (the
     picker reads shortcut_basename, a v0.7.0 field). This is REQ-DIST-SHORTCUT-BASENAME's held impl+int.

### Gating note (local binary)
Local `spt` is **0.6.0**; the v0.7.0 picker/strings ints need v0.7.0 installed. Upgrading spt-core
on this machine would disrupt the **running live-agent perch** (perri runs on spt-core) — do NOT do
it autonomously; confirm with the operator first. Until then the v0.7.0-gated ints stay held.