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)

case "$list" in *claude-spt:live*) ok "shipped profile resolves: claude-spt:live (LiveAgent overlay)" ;; *) bad "live profile not resolved" ;; esac
# The ccs overlay (:ccs leaf-replaces [session.self].command -> `ccs`, a drop-in for `claude`) —
# the LOCKED-ADD ccs profile template (REQ-CCS-PROFILES; validated vs sister project claude_skill_owl).
case "$list" in *claude-spt:ccs*) ok "shipped profile resolves: claude-spt:ccs (ccs overlay)" ;; *) bad "ccs profile not resolved" ;; 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.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
# 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
# subnet (LOCKED-ADD cross-machine membership skill — wraps `spt subnet`). [int->REQ-SKILL-SUBNET]
sbody=$(spt adapter get-string claude-spt skills.subnet 2>&1)
case "$sbody" in "# /sptc:subnet"*) ok "file-backed string resolves: skills.subnet -> body" ;; *) bad "skills.subnet not resolved to file body: '$(printf %.40s "$sbody")'" ;; 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: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"

# 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; }

+# The ccs overlay (:ccs leaf-replaces [session.self].command -> `ccs`, a drop-in for `claude`) —

+# the LOCKED-ADD ccs profile template (REQ-CCS-PROFILES; validated vs sister project claude_skill_owl).

+case "$list" in *claude-spt:ccs*) ok "shipped profile resolves: claude-spt:ccs (ccs overlay)" ;; *) bad "ccs profile not resolved" ;; esac


Edit

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'"

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'"
# :ccs overlay observable the same way (leaf-replaced adapter_label). [int->REQ-CCS-PROFILES]
ccs=$(spt adapter get-string claude-spt:ccs adapter_label 2>&1)
[ "$ccs" = "Claude Code (spt, ccs)" ] && ok "overlay observable: :ccs adapter_label differs" || bad "ccs adapter_label='$ccs'"


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'"
# :ccs overlay observable the same way (leaf-replaced adapter_label). [int->REQ-CCS-PROFILES]
ccs=$(spt adapter get-string claude-spt:ccs adapter_label 2>&1)
[ "$ccs" = "Claude Code (spt, ccs)" ] && ok "overlay observable: :ccs adapter_label differs" || bad "ccs adapter_label='$ccs'"

#!/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
# The ccs overlay (:ccs leaf-replaces [session.self].command -> `ccs`, a drop-in for `claude`) —
# the LOCKED-ADD ccs profile template (REQ-CCS-PROFILES; validated vs sister project claude_skill_owl).
case "$list" in *claude-spt:ccs*) ok "shipped profile resolves: claude-spt:ccs (ccs overlay)" ;; *) bad "ccs 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.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
# 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
# subnet (LOCKED-ADD cross-machine membership skill — wraps `spt subnet`). [int->REQ-SKILL-SUBNET]
sbody=$(spt adapter get-string claude-spt skills.subnet 2>&1)
case "$sbody" in "# /sptc:subnet"*) ok "file-backed string resolves: skills.subnet -> body" ;; *) bad "skills.subnet not resolved to file body: '$(printf %.40s "$sbody")'" ;; 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: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"

# 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; }

+# :ccs overlay observable the same way (leaf-replaced adapter_label). [int->REQ-CCS-PROFILES]

+ccs=$(spt adapter get-string claude-spt:ccs adapter_label 2>&1)

+[ "$ccs" = "Claude Code (spt, ccs)" ] && ok "overlay observable: :ccs adapter_label differs" || bad "ccs adapter_label='$ccs'"


**5. Update docs/PARITY.md** — flip ccs row + stale whoami/subnet rows + verdict.


| **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 |

| subnet skill (status/create/show-code/join) | ADD (LOCKED) | operative — `skills/subnet.md` (wraps `spt subnet`) | ✅ parity (REQ-SKILL-SUBNET) |
| ccs profile (`claude-spt:ccs`) | ADD (LOCKED) | operative — `[profiles.ccs]` overlay + CLAUDE_CONFIG_DIR-aware extractor | ✅ parity (REQ-CCS-PROFILES) |
| `/whoami` skill | **DROP** (core `spt whoami` stays → M12 `endpoint list` alias) | removed (was carry-over) | ✅ dropped |


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

| subnet skill (status/create/show-code/join) | ADD (LOCKED) | operative — `skills/subnet.md` (wraps `spt subnet`) | ✅ parity (REQ-SKILL-SUBNET) |
| ccs profile (`claude-spt:ccs`) | ADD (LOCKED) | operative — `[profiles.ccs]` overlay + CLAUDE_CONFIG_DIR-aware extractor | ✅ parity (REQ-CCS-PROFILES) |
| `/whoami` skill | **DROP** (core `spt whoami` stays → M12 `endpoint list` alias) | removed (was carry-over) | ✅ dropped |

# 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.

-| **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 |

+| subnet skill (status/create/show-code/join) | ADD (LOCKED) | operative — `skills/subnet.md` (wraps `spt subnet`) | ✅ parity (REQ-SKILL-SUBNET) |

+| ccs profile (`claude-spt:ccs`) | ADD (LOCKED) | operative — `[profiles.ccs]` overlay + CLAUDE_CONFIG_DIR-aware extractor | ✅ parity (REQ-CCS-PROFILES) |

+| `/whoami` skill | **DROP** (core `spt whoami` stays → M12 `endpoint list` alias) | removed (was carry-over) | ✅ dropped |


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

### ccs profile — LOCKED-ADD gap → BUILT ✅
SCOPE §ccs (LOCKED): ccs ships as a **profile template** under the adapter that retargets the spawn
command template through `ccs`, with ccs's relocated log dir honored. Templates only — the user
supplies their own ccs config/keys. **Operator ruling (2026-06-15):** the shipped profile is
`claude-spt:ccs` invoking **bare `ccs`** (the account set via `ccs auth default <name>`), NOT
`glm`/`kimi` — those were SCOPE *examples*; this profile IS the worked example of adapter-profile
authoring **and** the operator's own SPT-ecosystem hook. Built + validated against the known-good
sister project **claude_skill_owl**:
- **Spawn seam.** `[profiles.ccs.session.self].command = "ccs"` leaf-replaces the base `claude`.
  owl proves ccs is a drop-in for the `claude` binary on the same argv (`live/wrapper/claude.rs`
  Tier-2 PULSE recovery latches `cli_binary = "ccs"`); `SPT_ENDPOINT_ID` rides inherited env through
  the wrapper unchanged.
