The file C:\Users\decid\Documents\projects\spt-core\crates\spt\src\picker\data.rs has been updated successfully. (file state is current in your context — no need to Read it back)

/// The shortcut basename an adapter option declares in its RESOLVED manifest
/// (`[adapter] shortcut_basename`, REQ-MANIFEST-7), defaulting to the
/// harness-agnostic [`DEFAULT_BASENAME`](super::shortcut::DEFAULT_BASENAME)
/// (`spt`) when absent or the adapter is unresolvable. This is the seam that
/// lets spt-claude-code brand its shortcuts `cc-<id>` without spt-core ever
/// hardcoding a harness name.
// [impl->REQ-MANIFEST-7]
pub fn shortcut_basename(adapter_option: &str) -> String {
    let adapters_dir = perch::adapters_dir();
    spt_runtime::registry::resolve_option(&adapters_dir, adapter_option)
        .ok()
        .and_then(|(_, m)| m.adapter.shortcut_basename)
        .filter(|b| !b.trim().is_empty())
        .unwrap_or_else(|| super::shortcut::DEFAULT_BASENAME.to_string())
}

/// Every discoverable endpoint, across all three categories. The model

//! Picker **data gather** — read-only projections over the EXISTING surfaces
//! the rest of the CLI already uses. No new daemon surface, no new store
//! (doyle ruling): adapters from the registry, endpoints from the roster + the
//! WAN registry snapshots, descriptions from `info.json`, project history
//! best-effort from the context store, resume rows from the session ledger.
//!
//! Everything here degrades to empty rather than failing — the picker is a
//! front-end; a missing surface means "nothing to show", never a crash.
// [impl->REQ-RUN-PICKER]

use std::path::Path;

use spt_store::perch::{self, ParentHint};
use spt_store::{info, project, sessions};

use super::model::{AdapterInfo, AdapterOption, EndpointRow, EpStatus, ResumeRow};

/// The cwd project name — the Project category label + the project-history match
/// key. Derived exactly like the daemon derives it (remote-url slug → toplevel
/// folder → dir name).
pub fn project_name() -> String {
    let dir = std::env::current_dir().unwrap_or_else(|_| Path::new(".").to_path_buf());
    project::project_id_for_dir(&dir)
}

/// The selectable adapter tree: every active `kind="harness"` adapter with its
/// shipped + local profiles nested under it.
pub fn adapter_options() -> Vec<AdapterOption> {
    let adapters_dir = perch::adapters_dir();
    let mut infos: Vec<AdapterInfo> = Vec::new();
    for (record, manifest) in spt_runtime::registry::registered(&adapters_dir) {
        if manifest.adapter.kind != spt_runtime::manifest::AdapterKind::Harness {
            continue;
        }
        let mut shipped: Vec<String> = manifest.profiles.keys().cloned().collect();
        shipped.sort();
        let local = spt_runtime::registry::local_profile_names(&adapters_dir, &record.name);
        infos.push(AdapterInfo {
            name: record.name,
            shipped,
            local,
        });
    }
    infos.sort_by(|a, b| a.name.cmp(&b.name));
    super::model::build_adapter_tree(&infos)
}

/// Every discoverable endpoint, across all three categories. The model
/// partitions by category; this just produces the union (local perches +
/// subnet registry rows).
// [impl->REQ-RUN-PICKER]
pub fn gather_endpoints() -> Vec<EndpointRow> {
    let mut rows = Vec::new();
    rows.extend(local_rows());
    rows.extend(subnet_rows());
    rows
}

/// This node's local perches (from the roster). Local rows feed both the Local
/// and Project categories (the model filters Project by project-history match).
fn local_rows() -> Vec<EndpointRow> {
    crate::roster::enumerate()
        .into_iter()
        .map(|p| {
            let perch_path = perch::resolve_perch_path(&p.id, ParentHint::Infer);
            let rec = info::read_info(&perch_path);
            let status = if p.alive {
                EpStatus::Online
            } else {
                EpStatus::Offline
            };
            let adapter_profile = rec
                .as_ref()
                .and_then(|r| r.adapter.clone())
                .unwrap_or_else(|| "(unknown adapter)".to_string());
            let description = rec
                .as_ref()
                .and_then(|r| r.resources.clone())
                .unwrap_or_default();
            let project_history = project_history_for(&p.id);
            // Resume-from-history is offline + LOCAL only (doyle Q3): only an
            // offline local endpoint reads its ledger.
            let resume_rows = if status == EpStatus::Offline {
                resume_rows_for(&perch_path, &project_history)
            } else {
                Vec::new()
            };
            EndpointRow {
                id: p.id,
                group: "local".to_string(),
                node: "LOCAL".to_string(),
                status,
                is_local: true,
                adapter_profile,
                project_history,
                description,
                resume_rows,
            }
        })
        .collect()
}

/// Subnet endpoints, from the WAN registry snapshots, visibility-gated exactly
/// like `spt endpoint list`.
fn subnet_rows() -> Vec<EndpointRow> {
    use spt_net::net::registry::resource_projection;
    use spt_store::visibility::VisibilityStore;

    let subnets = spt_store::subnet::SubnetStore::load();
    let regs = crate::wansend::load_snapshots(&perch::identity_dir().join("registry"));
    let vis = VisibilityStore::load();
    let mut out = Vec::new();
    for s in &subnets.subnets {
        let Some(reg) = regs.get(&s.name) else {
            continue;
        };
        let hide_new = s.hide_new_endpoints;
        for row in resource_projection(reg, |id| vis.hidden(id, &s.name, hide_new)) {
            let status = match row.status {
                spt_net::net::registry::Status::Active
                | spt_net::net::registry::Status::Dormant => EpStatus::Online,
                // Suspended (cold) + Offline render offline in the W2 duality.
                _ => EpStatus::Offline,
            };
            out.push(EndpointRow {
                id: row.endpoint_id,
                group: format!("{}:{}", s.name, row.node),
                node: row.node,
                status,
                is_local: false,
                adapter_profile: row.resources.clone().unwrap_or_default(),
                project_history: Vec::new(),
                description: row.resources.unwrap_or_default(),
                resume_rows: Vec::new(),
            });
        }
    }
    out
}

/// Best-effort project history for a local endpoint, newest→oldest (doyle Q2):
/// scan the context store's `projects/<project>/<id>/` tree for the projects
/// this endpoint carries context in, ordered by the context file's mtime.
/// Empty if the store has nothing — an informational pane, degrade-to-empty.
fn project_history_for(id: &str) -> Vec<String> {
    let root = perch::tracked_dir();
    let projects_dir = root.join("projects");
    let Ok(entries) = std::fs::read_dir(&projects_dir) else {
        return Vec::new();
    };
    let mut found: Vec<(std::time::SystemTime, String)> = Vec::new();
    for e in entries.flatten() {
        let project_id = e.file_name().to_string_lossy().into_owned();
        let ctx = spt_store::contextstore::project_context_file(&root, &project_id, id);
        let Ok(meta) = std::fs::metadata(&ctx) else {
            continue; // this endpoint has no context in this project
        };
        let mtime = meta.modified().unwrap_or(std::time::UNIX_EPOCH);
        found.push((mtime, project_id));
    }
    found.sort_by(|a, b| b.0.cmp(&a.0)); // newest first
    found.into_iter().map(|(_, p)| p).collect()
}

/// Resume rows from the per-endpoint session ledger (newest→oldest), titled in
/// the model. The ledger row carries no project, so the project field is a
/// best-effort fill from the endpoint's most-recent project history; the title
/// falls back to the boundary trigger when that is empty.
fn resume_rows_for(perch_path: &Path, project_history: &[String]) -> Vec<ResumeRow> {
    let project = project_history.first().cloned().unwrap_or_default();
    let mut rows: Vec<ResumeRow> = sessions::last_k(perch_path, sessions::MAX_LEDGER)
        .into_iter()
        .map(|e| ResumeRow {
            session_id: e.session_id,
            ts: e.ts,
            trigger: e.trigger.token().to_string(),
            project: project.clone(),
        })
        .collect();
    rows.reverse(); // ledger is oldest→newest; show newest first
    rows
}

+/// The shortcut basename an adapter option declares in its RESOLVED manifest

+/// (`[adapter] shortcut_basename`, REQ-MANIFEST-7), defaulting to the

+/// (`spt`) when absent or the adapter is unresolvable. This is the seam that

+/// lets spt-claude-code brand its shortcuts `cc-<id>` without spt-core ever

