<tool_use_error>String to replace not found in file.
String: ## Slices (sequence; each its own commit + green gate)

1. **Adapter manifest authoring** — write the `claude-spt` adapter manifest (`[digest]` extractor,
   profiles, strings, hints, `shortcut_basename=sptc`). Add a **manifest-schema gate** to
   `ci/run-gates.sh` (currently SKIP stub): validate the manifest against the published
   v0.7.0 `manifest.schema.json`. Activate **REQ-DIST-MANIFEST-SCHEMA** → `[doc,impl,unit]`.
2. **shortcut_basename** — wire the `sptc-<id>` shortcut emission; test it. (Mint REQ if needed.)
3. **M12 parity surfaces** (seed REQs per surface as each starts — activate-don't-pre-fail):
   file-backed `[strings]` (skill bodies via UPS-injection, lifts the SKILL.md-skeleton bodies),
   `spt endpoint run` bringup, `cc` launcher, `/sptc:setup`, whoami-alias.
(note: Edit also tried swapping \uXXXX escapes and their characters; neither form matched, so the mismatch is likely elsewhere in old_string. Re-read the file and copy the exact surrounding text.)</tool_use_error>

Error: String to replace not found in file.
String: ## Slices (sequence; each its own commit + green gate)

1. **Adapter manifest authoring** — write the `claude-spt` adapter manifest (`[digest]` extractor,
   profiles, strings, hints, `shortcut_basename=sptc`). Add a **manifest-schema gate** to
   `ci/run-gates.sh` (currently SKIP stub): validate the manifest against the published
   v0.7.0 `manifest.schema.json`. Activate **REQ-DIST-MANIFEST-SCHEMA** → `[doc,impl,unit]`.
2. **shortcut_basename** — wire the `sptc-<id>` shortcut emission; test it. (Mint REQ if needed.)
3. **M12 parity surfaces** (seed REQs per surface as each starts — activate-don't-pre-fail):
   file-backed `[strings]` (skill bodies via UPS-injection, lifts the SKILL.md-skeleton bodies),
   `spt endpoint run` bringup, `cc` launcher, `/sptc:setup`, whoami-alias.
(note: Edit also tried swapping \uXXXX escapes and their characters; neither form matched, so the mismatch is likely elsewhere in old_string. Re-read the file and copy the exact surrounding text.)


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

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

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

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


Edit

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

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.


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)

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.

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

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

-1. **Adapter manifest authoring** — write the `claude-spt` adapter manifest (`[digest]` extractor,

-   profiles, strings, hints, `shortcut_basename=sptc`). Add a **manifest-schema gate** to

-   `ci/run-gates.sh` (currently SKIP stub): validate the manifest against the published

-   v0.7.0 `manifest.schema.json`. Activate **REQ-DIST-MANIFEST-SCHEMA** → `[doc,impl,unit]`.

-2. **shortcut_basename** — wire the `sptc-<id>` shortcut emission; test it. (Mint REQ if needed.)

+1. **DONE (bd16644)** — **Adapter manifest authoring**. `adapter/claude-spt.toml` ([adapter]+

+   [[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

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

-   file-backed `[strings]` (skill bodies via UPS-injection, lifts the SKILL.md-skeleton bodies),

-   `spt endpoint run` bringup, `cc` launcher, `/sptc:setup`, whoami-as-alias.

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

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


Bash

cd /c/Users/decid/Documents/projects/spt-claude-code
git add MANIFEST-PARITY-PLAN.md
git commit -q -F - <<'EOF'
docs: mark manifest slices 1/2/3a done; sharpen v0.7.0-gated remainder

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


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)
4763569 docs: JIT plan for the adapter-manifest + M12 parity milestone


Done. Hit natural milestone boundary — all unblocked work shipped.

## Shipped this session (4 commits, gates PASS, traceable-reqs green)

| Commit | Slice |
|--------|-------|
| `bd16644` | **Manifest authoring** — `adapter/claude-spt.toml` ([adapter]+`shortcut_basename=sptc`, [hooks.*]×6, [identity], [inject], [digest], [profiles.deep], [strings], [[hints]]) + vendored v0.7.0 `manifest.schema.json` + offline schema gate (`ci/manifest/`, py `tomllib`\|`tomli`+`jsonschema`). **REQ-DIST-MANIFEST-SCHEMA** `[doc,impl,unit]`. |
| `c93757d` | **shortcut_basename guard** — `tests/manifest-shortcut.sh` locks the `sptc` succession seam. **REQ-DIST-SHORTCUT-BASENAME** `[doc,unit]`. |
| `8cd8372` | **`/sptc:whoami` + `/sptc:setup` skeletons** — manifest advertised both; skeletons now exist. |
| `398aaa1` | Plan updated. |

**Decisions made (honest-to-public-surface):**
- `shortcut_basename = sptc` per plan (schema's `cc` example is spt-core's illustration; `sptc` = the `s/sptc/spt/` seam).
- **Deferred** `[session]`/roles, `[history]`, `[update]` — would declare unbuilt seams or invent a signing-key+registry-repo for `file_pull`. The `[update]` gap is logged as a finding, not guessed.
- Scanner classifies `impl` by file type → `.toml` config isn't `impl`; shortcut REQ is correctly `[doc,unit]`.

## Remaining — all gated on the **v0.7.0 binary** (local `spt` is 0.6.0)
- file-backed `[strings]` int (binary serves bodies)
- `spt endpoint run` + `cc`/`sptc` launcher (picker reads `shortcut_basename`, a v0.7.0 field)

**The gate is a real decision for you:** upgrading spt-core on this machine to v0.7.0 would unblock those ints, but **perri's live-agent perch runs on spt-core** — upgrade mid-session risks tearing it down. Did not do it autonomously.

Next-step options:
1. **Author file-backed `[strings]` now** (lift skill bodies into the manifest) — buildable without the binary; only the int waits on v0.7.0.
2. **Upgrade local spt-core to v0.7.0** (you confirm; expect perch disruption) → unblock the held ints.
3. Stop here — clean milestone boundary.

Which?


local spt-core is upgraded now. proceed


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


Verify the upgrade first.


spt --version 2>&1 | head -1; echo "---"; spt adapter --help 2>&1 | head -20; echo "--- endpoint ---"; spt endpoint --help 2>&1 | head -20


spt 0.7.0
---
Adapter registration: what this node can drive/launch.

The node-local registered set (one command for harness and shell adapters). Feeds creation-time
adapter selection, shell discovery, and the self-update ripple.

Usage: spt.exe adapter <COMMAND>

Commands:
  add             Register an adapter from a local path (a dir holding `manifest.toml`, or the
                  manifest file itself) or from GitHub (`--github user/repo`, cloned under
                  `adapters/_github/`). Manifest-first: an invalid manifest registers nothing.
                  Install is the first update — the declared `[update]` avenue is conducted once
                  after recording
  remove          Soft-deregister: hidden from new-creation/discovery; existing and live instances
                  keep running. The manifest's optional `uninstall` template is conducted only with
                  --force until quiesce detection lands
  list            List registered adapters (active and soft-deregistered), each followed by its
                  shipped + local profiles as composite options
  create-profile  Create (or overwrite) a **local** profile — a node-local sparse overlay registered
                  beside the adapter that survives `adapter add` re-registration. The overlay TOML
--- endpoint ---
Endpoint operations: list, lifecycle, fork, digest, access.

The noun home for per-endpoint verbs (M8 decision 1). Bare `spt endpoint` renders the merged listing
— every member subnet's endpoints grouped by subnet, this session's own endpoint pinned distinctly
at the top.

Usage: spt.exe endpoint [COMMAND]

Commands:
  list         Merged endpoint listing (the bare `spt endpoint` view)
  run          Bring up an spt-hosted harness endpoint into a broker-held PTY
  fork         Fork an endpoint into another subnet as a NEW identity
  suspend      Rest an endpoint cold (the suspend edge)
  wake         Wake a resting endpoint in place
  shutdown     Gracefully shut down an agent's own endpoint
  stop         Soft-stop a perch (spool preserved)
  rename       Rename an endpoint's logical id across its on-disk state
  digest       Show a session's live activity buffer (session digest)
  access       Endpoint access whitelist for unsolicited off-node inbound
  description  The endpoint's service-description blurb (ex-`resources`)


v0.7.0 confirmed. `spt adapter add` (manifest-first: invalid → registers nothing) + `spt endpoint run` both present. Now the held ints.

First int — **REQ-DIST-MANIFEST-SCHEMA**: real `spt adapter add` registration-validation (the 2nd layer beyond JSON schema). Register, then list.


cd /c/Users/decid/Documents/projects/spt-claude-code
echo "=== adapter add (manifest file) ==="
spt adapter add adapter/claude-spt.toml 2>&1 | head -40
echo "rc=$?"
echo "=== adapter list ==="
spt adapter list 2>&1 | head -30


=== adapter add (manifest file) ===
ADAPTER_ADD_FAIL: manifest invalid: manifest invalid: [digest] needs `source` (own-source) or a [history] `locate_template` to read from (a log-less adapter pushes via `api digest-entry` and declares no [digest])
rc=0
=== adapter list ===
no adapters registered


Real finding from the 2nd-layer validation — schema passed but spt-core's cross-field rule rejects: **`[digest]` needs `source` or `[history].locate_template`**. My "extractor self-locates" assumption violates the contract. The canonical reference is the **published mock-adapter manifest** (public surface). Fetch it to mirror correct `[history]`/`[digest]` shape + substitution keys.


WebFetch

Prompt: Show the mock-adapter's manifest.toml verbatim — especially the [history] and [digest] sections, the locate_template / source values, and which {substitution keys} they use (e.g. {session_id}, {cwd}, {home}, {project}). Also list the full catalog of substitution keys spt-core fills for locate_template and session role command templates. Quote verbatim.


# Mock Adapter's Manifest History & Digest Sections

The documentation doesn't include the mock-adapter's complete manifest.toml verbatim. However, the adapter quickstart references it:

> "Every release ships the mock adapter's source. With spt-core installed: `curl -fsSL -o mock-adapter.zip https://github.com/SaberMage/spt-releases/releases/latest/download/mock-adapter.zip`"

The docs describe the mock adapter's `[history]` pattern:

> "`fetcher` — spt-core runs your binary; it emits normalized history"

And note that the mock adapter uses "a trivial `mock-session` helper standing in for a real harness binary."

## Substitution Keys Catalog

The manifest reference documents these placeholder categories:

**Session role templates** accept: `{id}`, `{session_id}`, `{session_name}`, `{psyche_dir}`, `{cwd}`

**Notification template** (`[session.notif]`) fills: `{notif_id}`, `{notif_from}`, `{notif_subnet}`, `{notif_body}`

**History strategies** using `locate_template` can use: `{session_id}`, and context like `{cwd}`, `{home}`, `{project}` (implied by the environment-bridge section discussing env injection patterns)

The docs state: "Command templates are opaque strings — spt-core fills `{key}` placeholders and runs them."


Docs don't ship the mock manifest verbatim, but every release ships **mock-adapter source** (public surface, explicitly for this). The CC transcript path needs a cwd→slug transform a flat `locate_template` can't express — so I need the canonical worked example. Download + read the mock manifest.


Bash

cd "$(mktemp -d)"
echo "PWD=$PWD"
curl -fsSL -o mock-adapter.zip https://github.com/SaberMage/spt-releases/releases/latest/download/mock-adapter.zip 2>&1
echo "rc=$?"; ls -la mock-adapter.zip 2>&1
unzip -o mock-adapter.zip >/dev/null 2>&1 && echo "unzipped" || echo "unzip failed"
echo "=== files ==="; find . -name '*.toml' -o -name 'manifest*' | head
echo "=== manifest.toml ==="; find . -name 'manifest.toml' -exec cat {} \;


PWD=/tmp/tmp.9cxmRhuHU8
rc=0
-rw-r--r-- 1 decid 197609 7072 Jun 15 02:34 mock-adapter.zip
unzipped
=== files ===
./Cargo.toml
./manifest.toml
./tests/manifest_valid.rs
=== manifest.toml ===
# Generic mock harness adapter — the canonical, harness-AGNOSTIC exercise of the
# spt-core contract. ZERO vendor harness conventions: no vendor binary name, no
# vendor config dir, no vendor hook names. The command templates spawn the
# trivial `mock-session` helper; the [hooks.*] table binds generic event names to
# the inbound `api` surface.
#
# This is the only adapter-shaped artifact in spt-core (PRD R-DOCS-2). A real
# harness adapter (e.g. the downstream spt-plugin) lives in its own repo.

[adapter]
name = "mock"
kind = "harness"
version = "1.0.0"
min_spt_core_version = "1.0.0"
hostable_types = ["LiveAgent", "ReadyAgent", "Worker"]

# Inbound hooks: generic event names → the `api` command each fires. SessionStart
# seeds; Idle reports idle (and arms the echo-gate); SessionEnd soft-tears-down.
[hooks.SessionStart]
fires = "api seed --pid {parent_pid} --session-id {session_id} --adapter {adapter_name}"
reads = ["session_id", "parent_pid"]
can_inject = true

[hooks.Idle]
fires = "api state idle"
reads = []
can_inject = false              # no inject channel → sentinel/relay fallback

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

# Watched dirs: commune/signoff are file-drops (filenames spt-core-fixed), not
# commands.
[session]
commune_dir = ".mock"
signoff_dir = ".mock"

# Outbound session role: the opaque template ManifestRuntime spawns. `mock-session`
# stands in for a real harness binary; spt-core just runs it with keys filled.
[session.self]
command = "mock-session --id {id} --session-id {session_id}"
detach = true
keys = ["id", "session_id"]

# M2b live-agent lifecycle roles (harness-agnostic stand-ins). A real adapter's
# templates select a model + harness flags inside `command`; spt-core only fills
# {key} placeholders. The recursion guard makes the Psyche/echo children's own
# hooks bail (no Psyche-of-Psyche, no echo-of-echo).
[session.psyche_init]
command = "mock-session --mode psyche --id {id} --session-id {session_id}"
cwd = "{psyche_dir}"
recursion_guard_env = "MOCK_ECHO_COMMUNE"
detach = true
keys = ["id", "session_id", "psyche_dir", "psyche_prompt"]

[session.psyche_resume]
command = "mock-session --mode psyche --id {id} --session-id {session_id}"
cwd = "{psyche_dir}"
recursion_guard_env = "MOCK_ECHO_COMMUNE"
detach = true
keys = ["id", "session_id", "psyche_dir", "psyche_prompt"]

# Echo-commune: a bounded cheap-model template reading history on stdin.
[session.echo_commune]
command = "mock-session --mode echo --id {id}"
recursion_guard_env = "MOCK_ECHO_COMMUNE"
keys = ["id"]

# Signoff: a psyche_resume variant firing a final context-save.
[session.signoff]
command = "mock-session --mode echo --id {id}"
recursion_guard_env = "MOCK_ECHO_COMMUNE"
keys = ["id"]

# History: native Path-B store (the adapter pushes via `api history-log`).
[history]
strategy = "native"

# Env aliases the adapter injects for in-session use ($SPT = spt api, etc.).
[env.SPT_AGENT_ID]
direction = "inject"
value = "{id}"

# Inject methods per activity state (PTY is spt-core/M3, never offered here).
[inject]
activity = ["hook"]
idle = ["hook", "relay"]

[identity]
session_id_source = "post_spawn"
parent_ancestor_name = "mock-session"


Canonical mock-adapter learnings:
- It declares **no `[digest]`** (log-less, `[history] strategy="native"`) — that's why no cross-field trip.
- `{parent_pid}` and `{adapter_name}` ARE fillable keys (`reads = ["session_id", "parent_pid"]`).
- `source`/`locate_template` can be a **directory** ("log file(s)").

CC *has* a rich JSONL log, so the `[digest]` extractor seam is the right design (a defensible divergence from the deliberately log-less mock). The cwd→slug path isn't expressible as a flat template, but the per-project **root** is: `source = "{home}/.claude/projects"` + extractor finds `{session_id}.jsonl` within. Fix `[digest]`, enrich SessionStart to match mock.


Edit

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

[hooks.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


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)

[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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

-# Seed the endpoint. pid is sourced from the hook PROCESS tree, not stdin (F-001); session_id is

-# a CC stdin field. NOT the blocking poll/listen loop — that is /sptc:ready|live, never SessionStart.

-fires = "api seed --session-id {session_id} --pid {parent_pid}"

+# 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}"


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

# ── [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


# ── [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

# 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. The extractor binary self-locates CC's transcript from the
# session id (no [history] locate_template declared this slice), so no {source} placeholder.
# The extractor binary ships in the spt-core-conducted layer (built in a later parity slice);
# this is its declaration.
[digest]
extractor = "claude-spt-digest --session {session_id}"
window_turns = 6
arg_truncation = 240
sprint_collapse = true

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

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

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

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

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

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

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

-# one JSON record per output line. The extractor binary self-locates CC's transcript from the

-# session id (no [history] locate_template declared this slice), so no {source} placeholder.

-# The extractor binary ships in the spt-core-conducted layer (built in a later parity slice);

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

-extractor = "claude-spt-digest --session {session_id}"

+extractor = "claude-spt-digest --session {session_id} --in {source}"


Bash

cd /c/Users/decid/Documents/projects/spt-claude-code
echo "=== schema gate (must still pass) ==="; sh ci/manifest/check-manifest.sh
echo "=== adapter add ==="; spt adapter add adapter/claude-spt.toml 2>&1 | head -30
echo "=== adapter list ==="; spt adapter list 2>&1 | head -20


=== schema gate (must still pass) ===
ok   C:/Users/decid/Documents/projects/spt-claude-code/adapter/claude-spt.toml
ok   manifest validates against published manifest.schema.json
=== adapter add ===
ADAPTER_ADD:claude-spt:Harness:Copy (registered)
ADAPTER_INSTALL_SKIP: no [update] avenue (manifest-only adapter)
=== adapter list ===
claude-spt: Harness Copy active (from adapter)
  claude-spt:deep (shipped)


Registration **passed** — two held ints now proven:
- `ADAPTER_ADD:claude-spt:Harness:Copy (registered)` → **REQ-DIST-MANIFEST-SCHEMA int** (2nd-layer cross-field validation passes).
- `claude-spt:deep (shipped)` in list → shipped **profile overlay resolves**.
- `ADAPTER_INSTALL_SKIP: no [update] avenue (manifest-only adapter)` → confirms the deferred `[update]` is handled gracefully (validates the deferral).

Now the shortcut int. `endpoint run` spawns a real PTY harness → perch-collision risk. Check for a dry-run / how the shortcut name surfaces before launching anything.


Bring up an spt-hosted harness endpoint into a broker-held PTY.

Spawns the adapter's `[session.self]` command into a broker-owned PTY (the harness self-registers
its perch on bind), then starts / attaches / views per the terminal-action flag. The endpoint id
rides argv so the harness binds to exactly it. This is the non-interactive core (the interactive
picker lands in a later wave); the flags cover every terminal action so a `spt-<id>` shortcut can
bake a fully non-interactive launch.

Usage: spt.exe endpoint run [OPTIONS]

Options:
      --adapter <ADAPTER>
          The harness adapter to host: `<adapter>[:profile]` (must be a registered `kind="harness"`
          adapter on this node). Omit (with --id) to launch the interactive picker

      --id <ID>
          The endpoint id to bring up (charset: alphanumeric, `-`, `_`). Omit (with --adapter) to
          launch the interactive picker

      --create
          Mint a fresh session (the default; explicit so a non-interactive shortcut can bake
          create-vs-resume). Conflicts with --resume

      --resume <RESUME>
          Resume a prior session id instead of minting a fresh one

      --start
          Start the endpoint and return immediately (no attach)

      --attach
          Attach a local terminal after bringup (the default action)

      --view
          Attach read-only after bringup (watch; forward no input)

  -h, --help
          Print help (see a summary with '-h')


`endpoint run` needs `[session.self]` (which I deferred) AND the shortcut-generating picker "lands in a later wave" (not in 0.7.0). So the shortcut-picker int is **still genuinely held** — not on binary version, but on a future spt-core wave + `[session.self]`. That's a finding.

But the **MANIFEST-SCHEMA int is now real** (registration passed). Let me confirm `shortcut_basename` round-trips into the stored manifest, and check `adapter remove`, before formalizing an int proof.


cd /c/Users/decid/Documents/projects/spt-claude-code
echo "=== adapter subcommands ==="; spt adapter --help 2>&1 | sed -n '/Commands:/,/Options:/p'
echo "=== try show/get-string ==="; spt adapter get-string claude-spt adapter_label 2>&1 | head -3
echo "=== stored manifest location ==="; find "$HOME/AppData/Local/spt-core" -name '*.toml' 2>/dev/null | grep -i claude | head; find "$HOME/.spt" "$HOME/AppData"/*/spt* -iname '*claude*' 2>/dev/null | head


=== adapter subcommands ===
Commands:
  add             Register an adapter from a local path (a dir holding `manifest.toml`, or the
                  manifest file itself) or from GitHub (`--github user/repo`, cloned under
                  `adapters/_github/`). Manifest-first: an invalid manifest registers nothing.
                  Install is the first update — the declared `[update]` avenue is conducted once
                  after recording
  remove          Soft-deregister: hidden from new-creation/discovery; existing and live instances
                  keep running. The manifest's optional `uninstall` template is conducted only with
                  --force until quiesce detection lands
  list            List registered adapters (active and soft-deregistered), each followed by its
                  shipped + local profiles as composite options
  create-profile  Create (or overwrite) a **local** profile — a node-local sparse overlay registered
                  beside the adapter that survives `adapter add` re-registration. The overlay TOML
                  is read from `--from <file>` or piped stdin (empty = a placeholder profile to
                  populate later with `set-string`). Refuses a name shadowing a shipped profile, an
                  invalid name, or an overlay that loosens a consent floor — nothing is written
                  unless every check passes
  delete-profile  Delete a **local** profile. Refuses a shipped profile name (adapter-owned,
                  immutable) and errors if no local file exists
  get-string      Read a `[strings]` dot-path from an adapter option's merged view
                  (`<adapter>[:profile] <key.path>`). Resolves through the profile overlay like
                  every other consumer; prints the value (strings raw, else JSON). Exit 1 if the key
                  is unset. Strings are **data** — never executed
  digest-proof    Prove an adapter's `[digest]` extractor against a real log sample (ADR-0019). Runs
                  the declared extractor over `--sample <log>` (or the declared source) and prints
                  the parsed contract records, the rendered digest, and **every dropped line with
                  its reason** — the author-time answer to "`spt endpoint digest` returns nothing"
                  (no silent empty). Exit 1 if any line drops or nothing parses
  set-string      Set a `[strings]` dot-path on a **local** profile (`<adapter>:<profile>`). Sugar
                  over editing the overlay file; refuses a shipped profile and a bare option (a
                  local target is required — `create-profile` first)
  help            Print this message or the help of the given subcommand(s)

Options:
=== try show/get-string ===
Claude Code (spt)
=== stored manifest location ===
/c/Users/decid/AppData/Local/spt-core/adapters/claude-spt/manifest.toml
/c/Users/decid/AppData/Local/spt-core/adapters/claude-spt/record.toml
/c/Users/decid/AppData/Local/spt/psyches/tracked/projects/claude_skill_owl
/c/Users/decid/AppData/Local/spt/psyches/tracked/projects/spt-claude-code
/c/Users/decid/AppData/Local/spt/psyches/tracked/seed/logs/refs/heads/p-claude_skill_owl
/c/Users/decid/AppData/Local/spt/psyches/tracked/seed/logs/refs/heads/p-spt-claude-code
/c/Users/decid/AppData/Local/spt/psyches/tracked/seed/logs/refs/remotes/origin/p-claude_skill_owl
/c/Users/decid/AppData/Local/spt/psyches/tracked/seed/logs/refs/remotes/origin/p-spt-claude-code
/c/Users/decid/AppData/Local/spt/psyches/tracked/seed/refs/heads/p-spt-claude-code
/c/Users/decid/AppData/Local/spt/psyches/tracked/seed/refs/remotes/origin/p-spt-claude-code
/c/Users/decid/AppData/Local/spt/psyches/tracked/seed/worktrees/claude_skill_owl
/c/Users/decid/AppData/Local/spt/psyches/tracked/seed/worktrees/spt-claude-code


Strong proofs landing: `get-string claude-spt adapter_label` → "Claude Code (spt)" (**[strings] resolves through the registry**). Verify `shortcut_basename` round-tripped into the stored resolved manifest + the profile overlay's numeric override.


cd /c/Users/decid/Documents/projects/spt-claude-code
STORE="/c/Users/decid/AppData/Local/spt-core/adapters/claude-spt/manifest.toml"
echo "=== shortcut_basename in stored manifest ==="; grep -n 'shortcut_basename' "$STORE"
echo "=== base digest window (no profile) ==="; spt adapter get-string claude-spt digest.window_turns 2>&1 | head -2
echo "=== deep profile digest window (overlay) ==="; spt adapter get-string claude-spt:deep digest.window_turns 2>&1 | head -2
echo "=== record.toml ==="; head -20 "/c/Users/decid/AppData/Local/spt-core/adapters/claude-spt/record.toml"


=== shortcut_basename in stored manifest ===
31:# shortcut_basename). 0.7.0 is the floor this manifest's fields require.
37:shortcut_basename = "sptc"
=== base digest window (no profile) ===
ADAPTER_STRING_UNSET:claude-spt: no value at 'digest.window_turns'
=== deep profile digest window (overlay) ===
ADAPTER_STRING_UNSET:claude-spt:deep: no value at 'digest.window_turns'
=== record.toml ===
name = "claude-spt"
kind = "harness"
mode = "copy"
source_dir = "adapter"
registered_at_ms = 1781516184586
active = true


Confirmed: `shortcut_basename = "sptc"` round-tripped into the stored resolved manifest (line 37). `get-string` is strings-only (so `digest.window_turns` reads UNSET — expected; the `deep` profile overlays a non-string). To make the profile overlay **observable**, add a `[strings]` override to `deep`. Then build an int proof.


Edit

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

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


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

# 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.
# (Node-local user profiles are created with `spt adapter create-profile` and live in the
# registry, never in this manifest.)
[profiles.deep.digest]
window_turns = 20

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

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

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

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

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

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

-# `deep` widens the digest window for long-context review sessions without forking the base.

-# (Node-local user profiles are created with `spt adapter create-profile` and live in the

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

+adapter_label = "Claude Code (spt, deep)"


Bash

cd /c/Users/decid/Documents/projects/spt-claude-code
echo "=== schema gate ==="; sh ci/manifest/check-manifest.sh
echo "=== re-register (overwrites) ==="; spt adapter add adapter/claude-spt.toml 2>&1 | head -3
echo "=== overlay observable: base vs deep ==="
echo -n "base: "; spt adapter get-string claude-spt adapter_label 2>&1
echo -n "deep: "; spt adapter get-string claude-spt:deep adapter_label 2>&1


=== schema gate ===
ok   C:/Users/decid/Documents/projects/spt-claude-code/adapter/claude-spt.toml
ok   manifest validates against published manifest.schema.json
=== re-register (overwrites) ===
ADAPTER_ADD:claude-spt:Harness:Copy (registered)
ADAPTER_INSTALL_SKIP: no [update] avenue (manifest-only adapter)
=== overlay observable: base vs deep ===
base: Claude Code (spt)
deep: Claude Code (spt, deep)


Profile overlay observable through the registry: base `Claude Code (spt)` vs deep `Claude Code (spt, deep)`. Now the int proof script (CLI-portable, env+binary gated like the existing acceptance).


Write

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

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

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

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

# 2. Listed, with the shipped profile resolved as a composite option.
list=$(spt adapter list 2>&1)
case "$list" in *claude-spt*) ok "listed: claude-spt active" ;; *) bad "claude-spt not listed"; fi=1 ;; esac
case "$list" in *claude-spt:deep*) ok "shipped profile resolves: claude-spt:deep" ;; *) bad "deep profile not resolved" ;; esac

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

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

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

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


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

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

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

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

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

# 2. Listed, with the shipped profile resolved as a composite option.
list=$(spt adapter list 2>&1)
case "$list" in *claude-spt*) ok "listed: claude-spt active" ;; *) bad "claude-spt not listed"; fi=1 ;; esac
case "$list" in *claude-spt:deep*) ok "shipped profile resolves: claude-spt:deep" ;; *) bad "deep profile not resolved" ;; esac

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

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

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

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


Typo in the list-check (`fi=1` leftover). Fix.


Edit

case "$list" in *claude-spt*) ok "listed: claude-spt active" ;; *) bad "claude-spt not listed"; fi=1 ;; esac

case "$list" in *claude-spt*) ok "listed: claude-spt active" ;; *) bad "claude-spt not listed" ;; esac


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

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

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

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

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

# 2. Listed, with the shipped profile resolved as a composite option.
list=$(spt adapter list 2>&1)
case "$list" in *claude-spt*) ok "listed: claude-spt active" ;; *) bad "claude-spt not listed"; fi=1 ;; esac
case "$list" in *claude-spt:deep*) ok "shipped profile resolves: claude-spt:deep" ;; *) bad "deep profile not resolved" ;; esac

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

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

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

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

-case "$list" in *claude-spt*) ok "listed: claude-spt active" ;; *) bad "claude-spt not listed"; fi=1 ;; esac

+case "$list" in *claude-spt*) ok "listed: claude-spt active" ;; *) bad "claude-spt not listed" ;; esac


Bash

cd /c/Users/decid/Documents/projects/spt-claude-code
sh -n ci/manifest/registration-int.sh && echo "syntax ok"
echo "=== run int proof ==="; SPTC_ACCEPTANCE=1 sh ci/manifest/registration-int.sh
echo "rc=$?"
echo "=== confirm clean (no claude-spt active) ==="; spt adapter list 2>&1 | head


syntax ok
=== run int proof ===
ok   spt adapter add: registered
ok   listed: claude-spt active
ok   shipped profile resolves: claude-spt:deep
ok   strings resolve: adapter_label (base)
ok   overlay observable: :deep adapter_label differs
ok   removed (soft-deregistered)
MANIFEST-REGISTRATION-INT OK
rc=0
=== confirm clean (no claude-spt active) ===
claude-spt: Harness Copy deregistered (from C:/Users/decid/Documents/projects/spt-claude-code/adapter)
  claude-spt:deep (shipped)


Int proof green (6/6), registry left clean. Flip **REQ-DIST-MANIFEST-SCHEMA → `[doc,impl,unit,int]`** and update the note (int proven, not held).


Edit

required_stages = ["doc", "impl", "unit"]  # doc: ADR-0001 (manifest layer + version-of-truth). impl: adapter/claude-spt.toml + vendored adapter/manifest.schema.json + ci/manifest/{validate_manifest.py,check-manifest.sh} (gate). unit: tests/manifest-schema.sh (pass real + fail tampered). int held: real `spt adapter add` registration-validation (2nd layer) for throwaway validation.

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


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

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

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

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

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

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

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

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

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

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

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

-required_stages = ["doc", "impl", "unit"]  # doc: ADR-0001 (manifest layer + version-of-truth). impl: adapter/claude-spt.toml + vendored adapter/manifest.schema.json + ci/manifest/{validate_manifest.py,check-manifest.sh} (gate). unit: tests/manifest-schema.sh (pass real + fail tampered). int held: real `spt adapter add` registration-validation (2nd layer) for throwaway validation.

+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".


Record the public-surface findings (the key-catalog + `[digest]` cross-field rule confirmation, and the shortcut-picker observation) in SPT-CORE-FINDINGS.


public surface (observable behavior = public surface).

---

## F-002

public surface (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


<tool_use_error>String to replace not found in file.
String: public surface (observable behavior = public surface).

---

## F-002</tool_use_error>

Error: String to replace not found in file.
String: public surface (observable behavior = public surface).

---

## F-002


82	`CONTEXT.md`/ADRs then propagates (no new design). We will report the **observed** `api poll` frame
83	format so the publish can confirm-match. Until published we may rely on observed behavior of the
84	public binary (observable behavior = public surface).
85
86	---
87
88	## F-002 — `api poll` (agent path) has no inter-frame delimiter — multi-message drains unsplittable
89
90	**Reported:** 2026-06-15 to doyle. **Status:** open — **spt-core CODE gap** (not docs); doyle has
91	raised the framing as a contract decision to the operator.

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


public binary (observable behavior = public surface).

---

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

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