- **Log-dir seam → in the extractor, not a manifest leaf.** ccs relocates CC's whole state tree
  (incl. `projects/`) via the **`CLAUDE_CONFIG_DIR`** env var (`~/.ccs/instances/<account>/.claude`),
  a per-account runtime value with no static catalog path. SCOPE's "per-profile `~/.ccs` log dir"
  is therefore honored **in `claude-spt-digest`** (dir-locate branch prefers `$CLAUDE_CONFIG_DIR/
  projects` over the `--in` root) — the owl-validated `owlery::claude_projects_root` pattern. The
  base `[digest]` config thus serves base **and** ccs sessions transparently; no `[profiles.ccs.
  digest]` leaf. This env-aware resolver is REQ-CCS-PROFILES's `impl`/`unit` evidence (a `.toml`
  profile leaf alone does not register as `impl`).
- **Finding (refines SCOPE's model).** SCOPE conflated ccs *profiles* (glm/kimi = API-provider
  env-swap, NO transcript relocation) with ccs *accounts* (isolated instances that DO relocate via
  `CLAUDE_CONFIG_DIR`). The operator runs the account/default path, which relocates — so the digest
  seam is real, but its mechanism is `CLAUDE_CONFIG_DIR`, not a fixed `~/.ccs` path. → `REQ-CCS-PROFILES`.


### ccs profile — LOCKED-ADD gap → BUILT ✅
SCOPE §ccs (LOCKED): ccs ships as a **profile template** under the adapter that retargets the spawn
command template through `ccs`, with ccs's relocated log dir honored. Templates only — the user
supplies their own ccs config/keys. **Operator ruling (2026-06-15):** the shipped profile is
`claude-spt:ccs` invoking **bare `ccs`** (the account set via `ccs auth default <name>`), NOT
`glm`/`kimi` — those were SCOPE *examples*; this profile IS the worked example of adapter-profile
authoring **and** the operator's own SPT-ecosystem hook. Built + validated against the known-good
sister project **claude_skill_owl**:
- **Spawn seam.** `[profiles.ccs.session.self].command = "ccs"` leaf-replaces the base `claude`.
  owl proves ccs is a drop-in for the `claude` binary on the same argv (`live/wrapper/claude.rs`
  Tier-2 PULSE recovery latches `cli_binary = "ccs"`); `SPT_ENDPOINT_ID` rides inherited env through
  the wrapper unchanged.
- **Log-dir seam → in the extractor, not a manifest leaf.** ccs relocates CC's whole state tree
  (incl. `projects/`) via the **`CLAUDE_CONFIG_DIR`** env var (`~/.ccs/instances/<account>/.claude`),
  a per-account runtime value with no static catalog path. SCOPE's "per-profile `~/.ccs` log dir"
  is therefore honored **in `claude-spt-digest`** (dir-locate branch prefers `$CLAUDE_CONFIG_DIR/
  projects` over the `--in` root) — the owl-validated `owlery::claude_projects_root` pattern. The
  base `[digest]` config thus serves base **and** ccs sessions transparently; no `[profiles.ccs.
  digest]` leaf. This env-aware resolver is REQ-CCS-PROFILES's `impl`/`unit` evidence (a `.toml`
  profile leaf alone does not register as `impl`).
- **Finding (refines SCOPE's model).** SCOPE conflated ccs *profiles* (glm/kimi = API-provider
  env-swap, NO transcript relocation) with ccs *accounts* (isolated instances that DO relocate via
  `CLAUDE_CONFIG_DIR`). The operator runs the account/default path, which relocates — so the digest
  seam is real, but its mechanism is `CLAUDE_CONFIG_DIR`, not a fixed `~/.ccs` path. → `REQ-CCS-PROFILES`.

# 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 skill (status/create/show-code/join) | ADD (LOCKED) | operative — `skills/subnet.md` (wraps `spt subnet`) | ✅ parity (REQ-SKILL-SUBNET) |
| ccs profile (`claude-spt:ccs`) | ADD (LOCKED) | operative — `[profiles.ccs]` overlay + CLAUDE_CONFIG_DIR-aware extractor | ✅ parity (REQ-CCS-PROFILES) |
| `/whoami` skill | **DROP** (core `spt whoami` stays → M12 `endpoint list` alias) | removed (was carry-over) | ✅ dropped |
| `/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.

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

+### ccs profile — LOCKED-ADD gap → BUILT ✅

+SCOPE §ccs (LOCKED): ccs ships as a **profile template** under the adapter that retargets the spawn

+command template through `ccs`, with ccs's relocated log dir honored. Templates only — the user

+supplies their own ccs config/keys. **Operator ruling (2026-06-15):** the shipped profile is

+`claude-spt:ccs` invoking **bare `ccs`** (the account set via `ccs auth default <name>`), NOT

+`glm`/`kimi` — those were SCOPE *examples*; this profile IS the worked example of adapter-profile

+authoring **and** the operator's own SPT-ecosystem hook. Built + validated against the known-good

+- **Spawn seam.** `[profiles.ccs.session.self].command = "ccs"` leaf-replaces the base `claude`.

+  owl proves ccs is a drop-in for the `claude` binary on the same argv (`live/wrapper/claude.rs`

+  Tier-2 PULSE recovery latches `cli_binary = "ccs"`); `SPT_ENDPOINT_ID` rides inherited env through

+- **Log-dir seam → in the extractor, not a manifest leaf.** ccs relocates CC's whole state tree

+  (incl. `projects/`) via the **`CLAUDE_CONFIG_DIR`** env var (`~/.ccs/instances/<account>/.claude`),

+  a per-account runtime value with no static catalog path. SCOPE's "per-profile `~/.ccs` log dir"

+  is therefore honored **in `claude-spt-digest`** (dir-locate branch prefers `$CLAUDE_CONFIG_DIR/

+  projects` over the `--in` root) — the owl-validated `owlery::claude_projects_root` pattern. The

+  base `[digest]` config thus serves base **and** ccs sessions transparently; no `[profiles.ccs.

+  digest]` leaf. This env-aware resolver is REQ-CCS-PROFILES's `impl`/`unit` evidence (a `.toml`

+  profile leaf alone does not register as `impl`).

+- **Finding (refines SCOPE's model).** SCOPE conflated ccs *profiles* (glm/kimi = API-provider

+  env-swap, NO transcript relocation) with ccs *accounts* (isolated instances that DO relocate via

+  `CLAUDE_CONFIG_DIR`). The operator runs the account/default path, which relocates — so the digest

+  seam is real, but its mechanism is `CLAUDE_CONFIG_DIR`, not a fixed `~/.ccs` path. → `REQ-CCS-PROFILES`.


## 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.

## Parity verdict
**PROVEN (adapter surface).** All three open divergences are closed this slice: subnet skill added
(`skills/subnet.md`, REQ-SKILL-SUBNET), ccs profile added (`[profiles.ccs]` + CLAUDE_CONFIG_DIR-aware
extractor, REQ-CCS-PROFILES), whoami skill dropped. The full KEEP/ADD set is ✅ and the DROP set is
confirmed absent. `/new-alarm` is an accepted, documented gap (deferred-pending-arrangement-system)
and does **not** block the verdict.

The `sptc`→`spt` succession flip remains separately gated (NOT unblocked by this verdict alone):
it also requires **legacy owl retired** (KNOWN-HAZARDS / RELEASE-RUNBOOK) and rides the live-relay
int held for spt-core M11 counter-15. This audit clears only the parity precondition.


## Parity verdict
**PROVEN (adapter surface).** All three open divergences are closed this slice: subnet skill added
(`skills/subnet.md`, REQ-SKILL-SUBNET), ccs profile added (`[profiles.ccs]` + CLAUDE_CONFIG_DIR-aware
extractor, REQ-CCS-PROFILES), whoami skill dropped. The full KEEP/ADD set is ✅ and the DROP set is
confirmed absent. `/new-alarm` is an accepted, documented gap (deferred-pending-arrangement-system)
and does **not** block the verdict.

The `sptc`→`spt` succession flip remains separately gated (NOT unblocked by this verdict alone):
it also requires **legacy owl retired** (KNOWN-HAZARDS / RELEASE-RUNBOOK) and rides the live-relay
int held for spt-core M11 counter-15. This audit clears only the parity precondition.

# 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 skill (status/create/show-code/join) | ADD (LOCKED) | operative — `skills/subnet.md` (wraps `spt subnet`) | ✅ parity (REQ-SKILL-SUBNET) |
| ccs profile (`claude-spt:ccs`) | ADD (LOCKED) | operative — `[profiles.ccs]` overlay + CLAUDE_CONFIG_DIR-aware extractor | ✅ parity (REQ-CCS-PROFILES) |
| `/whoami` skill | **DROP** (core `spt whoami` stays → M12 `endpoint list` alias) | removed (was carry-over) | ✅ dropped |
| `/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 profile — LOCKED-ADD gap → BUILT ✅
SCOPE §ccs (LOCKED): ccs ships as a **profile template** under the adapter that retargets the spawn
command template through `ccs`, with ccs's relocated log dir honored. Templates only — the user
supplies their own ccs config/keys. **Operator ruling (2026-06-15):** the shipped profile is
`claude-spt:ccs` invoking **bare `ccs`** (the account set via `ccs auth default <name>`), NOT
`glm`/`kimi` — those were SCOPE *examples*; this profile IS the worked example of adapter-profile
authoring **and** the operator's own SPT-ecosystem hook. Built + validated against the known-good
sister project **claude_skill_owl**:
- **Spawn seam.** `[profiles.ccs.session.self].command = "ccs"` leaf-replaces the base `claude`.
  owl proves ccs is a drop-in for the `claude` binary on the same argv (`live/wrapper/claude.rs`
  Tier-2 PULSE recovery latches `cli_binary = "ccs"`); `SPT_ENDPOINT_ID` rides inherited env through
  the wrapper unchanged.
- **Log-dir seam → in the extractor, not a manifest leaf.** ccs relocates CC's whole state tree
  (incl. `projects/`) via the **`CLAUDE_CONFIG_DIR`** env var (`~/.ccs/instances/<account>/.claude`),
  a per-account runtime value with no static catalog path. SCOPE's "per-profile `~/.ccs` log dir"
  is therefore honored **in `claude-spt-digest`** (dir-locate branch prefers `$CLAUDE_CONFIG_DIR/
  projects` over the `--in` root) — the owl-validated `owlery::claude_projects_root` pattern. The
  base `[digest]` config thus serves base **and** ccs sessions transparently; no `[profiles.ccs.
  digest]` leaf. This env-aware resolver is REQ-CCS-PROFILES's `impl`/`unit` evidence (a `.toml`
  profile leaf alone does not register as `impl`).
- **Finding (refines SCOPE's model).** SCOPE conflated ccs *profiles* (glm/kimi = API-provider
  env-swap, NO transcript relocation) with ccs *accounts* (isolated instances that DO relocate via
  `CLAUDE_CONFIG_DIR`). The operator runs the account/default path, which relocates — so the digest
  seam is real, but its mechanism is `CLAUDE_CONFIG_DIR`, not a fixed `~/.ccs` path. → `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.

-**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

+**PROVEN (adapter surface).** All three open divergences are closed this slice: subnet skill added

+(`skills/subnet.md`, REQ-SKILL-SUBNET), ccs profile added (`[profiles.ccs]` + CLAUDE_CONFIG_DIR-aware

+extractor, REQ-CCS-PROFILES), whoami skill dropped. The full KEEP/ADD set is ✅ and the DROP set is

+confirmed absent. `/new-alarm` is an accepted, documented gap (deferred-pending-arrangement-system)

+The `sptc`→`spt` succession flip remains separately gated (NOT unblocked by this verdict alone):

+it also requires **legacy owl retired** (KNOWN-HAZARDS / RELEASE-RUNBOOK) and rides the live-relay

+int held for spt-core M11 counter-15. This audit clears only the parity precondition.


**6. Build + test.** Digest extractor first, then full gate + traceable check.


Bash

cd "C:\Users\decid\Documents\projects\spt-claude-code\tools\claude-spt-digest" && cargo test --quiet 2>&1 | tail -30

cargo test claude-spt-digest


running 12 tests
............
test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s


12 tests pass (9 + 3 new). Now traceable-reqs check + full gate.


cd "C:\Users\decid\Documents\projects\spt-claude-code" && command -v traceable-reqs >/dev/null 2>&1 && traceable-reqs check 2>&1 | tail -40 || echo "NO_TRACEABLE_ON_PATH"

Run traceable-reqs check


       Deterministic gates (shell-syntax + unit tests + traceable-reqs check + manifest-schema + docs-drift), each a binary pass/fail, runnable as one command
  [OK] REQ-CI-MANUAL  required: [doc, impl]  stages: +doc +impl -unit -int
       A manual 'run gates' command always exists — the same gate scripts runnable by hand on any fleet host
  [OK] REQ-CI-OWL-DISCOVERY  required: [doc, impl, unit]  stages: +doc +impl +unit -int
       The spt messaging (bus) binary is located robustly at run time (per-version plugins path / PATH / configured) — never a hard-coded versioned path
  [OK] REQ-CI-TRIGGER  required: [doc, impl]  stages: +doc +impl -unit -int
       A git push hook fires the gates: pings a fleet runner-agent over spt rather than polling
  [OK] REQ-DIST-BOOTSTRAP-INSTALL  required: [doc, impl]  stages: +doc +impl -unit -int
       SessionStart bootstrap installs spt-core when absent (invisible-installer pattern)
  [OK] REQ-DIST-DIGEST-EXTRACTOR  required: [impl, unit, int]  stages: -doc +impl +unit +int
       The claude-spt [digest] extractor (claude-spt-digest) maps Claude Code's JSONL transcript to the published digest-record NDJSON contract (role∈{input,agent,tool}; text/tool/ts), emitting RAW records for spt-core's renderer (ADR-0019)
  [OK] REQ-DIST-HOOKS-API  required: [doc, impl, unit, int]  stages: +doc +impl +unit +int
       hooks.json delegates to `spt api` rather than carrying adapter logic
  [OK] REQ-DIST-MANIFEST-SCHEMA  required: [doc, impl, unit, int]  stages: +doc +impl +unit +int
       CC adapter manifest validates against spt-core's published manifest.schema.json (from spt-releases)
  [OK] REQ-DIST-PLUGIN-SKELETON  required: [doc, impl, unit]  stages: +doc +impl +unit -int
       Marketplace artifact on cplugs is a thin skeleton: namespaced /sptc:* skill stubs + hooks.json + plugin.json, no embedded logic
  [OK] REQ-DIST-SHORTCUT-BASENAME  required: [doc, impl, unit]  stages: +doc +impl +unit -int
       The adapter manifest brands the `spt endpoint run` launcher shortcut as `cc-<id>` via adapter.shortcut_basename = "cc" (the M12 cc launcher; decoupled from the sptc plugin name, ADR-0001)
  [OK] REQ-DOCS-DRIFT  required: [doc, impl, unit]  stages: +doc +impl +unit -int
       Generated docs (llms.txt) are CI-gated against drift: a deterministic generator's output must match what's checked in, and the book must build
  [OK] REQ-DOCS-SITE  required: [doc, impl]  stages: +doc +impl -unit -int
       docs-site/ builds with mdBook from src/ (themed, Diátaxis-shaped, all-real-content — no placeholders)
  [OK] REQ-HAZARD-MSYS-PATHCONV  required: [unit]  stages: -doc -impl +unit -int
       Hook wrappers read the CC payload from stdin, never from a /-leading positional argv (Git-Bash/MSYS path-mangles those on Windows)
  [OK] REQ-HAZARD-PERCH-COLLISION  required: [unit]  stages: +doc +impl +unit +int
       The acceptance harness spawns every nested claude SUT under a DISPOSABLE identity (SPT_AGENT_ID=sptc-ci-<n>), never a live agent's perch id — a colliding id tears down the live agent's perch + poll stream (name-keyed, last-establish-wins)
  [OK] REQ-PARITY-AUDIT  required: [doc]  stages: +doc -impl -unit -int
       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
  [OK] REQ-SKILL-LIVE  required: [doc, impl, unit]  stages: +doc +impl +unit -int
       /sptc:live upgrades THIS CC session to a LiveAgent: the claude-spt:live profile overlays [session.psyche_init] (daemon spawns the Psyche), the claude-spt-psyche runner keeps the Psyche claude alive (one --continue turn per pulse, NOT one-shot `claude -p`), and the live body activates the :live composite + a resident Monitor relay as the single delivery pipe
  [OK] REQ-SKILL-SUBNET  required: [doc, int]  stages: +doc -impl -unit +int
       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)
  [OK] REQ-SKILL-VERSION  required: []  stages: -doc -impl -unit -int
       /spt:version reports the spt-core-tracked manifest/binary version (the version-of-truth, not the marketplace skeleton version)
  [OK] REQ-UPS-INJECTION  required: [doc, impl, unit, int]  stages: +doc +impl +unit +int
       UserPromptSubmit hook detects /sptc:X and injects X's real instructions as additionalContext (SKILL.md files stay skeletons); must be empirically confirmed UPS fires on slash-commands

Findings:
  [must] missing_stage REQ-CCS-PROFILES stage=doc — Requirement is missing documentation evidence


`doc` evidence not registering from the `.toml` comment. Check how the scanner is configured (which files/stages it accepts).


cd "C:\Users\decid\Documents\projects\spt-claude-code" && grep -nE "doc|impl|include|exclude|glob|path|ext|toml|md\b|scan" traceable-reqs.toml | grep -viE "^\s*#|title =|^[0-9]+:#" | head -40

Inspect traceable scan config


27:[scan]
32:roots = ["plugin", "ci", "ci/acceptance", "ci/publish", "ci/docs", "ci/manifest", "ci/digest", "adapter", "tools", "tests", "docs", "docs-site", "CONTEXT.md", "SCOPE.md", "AGENTS.md", "HANDOFF.md"]
64:required_stages = ["doc", "impl", "unit"]  # doc: ADR-0001 + RELEASE-RUNBOOK. impl: ci/publish/{validate,package}-skeleton.sh (installability gate + publish mechanic). unit: tests/skeleton-validate.sh (pass real + fail tampered).
69:required_stages = ["doc", "impl"]  # doc: ADR-0001. impl: plugin/sptc/bootstrap.{sh,ps1}.
74:required_stages = ["doc", "impl", "unit", "int"]  # doc: ADR-0001/0002. impl: hooks.json + wrappers. unit: tests/hooks-parse.sh. int: ci/hooks/poll-int.sh — `spt api poll` on live v0.7.1 emits the canonical <EVENT> envelope (ADR-0020), confirm-matched through render_frames; __REPLY_TO__ relic gone, multi-drain splits on </EVENT>. 5/5 green 2026-06-15 (throwaway perch, torn down).
79:required_stages = ["doc", "impl", "unit", "int"]  # doc: ADR-0001 (manifest layer + version-of-truth). impl: adapter/claude-spt.toml + vendored adapter/manifest.schema.json + ci/manifest/{validate_manifest.py,check-manifest.sh} (gate). unit: tests/manifest-schema.sh (pass real + fail tampered). int: ci/manifest/registration-int.sh — real `spt adapter add` (v0.7.0) cross-field registration-validation accepted the manifest, shipped profile resolved, [strings] read + overlay observable; 6/6 green 2026-06-15 (registry left clean via soft-remove). Cross-field discovery: [digest] requires source-or-[history].locate_template (not in JSON schema) — fixed with source="{home}/.claude/projects".
84:required_stages = ["doc", "impl", "unit"]  # doc: ADR-0001 (cc launcher brand, decoupled from the s/sptc/spt/ plugin-name succession). impl: the cc-launcher BRINGUP machinery — adapter [session.self] (spawns `claude`) + [env.SPT_ENDPOINT_ID] (broker injects the endpoint id) + the SessionStart bind branch (sptc_register_verb -> `api bind`, the spt-hosted self-register path). unit: tests/manifest-shortcut.sh (brand-value guard) + tests/hooks-parse.sh sptc_register_verb (bind/seed/boundary branch). int (real `spt endpoint run` picker emits cc-<id> + real-claude bringup-bind) deferred — the shortcut-emitting picker "lands in a later spt-core wave" (per `endpoint run --help`); the bringup-bind is now contract-unblocked (doyle confirmed 2026-06-15: bind is establishing/intrinsic-auth, --set-session-id only, no token) but still needs a real-claude acceptance spawn (heavier tier). adapter add accepting the [session.self]/[env] blocks is covered by registration-int.sh step 4d (REQ-DIST-MANIFEST-SCHEMA int).
89:required_stages = ["impl", "unit", "int"]  # impl: tools/claude-spt-digest (Rust — CC JSONL -> digest NDJSON; per-session locate of <slug>/<session_id>.jsonl under the projects root via the {session_id} the command receives; native UTF-8 stdout; defensive ~ expansion) + ci/digest/build.sh gate. unit: the crate's cargo tests (9: input/agent/tool mapping, arg-key preference, thinking+tool_result+noise+bad-json drops, UTF-8 round-trip, contract invariants, tilde expansion) run by ci/digest/build.sh. int: ci/digest/digest-proof-int.sh — `spt adapter digest-proof claude-spt --sample` on live v0.7.2 ran the published-shape extractor command (`--session {session_id} --in {source}`) and rendered DIGEST_PROOF_OK (5 parsed / 0 dropped). GREEN 2026-06-15 (registry left clean via soft-remove). Was F-004-blocked (digest-proof --sample passed an empty substitution-key map, cli.rs:5135) — doyle's spt-core PR#14 fix (v0.7.2, release counter 14) now fills {id}+{session_id} + an optional --session, matching runtime. doc deferred (the mapping is documented in the module + manifest [digest] comment; a docs-site section lands with the doc slice).
94:required_stages = ["doc", "impl", "unit"]  # doc: adapter/strings/skills/live.md (activation + poll/relay reconcile + Psyche-is-daemon-managed). impl: [profiles.live.session.psyche_init] in adapter/claude-spt.toml (the LiveAgent seam, 4 daemon keys) + tools/claude-spt-psyche (the resident runner). unit: claude-spt-psyche cargo tests (arg parse, seed/pulse/poll command construction, blank-pulse guard) run by ci/psyche/build.sh. int deferred: the daemon Psyche loop (lifecycle.rs run_pulse_loop) is STAGED behind the live-agent-adapter landing (brainproc.rs no-op today, REQ-DAEMON-1/M11-W0) and the Monitor relay needs a real live CC session — both are real-session acceptance, a heavier tier. The :live profile RESOLVING on the live binary is already covered by registration-int.sh (REQ-DIST-MANIFEST-SCHEMA int).
99:required_stages = ["doc", "impl", "unit", "int"]  # doc: ADR-0002. impl: user-prompt-submit.sh (skill-injection + drain) + _common.sh. unit: render_frames + sptc_skill_key tests. int: ci/hooks/poll-int.sh — the poll->additionalContext drain round-trip vs live v0.7.1 (<EVENT> envelope, render_frames confirm-match, multi-message). UPS-fires-on-slash empirical = ADR-0002 validation (CC 2.1.177); skill-injection resolve = registration-int 4c.
108:required_stages = ["doc", "impl", "unit"]
113:required_stages = ["doc", "impl"]
118:required_stages = ["doc", "impl"]
123:required_stages = ["doc", "impl"]
128:required_stages = ["doc", "impl", "unit"]
133:required_stages = ["doc", "impl", "int"]  # doc: docs/CI.md. impl: ci/acceptance/{lib,run-acceptance}.sh + harness unit. int: real `claude -p` spawn asserted UPS-hook-fires green 2026-06-15 under disposable id sptc-ci-40322 (operator perch perri survived — guard proven e2e).
151:required_stages = ["doc", "impl"]  # doc: DOCS-STRATEGY. impl: docs-site/book.toml + src/ pages.
156: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.
161: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.).
166:required_stages = ["doc", "int"]  # doc: adapter/strings/skills/subnet.md (one /sptc:subnet skill covering status/create/show-code/join — mirrors the single `spt subnet` verb-family CLI). int: ci/manifest/registration-int.sh file-backed-resolve assertion (skills.subnet -> body) on live spt — the wiring proof. No separate `impl`/`unit` stage: a file-backed skill has no unit-testable code of its own (the manifest [strings.skills].subnet pointer + plugin stub are config, and the int proves they resolve end-to-end through the live registry — same model as the commune/force-stop skills, which carry no per-skill impl). (No `spt how-to subnet` topic exists — minor published-guidance gap noted in docs/SPT-CORE-FINDINGS.md; the skill wraps `spt subnet --help` as canonical.)
181:required_stages = ["doc", "impl", "unit", "int"]


27	[scan]
28	# Every evidence location. A missing root makes evidence SILENTLY vanish from the trace —
29	# audit this whenever a new evidence dir appears. ADD `plugin`, `skills`, `hooks`, `tests`
30	# the moment those dirs are created (skeleton milestone, body #2) — until they exist a
31	# nonexistent root is a hard manifest_error, so only list what is on disk now.
32	roots = ["plugin", "ci", "ci/acceptance", "ci/publish", "ci/docs", "ci/manifest", "ci/digest", "adapter", "tools", "tests", "docs", "docs-site", "CONTEXT.md", "SCOPE.md", "AGENTS.md", "HANDOFF.md"]
33
34	[policy]
35	# Default policy applied at activation: doc + impl + unit. Networking/lifecycle/cross-node
36	# reqs add "int". Until a req is activated, its per-req `required_stages = []` keeps it out
37	# of the coverage gate.
38	required_stages = []
39
40	# ───────────────────────────── Requirements ─────────────────────────────────
41	# Add requirements as you scope milestones, seeded from SCOPE.md "Parity-trim". Shape:
42	#
43	# [[requirements]]
44	# id = "REQ-EXAMPLE-1"
45	# title = "One-line statement of the requirement"
46	# required_stages = []  # activate (e.g. ["doc","impl","unit"]) when the milestone starts

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

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

# ───────────────────────────── Requirements ─────────────────────────────────
# Add requirements as you scope milestones, seeded from SCOPE.md "Parity-trim". Shape:
#
# [[requirements]]
# id = "REQ-EXAMPLE-1"
# title = "One-line statement of the requirement"
# required_stages = []  # activate (e.g. ["doc","impl","unit"]) when the milestone starts


`adapter/` is scanned, but `doc`-stage markers only register from doc files (`.md`) — the `.toml` comment counts for impl/int, not doc (the trace gotcha). Move the doc tag into PARITY.md (markdown), and de-tag the toml comment to avoid a dangling marker.


Edit

### ccs profile — LOCKED-ADD gap → BUILT ✅
SCOPE §ccs (LOCKED):

### ccs profile — LOCKED-ADD gap → BUILT ✅
<!-- [doc->REQ-CCS-PROFILES] -->
SCOPE §ccs (LOCKED):


### ccs profile — LOCKED-ADD gap → BUILT ✅
<!-- [doc->REQ-CCS-PROFILES] -->
SCOPE §ccs (LOCKED):

# 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 skill (status/create/show-code/join) | ADD (LOCKED) | operative — `skills/subnet.md` (wraps `spt subnet`) | ✅ parity (REQ-SKILL-SUBNET) |
| ccs profile (`claude-spt:ccs`) | ADD (LOCKED) | operative — `[profiles.ccs]` overlay + CLAUDE_CONFIG_DIR-aware extractor | ✅ parity (REQ-CCS-PROFILES) |
| `/whoami` skill | **DROP** (core `spt whoami` stays → M12 `endpoint list` alias) | removed (was carry-over) | ✅ dropped |
| `/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 profile — LOCKED-ADD gap → BUILT ✅
SCOPE §ccs (LOCKED): ccs ships as a **profile template** under the adapter that retargets the spawn
command template through `ccs`, with ccs's relocated log dir honored. Templates only — the user
supplies their own ccs config/keys. **Operator ruling (2026-06-15):** the shipped profile is
`claude-spt:ccs` invoking **bare `ccs`** (the account set via `ccs auth default <name>`), NOT
`glm`/`kimi` — those were SCOPE *examples*; this profile IS the worked example of adapter-profile
authoring **and** the operator's own SPT-ecosystem hook. Built + validated against the known-good
sister project **claude_skill_owl**:
- **Spawn seam.** `[profiles.ccs.session.self].command = "ccs"` leaf-replaces the base `claude`.
  owl proves ccs is a drop-in for the `claude` binary on the same argv (`live/wrapper/claude.rs`
  Tier-2 PULSE recovery latches `cli_binary = "ccs"`); `SPT_ENDPOINT_ID` rides inherited env through
  the wrapper unchanged.