+pub fn shortcut_basename(adapter_option: &str) -> String {

+    let adapters_dir = perch::adapters_dir();

+    spt_runtime::registry::resolve_option(&adapters_dir, adapter_option)

+        .and_then(|(_, m)| m.adapter.shortcut_basename)

+        .filter(|b| !b.trim().is_empty())

+        .unwrap_or_else(|| super::shortcut::DEFAULT_BASENAME.to_string())


write the JIT plan for W2, verify it with doyle, execute on their greenlight


Edit

/// Write the `spt-<id>` shortcut at the project cwd, returning a one-line status
/// for the flash. (The file write is the only side effect; the TUI stays up.)
/// spt-core uses the harness-agnostic default basename (`spt`); an adapter/flow
/// override (e.g. `cc`) is a downstream concern.
fn write_shortcut_now(adapter: &str, id: &str, resume: Option<&str>, mode: RunMode) -> String {
    let dir = std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from("."));
    match shortcut::write_shortcut(&dir, shortcut::DEFAULT_BASENAME, id, adapter, resume, mode) {

/// Write the `<basename>-<id>` shortcut at the project cwd, returning a one-line
/// status for the flash. (The file write is the only side effect; the TUI stays
/// up.) The basename comes from the selected adapter's RESOLVED manifest
/// (`[adapter] shortcut_basename`, REQ-MANIFEST-7), defaulting to `spt` — so an
/// adapter brands its shortcuts (`cc-<id>`) without spt-core hardcoding it.
fn write_shortcut_now(adapter: &str, id: &str, resume: Option<&str>, mode: RunMode) -> String {
    let dir = std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from("."));
    let basename = data::shortcut_basename(adapter);
    match shortcut::write_shortcut(&dir, &basename, id, adapter, resume, mode) {


The file C:\Users\decid\Documents\projects\spt-core\crates\spt\src\picker\mod.rs has been updated successfully. (file state is current in your context — no need to Read it back)

/// Write the `spt-<id>` shortcut at the project cwd, returning a one-line status
/// for the flash. (The file write is the only side effect; the TUI stays up.)
/// spt-core uses the harness-agnostic default basename (`spt`); an adapter/flow
/// override (e.g. `cc`) is a downstream concern.
fn write_shortcut_now(adapter: &str, id: &str, resume: Option<&str>, mode: RunMode) -> String {
    let dir = std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from("."));
    match shortcut::write_shortcut(&dir, shortcut::DEFAULT_BASENAME, id, adapter, resume, mode) {

/// Write the `<basename>-<id>` shortcut at the project cwd, returning a one-line
/// status for the flash. (The file write is the only side effect; the TUI stays
/// up.) The basename comes from the selected adapter's RESOLVED manifest
/// (`[adapter] shortcut_basename`, REQ-MANIFEST-7), defaulting to `spt` — so an
/// adapter brands its shortcuts (`cc-<id>`) without spt-core hardcoding it.
fn write_shortcut_now(adapter: &str, id: &str, resume: Option<&str>, mode: RunMode) -> String {
    let dir = std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from("."));
    let basename = data::shortcut_basename(adapter);
    match shortcut::write_shortcut(&dir, &basename, id, adapter, resume, mode) {

-/// Write the `spt-<id>` shortcut at the project cwd, returning a one-line status

-/// for the flash. (The file write is the only side effect; the TUI stays up.)

-/// spt-core uses the harness-agnostic default basename (`spt`); an adapter/flow

-/// override (e.g. `cc`) is a downstream concern.

+/// Write the `<basename>-<id>` shortcut at the project cwd, returning a one-line

+/// status for the flash. (The file write is the only side effect; the TUI stays

+/// up.) The basename comes from the selected adapter's RESOLVED manifest

+/// (`[adapter] shortcut_basename`, REQ-MANIFEST-7), defaulting to `spt` — so an

+/// adapter brands its shortcuts (`cc-<id>`) without spt-core hardcoding it.

-    match shortcut::write_shortcut(&dir, shortcut::DEFAULT_BASENAME, id, adapter, resume, mode) {

+    let basename = data::shortcut_basename(adapter);

+    match shortcut::write_shortcut(&dir, &basename, id, adapter, resume, mode) {

Ok(WriteOutcome::Created(p)) => format!("✓ shortcut written: {}", p.display()),

Ok(WriteOutcome::Updated(p)) => format!("✓ shortcut updated: {}", p.display()),


Now docs/MANIFEST.md — `[adapter]` field + claude-spt example. Read the relevant sections:


24:### `[adapter]` — header
26:[adapter]
27:name = "claude-spt"
29:min_spt_core_version = "1.0.0"     # readable before any update; compat gate
30:hostable_types = ["LiveAgent", "ReadyAgent", "Psyche", "Worker"]
86:fetcher = "claude-spt-history --session {session_id}"   # adapter binary emits normalized history
97:extractor = "claude-spt-digest --session {session_id} --in {source}"  # native log → contract JSONL
185:### `[update]` — adapter self-update
191:version_check = true               # verify spt-core satisfies min_spt_core_version before/after
199:## Shell adapters (`kind = "shell"`)
204:[adapter]
208:min_spt_core_version = "1.0.0"
240:## Worked example: `claude-spt` (today's behavior, expressed)
263:- Capability-declaration shape beyond `hostable_types` (per-type operation advertisement).


24	### `[adapter]` — header
25	```toml
26	[adapter]
27	name = "claude-spt"
28	version = "2.0.0"
29	min_spt_core_version = "1.0.0"     # readable before any update; compat gate
30	hostable_types = ["LiveAgent", "ReadyAgent", "Psyche", "Worker"]
31	```
32
33	### `[hooks.<event>]` — inbound hook table
34	Per harness event: the `api` command it fires, the stdin fields it maps in, and its **output capability** — `can_inject` (whether this hook can surface context to the agent). When `can_inject = false`, spt-core falls back to a sentinel + relay/poll path instead of expecting injection. This is the load-bearing harness-varying fact (CC's Stop hook can't inject).
35
36	```toml
37	[hooks.SessionStart]
38	fires = "api seed --pid {parent_pid} --session-id {session_id} --adapter {adapter_name}"
39	reads = ["session_id", "source", "agent_type"]
40	can_inject = true                  # via additionalContext
41
42	[hooks.Stop]
43	fires = "api state idle"

### `[adapter]` — header
```toml
[adapter]
name = "claude-spt"
version = "2.0.0"
min_spt_core_version = "1.0.0"     # readable before any update; compat gate
hostable_types = ["LiveAgent", "ReadyAgent", "Psyche", "Worker"]
```

### `[hooks.<event>]` — inbound hook table
Per harness event: the `api` command it fires, the stdin fields it maps in, and its **output capability** — `can_inject` (whether this hook can surface context to the agent). When `can_inject = false`, spt-core falls back to a sentinel + relay/poll path instead of expecting injection. This is the load-bearing harness-varying fact (CC's Stop hook can't inject).

```toml
[hooks.SessionStart]
fires = "api seed --pid {parent_pid} --session-id {session_id} --adapter {adapter_name}"
reads = ["session_id", "source", "agent_type"]
can_inject = true                  # via additionalContext

[hooks.Stop]
fires = "api state idle"


### `[adapter]` — header
```toml
[adapter]
name = "claude-spt"
version = "2.0.0"
min_spt_core_version = "1.0.0"     # readable before any update; compat gate
hostable_types = ["LiveAgent", "ReadyAgent", "Psyche", "Worker"]
```

### `[adapter]` — header
```toml
[adapter]
name = "claude-spt"
version = "2.0.0"
min_spt_core_version = "1.0.0"     # readable before any update; compat gate
hostable_types = ["LiveAgent", "ReadyAgent", "Psyche", "Worker"]
shortcut_basename = "cc"           # optional; brands the picker's launcher → cc-<id> (default "spt")
```

`shortcut_basename` *(optional, default `spt`)* — the basename the `spt endpoint
run` picker's `s` keybind bakes into the generated `<basename>-<id>` launcher
shortcut at the project root (REQ-MANIFEST-7). Absent ⇒ the harness-agnostic
`spt` (→ `spt-<id>`); an adapter sets it to brand its shortcuts — `claude-spt`
uses `cc`, giving `cc-doyle`. spt-core never hardcodes a harness name; the
picker reads this from the **resolved** manifest of the selected adapter. The
launcher is the current OS's native form (`.cmd` on Windows — `.ps1` is excluded
by the default `PATHEXT`; a POSIX `sh` `+chmod +x` on Unix) and overwriting is
guarded by a generated-by sentinel (a foreign same-named file is never clobbered).


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

### `[adapter]` — header
```toml
[adapter]
name = "claude-spt"
version = "2.0.0"
min_spt_core_version = "1.0.0"     # readable before any update; compat gate
hostable_types = ["LiveAgent", "ReadyAgent", "Psyche", "Worker"]
```

### `[adapter]` — header
```toml
[adapter]
name = "claude-spt"
version = "2.0.0"
min_spt_core_version = "1.0.0"     # readable before any update; compat gate
hostable_types = ["LiveAgent", "ReadyAgent", "Psyche", "Worker"]
shortcut_basename = "cc"           # optional; brands the picker's launcher → cc-<id> (default "spt")
```

`shortcut_basename` *(optional, default `spt`)* — the basename the `spt endpoint
run` picker's `s` keybind bakes into the generated `<basename>-<id>` launcher
shortcut at the project root (REQ-MANIFEST-7). Absent ⇒ the harness-agnostic
`spt` (→ `spt-<id>`); an adapter sets it to brand its shortcuts — `claude-spt`
uses `cc`, giving `cc-doyle`. spt-core never hardcodes a harness name; the
picker reads this from the **resolved** manifest of the selected adapter. The
launcher is the current OS's native form (`.cmd` on Windows — `.ps1` is excluded
by the default `PATHEXT`; a POSIX `sh` `+chmod +x` on Unix) and overwriting is
guarded by a generated-by sentinel (a foreign same-named file is never clobbered).

+shortcut_basename = "cc"           # optional; brands the picker's launcher → cc-<id> (default "spt")

+`shortcut_basename` *(optional, default `spt`)* — the basename the `spt endpoint

+run` picker's `s` keybind bakes into the generated `<basename>-<id>` launcher

+shortcut at the project root (REQ-MANIFEST-7). Absent ⇒ the harness-agnostic

+`spt` (→ `spt-<id>`); an adapter sets it to brand its shortcuts — `claude-spt`

+uses `cc`, giving `cc-doyle`. spt-core never hardcodes a harness name; the

+picker reads this from the **resolved** manifest of the selected adapter. The

+launcher is the current OS's native form (`.cmd` on Windows — `.ps1` is excluded

+by the default `PATHEXT`; a POSIX `sh` `+chmod +x` on Unix) and overwriting is

+guarded by a generated-by sentinel (a foreign same-named file is never clobbered).

### `[hooks.<event>]` — inbound hook table

Per harness event: the `api` command it fires, the stdin fields it maps in, and its **output capability** — `can_inject` (whether this hook can surface context to the agent). When `can_inject = false`, spt-core falls back to a sentinel + relay/poll path instead of expecting injection. This is the load-bearing harness-varying fact (CC's Stop hook can't inject).


Check the worked-example section (doyle wants `shortcut_basename = "cc"` shown there):


248
249	**Instantiation governance:** `require_approval` gates `shell spawn` (reusing the consent grant store — `remembered` persists a grant via allow-always, `always` prompts every spawn); `max_instances_per_owner` + `over_cap` cap how many instances one owner may hold (online + offline both count toward the cap). An instance's **alias** is **not** a manifest field — it is per-instance runtime state on the shell perch's `info.json` (set at `shell spawn --alias` / `shell rename`), since the manifest describes the *adapter*, not individual instances.
250
251	## Worked example: `claude-spt` (today's behavior, expressed)
252
253	Maps the sister project's actual surface onto the schema.
254
255	| Concern | Today (modern SPT) | Manifest expression |
256	|---|---|---|
257	| 7 CC hooks | hooks.json → `owl.exe <subcmd>` | `[hooks.*]` table; `Stop.can_inject=false` drives the echo-gate fallback |
258	| Psyche init | `claude -p --agents … --model sonnet --fallback-model opus …` | `[session.psyche_init].command` (opaque) |
259	| Psyche resume | `claude -p --resume <uuid> --model sonnet …` | `[session.psyche_resume]` + `{session_id}` |
260	| Echo-commune | `claude -p --model haiku --effort low …`, `OWL_ECHO_COMMUNE=1` guard | `[session.echo_commune]` + `recursion_guard_env` |
261	| `ccs` fallback | tier-2 binary swap | **node-wide cross-adapter fallback** (not manifest; `ccs` is its own adapter) |
262	| Transcript read | `.claude/projects/<enc-cwd>/<uuid>.jsonl` | `[history]` (fetcher or locate_normalize) |
263	| Session digest | extract `.claude/…/<uuid>.jsonl` → `{role,text,tool,ts}` | `[digest].extractor` (own seam, ADR-0019; not `[history]`) |
264	| Commune/signoff | `.claude/<id>-commune.md` watched | `[session].commune_dir/signoff_dir`; filenames fixed |
265	| Env | `$OWL`/`$LIVE`/`OWL_SESSION_ID` via `CLAUDE_ENV_FILE` | `[env]`; PATH removes most; channel only harness-hosted |
266	| Identity | `OWL_SESSION_ID` + `claude.exe` ancestor walk | `[identity]` |
267	| Update | (today: cplugs) | `[update] avenue="delegated" command="claude plugin update"` |
268	| period / echo-window / route-guard / 24h refresh | hardcoded/flags | spt-core **global knobs** (per-endpoint override), not manifest |
269
270	## Open sub-items
271	- Exact substitution-key catalog (which keys spt-core guarantees to fill per role).
272	- TOML vs YAML final choice; schema-validation + helpful errors for adapter authors.
273	- The fixed event-block vocabulary's final tag names (`<SPT_EVENT>` etc.).
274	- Capability-declaration shape beyond `hostable_types` (per-type operation advertisement).
275

**Instantiation governance:** `require_approval` gates `shell spawn` (reusing the consent grant store — `remembered` persists a grant via allow-always, `always` prompts every spawn); `max_instances_per_owner` + `over_cap` cap how many instances one owner may hold (online + offline both count toward the cap). An instance's **alias** is **not** a manifest field — it is per-instance runtime state on the shell perch's `info.json` (set at `shell spawn --alias` / `shell rename`), since the manifest describes the *adapter*, not individual instances.

## Worked example: `claude-spt` (today's behavior, expressed)

Maps the sister project's actual surface onto the schema.

| Concern | Today (modern SPT) | Manifest expression |
|---|---|---|
| 7 CC hooks | hooks.json → `owl.exe <subcmd>` | `[hooks.*]` table; `Stop.can_inject=false` drives the echo-gate fallback |
| Psyche init | `claude -p --agents … --model sonnet --fallback-model opus …` | `[session.psyche_init].command` (opaque) |
| Psyche resume | `claude -p --resume <uuid> --model sonnet …` | `[session.psyche_resume]` + `{session_id}` |
| Echo-commune | `claude -p --model haiku --effort low …`, `OWL_ECHO_COMMUNE=1` guard | `[session.echo_commune]` + `recursion_guard_env` |
| `ccs` fallback | tier-2 binary swap | **node-wide cross-adapter fallback** (not manifest; `ccs` is its own adapter) |
| Transcript read | `.claude/projects/<enc-cwd>/<uuid>.jsonl` | `[history]` (fetcher or locate_normalize) |
| Session digest | extract `.claude/…/<uuid>.jsonl` → `{role,text,tool,ts}` | `[digest].extractor` (own seam, ADR-0019; not `[history]`) |
| Commune/signoff | `.claude/<id>-commune.md` watched | `[session].commune_dir/signoff_dir`; filenames fixed |
| Env | `$OWL`/`$LIVE`/`OWL_SESSION_ID` via `CLAUDE_ENV_FILE` | `[env]`; PATH removes most; channel only harness-hosted |
| Identity | `OWL_SESSION_ID` + `claude.exe` ancestor walk | `[identity]` |
| Update | (today: cplugs) | `[update] avenue="delegated" command="claude plugin update"` |
| period / echo-window / route-guard / 24h refresh | hardcoded/flags | spt-core **global knobs** (per-endpoint override), not manifest |

## Open sub-items
- Exact substitution-key catalog (which keys spt-core guarantees to fill per role).
- TOML vs YAML final choice; schema-validation + helpful errors for adapter authors.
- The fixed event-block vocabulary's final tag names (`<SPT_EVENT>` etc.).
- Capability-declaration shape beyond `hostable_types` (per-type operation advertisement).


| 7 CC hooks | hooks.json → `owl.exe <subcmd>` | `[hooks.*]` table; `Stop.can_inject=false` drives the echo-gate fallback |

| 7 CC hooks | hooks.json → `owl.exe <subcmd>` | `[hooks.*]` table; `Stop.can_inject=false` drives the echo-gate fallback |
| `cc`/`cc <id>` launcher | hand-written shell wrappers | `[adapter] shortcut_basename = "cc"` → the picker bakes `cc-<id>` (spt-core default is `spt-<id>`) |


| 7 CC hooks | hooks.json → `owl.exe <subcmd>` | `[hooks.*]` table; `Stop.can_inject=false` drives the echo-gate fallback |
| `cc`/`cc <id>` launcher | hand-written shell wrappers | `[adapter] shortcut_basename = "cc"` → the picker bakes `cc-<id>` (spt-core default is `spt-<id>`) |

| Concern | Today (modern SPT) | Manifest expression |

+| `cc`/`cc <id>` launcher | hand-written shell wrappers | `[adapter] shortcut_basename = "cc"` → the picker bakes `cc-<id>` (spt-core default is `spt-<id>`) |

| Psyche init | `claude -p --agents … --model sonnet --fallback-model opus …` | `[session.psyche_init].command` (opaque) |

| Psyche resume | `claude -p --resume <uuid> --model sonnet …` | `[session.psyche_resume]` + `{session_id}` |

| Echo-commune | `claude -p --model haiku --effort low …`, `OWL_ECHO_COMMUNE=1` guard | `[session.echo_commune]` + `recursion_guard_env` |


Add REQ-MANIFEST-7 + doc tag. First the REQ:


87	[[requirements]]
88	id = "REQ-MANIFEST-6"
89	title = "Cross-adapter fallback target addressing (M12-W3-T3.2): a cross-adapter fallback target is addressed as `<adapter>:<profile>` (not just a bare adapter_name), resolved through the one composite-addressing resolver (registry::resolve_option) at every adapter-option read site so a fallback may select a shipped/local profile (e.g. a `ccs` profile). CONTEXT.md §cross-adapter-fallback reconciled (\"ccs is a profile; cross-adapter fallback may target <adapter>:<profile>\"). Contract-only this milestone: the node-wide fallback SETTING + its rate-limit invocation are deferred to the consuming milestone (the runtime path does not exist yet); this REQ guarantees the ADDRESSING resolves."
90	required_stages = ["doc", "unit"]  # activated M12-W3 (contract-only: no int)
91	[[requirements]]
92	id = "REQ-SEAM-SPAWN"

[[requirements]]
id = "REQ-MANIFEST-6"
title = "Cross-adapter fallback target addressing (M12-W3-T3.2): a cross-adapter fallback target is addressed as `<adapter>:<profile>` (not just a bare adapter_name), resolved through the one composite-addressing resolver (registry::resolve_option) at every adapter-option read site so a fallback may select a shipped/local profile (e.g. a `ccs` profile). CONTEXT.md §cross-adapter-fallback reconciled (\"ccs is a profile; cross-adapter fallback may target <adapter>:<profile>\"). Contract-only this milestone: the node-wide fallback SETTING + its rate-limit invocation are deferred to the consuming milestone (the runtime path does not exist yet); this REQ guarantees the ADDRESSING resolves."
required_stages = ["doc", "unit"]  # activated M12-W3 (contract-only: no int)
[[requirements]]
id = "REQ-SEAM-SPAWN"


required_stages = ["doc", "unit"]  # activated M12-W3 (contract-only: no int)
[[requirements]]
id = "REQ-SEAM-SPAWN"

required_stages = ["doc", "unit"]  # activated M12-W3 (contract-only: no int)
[[requirements]]
id = "REQ-MANIFEST-7"
title = "Adapter-declared shortcut basename (M12-W2 follow-on): an optional `[adapter] shortcut_basename` manifest field names the basename the `spt endpoint run` picker bakes into the generated `<basename>-<id>` launcher shortcut (REQ-RUN-SHORTCUT). Absent ⇒ the harness-agnostic default `spt` (→ `spt-<id>`); an adapter sets it to brand its shortcuts (claude-spt → `cc` → `cc-<id>`), so the Claude-Code-ness lives in the PUBLISHED adapter manifest, never hardcoded in spt-core. The picker reads it from the RESOLVED manifest of the selected adapter (registry::resolve_option), falling back to `spt` when absent/empty/unresolvable. Additive + N-1-safe (serde-default Option, omitted from serialization when absent; old manifests parse clean); manifest.schema.json regenerated from the derive (ADR-0001, CI drift-gated). Documented in docs/MANIFEST.md `[adapter]` section + the claude-spt worked example — the adapter-author contract perri builds spt-claude-code against."
required_stages = ["doc", "impl", "unit"]  # activated M12-W2 follow-on
[[requirements]]
id = "REQ-SEAM-SPAWN"


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

required_stages = ["doc", "unit"]  # activated M12-W3 (contract-only: no int)
[[requirements]]
id = "REQ-SEAM-SPAWN"

required_stages = ["doc", "unit"]  # activated M12-W3 (contract-only: no int)
[[requirements]]
id = "REQ-MANIFEST-7"
title = "Adapter-declared shortcut basename (M12-W2 follow-on): an optional `[adapter] shortcut_basename` manifest field names the basename the `spt endpoint run` picker bakes into the generated `<basename>-<id>` launcher shortcut (REQ-RUN-SHORTCUT). Absent ⇒ the harness-agnostic default `spt` (→ `spt-<id>`); an adapter sets it to brand its shortcuts (claude-spt → `cc` → `cc-<id>`), so the Claude-Code-ness lives in the PUBLISHED adapter manifest, never hardcoded in spt-core. The picker reads it from the RESOLVED manifest of the selected adapter (registry::resolve_option), falling back to `spt` when absent/empty/unresolvable. Additive + N-1-safe (serde-default Option, omitted from serialization when absent; old manifests parse clean); manifest.schema.json regenerated from the derive (ADR-0001, CI drift-gated). Documented in docs/MANIFEST.md `[adapter]` section + the claude-spt worked example — the adapter-author contract perri builds spt-claude-code against."
required_stages = ["doc", "impl", "unit"]  # activated M12-W2 follow-on
[[requirements]]
id = "REQ-SEAM-SPAWN"

title = "Cross-adapter fallback target addressing (M12-W3-T3.2): a cross-adapter fallback target is addressed as `<adapter>:<profile>` (not just a bare adapter_name), resolved through the one composite-addressing resolver (registry::resolve_option) at every adapter-option read site so a fallback may select a shipped/local profile (e.g. a `ccs` profile). CONTEXT.md §cross-adapter-fallback reconciled (\"ccs is a profile; cross-adapter fallback may target <adapter>:<profile>\"). Contract-only this milestone: the node-wide fallback SETTING + its rate-limit invocation are deferred to the consuming milestone (the runtime path does not exist yet); this REQ guarantees the ADDRESSING resolves."

+title = "Adapter-declared shortcut basename (M12-W2 follow-on): an optional `[adapter] shortcut_basename` manifest field names the basename the `spt endpoint run` picker bakes into the generated `<basename>-<id>` launcher shortcut (REQ-RUN-SHORTCUT). Absent ⇒ the harness-agnostic default `spt` (→ `spt-<id>`); an adapter sets it to brand its shortcuts (claude-spt → `cc` → `cc-<id>`), so the Claude-Code-ness lives in the PUBLISHED adapter manifest, never hardcoded in spt-core. The picker reads it from the RESOLVED manifest of the selected adapter (registry::resolve_option), falling back to `spt` when absent/empty/unresolvable. Additive + N-1-safe (serde-default Option, omitted from serialization when absent; old manifests parse clean); manifest.schema.json regenerated from the derive (ADR-0001, CI drift-gated). Documented in docs/MANIFEST.md `[adapter]` section + the claude-spt worked example — the adapter-author contract perri builds spt-claude-code against."

+required_stages = ["doc", "impl", "unit"]  # activated M12-W2 follow-on


Add the doc tag to MANIFEST.md:


`shortcut_basename` *(optional, default `spt`)* — the basename the `spt endpoint

<!-- [doc->REQ-MANIFEST-7] -->
`shortcut_basename` *(optional, default `spt`)* — the basename the `spt endpoint


<!-- [doc->REQ-MANIFEST-7] -->
`shortcut_basename` *(optional, default `spt`)* — the basename the `spt endpoint

shortcut_basename = "cc"           # optional; brands the picker's launcher → cc-<id> (default "spt")

run` picker's `s` keybind bakes into the generated `<basename>-<id>` launcher

shortcut at the project root (REQ-MANIFEST-7). Absent ⇒ the harness-agnostic


Build + targeted tests + traceable. Then check docs-site drift:


Bash

cd "C:\Users\decid\Documents\projects\spt-core" && cargo build -p spt -p spt-runtime 2>&1 | grep -iE 'error|warning' | head; echo "BUILD_DONE"; cargo test -p spt-runtime shortcut_basename 2>&1 | grep -iE 'test result|FAILED'; cargo test -p spt --bin spt 'tests::bare' 2>&1 | grep -iE 'test result'; cargo test -p spt --bin spt picker:: 2>&1 | grep -iE 'test result'


<owl_messages>
<owl_message from="doyle" priority="high">DOCS ADDENDUM for REQ-MANIFEST-7 (operator clarified) — the checklist they mean is the PUBLISHED page too, not just MANIFEST.md:
source = docs-site/src/harness-contract/integration-checklist.md (renders to sabermage.github.io/spt-releases/harness-contract/integration-checklist.html; doc-drift CI-gated).

Add shortcut_basename there as well:
1. **Group 3 — Optional (capability-specific)** table: new row —
   `[adapter] shortcut_basename` | "Names the picker-generated project-root launcher `<basename>-<id>` (the `s` keybind) — your harness's brand instead of the `spt-<id>` default" | lifecycle stage = START.
2. The Group-3 **claude-code:** worked-example paragraph: add a clause — declares `shortcut_basename = "cc"` so the generated launcher is `cc-<id>`.
3. Leave the "Am I done? — the floor" list alone (it's optional, not floor).

So REQ-MANIFEST-7 doc stage spans BOTH: docs/MANIFEST.md [adapter] field + claude-spt example AND this integration-checklist.md Group-3 row + worked example. The page is REQ-DOCS-2-tagged; your MANIFEST-7 doc tag sits on the MANIFEST.md field, the checklist row rides REQ-DOCS-2's existing surface (or co-tag, your call). docs-site regenerates from the src .md → published page updates; drift-gate stays green.

Rest of the batch (bare-spt + the field impl/unit) unchanged. Ping to gate all together before W4.</owl_message>
</owl_messages>

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


BUILD_DONE
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 49 filtered out; finished in 0.00s


ACK. Need to also add `shortcut_basename` to the published integration-checklist page.


cd "C:\Users\decid\Documents\projects\spt-core" && $OWL send doyle <<'EOF'
Got it — checklist addendum noted. Adding shortcut_basename to docs-site/src/harness-contract/integration-checklist.md:
- Group 3 (Optional, capability-specific) new row: `[adapter] shortcut_basename` | names the picker-generated project-root `<basename>-<id>` launcher (s keybind) — your harness brand vs the spt-<id> default | START.
- Group-3 claude-code worked example: + declares shortcut_basename="cc" → cc-<id>.
- "Am I done? floor" untouched (optional, not floor).

Tagging: MANIFEST-7 doc tag stays on the MANIFEST.md [adapter] field (doc stage satisfied there); the checklist row rides the page's existing REQ-DOCS-2 surface (no dup tag). docs-site regen from src .md keeps the drift-gate green — will run the docs-gen + drift check before pinging.

So REQ-MANIFEST-7 doc now spans MANIFEST.md + integration-checklist.md. Rest of batch unchanged (bare-spt + field impl/unit green). Finishing docs + full sweep, then one ping to gate bare-spt + REQ-MANIFEST-7 together before W4.
EOF
echo "EXIT=$?"


➜ SENT:doyle
EXIT=0


3:
9:tagged with the **feature it buys** and **where in the interaction lifecycle**
17:> **The running example is [spt-claude-code](https://github.com/SaberMage/spt-releases)** —
19:> Where a row says *"claude-code: …"* that is how that harness wires the
23:## The interaction lifecycle
36:## Group 1 — Required (no adapter exists without these)
42:| **`[adapter]` manifest header** (`name`, `kind`, `version`, `min_spt_core_version`, `hostable_types`) | Identity + the compat gate spt-core reads *before* any install/update; declares which endpoint types you can host | REGISTER |
45:| **Startup pair — pick one flow:**
• harness-hosted: `[hooks.SessionStart] → api seed --pid {parent_pid} --session-id {session_id}` then the session's `api listen <id>`
• spt-hosted: `[session.self]` template (spt-core spawns it) then `api bind <id> --set-session-id <sid>` | A registered, held perch — the thing messages and lifecycle attach to. `seed→listen` = you own the process; `spawn→bind` = spt-core owns it | START |
48:**claude-code:** `SessionStart` hook fires `api seed`; the Claude Code session
54:## Group 2 — Recommended (the integration is hollow without them)
70:**claude-code:** `Idle` hook → `api state idle`; messages arrive over the hook
79:## Group 3 — Optional (capability-specific)
92:**claude-code:** uses `api shutdown` for graceful `/signoff`; declares a
99:## Group 4 — Beyond the API: integrations that make it good
113:**claude-code (the worked example):** ships the modern two-tier mind end to
128:      `min_spt_core_version`, `hostable_types`)
140:      echoes your `hostable_types`


79	## Group 3 — Optional (capability-specific)
80
81	Reach for these when the capability applies; ignore them otherwise.
82
83	| Surface | Feature it buys | Lifecycle stage |
84	| --- | --- | --- |
85	| **`api shutdown <id>`** | Graceful signoff — runs the final echo-commune BEFORE teardown so the context delta is never lost to ordering | END |
86	| **`api presence <id>` / `api driven-by <id>`** | Most-recently-active resolution across the subnet; lets a session tell local input from remote-drive | RUN |
87	| **Workers** (`api worker-start <parent> <id>`, `worker-poll`, `worker-stop`) | Nested, short-lived sub-agents under a parent endpoint | RUN |
88	| **`[digest]` extractor** (or `api digest-entry`) | A live activity digest (`spt endpoint digest`) — declare an extractor mapping your native log → the `{role, text, tool, ts}` contract (ADR-0019; its OWN seam, no longer riding `[history]`). Spans `/clear` via the session ledger; validate with `spt adapter digest-proof` | RUN |
89	| **`[session.notif]` template** | Native OS notification render (toast / shell alert) for consent + capability prompts, instead of burying them in agent output | RUN |
90	| **Shell surfaces** (`kind = "shell"`: `api bind-shell --link`, `api emit`, `api owner-shutdown`, the `[shell]` body) | Driven surfaces — notifiers, sensors, power buttons — authenticated by the launch link token alone. See [Shells](../shells/overview.md) | START / RUN |
91
92	**claude-code:** uses `api shutdown` for graceful `/signoff`; declares a
93	`[digest]` extractor mapping its per-session JSONL → the digest-record contract so
94	`spt endpoint digest` shows live tool calls and spans `/clear`; no shell body (it
95	is a harness, not a driven surface).
96
97	---
98

## Group 3 — Optional (capability-specific)

Reach for these when the capability applies; ignore them otherwise.

| Surface | Feature it buys | Lifecycle stage |
| --- | --- | --- |
| **`api shutdown <id>`** | Graceful signoff — runs the final echo-commune BEFORE teardown so the context delta is never lost to ordering | END |
| **`api presence <id>` / `api driven-by <id>`** | Most-recently-active resolution across the subnet; lets a session tell local input from remote-drive | RUN |
| **Workers** (`api worker-start <parent> <id>`, `worker-poll`, `worker-stop`) | Nested, short-lived sub-agents under a parent endpoint | RUN |
| **`[digest]` extractor** (or `api digest-entry`) | A live activity digest (`spt endpoint digest`) — declare an extractor mapping your native log → the `{role, text, tool, ts}` contract (ADR-0019; its OWN seam, no longer riding `[history]`). Spans `/clear` via the session ledger; validate with `spt adapter digest-proof` | RUN |
| **`[session.notif]` template** | Native OS notification render (toast / shell alert) for consent + capability prompts, instead of burying them in agent output | RUN |
| **Shell surfaces** (`kind = "shell"`: `api bind-shell --link`, `api emit`, `api owner-shutdown`, the `[shell]` body) | Driven surfaces — notifiers, sensors, power buttons — authenticated by the launch link token alone. See [Shells](../shells/overview.md) | START / RUN |

**claude-code:** uses `api shutdown` for graceful `/signoff`; declares a
`[digest]` extractor mapping its per-session JSONL → the digest-record contract so
`spt endpoint digest` shows live tool calls and spans `/clear`; no shell body (it
is a harness, not a driven surface).

---


Edit

| **`[session.notif]` template** | Native OS notification render (toast / shell alert) for consent + capability prompts, instead of burying them in agent output | RUN |
| **Shell surfaces** (`kind = "shell"`: `api bind-shell --link`, `api emit`, `api owner-shutdown`, the `[shell]` body) | Driven surfaces — notifiers, sensors, power buttons — authenticated by the launch link token alone. See [Shells](../shells/overview.md) | START / RUN |

**claude-code:** uses `api shutdown` for graceful `/signoff`; declares a
`[digest]` extractor mapping its per-session JSONL → the digest-record contract so
`spt endpoint digest` shows live tool calls and spans `/clear`; no shell body (it
is a harness, not a driven surface).

| **`[session.notif]` template** | Native OS notification render (toast / shell alert) for consent + capability prompts, instead of burying them in agent output | RUN |
| **`[adapter] shortcut_basename`** | Names the picker-generated project-root launcher `<basename>-<id>` (the `spt endpoint run` `s` keybind) — your harness's brand instead of the `spt-<id>` default | START |
| **Shell surfaces** (`kind = "shell"`: `api bind-shell --link`, `api emit`, `api owner-shutdown`, the `[shell]` body) | Driven surfaces — notifiers, sensors, power buttons — authenticated by the launch link token alone. See [Shells](../shells/overview.md) | START / RUN |

**claude-code:** uses `api shutdown` for graceful `/signoff`; declares a
`[digest]` extractor mapping its per-session JSONL → the digest-record contract so
`spt endpoint digest` shows live tool calls and spans `/clear`; declares
`shortcut_basename = "cc"` so the picker's generated launcher is `cc-<id>` (vs the
`spt-<id>` default); no shell body (it is a harness, not a driven surface).


The file C:\Users\decid\Documents\projects\spt-core\docs-site\src\harness-contract\integration-checklist.md has been updated successfully. (file state is current in your context — no need to Read it back)

| **`[session.notif]` template** | Native OS notification render (toast / shell alert) for consent + capability prompts, instead of burying them in agent output | RUN |
| **Shell surfaces** (`kind = "shell"`: `api bind-shell --link`, `api emit`, `api owner-shutdown`, the `[shell]` body) | Driven surfaces — notifiers, sensors, power buttons — authenticated by the launch link token alone. See [Shells](../shells/overview.md) | START / RUN |

**claude-code:** uses `api shutdown` for graceful `/signoff`; declares a
`[digest]` extractor mapping its per-session JSONL → the digest-record contract so
`spt endpoint digest` shows live tool calls and spans `/clear`; no shell body (it
is a harness, not a driven surface).

| **`[session.notif]` template** | Native OS notification render (toast / shell alert) for consent + capability prompts, instead of burying them in agent output | RUN |
| **`[adapter] shortcut_basename`** | Names the picker-generated project-root launcher `<basename>-<id>` (the `spt endpoint run` `s` keybind) — your harness's brand instead of the `spt-<id>` default | START |
| **Shell surfaces** (`kind = "shell"`: `api bind-shell --link`, `api emit`, `api owner-shutdown`, the `[shell]` body) | Driven surfaces — notifiers, sensors, power buttons — authenticated by the launch link token alone. See [Shells](../shells/overview.md) | START / RUN |

**claude-code:** uses `api shutdown` for graceful `/signoff`; declares a
`[digest]` extractor mapping its per-session JSONL → the digest-record contract so
`spt endpoint digest` shows live tool calls and spans `/clear`; declares
`shortcut_basename = "cc"` so the picker's generated launcher is `cc-<id>` (vs the
`spt-<id>` default); no shell body (it is a harness, not a driven surface).

| **Workers** (`api worker-start <parent> <id>`, `worker-poll`, `worker-stop`) | Nested, short-lived sub-agents under a parent endpoint | RUN |

| **`[digest]` extractor** (or `api digest-entry`) | A live activity digest (`spt endpoint digest`) — declare an extractor mapping your native log → the `{role, text, tool, ts}` contract (ADR-0019; its OWN seam, no longer riding `[history]`). Spans `/clear` via the session ledger; validate with `spt adapter digest-proof` | RUN |

+| **`[adapter] shortcut_basename`** | Names the picker-generated project-root launcher `<basename>-<id>` (the `spt endpoint run` `s` keybind) — your harness's brand instead of the `spt-<id>` default | START |

-`spt endpoint digest` shows live tool calls and spans `/clear`; no shell body (it

+`spt endpoint digest` shows live tool calls and spans `/clear`; declares

+`shortcut_basename = "cc"` so the picker's generated launcher is `cc-<id>` (vs the

+`spt-<id>` default); no shell body (it is a harness, not a driven surface).


Check the docs-gen / drift mechanism (docs-site is CI drift-gated):


1://! spt-core docs/site generation (M6-D5) — the anti-drift plumbing behind
4://! - `gen` — regenerate the checked-in generated docs (CLI reference).
5://! - `check` — drift gate: regenerate + diff the CLI reference, and
22:    Path::new(env!("CARGO_MANIFEST_DIR"))
63:/// `spt` name (Windows prints `spt.exe`, which would make the generated
110:/// The generated CLI reference: every command + nested subcommand's help,
112:/// reference IS the binary's own words — it cannot drift (REQ-DOCS-5).
118:         (`cargo run -p xtask -- gen`) and drift-gated in CI — this page \
163:    let generated = cli_reference(&bin);
164:    let path = root.join("docs-site/src/cli/reference.md");
169:        if on_disk != generated {
171:                "xtask check: docs-site/src/cli/reference.md drifted from the \
177:        std::fs::write(&path, &generated).expect("write cli reference");
189:    let llms = std::fs::read_to_string(root.join("docs-site/llms.txt"))
190:        .expect("docs-site/llms.txt (the curated index)");
204:            t => root.join("docs-site/src").join(t).exists(),
211:        eprintln!("xtask check: dead links in docs-site/llms.txt: {dead:?}");
220:    let src = root.join("docs-site/src");
223:        "# spt-core — full docs export\n\
277:            .current_dir(root.join("docs-site")),
282:    copy_tree_md(&root.join("docs-site/src"), out);
286:        ("docs-site/llms.txt", "llms.txt"),
323:/// The key ceremony's minting half (ADR-0015): generate one Ed25519 release
332:    let id = spt_proto::identity::Identity::generate();
781:    let id = spt_proto::identity::Identity::generate();
971:/// docs/DEBUG-CONVERGE-PLAN.md): after a coordinator stages a debug rollout,


I changed the CLI (bare `spt`, `--create`) → the generated `cli/reference.md` will drift. Regenerate + run the drift gate:


Bash

cd "C:\Users\decid\Documents\projects\spt-core" && cargo run -p xtask -- gen 2>&1 | tail -5; echo "=== CHECK ==="; cargo run -p xtask -- check 2>&1 | tail -15; echo "XTASK_DONE"


   Compiling spt-daemon v0.6.0 (C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon)
   Compiling xtask v0.6.0 (C:\Users\decid\Documents\projects\spt-core\crates\xtask)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 14.49s
     Running `target\debug\xtask.exe gen`
xtask gen: wrote C:\Users\decid\Documents\projects\spt-core\docs-site/src/cli/reference.md
=== CHECK ===
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.47s
     Running `target\debug\xtask.exe check`
xtask check: OK
XTASK_DONE


Docs gen + drift gate green. Now traceable + the bare-spt/picker/manifest tests:


cd "C:\Users\decid\Documents\projects\spt-core" && traceable-reqs check 2>&1 | grep -iE '[0-9]+ requirements|MANIFEST-7|parse error' | head; echo "---tests---"; cargo test -p spt --bin spt 'tests::bare' 2>&1 | grep -iE 'test result'; cargo test -p spt --bin spt picker:: 2>&1 | grep -iE 'test result'; cargo test -p spt-runtime shortcut_basename 2>&1 | grep -iE 'test result'


201 requirements: 201 complete, 0 incomplete, 0 findings
  [OK] REQ-MANIFEST-7  required: [doc, impl, unit]  stages: +doc +impl +unit -int
---tests---
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 49 filtered out; finished in 0.00s


Confirm the bare + picker test counts explicitly:


cd "C:\Users\decid\Documents\projects\spt-core" && cargo test -p spt --bin spt 2>&1 | grep -iE 'test result|FAILED'; echo "BIN_DONE"


No output — investigate (compile error?):


cd "C:\Users\decid\Documents\projects\spt-core" && cargo test -p spt --bin spt 2>&1 | tail -25


     |
     = note: expected enum `Option<cli::Cmd>`
                found enum `cli::Cmd`
help: try wrapping the pattern in `Some`
     |
8739 |             Some(Cmd::HowTo { topic: None })
     |             +++++                          +

error[E0308]: mismatched types
    --> crates\spt\src\cli.rs:8743:13
     |
8742 |             parse(&["spt", "how-to", "ready"]).unwrap().cmd,
     |             ----------------------------------------------- this expression has type `Option<cli::Cmd>`
8743 |             Cmd::HowTo { topic: Some(t) } if t == "ready"
     |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `Option<Cmd>`, found `Cmd`
     |
     = note: expected enum `Option<cli::Cmd>`
                found enum `cli::Cmd`
help: try wrapping the pattern in `Some`
     |
8743 |             Some(Cmd::HowTo { topic: Some(t) }) if t == "ready"
     |             +++++                             +

For more information about this error, try `rustc --explain E0308`.
error: could not compile `spt` (bin "spt" test) due to 60 previous errors


Existing cli tests use `.cmd` as `Cmd` — the Option change broke ~60. Mechanical fix needed. Survey the patterns:


934:    let Some(command) = cli.cmd else {
5932:        assert!(cli.cmd.is_none());
5934:        assert!(parse(&["spt", "whoami"]).unwrap().cmd.is_some());
6102:                .cmd,
6106:            parse(&["spt", "send", "bob", "--deferred"]).unwrap().cmd,
6112:                .cmd,
6116:            parse(&["spt", "ready", "bob"]).unwrap().cmd,
6122:            parse(&["spt", "ready", "bob", "--once"]).unwrap().cmd,
6130:            parse(&["spt", "whoami"]).unwrap().cmd,
6177:            parse(&["spt", "endpoint"]).unwrap().cmd,
6181:            parse(&["spt", "endpoint", "list", "--local"]).unwrap().cmd,
6189:                .cmd,
6204:                .cmd,
6225:            .cmd,
6239:            parse(&["spt", "endpoint", "suspend", "ling"]).unwrap().cmd,
6245:            parse(&["spt", "endpoint", "wake", "ling"]).unwrap().cmd,
6252:            parse(&["spt", "endpoint", "shutdown"]).unwrap().cmd,
6258:            parse(&["spt", "endpoint", "stop", "bob"]).unwrap().cmd,
6267:                .cmd,
6289:            .cmd,
6297:            parse(&["spt", "endpoint", "description"]).unwrap().cmd,
6315:            parse(&["spt", "daemon"]).unwrap().cmd,
6319:            parse(&["spt", "daemon", "run"]).unwrap().cmd,
6326:            parse(&["spt", "daemon", "start"]).unwrap().cmd,
6332:            parse(&["spt", "daemon", "stop"]).unwrap().cmd,
6338:            parse(&["spt", "daemon", "status"]).unwrap().cmd,
6349:            parse(&["spt", "daemon", "brain"]).unwrap().cmd,
6357:                .cmd,
6381:            .cmd,
6387:            parse(&["spt", "subnet", "notify"]).unwrap().cmd,
6395:                .cmd,
6401:            parse(&["spt", "notif", "dismiss", "cafe:7"]).unwrap().cmd,
6412:            parse(&["spt", "update", "apply"]).unwrap().cmd,
6458:            match parse(&full).unwrap().cmd {
6503:        .cmd
6522:                .cmd,
6533:                .cmd,
6539:            parse(&["spt", "grant", "list"]).unwrap().cmd,
6545:            parse(&["spt", "grant", "list", "ling"]).unwrap().cmd,
6563:                .cmd,

[Showing results with pagination = limit: 40]


Existing cli tests need `.cmd` → `.cmd.unwrap()` (Option change). Bulk-fix the dominant patterns, then compile to catch stragglers:


The file C:\Users\decid\Documents\projects\spt-core\crates\spt\src\cli.rs has been updated. All occurrences were successfully replaced. (file state is current in your context — no need to Read it back)

parse(&["spt", "send", "bob", "--from", "alice"])

-            parse(&["spt", "send", "bob", "--deferred"]).unwrap().cmd,

+            parse(&["spt", "send", "bob", "--deferred"]).unwrap().cmd.unwrap(),

parse(&["spt", "ring", "bob", "--timeout", "5"])

-            parse(&["spt", "ready", "bob"]).unwrap().cmd,

+            parse(&["spt", "ready", "bob"]).unwrap().cmd.unwrap(),

// [unit->REQ-MSG-2] `ready --once` absorbs the removed `poll`'s

// drain-then-exit semantics (M7 plan decision 2); `poll` itself is gone.

-            parse(&["spt", "ready", "bob", "--once"]).unwrap().cmd,

+            parse(&["spt", "ready", "bob", "--once"]).unwrap().cmd.unwrap(),

-            parse(&["spt", "whoami"]).unwrap().cmd,

+            parse(&["spt", "whoami"]).unwrap().cmd.unwrap(),

// Bare `spt endpoint` = the merged list (no action).

-            parse(&["spt", "endpoint"]).unwrap().cmd,

+            parse(&["spt", "endpoint"]).unwrap().cmd.unwrap(),

-            parse(&["spt", "endpoint", "list", "--local"]).unwrap().cmd,

+            parse(&["spt", "endpoint", "list", "--local"]).unwrap().cmd.unwrap(),

action: Some(EndpointCmd::List { local: true, .. })

parse(&["spt", "endpoint", "list", "--subnet", "home", "--detail"])

parse(&["spt", "endpoint", "rename", "ling", "oak"])

action: Some(EndpointCmd::Rename { .. })

// [unit->REQ-INST-3] the resting verbs (one local id).

-            parse(&["spt", "endpoint", "suspend", "ling"]).unwrap().cmd,

+            parse(&["spt", "endpoint", "suspend", "ling"]).unwrap().cmd.unwrap(),

action: Some(EndpointCmd::Suspend { .. })

-            parse(&["spt", "endpoint", "wake", "ling"]).unwrap().cmd,

+            parse(&["spt", "endpoint", "wake", "ling"]).unwrap().cmd.unwrap(),

// [unit->REQ-SHELL-2] shutdown with and without the self-default id.

-            parse(&["spt", "endpoint", "shutdown"]).unwrap().cmd,

+            parse(&["spt", "endpoint", "shutdown"]).unwrap().cmd.unwrap(),

action: Some(EndpointCmd::Shutdown { id: None })

-            parse(&["spt", "endpoint", "stop", "bob"]).unwrap().cmd,

+            parse(&["spt", "endpoint", "stop", "bob"]).unwrap().cmd.unwrap(),

parse(&["spt", "endpoint", "digest", "doyle", "--follow", "--json"])

action: Some(DescriptionCmd::Set { .. })

-            parse(&["spt", "endpoint", "description"]).unwrap().cmd,

+            parse(&["spt", "endpoint", "description"]).unwrap().cmd.unwrap(),

action: Some(EndpointCmd::Description { action: None })

-            parse(&["spt", "daemon"]).unwrap().cmd,

+            parse(&["spt", "daemon"]).unwrap().cmd.unwrap(),

-            parse(&["spt", "daemon", "run"]).unwrap().cmd,

+            parse(&["spt", "daemon", "run"]).unwrap().cmd.unwrap(),

// `start` graduated from a `run` alias to its own verb.

-            parse(&["spt", "daemon", "start"]).unwrap().cmd,

+            parse(&["spt", "daemon", "start"]).unwrap().cmd.unwrap(),

-            parse(&["spt", "daemon", "stop"]).unwrap().cmd,

+            parse(&["spt", "daemon", "stop"]).unwrap().cmd.unwrap(),

-            parse(&["spt", "daemon", "status"]).unwrap().cmd,

+            parse(&["spt", "daemon", "status"]).unwrap().cmd.unwrap(),

// the stamped form carries the broker's `{generation, start-reason}`.

-            parse(&["spt", "daemon", "brain"]).unwrap().cmd,

+            parse(&["spt", "daemon", "brain"]).unwrap().cmd.unwrap(),

action: Some(DaemonCmd::Brain { generation: 0, ref start_reason })

parse(&["spt", "daemon", "brain", "--generation", "4", "--start-reason", "update"])

action: Some(DaemonCmd::Brain { generation: 4, ref start_reason })

-            parse(&["spt", "subnet", "notify"]).unwrap().cmd,

+            parse(&["spt", "subnet", "notify"]).unwrap().cmd.unwrap(),

action: Some(SubnetCmd::Notify { body: None, .. })

parse(&["spt", "notif", "list", "--subnet", "home"])

-            parse(&["spt", "notif", "dismiss", "cafe:7"]).unwrap().cmd,

+            parse(&["spt", "notif", "dismiss", "cafe:7"]).unwrap().cmd.unwrap(),

// [unit->REQ-UPD-4] the update-consent body names this explicit ack verb.

-            parse(&["spt", "update", "apply"]).unwrap().cmd,

+            parse(&["spt", "update", "apply"]).unwrap().cmd.unwrap(),

parse(&["spt", "grant", "add", "owner-shutdown", "ling"])

parse(&["spt", "grant", "revoke", "spawn-shell", "ling"])

-            parse(&["spt", "grant", "list"]).unwrap().cmd,

+            parse(&["spt", "grant", "list"]).unwrap().cmd.unwrap(),

-            parse(&["spt", "grant", "list", "ling"]).unwrap().cmd,

+            parse(&["spt", "grant", "list", "ling"]).unwrap().cmd.unwrap(),

action: GrantCmd::List { agent: Some(_) }

parse(&["spt", "adapter", "add", "C:/adapters/mock"])

parse(&["spt", "adapter", "add", "--github", "user/repo"])

-            parse(&["spt", "adapter", "list"]).unwrap().cmd,

+            parse(&["spt", "adapter", "list"]).unwrap().cmd.unwrap(),

_ => panic!("expected Adapter CreateProfile"),

-            parse(&["spt", "adapter", "delete-profile", "claude-spt", "work"]).unwrap().cmd,

+            parse(&["spt", "adapter", "delete-profile", "claude-spt", "work"]).unwrap().cmd.unwrap(),

action: AdapterCmd::DeleteProfile { .. }

-            parse(&["spt", "shell", "spawn", "GameRobot"]).unwrap().cmd,

+            parse(&["spt", "shell", "spawn", "GameRobot"]).unwrap().cmd.unwrap(),

-            parse(&["spt", "shell", "list"]).unwrap().cmd,

+            parse(&["spt", "shell", "list"]).unwrap().cmd.unwrap(),

parse(&["spt", "shell", "teardown", "TempleKeeper"])

parse(&["spt", "shell", "rename", "GameRobot-0", "Keeper"])

parse(&["spt", "shell", "relink", "GameRobot-0"])

-            parse(&["spt", "shell", "send", "Scout"]).unwrap().cmd,

+            parse(&["spt", "shell", "send", "Scout"]).unwrap().cmd.unwrap(),

-            parse(&["spt", "how-to"]).unwrap().cmd,

+            parse(&["spt", "how-to"]).unwrap().cmd.unwrap(),

-            parse(&["spt", "how-to", "ready"]).unwrap().cmd,

+            parse(&["spt", "how-to", "ready"]).unwrap().cmd.unwrap(),

Cmd::HowTo { topic: Some(t) } if t == "ready"


let access = |args: &[&str]| -> AccessCmd {

let mut full = vec!["spt", "endpoint", "access"];

-            match parse(&full).unwrap().cmd {

+            match parse(&full).unwrap().cmd.unwrap() {

action: Some(EndpointCmd::Access { action }),

// [unit->REQ-TERM-4] digest defaults: a one-shot, human-rendered snapshot.

-        match parse(&["spt", "endpoint", "digest", "doyle"]).unwrap().cmd {

+        match parse(&["spt", "endpoint", "digest", "doyle"]).unwrap().cmd.unwrap() {

action: Some(EndpointCmd::Digest { id, follow, json }),

// [unit->REQ-MSG-2] ring timeout defaults to 60s when unspecified.

-        match parse(&["spt", "ring", "bob"]).unwrap().cmd {

+        match parse(&["spt", "ring", "bob"]).unwrap().cmd.unwrap() {

Cmd::Ring { timeout, .. } => assert_eq!(timeout, 60),

// [unit->REQ-MSG-2] send target is optional (supplied via --reply-to instead).

-        match parse(&["spt", "send", "--reply-to", "alice"]).unwrap().cmd {

+        match parse(&["spt", "send", "--reply-to", "alice"]).unwrap().cmd.unwrap() {

// ── subnet namespace ────────────────────────────────────────────────────────

fn subnet_cmd(args: &[&str]) -> Option<SubnetCmd> {

-        match parse(args).unwrap().cmd {

+        match parse(args).unwrap().cmd.unwrap() {


cd "C:\Users\decid\Documents\projects\spt-core" && cargo test -p spt --bin spt 2>&1 | grep -iE 'error\[|error:|\.cmd' | head -30; echo "COMPILE_CHECK"


error[E0308]: mismatched types
6503 | |         .cmd
error[E0308]: mismatched types
6584 | |             .cmd
error[E0308]: mismatched types
6602 | |             .cmd
error[E0308]: mismatched types
6620 | |             .cmd
error[E0308]: mismatched types
6808 | |         .cmd
error[E0308]: mismatched types
6858 | |             .cmd
error[E0308]: mismatched types
6880 | |         .cmd
error[E0308]: mismatched types
7004 | |         .cmd
error[E0308]: mismatched types
7264 | |         .cmd
error: could not compile `spt` (bin "spt" test) due to 9 previous errors
COMPILE_CHECK


9 stragglers — `.cmd` alone on a line (match block spanning lines). Inspect one:


6499	            "--qualifier",
6500	            "OsNotify",
6501	        ])
6502	        .unwrap()
6503	        .cmd
6504	        {
6505	            Cmd::Grant {
6506	                action:

            "--qualifier",
            "OsNotify",
        ])
        .unwrap()
        .cmd
        {
            Cmd::Grant {
                action:


Multiline `.cmd\n{` form. Fix both indent levels:


Edit

        .cmd
        .unwrap()
        {


        .cmd
        .unwrap()
        {

])
.unwrap()
.cmd
+        .unwrap()
{
Cmd::Grant {
action:

match parse(&["spt", "adapter", "remove", "mock-shell", "--force"])

action: AdapterCmd::Remove { name, force },

match parse(&["spt", "adapter", "create-profile", "claude-spt", "work", "--from", "o.toml"])

action: AdapterCmd::CreateProfile { adapter, name, from },

match parse(&["spt", "adapter", "digest-proof", "claude-spt", "--sample", "log.jsonl"])

action: AdapterCmd::DigestProof { option, sample },

])
.unwrap()
.cmd
+        .unwrap()
{
Cmd::Shell {
action:

match parse(&["spt", "shell", "cmd", "TempleKeeper", "move", "north", "3"])

action: ShellCmd::Cmd { shell_ref, op, .. },


            .cmd
            .unwrap()
            {


<tool_use_error>String to replace not found in file.
String:             .cmd
            {</tool_use_error>

Error: String to replace not found in file.
String:             .cmd
            {


Bash

cd "C:\Users\decid\Documents\projects\spt-core" && cargo test -p spt --bin spt 2>&1 | grep -iE 'error\[|error:|test result' | head -20; echo "DONE"


test result: ok. 153 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 30.37s
DONE


All 153 spt bin tests green (incl. bare-spt + picker). Full sweep — clippy -D + workspace tests:


cd "C:\Users\decid\Documents\projects\spt-core" && cargo clippy --workspace --all-targets -- -D warnings 2>&1 | grep -iE 'warning:|error' | head; echo "CLIPPY_DONE rc=${PIPESTATUS[0]}"


<owl_messages>
<owl_message from="doyle" priority="high">TINY ADD to your current batch (operator) — fold into the bare-spt cli.rs work since you're already there (avoids us both editing cli.rs on the shared tree):

UNDOCUMENTED easter egg: `spt spt`
- Hidden top-level subcommand Cmd::Spt with #[command(hide = true)] — must NOT appear in help/usage.
- stdout EXACTLY: Sentience Pocket Transacter   (single line + newline, nothing else, exit 0).
- Composes with bare-spt: no subcommand → picker (your work); `spt spt` → Some(Cmd::Spt) → print + exit.
- Intentionally REQ-LESS (operator-sanctioned easter egg; traceable only gates declared REQs, so no violation — do NOT add a REQ or doc tag, that'd defeat "undocumented").
- Changelog: add a deliberately vague line to the next/unreleased CHANGELOG section: "`spt spt` — ???" (nothing more).

That's it — 4-ish lines + the changelog stub. Include it in the gate batch (bare-spt + REQ-MANIFEST-7 + this). Ping when green.</owl_message>
</owl_messages>

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


CLIPPY_DONE rc=0


ACK. Clippy clean (rc=0). Adding the `spt spt` easter egg to this batch.


cd "C:\Users\decid\Documents\projects\spt-core" && $OWL send doyle <<'EOF'
Easter egg folded in (good call avoiding dual cli.rs edits on the shared tree). Adding:
- hidden Cmd::Spt (#[command(hide=true)] — absent from help/usage); dispatch prints exactly "Sentience Pocket Transacter\n", exit 0.
- composes with bare-spt: no subcommand → picker; `spt spt` → Some(Cmd::Spt) → print+exit.
- REQ-LESS + no doc tag (undocumented by design; traceable only gates declared REQs → no violation).
- CHANGELOG unreleased: `spt spt` — ??? (nothing more).

Batch now = bare-spt→picker + REQ-MANIFEST-7 (manifest field + picker resolve + MANIFEST.md + integration-checklist.md + schema bless) + this. Sweep so far green: spt bin 153/0 (incl bare-spt + picker), spt-runtime shortcut_basename, traceable 201/201, xtask docs check OK, clippy --workspace -D rc=0. Running full workspace test suite + adding egg, then ping to gate.
EOF
echo "EXIT=$?"


➜ SENT:doyle
EXIT=0


432	    ///
433	    /// The broker stamps `--generation`/`--start-reason` at spawn (ADR-0018 V2
434	    /// generation custody + the Q4 update-vs-crash discriminator). Both are
435	    /// **defaulted** so an older broker that spawns this brain with the bare old
436	    /// argv still parses (generation 0 / `cold`) — the new-brain × old-broker
437	    /// N-1 steady state (KH-2.3 handoff-argv-compat).
438	    #[command(hide = true)]
439	    Brain {
440	        /// Broker-owned generation counter for this brain instance (KH-2.4).
441	        #[arg(long, default_value_t = 0)]
442	        generation: u64,
443	        /// Why the broker spawned this brain: `cold` | `crash` | `update`
444	        /// (parsed leniently — an unknown value defaults to `cold`).
445	        #[arg(long, default_value = "cold")]

    ///
    /// The broker stamps `--generation`/`--start-reason` at spawn (ADR-0018 V2
    /// generation custody + the Q4 update-vs-crash discriminator). Both are
    /// **defaulted** so an older broker that spawns this brain with the bare old
    /// argv still parses (generation 0 / `cold`) — the new-brain × old-broker
    /// N-1 steady state (KH-2.3 handoff-argv-compat).
    #[command(hide = true)]
    Brain {
        /// Broker-owned generation counter for this brain instance (KH-2.4).
        #[arg(long, default_value_t = 0)]
        generation: u64,
        /// Why the broker spawned this brain: `cold` | `crash` | `update`
        /// (parsed leniently — an unknown value defaults to `cold`).
        #[arg(long, default_value = "cold")]


445	        #[arg(long, default_value = "cold")]
446	        start_reason: String,
447	    },
448	}
449
450	/// `spt access <action>` subcommands (REQ-SEC-1).
451	#[derive(Subcommand)]
452	enum AccessCmd {
453	    /// Whitelist a node for an endpoint (creates the restriction if absent).
454	    Allow {
455	        /// The endpoint to restrict.
456	        endpoint: String,
457	        /// The origin node's pubkey hex (as shown by `spt endpoint list` / pairing).
458	        node: String,
459	    },
460	    /// Remove a node from an endpoint's whitelist. Never widens: revoking the
461	    /// last node leaves the endpoint locked down (all unsolicited refused).
462	    Revoke { endpoint: String, node: String },
463	    /// Delete an endpoint's restriction entirely — back to default-open.
464	    Open { endpoint: String },

        #[arg(long, default_value = "cold")]
        start_reason: String,
    },
}

/// `spt access <action>` subcommands (REQ-SEC-1).
#[derive(Subcommand)]
enum AccessCmd {
    /// Whitelist a node for an endpoint (creates the restriction if absent).
    Allow {
        /// The endpoint to restrict.
        endpoint: String,
        /// The origin node's pubkey hex (as shown by `spt endpoint list` / pairing).
        node: String,
    },
    /// Remove a node from an endpoint's whitelist. Never widens: revoking the
    /// last node leaves the endpoint locked down (all unsolicited refused).
    Revoke { endpoint: String, node: String },
    /// Delete an endpoint's restriction entirely — back to default-open.
    Open { endpoint: String },


The `Cmd` enum closes at line 448. Add the hidden `Spt` variant before it:


Edit

        #[arg(long, default_value = "cold")]
        start_reason: String,
    },
}

/// `spt access <action>` subcommands (REQ-SEC-1).

        #[arg(long, default_value = "cold")]
        start_reason: String,
    },
    // Undocumented. Hidden from help/usage by design.
    #[command(hide = true)]
    Spt,
}

/// `spt access <action>` subcommands (REQ-SEC-1).


The file C:\Users\decid\Documents\projects\spt-core\crates\spt\src\cli.rs has been updated successfully. (file state is current in your context — no need to Read it back)

        #[arg(long, default_value = "cold")]
        start_reason: String,
    },
}

/// `spt access <action>` subcommands (REQ-SEC-1).

        #[arg(long, default_value = "cold")]
        start_reason: String,
    },
    // Undocumented. Hidden from help/usage by design.
    #[command(hide = true)]
    Spt,
}

/// `spt access <action>` subcommands (REQ-SEC-1).

+    // Undocumented. Hidden from help/usage by design.


Add the dispatch arm. Find where the match handles `Cmd::Brain`:


1057:        Cmd::Daemon { action } => match action.unwrap_or(DaemonCmd::Status) {
1062:            DaemonCmd::Brain {
6319:            Cmd::Daemon { action: None }
6323:            Cmd::Daemon {
6330:            Cmd::Daemon {
6336:            Cmd::Daemon {
6342:            Cmd::Daemon {
6353:            Cmd::Daemon {
6354:                action: Some(DaemonCmd::Brain { generation: 0, ref start_reason })
6361:            Cmd::Daemon {

[Showing results with pagination = limit: 10]


Grep

Cmd::Rc|Cmd::Update|Cmd::Grant|=> cmd_|Cmd::Adapter \{ action \}


6527:            Cmd::Grant {
6538:            Cmd::Grant {
6544:            Cmd::Grant {
6550:            Cmd::Grant {

[Showing results with pagination = offset: 40]


=> cmd_rc|Cmd::Rc|=> run_daemon|Cmd::Daemon \{ action \} =>


1036:        Cmd::Rc { id, view } => match crate::rc::run_attach(&id, view) {
1057:        Cmd::Daemon { action } => match action.unwrap_or(DaemonCmd::Status) {


1036	        Cmd::Rc { id, view } => match crate::rc::run_attach(&id, view) {
1037	            Ok(()) => 0,
1038	            Err(e) => {
1039	                eprintln!("RC_FAIL:{id}: {e}");
1040	                1
1041	            }
1042	        },
1043	        Cmd::Grant { action } => cmd_grant(action),
1044	        Cmd::Adapter { action } => cmd_adapter(action),
1045	        Cmd::Shell { action } => cmd_shell(action),
1046	        Cmd::Notif { action } => match action {
1047	            NotifCmd::List { subnet } => cmd_notif_list(subnet),
1048	            NotifCmd::Dismiss { notif_id } => cmd_notif_dismiss(&notif_id),
1049	        },
1050	        Cmd::Update { action } => match action {
1051	            UpdateCmd::Apply => cmd_update_apply(),
1052	            UpdateCmd::Fetch { channel, tag } => cmd_update_fetch(channel, tag),
1053	        },
1054	        Cmd::Api(args) => crate::api::run(args),
1055	        // The daemon noun (M8 decisions 5, 25b): bare = the node status view.
1056	        // [impl->REQ-CLI-2]
1057	        Cmd::Daemon { action } => match action.unwrap_or(DaemonCmd::Status) {
1058	            DaemonCmd::Run => cmd_daemon_run(),
1059	            DaemonCmd::Start => cmd_daemon_start(),

        Cmd::Rc { id, view } => match crate::rc::run_attach(&id, view) {
            Ok(()) => 0,
            Err(e) => {
                eprintln!("RC_FAIL:{id}: {e}");
                1
            }
        },
        Cmd::Grant { action } => cmd_grant(action),
        Cmd::Adapter { action } => cmd_adapter(action),
        Cmd::Shell { action } => cmd_shell(action),
        Cmd::Notif { action } => match action {
            NotifCmd::List { subnet } => cmd_notif_list(subnet),
            NotifCmd::Dismiss { notif_id } => cmd_notif_dismiss(&notif_id),
        },
        Cmd::Update { action } => match action {
            UpdateCmd::Apply => cmd_update_apply(),
            UpdateCmd::Fetch { channel, tag } => cmd_update_fetch(channel, tag),
        },
        Cmd::Api(args) => crate::api::run(args),
        // The daemon noun (M8 decisions 5, 25b): bare = the node status view.
        // [impl->REQ-CLI-2]
        Cmd::Daemon { action } => match action.unwrap_or(DaemonCmd::Status) {
            DaemonCmd::Run => cmd_daemon_run(),
            DaemonCmd::Start => cmd_daemon_start(),


Edit

        Cmd::Api(args) => crate::api::run(args),
        // The daemon noun (M8 decisions 5, 25b): bare = the node status view.

        Cmd::Api(args) => crate::api::run(args),
        Cmd::Spt => {
            println!("Sentience Pocket Transacter");
            0
        }
        // The daemon noun (M8 decisions 5, 25b): bare = the node status view.


        Cmd::Api(args) => crate::api::run(args),
        // The daemon noun (M8 decisions 5, 25b): bare = the node status view.

        Cmd::Api(args) => crate::api::run(args),
        Cmd::Spt => {
            println!("Sentience Pocket Transacter");
            0
        }
        // The daemon noun (M8 decisions 5, 25b): bare = the node status view.

UpdateCmd::Fetch { channel, tag } => cmd_update_fetch(channel, tag),

+            println!("Sentience Pocket Transacter");

Cmd::Daemon { action } => match action.unwrap_or(DaemonCmd::Status) {


Now the CHANGELOG vague stub. Find the unreleased section:


1	# Changelog
2
3	All notable **user-facing** changes to `spt` — what a person running the CLI
4	notices or does differently. The `## [<version>]` section of each release
5	becomes that release's GitHub Release notes verbatim (see
6	`docs/RELEASE-RUNBOOK.md`). This project follows
7	[Keep a Changelog](https://keepachangelog.com) and semantic versioning
8	(pre-1.0: breaking changes bump the minor).
9
10	## [Unreleased]
11
12	## [0.6.0] - 2026-06-13
13
14	The session digest grows up — its own adapter seam, it follows an agent across `/clear`, and it shows the context spt itself feeds the agent.
15
16	### Changed
17
18	- **The session digest gets its own adapter seam, follows an agent across `/clear`, and shows the context spt feeds it.** An adapter now declares a `[digest]` *extractor* that maps its native log to the digest's `{role, text, tool, ts}` contract — its **own** manifest section, separate from `[history]` (which stays full-fidelity for the echo-commune). The digest **spans** a `/clear` or `/compact`: it enumerates an endpoint's recent sessions and shows a `── /clear ──` divider instead of going blank at every reset. It also **interleaves spt's own injected context** (session-start Psyche download, echo mirror, incoming messages) with the agent's activity, in time order. New `spt adapter digest-proof <adapter> --sample <log>` runs your extractor against a real log and prints exactly what parsed, what rendered, and **every dropped line with the reason** — no more silent-empty digest.
19	  - **Breaking (adapter authors):** this **supersedes** the v0.5.0 guidance to emit the digest contract through your `[history]` normalizer. Declare a `[digest]` extractor (or push via `spt api digest-entry`) instead; one `[history]` normalizer can no longer serve both the opaque echo-commune and the contract-typed digest.
20
21	## [0.5.0] - 2026-06-13
22
23	Adapter customization and richer session surfaces — make an adapter your own without forking it, give an agent a durable role, mark who a message came from, and get an at-a-glance "what is this agent doing" view for any session.
24
25	### Added

# Changelog

All notable **user-facing** changes to `spt` — what a person running the CLI
notices or does differently. The `## [<version>]` section of each release
becomes that release's GitHub Release notes verbatim (see
`docs/RELEASE-RUNBOOK.md`). This project follows
[Keep a Changelog](https://keepachangelog.com) and semantic versioning
(pre-1.0: breaking changes bump the minor).

## [Unreleased]

## [0.6.0] - 2026-06-13

The session digest grows up — its own adapter seam, it follows an agent across `/clear`, and it shows the context spt itself feeds the agent.

### Changed

- **The session digest gets its own adapter seam, follows an agent across `/clear`, and shows the context spt feeds it.** An adapter now declares a `[digest]` *extractor* that maps its native log to the digest's `{role, text, tool, ts}` contract — its **own** manifest section, separate from `[history]` (which stays full-fidelity for the echo-commune). The digest **spans** a `/clear` or `/compact`: it enumerates an endpoint's recent sessions and shows a `── /clear ──` divider instead of going blank at every reset. It also **interleaves spt's own injected context** (session-start Psyche download, echo mirror, incoming messages) with the agent's activity, in time order. New `spt adapter digest-proof <adapter> --sample <log>` runs your extractor against a real log and prints exactly what parsed, what rendered, and **every dropped line with the reason** — no more silent-empty digest.
  - **Breaking (adapter authors):** this **supersedes** the v0.5.0 guidance to emit the digest contract through your `[history]` normalizer. Declare a `[digest]` extractor (or push via `spt api digest-entry`) instead; one `[history]` normalizer can no longer serve both the opaque echo-commune and the contract-typed digest.

## [0.5.0] - 2026-06-13

Adapter customization and richer session surfaces — make an adapter your own without forking it, give an agent a durable role, mark who a message came from, and get an at-a-glance "what is this agent doing" view for any session.

### Added


Add Unreleased entries — the picker (user-facing) + the vague egg line:


## [Unreleased]

### Added

- **`spt endpoint run` is now an interactive picker.** Run it bare (no `--adapter`/`--id`) and pick *Create new* (choose a harness adapter + profile, name the endpoint) or *Pick existing* (browse by project / local node / subnet with live status, type-to-filter, and a description pane), then attach / start / view / resume-from-history. The flagged form is unchanged for scripts. **Bare `spt`** (no subcommand) opens the same picker on an interactive terminal — a pipe, redirect, or CI run still prints help instead. Press `s` in the picker to bake the current selection into a project-root `spt-<id>` launcher shortcut (an adapter can brand it, e.g. `cc-<id>`, via the new `[adapter] shortcut_basename` manifest field).
- `spt spt` — ???

## [0.6.0] - 2026-06-13


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

## [Unreleased]

### Added

- **`spt endpoint run` is now an interactive picker.** Run it bare (no `--adapter`/`--id`) and pick *Create new* (choose a harness adapter + profile, name the endpoint) or *Pick existing* (browse by project / local node / subnet with live status, type-to-filter, and a description pane), then attach / start / view / resume-from-history. The flagged form is unchanged for scripts. **Bare `spt`** (no subcommand) opens the same picker on an interactive terminal — a pipe, redirect, or CI run still prints help instead. Press `s` in the picker to bake the current selection into a project-root `spt-<id>` launcher shortcut (an adapter can brand it, e.g. `cc-<id>`, via the new `[adapter] shortcut_basename` manifest field).
- `spt spt` — ???

## [0.6.0] - 2026-06-13

+- **`spt endpoint run` is now an interactive picker.** Run it bare (no `--adapter`/`--id`) and pick *Create new* (choose a harness adapter + profile, name the endpoint) or *Pick existing* (browse by project / local node / subnet with live status, type-to-filter, and a description pane), then attach / start / view / resume-from-history. The flagged form is unchanged for scripts. **Bare `spt`** (no subcommand) opens the same picker on an interactive terminal — a pipe, redirect, or CI run still prints help instead. Press `s` in the picker to bake the current selection into a project-root `spt-<id>` launcher shortcut (an adapter can brand it, e.g. `cc-<id>`, via the new `[adapter] shortcut_basename` manifest field).

The session digest grows up — its own adapter seam, it follows an agent across `/clear`, and it shows the context spt itself feeds the agent.


Build + verify the egg (prints exactly, hidden from help), then full sweep:


Bash

cd "C:\Users\decid\Documents\projects\spt-core" && cargo build -p spt 2>&1 | grep -iE 'error|warning'; echo "=== spt spt ==="; ./target/debug/spt spt; echo "=== hidden? (expect no match) ==="; ./target/debug/spt --help 2>&1 | grep -i 'sentience\|^  spt ' || echo "NOT IN HELP (good)"


error[E0599]: no variant or associated item named `Spt` found for enum `Cmd` in the current scope
For more information about this error, try `rustc --explain E0599`.
error: could not compile `spt` (bin "spt") due to 1 previous error
=== spt spt ===
error: unrecognized subcommand 'spt'

Usage: spt.exe [COMMAND]

For more information, try '--help'.
=== hidden? (expect no match) ===
NOT IN HELP (good)


444	        /// (parsed leniently — an unknown value defaults to `cold`).
445	        #[arg(long, default_value = "cold")]
446	        start_reason: String,
447	    },
448	    // Undocumented. Hidden from help/usage by design.
449	    #[command(hide = true)]
450	    Spt,
451	}
452
453	/// `spt access <action>` subcommands (REQ-SEC-1).

        /// (parsed leniently — an unknown value defaults to `cold`).
        #[arg(long, default_value = "cold")]
        start_reason: String,
    },
    // Undocumented. Hidden from help/usage by design.
    #[command(hide = true)]
    Spt,
}

/// `spt access <action>` subcommands (REQ-SEC-1).


Variant is present — need the full error location:


cd "C:\Users\decid\Documents\projects\spt-core" && cargo build -p spt 2>&1 | grep -B2 -A6 'E0599' | head -25


   Compiling spt v0.6.0 (C:\Users\decid\Documents\projects\spt-core\crates\spt)
error[E0599]: no variant or associated item named `Spt` found for enum `Cmd` in the current scope
    --> crates\spt\src\cli.rs:1055:14
     |
  60 | enum Cmd {
     | -------- variant or associated item `Spt` not found for this enum
...
1055 |         Cmd::Spt => {
     |              ^^^ variant or associated item not found in `Cmd`

For more information about this error, try `rustc --explain E0599`.
error: could not compile `spt` (bin "spt") due to 1 previous error


Variant is at line 450 but compiler says it's outside `Cmd` — likely a brace imbalance from an earlier edit. Check for duplicate enum + brace integrity:


cd "C:\Users\decid\Documents\projects\spt-core" && grep -n 'enum Cmd' crates/spt/src/cli.rs; echo "---around 55-60---"; sed -n '55,61p' crates/spt/src/cli.rs


60:enum Cmd {
---around 55-60---
    #[command(subcommand)]
    cmd: Option<Cmd>,
}

#[derive(Subcommand)]
enum Cmd {
    /// Send a message (body read from stdin); fire-and-forget.


cd "C:\Users\decid\Documents\projects\spt-core" && cargo build -p spt 2>&1 | grep -iE 'error' | head -20


error[E0599]: no variant or associated item named `Spt` found for enum `Cmd` in the current scope
For more information about this error, try `rustc --explain E0599`.
error: could not compile `spt` (bin "spt") due to 1 previous error


cd "C:\Users\decid\Documents\projects\spt-core" && grep -n 'Spt' crates/spt/src/cli.rs


450:    Spt,
1055:        Cmd::Spt => {


Both present — possibly stale incremental. Force-rebuild the spt crate:


cd "C:\Users\decid\Documents\projects\spt-core" && touch crates/spt/src/cli.rs && cargo build -p spt 2>&1 | grep -iE 'error|Compiling spt|Finished' | head


   Compiling spt v0.6.0 (C:\Users\decid\Documents\projects\spt-core\crates\spt)
error[E0599]: no variant or associated item named `Spt` found for enum `Cmd` in the current scope
For more information about this error, try `rustc --explain E0599`.
error: could not compile `spt` (bin "spt") due to 1 previous error


Let me view the Brain variant's full structure for a brace mismatch:


424	    /// manager so it does not auto-restart-fight; else a graceful IPC stop).
425	    Stop,
426	    /// Node status: daemon state, member subnets, local endpoints (the
427	    /// bare `spt daemon` view).
428	    Status,
429	    /// Hidden (ADR-0018 D1): the brain child process. The broker (`daemon run`)
430	    /// spawns + supervises this; it connects to the broker over the local IPC,
431	    /// signals ready, and idles. Not for direct user invocation.
432	    ///
433	    /// The broker stamps `--generation`/`--start-reason` at spawn (ADR-0018 V2
434	    /// generation custody + the Q4 update-vs-crash discriminator). Both are
435	    /// **defaulted** so an older broker that spawns this brain with the bare old
436	    /// argv still parses (generation 0 / `cold`) — the new-brain × old-broker
437	    /// N-1 steady state (KH-2.3 handoff-argv-compat).
438	    #[command(hide = true)]
439	    Brain {
440	        /// Broker-owned generation counter for this brain instance (KH-2.4).
441	        #[arg(long, default_value_t = 0)]
442	        generation: u64,
443	        /// Why the broker spawned this brain: `cold` | `crash` | `update`
444	        /// (parsed leniently — an unknown value defaults to `cold`).
445	        #[arg(long, default_value = "cold")]
446	        start_reason: String,
447	    },
448	    // Undocumented. Hidden from help/usage by design.
449	    #[command(hide = true)]
450	    Spt,
451	}

    /// manager so it does not auto-restart-fight; else a graceful IPC stop).
    Stop,
    /// Node status: daemon state, member subnets, local endpoints (the
    /// bare `spt daemon` view).
    Status,
    /// Hidden (ADR-0018 D1): the brain child process. The broker (`daemon run`)
    /// spawns + supervises this; it connects to the broker over the local IPC,
    /// signals ready, and idles. Not for direct user invocation.
    ///
    /// The broker stamps `--generation`/`--start-reason` at spawn (ADR-0018 V2
    /// generation custody + the Q4 update-vs-crash discriminator). Both are
    /// **defaulted** so an older broker that spawns this brain with the bare old
    /// argv still parses (generation 0 / `cold`) — the new-brain × old-broker
    /// N-1 steady state (KH-2.3 handoff-argv-compat).
    #[command(hide = true)]
    Brain {
        /// Broker-owned generation counter for this brain instance (KH-2.4).
        #[arg(long, default_value_t = 0)]
        generation: u64,
        /// Why the broker spawned this brain: `cold` | `crash` | `update`
        /// (parsed leniently — an unknown value defaults to `cold`).
        #[arg(long, default_value = "cold")]
        start_reason: String,
    },
    // Undocumented. Hidden from help/usage by design.
    #[command(hide = true)]
    Spt,
}


Found it — lines 424-451 are `DaemonCmd`, not `Cmd`. My `Spt` landed in the wrong enum. Revert it:


Edit

        start_reason: String,
    },
    // Undocumented. Hidden from help/usage by design.
    #[command(hide = true)]
    Spt,
}

/// `spt access <action>` subcommands (REQ-SEC-1).

        start_reason: String,
    },
}

/// `spt access <action>` subcommands (REQ-SEC-1).


The file C:\Users\decid\Documents\projects\spt-core\crates\spt\src\cli.rs has been updated successfully. (file state is current in your context — no need to Read it back)

        start_reason: String,
    },
    // Undocumented. Hidden from help/usage by design.
    #[command(hide = true)]
    Spt,
}

/// `spt access <action>` subcommands (REQ-SEC-1).

        start_reason: String,
    },
}

/// `spt access <action>` subcommands (REQ-SEC-1).

-    // Undocumented. Hidden from help/usage by design.


Find the actual `Cmd` enum boundary (it ends before the first sub-enum):


Bash

cd "C:\Users\decid\Documents\projects\spt-core" && grep -nE '^enum |^#\[derive\(Subcommand\)\]|^\}' crates/spt/src/cli.rs | head -20


57:}
59:#[derive(Subcommand)]
60:enum Cmd {
213:}
219:#[derive(Subcommand)]
220:enum EndpointCmd {
388:}
393:#[derive(Subcommand)]
394:enum DescriptionCmd {
409:}
413:#[derive(Subcommand)]
414:enum DaemonCmd {
448:}
451:#[derive(Subcommand)]
452:enum AccessCmd {
467:}
470:#[derive(Subcommand)]
471:enum AdapterCmd {
553:}
556:#[derive(Subcommand)]


`Cmd` ends at line 213. Read its tail to insert `Spt`:


200	    /// Harness-contract inbound surface (hook entry points).
201	    ///
202	    /// The entry points a harness's hooks fire to keep spt-core's on-disk
203	    /// state in sync.
204	    Api(crate::api::ApiArgs),
205	    /// The per-machine daemon: run, stop, or read node status.
206	    ///
207	    /// Bare `spt daemon` renders the node status view — daemon state,
208	    /// member subnets, local endpoints (M8 decision 25).
209	    Daemon {
210	        #[command(subcommand)]
211	        action: Option<DaemonCmd>,
212	    },
213	}

    /// Harness-contract inbound surface (hook entry points).
    ///
    /// The entry points a harness's hooks fire to keep spt-core's on-disk
    /// state in sync.
    Api(crate::api::ApiArgs),
    /// The per-machine daemon: run, stop, or read node status.
    ///
    /// Bare `spt daemon` renders the node status view — daemon state,
    /// member subnets, local endpoints (M8 decision 25).
    Daemon {
        #[command(subcommand)]
        action: Option<DaemonCmd>,
    },
}


Edit

    Daemon {
        #[command(subcommand)]
        action: Option<DaemonCmd>,
    },
}

/// `spt access <action>` subcommands (REQ-SEC-1).

    Daemon {
        #[command(subcommand)]
        action: Option<DaemonCmd>,
    },
    // Undocumented. Hidden from help/usage by design.
    #[command(hide = true)]
    Spt,
}

/// `spt access <action>` subcommands (REQ-SEC-1).


<tool_use_error>String to replace not found in file.
String:     Daemon {
        #[command(subcommand)]
        action: Option<DaemonCmd>,
    },
}

/// `spt access <action>` subcommands (REQ-SEC-1).</tool_use_error>

Error: String to replace not found in file.
String:     Daemon {
        #[command(subcommand)]
        action: Option<DaemonCmd>,
    },
}

/// `spt access <action>` subcommands (REQ-SEC-1).


213	}
214
215	/// `spt endpoint <action>` subcommands (M8 decisions 1–2, 25 — REQ-CLI-1).
216	/// The verbs ride their pre-M8 arg shapes verbatim; only the noun home is
217	/// new (decision 9: breaking renames land clean, no shims).
218	// [impl->REQ-CLI-1]
219	#[derive(Subcommand)]

}

/// `spt endpoint <action>` subcommands (M8 decisions 1–2, 25 — REQ-CLI-1).
/// The verbs ride their pre-M8 arg shapes verbatim; only the noun home is
/// new (decision 9: breaking renames land clean, no shims).
// [impl->REQ-CLI-1]
#[derive(Subcommand)]


    Daemon {
        #[command(subcommand)]
        action: Option<DaemonCmd>,
    },
}

/// `spt endpoint <action>` subcommands (M8 decisions 1–2, 25 — REQ-CLI-1).

    Daemon {
        #[command(subcommand)]
        action: Option<DaemonCmd>,
    },
    // Undocumented. Hidden from help/usage by design.
    #[command(hide = true)]
    Spt,
}

/// `spt endpoint <action>` subcommands (M8 decisions 1–2, 25 — REQ-CLI-1).


    Daemon {
        #[command(subcommand)]
        action: Option<DaemonCmd>,
    },
}

/// `spt endpoint <action>` subcommands (M8 decisions 1–2, 25 — REQ-CLI-1).

    Daemon {
        #[command(subcommand)]
        action: Option<DaemonCmd>,
    },
    // Undocumented. Hidden from help/usage by design.
    #[command(hide = true)]
    Spt,
}

/// `spt endpoint <action>` subcommands (M8 decisions 1–2, 25 — REQ-CLI-1).


Bash

cd "C:\Users\decid\Documents\projects\spt-core" && cargo build -p spt 2>&1 | grep -iE 'error|warning|Finished' | head; echo "=== spt spt ==="; ./target/debug/spt spt; echo "exit=$?"; echo "=== help hidden check ==="; ./target/debug/spt --help 2>&1 | grep -ci 'sentience\|  spt  ' && echo "FOUND(bad)" || echo "hidden OK"


    Finished `dev` profile [unoptimized + debuginfo] target(s) in 5.31s
=== spt spt ===
Sentience Pocket Transacter
exit=0
=== help hidden check ===
0
hidden OK


Egg works, hidden. Final sweep — clippy + full workspace test:


cd "C:\Users\decid\Documents\projects\spt-core" && cargo clippy -p spt -p spt-runtime --all-targets -- -D warnings 2>&1 | grep -iE 'warning:|error' | head; echo "CLIPPY rc=${PIPESTATUS[0]}"; echo "=== full test suite ==="; cargo test --workspace 2>&1 | grep -iE 'test result: FAILED|FAILED|error\[|result: ok' | grep -iE 'FAILED|error' | head; echo "TESTS_DONE (no FAILED lines above = green)"


CLIPPY rc=0
=== full test suite ===
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 153 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 29.86s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 14.42s
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 7.95s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 7.64s
test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.01s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.25s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.22s
TESTS_DONE (no FAILED lines above = green)


Let me confirm cleanly — any failures across the workspace?


cd "C:\Users\decid\Documents\projects\spt-core" && cargo test --workspace 2>&1 | grep -iE 'result:' | grep -ivE 'result: ok' | head; echo "=== any non-ok above? (empty = all green) ==="; cargo test --workspace 2>&1 | grep -cE 'result: ok'; echo "ok-result-lines counted; traceable:"; traceable-reqs check 2>&1 | grep -iE '[0-9]+ requirements'


=== any non-ok above? (empty = all green) ===
69
ok-result-lines counted; traceable:
201 requirements: 201 complete, 0 incomplete, 0 findings


All green: 69 ok result-lines, 0 non-ok; traceable 201/201; clippy rc=0; docs drift-gate OK. Batch complete (uncommitted). Ping doyle to gate:


cd "C:\Users\decid\Documents\projects\spt-core" && $OWL send doyle <<'EOF'
BATCH READY TO GATE (uncommitted, m12-w1-bringup-rc) — bare-spt→picker + REQ-MANIFEST-7 + `spt spt` egg.

Sweep all green:
- full `cargo test --workspace`: 69 result-lines, ZERO non-ok. spt bin 153/0 (bare_spt_parses_to_none, bare_tty_guard, 26 picker, shortcut_basename API), spt-runtime shortcut_basename_optional_with_default.
- clippy --workspace --all-targets -D warnings: rc=0.
- traceable-reqs check: 201/201, 0 findings; REQ-MANIFEST-7 [doc+impl+unit] OK.
- xtask gen + check: OK (CLI reference regenerated for the new bare/--create/picker shape; drift-gate green).

Load-bearing for your verify:
1. bare-spt: Cli.cmd → Option<Cmd>; run() routes None → bare_invocation → decide_bare(stdin_tty, stdout_tty): both TTY → picker::run(None,None), else → print help (NEVER the TUI). decide_bare unit-tested 4 ways. --help/-h/help untouched (clap intercepts). ~60 existing cli tests updated .cmd→.cmd.unwrap().
2. REQ-MANIFEST-7: manifest [adapter] shortcut_basename: Option<String> (serde-default, skip_if_none → N-1-safe); manifest.schema.json re-blessed (SPT_BLESS); picker resolves SELECTED adapter's manifest via data::shortcut_basename (resolve_option → field, fallback "spt"); mod.rs no longer hardcodes the const at the call site. Docs: docs/MANIFEST.md [adapter] field + worked-example row (doc tag here) AND docs-site integration-checklist.md Group-3 row + claude-code clause (rides REQ-DOCS-2). Unit: declared "cc"→cc-<id>, absent→spt-<id>, N-1 round-trip.
3. `spt spt`: hidden Cmd::Spt (#[command(hide=true)] — verified absent from --help), prints exactly "Sentience Pocket Transacter"\n exit 0. REQ-less by design (no tag). CHANGELOG unreleased: `spt spt` — ??? + a picker line.

Bug I caught + fixed mid-build: first Spt insert landed in DaemonCmd (adjacent enum) → E0599; moved to the real Cmd enum (closes line 213). Clean now.

Uncommitted — operator calls the commit. Over to you to gate; then W4 subnet QR.
EOF
echo "EXIT=$?"


➜ SENT:doyle
EXIT=0


Update memory with the W2 follow-on batch: