581	    /// Prove an adapter's `[digest]` extractor against a real log sample
582	    /// (ADR-0019). Runs the declared extractor over `--sample <log>` (or the
583	    /// declared source) and prints the parsed contract records, the rendered
584	    /// digest, and **every dropped line with its reason** — the author-time
585	    /// answer to "`spt endpoint digest` returns nothing" (no silent empty).
586	    /// Exit 1 if any line drops or nothing parses.
587	    DigestProof {
588	        /// `<adapter>` or `<adapter>:<profile>` (must declare `[digest]`).
589	        option: String,
590	        /// A real session-log sample to run the extractor over (recommended).
591	        #[arg(long)]
592	        sample: Option<String>,
593	        /// The `{session_id}` to fill into the extractor command (the daemon
594	        /// fills the live one at runtime). Defaults to a placeholder so a
595	        /// `{session_id}`-templated extractor — the published shape — proofs;
596	        /// pin a real id when the file the extractor locates depends on it.
597	        #[arg(long)]
598	        session: Option<String>,
599	        /// Proof against an on-disk install **dir** instead of the registered
600	        /// adapter: binaries resolve in this dir before PATH (the same resolution
601	        /// the daemon uses) and the manifest defaults to `<dir>/manifest.toml`. No
602	        /// full extracted install needed — proof a DEV binary from its build dir.
603	        #[arg(long)]
604	        dir: Option<String>,
605	        /// Pin the manifest **file** for the proof (overrides `<dir>/manifest.toml`;
606	        /// absent `--dir`, its parent dir is the install dir). Lets a bare-file
607	        /// `gh_release` adapter proof without staging an extracted install.
608	        #[arg(long)]
609	        manifest: Option<String>,
610	    },
611	    /// Prove an adapter's `[message-idle-translation-binary]` against an inbound
612	    /// event (ADR-0022). Spawns and feeds the declared translation binary exactly
613	    /// as the daemon does at idle-delivery — sends the `init` line then the
614	    /// `--event` envelope and reads back the emitted keystroke-command stream
615	    /// (`{key}`/`{text}`/`{delay_ms}`/`{commit}`), printed author-readable. This is
616	    /// the EMIT half ONLY: it proves the binary's spawn-feed-emit contract; it does
617	    /// NOT exercise the daemon's atomic PTY apply or controller buffering. Fills
618	    /// `{id}` and `{session_id}` into the envelope the same way the daemon does
619	    /// (use `--session` to pin the session id). Exit 1 if the binary fails to
620	    /// spawn, emits nothing, emits no commit, or emits an unparseable line.
621	    TranslateProof {
622	        /// `<adapter>` or `<adapter>:<profile>` (must declare
623	        /// `[message-idle-translation-binary]`).
624	        option: String,
625	        /// The inbound `<EVENT…>` envelope to feed (ADR-0020). `{id}` and
626	        /// `{session_id}` tokens in it are filled as the daemon fills them.

    /// Prove an adapter's `[digest]` extractor against a real log sample
    /// (ADR-0019). Runs the declared extractor over `--sample <log>` (or the
    /// declared source) and prints the parsed contract records, the rendered
    /// digest, and **every dropped line with its reason** — the author-time
    /// answer to "`spt endpoint digest` returns nothing" (no silent empty).
    /// Exit 1 if any line drops or nothing parses.
    DigestProof {
        /// `<adapter>` or `<adapter>:<profile>` (must declare `[digest]`).
        option: String,
        /// A real session-log sample to run the extractor over (recommended).
        #[arg(long)]
        sample: Option<String>,
        /// The `{session_id}` to fill into the extractor command (the daemon
        /// fills the live one at runtime). Defaults to a placeholder so a
        /// `{session_id}`-templated extractor — the published shape — proofs;
        /// pin a real id when the file the extractor locates depends on it.
        #[arg(long)]
        session: Option<String>,
        /// Proof against an on-disk install **dir** instead of the registered
        /// adapter: binaries resolve in this dir before PATH (the same resolution
        /// the daemon uses) and the manifest defaults to `<dir>/manifest.toml`. No
        /// full extracted install needed — proof a DEV binary from its build dir.
        #[arg(long)]
        dir: Option<String>,
        /// Pin the manifest **file** for the proof (overrides `<dir>/manifest.toml`;
        /// absent `--dir`, its parent dir is the install dir). Lets a bare-file
        /// `gh_release` adapter proof without staging an extracted install.
        #[arg(long)]
        manifest: Option<String>,
    },
    /// Prove an adapter's `[message-idle-translation-binary]` against an inbound
    /// event (ADR-0022). Spawns and feeds the declared translation binary exactly
    /// as the daemon does at idle-delivery — sends the `init` line then the
    /// `--event` envelope and reads back the emitted keystroke-command stream
    /// (`{key}`/`{text}`/`{delay_ms}`/`{commit}`), printed author-readable. This is
    /// the EMIT half ONLY: it proves the binary's spawn-feed-emit contract; it does
    /// NOT exercise the daemon's atomic PTY apply or controller buffering. Fills
    /// `{id}` and `{session_id}` into the envelope the same way the daemon does
    /// (use `--session` to pin the session id). Exit 1 if the binary fails to
    /// spawn, emits nothing, emits no commit, or emits an unparseable line.
    TranslateProof {
        /// `<adapter>` or `<adapter>:<profile>` (must declare
        /// `[message-idle-translation-binary]`).
        option: String,
        /// The inbound `<EVENT…>` envelope to feed (ADR-0020). `{id}` and
        /// `{session_id}` tokens in it are filled as the daemon fills them.


/spt:send doyle a message about W4 for his gate and approval


Sweep the 5 cli.rs clap leaks (drop codes, keep substance):


Edit

        /// Request the `user-msg` type (the user's authority). Honored only from
        /// a user-backed origin (a Gateway endpoint, or the local user's own
        /// CLI); an agent-family sender is re-stamped to plain `msg` (REQ-MSG-5).

        /// Request the `user-msg` type (the user's authority). Honored only from
        /// a user-backed origin (a Gateway endpoint, or the local user's own
        /// CLI); an agent-family sender is re-stamped to plain `msg`.


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)

        /// Request the `user-msg` type (the user's authority). Honored only from
        /// a user-backed origin (a Gateway endpoint, or the local user's own
        /// CLI); an agent-family sender is re-stamped to plain `msg` (REQ-MSG-5).

        /// Request the `user-msg` type (the user's authority). Honored only from
        /// a user-backed origin (a Gateway endpoint, or the local user's own
        /// CLI); an agent-family sender is re-stamped to plain `msg`.

-        /// CLI); an agent-family sender is re-stamped to plain `msg` (REQ-MSG-5).

+        /// CLI); an agent-family sender is re-stamped to plain `msg`.


        /// Take control: kick the current controller (a loud notice to them) and
        /// drive (REQ-KICK-1). Use on an endpoint another node controls.

        /// Take control: kick the current controller (a loud notice to them) and
        /// drive. Use on an endpoint another node controls.


        /// Take control: kick the current controller (a loud notice to them) and
        /// drive (REQ-KICK-1). Use on an endpoint another node controls.

        /// Take control: kick the current controller (a loud notice to them) and
        /// drive. Use on an endpoint another node controls.

-        /// drive (REQ-KICK-1). Use on an endpoint another node controls.

+        /// drive. Use on an endpoint another node controls.


    /// Prove an adapter's `[digest]` extractor against a real log sample
    /// (ADR-0019). Runs the declared extractor over `--sample <log>` (or the
    /// declared source) and prints the parsed contract records, the rendered

    /// Prove an adapter's `[digest]` extractor against a real log sample.
    /// Runs the declared extractor over `--sample <log>` (or the
    /// declared source) and prints the parsed contract records, the rendered


    /// Prove an adapter's `[digest]` extractor against a real log sample
    /// (ADR-0019). Runs the declared extractor over `--sample <log>` (or the
    /// declared source) and prints the parsed contract records, the rendered

    /// Prove an adapter's `[digest]` extractor against a real log sample.
    /// Runs the declared extractor over `--sample <log>` (or the
    /// declared source) and prints the parsed contract records, the rendered

/// Dot-separated key path into `[strings]` (e.g. `hook.additionalContext`).

-    /// Prove an adapter's `[digest]` extractor against a real log sample

-    /// (ADR-0019). Runs the declared extractor over `--sample <log>` (or the

+    /// Prove an adapter's `[digest]` extractor against a real log sample.

+    /// Runs the declared extractor over `--sample <log>` (or the

/// digest, and **every dropped line with its reason** — the author-time

/// answer to "`spt endpoint digest` returns nothing" (no silent empty).


    /// Prove an adapter's `[message-idle-translation-binary]` against an inbound
    /// event (ADR-0022). Spawns and feeds the declared translation binary exactly

    /// Prove an adapter's `[message-idle-translation-binary]` against an inbound
    /// event. Spawns and feeds the declared translation binary exactly


    /// Prove an adapter's `[message-idle-translation-binary]` against an inbound
    /// event (ADR-0022). Spawns and feeds the declared translation binary exactly

    /// Prove an adapter's `[message-idle-translation-binary]` against an inbound
    /// event. Spawns and feeds the declared translation binary exactly

-    /// event (ADR-0022). Spawns and feeds the declared translation binary exactly

+    /// event. Spawns and feeds the declared translation binary exactly

/// as the daemon does at idle-delivery — sends the `init` line then the

/// `--event` envelope and reads back the emitted keystroke-command stream

/// (`{key}`/`{text}`/`{delay_ms}`/`{commit}`), printed author-readable. This is


        /// The inbound `<EVENT…>` envelope to feed (ADR-0020). `{id}` and
        /// `{session_id}` tokens in it are filled as the daemon fills them.

        /// The inbound `<EVENT…>` envelope to feed. `{id}` and
        /// `{session_id}` tokens in it are filled as the daemon fills them.


        /// The inbound `<EVENT…>` envelope to feed (ADR-0020). `{id}` and
        /// `{session_id}` tokens in it are filled as the daemon fills them.

        /// The inbound `<EVENT…>` envelope to feed. `{id}` and
        /// `{session_id}` tokens in it are filled as the daemon fills them.

/// `<adapter>` or `<adapter>:<profile>` (must declare

-        /// The inbound `<EVENT…>` envelope to feed (ADR-0020). `{id}` and

+        /// The inbound `<EVENT…>` envelope to feed. `{id}` and


Now the 3 api/mod.rs clap leaks. Read contexts:


37	#[derive(Args)]
38	pub struct ApiArgs {
39	    /// adapter_name — the calling harness adapter. **Optional** (ADR-0021): an
40	    /// explicit `name[:profile]` override for adapter dev/iteration. Omitted,
41	    /// `listen` resolves the owning adapter/profile at bind from the seed's
42	    /// parent pid (host_binaries → active-profile pointer → registered_at_ms).
43	    #[arg(long)]
44	    adapter: Option<String>,

#[derive(Args)]
pub struct ApiArgs {
    /// adapter_name — the calling harness adapter. **Optional** (ADR-0021): an
    /// explicit `name[:profile]` override for adapter dev/iteration. Omitted,
    /// `listen` resolves the owning adapter/profile at bind from the seed's
    /// parent pid (host_binaries → active-profile pointer → registered_at_ms).
    #[arg(long)]
    adapter: Option<String>,


84	        #[arg(long)]
85	        subnet: Option<String>,
86	        /// The endpoint type tag (info.json `state`). Defaults to `live_agent`
87	        /// (the agent host); a non-agent endpoint — e.g. a `gateway` — binds
88	        /// with its own open-type tag (REQ-EP-1/REQ-EP-6). A revive keeps the
89	        /// prior type unless this overrides it.
90	        #[arg(long = "type", default_value = "live_agent")]
91	        endpoint_type: String,
92	        #[command(flatten)]
93	        auth: AuthFlags,

        #[arg(long)]
        subnet: Option<String>,
        /// The endpoint type tag (info.json `state`). Defaults to `live_agent`
        /// (the agent host); a non-agent endpoint — e.g. a `gateway` — binds
        /// with its own open-type tag (REQ-EP-1/REQ-EP-6). A revive keeps the
        /// prior type unless this overrides it.
        #[arg(long = "type", default_value = "live_agent")]
        endpoint_type: String,
        #[command(flatten)]
        auth: AuthFlags,


190	        auth: AuthFlags,
191	    },
192	    /// Push one digest-record (the published contract JSON line, on stdin) for a
193	    /// log-less adapter — appended to the perch's digest store, tailed by the
194	    /// session-digest projection (ADR-0008 amendment, REQ-TERM-4).
195	    DigestEntry {
196	        id: String,
197	        #[command(flatten)]

        auth: AuthFlags,
    },
    /// Push one digest-record (the published contract JSON line, on stdin) for a
    /// log-less adapter — appended to the perch's digest store, tailed by the
    /// session-digest projection (ADR-0008 amendment, REQ-TERM-4).
    DigestEntry {
        id: String,
        #[command(flatten)]


    /// adapter_name — the calling harness adapter. **Optional** (ADR-0021): an
    /// explicit `name[:profile]` override for adapter dev/iteration. Omitted,

    /// adapter_name — the calling harness adapter. **Optional**: an
    /// explicit `name[:profile]` override for adapter dev/iteration. Omitted,


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

    /// adapter_name — the calling harness adapter. **Optional** (ADR-0021): an
    /// explicit `name[:profile]` override for adapter dev/iteration. Omitted,

    /// adapter_name — the calling harness adapter. **Optional**: an
    /// explicit `name[:profile]` override for adapter dev/iteration. Omitted,

/// (spawn roles, capability advertisement).

-    /// adapter_name — the calling harness adapter. **Optional** (ADR-0021): an

+    /// adapter_name — the calling harness adapter. **Optional**: an

/// `listen` resolves the owning adapter/profile at bind from the seed's

/// parent pid (host_binaries → active-profile pointer → registered_at_ms).


        /// The endpoint type tag (info.json `state`). Defaults to `live_agent`
        /// (the agent host); a non-agent endpoint — e.g. a `gateway` — binds
        /// with its own open-type tag (REQ-EP-1/REQ-EP-6). A revive keeps the
        /// prior type unless this overrides it.

        /// The endpoint type tag (info.json `state`). Defaults to `live_agent`
        /// (the agent host); a non-agent endpoint — e.g. a `gateway` — binds
        /// with its own open-type tag. A revive keeps the
        /// prior type unless this overrides it.


        /// The endpoint type tag (info.json `state`). Defaults to `live_agent`
        /// (the agent host); a non-agent endpoint — e.g. a `gateway` — binds
        /// with its own open-type tag (REQ-EP-1/REQ-EP-6). A revive keeps the
        /// prior type unless this overrides it.

        /// The endpoint type tag (info.json `state`). Defaults to `live_agent`
        /// (the agent host); a non-agent endpoint — e.g. a `gateway` — binds
        /// with its own open-type tag. A revive keeps the
        /// prior type unless this overrides it.

-        /// with its own open-type tag (REQ-EP-1/REQ-EP-6). A revive keeps the

+        /// with its own open-type tag. A revive keeps the

#[arg(long = "type", default_value = "live_agent")]


    /// Push one digest-record (the published contract JSON line, on stdin) for a
    /// log-less adapter — appended to the perch's digest store, tailed by the
    /// session-digest projection (ADR-0008 amendment, REQ-TERM-4).

    /// Push one digest-record (the published contract JSON line, on stdin) for a
    /// log-less adapter — appended to the perch's digest store, tailed by the
    /// session-digest projection.


    /// Push one digest-record (the published contract JSON line, on stdin) for a
    /// log-less adapter — appended to the perch's digest store, tailed by the
    /// session-digest projection (ADR-0008 amendment, REQ-TERM-4).

    /// Push one digest-record (the published contract JSON line, on stdin) for a
    /// log-less adapter — appended to the perch's digest store, tailed by the
    /// session-digest projection.

-    /// session-digest projection (ADR-0008 amendment, REQ-TERM-4).


Sweep done (8 edits). Now build base REQ-ADAPTER-VERSION-CMD. Read the AdapterCmd enum + a sibling handler to mirror:


498	/// `spt adapter <action>` subcommands (REQ-INSTALL-4).
499	#[derive(Subcommand)]
500	enum AdapterCmd {
501	    /// Register an adapter from a local path (a dir holding `manifest.toml`,
502	    /// or the manifest file itself) or from GitHub (`--github user/repo`,
503	    /// cloned under `adapters/_github/`). Manifest-first: an invalid manifest
504	    /// registers nothing. Install is the first update — the declared
505	    /// `[update]` avenue is conducted once after recording.
506	    Add {
507	        /// Local manifest source (omit when using --github or --release).
508	        path: Option<String>,
509	        /// GitHub source `user/repo` — shallow-clone the repo and register the
510	        /// clone root. Manifest-first, then install via the declared `[update]`
511	        /// avenue.
512	        #[arg(long)]
513	        github: Option<String>,
514	        /// GitHub release source `user/repo` — fetch the adapter archive asset
515	        /// from the release and register it: ships built binaries, source-free
516	        /// and versioned (the pattern for a monorepo whose adapter is a subdir).
517	        #[arg(long)]
518	        release: Option<String>,
519	        /// Release tag for --release (default: the latest release).
520	        #[arg(long)]
521	        tag: Option<String>,
522	        /// Release asset name for --release (default: adapter.spt — a tar
523	        /// archive whose root holds manifest.toml + strings/ + binaries).
524	        #[arg(long)]
525	        asset: Option<String>,
526	        /// Force the `gh` CLI transport for --release (the private-repo path; gh
527	        /// honors OAuth + GH_TOKEN, so spt custodies no token). Mutually
528	        /// exclusive with --https. Default: auto (gh when installed+authed, else
529	        /// HTTPS).
530	        #[arg(long, conflicts_with = "https")]
531	        gh: bool,
532	        /// Force direct HTTPS transport for --release (public repos). Mutually
533	        /// exclusive with --gh. Default: auto.
534	        #[arg(long)]
535	        https: bool,
536	    },
537	    /// Soft-deregister: hidden from new-creation/discovery; existing and live
538	    /// instances keep running. The manifest's optional `uninstall` template
539	    /// is conducted only with --force until quiesce detection lands.
540	    Remove {
541	        name: String,
542	        /// Conduct the manifest `uninstall` template now, without waiting for
543	        /// quiesce.
544	        #[arg(long)]
545	        force: bool,
546	    },
547	    /// List registered adapters (active and soft-deregistered), each followed
548	    /// by its shipped + local profiles as composite options.
549	    List,
550	    /// Create (or overwrite) a **local** profile — a node-local sparse overlay
551	    /// registered beside the adapter that survives `adapter add` re-registration.
552	    /// The overlay TOML is read from `--from <file>` or piped stdin (empty =
553	    /// a placeholder profile to populate later with `set-string`). Refuses a
554	    /// name shadowing a shipped profile, an invalid name, or an overlay that
555	    /// loosens a consent floor — nothing is written unless every check passes.
556	    CreateProfile {
557	        /// The parent adapter (must be registered).
558	        adapter: String,
559	        /// The local profile name (the `:<profile>` of the composite address).
560	        name: String,
561	        /// Read the overlay TOML from this file instead of stdin.
562	        #[arg(long)]
563	        from: Option<String>,
564	    },
565	    /// Delete a **local** profile. Refuses a shipped profile name (adapter-owned,
566	    /// immutable) and errors if no local file exists.
567	    DeleteProfile {
568	        adapter: String,
569	        name: String,
570	    },
571	    /// Read a `[strings]` dot-path from an adapter option's merged view
572	    /// (`<adapter>[:profile] <key.path>`). Resolves through the profile overlay
573	    /// like every other consumer; prints the value (strings raw, else JSON).
574	    /// Exit 1 if the key is unset. Strings are **data** — never executed.
575	    GetString {
576	        /// `<adapter>` or `<adapter>:<profile>`.
577	        option: String,
578	        /// Dot-separated key path into `[strings]` (e.g. `hook.additionalContext`).
579	        key: String,
580	    },
581	    /// Prove an adapter's `[digest]` extractor against a real log sample.
582	    /// Runs the declared extractor over `--sample <log>` (or the

/// `spt adapter <action>` subcommands (REQ-INSTALL-4).
#[derive(Subcommand)]
enum AdapterCmd {
    /// Register an adapter from a local path (a dir holding `manifest.toml`,
    /// or the manifest file itself) or from GitHub (`--github user/repo`,
    /// cloned under `adapters/_github/`). Manifest-first: an invalid manifest
    /// registers nothing. Install is the first update — the declared
    /// `[update]` avenue is conducted once after recording.
    Add {
        /// Local manifest source (omit when using --github or --release).
        path: Option<String>,
        /// GitHub source `user/repo` — shallow-clone the repo and register the
        /// clone root. Manifest-first, then install via the declared `[update]`
        /// avenue.
        #[arg(long)]
        github: Option<String>,
        /// GitHub release source `user/repo` — fetch the adapter archive asset
        /// from the release and register it: ships built binaries, source-free
        /// and versioned (the pattern for a monorepo whose adapter is a subdir).
        #[arg(long)]
        release: Option<String>,
        /// Release tag for --release (default: the latest release).
        #[arg(long)]
        tag: Option<String>,
        /// Release asset name for --release (default: adapter.spt — a tar
        /// archive whose root holds manifest.toml + strings/ + binaries).
        #[arg(long)]
        asset: Option<String>,
        /// Force the `gh` CLI transport for --release (the private-repo path; gh
        /// honors OAuth + GH_TOKEN, so spt custodies no token). Mutually
        /// exclusive with --https. Default: auto (gh when installed+authed, else
        /// HTTPS).
        #[arg(long, conflicts_with = "https")]
        gh: bool,
        /// Force direct HTTPS transport for --release (public repos). Mutually
        /// exclusive with --gh. Default: auto.
        #[arg(long)]
        https: bool,
    },
    /// Soft-deregister: hidden from new-creation/discovery; existing and live
    /// instances keep running. The manifest's optional `uninstall` template
    /// is conducted only with --force until quiesce detection lands.
    Remove {
        name: String,
        /// Conduct the manifest `uninstall` template now, without waiting for
        /// quiesce.
        #[arg(long)]
        force: bool,
    },
    /// List registered adapters (active and soft-deregistered), each followed
    /// by its shipped + local profiles as composite options.
    List,
    /// Create (or overwrite) a **local** profile — a node-local sparse overlay
    /// registered beside the adapter that survives `adapter add` re-registration.
    /// The overlay TOML is read from `--from <file>` or piped stdin (empty =
    /// a placeholder profile to populate later with `set-string`). Refuses a
    /// name shadowing a shipped profile, an invalid name, or an overlay that
    /// loosens a consent floor — nothing is written unless every check passes.
    CreateProfile {
        /// The parent adapter (must be registered).
        adapter: String,
        /// The local profile name (the `:<profile>` of the composite address).
        name: String,
        /// Read the overlay TOML from this file instead of stdin.
        #[arg(long)]
        from: Option<String>,
    },
    /// Delete a **local** profile. Refuses a shipped profile name (adapter-owned,
    /// immutable) and errors if no local file exists.
    DeleteProfile {
        adapter: String,
        name: String,
    },
    /// Read a `[strings]` dot-path from an adapter option's merged view
    /// (`<adapter>[:profile] <key.path>`). Resolves through the profile overlay
    /// like every other consumer; prints the value (strings raw, else JSON).
    /// Exit 1 if the key is unset. Strings are **data** — never executed.
    GetString {
        /// `<adapter>` or `<adapter>:<profile>`.
        option: String,
        /// Dot-separated key path into `[strings]` (e.g. `hook.additionalContext`).
        key: String,
    },
    /// Prove an adapter's `[digest]` extractor against a real log sample.
    /// Runs the declared extractor over `--sample <log>` (or the


Add the `Version` variant after `List` (clean help):


    /// List registered adapters (active and soft-deregistered), each followed
    /// by its shipped + local profiles as composite options.
    List,

    /// List registered adapters (active and soft-deregistered), each followed
    /// by its shipped + local profiles as composite options.
    List,
    /// Print a registered adapter's declared version — the `[adapter].version`
    /// from its manifest. Resolves the option's merged view like the other
    /// adapter commands; exit 1 if the adapter is not registered.
    Version {
        /// `<adapter>` or `<adapter>:<profile>`.
        option: String,
    },


    /// List registered adapters (active and soft-deregistered), each followed
    /// by its shipped + local profiles as composite options.
    List,
    /// Print a registered adapter's declared version — the `[adapter].version`
    /// from its manifest. Resolves the option's merged view like the other
    /// adapter commands; exit 1 if the adapter is not registered.
    Version {
        /// `<adapter>` or `<adapter>:<profile>`.
        option: String,
    },

+    /// Print a registered adapter's declared version — the `[adapter].version`

+    /// from its manifest. Resolves the option's merged view like the other

+    /// adapter commands; exit 1 if the adapter is not registered.

+        /// `<adapter>` or `<adapter>:<profile>`.

/// Create (or overwrite) a **local** profile — a node-local sparse overlay

/// registered beside the adapter that survives `adapter add` re-registration.

/// The overlay TOML is read from `--from <file>` or piped stdin (empty =


Now find the dispatch arms (GetString sibling) to add the Version arm + mirror error handling:


crates\spt\src\cli.rs:5929:        AdapterCmd::List => {
crates\spt\src\cli.rs-5930-            let records = registry::all_records(&adapters);
C:\Users\decid\Documents\projects\spt-core\crates\spt\src\cli.rs-5931-            if records.is_empty() {
C:\Users\decid\Documents\projects\spt-core\crates\spt\src\cli.rs-5932-                println!("no adapters registered");
C:\Users\decid\Documents\projects\spt-core\crates\spt\src\cli.rs-5933-            }
C:\Users\decid\Documents\projects\spt-core\crates\spt\src\cli.rs-5934-            for r in records {
C:\Users\decid\Documents\projects\spt-core\crates\spt\src\cli.rs-5935-                let state = if r.active { "active" } else { "deregistered" };
C:\Users\decid\Documents\projects\spt-core\crates\spt\src\cli.rs-5936-                println!(
crates\spt\src\cli.rs-5937-                    "{}: {:?} {:?} {state} (from {})",
C:\Users\decid\Documents\projects\spt-core\crates\spt\src\cli.rs-5938-                    r.name, r.kind, r.mode, r.source_dir
C:\Users\decid\Documents\projects\spt-core\crates\spt\src\cli.rs-5939-                );
C:\Users\decid\Documents\projects\spt-core\crates\spt\src\cli.rs-5940-                // Profiles render adjacent as composite options — shipped (from
C:\Users\decid\Documents\projects\spt-core\crates\spt\src\cli.rs-5941-                // the parent manifest) then local (overlay files beside it).
C:\Users\decid\Documents\projects\spt-core\crates\spt\src\cli.rs-5942-                // [impl->REQ-MANIFEST-2]
crates\spt\src\cli.rs-5943-                if let Ok(m) = registry::load_manifest(&adapters, &r) {
crates\spt\src\cli.rs-5944-                    let mut shipped: Vec<&String> = m.profiles.keys().collect();
C:\Users\decid\Documents\projects\spt-core\crates\spt\src\cli.rs-5945-                    shipped.sort();
C:\Users\decid\Documents\projects\spt-core\crates\spt\src\cli.rs-5946-                    for p in shipped {
crates\spt\src\cli.rs-5947-                        println!("  {}:{p} (shipped)", r.name);
--
crates\spt\src\cli.rs:6004:        AdapterCmd::GetString { option, key } => match registry::get_string(&adapters, &option, &key)
C:\Users\decid\Documents\projects\spt-core\crates\spt\src\cli.rs-6005-        {
C:\Users\decid\Documents\projects\spt-core\crates\spt\src\cli.rs-6006-            Ok(Some(value)) => {
C:\Users\decid\Documents\projects\spt-core\crates\spt\src\cli.rs-6007-                // Strings print raw; structured leaves print as JSON (machine-
C:\Users\decid\Documents\projects\spt-core\crates\spt\src\cli.rs-6008-                // readable for a hook). spt-core never executes the value.
C:\Users\decid\Documents\projects\spt-core\crates\spt\src\cli.rs-6009-                if let Some(s) = value.as_str() {
C:\Users\decid\Documents\projects\spt-core\crates\spt\src\cli.rs-6010-                    println!("{s}");
C:\Users\decid\Documents\projects\spt-core\crates\spt\src\cli.rs-6011-                } else {
crates\spt\src\cli.rs-6012-                    println!("{}", serde_json::to_string(&value).unwrap_or_default());
C:\Users\decid\Documents\projects\spt-core\crates\spt\src\cli.rs-6013-                }
C:\Users\decid\Documents\projects\spt-core\crates\spt\src\cli.rs-6014-                0
C:\Users\decid\Documents\projects\spt-core\crates\spt\src\cli.rs-6015-            }
C:\Users\decid\Documents\projects\spt-core\crates\spt\src\cli.rs-6016-            Ok(None) => {
crates\spt\src\cli.rs-6017-                eprintln!("ADAPTER_STRING_UNSET:{option}: no value at '{key}'");
C:\Users\decid\Documents\projects\spt-core\crates\spt\src\cli.rs-6018-                1
C:\Users\decid\Documents\projects\spt-core\crates\spt\src\cli.rs-6019-            }
C:\Users\decid\Documents\projects\spt-core\crates\spt\src\cli.rs-6020-            Err(e) => {
crates\spt\src\cli.rs-6021-                eprintln!("ADAPTER_STRING_FAIL:{option}: {e}");
C:\Users\decid\Documents\projects\spt-core\crates\spt\src\cli.rs-6022-                1
--
crates\spt\src\cli.rs:9889:            cmd_adapter(AdapterCmd::GetString { option: opt.into(), key: key.into() })
C:\Users\decid\Documents\projects\spt-core\crates\spt\src\cli.rs-9890-        };
crates\spt\src\cli.rs-9891-        let set = |opt: &str, key: &str, val: &str| {
crates\spt\src\cli.rs-9892-            cmd_adapter(AdapterCmd::SetString {
crates\spt\src\cli.rs-9893-                option: opt.into(),
crates\spt\src\cli.rs-9894-                key: key.into(),
crates\spt\src\cli.rs-9895-                value: val.into(),
C:\Users\decid\Documents\projects\spt-core\crates\spt\src\cli.rs-9896-            })
C:\Users\decid\Documents\projects\spt-core\crates\spt\src\cli.rs-9897-        };
C:\Users\decid\Documents\projects\spt-core\crates\spt\src\cli.rs-9898-
C:\Users\decid\Documents\projects\spt-core\crates\spt\src\cli.rs-9899-        // Read the base string (found = 0); a missing key = 1.
C:\Users\decid\Documents\projects\spt-core\crates\spt\src\cli.rs-9900-        assert_eq!(get("claude-spt", "base"), 0);
C:\Users\decid\Documents\projects\spt-core\crates\spt\src\cli.rs-9901-        assert_eq!(get("claude-spt", "nope"), 1, "unset key exits 1");
C:\Users\decid\Documents\projects\spt-core\crates\spt\src\cli.rs-9902-
crates\spt\src\cli.rs-9903-        \ set-string needs a local target: a bare option is a usage error.
C:\Users\decid\Documents\projects\spt-core\crates\spt\src\cli.rs-9904-        assert_eq!(set("claude-spt", "base", "x"), 2, "bare option has no local target");
C:\Users\decid\Documents\projects\spt-core\crates\spt\src\cli.rs-9905-
C:\Users\decid\Documents\projects\spt-core\crates\spt\src\cli.rs-9906-        // Create a local profile, set a string into it, read it back composite.
crates\spt\src\cli.rs-9907-        cmd_adapter(AdapterCmd::CreateProfile {


Add the Version dispatch arm before `GetString`:


AdapterCmd::GetString { option, key } => match registry::get_string(&adapters, &option, &key)

        // [impl->REQ-ADAPTER-VERSION-CMD]
        AdapterCmd::Version { option } => cmd_adapter_version(&adapters, &option),
        AdapterCmd::GetString { option, key } => match registry::get_string(&adapters, &option, &key)


        // [impl->REQ-ADAPTER-VERSION-CMD]
        AdapterCmd::Version { option } => cmd_adapter_version(&adapters, &option),
        AdapterCmd::GetString { option, key } => match registry::get_string(&adapters, &option, &key)

+        // [impl->REQ-ADAPTER-VERSION-CMD]

+        AdapterCmd::Version { option } => cmd_adapter_version(&adapters, &option),


Now define `cmd_adapter_version` (place above `cmd_adapter_digest_proof`, near `resolve_proof_target`):


/// Resolve the `(install_dir, manifest)` a `*-proof` command runs against. With

/// `spt adapter version <adapter>` — print the adapter's declared
/// `[adapter].version` (the existing mandatory manifest field — no second
/// version source). Resolves the option's merged view like the sibling adapter
/// commands; exit 1 when the adapter is not registered.
// [impl->REQ-ADAPTER-VERSION-CMD]
fn cmd_adapter_version(adapters: &std::path::Path, option: &str) -> i32 {
    match spt_runtime::registry::resolve_option(adapters, option) {
        Ok((_, m)) => {
            println!("{}", m.adapter.version);
            0
        }
        Err(e) => {
            eprintln!("ADAPTER_VERSION_FAIL:{option}: {e}");
            1
        }
    }
}

/// Resolve the `(install_dir, manifest)` a `*-proof` command runs against. With


/// `spt adapter version <adapter>` — print the adapter's declared
/// `[adapter].version` (the existing mandatory manifest field — no second
/// version source). Resolves the option's merged view like the sibling adapter
/// commands; exit 1 when the adapter is not registered.
// [impl->REQ-ADAPTER-VERSION-CMD]
fn cmd_adapter_version(adapters: &std::path::Path, option: &str) -> i32 {
    match spt_runtime::registry::resolve_option(adapters, option) {
        Ok((_, m)) => {
            println!("{}", m.adapter.version);
            0
        }
        Err(e) => {
            eprintln!("ADAPTER_VERSION_FAIL:{option}: {e}");
            1
        }
    }
}

/// Resolve the `(install_dir, manifest)` a `*-proof` command runs against. With

spt_daemon::verify_detached(&bytes, sig_hex.trim(), &key).map_err(|e| e.to_string())

+/// `spt adapter version <adapter>` — print the adapter's declared

+/// `[adapter].version` (the existing mandatory manifest field — no second

+/// version source). Resolves the option's merged view like the sibling adapter

+/// commands; exit 1 when the adapter is not registered.

+fn cmd_adapter_version(adapters: &std::path::Path, option: &str) -> i32 {

+    match spt_runtime::registry::resolve_option(adapters, option) {

+            println!("{}", m.adapter.version);

+            eprintln!("ADAPTER_VERSION_FAIL:{option}: {e}");

/// no `--dir`/`--manifest` it resolves the REGISTERED option (the normal path,

/// `source_dir` = the extracted install). With either override it reads a DEV


Add the version unit test (parse + behavior + field-wiring assertion) after the resolve_proof_target test:


        // Neither override → the registry path; an unregistered option errors.
        assert!(
            resolve_proof_target(&adapters, "ghost-unregistered", None, None).is_err(),
            "no override + unknown adapter falls through to the registry and errors",
        );
    }

        // Neither override → the registry path; an unregistered option errors.
        assert!(
            resolve_proof_target(&adapters, "ghost-unregistered", None, None).is_err(),
            "no override + unknown adapter falls through to the registry and errors",
        );
    }

    // ── REQ-ADAPTER-VERSION-CMD (v0.13.2 W6): `spt adapter version <name>` prints
    // the EXISTING mandatory `[adapter].version` manifest field (not a second
    // version source). Parse + behavior + the field wiring.

    // [unit->REQ-ADAPTER-VERSION-CMD] the subcommand parses to Version{option}.
    #[test]
    fn adapter_version_parses() {
        match parse(&["spt", "adapter", "version", "claude-spt"]).unwrap().cmd.unwrap() {
            Cmd::Adapter { action: AdapterCmd::Version { option } } => {
                assert_eq!(option, "claude-spt");
            }
            _ => panic!("expected Adapter Version"),
        }
    }

    // [unit->REQ-ADAPTER-VERSION-CMD] a registered adapter → its declared
    // `[adapter].version` and exit 0; an unregistered option → exit 1. The version
    // surfaced is the resolved manifest's `[adapter].version` (the mandatory field),
    // proving the wiring reads the right source.
    #[test]
    fn adapter_version_surfaces_declared_adapter_version() {
        let _h = crate::testutil::isolated_home();
        let adapters = spt_store::perch::adapters_dir();
        let manifest = "[adapter]\nname = \"cc\"\nkind = \"harness\"\nversion = \"3.1.4\"\n\
                        min_spt_core_version = \"0\"\n";
        let d = spt_store::perch::spt_home().join("srcs").join("cc");
        std::fs::create_dir_all(&d).unwrap();
        std::fs::write(d.join("manifest.toml"), manifest).unwrap();
        spt_runtime::registry::register(&adapters, &d, 1).unwrap();

        // The resolved manifest exposes exactly the declared [adapter].version —
        // the value `cmd_adapter_version` prints.
        let (_, m) = spt_runtime::registry::resolve_option(&adapters, "cc").unwrap();
        assert_eq!(m.adapter.version, "3.1.4", "the [adapter].version field is the source");

        assert_eq!(cmd_adapter_version(&adapters, "cc"), 0, "registered adapter → exit 0");
        assert_eq!(cmd_adapter_version(&adapters, "ghost"), 1, "unregistered adapter → exit 1");
    }


        // Neither override → the registry path; an unregistered option errors.
        assert!(
            resolve_proof_target(&adapters, "ghost-unregistered", None, None).is_err(),
            "no override + unknown adapter falls through to the registry and errors",
        );
    }

    // ── REQ-ADAPTER-VERSION-CMD (v0.13.2 W6): `spt adapter version <name>` prints
    // the EXISTING mandatory `[adapter].version` manifest field (not a second
    // version source). Parse + behavior + the field wiring.

    // [unit->REQ-ADAPTER-VERSION-CMD] the subcommand parses to Version{option}.
    #[test]
    fn adapter_version_parses() {
        match parse(&["spt", "adapter", "version", "claude-spt"]).unwrap().cmd.unwrap() {
            Cmd::Adapter { action: AdapterCmd::Version { option } } => {
                assert_eq!(option, "claude-spt");
            }
            _ => panic!("expected Adapter Version"),
        }
    }

    // [unit->REQ-ADAPTER-VERSION-CMD] a registered adapter → its declared
    // `[adapter].version` and exit 0; an unregistered option → exit 1. The version
    // surfaced is the resolved manifest's `[adapter].version` (the mandatory field),
    // proving the wiring reads the right source.
    #[test]
    fn adapter_version_surfaces_declared_adapter_version() {
        let _h = crate::testutil::isolated_home();
        let adapters = spt_store::perch::adapters_dir();
        let manifest = "[adapter]\nname = \"cc\"\nkind = \"harness\"\nversion = \"3.1.4\"\n\
                        min_spt_core_version = \"0\"\n";
        let d = spt_store::perch::spt_home().join("srcs").join("cc");
        std::fs::create_dir_all(&d).unwrap();
        std::fs::write(d.join("manifest.toml"), manifest).unwrap();
        spt_runtime::registry::register(&adapters, &d, 1).unwrap();

        // The resolved manifest exposes exactly the declared [adapter].version —
        // the value `cmd_adapter_version` prints.
        let (_, m) = spt_runtime::registry::resolve_option(&adapters, "cc").unwrap();
        assert_eq!(m.adapter.version, "3.1.4", "the [adapter].version field is the source");

        assert_eq!(cmd_adapter_version(&adapters, "cc"), 0, "registered adapter → exit 0");
        assert_eq!(cmd_adapter_version(&adapters, "ghost"), 1, "unregistered adapter → exit 1");
    }

+    // ── REQ-ADAPTER-VERSION-CMD (v0.13.2 W6): `spt adapter version <name>` prints

+    // the EXISTING mandatory `[adapter].version` manifest field (not a second

+    // version source). Parse + behavior + the field wiring.

+    // [unit->REQ-ADAPTER-VERSION-CMD] the subcommand parses to Version{option}.

+        match parse(&["spt", "adapter", "version", "claude-spt"]).unwrap().cmd.unwrap() {

+            Cmd::Adapter { action: AdapterCmd::Version { option } } => {

+                assert_eq!(option, "claude-spt");

+            _ => panic!("expected Adapter Version"),

+    // [unit->REQ-ADAPTER-VERSION-CMD] a registered adapter → its declared

+    // `[adapter].version` and exit 0; an unregistered option → exit 1. The version

+    // surfaced is the resolved manifest's `[adapter].version` (the mandatory field),

+    // proving the wiring reads the right source.

+    fn adapter_version_surfaces_declared_adapter_version() {

+        let _h = crate::testutil::isolated_home();

+        let adapters = spt_store::perch::adapters_dir();

+        let manifest = "[adapter]\nname = \"cc\"\nkind = \"harness\"\nversion = \"3.1.4\"\n\

+                        min_spt_core_version = \"0\"\n";

+        let d = spt_store::perch::spt_home().join("srcs").join("cc");

+        std::fs::create_dir_all(&d).unwrap();

+        std::fs::write(d.join("manifest.toml"), manifest).unwrap();

+        spt_runtime::registry::register(&adapters, &d, 1).unwrap();

+        // The resolved manifest exposes exactly the declared [adapter].version —

+        // the value `cmd_adapter_version` prints.

+        let (_, m) = spt_runtime::registry::resolve_option(&adapters, "cc").unwrap();

+        assert_eq!(m.adapter.version, "3.1.4", "the [adapter].version field is the source");

+        assert_eq!(cmd_adapter_version(&adapters, "cc"), 0, "registered adapter → exit 0");

+        assert_eq!(cmd_adapter_version(&adapters, "ghost"), 1, "unregistered adapter → exit 1");

// [unit->REQ-ADAPTER-GH-TRANSPORT] The version command is the exact `gh api`

// form that prints the latest tag, with the single `{repo}` substitution key.


Mint both REQs in traceable-reqs.toml after REQ-ADAPTER-PROOF-DIR-OVERRIDE:


1357	[[requirements]]
1358	id = "REQ-ADAPTER-PROOF-DIR-OVERRIDE"
1359	title = "The author-time proof commands (`spt adapter digest-proof`, `spt adapter translate-proof`) gain a `--dir <path>` / `--manifest <file>` override so an author proofs a DEV binary against an on-disk manifest+install dir WITHOUT staging a full extracted GhReleaseManaged install (mirrors digest-proof's `--sample` pointing straight at a file). Fixes perri F-011: a bare-file-added gh_release adapter currently can't be resolved by the *-proof commands ('manifest is not present yet at <dir>'); un-stales the bare-file digest-proof int. (perri F-011, v0.13.x DX)"
1360	required_stages = ["doc", "impl", "unit", "int"]  # v0.13.2 W5. doc = harness-contract patterns + MANIFEST.md proof note. impl = resolve_proof_target + the two --dir/--manifest flags. unit = resolve_proof_target resolution contract. int = translate_proof.rs override proofs an UNregistered on-disk install (real binary).
1361

[[requirements]]
id = "REQ-ADAPTER-PROOF-DIR-OVERRIDE"
title = "The author-time proof commands (`spt adapter digest-proof`, `spt adapter translate-proof`) gain a `--dir <path>` / `--manifest <file>` override so an author proofs a DEV binary against an on-disk manifest+install dir WITHOUT staging a full extracted GhReleaseManaged install (mirrors digest-proof's `--sample` pointing straight at a file). Fixes perri F-011: a bare-file-added gh_release adapter currently can't be resolved by the *-proof commands ('manifest is not present yet at <dir>'); un-stales the bare-file digest-proof int. (perri F-011, v0.13.x DX)"
required_stages = ["doc", "impl", "unit", "int"]  # v0.13.2 W5. doc = harness-contract patterns + MANIFEST.md proof note. impl = resolve_proof_target + the two --dir/--manifest flags. unit = resolve_proof_target resolution contract. int = translate_proof.rs override proofs an UNregistered on-disk install (real binary).


required_stages = ["doc", "impl", "unit", "int"]  # v0.13.2 W5. doc = harness-contract patterns + MANIFEST.md proof note. impl = resolve_proof_target + the two --dir/--manifest flags. unit = resolve_proof_target resolution contract. int = translate_proof.rs override proofs an UNregistered on-disk install (real binary).

required_stages = ["doc", "impl", "unit", "int"]  # v0.13.2 W5. doc = harness-contract patterns + MANIFEST.md proof note. impl = resolve_proof_target + the two --dir/--manifest flags. unit = resolve_proof_target resolution contract. int = translate_proof.rs override proofs an UNregistered on-disk install (real binary).

[[requirements]]
id = "REQ-ADAPTER-VERSION-CMD"
title = "`spt adapter version <name>` prints a registered adapter's declared version — the EXISTING mandatory `[adapter].version` manifest field (manifest.rs already requires it; NOT a `[strings].version`, NOT `get-string`, no second version source). A new `AdapterCmd::Version{option}` resolves the option's merged view via `registry::resolve_option` like the sibling adapter subcommands and prints `manifest.adapter.version`; an unresolvable option errors (exit 1) the same way. (v0.13.2 W6)"
required_stages = ["doc", "impl", "unit"]  # v0.13.2 W6 base. doc = harness-contract adapter-cmd note. impl = the Version arm + cmd_adapter_version resolve+print. unit = parse + behavior (registered prints [adapter].version exit 0; unregistered exit 1). No int (read-only CLI print, unit-covered).

[[requirements]]
id = "REQ-DOCS-NO-INTERNAL-CODES"
title = "Public CLI --help (the clap `///` doc-comments) and the generated `docs-site/src/cli/reference.md` MUST NOT contain internal tracker/decision codes — `REQ-*`, `F-###`, `M#-W#`, `ADR-####`. They are meaningless to an end user reading --help and ship to GH-Pages. A CI-gated scan (the `xtask check` docs gate) fails on any such token in the GENERATED reference.md (which by construction contains only clap help, so rustdoc `///` on fns/structs is OUT of scope and keeps its REQ/ADR cross-refs). Substance is kept; codes are rewritten to plain language. (v0.13.2 W6)"
required_stages = ["doc", "impl", "unit"]  # v0.13.2 W6. doc = DOCS-STRATEGY public-help no-internal-codes rule. impl = (a) the forbidden-token scan in xtask check over reference.md; (b) the sweep of all existing clap-/// leak sites. unit = xtask test: a planted forbidden token FAILS the scan, a clean reference PASSES (non-vacuous).


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", "impl", "unit", "int"]  # v0.13.2 W5. doc = harness-contract patterns + MANIFEST.md proof note. impl = resolve_proof_target + the two --dir/--manifest flags. unit = resolve_proof_target resolution contract. int = translate_proof.rs override proofs an UNregistered on-disk install (real binary).

[[requirements]]
id = "REQ-ADAPTER-VERSION-CMD"
title = "`spt adapter version <name>` prints a registered adapter's declared version — the EXISTING mandatory `[adapter].version` manifest field (manifest.rs already requires it; NOT a `[strings].version`, NOT `get-string`, no second version source). A new `AdapterCmd::Version{option}` resolves the option's merged view via `registry::resolve_option` like the sibling adapter subcommands and prints `manifest.adapter.version`; an unresolvable option errors (exit 1) the same way. (v0.13.2 W6)"
required_stages = ["doc", "impl", "unit"]  # v0.13.2 W6 base. doc = harness-contract adapter-cmd note. impl = the Version arm + cmd_adapter_version resolve+print. unit = parse + behavior (registered prints [adapter].version exit 0; unregistered exit 1). No int (read-only CLI print, unit-covered).

[[requirements]]
id = "REQ-DOCS-NO-INTERNAL-CODES"
title = "Public CLI --help (the clap `///` doc-comments) and the generated `docs-site/src/cli/reference.md` MUST NOT contain internal tracker/decision codes — `REQ-*`, `F-###`, `M#-W#`, `ADR-####`. They are meaningless to an end user reading --help and ship to GH-Pages. A CI-gated scan (the `xtask check` docs gate) fails on any such token in the GENERATED reference.md (which by construction contains only clap help, so rustdoc `///` on fns/structs is OUT of scope and keeps its REQ/ADR cross-refs). Substance is kept; codes are rewritten to plain language. (v0.13.2 W6)"
required_stages = ["doc", "impl", "unit"]  # v0.13.2 W6. doc = DOCS-STRATEGY public-help no-internal-codes rule. impl = (a) the forbidden-token scan in xtask check over reference.md; (b) the sweep of all existing clap-/// leak sites. unit = xtask test: a planted forbidden token FAILS the scan, a clean reference PASSES (non-vacuous).

title = "The author-time proof commands (`spt adapter digest-proof`, `spt adapter translate-proof`) gain a `--dir <path>` / `--manifest <file>` override so an author proofs a DEV binary against an on-disk manifest+install dir WITHOUT staging a full extracted GhReleaseManaged install (mirrors digest-proof's `--sample` pointing straight at a file). Fixes perri F-011: a bare-file-added gh_release adapter currently can't be resolved by the *-proof commands ('manifest is not present yet at <dir>'); un-stales the bare-file digest-proof int. (perri F-011, v0.13.x DX)"

+title = "`spt adapter version <name>` prints a registered adapter's declared version — the EXISTING mandatory `[adapter].version` manifest field (manifest.rs already requires it; NOT a `[strings].version`, NOT `get-string`, no second version source). A new `AdapterCmd::Version{option}` resolves the option's merged view via `registry::resolve_option` like the sibling adapter subcommands and prints `manifest.adapter.version`; an unresolvable option errors (exit 1) the same way. (v0.13.2 W6)"

+required_stages = ["doc", "impl", "unit"]  # v0.13.2 W6 base. doc = harness-contract adapter-cmd note. impl = the Version arm + cmd_adapter_version resolve+print. unit = parse + behavior (registered prints [adapter].version exit 0; unregistered exit 1). No int (read-only CLI print, unit-covered).

+title = "Public CLI --help (the clap `///` doc-comments) and the generated `docs-site/src/cli/reference.md` MUST NOT contain internal tracker/decision codes — `REQ-*`, `F-###`, `M#-W#`, `ADR-####`. They are meaningless to an end user reading --help and ship to GH-Pages. A CI-gated scan (the `xtask check` docs gate) fails on any such token in the GENERATED reference.md (which by construction contains only clap help, so rustdoc `///` on fns/structs is OUT of scope and keeps its REQ/ADR cross-refs). Substance is kept; codes are rewritten to plain language. (v0.13.2 W6)"

+required_stages = ["doc", "impl", "unit"]  # v0.13.2 W6. doc = DOCS-STRATEGY public-help no-internal-codes rule. impl = (a) the forbidden-token scan in xtask check over reference.md; (b) the sweep of all existing clap-/// leak sites. unit = xtask test: a planted forbidden token FAILS the scan, a clean reference PASSES (non-vacuous).


Now the xtask forbidden-token scan. Read the xtask `check` + the reference.md drift logic:


1	//! spt-core docs/site generation (M6-D5) — the anti-drift plumbing behind
2	//! DOCS-STRATEGY's "generation is part of the build" commitment (REQ-DOCS-5):
3	//!
4	//! - `gen` — regenerate the checked-in generated docs (CLI reference).
5	//! - `check` — drift gate: regenerate + diff the CLI reference, and
6	//!   validate every page link in the curated llms.txt.
7	//! - `site <outdir>` — assemble the full publish tree for the spt-releases
8	//!   Pages branch: mdBook HTML + raw `.md` alongside it + llms.txt /
9	//!   llms-full.txt / manifest.schema.json / install scripts at site root +
10	//!   rustdoc for the SDK crates.
11	//!
12	//! No deps; shells out to the workspace binary, mdbook, and cargo doc.
13	//
14	// [impl->REQ-DOCS-1]
15	// [impl->REQ-DOCS-4]
16
17	use std::path::{Path, PathBuf};
18	use std::process::Command;
19
20	fn repo_root() -> PathBuf {
21	    // crates/xtask -> crates -> repo root
22	    Path::new(env!("CARGO_MANIFEST_DIR"))
23	        .ancestors()
24	        .nth(2)
25	        .expect("repo root")
26	        .to_path_buf()
27	}
28
29	fn main() {
30	    let args: Vec<String> = std::env::args().skip(1).collect();
31	    let cmd = args.first().map(String::as_str).unwrap_or("");
32	    match cmd {
33	        "gen" => gen(false),
34	        "check" => {
35	            gen(true);
36	            check_llms_links();
37	            eprintln!("xtask check: OK");
38	        }
39	        "site" => {
40	            let out = args.get(1).expect("usage: xtask site <outdir>");
41	            site(Path::new(out));
42	        }
43	        "release-keygen" => release_keygen(&args[1..]),
44	        "release-sign" => release_sign(&args[1..]),
45	        "release-publish" => release_publish(&args[1..]),
46	        "release-verify" => release_verify(&args[1..]),
47	        "debug-keygen" => debug_keygen(&args[1..]),
48	        "debug-pin" => debug_pin(&args[1..]),
49	        "debug-rollout" => debug_rollout(&args[1..]),
50	        "debug-converge" => debug_converge(&args[1..]),
51	        _ => {
52	            eprintln!(
53	                "usage: xtask <gen|check|site <outdir>|release-keygen <key-id>|release-sign …|release-publish …|debug-keygen <key-id>|debug-pin …|debug-rollout …|debug-converge --version <u64> …>"
54	            );
55	            std::process::exit(2);
56	        }
57	    }
58	}
59
60	// ---------------------------------------------------------------- CLI ref
61
62	/// Run the workspace `spt` binary with `args` + `--help`, normalized to the
63	/// `spt` name (Windows prints `spt.exe`, which would make the generated
64	/// reference platform-dependent).
65	fn help_for(bin: &Path, args: &[&str]) -> String {
66	    let mut cmd = Command::new(bin);
67	    cmd.args(args).arg("--help");
68	    let out = cmd.output().expect("run spt --help");
69	    let text = String::from_utf8_lossy(&out.stdout);
70	    text.replace("spt.exe", "spt").replace("\r\n", "\n")
71	}
72
73	/// Parse the command-list block(s) of a clap help text into subcommand
74	/// names. Nested helps carry clap's flat `Commands:` heading; the root help
75	/// renders two grouped blocks (`User commands:` / `Agent commands:`).
76	fn subcommands(help: &str) -> Vec<String> {
77	    let mut cmds = Vec::new();
78	    let mut in_block = false;
79	    for line in help.lines() {
80	        let heading = line.trim_end();
81	        if heading == "Commands:" || heading == "User commands:" || heading == "Agent commands:" {
82	            in_block = true;
83	            continue;
84	        }
85	        if in_block {
86	            // A blank line ends the current block (another may follow);
87	            // `Options:` ends the command listing for good.
88	            if line.trim().is_empty() {
89	                in_block = false;
90	                continue;
91	            }
92	            if line.starts_with("Options:") {
93	                break;
94	            }
95	            // Entries are `  <name>  <doc…>`; continuation lines are deeper-indented.
96	            if let Some(rest) = line.strip_prefix("  ") {
97	                if !rest.starts_with(' ') {
98	                    if let Some(name) = rest.split_whitespace().next() {
99	                        if name != "help" {
100	                            cmds.push(name.to_string());
101	                        }
102	                    }
103	                }
104	            }
105	        }
106	    }
107	    cmds
108	}
109
110	/// The generated CLI reference: every command + nested subcommand's help,
111	/// verbatim. Help text is a first-class doc surface (DOCS-STRATEGY), so the
112	/// reference IS the binary's own words — it cannot drift (REQ-DOCS-5).
113	// [impl->REQ-DOCS-5]
114	fn cli_reference(bin: &Path) -> String {
115	    let mut out = String::from(
116	        "# CLI reference\n\n\
117	         > **Generated** from the `spt` binary's own `--help` output \
118	         (`cargo run -p xtask -- gen`) and drift-gated in CI — this page \
119	         cannot disagree with the binary. Do not edit by hand.\n\n",
120	    );
121	    let root_help = help_for(bin, &[]);
122	    out.push_str("## spt\n\n```text\n");
123	    out.push_str(&root_help);
124	    out.push_str("```\n");
125	    for cmd in subcommands(&root_help) {
126	        // `spt api` requires --adapter even for subcommand help; pass a
127	        // stand-in so clap renders the help instead of erroring.
128	        let help = if cmd == "api" {
129	            help_for(bin, &["api", "--adapter", "x"])
130	        } else {
131	            help_for(bin, &[&cmd])
132	        };
133	        out.push_str(&format!("\n## spt {cmd}\n\n```text\n{help}```\n"));
134	        for sub in subcommands(&help) {
135	            let sub_help = if cmd == "api" {
136	                help_for(bin, &["api", "--adapter", "x", &sub])
137	            } else {
138	                help_for(bin, &[&cmd, &sub])
139	            };
140	            out.push_str(&format!(
141	                "\n### spt {cmd} {sub}\n\n```text\n{sub_help}```\n"
142	            ));
143	        }
144	    }
145	    out
146	}
147
148	/// Locate (building if needed) the workspace `spt` binary.
149	fn spt_bin(root: &Path) -> PathBuf {
150	    let status = Command::new("cargo")
151	        .args(["build", "-q", "-p", "spt", "--bin", "spt"])
152	        .current_dir(root)
153	        .status()
154	        .expect("cargo build spt");
155	    assert!(status.success(), "building spt failed");
156	    let name = if cfg!(windows) { "spt.exe" } else { "spt" };
157	    root.join("target/debug").join(name)
158	}
159
160	fn gen(check: bool) {
161	    let root = repo_root();
162	    let bin = spt_bin(&root);
163	    let generated = cli_reference(&bin);
164	    let path = root.join("docs-site/src/cli/reference.md");
165	    if check {
166	        let on_disk = std::fs::read_to_string(&path)
167	            .expect("checked-in cli/reference.md (run `cargo run -p xtask -- gen`)")
168	            .replace("\r\n", "\n");
169	        if on_disk != generated {
170	            eprintln!(
171	                "xtask check: docs-site/src/cli/reference.md drifted from the \
172	                 binary's --help — run `cargo run -p xtask -- gen` and commit"
173	            );
174	            std::process::exit(1);
175	        }
176	    } else {
177	        std::fs::write(&path, &generated).expect("write cli reference");
178	        eprintln!("xtask gen: wrote {}", path.display());
179	    }
180	}
181
182	// ---------------------------------------------------------------- llms
183
184	/// Every relative `.md`/`.json`/`.sh`/`.ps1` link in the curated llms.txt
185	/// must resolve — agents cache links; a dead one is a contract break
186	/// (DOCS-STRATEGY principle 9).
187	fn check_llms_links() {
188	    let root = repo_root();
189	    let llms = std::fs::read_to_string(root.join("docs-site/llms.txt"))
190	        .expect("docs-site/llms.txt (the curated index)");
191	    let mut dead = Vec::new();
192	    for (idx, _) in llms.match_indices("](") {
193	        let rest = &llms[idx + 2..];
194	        let Some(end) = rest.find(')') else { continue };
195	        let target = &rest[..end];
196	        if target.starts_with("http://") || target.starts_with("https://") {
197	            continue;
198	        }
199	        let target = target.trim_start_matches('/');
200	        // Site-root assets are placed by `site`; page links map into src/.
201	        let exists = match target {
202	            "llms-full.txt" | "manifest.schema.json" => true,
203	            "install.sh" | "install.ps1" => true,
204	            t => root.join("docs-site/src").join(t).exists(),
205	        };
206	        if !exists {
207	            dead.push(target.to_string());
208	        }
209	    }
210	    if !dead.is_empty() {
211	        eprintln!("xtask check: dead links in docs-site/llms.txt: {dead:?}");
212	        std::process::exit(1);
213	    }
214	}
215
216	/// llms-full.txt: the CI-concatenated full export — every page in SUMMARY
217	/// order, fenced with its site path so agents can cite anchors
218	/// (generation-only, never hand-authored).
219	fn llms_full(root: &Path) -> String {
220	    let src = root.join("docs-site/src");

//! spt-core docs/site generation (M6-D5) — the anti-drift plumbing behind
//! DOCS-STRATEGY's "generation is part of the build" commitment (REQ-DOCS-5):
//!
//! - `gen` — regenerate the checked-in generated docs (CLI reference).
//! - `check` — drift gate: regenerate + diff the CLI reference, and
//!   validate every page link in the curated llms.txt.
//! - `site <outdir>` — assemble the full publish tree for the spt-releases
//!   Pages branch: mdBook HTML + raw `.md` alongside it + llms.txt /
//!   llms-full.txt / manifest.schema.json / install scripts at site root +
//!   rustdoc for the SDK crates.
//!
//! No deps; shells out to the workspace binary, mdbook, and cargo doc.
//
// [impl->REQ-DOCS-1]
// [impl->REQ-DOCS-4]

use std::path::{Path, PathBuf};
use std::process::Command;

fn repo_root() -> PathBuf {
    // crates/xtask -> crates -> repo root
    Path::new(env!("CARGO_MANIFEST_DIR"))
        .ancestors()
        .nth(2)
        .expect("repo root")
        .to_path_buf()
}

fn main() {
    let args: Vec<String> = std::env::args().skip(1).collect();
    let cmd = args.first().map(String::as_str).unwrap_or("");
    match cmd {
        "gen" => gen(false),
        "check" => {
            gen(true);
            check_llms_links();
            eprintln!("xtask check: OK");
        }
        "site" => {
            let out = args.get(1).expect("usage: xtask site <outdir>");
            site(Path::new(out));
        }
        "release-keygen" => release_keygen(&args[1..]),
        "release-sign" => release_sign(&args[1..]),
        "release-publish" => release_publish(&args[1..]),
        "release-verify" => release_verify(&args[1..]),
        "debug-keygen" => debug_keygen(&args[1..]),
        "debug-pin" => debug_pin(&args[1..]),
        "debug-rollout" => debug_rollout(&args[1..]),
        "debug-converge" => debug_converge(&args[1..]),
        _ => {
            eprintln!(
                "usage: xtask <gen|check|site <outdir>|release-keygen <key-id>|release-sign …|release-publish …|debug-keygen <key-id>|debug-pin …|debug-rollout …|debug-converge --version <u64> …>"
            );
            std::process::exit(2);
        }
    }
}

// ---------------------------------------------------------------- CLI ref

/// Run the workspace `spt` binary with `args` + `--help`, normalized to the
/// `spt` name (Windows prints `spt.exe`, which would make the generated
/// reference platform-dependent).
fn help_for(bin: &Path, args: &[&str]) -> String {
    let mut cmd = Command::new(bin);
    cmd.args(args).arg("--help");
    let out = cmd.output().expect("run spt --help");
    let text = String::from_utf8_lossy(&out.stdout);
    text.replace("spt.exe", "spt").replace("\r\n", "\n")
}

/// Parse the command-list block(s) of a clap help text into subcommand
/// names. Nested helps carry clap's flat `Commands:` heading; the root help
/// renders two grouped blocks (`User commands:` / `Agent commands:`).
fn subcommands(help: &str) -> Vec<String> {
    let mut cmds = Vec::new();
    let mut in_block = false;
    for line in help.lines() {
        let heading = line.trim_end();
        if heading == "Commands:" || heading == "User commands:" || heading == "Agent commands:" {
            in_block = true;
            continue;
        }
        if in_block {
            // A blank line ends the current block (another may follow);
            // `Options:` ends the command listing for good.
            if line.trim().is_empty() {
                in_block = false;
                continue;
            }
            if line.starts_with("Options:") {
                break;
            }
            // Entries are `  <name>  <doc…>`; continuation lines are deeper-indented.
            if let Some(rest) = line.strip_prefix("  ") {
                if !rest.starts_with(' ') {
                    if let Some(name) = rest.split_whitespace().next() {
                        if name != "help" {
                            cmds.push(name.to_string());
                        }
                    }
                }
            }
        }
    }
    cmds
}

/// The generated CLI reference: every command + nested subcommand's help,
/// verbatim. Help text is a first-class doc surface (DOCS-STRATEGY), so the
/// reference IS the binary's own words — it cannot drift (REQ-DOCS-5).
// [impl->REQ-DOCS-5]
fn cli_reference(bin: &Path) -> String {
    let mut out = String::from(
        "# CLI reference\n\n\
         > **Generated** from the `spt` binary's own `--help` output \
         (`cargo run -p xtask -- gen`) and drift-gated in CI — this page \
         cannot disagree with the binary. Do not edit by hand.\n\n",
    );
    let root_help = help_for(bin, &[]);
    out.push_str("## spt\n\n```text\n");
    out.push_str(&root_help);
    out.push_str("```\n");
    for cmd in subcommands(&root_help) {
        // `spt api` requires --adapter even for subcommand help; pass a
        // stand-in so clap renders the help instead of erroring.
        let help = if cmd == "api" {
            help_for(bin, &["api", "--adapter", "x"])
        } else {
            help_for(bin, &[&cmd])
        };
        out.push_str(&format!("\n## spt {cmd}\n\n```text\n{help}```\n"));
        for sub in subcommands(&help) {
            let sub_help = if cmd == "api" {
                help_for(bin, &["api", "--adapter", "x", &sub])
            } else {
                help_for(bin, &[&cmd, &sub])
            };
            out.push_str(&format!(
                "\n### spt {cmd} {sub}\n\n```text\n{sub_help}```\n"
            ));
        }
    }
    out
}

/// Locate (building if needed) the workspace `spt` binary.
fn spt_bin(root: &Path) -> PathBuf {
    let status = Command::new("cargo")
        .args(["build", "-q", "-p", "spt", "--bin", "spt"])
        .current_dir(root)
        .status()
        .expect("cargo build spt");
    assert!(status.success(), "building spt failed");
    let name = if cfg!(windows) { "spt.exe" } else { "spt" };
    root.join("target/debug").join(name)
}

fn gen(check: bool) {
    let root = repo_root();
    let bin = spt_bin(&root);
    let generated = cli_reference(&bin);
    let path = root.join("docs-site/src/cli/reference.md");
    if check {
        let on_disk = std::fs::read_to_string(&path)
            .expect("checked-in cli/reference.md (run `cargo run -p xtask -- gen`)")
            .replace("\r\n", "\n");
        if on_disk != generated {
            eprintln!(
                "xtask check: docs-site/src/cli/reference.md drifted from the \
                 binary's --help — run `cargo run -p xtask -- gen` and commit"
            );
            std::process::exit(1);
        }
    } else {
        std::fs::write(&path, &generated).expect("write cli reference");
        eprintln!("xtask gen: wrote {}", path.display());
    }
}

// ---------------------------------------------------------------- llms

/// Every relative `.md`/`.json`/`.sh`/`.ps1` link in the curated llms.txt
/// must resolve — agents cache links; a dead one is a contract break
/// (DOCS-STRATEGY principle 9).
fn check_llms_links() {
    let root = repo_root();
    let llms = std::fs::read_to_string(root.join("docs-site/llms.txt"))
        .expect("docs-site/llms.txt (the curated index)");
    let mut dead = Vec::new();
    for (idx, _) in llms.match_indices("](") {
        let rest = &llms[idx + 2..];
        let Some(end) = rest.find(')') else { continue };
        let target = &rest[..end];
        if target.starts_with("http://") || target.starts_with("https://") {
            continue;
        }
        let target = target.trim_start_matches('/');
        // Site-root assets are placed by `site`; page links map into src/.
        let exists = match target {
            "llms-full.txt" | "manifest.schema.json" => true,
            "install.sh" | "install.ps1" => true,
            t => root.join("docs-site/src").join(t).exists(),
        };
        if !exists {
            dead.push(target.to_string());
        }
    }
    if !dead.is_empty() {
        eprintln!("xtask check: dead links in docs-site/llms.txt: {dead:?}");
        std::process::exit(1);
    }
}

/// llms-full.txt: the CI-concatenated full export — every page in SUMMARY
/// order, fenced with its site path so agents can cite anchors
/// (generation-only, never hand-authored).
fn llms_full(root: &Path) -> String {
    let src = root.join("docs-site/src");


I'll add a pure `forbidden_codes_in` scanner + wire it into `check`, with an xtask unit test. Check whether xtask already has a test module:


Bash

grep -n "mod tests\|#\[cfg(test)\]\|#\[test\]" crates/xtask/src/main.rs | head; echo "---LINES---"; wc -l < crates/xtask/src/main.rs

Check for existing xtask tests + file length


1256:#[cfg(test)]
1264:    #[test]
---LINES---
1300


1256	#[cfg(test)]
1257	mod converge_tests {
1258	    use spt_daemon::propagate::ConvergeState;
1259
1260	    // [unit->REQ-UPD-6] the convergence table renders one row per expected
1261	    // node with the typed state + actionable hint (the DEBUG-CONVERGE-PLAN
1262	    // output shape) — and the success predicate behind the exit-code policy
1263	    // (0 converged / 1 not) is exactly "every node Applied".
1264	    #[test]
1265	    fn converge_table_renders_rows_and_success_predicate() {
1266	        let mut states = std::collections::BTreeMap::new();
1267	        states.insert(
1268	            "aaaa111122223333deadbeef".to_string(),
1269	            ConvergeState::Applied { version: 7 },
1270	        );
1271	        states.insert(
1272	            "bbbb111122223333deadbeef".to_string(),
1273	            ConvergeState::StagedAwaitingConsent { staged: 7 },
1274	        );
1275	        states.insert(
1276	            "cccc111122223333deadbeef".to_string(),
1277	            ConvergeState::Rejected {
1278	                reason: "NoArtifactForPlatform(\"x86_64-apple-darwin\")".to_string(),
1279	            },
1280	        );
1281	        let table = super::render_converge_table(&states);
1282	        assert!(table.contains("aaaa111122223333"), "node ids shortened");
1283	        assert!(table.contains("Applied                v7"));
1284	        assert!(table.contains("StagedAwaitingConsent  v7  (run: spt update apply)"));
1285	        assert!(table.contains("NoArtifactForPlatform"));
1286	        assert_eq!(table.lines().count(), 3, "one row per expected node");
1287
1288	        // Exit-code policy: all-Applied is the only success.
1289	        assert!(!states.values().all(ConvergeState::converged));
1290	        states.insert(
1291	            "bbbb111122223333deadbeef".to_string(),
1292	            ConvergeState::Applied { version: 7 },
1293	        );
1294	        states.insert(
1295	            "cccc111122223333deadbeef".to_string(),
1296	            ConvergeState::Applied { version: 8 },
1297	        );
1298	        assert!(states.values().all(ConvergeState::converged));
1299	    }
1300	}

#[cfg(test)]
mod converge_tests {
    use spt_daemon::propagate::ConvergeState;

    // [unit->REQ-UPD-6] the convergence table renders one row per expected
    // node with the typed state + actionable hint (the DEBUG-CONVERGE-PLAN
    // output shape) — and the success predicate behind the exit-code policy
    // (0 converged / 1 not) is exactly "every node Applied".
    #[test]
    fn converge_table_renders_rows_and_success_predicate() {
        let mut states = std::collections::BTreeMap::new();
        states.insert(
            "aaaa111122223333deadbeef".to_string(),
            ConvergeState::Applied { version: 7 },
        );
        states.insert(
            "bbbb111122223333deadbeef".to_string(),
            ConvergeState::StagedAwaitingConsent { staged: 7 },
        );
        states.insert(
            "cccc111122223333deadbeef".to_string(),
            ConvergeState::Rejected {
                reason: "NoArtifactForPlatform(\"x86_64-apple-darwin\")".to_string(),
            },
        );
        let table = super::render_converge_table(&states);
        assert!(table.contains("aaaa111122223333"), "node ids shortened");
        assert!(table.contains("Applied                v7"));
        assert!(table.contains("StagedAwaitingConsent  v7  (run: spt update apply)"));
        assert!(table.contains("NoArtifactForPlatform"));
        assert_eq!(table.lines().count(), 3, "one row per expected node");

        // Exit-code policy: all-Applied is the only success.
        assert!(!states.values().all(ConvergeState::converged));
        states.insert(
            "bbbb111122223333deadbeef".to_string(),
            ConvergeState::Applied { version: 7 },
        );
        states.insert(
            "cccc111122223333deadbeef".to_string(),
            ConvergeState::Applied { version: 8 },
        );
        assert!(states.values().all(ConvergeState::converged));
    }
}


Add the scanner + wire into `check`. Insert after `gen()`:


Edit

// ------------------------------------------------ public-help token gate

/// Capture an internal-code token starting at `s[0]`: the run of
/// `[A-Za-z0-9-]` (so `REQ-MSG-5`, `ADR-0019`, `M8-W5` are returned whole).
fn capture_code(s: &str) -> String {
    s.chars()
        .take_while(|c| c.is_ascii_alphanumeric() || *c == '-')
        .collect()
}

/// Internal tracker/decision codes that must never appear in the GENERATED CLI
/// reference (it is public `--help`). Pure: returns each `(line_number, token)`
/// so the gate reports precisely. Classes: `REQ-<UPPER>`, `ADR-<digit>`,
/// `F-<digit>`, `M<digit…>-W<digit…>` — each only at a LEFT word boundary, so a
/// plain word like `UTF-8` never trips the `F-` rule and `PERF-X` never trips
/// nothing. rustdoc `///` on fns/structs is NOT scanned (it is not in
/// reference.md) — those keep their REQ/ADR cross-refs.
// [impl->REQ-DOCS-NO-INTERNAL-CODES]
fn forbidden_codes_in(text: &str) -> Vec<(usize, String)> {
    let bytes = text.as_bytes();
    // Line number (1-based) for a byte offset.
    let mut line_starts = vec![0usize];
    for (i, c) in text.char_indices() {
        if c == '\n' {
            line_starts.push(i + 1);
        }
    }
    let line_of = |idx: usize| line_starts.partition_point(|&s| s <= idx);
    // Left boundary: start of text, or a non-alphanumeric byte before `idx`.
    let left_ok = |idx: usize| idx == 0 || !bytes[idx - 1].is_ascii_alphanumeric();
    let next_is = |idx: usize, pred: fn(char) -> bool| {
        text[idx..].chars().next().map(pred).unwrap_or(false)
    };

    let mut hits = Vec::new();
    // REQ-<UPPER…> / ADR-<digit…> / F-<digit…>
    for (prefix, ok_next) in [
        ("REQ-", char::is_ascii_uppercase as fn(&char) -> bool),
        ("ADR-", char::is_ascii_digit as fn(&char) -> bool),
        ("F-", char::is_ascii_digit as fn(&char) -> bool),
    ] {
        for (idx, _) in text.match_indices(prefix) {
            if left_ok(idx) && next_is(idx + prefix.len(), |c| ok_next(&c)) {
                hits.push((line_of(idx), capture_code(&text[idx..])));
            }
        }
    }
    // M<digit…>-W<digit…> (e.g. `M8-W5`, `M12-W1`): anchor on `-W<digit>`, walk
    // back over digits to a boundary-anchored `M`.
    for (idx, _) in text.match_indices("-W") {
        if !next_is(idx + 2, |c| c.is_ascii_digit()) {
            continue;
        }
        let mut k = idx;
        while k > 0 && bytes[k - 1].is_ascii_digit() {
            k -= 1;
        }
        if k == idx || k == 0 || bytes[k - 1] != b'M' {
            continue; // need ≥1 digit then an `M`
        }
        let mstart = k - 1;
        if mstart != 0 && bytes[mstart - 1].is_ascii_alphanumeric() {
            continue; // `M` must be at a left boundary
        }
        hits.push((line_of(mstart), capture_code(&text[mstart..])));
    }
    hits
}

/// Fail the docs gate if the GENERATED CLI reference carries any internal
/// tracker/decision code. Regeneration alone keeps drift==0, but a faithfully
/// regenerated leak still ships to GH-Pages — THIS scan is what gives `xtask
/// check` teeth over public `--help`.
// [impl->REQ-DOCS-NO-INTERNAL-CODES]
fn scan_cli_reference_for_internal_codes() {
    let root = repo_root();
    let path = root.join("docs-site/src/cli/reference.md");
    let text = std::fs::read_to_string(&path).expect("checked-in cli/reference.md");
    let hits = forbidden_codes_in(&text);
    if !hits.is_empty() {
        eprintln!(
            "xtask check: internal tracker/decision codes leaked into public CLI \
             --help (docs-site/src/cli/reference.md). Rewrite the offending clap \
             `///` doc-comments in PLAIN language — REQ-*/ADR-####/F-###/M#-W# are \
             meaningless to an end user reading --help and ship to GH-Pages:"
        );
        for (line, tok) in &hits {
            eprintln!("  reference.md:{line}: {tok}");
        }
        std::process::exit(1);
    }
}

// ---------------------------------------------------------------- llms


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

// ------------------------------------------------ public-help token gate

/// Capture an internal-code token starting at `s[0]`: the run of
/// `[A-Za-z0-9-]` (so `REQ-MSG-5`, `ADR-0019`, `M8-W5` are returned whole).
fn capture_code(s: &str) -> String {
    s.chars()
        .take_while(|c| c.is_ascii_alphanumeric() || *c == '-')
        .collect()
}

/// Internal tracker/decision codes that must never appear in the GENERATED CLI
/// reference (it is public `--help`). Pure: returns each `(line_number, token)`
/// so the gate reports precisely. Classes: `REQ-<UPPER>`, `ADR-<digit>`,
/// `F-<digit>`, `M<digit…>-W<digit…>` — each only at a LEFT word boundary, so a
/// plain word like `UTF-8` never trips the `F-` rule and `PERF-X` never trips
/// nothing. rustdoc `///` on fns/structs is NOT scanned (it is not in
/// reference.md) — those keep their REQ/ADR cross-refs.
// [impl->REQ-DOCS-NO-INTERNAL-CODES]
fn forbidden_codes_in(text: &str) -> Vec<(usize, String)> {
    let bytes = text.as_bytes();
    // Line number (1-based) for a byte offset.
    let mut line_starts = vec![0usize];
    for (i, c) in text.char_indices() {
        if c == '\n' {
            line_starts.push(i + 1);
        }
    }
    let line_of = |idx: usize| line_starts.partition_point(|&s| s <= idx);
    // Left boundary: start of text, or a non-alphanumeric byte before `idx`.
    let left_ok = |idx: usize| idx == 0 || !bytes[idx - 1].is_ascii_alphanumeric();
    let next_is = |idx: usize, pred: fn(char) -> bool| {
        text[idx..].chars().next().map(pred).unwrap_or(false)
    };

    let mut hits = Vec::new();
    // REQ-<UPPER…> / ADR-<digit…> / F-<digit…>
    for (prefix, ok_next) in [
        ("REQ-", char::is_ascii_uppercase as fn(&char) -> bool),
        ("ADR-", char::is_ascii_digit as fn(&char) -> bool),
        ("F-", char::is_ascii_digit as fn(&char) -> bool),
    ] {
        for (idx, _) in text.match_indices(prefix) {
            if left_ok(idx) && next_is(idx + prefix.len(), |c| ok_next(&c)) {
                hits.push((line_of(idx), capture_code(&text[idx..])));
            }
        }
    }
    // M<digit…>-W<digit…> (e.g. `M8-W5`, `M12-W1`): anchor on `-W<digit>`, walk
    // back over digits to a boundary-anchored `M`.
    for (idx, _) in text.match_indices("-W") {
        if !next_is(idx + 2, |c| c.is_ascii_digit()) {
            continue;
        }
        let mut k = idx;
        while k > 0 && bytes[k - 1].is_ascii_digit() {
            k -= 1;
        }
        if k == idx || k == 0 || bytes[k - 1] != b'M' {
            continue; // need ≥1 digit then an `M`
        }
        let mstart = k - 1;
        if mstart != 0 && bytes[mstart - 1].is_ascii_alphanumeric() {
            continue; // `M` must be at a left boundary
        }
        hits.push((line_of(mstart), capture_code(&text[mstart..])));
    }
    hits
}

/// Fail the docs gate if the GENERATED CLI reference carries any internal
/// tracker/decision code. Regeneration alone keeps drift==0, but a faithfully
/// regenerated leak still ships to GH-Pages — THIS scan is what gives `xtask
/// check` teeth over public `--help`.
// [impl->REQ-DOCS-NO-INTERNAL-CODES]
fn scan_cli_reference_for_internal_codes() {
    let root = repo_root();
    let path = root.join("docs-site/src/cli/reference.md");
    let text = std::fs::read_to_string(&path).expect("checked-in cli/reference.md");
    let hits = forbidden_codes_in(&text);
    if !hits.is_empty() {
        eprintln!(
            "xtask check: internal tracker/decision codes leaked into public CLI \
             --help (docs-site/src/cli/reference.md). Rewrite the offending clap \
             `///` doc-comments in PLAIN language — REQ-*/ADR-####/F-###/M#-W# are \
             meaningless to an end user reading --help and ship to GH-Pages:"
        );
        for (line, tok) in &hits {
            eprintln!("  reference.md:{line}: {tok}");
        }
        std::process::exit(1);
    }
}

// ---------------------------------------------------------------- llms

+/// Capture an internal-code token starting at `s[0]`: the run of

+        .take_while(|c| c.is_ascii_alphanumeric() || *c == '-')

+/// Internal tracker/decision codes that must never appear in the GENERATED CLI

+/// reference (it is public `--help`). Pure: returns each `(line_number, token)`

+/// so the gate reports precisely. Classes: `REQ-<UPPER>`, `ADR-<digit>`,

+/// `F-<digit>`, `M<digit…>-W<digit…>` — each only at a LEFT word boundary, so a

+/// plain word like `UTF-8` never trips the `F-` rule and `PERF-X` never trips

+/// nothing. rustdoc `///` on fns/structs is NOT scanned (it is not in

+/// reference.md) — those keep their REQ/ADR cross-refs.

+fn forbidden_codes_in(text: &str) -> Vec<(usize, String)> {

+    // Line number (1-based) for a byte offset.

+    let mut line_starts = vec![0usize];

+    for (i, c) in text.char_indices() {

+    let line_of = |idx: usize| line_starts.partition_point(|&s| s <= idx);

+    // Left boundary: start of text, or a non-alphanumeric byte before `idx`.

+    let left_ok = |idx: usize| idx == 0 || !bytes[idx - 1].is_ascii_alphanumeric();

+    let next_is = |idx: usize, pred: fn(char) -> bool| {

+        text[idx..].chars().next().map(pred).unwrap_or(false)

+    // REQ-<UPPER…> / ADR-<digit…> / F-<digit…>

+        ("REQ-", char::is_ascii_uppercase as fn(&char) -> bool),

+        ("ADR-", char::is_ascii_digit as fn(&char) -> bool),

+        ("F-", char::is_ascii_digit as fn(&char) -> bool),

+        for (idx, _) in text.match_indices(prefix) {

+            if left_ok(idx) && next_is(idx + prefix.len(), |c| ok_next(&c)) {

+                hits.push((line_of(idx), capture_code(&text[idx..])));

+    // M<digit…>-W<digit…> (e.g. `M8-W5`, `M12-W1`): anchor on `-W<digit>`, walk

+    // back over digits to a boundary-anchored `M`.

+    for (idx, _) in text.match_indices("-W") {

+        if !next_is(idx + 2, |c| c.is_ascii_digit()) {

+        while k > 0 && bytes[k - 1].is_ascii_digit() {

+            continue; // need ≥1 digit then an `M`

+        if mstart != 0 && bytes[mstart - 1].is_ascii_alphanumeric() {

+            continue; // `M` must be at a left boundary

+        hits.push((line_of(mstart), capture_code(&text[mstart..])));

+/// Fail the docs gate if the GENERATED CLI reference carries any internal

+/// tracker/decision code. Regeneration alone keeps drift==0, but a faithfully

+/// regenerated leak still ships to GH-Pages — THIS scan is what gives `xtask

+    let path = root.join("docs-site/src/cli/reference.md");

+    let text = std::fs::read_to_string(&path).expect("checked-in cli/reference.md");

+    let hits = forbidden_codes_in(&text);

+            "xtask check: internal tracker/decision codes leaked into public CLI \

+             --help (docs-site/src/cli/reference.md). Rewrite the offending clap \

+             `///` doc-comments in PLAIN language — REQ-*/ADR-####/F-###/M#-W# are \

+             meaningless to an end user reading --help and ship to GH-Pages:"

+            eprintln!("  reference.md:{line}: {tok}");

/// Every relative `.md`/`.json`/`.sh`/`.ps1` link in the curated llms.txt


        "check" => {
            gen(true);
            check_llms_links();
            eprintln!("xtask check: OK");
        }

        "check" => {
            gen(true);
            scan_cli_reference_for_internal_codes();
            check_llms_links();
            eprintln!("xtask check: OK");
        }


        "check" => {
            gen(true);
            check_llms_links();
            eprintln!("xtask check: OK");
        }

        "check" => {
            gen(true);
            scan_cli_reference_for_internal_codes();
            check_llms_links();
            eprintln!("xtask check: OK");
        }

+            scan_cli_reference_for_internal_codes();


Add the xtask unit test (planted token fails, clean passes, UTF-8 boundary):


        assert!(states.values().all(ConvergeState::converged));
    }
}

        assert!(states.values().all(ConvergeState::converged));
    }
}

#[cfg(test)]
mod docs_token_gate_tests {
    use super::forbidden_codes_in;

    // [unit->REQ-DOCS-NO-INTERNAL-CODES] the scan is NON-VACUOUS: a planted token
    // of each banned class (REQ-*, ADR-####, F-###, M#-W#) is detected, with its
    // line number and the whole token captured.
    #[test]
    fn planted_internal_codes_are_flagged() {
        let text = "line one is clean\n\
                    take control and drive (REQ-KICK-1).\n\
                    proof against a log sample (ADR-0019).\n\
                    the gh fix (F-011) for private repos\n\
                    added in M8-W5 of the plan\n";
        let hits = forbidden_codes_in(text);
        let tokens: Vec<&str> = hits.iter().map(|(_, t)| t.as_str()).collect();
        assert!(tokens.contains(&"REQ-KICK-1"), "REQ-* flagged: {tokens:?}");
        assert!(tokens.contains(&"ADR-0019"), "ADR-#### flagged: {tokens:?}");
        assert!(tokens.contains(&"F-011"), "F-### flagged: {tokens:?}");
        assert!(tokens.contains(&"M8-W5"), "M#-W# flagged: {tokens:?}");
        // Line numbers are 1-based and precise.
        assert_eq!(hits.iter().find(|(_, t)| t == "REQ-KICK-1").unwrap().0, 2);
        assert_eq!(hits.iter().find(|(_, t)| t == "M8-W5").unwrap().0, 5);
    }

    // [unit->REQ-DOCS-NO-INTERNAL-CODES] a clean reference PASSES — and the left
    // word-boundary rule means ordinary prose with embedded `-` runs (UTF-8, a
    // hyphenated word ending in F-, a bare `WARN-1`) does NOT false-positive.
    #[test]
    fn clean_help_text_passes() {
        let text = "Send a message. Bytes are UTF-8.\n\
                    Take control and drive. Use on an endpoint another node controls.\n\
                    Prove the extractor against a real log sample; no silent empty.\n\
                    Self-contained, world-readable output.\n";
        let hits = forbidden_codes_in(text);
        assert!(hits.is_empty(), "clean help must pass, got: {hits:?}");
    }
}


        assert!(states.values().all(ConvergeState::converged));
    }
}

#[cfg(test)]
mod docs_token_gate_tests {
    use super::forbidden_codes_in;

    // [unit->REQ-DOCS-NO-INTERNAL-CODES] the scan is NON-VACUOUS: a planted token
    // of each banned class (REQ-*, ADR-####, F-###, M#-W#) is detected, with its
    // line number and the whole token captured.
    #[test]
    fn planted_internal_codes_are_flagged() {
        let text = "line one is clean\n\
                    take control and drive (REQ-KICK-1).\n\
                    proof against a log sample (ADR-0019).\n\
                    the gh fix (F-011) for private repos\n\
                    added in M8-W5 of the plan\n";
        let hits = forbidden_codes_in(text);
        let tokens: Vec<&str> = hits.iter().map(|(_, t)| t.as_str()).collect();
        assert!(tokens.contains(&"REQ-KICK-1"), "REQ-* flagged: {tokens:?}");
        assert!(tokens.contains(&"ADR-0019"), "ADR-#### flagged: {tokens:?}");
        assert!(tokens.contains(&"F-011"), "F-### flagged: {tokens:?}");
        assert!(tokens.contains(&"M8-W5"), "M#-W# flagged: {tokens:?}");
        // Line numbers are 1-based and precise.
        assert_eq!(hits.iter().find(|(_, t)| t == "REQ-KICK-1").unwrap().0, 2);
        assert_eq!(hits.iter().find(|(_, t)| t == "M8-W5").unwrap().0, 5);
    }

    // [unit->REQ-DOCS-NO-INTERNAL-CODES] a clean reference PASSES — and the left
    // word-boundary rule means ordinary prose with embedded `-` runs (UTF-8, a
    // hyphenated word ending in F-, a bare `WARN-1`) does NOT false-positive.
    #[test]
    fn clean_help_text_passes() {
        let text = "Send a message. Bytes are UTF-8.\n\
                    Take control and drive. Use on an endpoint another node controls.\n\
                    Prove the extractor against a real log sample; no silent empty.\n\
                    Self-contained, world-readable output.\n";
        let hits = forbidden_codes_in(text);
        assert!(hits.is_empty(), "clean help must pass, got: {hits:?}");
    }
}

+    // [unit->REQ-DOCS-NO-INTERNAL-CODES] the scan is NON-VACUOUS: a planted token

+    // of each banned class (REQ-*, ADR-####, F-###, M#-W#) is detected, with its

+    // line number and the whole token captured.

+    fn planted_internal_codes_are_flagged() {

+        let text = "line one is clean\n\

+                    take control and drive (REQ-KICK-1).\n\

+                    proof against a log sample (ADR-0019).\n\

+                    the gh fix (F-011) for private repos\n\

+                    added in M8-W5 of the plan\n";

+        let hits = forbidden_codes_in(text);

+        let tokens: Vec<&str> = hits.iter().map(|(_, t)| t.as_str()).collect();

+        assert!(tokens.contains(&"REQ-KICK-1"), "REQ-* flagged: {tokens:?}");

+        assert!(tokens.contains(&"ADR-0019"), "ADR-#### flagged: {tokens:?}");

+        assert!(tokens.contains(&"F-011"), "F-### flagged: {tokens:?}");

+        assert!(tokens.contains(&"M8-W5"), "M#-W# flagged: {tokens:?}");

+        // Line numbers are 1-based and precise.

+        assert_eq!(hits.iter().find(|(_, t)| t == "REQ-KICK-1").unwrap().0, 2);

+    // [unit->REQ-DOCS-NO-INTERNAL-CODES] a clean reference PASSES — and the left

+    // word-boundary rule means ordinary prose with embedded `-` runs (UTF-8, a

+    // hyphenated word ending in F-, a bare `WARN-1`) does NOT false-positive.

+        let text = "Send a message. Bytes are UTF-8.\n\

+                    Take control and drive. Use on an endpoint another node controls.\n\

+                    Prove the extractor against a real log sample; no silent empty.\n\

+                    Self-contained, world-readable output.\n";

+        assert!(hits.is_empty(), "clean help must pass, got: {hits:?}");


Now docs. Find the `[adapter].version` doc + DOCS-STRATEGY.md:


Bash

grep -n "version" docs/MANIFEST.md | head; echo "---adapter section---"; grep -n "\[adapter\]\|^### \`\[adapter\]" docs/MANIFEST.md | head; echo "---DOCS-STRATEGY---"; ls docs/DOCS-STRATEGY.md && grep -n "## \|generated\|reference" docs/DOCS-STRATEGY.md | head -20

Locate adapter version doc + DOCS-STRATEGY structure


7:**SPT is not a harness.** The manifest declares only what *varies per harness*. Command templates are **opaque strings** — spt-core never parses out a model, tool list, or flag; the adapter writes the full command line and spt-core just runs it with substitution keys filled. Anything spt-core owns (sentinels, spool/registry/perch schemas, the event-block vocabulary, the version-trampoline mechanism, config knobs) is **not** in the manifest.
17:- **Plugin-cache / version-trampoline layout** — none of SPT's concern. A CC-plugin adapter updates via `claude plugin update` (delegated command).
28:version = "2.0.0"
29:min_spt_core_version = "1.0.0"     # readable before any update; compat gate
289:version_check = true               # verify spt-core satisfies min_spt_core_version before/after
306:`spt adapter update [name]` (with no name, every registered `gh_release` adapter; with a name, just that one) compares the repo's latest GitHub release version against the installed adapter version and, when newer, fetches the release `.spt` (the same archive primitive as `spt adapter add --release`), then re-extracts and re-registers it in the adapter's durable `_github/<safe>` home (pointer-mode, re-read live). The network fetch lives in the `spt` CLI, never the daemon. **`repo` is required**; `asset` defaults to `adapter.spt`; `signing_key` is **optional**.
309:**Transport — public HTTPS or private `gh`.** The optional **`transport`** selects how the asset bytes + the latest-release version are fetched: `https` (direct, the public-repo path), `gh` (shell the pre-authorized [`gh` CLI](https://cli.github.com/) — `gh release download` for the asset, `gh api` for the version — the **private-repo** path), or **`auto`** (the default: prefer `gh` when it is installed and authenticated, else fall back to HTTPS). Because `gh` honors OAuth + `GH_TOKEN`, an adapter shipping from a **private** repo updates with no token in spt-core's hands — spt never reads or stores a credential. `spt adapter add --release` takes `--gh` / `--https` to force the choice (default auto). A signed private adapter's `<asset>.sig` is fetched over the **same** transport, so verification still works. Transport is additive — the verify→extract→register path is unchanged.
312:**Post-update notice — `message`.** The optional **`message`** is a plain (multi-line) human notice the adapter surfaces to stdout **only when `spt adapter update` actually applies an update** (the version changed) — never on a no-op / up-to-date run. It is read from the **newly-installed** manifest and rendered through the same inline-Markdown prose path as `spt`'s help (`**bold**`, `` `code` ``, `[text](url)`); there is no `{key}` substitution. It is avenue-agnostic (`gh_release` / `delegated` / `file_pull`). Use it to tell the operator a required post-update action — e.g. an in-harness adapter announcing "run `/reload-plugins` in any ongoing sessions" so already-running sessions pick up the new plugin.
328:version = "1.0.0"
329:min_spt_core_version = "1.0.0"
---adapter section---
24:### `[adapter]` — header
26:[adapter]
325:[adapter]
368:| `cc`/`cc <id>` launcher | hand-written shell wrappers | `[adapter] shortcut_basename = "cc"` → the picker bakes `cc-<id>` (spt-core default is `spt-<id>`) |
---DOCS-STRATEGY---
docs/DOCS-STRATEGY.md
5:## The defining constraint: a dual audience
9:## Principles (top techniques, prioritized for spt-core)
12:2. **Diátaxis four-mode separation** — tutorial / how-to / reference / explanation, never mixed (mixing is the most-cited cause of confusing docs).
14:4. **Dual-depth agent exports** — `llms.txt` (slim curated index) + `llms-full.txt` (full concatenated export), auto-generated in CI, plus markdown content negotiation (`.md` suffix / `Accept: text/markdown`) which cuts agent token use ~90% vs HTML.
16:6. **Complete reference, auto-generated, all error variants** — rustdoc for the public SDK crates (`spt-proto`, `spt-runtime`, `spt-msg`) + a machine-readable manifest/contract schema. Generic placeholders in reference are a failure mode.
22:## Information architecture — by capability vertical
28:## Killer quickstart targets (two, one per audience)
33:## Agent-consumable docs (spt-core specifics)
40:- **MCP doc/resource server** — the standout fit, given the harness contract is the core integration surface. Expose the contract + reference as **deterministic, side-effect-free MCP resources** (same request → same result), giving integrating agents structured knowledge without RAG fuzziness. Highest-leverage agent-docs move for spt-core.
43:## v0.1 deployment scope (grilled 2026-06-05)
45:Deployed to GitHub Pages on `spt-releases` (ADR-0014; truth + CI generation stay in the source repo). **Tier 1 (ships with v0.1):** mental model/overview · human quickstart (install → two agents message) · adapter quickstart (via `mock-adapter`) · **harness contract vertical complete** (public manifest reference + full `spt api` reference + install-on-demand bootstrap pattern) · shell adapter getting-started (`spt-shell-notify` as the live example) · CLI reference generated from clap, CI-gated · **manifest JSON Schema (new code: schemars on the manifest types)** · root `llms.txt` + `llms-full.txt` CI-generated · rustdoc → Pages (no docs.rs — SDK crates are first-party-only while source is private).
49:**Tier 2 (deferred, tracked):** remaining verticals' tutorials/how-tos (overview + reference stubs only in v0.1) · MCP doc server · `--help --json` mode (low marginal value over the generated CLI reference) · capstone tutorial · per-vertical `llms.txt` fan-out (root links pages directly until then).
51:## Anti-patterns to design against (most-cited failures)
53:Doc/code drift (#1 — solved by CI gating); *what* without *why*; too much setup before first success; generic placeholders in reference; mixed Diátaxis modes; poor search/navigation; multiple non-canonical ways to do X (fatal for agents).
55:## CI commitments
57:rustdoc, the manifest JSON Schema, `llms.txt`/`llms-full.txt`, and CLI help exports are **generated and checked in CI** — a doc-drift gate. Doc quality lives on the same footing as tests.
61:## Concrete templates (from firsthand artifacts)
65:### `llms.txt` — two-level (Cloudflare pattern, not Anthropic's flat dump)
78:## Start here
83:## Capabilities      (one bullet per vertical, each → its own llms.txt)