- **Log-dir seam → in the extractor, not a manifest leaf.** ccs relocates CC's whole state tree
  (incl. `projects/`) via the **`CLAUDE_CONFIG_DIR`** env var (`~/.ccs/instances/<account>/.claude`),
  a per-account runtime value with no static catalog path. SCOPE's "per-profile `~/.ccs` log dir"
  is therefore honored **in `claude-spt-digest`** (dir-locate branch prefers `$CLAUDE_CONFIG_DIR/
  projects` over the `--in` root) — the owl-validated `owlery::claude_projects_root` pattern. The
  base `[digest]` config thus serves base **and** ccs sessions transparently; no `[profiles.ccs.
  digest]` leaf. This env-aware resolver is REQ-CCS-PROFILES's `impl`/`unit` evidence (a `.toml`
  profile leaf alone does not register as `impl`).
- **Finding (refines SCOPE's model).** SCOPE conflated ccs *profiles* (glm/kimi = API-provider
  env-swap, NO transcript relocation) with ccs *accounts* (isolated instances that DO relocate via
  `CLAUDE_CONFIG_DIR`). The operator runs the account/default path, which relocates — so the digest
  seam is real, but its mechanism is `CLAUDE_CONFIG_DIR`, not a fixed `~/.ccs` path. → `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
**PROVEN (adapter surface).** All three open divergences are closed this slice: subnet skill added
(`skills/subnet.md`, REQ-SKILL-SUBNET), ccs profile added (`[profiles.ccs]` + CLAUDE_CONFIG_DIR-aware
extractor, REQ-CCS-PROFILES), whoami skill dropped. The full KEEP/ADD set is ✅ and the DROP set is
confirmed absent. `/new-alarm` is an accepted, documented gap (deferred-pending-arrangement-system)
and does **not** block the verdict.

The `sptc`→`spt` succession flip remains separately gated (NOT unblocked by this verdict alone):
it also requires **legacy owl retired** (KNOWN-HAZARDS / RELEASE-RUNBOOK) and rides the live-relay
int held for spt-core M11 counter-15. This audit clears only the parity precondition.


# and ccs sessions transparently. That env-aware resolver is REQ-CCS-PROFILES's impl evidence.
# [doc->REQ-CCS-PROFILES]
[profiles.ccs.session.self]

# and ccs sessions transparently. That env-aware resolver is REQ-CCS-PROFILES's impl evidence.
# (REQ-CCS-PROFILES doc evidence lives in docs/PARITY.md; int = registration-int :ccs assertions.)
[profiles.ccs.session.self]


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)

# and ccs sessions transparently. That env-aware resolver is REQ-CCS-PROFILES's impl evidence.
# [doc->REQ-CCS-PROFILES]
[profiles.ccs.session.self]

# and ccs sessions transparently. That env-aware resolver is REQ-CCS-PROFILES's impl evidence.
# (REQ-CCS-PROFILES doc evidence lives in docs/PARITY.md; int = registration-int :ccs assertions.)
[profiles.ccs.session.self]

# $CLAUDE_CONFIG_DIR/projects over the base ~/.claude/projects on its dir-locate branch — the

# owl-validated owlery::claude_projects_root pattern), so the base [digest] config serves both base

+# (REQ-CCS-PROFILES doc evidence lives in docs/PARITY.md; int = registration-int :ccs assertions.)


Bash

cd "C:\Users\decid\Documents\projects\spt-claude-code" && traceable-reqs check 2>&1 | grep -iE "CCS-PROFILES|finding|missing|error|exit|all .*green|OK\]" | head -20; echo "EXIT=$?"; traceable-reqs check >/dev/null 2>&1; echo "RC=$?"

Re-run traceable check for CCS-PROFILES


22 requirements: 22 complete, 0 incomplete, 0 findings
  [OK] REQ-CCS-PROFILES  required: [doc, impl, unit, int]  stages: +doc +impl +unit +int
  [OK] REQ-CI-ACCEPTANCE  required: [doc, impl, int]  stages: +doc +impl -unit +int
  [OK] REQ-CI-BUS  required: [doc, impl]  stages: +doc +impl -unit -int
  [OK] REQ-CI-GATES  required: [doc, impl, unit]  stages: +doc +impl +unit -int
  [OK] REQ-CI-MANUAL  required: [doc, impl]  stages: +doc +impl -unit -int
  [OK] REQ-CI-OWL-DISCOVERY  required: [doc, impl, unit]  stages: +doc +impl +unit -int
  [OK] REQ-CI-TRIGGER  required: [doc, impl]  stages: +doc +impl -unit -int
  [OK] REQ-DIST-BOOTSTRAP-INSTALL  required: [doc, impl]  stages: +doc +impl -unit -int
  [OK] REQ-DIST-DIGEST-EXTRACTOR  required: [impl, unit, int]  stages: -doc +impl +unit +int
  [OK] REQ-DIST-HOOKS-API  required: [doc, impl, unit, int]  stages: +doc +impl +unit +int
  [OK] REQ-DIST-MANIFEST-SCHEMA  required: [doc, impl, unit, int]  stages: +doc +impl +unit +int
  [OK] REQ-DIST-PLUGIN-SKELETON  required: [doc, impl, unit]  stages: +doc +impl +unit -int
  [OK] REQ-DIST-SHORTCUT-BASENAME  required: [doc, impl, unit]  stages: +doc +impl +unit -int
  [OK] REQ-DOCS-DRIFT  required: [doc, impl, unit]  stages: +doc +impl +unit -int
  [OK] REQ-DOCS-SITE  required: [doc, impl]  stages: +doc +impl -unit -int
  [OK] REQ-HAZARD-MSYS-PATHCONV  required: [unit]  stages: -doc -impl +unit -int
  [OK] REQ-HAZARD-PERCH-COLLISION  required: [unit]  stages: +doc +impl +unit +int
  [OK] REQ-PARITY-AUDIT  required: [doc]  stages: +doc -impl -unit -int
  [OK] REQ-SKILL-LIVE  required: [doc, impl, unit]  stages: +doc +impl +unit -int
EXIT=0
RC=0


All green (22/22, 0 findings). Run full gate, then the live registration-int (SPTC_ACCEPTANCE) to actually prove `:ccs` resolves on live v0.7.2.


cd "C:\Users\decid\Documents\projects\spt-claude-code" && sh ci/run-gates.sh 2>&1 | grep -iE "GATE:|FAIL|ok  coverage|SKIP|GATES" | head -40; echo "GATE_RC=${PIPESTATUS[0]}"

Run full deterministic gate suite


=== GATE: shell-syntax (sh -n) ===
ok  ci/run-gates.sh
ok  tests/ci-gates.sh
=== GATE: unit tests (tests/*.sh) ===
ok  tests/ci-gates.sh
=== GATE: digest-extractor (cargo build + test) ===
test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
=== GATE: psyche-runner (cargo build + test) ===
test result: ok. 8 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
=== GATE: traceable-reqs check (requirement coverage) ===
ok  coverage green
=== GATE: skeleton-validate (cplugs installability) ===
=== GATE: manifest-schema ===
=== GATE: docs-drift ===
GATE_RC=0


cd "C:\Users\decid\Documents\projects\spt-claude-code" && sh ci/run-gates.sh >/tmp/gate.log 2>&1; echo "RC=$?"; grep -iE "FAIL|GATES (PASS|OK|FAIL)|ALL GATES|^GATES" /tmp/gate.log | head; tail -3 /tmp/gate.log

Confirm gate exit + summary


RC=0
test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 8 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
ok   llms.txt in sync

=== RESULT: PASS ===


Gate PASS (RC=0). Now prove `:ccs` resolves on live v0.7.2.


cd "C:\Users\decid\Documents\projects\spt-claude-code" && SPTC_ACCEPTANCE=1 sh ci/manifest/registration-int.sh 2>&1 | grep -iE "ccs|deep|live|OK|FAIL|registered|removed" | head -25; echo "INT_RC=${PIPESTATUS[0]}"

Run live registration-int, check ccs assertions


ok   spt adapter add: registered
ok   listed: claude-spt active
ok   shipped profile resolves: claude-spt:deep
ok   shipped profile resolves: claude-spt:live (LiveAgent overlay)
ok   shipped profile resolves: claude-spt:ccs (ccs overlay)
ok   strings resolve: adapter_label (base)
ok   overlay observable: :deep adapter_label differs
ok   overlay observable: :ccs adapter_label differs
ok   file-backed string resolves: skills.ready -> body
ok   file-backed string resolves: skills.commune -> body
ok   file-backed string resolves: skills.force-stop -> body
ok   file-backed string resolves: skills.live -> body
ok   file-backed string resolves: skills.subnet -> body
ok   UPS skill-injection: /sptc:ready -> wrapped body
ok   bringup blocks accepted; capability reports hostable harness
ok   removed (soft-deregistered)
MANIFEST-REGISTRATION-INT OK
INT_RC=0


All green on live v0.7.2 — `claude-spt:ccs` resolves, `:ccs` adapter_label observable, registry left clean. Commit Phase D.


cd "C:\Users\decid\Documents\projects\spt-claude-code" && git add -A && git status --short && echo "---" && git log --oneline -1

Stage Phase D + show status


warning: in the working copy of 'tools/claude-spt-digest/src/main.rs', LF will be replaced by CRLF the next time Git touches it
M  adapter/claude-spt.toml
M  ci/manifest/registration-int.sh
M  docs/PARITY.md
M  tools/claude-spt-digest/src/main.rs
M  traceable-reqs.toml
---
7073e6a docs(plan): mark parity Phases A/B/C done; Phase D (ccs) next


cd "C:\Users\decid\Documents\projects\spt-claude-code" && git commit -q -F - <<'EOF'
feat(ccs): add claude-spt:ccs profile (Phase D) — spawn retarget + CLAUDE_CONFIG_DIR-aware digest

LOCKED-ADD ccs profile, scoped to the default-profile shape the operator runs:
`ccs` (bare; account via `ccs auth default`), NOT glm/kimi (those were SCOPE
examples). This profile IS the worked example of adapter-profile authoring + the
operator's own SPT-ecosystem hook. Validated against the known-good sister
project claude_skill_owl.

- [profiles.ccs.session.self].command = "ccs" — leaf-replaces base `claude`;
  ccs is a drop-in for the claude binary on the same argv (owl live/wrapper/
  claude.rs Tier-2 latch). SPT_ENDPOINT_ID rides inherited env unchanged.
- [profiles.ccs.strings].adapter_label — observability leaf (mirrors :deep),
  the registration-int resolve proof.
- claude-spt-digest now honors CLAUDE_CONFIG_DIR on its dir-locate branch
  (prefers $CLAUDE_CONFIG_DIR/projects over the --in root; --sample untouched),
  mirroring owl's owlery::claude_projects_root. This is the log-dir seam: ccs
  relocates CC's transcript tree per-account via CLAUDE_CONFIG_DIR, a runtime
  value with no static catalog path — so the seam lives in the extractor, not a
  [profiles.ccs.digest] leaf. The base [digest] config serves base + ccs
  transparently. +3 unit tests (honor/none/empty).

Finding (refines SCOPE): SCOPE conflated ccs *profiles* (glm/kimi = API env-swap,
no relocation) with ccs *accounts* (isolated instances that DO relocate via
CLAUDE_CONFIG_DIR). Operator runs the account/default path → the digest seam is
real, mechanism is CLAUDE_CONFIG_DIR not a fixed ~/.ccs path.

REQ-CCS-PROFILES activated [doc,impl,unit,int], all green. docs/PARITY.md verdict
flips to PROVEN (adapter surface): subnet added, ccs added, whoami dropped; DROP
set absent. Succession flip stays separately gated (legacy owl retirement + M11
counter-15). Gate PASS; registration-int OK on live v0.7.2 (:ccs resolves +
adapter_label observable; registry left clean).

Co-authored by: perri
EOF
echo "RC=$?"; git log --oneline -1

Commit Phase D


RC=0
b15c1bf feat(ccs): add claude-spt:ccs profile (Phase D) — spawn retarget + CLAUDE_CONFIG_DIR-aware digest


Phase D committed (`b15c1bf`). Update plan progress, then proceed to Phase E.


Edit

- ⏭ **Phase D** (ccs profiles) — NEXT. Needs research: the spawn/psyche/echo command seams a ccs
  overlay leaf-replaces. Activate REQ-CCS-PROFILES [doc,impl] when starting.
- ⏭ **Phase E** (publish prep as sptc) — after D.

- ✅ **Phase D** (b15c1bf) — `claude-spt:ccs` profile. Scoped to bare `ccs` (default account, NOT
  glm/kimi — operator ruling); validated vs sister project claude_skill_owl. Spawn seam
  (`[profiles.ccs.session.self].command = "ccs"`, a drop-in for `claude`) + observability string
  leaf. Log-dir seam landed IN the extractor: claude-spt-digest now honors `CLAUDE_CONFIG_DIR`
  (owl `owlery::claude_projects_root` parity) — ccs relocates CC's transcript tree per-account, a
  runtime value with no static path, so no `[profiles.ccs.digest]` leaf. REQ-CCS-PROFILES
  [doc,impl,unit,int] green. Finding logged (SCOPE conflated ccs profiles vs accounts).
  **docs/PARITY.md verdict → PROVEN (adapter surface).** Gate PASS; registration-int OK on v0.7.2.
- ⏭ **Phase E** (publish prep as sptc) — NEXT. CHANGELOG.md + plugin.json bump + validate/package
  skeleton dry-run + docs regen. cplugs publish = operator's step.


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)

- ✅ **Phase D** (b15c1bf) — `claude-spt:ccs` profile. Scoped to bare `ccs` (default account, NOT
  glm/kimi — operator ruling); validated vs sister project claude_skill_owl. Spawn seam
  (`[profiles.ccs.session.self].command = "ccs"`, a drop-in for `claude`) + observability string
  leaf. Log-dir seam landed IN the extractor: claude-spt-digest now honors `CLAUDE_CONFIG_DIR`
  (owl `owlery::claude_projects_root` parity) — ccs relocates CC's transcript tree per-account, a
  runtime value with no static path, so no `[profiles.ccs.digest]` leaf. REQ-CCS-PROFILES
  [doc,impl,unit,int] green. Finding logged (SCOPE conflated ccs profiles vs accounts).
  **docs/PARITY.md verdict → PROVEN (adapter surface).** Gate PASS; registration-int OK on v0.7.2.
- ⏭ **Phase E** (publish prep as sptc) — NEXT. CHANGELOG.md + plugin.json bump + validate/package
  skeleton dry-run + docs regen. cplugs publish = operator's step.

# 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                     | ACCEPTED GAP (deferred-pending-arrangement-system) | dead stub, no manifest | **remove stub**; no REQ, no build |
| /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).

## Progress (this session)
- ✅ **Phase A** (b1db67f) — docs/PARITY.md matrix + REQ seed (REQ-PARITY-AUDIT [doc] satisfied;
  REQ-SKILL-SUBNET / REQ-CCS-PROFILES seeded). DROP set confirmed absent.
- ✅ **Phase B** (0595910) — whoami skill DROPPED (manifest + body + stub + hint + test repoints);
  dead new-alarm stub removed. Surface 11→9.
- ✅ **Phase C** (e3382a0) — /sptc:subnet skill added (one skill wrapping `spt subnet`).
  REQ-SKILL-SUBNET [doc,int] green on v0.7.2. how-to-subnet finding logged. Surface 9→10.
- ⏭ **Phase D** (ccs profiles) — NEXT. Needs research: the spawn/psyche/echo command seams a ccs
  overlay leaf-replaces. Activate REQ-CCS-PROFILES [doc,impl] when starting.
- ⏭ **Phase E** (publish prep as sptc) — after D.

## 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).

-- ⏭ **Phase D** (ccs profiles) — NEXT. Needs research: the spawn/psyche/echo command seams a ccs

-  overlay leaf-replaces. Activate REQ-CCS-PROFILES [doc,impl] when starting.

-- ⏭ **Phase E** (publish prep as sptc) — after D.

+- ✅ **Phase D** (b15c1bf) — `claude-spt:ccs` profile. Scoped to bare `ccs` (default account, NOT

+  glm/kimi — operator ruling); validated vs sister project claude_skill_owl. Spawn seam

+  (`[profiles.ccs.session.self].command = "ccs"`, a drop-in for `claude`) + observability string

+  leaf. Log-dir seam landed IN the extractor: claude-spt-digest now honors `CLAUDE_CONFIG_DIR`

+  (owl `owlery::claude_projects_root` parity) — ccs relocates CC's transcript tree per-account, a

+  runtime value with no static path, so no `[profiles.ccs.digest]` leaf. REQ-CCS-PROFILES

+  [doc,impl,unit,int] green. Finding logged (SCOPE conflated ccs profiles vs accounts).

+  **docs/PARITY.md verdict → PROVEN (adapter surface).** Gate PASS; registration-int OK on v0.7.2.

+- ⏭ **Phase E** (publish prep as sptc) — NEXT. CHANGELOG.md + plugin.json bump + validate/package

+  skeleton dry-run + docs regen. cplugs publish = operator's step.


Now Phase E (publish prep). Gather current state: plugin.json, CHANGELOG existence, the publish scripts.


Bash

cd "C:\Users\decid\Documents\projects\spt-claude-code" && echo "===plugin.json==="; cat plugin/sptc/.claude-plugin/plugin.json 2>/dev/null || find . -name plugin.json -not -path '*/node_modules/*' 2>/dev/null; echo "===CHANGELOG?==="; ls CHANGELOG.md 2>/dev/null || echo "ABSENT"; echo "===publish scripts==="; ls ci/publish/ 2>/dev/null

Inspect Phase E inputs


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


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

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

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

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

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

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

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

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

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