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

public binary (observable behavior = public surface).

### Validation note (2026-06-15, against live v0.7.0 `spt adapter add` + the shipped mock-adapter)

Authored + registered `adapter/claude-spt.toml` against the real v0.7.0 binary
(`REQ-DIST-MANIFEST-SCHEMA` int — `ci/manifest/registration-int.sh`, 6/6 green). Confirms the
residual: the **published docs still omit** two things a manifest author needs, both learnable only
from the **shipped mock-adapter source** (which IS public surface) + live `adapter add` errors:

1. **Substitution-key catalog.** `{parent_pid}` and `{adapter_name}` are spt-filled SessionStart
   keys (mock `api seed --pid {parent_pid} … --adapter {adapter_name}`); the docs-site lists only a
   partial set. The full catalog is still code-only.
2. **`[digest]` cross-field rule.** The JSON schema accepts `[digest]` with just `extractor`, but
   `spt adapter add` **rejects** it: `[digest] needs source (own-source) or a [history]
   locate_template`. Not documented prose-side; surfaced only at registration. (Worked around with
   `source = "{home}/.claude/projects"` — CC's cwd-slug subdir is not expressible as a flat
   `locate_template`, so `source` names the per-project root and the extractor finds
   `{session_id}.jsonl` within. A CC-shaped extractor is the right design vs the mock's log-less
   `[history] native`.) **Non-blocking** (resolved by reading the shipped mock + the error text);
   logged so doyle can publish the key catalog + the digest source-requirement to the docs-site.

Also observed (not a gap, a roadmap fact): `spt endpoint run` notes the **interactive picker that
emits the `<basename>-<id>` shortcut "lands in a later wave"** — so the `sptc-<id>` *emission* int
(`REQ-DIST-SHORTCUT-BASENAME`) stays held on that wave (and on authoring `[session.self]`, which
`endpoint run` spawns). `shortcut_basename = "sptc"` is confirmed to round-trip into the stored
resolved manifest, so the declaration side is proven.

---

## F-002 — `api poll` (agent path) has no inter-frame delimiter — multi-message drains unsplittable

# spt-core public-surface findings

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

| # | Date | Status | Summary |
|---|------|--------|---------|
| F-001 | 2026-06-14 | **re-scoped 2026-06-15** — most = adapter-authoring (closed); 1 residual spt-core docs item open | Hook-wiring for a CC adapter — boundary clarified |
| F-002 | 2026-06-15 | **resolved-by-design** (ADR-0020) — envelope self-delimits; impl refactor pending | `api poll` agent path has no inter-frame delimiter → multi-message drains are unsplittable |

---

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

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

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

**The gaps:**

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

**Impact (as first reported):** SessionStart / Stop(idle) / SessionEnd / boundary are buildable now.
UserPromptSubmit + PreToolUse + Subagent wiring + the entire injection path were *thought* blocked.

### Resolution (2026-06-15 — converged with doyle, grounded in `CONTEXT.md`)

The boundary canon (`CONTEXT.md` L52/L181/L112/L149/L137) re-frames F-001: **spt-core is
harness-independent and supplies the agnostic `spt api` primitives + their I/O format; the adapter
authors the harness-specific wiring + output-formatting.** So most of F-001 is **adapter-authoring
work (ours), not an spt-core gap**:

- **Gap 1 (CC-event → api mapping) — OURS, closed.** Which CC hook drives which `api` primitive is
  adapter glue (L181). Owned mappings: SessionStart → `api seed` (+ env aliases); `/sptc:ready|live`
  → `api listen <id>` (the blocking poll loop — *not* SessionStart, which must not block); Stop/Idle
  → `api state idle|busy`; PreCompact/clear → `api boundary`; SessionEnd → `api session-end` /
  signoff → `api shutdown`; SubagentStart/Stop → `api worker-start`/`worker-stop`; UserPromptSubmit
  → `api poll`; **PreToolUse out-of-scope v1** (UPS covers delivery). See `docs/adr/0002-*`.
- **Gap 2 (medium) — OURS, closed.** The `sptc` plugin **hand-writes** its CC `hooks.json` shelling
  `spt api`. spt-core materializing a CC `hooks.json` would violate L52/L181 — it correctly doesn't.
- **Gap 3 (injection HOW) — OURS, closed.** `api poll` emits message frames to **stdout** by design
  (L149) — that IS the hook-injection delivery path; routing/formatting that stdout into CC's
  `additionalContext` channel is adapter glue (L112). The earlier "`can_inject`/`[inject]`
  method-discard is dead code" alarm is **retracted** — it is M2a roadmap staging (stdout/hook only
  now; PTY at M3), and `can_inject` drives the *built* echo-gate/relay fallback (L137) for hooks that
  cannot inject (e.g. CC Stop).
- **CC param-sourcing** ({parent_pid}/{session_id}) — OURS: read off the CC hook-input schema
  (`session_id` is a common stdin field; pid via the hook process). Not an spt-core concern.

**Residual spt-core item (doyle owns; non-blocking):** publish the **agnostic primitives** to the
adapter-facing docs-site — specifically `api poll`'s **emit frame format** (the frame we parse +
format for CC) and the **substitution-key catalog** (currently code-only). doyle verifies against
`CONTEXT.md`/ADRs then propagates (no new design). We will report the **observed** `api poll` frame
format so the publish can confirm-match. Until published we may rely on observed behavior of the
public binary (observable behavior = public surface).

---

## F-002 — `api poll` (agent path) has no inter-frame delimiter — multi-message drains unsplittable

**Reported:** 2026-06-15 to doyle. **Status:** open — **spt-core CODE gap** (not docs); doyle has
raised the framing as a contract decision to the operator.

**The gap (confirmed from source by doyle):** the **agent** `api poll` path emits each drained
message with `print!("{msg}")` (`delivery.rs:192`) and `format_row` adds **no trailing newline**
(`spool.rs:91-97`). So a multi-message drain **concatenates frames with nothing between them**.
Combined with the frame format (anonymous senders emit a **bare body**, and bodies may be
multiline), a multi-message `poll` stdout is **genuinely unsplittable** — there is no delimiter to
parse on. Tell that this is a rough edge, not intent: the **shell** drain (`cmd_poll_shell`,
`delivery.rs:169`) uses `println!` (newline-framed); only the agent path omits it. The framing is
unspecified in `CONTEXT.md` / `docs/api.md`.

**Impact / our handling:**
- **Single-message poll = unambiguous, works now** — `__REPLY_TO__:<id>\n<body>` or bare body.
- **Multi-frame splitter is HELD** — we do not assume a delimiter that does not exist. The
  UserPromptSubmit wrapper surfaces whatever `poll` returns: a single frame is cleanly per-sender
  formatted; a (rare) concatenated multi-drain degrades to a surfaced-but-unattributed blob rather
  than a parser guessing boundaries.

**Resolution — RESOLVED-BY-DESIGN (ADR-0020, operator-ruled 2026-06-15):** the framing question is
settled, more cleanly than a delimiter patch. The **canonical format at every surface — including
`api poll` — is the `<EVENT type="msg" from="<sender>">body</EVENT>` envelope** (`spt-proto::event`,
the ADR-0001 grammar the live listener already emits). `__REPLY_TO__` was a **mis-elevated relic**
(wrongly frozen as the "stable wire format" during the clean-room port) and is being **deleted from
spt-core**. The `<EVENT>` envelope is **self-delimiting**, so multi-message drains split cleanly on
`</EVENT>` — no delimiter needed, and the F-002 multi-frame hold is **lifted**.

- **Our parser** now targets `<EVENT>` (the `from` attr → `<sptc_messages from="…">`, `<br>` →
  newline, entity unescape with `&` last) — exactly the live-agent body-parsing rule. See
  `render_frames` in `plugin/sptc/hooks/_common.sh` + `tests/hooks-parse.sh` (multi-message covered).
- **Transitional (impl pending, spt-core side):** the refactor that makes `api poll` actually *emit*
  `<EVENT>` (`REQ-MSG-ENVELOPE`) is scoped in ADR-0020 but **not yet built** (multi-crate:
  spt-store/spt-msg/spt/spt-daemon/spt-live). The current 0.6.0 binary still emits the `__REPLY_TO__`
  relic at the poll surface. We therefore build for canonical `<EVENT>` but do **not** validate
  against current poll output; doyle pings when the refactor lands, and our throwaway byte-capture
  then confirms `<EVENT>`.

+### Validation note (2026-06-15, against live v0.7.0 `spt adapter add` + the shipped mock-adapter)

+Authored + registered `adapter/claude-spt.toml` against the real v0.7.0 binary

+(`REQ-DIST-MANIFEST-SCHEMA` int — `ci/manifest/registration-int.sh`, 6/6 green). Confirms the

+residual: the **published docs still omit** two things a manifest author needs, both learnable only

+from the **shipped mock-adapter source** (which IS public surface) + live `adapter add` errors:

+1. **Substitution-key catalog.** `{parent_pid}` and `{adapter_name}` are spt-filled SessionStart

+   keys (mock `api seed --pid {parent_pid} … --adapter {adapter_name}`); the docs-site lists only a

+   partial set. The full catalog is still code-only.

+2. **`[digest]` cross-field rule.** The JSON schema accepts `[digest]` with just `extractor`, but

+   `spt adapter add` **rejects** it: `[digest] needs source (own-source) or a [history]

+   locate_template`. Not documented prose-side; surfaced only at registration. (Worked around with

+   `source = "{home}/.claude/projects"` — CC's cwd-slug subdir is not expressible as a flat

+   `locate_template`, so `source` names the per-project root and the extractor finds

+   `{session_id}.jsonl` within. A CC-shaped extractor is the right design vs the mock's log-less

+   `[history] native`.) **Non-blocking** (resolved by reading the shipped mock + the error text);

+   logged so doyle can publish the key catalog + the digest source-requirement to the docs-site.

+Also observed (not a gap, a roadmap fact): `spt endpoint run` notes the **interactive picker that

+emits the `<basename>-<id>` shortcut "lands in a later wave"** — so the `sptc-<id>` *emission* int

+(`REQ-DIST-SHORTCUT-BASENAME`) stays held on that wave (and on authoring `[session.self]`, which

+`endpoint run` spawns). `shortcut_basename = "sptc"` is confirmed to round-trip into the stored

+resolved manifest, so the declaration side is proven.


Run full gates + trace, then commit slice 3b (the v0.7.0 ints).


Bash

cd /c/Users/decid/Documents/projects/spt-claude-code
sh ci/run-gates.sh 2>&1 | tail -8
echo "=== trace MANIFEST-SCHEMA ==="; traceable-reqs trace REQ-DIST-MANIFEST-SCHEMA 2>&1 | head -14


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

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

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


All 4 stages green, gates PASS. Commit slice 3b.


cd /c/Users/decid/Documents/projects/spt-claude-code
traceable-reqs check >/dev/null 2>&1 && echo "TRACE GREEN" || { echo RED; traceable-reqs check 2>&1 | tail; }
git add adapter/claude-spt.toml ci/manifest/registration-int.sh traceable-reqs.toml docs/SPT-CORE-FINDINGS.md
git commit -q -F - <<'EOF'
feat(adapter): prove manifest registration against live v0.7.0 (slice 3b)

Upgraded local spt-core to v0.7.0; ran the held registration int. spt-core's
cross-field validation (the 2nd layer beyond JSON Schema) surfaced a real
constraint our schema gate could not: [digest] requires source or a [history]
locate_template. Fixed honestly for CC's transcript layout.

- [digest] now declares source = "{home}/.claude/projects" (CC's per-project
  transcript root; the extractor finds {session_id}.jsonl within — the cwd-slug
  subdir is not expressible as a flat locate_template). Keeps the extractor seam
  (right for CC's rich JSONL vs the mock's log-less [history] native).
- SessionStart enriched to match the canonical mock-adapter: {parent_pid} +
  {adapter_name} are spt-filled keys.
- [profiles.deep] also overrides a [strings] leaf so the overlay is observable
  via get-string (base vs :deep differ).
- ci/manifest/registration-int.sh: SPTC_ACCEPTANCE-gated CLI proof — adapter add
  accepted, shipped profile resolves, strings + overlay read through the
  registry, clean soft-remove. 6/6 green. REQ-DIST-MANIFEST-SCHEMA → +int.
- SPT-CORE-FINDINGS: logged that the published docs omit the substitution-key
  catalog ({parent_pid}/{adapter_name}) and the [digest] cross-field rule (both
  learnable only from the shipped mock-adapter source + the live add error).

The sptc-<id> shortcut-emission int stays held: spt endpoint run's picker
"lands in a later wave" and needs [session.self]. shortcut_basename="sptc" is
confirmed to round-trip into the stored resolved manifest.

Gates PASS, traceable-reqs check green.

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


TRACE GREEN
435e1be feat(adapter): prove manifest registration against live v0.7.0 (slice 3b)
398aaa1 docs: mark manifest slices 1/2/3a done; sharpen v0.7.0-gated remainder
8cd8372 feat(plugin): add /sptc:whoami + /sptc:setup skill skeletons (slice 3a)


Slice 3b committed (`435e1be`). Manifest now **proven against live v0.7.0**. Update the plan's remainder.


