1	# Release runbook
2
3	> How an spt-claude-code release ships. The spine is fixed (changelog · bump · regenerate
4	> docs · tag · publish · docs publish). **Release here is LIGHT: signing is DELEGATED to the
5	> `spt` binary — there is NO two-key signing ceremony in this project.** spt-claude-code is a
6	> thin adapter; the heavy signed-release machinery lives in spt-core, not here.
7
8	<!-- [<doc>->REQ-EXAMPLE-REL] (illustration; escaped per docs/TRACEABILITY.md) -->
9
10	## Publish targets
11
12	An spt-claude-code release lands in **two places**, by volatility:
13
14	1. **The `SaberMage/cplugs` marketplace** — the thin **skeleton plugin** (namespaced
15	   `/spt:*` skill skeletons, `hooks.json`, the SessionStart bootstrap, `plugin.json`). This
16	   is low-churn: it only changes on **structural** plugin changes, not on logic/instruction
17	   updates.
18	2. **The spt-core adapter registry** — the volatile bulk: the CC adapter **manifest**
19	   (`[digest]` extractor, profiles, strings, hints) **+ binary**. This publish is
20	   **spt-core-conducted** (the same framework that conducts adapter updates), not a plugin
21	   file push.
22
23	## One-time setup
24
25	No signing-key ceremony in this project. **Signing is delegated to the `spt` binary**, which
26	handles adapter manifest/binary integrity through spt-core's own signed-update framework. Keep
27	no release keys here. The only setup is marketplace/registry publish credentials for the two
28	targets above (a `cplugs` push token + whatever the spt-core adapter-registry publish step
29	requires).
30
31	## Per release
32
33	1. **Bump** the version (the plugin `plugin.json` version and/or the adapter manifest version —
34	   note these are distinct numbers; see Notes) if needed; **regenerate generated docs**
35	   (the mdBook `docs-site/` build + `llms.txt` / schema / CLI-help exports); land everything
36	   green.
37	2. **Write the user-facing changelog** — add a `## [<version>] - <date>` section at the top of
38	   `CHANGELOG.md` with **Added / Changed / Fixed** subsections. This section becomes the public
39	   release body verbatim, so it is the changelog every user reads. Rules:
40	   - **User-facing UX only.** What a person using the product notices or does differently. Name
41	     the actual commands / flags / surfaces they touch (`/spt:*` skills, `cc`, `/spt:setup`).
42	   - **No internal lingo** — no requirement ids, internal module names, commit hashes, or
43	     milestone / hazard codes. A reader who has never seen the source must understand every line.
44	   - **Flag breaking changes** prominently under Changed.
45	   - The release **fails loudly** if the tagged version has no `## [<version>]` section — the
46	     changelog is not optional.
47	3. **Tag**: `git tag <vX.Y.Z> && git push origin <vX.Y.Z>`. This triggers:
48	   - the **docs-publish workflow** — build `docs-site/` and publish to GitHub Pages (drift-gated;
49	     see `docs/DOCS-STRATEGY.md`).
50	4. **Publish** (light — signing delegated to `spt`):
51	   - **Skeleton plugin** → push to the `SaberMage/cplugs` marketplace **only when the plugin
52	     skeleton structurally changed** (new/removed skill skeleton, hook wiring, bootstrap, or
53	     `plugin.json`). Most releases do not bump the marketplace.
54	   - **Manifest + binary** → publish through the **spt-core adapter registry**; the `spt`
55	     binary performs signing/integrity as part of spt-core's adapter-update framework. You do
56	     **not** run a separate signing step.
57
58	## cplugs skeleton publish — concrete mechanics
59
60	<!-- [doc->REQ-DIST-PLUGIN-SKELETON] -->
61	The concrete cplugs marketplace steps, captured from the sister project's
62	`claude_skill_owl/docs/DEPLOY.md`. **Take only the SKELETON SUBSET.** spt-claude-code's cplugs
63	target is the **thin skeleton** — `/spt:*` skill skeletons, `hooks.json`, the SessionStart
64	bootstrap, `plugin.json`. **No binary, no manifest in cplugs** — the binary + adapter manifest
65	ride the **spt-core adapter registry** (spt-conducted; the *other* publish target above), so
66	none of owl's binary-lifecycle machinery applies here.
67
68	> **Now scripted (`ci/publish/`).** The validate + stage mechanics below are codified:
69	> `ci/publish/validate-skeleton.sh` is a binary installability gate (valid `plugin.json` with
70	> `name=sptc`; valid `hooks.json` whose referenced wrappers all exist; every skill has a
71	> `SKILL.md`; **no runtime-state / binary / manifest leak in the published surface**) — it runs in
72	> `ci/run-gates.sh` (the `skeleton-validate` gate) and is unit-tested by `tests/skeleton-validate.sh`.
73	> `ci/publish/package-skeleton.sh` codifies the per-bump copy below — validates first, **dry-run by
74	> default**, stages only the skeleton subset, never pushes (the marketplace commit/push stays the
75	> operator's step).
76
77	**One-time marketplace setup:**
78
79	```bash
80	# Clone the marketplace repo
81	git clone https://github.com/SaberMage/cplugs.git ~/.claude/plugins/marketplaces/cplugs
82	# Register a "cplugs" entry in ~/.claude/plugins/known_marketplaces.json
83	#   source: { source: "github", repo: "SaberMage/cplugs" }
84	```
85
86	**Per skeleton bump** (only when the skeleton *structurally* changes — see "Per release"):
87
88	```bash
89	# 1. Bump plugin.json version. The plugin manager compares this to the cached copy and
90	#    SKIPS the update entirely if they match — a stale version = a silent no-update.
91	# 2. Copy skeleton files (NO binary) into the marketplace clone:
92	MARKET=~/.claude/plugins/marketplaces/cplugs/plugins/sptc
93	cp -r skills/*  "$MARKET/skills/"
94	cp -r hooks/*   "$MARKET/hooks/"
95	cp .claude-plugin/plugin.json "$MARKET/.claude-plugin/"
96	# 3. Commit + push the cplugs repo:
97	cd ~/.claude/plugins/marketplaces/cplugs && git add plugins/sptc/ \
98	  && git commit -m "sptc: <change>" && git push
99	```
100
101	**Pointer flip** (the authoritative install-state update):
102
103	```bash
104	claude plugin install sptc@cplugs   # un-orphans + rewrites installed_plugins.json atomically
105	# then, inside Claude Code:
106	/reload-plugins
107	```
108
109	**Gotchas (these DO apply to the skeleton):**
110	- **Never hand-patch `installed_plugins.json`** (`jq`/`sed` are brittle; CC may silently reject).
111	  Use the `claude plugin install` CLI — it's the atomic pointer flip + orphan-marker cleanup.
112	- **No legacy manual-install dir alongside the marketplace install.** A `~/.claude/plugins/<name>/`
113	  regular dir coexisting with the marketplace cache install makes `/plugin` report the plugin
114	  **not installed** (CC enumerates both; the untracked one wins the conflict). Keep the two exclusive.
115	- **Restart the Claude Code session after install** so the SessionStart hook re-runs and repopulates
116	  the plugin's env (`$OWL`/`$LIVE` equivalents) in Bash subprocesses.
117
118	**Explicitly NOT ours** (owl ships a live self-migrating binary; the thin skeleton has none, so
119	skip all of it): `owl.exe` binary sync · the cache targeted-prune + keep-PREVIOUS-version logic ·
120	the seamless binary handoff (owl Phase 18.4/18.5) · `DEPLOY.ps1`'s build/handoff orchestration.
121
122	> **RULED (operator, 2026-06-14) — cplugs plugin name = `sptc`.** CC ties the skill prefix to the
123	> `plugin.json` name (no override), so skills surface as `/sptc:*`. **Succession:** once parity is
124	> proven, flip `plugin.json` name `sptc`→`spt` (skills → `/spt:*`) and retire/rename legacy owl's
125	> cplugs `spt` plugin in the same coordinated move — two plugins cannot share the `spt` name. The
126	> flip is a single substitution `s/sptc/spt/` (plugin dir, `plugin.json` name, marketplace path).
127
128	## Update path (dual, kept in sync)
129
130	Users receive updates two ways, and both must stay consistent:
131	- **File-pull (the real channel)** — spt-core conducts manifest + binary updates seamlessly;
132	  this is how logic and instruction churn reaches users. Version-of-truth = the
133	  manifest/binary version spt-core tracks (`spt adapter list` / `/spt:version`).
134	- **`claude plugin update` (cautionary, skeleton sync)** — only resyncs the low-churn
135	  marketplace skeleton on the rare structural bump. It is NOT the version-of-truth.
136
137	## Notes
138
139	- **Distinct numbers — never conflate them:** the **plugin (marketplace) version** (in
140	  `plugin.json`, bumps rarely) and the **adapter manifest/binary version** (spt-core-tracked,
141	  the user-visible version-of-truth). They move on independent schedules — the plugin can sit
142	  static across many manifest updates.
143	- **Trust model:** adapter manifest/binary integrity is the `spt` binary's responsibility via
144	  spt-core's signed-update framework — spt-claude-code defines no separate trust ceremony.
145	  (Claude Code has no plugin-file integrity check, which is why the skeleton plugin is safe to
146	  in-place-edit; only old version dirs orphan on update and are GC'd after 7 days.)
147

# Release runbook

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

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

## Publish targets

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

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

## One-time setup

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

## Per release

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

## cplugs skeleton publish — concrete mechanics

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

> **Now scripted (`ci/publish/`).** The validate + stage mechanics below are codified:
> `ci/publish/validate-skeleton.sh` is a binary installability gate (valid `plugin.json` with
> `name=sptc`; valid `hooks.json` whose referenced wrappers all exist; every skill has a
> `SKILL.md`; **no runtime-state / binary / manifest leak in the published surface**) — it runs in
> `ci/run-gates.sh` (the `skeleton-validate` gate) and is unit-tested by `tests/skeleton-validate.sh`.
> `ci/publish/package-skeleton.sh` codifies the per-bump copy below — validates first, **dry-run by
> default**, stages only the skeleton subset, never pushes (the marketplace commit/push stays the
> operator's step).

**One-time marketplace setup:**

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

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

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

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

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

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

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

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

## Update path (dual, kept in sync)

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

## Notes

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


Read plugin.json + package-skeleton to round out publish-readiness, then write the plan.


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

sptc

spt spacetime messaging agents live claude-spt


1	#!/bin/sh
2	# Stage the sptc cplugs SKELETON into a marketplace clone — the runbook "per skeleton bump" cp
3	# mechanic as a real, idempotent, validated script. DRY-RUN by default: prints the plan and stages
4	# nothing. Never pushes (marketplace credentials + the commit/push are the operator's step).
5	# [impl->REQ-DIST-PLUGIN-SKELETON]
6	#
7	# Usage:
8	#   package-skeleton.sh                 # dry-run against the default $MARKET
9	#   package-skeleton.sh --apply         # actually copy the skeleton subset into $MARKET
10	#   MARKET=/path/to/cplugs/plugins/sptc package-skeleton.sh --apply
11	set -u
12	HERE=$(CDPATH= cd "$(dirname "$0")" && pwd)
13	ROOT=$(CDPATH= cd "$HERE/../.." && pwd)
14	PLUGIN="$ROOT/plugin/sptc"
15	MARKET="${MARKET:-$HOME/.claude/plugins/marketplaces/cplugs/plugins/sptc}"
16	APPLY=0
17	[ "${1:-}" = "--apply" ] && APPLY=1
18
19	# Validate first — refuse to stage an invalid / leaky skeleton.
20	echo "== validate skeleton =="
21	if ! sh "$HERE/validate-skeleton.sh" "$PLUGIN"; then
22	  echo "REFUSING to package: skeleton failed validation (fix above, then retry)." >&2
23	  exit 1
24	fi
25
26	# The skeleton SUBSET — exactly what cplugs carries (no binary, no manifest, no runtime state).
27	SUBSET=".claude-plugin/plugin.json hooks skills bootstrap.sh bootstrap.ps1"
28
29	echo
30	echo "== plan ($([ "$APPLY" -eq 1 ] && echo APPLY || echo DRY-RUN)) =="
31	echo "source : $PLUGIN"
32	echo "target : $MARKET"
33	for item in $SUBSET; do
34	  if [ -e "$PLUGIN/$item" ]; then echo "  stage  $item"; else echo "  WARN   missing source: $item"; fi
35	done
36
37	if [ "$APPLY" -ne 1 ]; then
38	  echo
39	  echo "DRY-RUN: nothing copied. Re-run with --apply to stage into \$MARKET, then commit+push the"
40	  echo "cplugs repo and 'claude plugin install sptc@cplugs' (see docs/RELEASE-RUNBOOK.md)."
41	  exit 0
42	fi
43
44	mkdir -p "$MARKET/.claude-plugin" "$MARKET/hooks" "$MARKET/skills" || { echo "FATAL: cannot create $MARKET"; exit 2; }
45	cp "$PLUGIN/.claude-plugin/plugin.json" "$MARKET/.claude-plugin/"
46	# Replace dir contents wholesale so removed skeleton files don't linger in the marketplace clone.
47	rm -rf "$MARKET/hooks" "$MARKET/skills"
48	cp -r "$PLUGIN/hooks" "$MARKET/hooks"
49	cp -r "$PLUGIN/skills" "$MARKET/skills"
50	for b in bootstrap.sh bootstrap.ps1; do [ -f "$PLUGIN/$b" ] && cp "$PLUGIN/$b" "$MARKET/"; done
51
52	echo
53	echo "STAGED into $MARKET. Next (operator): cd into the cplugs repo, 'git add plugins/sptc/',"
54	echo "commit + push, then 'claude plugin install sptc@cplugs' + /reload-plugins."
55	exit 0
56

#!/bin/sh
# Stage the sptc cplugs SKELETON into a marketplace clone — the runbook "per skeleton bump" cp
# mechanic as a real, idempotent, validated script. DRY-RUN by default: prints the plan and stages
# nothing. Never pushes (marketplace credentials + the commit/push are the operator's step).
# [impl->REQ-DIST-PLUGIN-SKELETON]
#
# Usage:
#   package-skeleton.sh                 # dry-run against the default $MARKET
#   package-skeleton.sh --apply         # actually copy the skeleton subset into $MARKET
#   MARKET=/path/to/cplugs/plugins/sptc package-skeleton.sh --apply
set -u
HERE=$(CDPATH= cd "$(dirname "$0")" && pwd)
ROOT=$(CDPATH= cd "$HERE/../.." && pwd)
PLUGIN="$ROOT/plugin/sptc"
MARKET="${MARKET:-$HOME/.claude/plugins/marketplaces/cplugs/plugins/sptc}"
APPLY=0
[ "${1:-}" = "--apply" ] && APPLY=1

# Validate first — refuse to stage an invalid / leaky skeleton.
echo "== validate skeleton =="
if ! sh "$HERE/validate-skeleton.sh" "$PLUGIN"; then
  echo "REFUSING to package: skeleton failed validation (fix above, then retry)." >&2
  exit 1
fi

# The skeleton SUBSET — exactly what cplugs carries (no binary, no manifest, no runtime state).
SUBSET=".claude-plugin/plugin.json hooks skills bootstrap.sh bootstrap.ps1"

echo
echo "== plan ($([ "$APPLY" -eq 1 ] && echo APPLY || echo DRY-RUN)) =="
echo "source : $PLUGIN"
echo "target : $MARKET"
for item in $SUBSET; do
  if [ -e "$PLUGIN/$item" ]; then echo "  stage  $item"; else echo "  WARN   missing source: $item"; fi
done

if [ "$APPLY" -ne 1 ]; then
  echo
  echo "DRY-RUN: nothing copied. Re-run with --apply to stage into \$MARKET, then commit+push the"
  echo "cplugs repo and 'claude plugin install sptc@cplugs' (see docs/RELEASE-RUNBOOK.md)."
  exit 0
fi

mkdir -p "$MARKET/.claude-plugin" "$MARKET/hooks" "$MARKET/skills" || { echo "FATAL: cannot create $MARKET"; exit 2; }
cp "$PLUGIN/.claude-plugin/plugin.json" "$MARKET/.claude-plugin/"
# Replace dir contents wholesale so removed skeleton files don't linger in the marketplace clone.
rm -rf "$MARKET/hooks" "$MARKET/skills"
cp -r "$PLUGIN/hooks" "$MARKET/hooks"
cp -r "$PLUGIN/skills" "$MARKET/skills"
for b in bootstrap.sh bootstrap.ps1; do [ -f "$PLUGIN/$b" ] && cp "$PLUGIN/$b" "$MARKET/"; done

echo
echo "STAGED into $MARKET. Next (operator): cd into the cplugs repo, 'git add plugins/sptc/',"
echo "commit + push, then 'claude plugin install sptc@cplugs' + /reload-plugins."
exit 0


Have the picture. Quick fact-checks before drafting: CHANGELOG, ccs wiring, subnet verbs on the public surface, whoami disposition.


Bash

cd C:/Users/decid/Documents/projects/spt-claude-code && echo "===CHANGELOG?===" && ls CHANGELOG.md 2>&1 && head -20 CHANGELOG.md 2>/dev/null; echo "===subnet verbs (public surface)===" && spt --help 2>&1 | grep -iE 'subnet|join|invite|code' ; echo "===spt subnet help===" && spt subnet --help 2>&1 | head -20; echo "===ccs in scope===" && grep -n -iE 'ccs' SCOPE.md | head

CHANGELOG, subnet verbs, ccs scope


===CHANGELOG?===
ls: cannot access 'CHANGELOG.md': No such file or directory
===subnet verbs (public surface)===
  subnet    Subnet membership: status, create, show-code
===spt subnet help===
Subnet membership: status, create, show-code.

A subnet is a private group of paired machines — your agents reach each other across every member
node. Bare `spt subnet` shows the membership status view.

Usage: spt.exe subnet [COMMAND]

Commands:
  status     Show subnet membership: name, paired nodes, endpoints
  create     Mint a fresh subnet and print its joining material
  show-code  Show a subnet's current 6-digit pairing code (+ URI and QR)
  join       Pair this machine into an existing subnet (guided)
  leave      Exit a subnet: drop its membership and trust material from this node
  prune      Remove a dead node identity's trust rows (and registry rows)
  revoke     Revoke node(s) fleet-wide and rotate the subnet seed
  detach     Stop serving a held subnet (the daemon keeps running)
  attach     Resume serving a detached subnet
  notify     Issue a subnet-wide user notification
  help       Print this message or the help of the given subcommand(s)

===ccs in scope===
68:## ccs integration
70:**LOCKED.** ccs = **profile(s) under spt-claude-code** (`claude-spt:glm`,
73:spawn/psyche/echo command templates). Per-profile `~/.ccs` log dir → profile also
75:profile *templates* (ccs-invoking command structure), user supplies own ccs config/keys.
92:7. ccs profile wiring — if `~/.ccs` present; **also offer to install ccs** (+1-sentence value
137:    lives in a per-version `~/.claude/` or `~/.ccs/` plugins folder whose path changes each
151:profiles + strings + hints wiring, ccs profiles.


Facts in. Key parity deltas found:
- **Subnet skills** (create/join/show-code) — LOCKED ADD, **absent**. Verbs exist on public surface (`spt subnet …`) → buildable.
- **ccs profiles** (`claude-spt:glm` …) — LOCKED ADD, **absent** (only `deep`/`live` profiles wired).
- **whoami** — SCOPE says DROP the *skill*; it's currently operative. Conflict to reconcile.
- **new-alarm** — dead stub (out-of-scope, no manifest entry).
- **No CHANGELOG.md** — release fails loudly without it.

Two load-bearing scope decisions shape the plan:


AskUserQuestion

Question: Parity audit found LOCKED-ADD gaps (subnet skills, ccs profiles) absent from the current 10-skill surface. How wide is THIS slice?

Choices:
Build subnet skills (create/join/show-code) + ccs profiles, reconcile whoami/new-alarm, THEN publish-prep as sptc. Largest; full parity before publish (matches the succession guardrail's 'proven parity').
Produce the parity matrix + findings, reconcile whoami/new-alarm housekeeping, publish the current operative surface as sptc NOW; subnet/ccs gaps become tracked REQs/findings for a follow-up slice.
Just the parity matrix + findings + REQ seeding. No gap-fill, no publish. Surfaces the full delta, decide build/publish next.

Question: SCOPE.md (LOCKED) says DROP the /whoami SKILL (core `spt whoami` stays, becomes an `endpoint list` alias in M12). But /sptc:whoami is currently operative (file-backed body + REQ evidence). Reconcile how?

Choices:
Supersede the SCOPE drop — /sptc:whoami stays a thin convenience skill for v1. Update SCOPE.md to record the reversal + why.
Honor the LOCKED decision — remove whoami from [strings.skills] + the stub; rely on core `spt whoami`. Unwinds this session's whoami evidence.
Flag as an open parity finding; ask doyle/operator for the ruling before touching it. Audit notes the conflict, doesn't resolve it.


Your questions have been answered: "Parity audit found LOCKED-ADD gaps (subnet skills, ccs profiles) absent from the current 10-skill surface. How wide is THIS slice?"="audit + fill gaps + publish. `new-alarm` can be deferred until a later time, and isn't an immediate goal.", "SCOPE.md (LOCKED) says DROP the /whoami SKILL (core `spt whoami` stays, becomes an `endpoint list` alias in M12). But /sptc:whoami is currently operative (file-backed body + REQ evidence). Reconcile how?"="Drop the skill". You can now continue with these answers in mind.

Question: Parity audit found LOCKED-ADD gaps (subnet skills, ccs profiles) absent from the current 10-skill surface. How wide is THIS slice?

Choices:
Build subnet skills (create/join/show-code) + ccs profiles, reconcile whoami/new-alarm, THEN publish-prep as sptc. Largest; full parity before publish (matches the succession guardrail's 'proven parity').
Produce the parity matrix + findings, reconcile whoami/new-alarm housekeeping, publish the current operative surface as sptc NOW; subnet/ccs gaps become tracked REQs/findings for a follow-up slice.
Just the parity matrix + findings + REQ seeding. No gap-fill, no publish. Surfaces the full delta, decide build/publish next.

Question: SCOPE.md (LOCKED) says DROP the /whoami SKILL (core `spt whoami` stays, becomes an `endpoint list` alias in M12). But /sptc:whoami is currently operative (file-backed body + REQ evidence). Reconcile how?

Choices:
Supersede the SCOPE drop — /sptc:whoami stays a thin convenience skill for v1. Update SCOPE.md to record the reversal + why.
Honor the LOCKED decision — remove whoami from [strings.skills] + the stub; rely on core `spt whoami`. Unwinds this session's whoami evidence.
Flag as an open parity finding; ask doyle/operator for the ruling before touching it. Audit notes the conflict, doesn't resolve it.

audit + fill gaps + publish. `new-alarm` can be deferred until a later time, and isn't an immediate goal.


Decisions locked: **fill gaps + publish** (new-alarm stays deferred); **drop whoami skill**. Writing the JIT plan.


Write

# JIT plan — parity audit → fill LOCKED-ADD gaps → cplugs publish (as sptc)

> Next slice after the live sub-slice (4b042cc) + F-004 flip (692b67c). Goal: PROVE parity
> against the SCOPE.md LOCKED parity-trim, fill the remaining LOCKED-ADD gaps, then publish the
> thin skeleton to cplugs **as `sptc`** (NOT `spt` — succession flip stays gated, see
> [[succession-flip-gated]] / RELEASE-RUNBOOK). Public-surface-only still binds (AGENTS.md).

## Operator decisions (locked this session, 2026-06-15)
1. **Slice = audit + fill gaps + publish.** Build the absent LOCKED-ADD surface (subnet skills,
   ccs profiles), reconcile whoami, THEN publish as sptc. `new-alarm` stays **deferred**
   (out-of-scope v1, no core alarm primitive — doyle finding; the dead stub gets removed, not built).
2. **Drop the `/whoami` SKILL.** Honor the LOCKED SCOPE decision — core `spt whoami` stays
   (becomes the `endpoint list` alias in M12); the adapter ships no whoami skill.

## Parity matrix (current vs SCOPE LOCKED parity-trim)

| Legacy / target surface        | SCOPE disposition        | Adapter today          | Action (this slice)            |
|--------------------------------|--------------------------|------------------------|--------------------------------|
| /live /commune /ready /send    | KEEP                     | operative (file-backed)| ✓ none                         |
| /list-agents /signoff /force-stop | KEEP                  | operative              | ✓ none                         |
| /new-alarm                     | KEEP (but no core prim.) | dead stub, no manifest | **remove stub** (deferred)     |
| /spt:setup /spt:version        | ADD                      | operative              | ✓ none                         |
| profiles + strings + hints     | ADD                      | wired (deep/live)      | ✓ none                         |
| **subnet skills (create/join/show-code)** | ADD (LOCKED)  | **ABSENT**             | **BUILD** (Phase C)            |
| **ccs profiles (claude-spt:glm …)** | ADD (LOCKED)        | **ABSENT**             | **BUILD** (Phase D)            |
| /whoami SKILL                  | DROP skill               | **operative (violation)** | **REMOVE** (Phase B)        |
| /fork /amend-signoff /revive /clear-psyche | DROP         | absent                 | ✓ none (confirm absent)        |

Public surface confirms the ADD gaps are buildable: `spt subnet {create,join,show-code,status}`
exists; ccs = profile overlays retargeting command templates (SCOPE §ccs, profile *templates*
only — user supplies their own ccs config/keys).

## Phases (sequence; one commit + green gate each)

### Phase A — Parity audit artifact + REQ seed
- Write `docs/PARITY.md` (the matrix above + per-row evidence/finding) — the "proven parity"
  record the succession flip gates on. Tag `<!-- doc->REQ-PARITY-AUDIT -->`.
- Seed REQs in `traceable-reqs.toml` FIRST (before building): `REQ-SKILL-SUBNET`,
  `REQ-CCS-PROFILES`, `REQ-PARITY-AUDIT`. Activate stages as each phase lands.
- Confirm the DROP set is truly absent (grep no fork/revive/clear-psyche/amend-signoff residue).

### Phase B — Drop the whoami skill (honor LOCKED)
- Remove `whoami = { file = "skills/whoami.md" }` from manifest `[strings.skills]`.
- Delete `adapter/strings/skills/whoami.md` + `plugin/sptc/skills/whoami/`.
- **registration-int.sh** uses `skills.whoami` as the canonical file-backed + UPS-injection
  example (4b/4c) — repoint to a surviving skill (`skills.ready`).
- Redirect the `who am i` **hint** (currently → /sptc:whoami) to core `spt whoami` guidance.
- Audit REQ evidence: nothing is tagged `REQ-*` on whoami.md directly, but check
  `tests/hooks-parse.sh` / manifest-shortcut for whoami coupling before deleting.

### Phase C — Subnet skills (LOCKED ADD; public verbs exist)
- **Open shape decision (resolve at top of phase):** three skills
  (`/sptc:subnet-create|join|show-code`) vs one routing `/sptc:subnet` skill. Lean: match SCOPE's
  enumeration but as ONE `subnet` skill body that documents create/join/show-code (fewer
  surfaces, cleaner UX) — confirm before authoring.
- File-backed body(ies) `adapter/strings/skills/subnet*.md` (run `spt how-to subnet` / wrap the
  real `spt subnet …` verbs); manifest `[strings.skills]` entries; thin SKILL.md stub(s); hint(s).
- `REQ-SKILL-SUBNET` [doc,impl] (+ int via registration-int file-backed-resolve assertion).

### Phase D — ccs profiles (LOCKED ADD)
- **Research first** (mcp/context7 not needed — read SCOPE §ccs + the existing `[profiles.*]`
  command seams). ccs profile = a `[profiles.<glm|kimi>]` overlay that leaf-replaces the spawn /
  psyche / echo command templates to route through `ccs <profile> …`, with a per-profile `~/.ccs`
  log dir. Author profile *templates* only (no user keys).
- Add `[profiles.glm]` (+ maybe `[profiles.kimi]`) overlays; observable via `spt adapter
  get-string claude-spt:glm …` + resolves in registration-int (mirror the :deep/:live assertions).
- `REQ-CCS-PROFILES` [doc,impl] (+ int).

### Phase E — Publish prep (as sptc; NO name flip)
- **Create `CHANGELOG.md`** with a `## [<version>] - 2026-06-15` section (Added/Changed/Fixed,
  user-facing UX only, no internal lingo) — release fails loudly without it.
- **Bump `plugin.json`** `version` 0.0.1 → first real (structural first publish). Keep `name=sptc`.
- Run `sh ci/publish/validate-skeleton.sh` + `sh ci/publish/package-skeleton.sh` (DRY-RUN) green.
- Regenerate docs (mdBook + llms.txt) — drift gate green.
- The actual cplugs commit/push + `claude plugin install sptc@cplugs` stays the **operator's**
  step (credentials + pointer flip) — plan stops at a validated dry-run + a publish checklist.

### Gate (every phase)
`sh ci/run-gates.sh` PASS + `traceable-reqs check` green. Commit `Co-authored by: perri`.

## Open questions (resolve in-phase, don't block planning)
- Subnet skill shape: 3 skills vs 1 routing skill (Phase C top).
- ccs: which profiles ship as templates (glm only, or glm+kimi)? SCOPE names `claude-spt:glm`.
- plugin.json first-publish version number (0.1.0? matches adapter manifest 0.1.0, but RUNBOOK
  says keep them DISTINCT — pick independently).

## Findings carried (not ours to fix)
- new-alarm core alarm/deferred-pulse primitive (blocks new-alarm parity) — doyle.
- hard/no-grace force-stop primitive — doyle.
- Live relay int held for spt-core M11 counter-15 release ([[live-int-gated-m11-counter15]]).

## Done immediately before (context)
- 4b042cc live sub-slice (claude-spt-psyche runner + /sptc:live body, REQ-SKILL-LIVE [doc,impl,unit]).
- 692b67c F-004 flip (digest-proof int GREEN on v0.7.2, REQ-DIST-DIGEST-EXTRACTOR full).
- spt upgraded 0.7.1 → 0.7.2 (operator-approved daemon bounce).


<owl_messages>
<owl_message from="doyle" priority="high">[doyle -> perri] new-alarm parity delta — TRIAGED, disposition = ACCEPTED GAP, stays deferred. Not an oversight: spt-core has only the alarm event shape (spt-proto) + relay handling (psyrelay.rs) + a test fixture; the durable "fire at target-time" timer still lives in legacy owl in-memory, and the in-daemon durable scheduler is a deliberate deferral (DEFERRED.md:43, ADR-0018 Q4/V3 — deferred because the daemon had no one-shot consumer; building pre-consumer = untested dead code).

Operator ruling just now: KEEP IT DEFERRED. He has a more robust "arrangement" system in mind that would SUPERSEDE the alarm port — not ready to detail yet. So: do NOT build an adapter-side stopgap timer (it'd be throwaway against the coming arrangement model). Log /spt:new-alarm as a KNOWN, ACCEPTED parity gap in your audit — call it deferred-pending-arrangement-system, not missing — and move on. No core work, no REQ minted. I'll loop you when the arrangement design surfaces. Carry on the parity audit.</owl_message>
</owl_messages>

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


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

# JIT plan — parity audit → fill LOCKED-ADD gaps → cplugs publish (as sptc)

> Next slice after the live sub-slice (4b042cc) + F-004 flip (692b67c). Goal: PROVE parity
> against the SCOPE.md LOCKED parity-trim, fill the remaining LOCKED-ADD gaps, then publish the
> thin skeleton to cplugs **as `sptc`** (NOT `spt` — succession flip stays gated, see
> [[succession-flip-gated]] / RELEASE-RUNBOOK). Public-surface-only still binds (AGENTS.md).

## Operator decisions (locked this session, 2026-06-15)
1. **Slice = audit + fill gaps + publish.** Build the absent LOCKED-ADD surface (subnet skills,
   ccs profiles), reconcile whoami, THEN publish as sptc. `new-alarm` stays **deferred**
   (out-of-scope v1, no core alarm primitive — doyle finding; the dead stub gets removed, not built).
2. **Drop the `/whoami` SKILL.** Honor the LOCKED SCOPE decision — core `spt whoami` stays
   (becomes the `endpoint list` alias in M12); the adapter ships no whoami skill.

## Parity matrix (current vs SCOPE LOCKED parity-trim)

| Legacy / target surface        | SCOPE disposition        | Adapter today          | Action (this slice)            |
|--------------------------------|--------------------------|------------------------|--------------------------------|
| /live /commune /ready /send    | KEEP                     | operative (file-backed)| ✓ none                         |
| /list-agents /signoff /force-stop | KEEP                  | operative              | ✓ none                         |
| /new-alarm                     | KEEP (but no core prim.) | dead stub, no manifest | **remove stub** (deferred)     |
| /spt:setup /spt:version        | ADD                      | operative              | ✓ none                         |
| profiles + strings + hints     | ADD                      | wired (deep/live)      | ✓ none                         |
| **subnet skills (create/join/show-code)** | ADD (LOCKED)  | **ABSENT**             | **BUILD** (Phase C)            |
| **ccs profiles (claude-spt:glm …)** | ADD (LOCKED)        | **ABSENT**             | **BUILD** (Phase D)            |
| /whoami SKILL                  | DROP skill               | **operative (violation)** | **REMOVE** (Phase B)        |
| /fork /amend-signoff /revive /clear-psyche | DROP         | absent                 | ✓ none (confirm absent)        |

Public surface confirms the ADD gaps are buildable: `spt subnet {create,join,show-code,status}`
exists; ccs = profile overlays retargeting command templates (SCOPE §ccs, profile *templates*
only — user supplies their own ccs config/keys).

## Phases (sequence; one commit + green gate each)

### Phase A — Parity audit artifact + REQ seed
- Write `docs/PARITY.md` (the matrix above + per-row evidence/finding) — the "proven parity"
  record the succession flip gates on. Tag `<!-- doc->REQ-PARITY-AUDIT -->`.
- Seed REQs in `traceable-reqs.toml` FIRST (before building): `REQ-SKILL-SUBNET`,
  `REQ-CCS-PROFILES`, `REQ-PARITY-AUDIT`. Activate stages as each phase lands.
- Confirm the DROP set is truly absent (grep no fork/revive/clear-psyche/amend-signoff residue).

### Phase B — Drop the whoami skill (honor LOCKED)
- Remove `whoami = { file = "skills/whoami.md" }` from manifest `[strings.skills]`.
- Delete `adapter/strings/skills/whoami.md` + `plugin/sptc/skills/whoami/`.
- **registration-int.sh** uses `skills.whoami` as the canonical file-backed + UPS-injection
  example (4b/4c) — repoint to a surviving skill (`skills.ready`).
- Redirect the `who am i` **hint** (currently → /sptc:whoami) to core `spt whoami` guidance.
- Audit REQ evidence: nothing is tagged `REQ-*` on whoami.md directly, but check
  `tests/hooks-parse.sh` / manifest-shortcut for whoami coupling before deleting.

### Phase C — Subnet skills (LOCKED ADD; public verbs exist)
- **Open shape decision (resolve at top of phase):** three skills
  (`/sptc:subnet-create|join|show-code`) vs one routing `/sptc:subnet` skill. Lean: match SCOPE's
  enumeration but as ONE `subnet` skill body that documents create/join/show-code (fewer
  surfaces, cleaner UX) — confirm before authoring.
- File-backed body(ies) `adapter/strings/skills/subnet*.md` (run `spt how-to subnet` / wrap the
  real `spt subnet …` verbs); manifest `[strings.skills]` entries; thin SKILL.md stub(s); hint(s).
- `REQ-SKILL-SUBNET` [doc,impl] (+ int via registration-int file-backed-resolve assertion).

### Phase D — ccs profiles (LOCKED ADD)
- **Research first** (mcp/context7 not needed — read SCOPE §ccs + the existing `[profiles.*]`
  command seams). ccs profile = a `[profiles.<glm|kimi>]` overlay that leaf-replaces the spawn /
  psyche / echo command templates to route through `ccs <profile> …`, with a per-profile `~/.ccs`
  log dir. Author profile *templates* only (no user keys).
- Add `[profiles.glm]` (+ maybe `[profiles.kimi]`) overlays; observable via `spt adapter
  get-string claude-spt:glm …` + resolves in registration-int (mirror the :deep/:live assertions).
- `REQ-CCS-PROFILES` [doc,impl] (+ int).

### Phase E — Publish prep (as sptc; NO name flip)
- **Create `CHANGELOG.md`** with a `## [<version>] - 2026-06-15` section (Added/Changed/Fixed,
  user-facing UX only, no internal lingo) — release fails loudly without it.
- **Bump `plugin.json`** `version` 0.0.1 → first real (structural first publish). Keep `name=sptc`.
- Run `sh ci/publish/validate-skeleton.sh` + `sh ci/publish/package-skeleton.sh` (DRY-RUN) green.
- Regenerate docs (mdBook + llms.txt) — drift gate green.
- The actual cplugs commit/push + `claude plugin install sptc@cplugs` stays the **operator's**
  step (credentials + pointer flip) — plan stops at a validated dry-run + a publish checklist.

### Gate (every phase)
`sh ci/run-gates.sh` PASS + `traceable-reqs check` green. Commit `Co-authored by: perri`.

## Open questions (resolve in-phase, don't block planning)
- Subnet skill shape: 3 skills vs 1 routing skill (Phase C top).
- ccs: which profiles ship as templates (glm only, or glm+kimi)? SCOPE names `claude-spt:glm`.
- plugin.json first-publish version number (0.1.0? matches adapter manifest 0.1.0, but RUNBOOK
  says keep them DISTINCT — pick independently).

## Findings carried (not ours to fix)
- new-alarm core alarm/deferred-pulse primitive (blocks new-alarm parity) — doyle.
- hard/no-grace force-stop primitive — doyle.
- Live relay int held for spt-core M11 counter-15 release ([[live-int-gated-m11-counter15]]).

## Done immediately before (context)
- 4b042cc live sub-slice (claude-spt-psyche runner + /sptc:live body, REQ-SKILL-LIVE [doc,impl,unit]).
- 692b67c F-004 flip (digest-proof int GREEN on v0.7.2, REQ-DIST-DIGEST-EXTRACTOR full).
- spt upgraded 0.7.1 → 0.7.2 (operator-approved daemon bounce).


doyle high-prio: new-alarm = ACCEPTED gap, deferred-pending-arrangement-system. No stopgap timer, no REQ. Ack first.


Bash

cd C:/Users/decid/Documents/projects/spt-claude-code && $OWL send doyle <<'EOF'
[perri → doyle] Ack — new-alarm = ACCEPTED parity gap, framed "deferred-pending-arrangement-system" (NOT missing). No adapter-side stopgap timer (would be throwaway vs the coming arrangement model), no REQ, no core work. Logging it that way in docs/PARITY.md; removing the dead /sptc:new-alarm stub from the published surface (deferred ≠ shipping a no-op skill). Awaiting your loop when the arrangement design surfaces. Carrying on the parity audit.
EOF

Ack new-alarm disposition to doyle


➜ SENT:doyle


Ack'd. Fold doyle's framing into the plan.


Edit

1. **Slice = audit + fill gaps + publish.** Build the absent LOCKED-ADD surface (subnet skills,
   ccs profiles), reconcile whoami, THEN publish as sptc. `new-alarm` stays **deferred**
   (out-of-scope v1, no core alarm primitive — doyle finding; the dead stub gets removed, not built).

1. **Slice = audit + fill gaps + publish.** Build the absent LOCKED-ADD surface (subnet skills,
   ccs profiles), reconcile whoami, THEN publish as sptc. `new-alarm` stays **deferred** — doyle
   TRIAGED it (high-prio 2026-06-15) as an **ACCEPTED parity gap = "deferred-pending-arrangement-
   system", NOT missing**: spt-core has only the alarm event shape + relay handling + a test
   fixture; the durable timer is a deliberate deferral (DEFERRED.md:43, ADR-0018 Q4/V3), and the
   operator has a superseding "arrangement" system in mind. **Do NOT build an adapter-side stopgap
   timer** (throwaway vs the coming model), **no REQ minted**. Remove the dead stub (deferred ≠
   shipping a no-op skill); doyle loops me when the arrangement design surfaces.


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

1. **Slice = audit + fill gaps + publish.** Build the absent LOCKED-ADD surface (subnet skills,
   ccs profiles), reconcile whoami, THEN publish as sptc. `new-alarm` stays **deferred** — doyle
   TRIAGED it (high-prio 2026-06-15) as an **ACCEPTED parity gap = "deferred-pending-arrangement-
   system", NOT missing**: spt-core has only the alarm event shape + relay handling + a test
   fixture; the durable timer is a deliberate deferral (DEFERRED.md:43, ADR-0018 Q4/V3), and the
   operator has a superseding "arrangement" system in mind. **Do NOT build an adapter-side stopgap
   timer** (throwaway vs the coming model), **no REQ minted**. Remove the dead stub (deferred ≠
   shipping a no-op skill); doyle loops me when the arrangement design surfaces.

# JIT plan — parity audit → fill LOCKED-ADD gaps → cplugs publish (as sptc)

> Next slice after the live sub-slice (4b042cc) + F-004 flip (692b67c). Goal: PROVE parity
> against the SCOPE.md LOCKED parity-trim, fill the remaining LOCKED-ADD gaps, then publish the
> thin skeleton to cplugs **as `sptc`** (NOT `spt` — succession flip stays gated, see
> [[succession-flip-gated]] / RELEASE-RUNBOOK). Public-surface-only still binds (AGENTS.md).

## Operator decisions (locked this session, 2026-06-15)
1. **Slice = audit + fill gaps + publish.** Build the absent LOCKED-ADD surface (subnet skills,
   ccs profiles), reconcile whoami, THEN publish as sptc. `new-alarm` stays **deferred**
   (out-of-scope v1, no core alarm primitive — doyle finding; the dead stub gets removed, not built).
2. **Drop the `/whoami` SKILL.** Honor the LOCKED SCOPE decision — core `spt whoami` stays
   (becomes the `endpoint list` alias in M12); the adapter ships no whoami skill.

## Parity matrix (current vs SCOPE LOCKED parity-trim)

| Legacy / target surface        | SCOPE disposition        | Adapter today          | Action (this slice)            |
|--------------------------------|--------------------------|------------------------|--------------------------------|
| /live /commune /ready /send    | KEEP                     | operative (file-backed)| ✓ none                         |
| /list-agents /signoff /force-stop | KEEP                  | operative              | ✓ none                         |
| /new-alarm                     | KEEP (but no core prim.) | dead stub, no manifest | **remove stub** (deferred)     |
| /spt:setup /spt:version        | ADD                      | operative              | ✓ none                         |
| profiles + strings + hints     | ADD                      | wired (deep/live)      | ✓ none                         |
| **subnet skills (create/join/show-code)** | ADD (LOCKED)  | **ABSENT**             | **BUILD** (Phase C)            |
| **ccs profiles (claude-spt:glm …)** | ADD (LOCKED)        | **ABSENT**             | **BUILD** (Phase D)            |
| /whoami SKILL                  | DROP skill               | **operative (violation)** | **REMOVE** (Phase B)        |
| /fork /amend-signoff /revive /clear-psyche | DROP         | absent                 | ✓ none (confirm absent)        |

Public surface confirms the ADD gaps are buildable: `spt subnet {create,join,show-code,status}`
exists; ccs = profile overlays retargeting command templates (SCOPE §ccs, profile *templates*
only — user supplies their own ccs config/keys).

## Phases (sequence; one commit + green gate each)

### Phase A — Parity audit artifact + REQ seed
- Write `docs/PARITY.md` (the matrix above + per-row evidence/finding) — the "proven parity"
  record the succession flip gates on. Tag `<!-- doc->REQ-PARITY-AUDIT -->`.
- Seed REQs in `traceable-reqs.toml` FIRST (before building): `REQ-SKILL-SUBNET`,
  `REQ-CCS-PROFILES`, `REQ-PARITY-AUDIT`. Activate stages as each phase lands.
- Confirm the DROP set is truly absent (grep no fork/revive/clear-psyche/amend-signoff residue).

### Phase B — Drop the whoami skill (honor LOCKED)
- Remove `whoami = { file = "skills/whoami.md" }` from manifest `[strings.skills]`.
- Delete `adapter/strings/skills/whoami.md` + `plugin/sptc/skills/whoami/`.
- **registration-int.sh** uses `skills.whoami` as the canonical file-backed + UPS-injection
  example (4b/4c) — repoint to a surviving skill (`skills.ready`).
- Redirect the `who am i` **hint** (currently → /sptc:whoami) to core `spt whoami` guidance.
- Audit REQ evidence: nothing is tagged `REQ-*` on whoami.md directly, but check
  `tests/hooks-parse.sh` / manifest-shortcut for whoami coupling before deleting.

### Phase C — Subnet skills (LOCKED ADD; public verbs exist)
- **Open shape decision (resolve at top of phase):** three skills
  (`/sptc:subnet-create|join|show-code`) vs one routing `/sptc:subnet` skill. Lean: match SCOPE's
  enumeration but as ONE `subnet` skill body that documents create/join/show-code (fewer
  surfaces, cleaner UX) — confirm before authoring.
- File-backed body(ies) `adapter/strings/skills/subnet*.md` (run `spt how-to subnet` / wrap the
  real `spt subnet …` verbs); manifest `[strings.skills]` entries; thin SKILL.md stub(s); hint(s).
- `REQ-SKILL-SUBNET` [doc,impl] (+ int via registration-int file-backed-resolve assertion).

### Phase D — ccs profiles (LOCKED ADD)
- **Research first** (mcp/context7 not needed — read SCOPE §ccs + the existing `[profiles.*]`
  command seams). ccs profile = a `[profiles.<glm|kimi>]` overlay that leaf-replaces the spawn /
  psyche / echo command templates to route through `ccs <profile> …`, with a per-profile `~/.ccs`
  log dir. Author profile *templates* only (no user keys).
- Add `[profiles.glm]` (+ maybe `[profiles.kimi]`) overlays; observable via `spt adapter
  get-string claude-spt:glm …` + resolves in registration-int (mirror the :deep/:live assertions).
- `REQ-CCS-PROFILES` [doc,impl] (+ int).

### Phase E — Publish prep (as sptc; NO name flip)
- **Create `CHANGELOG.md`** with a `## [<version>] - 2026-06-15` section (Added/Changed/Fixed,
  user-facing UX only, no internal lingo) — release fails loudly without it.
- **Bump `plugin.json`** `version` 0.0.1 → first real (structural first publish). Keep `name=sptc`.
- Run `sh ci/publish/validate-skeleton.sh` + `sh ci/publish/package-skeleton.sh` (DRY-RUN) green.
- Regenerate docs (mdBook + llms.txt) — drift gate green.
- The actual cplugs commit/push + `claude plugin install sptc@cplugs` stays the **operator's**
  step (credentials + pointer flip) — plan stops at a validated dry-run + a publish checklist.

### Gate (every phase)
`sh ci/run-gates.sh` PASS + `traceable-reqs check` green. Commit `Co-authored by: perri`.

## Open questions (resolve in-phase, don't block planning)
- Subnet skill shape: 3 skills vs 1 routing skill (Phase C top).
- ccs: which profiles ship as templates (glm only, or glm+kimi)? SCOPE names `claude-spt:glm`.
- plugin.json first-publish version number (0.1.0? matches adapter manifest 0.1.0, but RUNBOOK
  says keep them DISTINCT — pick independently).

## Findings carried (not ours to fix)
- new-alarm core alarm/deferred-pulse primitive (blocks new-alarm parity) — doyle.
- hard/no-grace force-stop primitive — doyle.
- Live relay int held for spt-core M11 counter-15 release ([[live-int-gated-m11-counter15]]).

## Done immediately before (context)
- 4b042cc live sub-slice (claude-spt-psyche runner + /sptc:live body, REQ-SKILL-LIVE [doc,impl,unit]).
- 692b67c F-004 flip (digest-proof int GREEN on v0.7.2, REQ-DIST-DIGEST-EXTRACTOR full).
- spt upgraded 0.7.1 → 0.7.2 (operator-approved daemon bounce).

-   ccs profiles), reconcile whoami, THEN publish as sptc. `new-alarm` stays **deferred**

-   (out-of-scope v1, no core alarm primitive — doyle finding; the dead stub gets removed, not built).

+   ccs profiles), reconcile whoami, THEN publish as sptc. `new-alarm` stays **deferred** — doyle

+   TRIAGED it (high-prio 2026-06-15) as an **ACCEPTED parity gap = "deferred-pending-arrangement-

+   system", NOT missing**: spt-core has only the alarm event shape + relay handling + a test

+   fixture; the durable timer is a deliberate deferral (DEFERRED.md:43, ADR-0018 Q4/V3), and the

+   operator has a superseding "arrangement" system in mind. **Do NOT build an adapter-side stopgap

+   timer** (throwaway vs the coming model), **no REQ minted**. Remove the dead stub (deferred ≠

+   shipping a no-op skill); doyle loops me when the arrangement design surfaces.


| /new-alarm                     | KEEP (but no core prim.) | dead stub, no manifest | **remove stub** (deferred)     |

| /new-alarm                     | ACCEPTED GAP (deferred-pending-arrangement-system) | dead stub, no manifest | **remove stub**; no REQ, no build |


# JIT plan — parity audit → fill LOCKED-ADD gaps → cplugs publish (as sptc)

> Next slice after the live sub-slice (4b042cc) + F-004 flip (692b67c). Goal: PROVE parity
> against the SCOPE.md LOCKED parity-trim, fill the remaining LOCKED-ADD gaps, then publish the
> thin skeleton to cplugs **as `sptc`** (NOT `spt` — succession flip stays gated, see
> [[succession-flip-gated]] / RELEASE-RUNBOOK). Public-surface-only still binds (AGENTS.md).

## Operator decisions (locked this session, 2026-06-15)
1. **Slice = audit + fill gaps + publish.** Build the absent LOCKED-ADD surface (subnet skills,
   ccs profiles), reconcile whoami, THEN publish as sptc. `new-alarm` stays **deferred** — doyle
   TRIAGED it (high-prio 2026-06-15) as an **ACCEPTED parity gap = "deferred-pending-arrangement-
   system", NOT missing**: spt-core has only the alarm event shape + relay handling + a test
   fixture; the durable timer is a deliberate deferral (DEFERRED.md:43, ADR-0018 Q4/V3), and the
   operator has a superseding "arrangement" system in mind. **Do NOT build an adapter-side stopgap
   timer** (throwaway vs the coming model), **no REQ minted**. Remove the dead stub (deferred ≠
   shipping a no-op skill); doyle loops me when the arrangement design surfaces.
2. **Drop the `/whoami` SKILL.** Honor the LOCKED SCOPE decision — core `spt whoami` stays
   (becomes the `endpoint list` alias in M12); the adapter ships no whoami skill.

## Parity matrix (current vs SCOPE LOCKED parity-trim)

| Legacy / target surface        | SCOPE disposition        | Adapter today          | Action (this slice)            |
|--------------------------------|--------------------------|------------------------|--------------------------------|
| /live /commune /ready /send    | KEEP                     | operative (file-backed)| ✓ none                         |
| /list-agents /signoff /force-stop | KEEP                  | operative              | ✓ none                         |
| /new-alarm                     | KEEP (but no core prim.) | dead stub, no manifest | **remove stub** (deferred)     |
| /spt:setup /spt:version        | ADD                      | operative              | ✓ none                         |
| profiles + strings + hints     | ADD                      | wired (deep/live)      | ✓ none                         |
| **subnet skills (create/join/show-code)** | ADD (LOCKED)  | **ABSENT**             | **BUILD** (Phase C)            |
| **ccs profiles (claude-spt:glm …)** | ADD (LOCKED)        | **ABSENT**             | **BUILD** (Phase D)            |
| /whoami SKILL                  | DROP skill               | **operative (violation)** | **REMOVE** (Phase B)        |
| /fork /amend-signoff /revive /clear-psyche | DROP         | absent                 | ✓ none (confirm absent)        |

Public surface confirms the ADD gaps are buildable: `spt subnet {create,join,show-code,status}`
exists; ccs = profile overlays retargeting command templates (SCOPE §ccs, profile *templates*
only — user supplies their own ccs config/keys).

## Phases (sequence; one commit + green gate each)

### Phase A — Parity audit artifact + REQ seed
- Write `docs/PARITY.md` (the matrix above + per-row evidence/finding) — the "proven parity"
  record the succession flip gates on. Tag `<!-- doc->REQ-PARITY-AUDIT -->`.
- Seed REQs in `traceable-reqs.toml` FIRST (before building): `REQ-SKILL-SUBNET`,
  `REQ-CCS-PROFILES`, `REQ-PARITY-AUDIT`. Activate stages as each phase lands.
- Confirm the DROP set is truly absent (grep no fork/revive/clear-psyche/amend-signoff residue).

### Phase B — Drop the whoami skill (honor LOCKED)
- Remove `whoami = { file = "skills/whoami.md" }` from manifest `[strings.skills]`.
- Delete `adapter/strings/skills/whoami.md` + `plugin/sptc/skills/whoami/`.
- **registration-int.sh** uses `skills.whoami` as the canonical file-backed + UPS-injection
  example (4b/4c) — repoint to a surviving skill (`skills.ready`).
- Redirect the `who am i` **hint** (currently → /sptc:whoami) to core `spt whoami` guidance.
- Audit REQ evidence: nothing is tagged `REQ-*` on whoami.md directly, but check
  `tests/hooks-parse.sh` / manifest-shortcut for whoami coupling before deleting.

### Phase C — Subnet skills (LOCKED ADD; public verbs exist)
- **Open shape decision (resolve at top of phase):** three skills
  (`/sptc:subnet-create|join|show-code`) vs one routing `/sptc:subnet` skill. Lean: match SCOPE's
  enumeration but as ONE `subnet` skill body that documents create/join/show-code (fewer
  surfaces, cleaner UX) — confirm before authoring.
- File-backed body(ies) `adapter/strings/skills/subnet*.md` (run `spt how-to subnet` / wrap the
  real `spt subnet …` verbs); manifest `[strings.skills]` entries; thin SKILL.md stub(s); hint(s).
- `REQ-SKILL-SUBNET` [doc,impl] (+ int via registration-int file-backed-resolve assertion).

### Phase D — ccs profiles (LOCKED ADD)
- **Research first** (mcp/context7 not needed — read SCOPE §ccs + the existing `[profiles.*]`
  command seams). ccs profile = a `[profiles.<glm|kimi>]` overlay that leaf-replaces the spawn /
  psyche / echo command templates to route through `ccs <profile> …`, with a per-profile `~/.ccs`
  log dir. Author profile *templates* only (no user keys).
- Add `[profiles.glm]` (+ maybe `[profiles.kimi]`) overlays; observable via `spt adapter
  get-string claude-spt:glm …` + resolves in registration-int (mirror the :deep/:live assertions).
- `REQ-CCS-PROFILES` [doc,impl] (+ int).

### Phase E — Publish prep (as sptc; NO name flip)
- **Create `CHANGELOG.md`** with a `## [<version>] - 2026-06-15` section (Added/Changed/Fixed,
  user-facing UX only, no internal lingo) — release fails loudly without it.
- **Bump `plugin.json`** `version` 0.0.1 → first real (structural first publish). Keep `name=sptc`.
- Run `sh ci/publish/validate-skeleton.sh` + `sh ci/publish/package-skeleton.sh` (DRY-RUN) green.
- Regenerate docs (mdBook + llms.txt) — drift gate green.
- The actual cplugs commit/push + `claude plugin install sptc@cplugs` stays the **operator's**
  step (credentials + pointer flip) — plan stops at a validated dry-run + a publish checklist.

### Gate (every phase)
`sh ci/run-gates.sh` PASS + `traceable-reqs check` green. Commit `Co-authored by: perri`.

## Open questions (resolve in-phase, don't block planning)
- Subnet skill shape: 3 skills vs 1 routing skill (Phase C top).
- ccs: which profiles ship as templates (glm only, or glm+kimi)? SCOPE names `claude-spt:glm`.
- plugin.json first-publish version number (0.1.0? matches adapter manifest 0.1.0, but RUNBOOK
  says keep them DISTINCT — pick independently).

## Findings carried (not ours to fix)
- new-alarm core alarm/deferred-pulse primitive (blocks new-alarm parity) — doyle.
- hard/no-grace force-stop primitive — doyle.
- Live relay int held for spt-core M11 counter-15 release ([[live-int-gated-m11-counter15]]).

## Done immediately before (context)
- 4b042cc live sub-slice (claude-spt-psyche runner + /sptc:live body, REQ-SKILL-LIVE [doc,impl,unit]).
- 692b67c F-004 flip (digest-proof int GREEN on v0.7.2, REQ-DIST-DIGEST-EXTRACTOR full).
- spt upgraded 0.7.1 → 0.7.2 (operator-approved daemon bounce).

-| /new-alarm                     | KEEP (but no core prim.) | dead stub, no manifest | **remove stub** (deferred)     |

+| /new-alarm                     | ACCEPTED GAP (deferred-pending-arrangement-system) | dead stub, no manifest | **remove stub**; no REQ, no build |


Plan complete, doyle's ruling folded in. Operator said proceed — executing **Phase A** (audit artifact + REQ seed). First, verify DROP set absent + whoami coupling before writing.


Bash

cd C:/Users/decid/Documents/projects/spt-claude-code && echo "===DROP residue (should be empty)===" && grep -rniE 'fork|revive|clear-psyche|amend-signoff' adapter plugin --include=*.toml --include=*.md --include=*.json --include=*.sh 2>/dev/null | grep -viE 'fork.*bomb|workflow' | head; echo "===whoami coupling outside its own files===" && grep -rni 'whoami' adapter plugin ci tests --include=*.sh --include=*.toml --include=*.json 2>/dev/null | grep -vi 'skills/whoami\|skills.whoami\|/whoami report\|skills/whoami.md' | head -20

Check DROP residue + whoami coupling


===DROP residue (should be empty)===
adapter/claude-spt.toml:160:# `deep` widens the digest window for long-context review sessions without forking the base, and
adapter/manifest.schema.json:623:          "description": "Long-running wake-watcher run WHILE offline; exit ⇒ revive.",
===whoami coupling outside its own files===
adapter/claude-spt.toml:235:text = "Tip: /sptc:whoami reports this session's spt endpoint id."
plugin/sptc/hooks/session-start.sh:4:# CC), or boundary-rebind (clear/compact) — and persist session env so whoami + per-prompt hooks
plugin/sptc/hooks/session-start.sh:39:# Persist for the rest of the session (whoami + per-prompt hooks read these).
plugin/sptc/hooks/user-prompt-submit.sh:17:# 1. Skill-injection runs BEFORE the perch check — skills like /sptc:whoami and /sptc:setup are
plugin/sptc/hooks/_common.sh:3:# DRAFT (pending throwaway-CC validation: Windows-shell, env-file timing, whoami-in-hook).
plugin/sptc/hooks/_common.sh:44:# Resolve this session's perch id via spt whoami (off $OWL_SESSION_ID / $SPT_AGENT_ID).
plugin/sptc/hooks/_common.sh:48:  OWL_SESSION_ID="${OWL_SESSION_ID:-$_sid}" "$_spt" whoami 2>/dev/null | head -n1
ci/manifest/registration-int.sh:65:  inj=$(sptc_inject_skill "$(sptc_skill_key '/sptc:whoami report me')")
ci/manifest/registration-int.sh:67:    '<sptc_skill name="whoami">'*'# /sptc:whoami'*'</sptc_skill>'*) exit 0 ;;
ci/manifest/registration-int.sh:69:  esac ) && ok "UPS skill-injection: /sptc:whoami -> wrapped body" || bad "skill-injection did not emit wrapped body"
tests/hooks-parse.sh:50:check "skill_key bare"        "whoami" "$(sptc_skill_key '/sptc:whoami')"
tests/hooks-parse.sh:54:check "skill_key mid-prose"   ""       "$(sptc_skill_key 'please run /sptc:whoami for me')"
tests/hooks-parse.sh:55:check "skill_key other ns"    ""       "$(sptc_skill_key '/other:whoami')"
tests/hooks-parse.sh:56:check "skill_key not a cmd"   ""       "$(sptc_skill_key 'whoami am i')"


DROP set clean (the 2 hits are legit prose/schema). whoami coupling is limited + no REQ evidence rides it. Good.

**Phase A** — seed REQs first (per traceability rule), then write the audit doc.


Edit

[[requirements]]
id = "REQ-SKILL-VERSION"

[[requirements]]
id = "REQ-PARITY-AUDIT"
title = "The adapter's operative skill/profile surface is reconciled against the SCOPE.md LOCKED parity-trim (KEEP/ADD/DROP/TRANSFORM): every divergence is either resolved or logged as an accepted gap in docs/PARITY.md — the 'proven parity' record the sptc->spt succession flip gates on"
required_stages = ["doc"]  # doc: docs/PARITY.md (the parity matrix + per-row evidence/finding). Pure reconciliation record (no code of its own); the per-surface impl/unit evidence lives on the surface's own REQ (REQ-SKILL-SUBNET, REQ-CCS-PROFILES, REQ-SKILL-LIVE, etc.).

[[requirements]]
id = "REQ-SKILL-SUBNET"
title = "The adapter ships subnet membership skill(s) (create/join/show-code) wrapping the published `spt subnet <verb>` surface — the LOCKED-ADD cross-subnet entrypoint (v1 topology is mandatory, SCOPE)"
required_stages = []  # activate [doc,impl] when Phase C starts. doc: adapter/strings/skills/subnet*.md. impl: [strings.skills] entries + plugin stub(s). int: registration-int file-backed-resolve assertion (mirrors :live).

[[requirements]]
id = "REQ-CCS-PROFILES"
title = "The adapter ships ccs profile template(s) (claude-spt:glm …) — LOCKED-ADD overlays that retarget the spawn/psyche/echo command templates through `ccs <profile>` with a per-profile ~/.ccs log dir; templates only (user supplies their own ccs config/keys)"
required_stages = []  # activate [doc,impl] when Phase D starts. impl: [profiles.<glm>] overlay(s) in claude-spt.toml. int: registration-int profile-resolves assertion (mirrors :deep/:live).

[[requirements]]
id = "REQ-SKILL-VERSION"


<owl_messages>
<owl_message from="doyle" priority="high">[doyle -> perri] Ack — framing is right. Pull the no-op /sptc:new-alarm stub from the published surface: deferred != ship a dead skill, agreed. "deferred-pending-arrangement-system" in PARITY.md is the correct disposition. I loop you when the arrangement design surfaces. Carry on.</owl_message>
</owl_messages>

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


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-PARITY-AUDIT"
title = "The adapter's operative skill/profile surface is reconciled against the SCOPE.md LOCKED parity-trim (KEEP/ADD/DROP/TRANSFORM): every divergence is either resolved or logged as an accepted gap in docs/PARITY.md — the 'proven parity' record the sptc->spt succession flip gates on"
required_stages = ["doc"]  # doc: docs/PARITY.md (the parity matrix + per-row evidence/finding). Pure reconciliation record (no code of its own); the per-surface impl/unit evidence lives on the surface's own REQ (REQ-SKILL-SUBNET, REQ-CCS-PROFILES, REQ-SKILL-LIVE, etc.).

[[requirements]]
id = "REQ-SKILL-SUBNET"
title = "The adapter ships subnet membership skill(s) (create/join/show-code) wrapping the published `spt subnet <verb>` surface — the LOCKED-ADD cross-subnet entrypoint (v1 topology is mandatory, SCOPE)"
required_stages = []  # activate [doc,impl] when Phase C starts. doc: adapter/strings/skills/subnet*.md. impl: [strings.skills] entries + plugin stub(s). int: registration-int file-backed-resolve assertion (mirrors :live).

[[requirements]]
id = "REQ-CCS-PROFILES"
title = "The adapter ships ccs profile template(s) (claude-spt:glm …) — LOCKED-ADD overlays that retarget the spawn/psyche/echo command templates through `ccs <profile>` with a per-profile ~/.ccs log dir; templates only (user supplies their own ccs config/keys)"
required_stages = []  # activate [doc,impl] when Phase D starts. impl: [profiles.<glm>] overlay(s) in claude-spt.toml. int: registration-int profile-resolves assertion (mirrors :deep/:live).

[[requirements]]
id = "REQ-SKILL-VERSION"

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.

+title = "The adapter's operative skill/profile surface is reconciled against the SCOPE.md LOCKED parity-trim (KEEP/ADD/DROP/TRANSFORM): every divergence is either resolved or logged as an accepted gap in docs/PARITY.md — the 'proven parity' record the sptc->spt succession flip gates on"

+required_stages = ["doc"]  # doc: docs/PARITY.md (the parity matrix + per-row evidence/finding). Pure reconciliation record (no code of its own); the per-surface impl/unit evidence lives on the surface's own REQ (REQ-SKILL-SUBNET, REQ-CCS-PROFILES, REQ-SKILL-LIVE, etc.).

+title = "The adapter ships subnet membership skill(s) (create/join/show-code) wrapping the published `spt subnet <verb>` surface — the LOCKED-ADD cross-subnet entrypoint (v1 topology is mandatory, SCOPE)"

+required_stages = []  # activate [doc,impl] when Phase C starts. doc: adapter/strings/skills/subnet*.md. impl: [strings.skills] entries + plugin stub(s). int: registration-int file-backed-resolve assertion (mirrors :live).

+title = "The adapter ships ccs profile template(s) (claude-spt:glm …) — LOCKED-ADD overlays that retarget the spawn/psyche/echo command templates through `ccs <profile>` with a per-profile ~/.ccs log dir; templates only (user supplies their own ccs config/keys)"

+required_stages = []  # activate [doc,impl] when Phase D starts. impl: [profiles.<glm>] overlay(s) in claude-spt.toml. int: registration-int profile-resolves assertion (mirrors :deep/:live).

title = "/spt:version reports the spt-core-tracked manifest/binary version (the version-of-truth, not the marketplace skeleton version)"


doyle confirms — carry on. Brief ack, then continuing.


Bash

cd C:/Users/decid/Documents/projects/spt-claude-code && $OWL send doyle <<'EOF'
[perri → doyle] 👍 stub pull + "deferred-pending-arrangement-system" locked. Carrying on — writing docs/PARITY.md now.
EOF

Brief ack to doyle


Now the audit doc.


Write

# Parity audit — claude-spt vs the legacy owl surface

> The reconciliation record: the adapter's operative surface measured against the **LOCKED
> parity-trim** in `SCOPE.md` (KEEP / ADD / DROP / TRANSFORM). This is the **"proven parity"**
> gate the `sptc`→`spt` succession flip depends on (see `docs/RELEASE-RUNBOOK.md` +
> `docs/KNOWN-HAZARDS.md`): the name flip happens only once this audit shows no unresolved
> divergence AND legacy owl is retired. Audited 2026-06-15 against published spt v0.7.2.
>
> Public-surface-only binds (AGENTS.md): a capability missing from the published `spt` surface is
> a **finding**, never a reason to reach into spt-core source.

<!-- [doc->REQ-PARITY-AUDIT] -->

## Disposition matrix

| Surface | SCOPE bucket | Adapter today | Status |
|---|---|---|---|
| `/live` | KEEP | operative — `skills/live.md` (`:live` profile + Monitor relay) | ✅ parity |
| `/commune` | KEEP | operative — `skills/commune.md` (file-drop) | ✅ parity |
| `/ready` | KEEP | operative — `skills/ready.md` | ✅ parity |
| `/send` | KEEP | operative — `skills/send.md` | ✅ parity |
| `/list-agents` | KEEP (→ `spt endpoint list`) | operative — `skills/list-agents.md` | ✅ parity |
| `/signoff` | KEEP (harness-hosted v1) | operative — `skills/signoff.md` | ✅ parity |
| `/force-stop` | KEEP (topology-aware) | operative — `skills/force-stop.md` | ✅ parity |
| `/spt:setup` | ADD | operative — `skills/setup.md` | ✅ parity |
| `/spt:version` | ADD | operative — `skills/version.md` | ✅ parity |
| profiles + strings + hints | ADD | wired (`:deep`, `:live`, `[strings]`, `[[hints]]`) | ✅ parity |
| **subnet skills (create/join/show-code)** | ADD (LOCKED) | **absent** | 🔨 BUILD (REQ-SKILL-SUBNET) |
| **ccs profiles (`claude-spt:glm` …)** | ADD (LOCKED) | **absent** | 🔨 BUILD (REQ-CCS-PROFILES) |
| `/whoami` skill | **DROP** (core `spt whoami` stays → M12 `endpoint list` alias) | operative (carry-over) | ⛔ REMOVE |
| `/new-alarm` | accepted gap | dead stub, no manifest entry | ⏸ DEFER + remove stub |
| `/fork` | DROP | absent | ✅ confirmed absent |
| `/amend-signoff` | DROP | absent | ✅ confirmed absent |
| `/revive` | DROP (daemon owns liveness) | absent | ✅ confirmed absent |
| `/clear-psyche` | DROP (= delete+recreate endpoint) | absent | ✅ confirmed absent |

TRANSFORM bucket (already realised, not skills): capsule→spt-hosted+`cc` (`[session.self]` +
`shortcut_basename="cc"`); echo-commune→`[digest]` extractor (`claude-spt-digest`); psyche-sync→
`/spt:setup`; doctor→setup-verify; working-perches→`api worker-start/stop` hooks. ✅

## Open divergences and dispositions

### subnet skills — LOCKED-ADD gap → BUILD
The published surface carries the verbs (`spt subnet {create,join,show-code,status,…}`), and v1
topology scope is **mandatory** (operator vetoed harness-hosted-only — cross-subnet/PTY proof is
spt-core's central value prop, SCOPE). So this is a real gap to fill, not defer. → `REQ-SKILL-SUBNET`.

### ccs profiles — LOCKED-ADD gap → BUILD
SCOPE §ccs (LOCKED): ccs ships as **profile templates** under the adapter (`claude-spt:glm`, …)
that retarget the spawn/psyche/echo command templates through `ccs <profile>`, with a per-profile
`~/.ccs` log dir. Templates only — the user supplies their own ccs config/keys. → `REQ-CCS-PROFILES`.

### `/whoami` skill — DROP (honoring LOCKED)
SCOPE DROPs the whoami **skill**; core `spt whoami` stays in the hot path (and becomes the
`endpoint list` alias in M12). The adapter currently still ships a whoami skill (a carry-over from
an early slice). Operator confirmed the drop 2026-06-15. Action: remove `[strings.skills].whoami` +
the file body + the plugin stub; redirect the "who am i" hint to core `spt whoami`. No REQ rides
whoami evidence (the file-backed-string + UPS-injection int examples repoint to `skills.ready`;
the hook-parser unit examples repoint to a surviving skill name). Core `spt whoami` calls in the
hooks (`session-start.sh`, `_common.sh`) are the CLI, not the skill — they STAY.

### `/new-alarm` — ACCEPTED GAP (deferred-pending-arrangement-system)
**Not missing — deliberately deferred.** doyle triage (2026-06-15): spt-core carries only the
alarm *event shape* (spt-proto) + relay handling (psyrelay.rs) + a test fixture; the durable
"fire-at-target-time" timer still lives in legacy owl in-memory, and an in-daemon durable scheduler
is a deliberate deferral (DEFERRED.md:43, ADR-0018 Q4/V3 — deferred because the daemon had no
one-shot consumer; building pre-consumer = untested dead code). The **operator has a superseding
"arrangement" system** in mind that would replace the alarm port. Disposition: **do not build an
adapter-side stopgap timer** (throwaway vs the coming model); **no REQ minted**; **remove the dead
`/sptc:new-alarm` stub** from the published surface (deferred ≠ shipping a no-op skill). doyle loops
me when the arrangement design surfaces.

## Parity verdict
**Not yet proven.** Two LOCKED-ADD surfaces (subnet skills, ccs profiles) are unbuilt and one DROP
(whoami skill) is unhonored. Parity is proven — and the succession flip unlocked — once those three
close (this slice) with the rest already ✅. `/new-alarm` is an accepted, documented gap and does
**not** block the parity verdict.


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

# Parity audit — claude-spt vs the legacy owl surface

> The reconciliation record: the adapter's operative surface measured against the **LOCKED
> parity-trim** in `SCOPE.md` (KEEP / ADD / DROP / TRANSFORM). This is the **"proven parity"**
> gate the `sptc`→`spt` succession flip depends on (see `docs/RELEASE-RUNBOOK.md` +
> `docs/KNOWN-HAZARDS.md`): the name flip happens only once this audit shows no unresolved
> divergence AND legacy owl is retired. Audited 2026-06-15 against published spt v0.7.2.
>
> Public-surface-only binds (AGENTS.md): a capability missing from the published `spt` surface is
> a **finding**, never a reason to reach into spt-core source.

<!-- [doc->REQ-PARITY-AUDIT] -->

## Disposition matrix

| Surface | SCOPE bucket | Adapter today | Status |
|---|---|---|---|
| `/live` | KEEP | operative — `skills/live.md` (`:live` profile + Monitor relay) | ✅ parity |
| `/commune` | KEEP | operative — `skills/commune.md` (file-drop) | ✅ parity |
| `/ready` | KEEP | operative — `skills/ready.md` | ✅ parity |
| `/send` | KEEP | operative — `skills/send.md` | ✅ parity |
| `/list-agents` | KEEP (→ `spt endpoint list`) | operative — `skills/list-agents.md` | ✅ parity |
| `/signoff` | KEEP (harness-hosted v1) | operative — `skills/signoff.md` | ✅ parity |
| `/force-stop` | KEEP (topology-aware) | operative — `skills/force-stop.md` | ✅ parity |
| `/spt:setup` | ADD | operative — `skills/setup.md` | ✅ parity |
| `/spt:version` | ADD | operative — `skills/version.md` | ✅ parity |
| profiles + strings + hints | ADD | wired (`:deep`, `:live`, `[strings]`, `[[hints]]`) | ✅ parity |
| **subnet skills (create/join/show-code)** | ADD (LOCKED) | **absent** | 🔨 BUILD (REQ-SKILL-SUBNET) |
| **ccs profiles (`claude-spt:glm` …)** | ADD (LOCKED) | **absent** | 🔨 BUILD (REQ-CCS-PROFILES) |
| `/whoami` skill | **DROP** (core `spt whoami` stays → M12 `endpoint list` alias) | operative (carry-over) | ⛔ REMOVE |
| `/new-alarm` | accepted gap | dead stub, no manifest entry | ⏸ DEFER + remove stub |
| `/fork` | DROP | absent | ✅ confirmed absent |
| `/amend-signoff` | DROP | absent | ✅ confirmed absent |
| `/revive` | DROP (daemon owns liveness) | absent | ✅ confirmed absent |
| `/clear-psyche` | DROP (= delete+recreate endpoint) | absent | ✅ confirmed absent |

TRANSFORM bucket (already realised, not skills): capsule→spt-hosted+`cc` (`[session.self]` +
`shortcut_basename="cc"`); echo-commune→`[digest]` extractor (`claude-spt-digest`); psyche-sync→
`/spt:setup`; doctor→setup-verify; working-perches→`api worker-start/stop` hooks. ✅

## Open divergences and dispositions

### subnet skills — LOCKED-ADD gap → BUILD
The published surface carries the verbs (`spt subnet {create,join,show-code,status,…}`), and v1
topology scope is **mandatory** (operator vetoed harness-hosted-only — cross-subnet/PTY proof is
spt-core's central value prop, SCOPE). So this is a real gap to fill, not defer. → `REQ-SKILL-SUBNET`.

### ccs profiles — LOCKED-ADD gap → BUILD
SCOPE §ccs (LOCKED): ccs ships as **profile templates** under the adapter (`claude-spt:glm`, …)
that retarget the spawn/psyche/echo command templates through `ccs <profile>`, with a per-profile
`~/.ccs` log dir. Templates only — the user supplies their own ccs config/keys. → `REQ-CCS-PROFILES`.

### `/whoami` skill — DROP (honoring LOCKED)
SCOPE DROPs the whoami **skill**; core `spt whoami` stays in the hot path (and becomes the
`endpoint list` alias in M12). The adapter currently still ships a whoami skill (a carry-over from
an early slice). Operator confirmed the drop 2026-06-15. Action: remove `[strings.skills].whoami` +
the file body + the plugin stub; redirect the "who am i" hint to core `spt whoami`. No REQ rides
whoami evidence (the file-backed-string + UPS-injection int examples repoint to `skills.ready`;
the hook-parser unit examples repoint to a surviving skill name). Core `spt whoami` calls in the
hooks (`session-start.sh`, `_common.sh`) are the CLI, not the skill — they STAY.

### `/new-alarm` — ACCEPTED GAP (deferred-pending-arrangement-system)
**Not missing — deliberately deferred.** doyle triage (2026-06-15): spt-core carries only the
alarm *event shape* (spt-proto) + relay handling (psyrelay.rs) + a test fixture; the durable
"fire-at-target-time" timer still lives in legacy owl in-memory, and an in-daemon durable scheduler
is a deliberate deferral (DEFERRED.md:43, ADR-0018 Q4/V3 — deferred because the daemon had no
one-shot consumer; building pre-consumer = untested dead code). The **operator has a superseding
"arrangement" system** in mind that would replace the alarm port. Disposition: **do not build an
adapter-side stopgap timer** (throwaway vs the coming model); **no REQ minted**; **remove the dead
`/sptc:new-alarm` stub** from the published surface (deferred ≠ shipping a no-op skill). doyle loops
me when the arrangement design surfaces.

## Parity verdict
**Not yet proven.** Two LOCKED-ADD surfaces (subnet skills, ccs profiles) are unbuilt and one DROP
(whoami skill) is unhonored. Parity is proven — and the succession flip unlocked — once those three
close (this slice) with the rest already ✅. `/new-alarm` is an accepted, documented gap and does
**not** block the parity verdict.


Phase A done. Gate + commit.


Bash

cd C:/Users/decid/Documents/projects/spt-claude-code && sh ci/run-gates.sh 2>&1 | tail -4 && traceable-reqs check >/dev/null 2>&1 && echo "trace OK" || echo "trace FAIL"

Gate + trace for Phase A


ok   mdbook build
ok   llms.txt in sync

=== RESULT: PASS ===
trace OK


cd C:/Users/decid/Documents/projects/spt-claude-code && git add -A && git commit -q -F - <<'EOF'
docs(parity): audit matrix + REQ seed (parity-publish slice Phase A)

Reconcile the operative surface against SCOPE.md's LOCKED parity-trim — the
"proven parity" record the sptc->spt succession flip gates on.

- docs/PARITY.md: disposition matrix (KEEP/ADD/DROP/TRANSFORM) + per-row status.
  Verdict: NOT YET proven — two LOCKED-ADD surfaces unbuilt (subnet skills, ccs
  profiles) + one DROP unhonored (whoami skill). new-alarm = accepted gap
  (deferred-pending-arrangement-system, doyle triage), does not block the verdict.
- traceable-reqs: seed REQ-PARITY-AUDIT [doc] (satisfied), REQ-SKILL-SUBNET +
  REQ-CCS-PROFILES (inactive — activate when their phases start).
- DROP set confirmed absent (no fork/revive/clear-psyche/amend-signoff residue).

PARITY-PUBLISH-PLAN.md: the JIT plan for this slice (audit -> fill gaps -> publish
as sptc; flip stays gated).

Gate PASS + traceable green.

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

Commit Phase A


b1db67f docs(parity): audit matrix + REQ seed (parity-publish slice Phase A)


Phase A committed (`b1db67f`). **Phase B** — drop the whoami skill. Manifest first.


Edit

whoami = { file = "skills/whoami.md" }
setup = { file = "skills/setup.md" }

# whoami SKILL dropped (SCOPE LOCKED parity-trim; operator-confirmed 2026-06-15): core `spt whoami`
# stays in the hot path (-> M12 `endpoint list` alias), so the adapter ships no whoami skill. The
# "who am i" hint below points at the core CLI. (docs/PARITY.md.)
setup = { file = "skills/setup.md" }


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)

whoami = { file = "skills/whoami.md" }
setup = { file = "skills/setup.md" }

# whoami SKILL dropped (SCOPE LOCKED parity-trim; operator-confirmed 2026-06-15): core `spt whoami`
# stays in the hot path (-> M12 `endpoint list` alias), so the adapter ships no whoami skill. The
# "who am i" hint below points at the core CLI. (docs/PARITY.md.)
setup = { file = "skills/setup.md" }

# resolved lazily at `spt adapter get-string` — proven vs live v0.7.0, F-003). File-backed so the

# manifest does not bloat (ADR-0001). Skills not yet authored keep a one-line inline summary until

# their body lands; converting an inline summary to a `{ file }` pointer is the per-skill increment.

+# whoami SKILL dropped (SCOPE LOCKED parity-trim; operator-confirmed 2026-06-15): core `spt whoami`

+# stays in the hot path (-> M12 `endpoint list` alias), so the adapter ships no whoami skill. The

+# "who am i" hint below points at the core CLI. (docs/PARITY.md.)


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

keywords = ["who am i", "my owl name", "my agent name", "my endpoint id"]
text = "Tip: run `spt whoami` to see this session's spt endpoint id."


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

keywords = ["who am i", "my owl name", "my agent name", "my endpoint id"]
text = "Tip: run `spt whoami` to see this session's spt endpoint id."

-text = "Tip: /sptc:whoami reports this session's spt endpoint id."

+text = "Tip: run `spt whoami` to see this session's spt endpoint id."

keywords = ["message another agent", "send to agent", "tell the other agent"]


Delete the whoami body + stub dir.


Bash

cd C:/Users/decid/Documents/projects/spt-claude-code && rm -f adapter/strings/skills/whoami.md && rm -rf plugin/sptc/skills/whoami && echo "removed" && ls adapter/strings/skills/ && echo "---stubs---" && ls plugin/sptc/skills/

Delete whoami body + stub


removed
commune.md
force-stop.md
list-agents.md
live.md
ready.md
send.md
setup.md
signoff.md
version.md
---stubs---
commune
force-stop
list-agents
live
new-alarm
ready
send
setup
signoff
version


Remove the dead `new-alarm` stub too (doyle/operator: deferred ≠ ship no-op skill).


cd C:/Users/decid/Documents/projects/spt-claude-code && rm -rf plugin/sptc/skills/new-alarm && echo "new-alarm stub removed" && ls plugin/sptc/skills/

Remove new-alarm stub


new-alarm stub removed
commune
force-stop
list-agents
live
ready
send
setup
signoff
version


9 skills clean. Now repoint the whoami test/hook references. Read current registration-int 4b/4c.


48	# 4b. File-backed [strings] pointer resolves to FILE CONTENTS (not the table, not raw); an inline
49	#     sibling still prints as-is. Proves `{ file = "skills/<x>.md" }` over adapter/strings/ (F-003).
50	body=$(spt adapter get-string claude-spt skills.whoami 2>&1)
51	case "$body" in "# /sptc:whoami"*) ok "file-backed string resolves: skills.whoami -> body" ;; *) bad "skills.whoami not resolved to file body: '$(printf %.40s "$body")'" ;; esac
52	# Newly file-backed skills (commune = file-drop body; force-stop = endpoint shutdown body).
53	cbody=$(spt adapter get-string claude-spt skills.commune 2>&1)
54	case "$cbody" in "# /sptc:commune"*) ok "file-backed string resolves: skills.commune -> body" ;; *) bad "skills.commune not resolved to file body: '$(printf %.40s "$cbody")'" ;; esac
55	fbody=$(spt adapter get-string claude-spt "skills.force-stop" 2>&1)
56	case "$fbody" in "# /sptc:force-stop"*) ok "file-backed string resolves: skills.force-stop -> body" ;; *) bad "skills.force-stop not resolved to file body: '$(printf %.40s "$fbody")'" ;; esac
57	# `live` is now file-backed too (LiveAgent activation body; the inline summary was flipped once the
58	# :live profile + claude-spt-psyche runner landed — REQ-SKILL-LIVE).
59	lbody=$(spt adapter get-string claude-spt skills.live 2>&1)
60	case "$lbody" in "# /sptc:live"*) ok "file-backed string resolves: skills.live -> body" ;; *) bad "skills.live not resolved to file body: '$(printf %.40s "$lbody")'" ;; esac
61
62	# 4c. UPS skill-injection end-to-end: the hook helper resolves a /sptc:<skill> prompt to the wrapped
63	#     operative body via get-string (REQ-UPS-INJECTION impl, on the registered adapter).
64	( . "$ROOT/plugin/sptc/hooks/_common.sh"
65	  inj=$(sptc_inject_skill "$(sptc_skill_key '/sptc:whoami report me')")
66	  case "$inj" in
67	    '<sptc_skill name="whoami">'*'# /sptc:whoami'*'</sptc_skill>'*) exit 0 ;;
68	    *) printf 'inject got: %.60s\n' "$inj"; exit 1 ;;
69	  esac ) && ok "UPS skill-injection: /sptc:whoami -> wrapped body" || bad "skill-injection did not emit wrapped body"
70
71	# 4d. The spt-hosted bringup blocks ([session.self] + [env.SPT_ENDPOINT_ID]) cross-field-validate:

# 4b. File-backed [strings] pointer resolves to FILE CONTENTS (not the table, not raw); an inline
#     sibling still prints as-is. Proves `{ file = "skills/<x>.md" }` over adapter/strings/ (F-003).
body=$(spt adapter get-string claude-spt skills.whoami 2>&1)
case "$body" in "# /sptc:whoami"*) ok "file-backed string resolves: skills.whoami -> body" ;; *) bad "skills.whoami not resolved to file body: '$(printf %.40s "$body")'" ;; esac
# Newly file-backed skills (commune = file-drop body; force-stop = endpoint shutdown body).
cbody=$(spt adapter get-string claude-spt skills.commune 2>&1)
case "$cbody" in "# /sptc:commune"*) ok "file-backed string resolves: skills.commune -> body" ;; *) bad "skills.commune not resolved to file body: '$(printf %.40s "$cbody")'" ;; esac
fbody=$(spt adapter get-string claude-spt "skills.force-stop" 2>&1)
case "$fbody" in "# /sptc:force-stop"*) ok "file-backed string resolves: skills.force-stop -> body" ;; *) bad "skills.force-stop not resolved to file body: '$(printf %.40s "$fbody")'" ;; esac
# `live` is now file-backed too (LiveAgent activation body; the inline summary was flipped once the
# :live profile + claude-spt-psyche runner landed — REQ-SKILL-LIVE).
lbody=$(spt adapter get-string claude-spt skills.live 2>&1)
case "$lbody" in "# /sptc:live"*) ok "file-backed string resolves: skills.live -> body" ;; *) bad "skills.live not resolved to file body: '$(printf %.40s "$lbody")'" ;; esac

# 4c. UPS skill-injection end-to-end: the hook helper resolves a /sptc:<skill> prompt to the wrapped
#     operative body via get-string (REQ-UPS-INJECTION impl, on the registered adapter).
( . "$ROOT/plugin/sptc/hooks/_common.sh"
  inj=$(sptc_inject_skill "$(sptc_skill_key '/sptc:whoami report me')")
  case "$inj" in
    '<sptc_skill name="whoami">'*'# /sptc:whoami'*'</sptc_skill>'*) exit 0 ;;
    *) printf 'inject got: %.60s\n' "$inj"; exit 1 ;;
  esac ) && ok "UPS skill-injection: /sptc:whoami -> wrapped body" || bad "skill-injection did not emit wrapped body"

# 4d. The spt-hosted bringup blocks ([session.self] + [env.SPT_ENDPOINT_ID]) cross-field-validate:


Edit

body=$(spt adapter get-string claude-spt skills.whoami 2>&1)
case "$body" in "# /sptc:whoami"*) ok "file-backed string resolves: skills.whoami -> body" ;; *) bad "skills.whoami not resolved to file body: '$(printf %.40s "$body")'" ;; esac

body=$(spt adapter get-string claude-spt skills.ready 2>&1)
case "$body" in "# /sptc:ready"*) ok "file-backed string resolves: skills.ready -> body" ;; *) bad "skills.ready not resolved to file body: '$(printf %.40s "$body")'" ;; esac


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

body=$(spt adapter get-string claude-spt skills.ready 2>&1)
case "$body" in "# /sptc:ready"*) ok "file-backed string resolves: skills.ready -> body" ;; *) bad "skills.ready not resolved to file body: '$(printf %.40s "$body")'" ;; esac

#!/bin/sh
# Integration proof for the claude-spt adapter manifest against a REAL spt-core (>= v0.7.0): the
# 2nd validation layer beyond JSON Schema — spt-core's `spt adapter add` cross-field registration —
# accepts the manifest, the shipped profile resolves, [strings] read through the registry, and the
# profile overlay is observable (base vs :deep differ). This is the v1 acceptance proof for the
# adapter manifest (LLM never in the loop; pure CLI assertions). [int->REQ-DIST-MANIFEST-SCHEMA]
#
# Mutates the node-local adapter registry (add + soft-remove), so it is gated behind SPTC_ACCEPTANCE=1
# and a present spt>=0.7.0 — never runs in the default `tests/*.sh` unit sweep. Idempotent: removes
# what it adds. Run: SPTC_ACCEPTANCE=1 sh ci/manifest/registration-int.sh   (exit 0 = pass).
set -u
ROOT=$(CDPATH= cd "$(dirname "$0")/../.." && pwd)
MANIFEST="$ROOT/adapter/claude-spt.toml"

if [ "${SPTC_ACCEPTANCE:-0}" != "1" ]; then echo "SKIP: set SPTC_ACCEPTANCE=1 to run (mutates the adapter registry)"; exit 0; fi
command -v spt >/dev/null 2>&1 || { echo "SKIP: no spt on PATH"; exit 0; }
ver=$(spt --version 2>/dev/null | awk '{print $NF}')
case "$ver" in
  0.6.*|0.5.*|0.4.*|0.3.*|0.2.*|0.1.*|0.0.*) echo "SKIP: spt $ver < 0.7.0 (shortcut_basename + adapter add are v0.7.0)"; exit 0 ;;
esac

fail=0
ok()  { echo "ok   $1"; }
bad() { echo "FAIL $1"; fail=1; }
# Clean up registry residue however we exit.
trap 'spt adapter remove claude-spt >/dev/null 2>&1 || true' EXIT INT TERM

# 1. Registration cross-field validation accepts the manifest.
out=$(spt adapter add "$MANIFEST" 2>&1)
case "$out" in *registered*) ok "spt adapter add: registered" ;; *) bad "adapter add rejected: $out" ;; esac

# 2. Listed, with the shipped profile resolved as a composite option.
list=$(spt adapter list 2>&1)
case "$list" in *claude-spt*) ok "listed: claude-spt active" ;; *) bad "claude-spt not listed" ;; esac
case "$list" in *claude-spt:deep*) ok "shipped profile resolves: claude-spt:deep" ;; *) bad "deep profile not resolved" ;; esac
# The LiveAgent overlay (:live adds [session.psyche_init]) — proves a profile [session] overlay
# validates + resolves on the live binary (the /sptc:live seam; doyle-grounded propagation).
case "$list" in *claude-spt:live*) ok "shipped profile resolves: claude-spt:live (LiveAgent overlay)" ;; *) bad "live profile not resolved" ;; esac

