# W31 — The shell says where it is

First wave of **Milestone #38** (*the control surface: what the board can do*). Three
changes, all on the agent-facing shell surface rather than the Discord board, and all one
subject: **an instance that tells an agent which repo it is.** The board waves come next
(W32: #11 and #18; W33: #26; W34–W35: #10).

Design ruled with the Operator on 2026-07-28 in a grill session; the milestone-level scope,
the two Requests deliberately excluded, and the one platform constraint are in issue **#38**.

## Why these three first

They are the wave with no Discord surface, so they are provable by `cargo test` plus one
live drive of the installed binary — no Operator board read is owed. That matters because
the board waves each end in the Operator's eyes, and shipping the surface-free work first
keeps the milestone moving while those queue.

They also close a wound taken during the grill itself. Three writes went to
`alchemy-testbed#23` believing the instance was bound to spt-progress-tool: every ack read
`… added to #23` and none named a repo. Repaired by hand (comments deleted, `state: CUT`
reverted to `state: DONE`, close reason restored). #37 is that misfire minted.

## A. #37 — every write verb's ack names the bound repo

`CONTEXT.md` keeps repo out of the argument vector by design ("repo is never a verb
argument"), and that rule stays. The input side is defended by the instance alias
("its alias names that repo") — which was `null` on both of this owner's instances until the
grill set it, and which `spt shell teardown` **frees**, so the defense is absent exactly when
a node has just churned its instances. The ack is the durable half.

- **T1 — impl.** Every write verb's answer echoes `owner/repo#N` where it echoes `#N` today:
  `create`, `comment`, `state`, `tag`, `flag`, `link`, `dispatch`, `milestone`, `watch`,
  `unwatch`, `sweep`, `release`. Read verbs already name the repo — unchanged.
- **T2 — unit.** One test per verb family asserting the repo appears in the answer. The
  regression this guards is not a crash; it is an ack that reads correct against the wrong
  repo, so the assertion is on the repo string, never on "contains #N".
- `REQ-ACK-NAMES-REPO` → `["doc", "impl", "unit"]`.

## B. #36 — `bind-repo` finishes the job

Two halves, one answer.

**Self-alias.** `spt shell rename <id> <alias> --owner <endpoint>` is reachable from the
shell process: `--owner` is node-local namespace selection, not authenticated identity
(measured — `spt shell list --owner liam` answers from this session with no challenge), and
alchemy already shells out to the `spt` CLI for every answer it sends. The owner id is known
at exactly the right moment: the pump learns it from the first owner frame (`main.rs:138`),
and `bind-repo` **is** an owner frame. Note the shell-side `spt api` surface has no alias
verb at all — this rides the owner-facing CLI deliberately, and that is why it works today
with no spt-core dependency and nothing routed to doyle.

**The followup.** The same answer names what is missing: no channel provisioned for the
bound repo → the verb that provisions one, plus the nudge that offering it is the user's
call, not the agent's. No Discord bot configured at all → say *that* instead. Never silence:
a bind that succeeds and a board that never appears is the silent degradation this project
refuses to ship.

- **T3 — impl.** `bind_repo` renames the instance and appends `alias set to <repo>` to its
  answer. A rename that fails is reported in the same answer and never fails the bind — the
  bind is the thing the owner asked for.
- **T4 — impl.** The followup lines, gated on the registry read the shell already does for
  `daemon status` (the config dir's `daemon.toml`), not on a Discord call.
- **T5 — unit.** Bound-and-registered → no tip. Bound-and-unregistered → the `daemon project
  add` tip. No daemon config → the setup line. Rename failure → bind still succeeds, and the
  answer says both things.
- `REQ-BIND-SELF-ALIAS`, `REQ-BIND-FOLLOWUP` → `["doc", "impl", "unit"]`.

## C. `daemon project add` stops taking a repo

Operator ruling, and `CONTEXT.md` conformance rather than a preference: `daemon project add
owner/repo` has violated "repo is never a verb argument" since it shipped.

- **T6 — impl.** `add` takes no positional and registers the invoking instance's bound repo;
  a supplied repo is **refused by name** under `REQ-VERB-ARITY-REFUSE` rather than ignored;
  re-adding an already-registered repo is idempotent and says so.
- **T7 — impl.** `remove` keeps its optional explicit ref, defaulting to the bound repo. The
  asymmetry is deliberate and stated in the help: `add` is a claim a shell makes about
  itself, `remove` is cleanup that outlives its subject — an archived, renamed or deleted
  repo can no longer be bound, and a bound-repo-only `remove` would strand a dead entry the
  daemon polls every cycle with no verb able to reach it.
- **T8 — unit.** The refusal names the token. `remove` with a foreign ref still works.
- `REQ-DAEMON-PROJECT-BOUND-REPO` → `["doc", "impl", "unit"]`.

## Docs

`CONTEXT.md` carries the doc-stage evidence for all four requirements in this wave: the ack
rule and the alias rule in the **Alchemy** agent-surface section, the registry-verb arity in
the **Hub Daemon** paragraph that already describes `daemon project`. The **Target** term
landed at plan time (it is language, not behavior, and W34 needs it defined).

`manifest.toml` changes with C — `daemon`'s opts contract loses the repo argument — so the
manifest and `runtime.rs`'s arg table move together or `REQ-VOCAB-PARITY` fails, which is
the check working.

## Gate

`cargo test` green, `./traceable-reqs.exe check` exit 0, **and** the three behaviors driven
against the installed binary rather than the build tree: a write ack naming the repo, a
fresh bind reporting its own rename, and `daemon project add` refusing an argument. Verify
against what shipped or it is not verified — v0.7.0 through v0.7.2 are the standing evidence
for that rule.

## Not in this wave

- **#23** — routed to spt-core, rests in Cut, lives on as `BigscreenVR/spt-bs-releases#2`
  under doyle's ruling with a persistent Watch carrying its outcome back. Seamless update is
  theirs to design; what changes for us when it lands is that the alias-freed-by-teardown
  argument in A decays.
- **#25** — untouched, and it keeps its precedence over #26 (W33).
- No verb edits a Request's or Milestone's **body** from the shell; #38's body was written
  with `gh` because `milestone create` is single-line and there is no `edit` verb. Not
  minted yet — raise it with the Operator before it becomes a habit.