Edit

   - **TODO** file-backed `[strings]` — lift the SKILL.md-skeleton bodies into adapter `[strings]`
     (UPS-injection source). Authoring buildable now; the **int** (binary serves file-backed
     strings) needs the **v0.7.0 binary locally** (have 0.6.0).
   - **TODO** `spt endpoint run` bringup + `cc`/`sptc` launcher — needs the **v0.7.0 binary** (the
     picker reads shortcut_basename, a v0.7.0 field). This is REQ-DIST-SHORTCUT-BASENAME's held impl+int.

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

   - **DONE (435e1be)** — **manifest proven against live v0.7.0**. Local spt upgraded to 0.7.0;
     `ci/manifest/registration-int.sh` (SPTC_ACCEPTANCE-gated) green 6/6: `spt adapter add`
     accepted (2nd-layer cross-field validation), shipped `:deep` profile resolves, `[strings]`
     read + overlay observable via `get-string`, clean soft-remove. **REQ-DIST-MANIFEST-SCHEMA →
     [doc,impl,unit,int]**. Cross-field discovery fixed: `[digest]` needs `source` →
     `source="{home}/.claude/projects"`. Findings logged (key catalog + digest rule undocumented).
   - **TODO** file-backed `[strings]` — inline `[strings]` + `get-string` PROVEN. Remaining: lift
     the SKILL.md-skeleton BODIES out as **file-backed** values (so the manifest doesn't bloat).
     ⚠ RESEARCH FIRST: the v0.7.0 `[strings]` schema is `additionalProperties` inline TOML with NO
     visible file-backed mechanism — confirm how (if) v0.7.0 externalizes large string bodies
     (get-string runtime include? adapter-shipped files via `[update]` file_pull?) before authoring.
   - **TODO** `[session.self]` + `spt endpoint run` bringup + `cc`/`sptc` launcher → unblocks the
     held **REQ-DIST-SHORTCUT-BASENAME** impl+int (the `<basename>-<id>` picker "lands in a later
     spt-core wave" per `endpoint run --help`; `endpoint run` spawns `[session.self]`, still deferred).
   - **TODO** digest extractor binary `claude-spt-digest` → unblocks `spt adapter digest-proof`
     (the deeper [digest] int; the seam is declared + registration-accepted, extractor unbuilt).

### Still held on spt-core (non-blocking)
- `sptc-<id>` shortcut EMISSION — spt-core picker wave (not in 0.7.0) + needs `[session.self]`.
- REQ-MSG-ENVELOPE poll→additionalContext bus-delivery int — doyle pings for byte-capture when
  todlando lands the ADR-0020 relic removal; I owe a confirm-match + a HOOKS-API/UPS-INJECTION int flip.

### Note: claude-spt is now a REGISTERED adapter artifact on this node (the v1 acceptance proof).
The int test soft-removes it each run; re-add with `spt adapter add adapter/claude-spt.toml`.
Local `spt` is **0.7.0** (operator-upgraded 2026-06-15).


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

   - **DONE (435e1be)** — **manifest proven against live v0.7.0**. Local spt upgraded to 0.7.0;
     `ci/manifest/registration-int.sh` (SPTC_ACCEPTANCE-gated) green 6/6: `spt adapter add`
     accepted (2nd-layer cross-field validation), shipped `:deep` profile resolves, `[strings]`
     read + overlay observable via `get-string`, clean soft-remove. **REQ-DIST-MANIFEST-SCHEMA →
     [doc,impl,unit,int]**. Cross-field discovery fixed: `[digest]` needs `source` →
     `source="{home}/.claude/projects"`. Findings logged (key catalog + digest rule undocumented).
   - **TODO** file-backed `[strings]` — inline `[strings]` + `get-string` PROVEN. Remaining: lift
     the SKILL.md-skeleton BODIES out as **file-backed** values (so the manifest doesn't bloat).
     ⚠ RESEARCH FIRST: the v0.7.0 `[strings]` schema is `additionalProperties` inline TOML with NO
     visible file-backed mechanism — confirm how (if) v0.7.0 externalizes large string bodies
     (get-string runtime include? adapter-shipped files via `[update]` file_pull?) before authoring.
   - **TODO** `[session.self]` + `spt endpoint run` bringup + `cc`/`sptc` launcher → unblocks the
     held **REQ-DIST-SHORTCUT-BASENAME** impl+int (the `<basename>-<id>` picker "lands in a later
     spt-core wave" per `endpoint run --help`; `endpoint run` spawns `[session.self]`, still deferred).
   - **TODO** digest extractor binary `claude-spt-digest` → unblocks `spt adapter digest-proof`
     (the deeper [digest] int; the seam is declared + registration-accepted, extractor unbuilt).

### Still held on spt-core (non-blocking)
- `sptc-<id>` shortcut EMISSION — spt-core picker wave (not in 0.7.0) + needs `[session.self]`.
- REQ-MSG-ENVELOPE poll→additionalContext bus-delivery int — doyle pings for byte-capture when
  todlando lands the ADR-0020 relic removal; I owe a confirm-match + a HOOKS-API/UPS-INJECTION int flip.

### Note: claude-spt is now a REGISTERED adapter artifact on this node (the v1 acceptance proof).
The int test soft-removes it each run; re-add with `spt adapter add adapter/claude-spt.toml`.
Local `spt` is **0.7.0** (operator-upgraded 2026-06-15).

# Adapter-manifest + M12 parity — JIT plan (next milestone)

> Fresh milestone after the CI/distribution slices. Authors the CC adapter **manifest** (publish
> target #2, the spt-core registry side) against the REAL v0.7.0 published surface, then the M12
> parity surfaces. NEEDS public-docs research first — run in clean context.

## Public-surface-only (hard constraint — re-read)

Build ONLY from `SaberMage/spt-releases` (v0.7.0) + `https://sabermage.github.io/spt-releases`
(harness-contract, CLI reference, manifest.schema.json, rustdoc, llms-full.txt). NEVER read
spt-core source. A missing capability = a FINDING for doyle/todlando, not a workaround. (AGENTS.md)

## Research first (before authoring)

1. Pull v0.7.0 `manifest.schema.json` from spt-releases — the authoritative shape the adapter
   manifest must validate against. Note required fields, `[adapter]` block, `[strings]` (file-backed
   large bodies), `shortcut_basename`, profiles/hints/`[digest]` extractor.
2. Read the published harness-contract + CLI reference for: `spt adapter` registration verbs,
   `spt endpoint run`, `shortcut_basename` semantics (emits `sptc-<id>` shortcuts), whoami-as-alias,
   adapter:profile fallback addressing.
3. Confirm where the manifest physically lives (spt-core-conducted registry, NOT plugin files —
   ADR-0001/SCOPE) and how it's validated locally.

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

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

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

## Still gated on spt-core (non-blocking)

poll→additionalContext bus-delivery `int` — REQ-MSG-ENVELOPE (todlando building, ADR-0020 relic
removal in flight). doyle pings for byte-capture when it lands; I owe him a confirm-match then,
and a flip of HOOKS-API/UPS-INJECTION `int`.

## Gate (every slice)

`sh ci/run-gates.sh` ⇒ PASS · `traceable-reqs check` green. Atomic commits, `Co-authored by: perri`.

## Session state at handoff (2026-06-15)

Shipped this session (all green, gates PASS, 16/16 traceable): CI ACCEPTANCE slice 1
(73677fd/d834611 — real-claude SUT + perch-collision guard, REQ-CI-ACCEPTANCE [doc,impl,int] +
REQ-HAZARD-PERCH-COLLISION); cplugs publish-prep (e28db92 — validate/package skeleton + untracked
a real perch-state leak, REQ-DIST-PLUGIN-SKELETON [doc,impl,unit]); mdBook docs-site
(d999c60/905c62c — drift-gated book + llms.txt, REQ-DOCS-SITE/REQ-DOCS-DRIFT). Collaborators:
doyle (design/gate) + todlando (builds spt-core) over the spt bus. v0.7.0/M12 is PUBLIC.

-   - **TODO** file-backed `[strings]` — lift the SKILL.md-skeleton bodies into adapter `[strings]`

-     (UPS-injection source). Authoring buildable now; the **int** (binary serves file-backed

-     strings) needs the **v0.7.0 binary locally** (have 0.6.0).

-   - **TODO** `spt endpoint run` bringup + `cc`/`sptc` launcher — needs the **v0.7.0 binary** (the

-     picker reads shortcut_basename, a v0.7.0 field). This is REQ-DIST-SHORTCUT-BASENAME's held impl+int.

+   - **DONE (435e1be)** — **manifest proven against live v0.7.0**. Local spt upgraded to 0.7.0;

+     `ci/manifest/registration-int.sh` (SPTC_ACCEPTANCE-gated) green 6/6: `spt adapter add`

+     accepted (2nd-layer cross-field validation), shipped `:deep` profile resolves, `[strings]`

+     read + overlay observable via `get-string`, clean soft-remove. **REQ-DIST-MANIFEST-SCHEMA →

+     [doc,impl,unit,int]**. Cross-field discovery fixed: `[digest]` needs `source` →

+     `source="{home}/.claude/projects"`. Findings logged (key catalog + digest rule undocumented).

+   - **TODO** file-backed `[strings]` — inline `[strings]` + `get-string` PROVEN. Remaining: lift

+     the SKILL.md-skeleton BODIES out as **file-backed** values (so the manifest doesn't bloat).

+     ⚠ RESEARCH FIRST: the v0.7.0 `[strings]` schema is `additionalProperties` inline TOML with NO

+     visible file-backed mechanism — confirm how (if) v0.7.0 externalizes large string bodies

+     (get-string runtime include? adapter-shipped files via `[update]` file_pull?) before authoring.

+   - **TODO** `[session.self]` + `spt endpoint run` bringup + `cc`/`sptc` launcher → unblocks the

+     held **REQ-DIST-SHORTCUT-BASENAME** impl+int (the `<basename>-<id>` picker "lands in a later

+     spt-core wave" per `endpoint run --help`; `endpoint run` spawns `[session.self]`, still deferred).

+   - **TODO** digest extractor binary `claude-spt-digest` → unblocks `spt adapter digest-proof`

+     (the deeper [digest] int; the seam is declared + registration-accepted, extractor unbuilt).

-Local `spt` is **0.6.0**; the v0.7.0 picker/strings ints need v0.7.0 installed. Upgrading spt-core

-on this machine could disrupt the **running live-agent perch** (perri runs on spt-core) — do NOT do

-it autonomously; confirm with the operator first. Until then the v0.7.0-gated ints stay held.

+### Still held on spt-core (non-blocking)

+- `sptc-<id>` shortcut EMISSION — spt-core picker wave (not in 0.7.0) + needs `[session.self]`.

+- REQ-MSG-ENVELOPE poll→additionalContext bus-delivery int — doyle pings for byte-capture when

+  todlando lands the ADR-0020 relic removal; I owe a confirm-match + a HOOKS-API/UPS-INJECTION int flip.

+### Note: claude-spt is now a REGISTERED adapter artifact on this node (the v1 acceptance proof).

+The int test soft-removes it each run; re-add with `spt adapter add adapter/claude-spt.toml`.

+Local `spt` is **0.7.0** (operator-upgraded 2026-06-15).


Bash

cd /c/Users/decid/Documents/projects/spt-claude-code
git add MANIFEST-PARITY-PLAN.md
git commit -q -F - <<'EOF'
docs: mark slice 3b done (manifest proven vs live v0.7.0); sharpen remainder

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


2a2f198 docs: mark slice 3b done (manifest proven vs live v0.7.0); sharpen remainder
435e1be feat(adapter): prove manifest registration against live v0.7.0 (slice 3b)
398aaa1 docs: mark manifest slices 1/2/3a done; sharpen v0.7.0-gated remainder
8cd8372 feat(plugin): add /sptc:whoami + /sptc:setup skill skeletons (slice 3a)
c93757d feat(adapter): guard shortcut_basename=sptc brand (slice 2)
bd16644 feat(adapter): author claude-spt manifest + manifest-schema gate (slice 1)


Done — clean milestone boundary. **The adapter manifest is now proven end-to-end against real spt-core v0.7.0** (the v1 acceptance proof's manifest half: doc→impl→unit→int all green).

## This session — 6 commits, all gates PASS, traceable-reqs green

| Commit | What |
|--------|------|
| `bd16644` | Manifest authored + offline schema gate |
| `c93757d` | `shortcut_basename=sptc` brand guard |
| `8cd8372` | `/sptc:whoami` + `/sptc:setup` skeletons |
| `435e1be` | **Registration int vs live v0.7.0** — `REQ-DIST-MANIFEST-SCHEMA [doc,impl,unit,int]` |
| `398aaa1`/`2a2f198` | Plan updates |

**Live-v0.7.0 proof (`ci/manifest/registration-int.sh`, 6/6):** `spt adapter add` accepted (2nd-layer cross-field validation), shipped `:deep` profile resolves, `[strings]` read + overlay observable via `get-string`, clean soft-remove.

**Real findings surfaced (logged for doyle):**
- `[digest]` cross-field rule (`needs source or [history].locate_template`) — passes JSON schema but rejected at registration. Not documented; learned from the live error. Fixed: `source="{home}/.claude/projects"`.
- Substitution-key catalog (`{parent_pid}`, `{adapter_name}`) undocumented — learned from shipped mock-adapter source.

## Remaining (all sharpened in the plan)
- **file-backed `[strings]`** — inline strings PROVEN; externalizing bodies needs research (v0.7.0 `[strings]` schema shows no file-backed mechanism).
- **`[session.self]` + `endpoint run` + `cc` launcher** → unblocks `sptc-<id>` emission int (picker is a *later* spt-core wave).
- **`claude-spt-digest` extractor binary** → unblocks `digest-proof`.
- still held: REQ-MSG-ENVELOPE (doyle byte-capture).

Next:
1. Research + author **file-backed `[strings]`**
2. Build **`[session.self]` + endpoint-run/cc-launcher**
3. Stop / checkpoint here

Which?


please make the `shortcut_basename=cc`. -let doyle know about your findings so he can ensure the official docs gaps are covered. --- /commune with immediate next steps and broad summary of the project's status + end goal. we'll resume in the next session


<owl_reachability_notice>
You have an active owl perch. When spawning subagents or doing work, use run_in_background: true so you stay reachable for incoming messages.
</owl_reachability_notice>


CAVEMAN MODE ACTIVE (full). Drop articles/filler/pleasantries/hedging. Fragments OK. Code/commits/security: write normal.


Three tasks. First: flip `shortcut_basename` → `cc` (decouples the launcher brand from the plugin name — `cc` matches the schema's own example + the M12 cc-launcher). Update manifest, the guard test, and the REQ note.


Edit

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

# Brands the `spt endpoint run` picker shortcut as `cc-<id>` (default would be `spt-<id>`) — this is
# the M12 `cc` launcher, and matches the schema's own example (spt-claude-code → `cc`). The launcher
# brand is DECOUPLED from the plugin name (`sptc`, skills `/sptc:*`): the s/sptc/spt/ succession seam
# (ADR-0001) renames the plugin, not this shortcut. Guarded by tests/manifest-shortcut.sh
# (unit->REQ-DIST-SHORTCUT-BASENAME).
shortcut_basename = "cc"


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

# Brands the `spt endpoint run` picker shortcut as `cc-<id>` (default would be `spt-<id>`) — this is
# the M12 `cc` launcher, and matches the schema's own example (spt-claude-code → `cc`). The launcher
# brand is DECOUPLED from the plugin name (`sptc`, skills `/sptc:*`): the s/sptc/spt/ succession seam
# (ADR-0001) renames the plugin, not this shortcut. Guarded by tests/manifest-shortcut.sh
# (unit->REQ-DIST-SHORTCUT-BASENAME).
shortcut_basename = "cc"

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

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

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

[hooks.SessionStart]
# Seed the endpoint. {parent_pid} + {adapter_name} are spt-filled keys (confirmed against the
# published mock-adapter manifest); session_id is a CC stdin field. NOT the blocking poll/listen
# loop — that is /sptc:ready|live, never SessionStart.
fires = "api seed --session-id {session_id} --pid {parent_pid} --adapter {adapter_name}"
reads = ["session_id", "parent_pid"]
can_inject = true

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

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

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

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

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

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

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

# ── [digest] ───────────────────────────────────────────────────────────────────────────────
# Session-digest extractor seam (ADR-0019). CC's transcript is JSONL (one event per line); the
# extractor maps that native log → the published {role,text,tool,ts} digest-record contract,
# one JSON record per output line. (CC has a rich log, so the extractor seam fits — a defensible
# divergence from the deliberately log-less mock adapter, which declares no [digest].)
#
# spt-core's registration cross-field rule requires [digest] to name where it reads from (`source`
# or a [history] `locate_template`) — verified against the live v0.7.0 `spt adapter add`. CC stores
# transcripts at `{home}/.claude/projects/<cwd-slug>/<session_id>.jsonl`; the cwd-slug subdir is a
# CC-internal encoding NOT expressible as a flat template with the published key catalog, so
# `source` names the per-project ROOT dir and the extractor finds `{session_id}.jsonl` within it
# (the `source` field documents accepting "log file(s)" — a dir is valid). The extractor binary
# ships in the spt-core-conducted layer (built in a later parity slice); this is its declaration.
[digest]
extractor = "claude-spt-digest --session {session_id} --in {source}"
source = "{home}/.claude/projects"
window_turns = 6
arg_truncation = 240
sprint_collapse = true

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

[profiles.deep.strings]
adapter_label = "Claude Code (spt, deep)"

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

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

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

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

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

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

-# Brands the `spt endpoint run` picker shortcut as `sptc-<id>` (default would be `spt-<id>`).

-# `sptc` is the single-substitution succession seam (ADR-0001): parity-proof flips s/sptc/spt/.

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

+# Brands the `spt endpoint run` picker shortcut as `cc-<id>` (default would be `spt-<id>`) — this is

+# the M12 `cc` launcher, and matches the schema's own example (spt-claude-code → `cc`). The launcher

+# brand is DECOUPLED from the plugin name (`sptc`, skills `/sptc:*`): the s/sptc/spt/ succession seam

+# (ADR-0001) renames the plugin, not this shortcut. Guarded by tests/manifest-shortcut.sh


please make the `shortcut_basename=cc`. -let doyle know about your findings so he can ensure the official docs gaps are covered. --- /commune with immediate next steps and broad summary of the project…


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

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

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

# Brand-value regression guard for the `spt endpoint run` shortcut: the manifest MUST declare
# adapter.shortcut_basename = "cc" (→ cc-<id>, the M12 cc launcher). A drift to "sptc"/"spt"/default
# is caught here, distinct from the structural manifest-schema check. (The launcher brand is
# decoupled from the plugin name `sptc`; the s/sptc/spt/ succession renames the plugin, not this.)
# Run: sh tests/manifest-shortcut.sh (exit 0 = pass).
# [unit->REQ-DIST-SHORTCUT-BASENAME]
ROOT=$(CDPATH= cd "$(dirname "$0")/.." && pwd)
MANIFEST="$ROOT/adapter/claude-spt.toml"
fail=0

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

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


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

# Brand-value regression guard for the `spt endpoint run` shortcut: the manifest MUST declare
# adapter.shortcut_basename = "cc" (→ cc-<id>, the M12 cc launcher). A drift to "sptc"/"spt"/default
# is caught here, distinct from the structural manifest-schema check. (The launcher brand is
# decoupled from the plugin name `sptc`; the s/sptc/spt/ succession renames the plugin, not this.)
# Run: sh tests/manifest-shortcut.sh (exit 0 = pass).
# [unit->REQ-DIST-SHORTCUT-BASENAME]
ROOT=$(CDPATH= cd "$(dirname "$0")/.." && pwd)
MANIFEST="$ROOT/adapter/claude-spt.toml"
fail=0

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

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

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

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

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

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

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

-# adapter.shortcut_basename = "sptc" (→ sptc-<id>). This is the s/sptc/spt/ succession seam

-# (ADR-0001) — a premature flip to "spt", or a drift to "cc"/default, is caught here, distinct

-# from the structural manifest-schema check. Run: sh tests/manifest-shortcut.sh (exit 0 = pass).

+# adapter.shortcut_basename = "cc" (→ cc-<id>, the M12 cc launcher). A drift to "sptc"/"spt"/default

+# is caught here, distinct from the structural manifest-schema check. (The launcher brand is

+# decoupled from the plugin name `sptc`; the s/sptc/spt/ succession renames the plugin, not this.)

+# Run: sh tests/manifest-shortcut.sh (exit 0 = pass).

-  *'"sptc"'*) echo "ok   shortcut_basename = \"sptc\" (sptc-<id> brand intact)" ;;

-  *) echo "FAIL shortcut_basename is not \"sptc\": $line"; fail=1 ;;

+  *'"cc"'*) echo "ok   shortcut_basename = \"cc\" (cc-<id> brand intact)" ;;

+  *) echo "FAIL shortcut_basename is not \"cc\": $line"; fail=1 ;;


Update the REQ note + retarget the ADR doc evidence (shortcut is now decoupled from the plugin-name succession). Read the ADR region.


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

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


- New obligation: a dual update path kept in sync — file-pull (real) + a cautionary
  `claude plugin update` (skeleton sync only).
<!-- [doc->REQ-DIST-SHORTCUT-BASENAME] -->
- Forces the cplugs **plugin-name** question, now **ruled (operator, 2026-06-14)**: because CC

- New obligation: a dual update path kept in sync — file-pull (real) + a cautionary
  `claude plugin update` (skeleton sync only).
<!-- [doc->REQ-DIST-SHORTCUT-BASENAME] -->
- The manifest brands the `spt endpoint run` launcher shortcut **`cc-<id>`** via
  `shortcut_basename = "cc"` (the M12 `cc` launcher; the harness-agnostic default would be
  `spt-<id>`, and it matches the schema's own `spt-claude-code → cc` example). This launcher brand
  is **decoupled** from the `sptc` plugin name — the `s/sptc/spt/` succession (next bullet) renames
  the plugin, not this shortcut. (Operator-ruled 2026-06-15.)
- Forces the cplugs **plugin-name** question, now **ruled (operator, 2026-06-14)**: because CC


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

- New obligation: a dual update path kept in sync — file-pull (real) + a cautionary
  `claude plugin update` (skeleton sync only).
<!-- [doc->REQ-DIST-SHORTCUT-BASENAME] -->
- The manifest brands the `spt endpoint run` launcher shortcut **`cc-<id>`** via
  `shortcut_basename = "cc"` (the M12 `cc` launcher; the harness-agnostic default would be
  `spt-<id>`, and it matches the schema's own `spt-claude-code → cc` example). This launcher brand
  is **decoupled** from the `sptc` plugin name — the `s/sptc/spt/` succession (next bullet) renames
  the plugin, not this shortcut. (Operator-ruled 2026-06-15.)
- Forces the cplugs **plugin-name** question, now **ruled (operator, 2026-06-14)**: because CC

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

## Status

accepted (2026-06-14)

## Context

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

Forces:

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

## Decision

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

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

Concretely:

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

### Rejected alternatives

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

### Open / to-confirm

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

## Consequences

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

+- The manifest brands the `spt endpoint run` launcher shortcut **`cc-<id>`** via

+  `shortcut_basename = "cc"` (the M12 `cc` launcher; the harness-agnostic default would be

+  `spt-<id>`, and it matches the schema's own `spt-claude-code → cc` example). This launcher brand

+  is **decoupled** from the `sptc` plugin name — the `s/sptc/spt/` succession (next bullet) renames

+  the plugin, not this shortcut. (Operator-ruled 2026-06-15.)


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

title = "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)"
required_stages = ["doc", "unit"]  # doc: ADR-0001 (cc launcher brand, decoupled from the s/sptc/spt/ plugin-name succession). unit: tests/manifest-shortcut.sh (brand-value regression guard on the declarative field). impl (the cc launcher) + int (real `spt endpoint run` picker emits cc-<id>) deferred to the endpoint-run/cc-launcher parity slice — the shortcut-emitting picker "lands in a later spt-core wave" (per `endpoint run --help`) and needs `[session.self]`. The field itself is declarative config, not production code, so it carries no impl tag (the scanner classifies impl by file type — config is not impl).


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

title = "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)"
required_stages = ["doc", "unit"]  # doc: ADR-0001 (cc launcher brand, decoupled from the s/sptc/spt/ plugin-name succession). unit: tests/manifest-shortcut.sh (brand-value regression guard on the declarative field). impl (the cc launcher) + int (real `spt endpoint run` picker emits cc-<id>) deferred to the endpoint-run/cc-launcher parity slice — the shortcut-emitting picker "lands in a later spt-core wave" (per `endpoint run --help`) and needs `[session.self]`. The field itself is declarative config, not production code, so it carries no impl tag (the scanner classifies impl by file type — config is not impl).

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

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

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

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

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

[[requirements]]
id = "REQ-DIST-PLUGIN-SKELETON"
title = "Marketplace artifact on cplugs is a thin skeleton: namespaced /sptc:* skill stubs + hooks.json + plugin.json, no embedded logic"
required_stages = ["doc", "impl", "unit"]  # doc: ADR-0001 + RELEASE-RUNBOOK. impl: ci/publish/{validate,package}-skeleton.sh (installability gate + publish mechanic). unit: tests/skeleton-validate.sh (pass real + fail tampered).

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

[[requirements]]
id = "REQ-DIST-HOOKS-API"
title = "hooks.json delegates to `spt api` rather than carrying adapter logic"
required_stages = ["doc", "impl", "unit"]  # doc: ADR-0001/0002. impl: hooks.json + wrappers. unit: tests/hooks-parse.sh. int (real-CC e2e) held for throwaway validation.

[[requirements]]
id = "REQ-DIST-MANIFEST-SCHEMA"
title = "CC adapter manifest validates against spt-core's published manifest.schema.json (from spt-releases)"
required_stages = ["doc", "impl", "unit", "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".

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

[[requirements]]
id = "REQ-UPS-INJECTION"
title = "UserPromptSubmit hook detects /sptc:X and injects X's real instructions as additionalContext (SKILL.md files stay skeletons); must be empirically confirmed UPS fires on slash-commands"
required_stages = ["doc", "impl", "unit"]  # doc: ADR-0002. impl: user-prompt-submit.sh. unit: render_frames test. int (UPS-fires + multi-frame post-F-002) held for throwaway validation.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

+title = "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)"

+required_stages = ["doc", "unit"]  # doc: ADR-0001 (cc launcher brand, decoupled from the s/sptc/spt/ plugin-name succession). unit: tests/manifest-shortcut.sh (brand-value regression guard on the declarative field). impl (the cc launcher) + int (real `spt endpoint run` picker emits cc-<id>) deferred to the endpoint-run/cc-launcher parity slice — the shortcut-emitting picker "lands in a later spt-core wave" (per `endpoint run --help`) and needs `[session.self]`. The field itself is declarative config, not production code, so it carries no impl tag (the scanner classifies impl by file type — config is not impl).


Re-run gates, shortcut test, and the registration int (confirm `cc` registers against v0.7.0).


Bash

cd /c/Users/decid/Documents/projects/spt-claude-code
echo "=== shortcut test ==="; sh tests/manifest-shortcut.sh
echo "=== schema gate ==="; sh ci/manifest/check-manifest.sh | tail -1
echo "=== registration int (cc vs v0.7.0) ==="; SPTC_ACCEPTANCE=1 sh ci/manifest/registration-int.sh | tail -3
echo "=== trace ==="; traceable-reqs check >/dev/null 2>&1 && echo GREEN || echo RED


=== shortcut test ===
ok   shortcut_basename = "cc" (cc-<id> brand intact)
ok   single shortcut_basename assignment
MANIFEST-SHORTCUT OK
=== schema gate ===
ok   manifest validates against published manifest.schema.json
=== registration int (cc vs v0.7.0) ===
ok   overlay observable: :deep adapter_label differs
ok   removed (soft-deregistered)
MANIFEST-REGISTRATION-INT OK
=== trace ===
GREEN


All green with `cc`. Commit.


cd /c/Users/decid/Documents/projects/spt-claude-code
git add adapter/claude-spt.toml tests/manifest-shortcut.sh docs/adr/0001-distribution-splits-by-volatility.md traceable-reqs.toml
git commit -q -F - <<'EOF'
feat(adapter): brand shortcut_basename=cc (cc-<id> launcher)

Operator-ruled: the spt endpoint run launcher shortcut is `cc-<id>`, not
`sptc-<id>`. This decouples the launcher brand from the plugin name (`sptc`,
skills `/sptc:*`) — the s/sptc/spt/ succession renames the plugin, not the
shortcut — and matches the schema's own `spt-claude-code → cc` example + the
M12 `cc` launcher surface.

- adapter/claude-spt.toml: shortcut_basename = "cc".
- tests/manifest-shortcut.sh: guard the "cc" brand.
- ADR-0001: dedicated bullet documenting the cc launcher (decoupled from
  succession); REQ-DIST-SHORTCUT-BASENAME doc retargeted there.
- Re-proven against live v0.7.0 (registration int green).

Gates PASS, traceable-reqs check green.

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


be59133 feat(adapter): brand shortcut_basename=cc (cc-<id> launcher)


Task 1 done. Task 2: send doyle the findings over the bus. Discover the owl binary.


echo "OWL env: ${OWL:-<unset>}"; echo "LIVE env: ${LIVE:-<unset>}"
echo "--- locate owl ---"; command -v owl 2>&1; ls "$HOME/AppData/Local"/spt*/bin 2>&1 | head
echo "--- spt send? ---"; spt send --help 2>&1 | head -8
echo "--- spt api emit/help ---"; spt --help 2>&1 | grep -iE 'send|owl|msg|message' | head


OWL env: C:/Users/decid/.claude/plugins/cache/cplugs/spt/1.11.25/owl.exe
LIVE env: C:/Users/decid/.claude/plugins/cache/cplugs/spt/1.11.25/owl.exe live
--- locate owl ---
spt.exe
spt.exe.old-10
spt.exe.old-11
spt.exe.old-12
spt.exe.old-5
spt.exe.old-6
spt.exe.old-7
spt.exe.old-8
spt.exe.old-9
--- spt send? ---
Send a message (body read from stdin); fire-and-forget

Usage: spt.exe send [OPTIONS] [TARGET]

Arguments:
  [TARGET]  Target perch id. May be omitted when --reply-to is given

Options:
--- spt api emit/help ---
  help      Print this message or the help of the given subcommand(s)
  notif     Inspect and acknowledge notifications
  ring      Send and block for a reply (body read from stdin)
  send      Send a message (body read from stdin); fire-and-forget


`$OWL` found. Send doyle the findings (sender `perri` explicit, body on stdin).


cd /c/Users/decid/Documents/projects/spt-claude-code
"$OWL" send doyle perri <<'MSG'
perri → doyle. Built the claude-spt adapter manifest against PUBLISHED v0.7.0 surface only + registered it vs the live binary (spt adapter add accepted, profile + strings resolve, soft-remove clean — the manifest half of the v1 acceptance proof is green end-to-end). Surfaced 2 PUBLISHED-DOCS gaps for you to cover so the next adapter author doesn't need the mock source to learn them:

1. Substitution-key catalog is incomplete on the docs-site. {parent_pid} and {adapter_name} are spt-filled SessionStart keys (mock manifest: `api seed --pid {parent_pid} ... --adapter {adapter_name}`), but the harness-contract/manifest reference lists only a partial set. The full {key} catalog is still code-only. Please publish it (this is the residual F-001 doc item, now concretely confirmed).

2. [digest] cross-field rule is undocumented prose-side. The JSON schema accepts [digest] with just `extractor`, but `spt adapter add` REJECTS it: "[digest] needs source (own-source) or a [history] locate_template". Surfaced only at registration, not in the manifest.md prose. Worth a sentence in the [digest] docs. (I worked around it with source="{home}/.claude/projects" since CC's cwd-slug transcript subdir isn't expressible as a flat locate_template — the extractor finds {session_id}.jsonl under that root.)

Neither blocked me (resolved from the shipped mock-adapter source + the live error text = public surface), so these are docs-completeness findings, not contract gaps. Logged in docs/SPT-CORE-FINDINGS.md.

Also confirming a roadmap fact, not a gap: `spt endpoint run --help` says the interactive picker that emits the <basename>-<id> shortcut "lands in a later wave". So my shortcut_basename="cc" (cc-<id> launcher) declaration round-trips into the stored manifest, but the emission int stays held on that picker wave. No action needed.

Still owe you the REQ-MSG-ENVELOPE byte-capture when todlando lands the ADR-0020 relic removal — ping me.
MSG
echo "send rc=$?"


➜ SENT:doyle
send rc=0


Sent to doyle. Task 3: commune the checkpoint.


Launching skill: spt:commune


Base directory for this skill: C:\Users\decid\.ccs\instances\bigscreen\plugins\cache\cplugs\spt\1.11.25\skills\commune

# /spt:commune

All commands use `$LIVE` env var, auto-injected by the plugin's SessionStart hook. If commands fail with "command not found", restart the Claude Code session so the SessionStart hook re-runs.

> **Identity auto-detection:** Your identity is auto-detected from your session. Pass your ID explicitly only if auto-detection fails.

## Flow

1. **Download Psyche's *current* context** — it shifts during the session as background echo communes fire. Re-download right before composing; the gen-N snapshot you read at session start is already stale:
   ```bash
   $LIVE psyche-download <your-id>
   ```

   > **Output truncation.** If the Bash tool reports `Output too large (N KB)` and saves the full result to a `tool-results/<id>.txt` file, use the `Read` tool on that saved path to access the full content. Do NOT re-run `psyche-download` to grep/awk out sections — repeated invocations re-download the same context, waste tokens, and may duplicate `## Pending Commune` sections in `live_context.md`.

2. **Diff your knowledge against this fresh download** — only what's missing from *it* is the real delta (new work, decisions, intentions). Anything already absorbed is not. If the download includes a `<memformat>` section, use it as a guide for what topics to cover. A `## Pending Commune (uncommitted)` section means a previous commune is still queued — do NOT re-send it.

3. **Commune only the delta** -- use the **Write tool** to create
   `.claude/{your-id}-commune.md` with the two-slice body as its contents
   (see `## Two-slice body shape (Phase 25 D-10/D-11)` below for the required
   envelope wrapping). Do not use Bash/heredoc -- Write is the canonical path:
   no shell escaping, no `EOF` collision risk, and Write auto-creates the
   `.claude/` parent directory.

   Your Self listener detects the file on its next poll iteration, notifies
   your Psyche wrapper, and the wrapper ingests the content via the existing
   Psyche session. On success (subprocess exit code 0) the wrapper deletes the
   file. On error (token rate limit, etc) the file is retained and retried on
   the next consume cycle. If you write again before the wrapper consumes, the
   latest write wins (last-write-wins overwrite semantics).

If Psyche's context is already current, no commune is needed. Say so and move on.

Read `commune.md` (in this skill directory) for the full commune protocol -- when to send, what to include, and diligence triggers.

**Agent-type guard:** Only works on live agents. If target is not live, refuses with: "Communes require a live agent with Psyche."

## Two-slice body shape (Phase 25 D-10/D-11)

The body you write in `.claude/{your-id}-commune.md` MUST itself be wrapped in two-slice envelopes per Phase 25 D-10/D-11. The taxonomy — what belongs in `<live-context>` vs `<project-context>` — is defined canonically in `psyche.md` §`<output_envelope>` (lines 282–323). Read that section as the source of truth; this skill doc teaches only when and how to apply it.

**In-project detection rule (D-25.1-04).** Before composing your commune body, run `$LIVE psyche-download <your-id>` (Step 1 of `## Flow` above) and inspect the `<current ... />` tag at the top of the output. The `<current/>` tag carries a `project="..."` attribute whenever any payload is produced — its presence means you are inside a tracked project (inside a git repo, `project` is the repo name; outside a git repo per D-11, `project` is the cwd-basename fallback, which still counts as a project context for routing purposes). A `psyche-download` invocation that returns no payload at all (NO-CONTEXT exit) means no project resolved. So: `project="..."` populated on `<current/>` → emit BOTH envelopes (apply rule 1 or 2 below); no payload at all → emit ONLY `<live-context>` (rule 3). Secondary signal: the `<project-context-resolved name="..."/>` sentinel further down the output indicates the project file already has prior content — useful for spotting first-time-in-project pairings, but NOT the routing rule.

**Three rules (D-25.1-01..03):**

- **In-project, both slices populated** (marker present, you have project-specific content this cycle): emit `<live-context>...</live-context>` AND `<project-context>...</project-context>`, both with non-empty bodies.
- **In-project, no project-specific update this cycle** (marker present, but nothing project-bound to report): emit `<live-context>...</live-context>` AND `<project-context></project-context>` (empty body — the deliberate "in-project but quiet" signal per D-25.1-02). Distinguishable from "no project resolved".
- **Outside any tracked project** (marker absent): emit ONLY `<live-context>...</live-context>`. Do NOT emit an empty `<project-context>` envelope (missing tag means "no project resolved" per D-25.1-03).

**Nested envelopes.** The Phase 23 `<EVENT type="commune">` envelope (see `## Envelope shape (Phase 23 v1.8)` below) is composed by `$LIVE commune` automatically — you NEVER write `<EVENT>` tags. You write ONLY the inner two-slice body. The two envelope layers nest: outer EVENT wraps inner two-slice body.

(See `psyche.md` §`<output_envelope>` for the canonical taxonomy and per-slice content rules.)

## Envelope shape (Phase 23 v1.8)

As of v1.8 Phase 23, commune deliveries are wrapped in a typed EVENT envelope carrying a 5-field stamp (`machine`, `project`, `branch`, `head_sha`, `head_subject`); the prose `COMMUNE (timestamp): body` form is gone.

```xml
<EVENT type="commune" timestamp="2026-05-20T06:00:00-07:00" machine="hostname" project="claude_skill_owl" branch="main" head_sha="abc123..." head_subject="subject line capped at 72 chars">body</EVENT>
```

Outside a git repo (D-11), `branch`, `head_sha`, and `head_subject` are omitted entirely (not empty-string-set):

```xml
<EVENT type="commune" timestamp="2026-05-20T06:00:00-07:00" machine="hostname" project="cwd-basename">body</EVENT>
```

`$LIVE commune` composes this envelope automatically — Self does NOT manually construct EVENT XML. The `body` placeholder in the example above is the two-slice body (see `## Two-slice body shape (Phase 25 D-10/D-11)` above); the two envelope layers nest. The wrapper-side parser ignores unknown attrs, so the stamp fields are forward-compatible with older receivers. For drift detection on resume, see the `live` skill.


Follow protocol — download fresh Psyche context first (it shifts during session).


cd /c/Users/decid/Documents/projects/spt-claude-code
"$LIVE" psyche-download perri 2>&1 | head -60


/usr/bin/bash: line 6: C:/Users/decid/.claude/plugins/cache/cplugs/spt/1.11.25/owl.exe live: No such file or directory


`$LIVE` is `owl.exe live` (two tokens) — quoting broke it. Use `$OWL live`.


cd /c/Users/decid/Documents/projects/spt-claude-code
"$OWL" live psyche-download perri 2>&1 | head -70


<current machine="HFENDULEAM" project="spt-claude-code" branch="main" head_sha="be591338727c20126be2898310573842531accfa" head_subject="feat(adapter): brand shortcut_basename=cc (cc-<id> launcher)" commits_since="0" commits_unpulled="0"/>
**ADAPTER-MANIFEST MILESTONE COMPLETE (4 commits shipped, all green)**

Manifest + shortcut_basename + whoami/setup skeletons all authored and gated. Real spt-core v0.7.0 now live on machine (upgrade confirmed; perch survived). Integration tests for manifest registration + endpoint-run now unblocked.

**Decisions locked:**
- `shortcut_basename = sptc` (seam to v0.7.0 picker)
- `impl` classification by file type (`.toml` config is `[doc,unit]`, not `impl`)
- Deferred `[session]`/roles, `[history]`, `[update]` — would invent unbuilt seams

**Next gate:** real `spt adapter add` validation (REQ-DIST-MANIFEST-SCHEMA int). File-backed `[strings]` int + cc-launcher int pending v0.7.0 binary seams.

**Poll envelope (REQ-MSG-ENVELOPE) status:** still waiting on doyle's ADR-0020 relic-removal signal + byte-capture confirm before flipping HOOKS-API/UPS-INJECTION `int`. Ready to confirm match when todlando lands the relic removal.

Perch loop remains reachable across the upgrade; no disruption observed.<project-context-resolved name="spt-claude-code"/>
**MANIFEST MILESTONE: shipped 4 commits (bd16644, c93757d, 8cd8372, 398aaa1)**

1. `bd16644`: Manifest authoring — full adapter config (shortcuts, hooks×6, identity, inject, digest, profiles.deep, strings + hints), vendored schema, offline gate (Python tomllib/tomli + jsonschema). **REQ-DIST-MANIFEST-SCHEMA** `[doc,impl,unit]` **green**.
2. `c93757d`: `shortcut_basename = sptc` test locked. **REQ-DIST-SHORTCUT-BASENAME** `[doc,unit]` **green**.
3. `8cd8372`: `/sptc:whoami` + `/sptc:setup` skeletons added (all 11 skills now validate).
4. `398aaa1`: Plan updated; milestone boundary marked.

**Req count (MANIFEST): 3 REQs shipped** — all gated and traceable.

**Remaining (v0.7.0-gated):**
- File-backed `[strings]` int (binary serves skill bodies)
- `spt endpoint run` + `cc`/`sptc` launcher int (picker reads `shortcut_basename`)

**Integration next:** execute REQ-DIST-MANIFEST-SCHEMA `int` (real `spt adapter add` validation via v0.7.0 binary).


Psyche context is stale (pre-3b: says `sptc`, "next gate: adapter add"). No pending-commune section — safe to write the delta. `project=` present → emit both slices. Write the commune body.


Write

<live-context>
## perri — checkpoint 2026-06-15 (session-end; resume next session)

**CORRECTION to my last echo-commune** (it predates this work, says wrong things):
- `shortcut_basename` is now **`cc`** (operator-ruled), NOT `sptc`. The launcher brand `cc-<id>`
  is DECOUPLED from the plugin name `sptc`; the s/sptc/spt/ succession renames the plugin, not the
  shortcut. (Matches the schema's own `spt-claude-code → cc` example + the M12 cc launcher.)
- The "next gate: real adapter add" is **DONE** — registration int is GREEN vs live v0.7.0.

**How I work this project** (unchanged): build from spt-core PUBLIC surface ONLY (spt-releases
v0.7.0 + sabermage.github.io/spt-releases); NEVER read spt-core source; a missing capability =
a FINDING for doyle/todlando, not a workaround. Small atomic commits, `Co-authored by: perri`,
`sh ci/run-gates.sh` ⇒ PASS + `traceable-reqs check` green before declaring done. Activate-don't-
pre-fail on REQs. Bus replies: `$OWL send doyle perri` / `$OWL send todlando perri` (pass `perri`
explicitly; do NOT use --reply-to). Local `spt` is now **0.7.0** (operator-upgraded; perch survived).

**Open threads I own:**
- **doyle** — just sent (this session) 2 published-docs-gap findings from building the manifest:
  (1) substitution-key catalog incomplete on docs-site ({parent_pid},{adapter_name} are spt-filled
  but only in mock source); (2) [digest] cross-field rule undocumented (schema accepts [digest] w/
  just extractor, but `adapter add` rejects: needs source or [history].locate_template). Both
  non-blocking (resolved from mock source + live error). Awaiting any doyle ack.
- **REQ-MSG-ENVELOPE** — still owe doyle the poll→additionalContext byte-capture when todlando lands
  the ADR-0020 relic removal; then I flip HOOKS-API / UPS-INJECTION `int`. (Unchanged, still pending.)

**Immediate next steps (act on resume, in MANIFEST-PARITY-PLAN.md):**
1. **file-backed `[strings]`** — RESEARCH FIRST: inline strings + `get-string` are PROVEN, but the
   v0.7.0 `[strings]` schema is inline TOML with NO visible file-backed mechanism. Confirm how (if)
   v0.7.0 externalizes large string bodies BEFORE authoring (get-string include? files via [update]
   file_pull?). Then lift the SKILL.md skeleton bodies out.
2. **`[session.self]` + `spt endpoint run` + `cc` launcher** → unblocks the held
   REQ-DIST-SHORTCUT-BASENAME impl+int. NOTE: the shortcut-emitting picker "lands in a LATER
   spt-core wave" (per `endpoint run --help`) — emission int stays held on that wave regardless.
3. **`claude-spt-digest` extractor binary** → unblocks `spt adapter digest-proof` (deeper [digest] int).
</live-context>
<project-context>
## spt-claude-code — status 2026-06-15

**End goal:** `claude-spt` is spt-core's v1 acceptance proof — (1) parity with legacy
claude_skill_owl delegating core to the `spt` binary; (2) first casual-end-user entrypoint, a CC
plugin on cplugs (name `sptc`, skills `/sptc:*`; succession flips sptc→spt on parity-proof);
(3) invisible spt-core installer (SessionStart bootstrap). Distribution splits by volatility
(ADR-0001): thin skeleton on cplugs; volatile bulk (binary + adapter manifest) spt-core-conducted.

**ADAPTER-MANIFEST MILESTONE — the manifest half is now GREEN END-TO-END vs real v0.7.0.**
Commits this session (8 total, all gates PASS, traceable green):
- `bd16644` manifest authored + offline schema gate (py tomllib|tomli + jsonschema).
- `c93757d` shortcut brand guard.
- `8cd8372` /sptc:whoami + /sptc:setup skeletons (11 skills validate).
- `435e1be` **registration int vs live v0.7.0** — `spt adapter add` accepted (2nd-layer cross-field
  validation), shipped `:deep` profile resolves, `[strings]` read + overlay observable via
  get-string, clean soft-remove. `ci/manifest/registration-int.sh` (SPTC_ACCEPTANCE-gated, 6/6).
- `be59133` **shortcut_basename = "cc"** (operator-ruled; cc-<id> launcher, decoupled from plugin name).
- plan/docs commits (398aaa1, 2a2f198) + this commune.

**REQs:** REQ-DIST-MANIFEST-SCHEMA → **[doc,impl,unit,int] GREEN**. REQ-DIST-SHORTCUT-BASENAME
[doc,unit] green (impl=cc launcher + int=picker emits cc-<id> deferred; picker is a later spt-core wave).

**Key manifest decisions:** `[digest]` keeps the extractor seam (CC has rich JSONL — defensible
divergence from the log-less mock) with `source="{home}/.claude/projects"` (CC's cwd-slug subdir
isn't expressible as a flat locate_template; extractor finds {session_id}.jsonl within). SessionStart
uses {parent_pid}+{adapter_name} (confirmed from mock). Deferred with notes: `[session]`/roles,
`[history]`, `[update]` (file_pull needs a signing key + registry repo not on the public surface — a
finding). claude-spt IS NOW a registered adapter artifact on this node (the int soft-removes each run).

**Remaining milestone work:** file-backed `[strings]` (research first), `[session.self]`+endpoint-run
+cc-launcher, `claude-spt-digest` extractor. Then ship when REQ-MSG-ENVELOPE lands + parity proven.

**Orientation:** SCOPE.md (ledger), HANDOFF.md (public-surface constraint), CONTEXT.md (glossary),
AGENTS.md (rules), MANIFEST-PARITY-PLAN.md (active plan), docs/{KNOWN-HAZARDS,CI,SPT-CORE-FINDINGS,
TRACEABILITY}.md, docs/adr/.
</project-context>


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

<live-context>
## perri — checkpoint 2026-06-15 (session-end; resume next session)

**CORRECTION to my last echo-commune** (it predates this work, says wrong things):
- `shortcut_basename` is now **`cc`** (operator-ruled), NOT `sptc`. The launcher brand `cc-<id>`
  is DECOUPLED from the plugin name `sptc`; the s/sptc/spt/ succession renames the plugin, not the
  shortcut. (Matches the schema's own `spt-claude-code → cc` example + the M12 cc launcher.)
- The "next gate: real adapter add" is **DONE** — registration int is GREEN vs live v0.7.0.

**How I work this project** (unchanged): build from spt-core PUBLIC surface ONLY (spt-releases
v0.7.0 + sabermage.github.io/spt-releases); NEVER read spt-core source; a missing capability =
a FINDING for doyle/todlando, not a workaround. Small atomic commits, `Co-authored by: perri`,
`sh ci/run-gates.sh` ⇒ PASS + `traceable-reqs check` green before declaring done. Activate-don't-
pre-fail on REQs. Bus replies: `$OWL send doyle perri` / `$OWL send todlando perri` (pass `perri`
explicitly; do NOT use --reply-to). Local `spt` is now **0.7.0** (operator-upgraded; perch survived).

**Open threads I own:**
- **doyle** — just sent (this session) 2 published-docs-gap findings from building the manifest:
  (1) substitution-key catalog incomplete on docs-site ({parent_pid},{adapter_name} are spt-filled
  but only in mock source); (2) [digest] cross-field rule undocumented (schema accepts [digest] w/
  just extractor, but `adapter add` rejects: needs source or [history].locate_template). Both
  non-blocking (resolved from mock source + live error). Awaiting any doyle ack.
- **REQ-MSG-ENVELOPE** — still owe doyle the poll→additionalContext byte-capture when todlando lands
  the ADR-0020 relic removal; then I flip HOOKS-API / UPS-INJECTION `int`. (Unchanged, still pending.)

**Immediate next steps (act on resume, in MANIFEST-PARITY-PLAN.md):**
1. **file-backed `[strings]`** — RESEARCH FIRST: inline strings + `get-string` are PROVEN, but the
   v0.7.0 `[strings]` schema is inline TOML with NO visible file-backed mechanism. Confirm how (if)
   v0.7.0 externalizes large string bodies BEFORE authoring (get-string include? files via [update]
   file_pull?). Then lift the SKILL.md skeleton bodies out.
2. **`[session.self]` + `spt endpoint run` + `cc` launcher** → unblocks the held
   REQ-DIST-SHORTCUT-BASENAME impl+int. NOTE: the shortcut-emitting picker "lands in a LATER
   spt-core wave" (per `endpoint run --help`) — emission int stays held on that wave regardless.
3. **`claude-spt-digest` extractor binary** → unblocks `spt adapter digest-proof` (deeper [digest] int).
</live-context>
<project-context>
## spt-claude-code — status 2026-06-15

**End goal:** `claude-spt` is spt-core's v1 acceptance proof — (1) parity with legacy
claude_skill_owl delegating core to the `spt` binary; (2) first casual-end-user entrypoint, a CC
plugin on cplugs (name `sptc`, skills `/sptc:*`; succession flips sptc→spt on parity-proof);
(3) invisible spt-core installer (SessionStart bootstrap). Distribution splits by volatility
(ADR-0001): thin skeleton on cplugs; volatile bulk (binary + adapter manifest) spt-core-conducted.

**ADAPTER-MANIFEST MILESTONE — the manifest half is now GREEN END-TO-END vs real v0.7.0.**
Commits this session (8 total, all gates PASS, traceable green):
- `bd16644` manifest authored + offline schema gate (py tomllib|tomli + jsonschema).
- `c93757d` shortcut brand guard.
- `8cd8372` /sptc:whoami + /sptc:setup skeletons (11 skills validate).
- `435e1be` **registration int vs live v0.7.0** — `spt adapter add` accepted (2nd-layer cross-field
  validation), shipped `:deep` profile resolves, `[strings]` read + overlay observable via
  get-string, clean soft-remove. `ci/manifest/registration-int.sh` (SPTC_ACCEPTANCE-gated, 6/6).
- `be59133` **shortcut_basename = "cc"** (operator-ruled; cc-<id> launcher, decoupled from plugin name).
- plan/docs commits (398aaa1, 2a2f198) + this commune.

**REQs:** REQ-DIST-MANIFEST-SCHEMA → **[doc,impl,unit,int] GREEN**. REQ-DIST-SHORTCUT-BASENAME
[doc,unit] green (impl=cc launcher + int=picker emits cc-<id> deferred; picker is a later spt-core wave).

**Key manifest decisions:** `[digest]` keeps the extractor seam (CC has rich JSONL — defensible
divergence from the log-less mock) with `source="{home}/.claude/projects"` (CC's cwd-slug subdir
isn't expressible as a flat locate_template; extractor finds {session_id}.jsonl within). SessionStart
uses {parent_pid}+{adapter_name} (confirmed from mock). Deferred with notes: `[session]`/roles,
`[history]`, `[update]` (file_pull needs a signing key + registry repo not on the public surface — a
finding). claude-spt IS NOW a registered adapter artifact on this node (the int soft-removes each run).

**Remaining milestone work:** file-backed `[strings]` (research first), `[session.self]`+endpoint-run
+cc-launcher, `claude-spt-digest` extractor. Then ship when REQ-MSG-ENVELOPE lands + parity proven.

**Orientation:** SCOPE.md (ledger), HANDOFF.md (public-surface constraint), CONTEXT.md (glossary),
AGENTS.md (rules), MANIFEST-PARITY-PLAN.md (active plan), docs/{KNOWN-HAZARDS,CI,SPT-CORE-FINDINGS,
TRACEABILITY}.md, docs/adr/.
</project-context>