# 3. [strings] read through the registry (base value).
base=$(spt adapter get-string claude-spt adapter_label 2>&1)
[ "$base" = "Claude Code (spt)" ] && ok "strings resolve: adapter_label (base)" || bad "base adapter_label='$base'"

# 4. Profile overlay is observable: :deep leaf-replaces the base string.
deep=$(spt adapter get-string claude-spt:deep adapter_label 2>&1)
[ "$deep" = "Claude Code (spt, deep)" ] && ok "overlay observable: :deep adapter_label differs" || bad "deep adapter_label='$deep'"

# 4b. File-backed [strings] pointer resolves to FILE CONTENTS (not the table, not raw); an inline
#     sibling still prints as-is. Proves `{ file = "skills/<x>.md" }` over adapter/strings/ (F-003).
body=$(spt adapter get-string claude-spt skills.whoami 2>&1)
case "$body" in "# /sptc:whoami"*) ok "file-backed string resolves: skills.whoami -> body" ;; *) bad "skills.whoami not resolved to file body: '$(printf %.40s "$body")'" ;; esac
# Newly file-backed skills (commune = file-drop body; force-stop = endpoint shutdown body).
cbody=$(spt adapter get-string claude-spt skills.commune 2>&1)
case "$cbody" in "# /sptc:commune"*) ok "file-backed string resolves: skills.commune -> body" ;; *) bad "skills.commune not resolved to file body: '$(printf %.40s "$cbody")'" ;; esac
fbody=$(spt adapter get-string claude-spt "skills.force-stop" 2>&1)
case "$fbody" in "# /sptc:force-stop"*) ok "file-backed string resolves: skills.force-stop -> body" ;; *) bad "skills.force-stop not resolved to file body: '$(printf %.40s "$fbody")'" ;; esac
# `live` is now file-backed too (LiveAgent activation body; the inline summary was flipped once the
# :live profile + claude-spt-psyche runner landed — REQ-SKILL-LIVE).
lbody=$(spt adapter get-string claude-spt skills.live 2>&1)
case "$lbody" in "# /sptc:live"*) ok "file-backed string resolves: skills.live -> body" ;; *) bad "skills.live not resolved to file body: '$(printf %.40s "$lbody")'" ;; esac

# 4c. UPS skill-injection end-to-end: the hook helper resolves a /sptc:<skill> prompt to the wrapped
#     operative body via get-string (REQ-UPS-INJECTION impl, on the registered adapter).
( . "$ROOT/plugin/sptc/hooks/_common.sh"
  inj=$(sptc_inject_skill "$(sptc_skill_key '/sptc:whoami report me')")
  case "$inj" in
    '<sptc_skill name="whoami">'*'# /sptc:whoami'*'</sptc_skill>'*) exit 0 ;;
    *) printf 'inject got: %.60s\n' "$inj"; exit 1 ;;
  esac ) && ok "UPS skill-injection: /sptc:whoami -> wrapped body" || bad "skill-injection did not emit wrapped body"

# 4d. The spt-hosted bringup blocks ([session.self] + [env.SPT_ENDPOINT_ID]) cross-field-validate:
#     `adapter add` (step 1) is manifest-first ("an invalid manifest registers nothing"), so the
#     manifest carrying these blocks registering at all proves spt-core accepted their shape. Confirm
#     the registered adapter advertises a hostable harness — the thing `spt endpoint run` spawns via
#     [session.self] (the M12 cc-launcher target). [int->REQ-DIST-MANIFEST-SCHEMA]
cap=$(spt api --adapter claude-spt --manifest "$MANIFEST" capability 2>&1)
case "$cap" in *LiveAgent*) ok "bringup blocks accepted; capability reports hostable harness" ;; *) bad "capability missing LiveAgent: $cap" ;; esac

# 5. Soft-deregister cleanly.
rm=$(spt adapter remove claude-spt 2>&1)
list2=$(spt adapter list 2>&1)
case "$list2" in *"claude-spt"*"active"*) bad "claude-spt still active after remove" ;; *) ok "removed (soft-deregistered)" ;; esac

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

-body=$(spt adapter get-string claude-spt skills.whoami 2>&1)

-case "$body" in "# /sptc:whoami"*) ok "file-backed string resolves: skills.whoami -> body" ;; *) bad "skills.whoami not resolved to file body: '$(printf %.40s "$body")'" ;; esac

+body=$(spt adapter get-string claude-spt skills.ready 2>&1)

+case "$body" in "# /sptc:ready"*) ok "file-backed string resolves: skills.ready -> body" ;; *) bad "skills.ready not resolved to file body: '$(printf %.40s "$body")'" ;; esac


  inj=$(sptc_inject_skill "$(sptc_skill_key '/sptc:whoami report me')")
  case "$inj" in
    '<sptc_skill name="whoami">'*'# /sptc:whoami'*'</sptc_skill>'*) exit 0 ;;
    *) printf 'inject got: %.60s\n' "$inj"; exit 1 ;;
  esac ) && ok "UPS skill-injection: /sptc:whoami -> wrapped body" || bad "skill-injection did not emit wrapped body"

  inj=$(sptc_inject_skill "$(sptc_skill_key '/sptc:ready listen up')")
  case "$inj" in
    '<sptc_skill name="ready">'*'# /sptc:ready'*'</sptc_skill>'*) exit 0 ;;
    *) printf 'inject got: %.60s\n' "$inj"; exit 1 ;;
  esac ) && ok "UPS skill-injection: /sptc:ready -> wrapped body" || bad "skill-injection did not emit wrapped body"