CI & Build / Python lint (push) Successful in 4s
CI & Build / Plugin hooks (push) Successful in 7s
CI & Build / TypeScript typecheck (push) Successful in 11s
CI & Build / integration (push) Successful in 18s
CI & Build / Python tests (push) Successful in 48s
CI & Build / Build & push image (push) Successful in 41s
Survey pass 3 (#2250) tabulated capabilities per record kind. Processes came out lowest on every column, and they are the kind with the most authority: build_process_manifest turns each one into a skill file on the operator's machine that auto-surfaces and is followed as written — its own docstring calls it "the most consequential passive surface Scribe has." Three gaps closed. NO PULL TELEMETRY (#2476). get_process recorded nothing, while the auto-inject menu header names get_process as the way to open that kind. Every note is embedded regardless of note_type, so a Process is surfaceable — and the getter the product points at was the one getter that recorded nothing, leaving every Process permanently at zero pulls and looking like dead weight beside kinds that merely had a counter. get_note's own comment already listed processes as a reason to record pulls. The fix for #2245 covered notes, tasks and snippets: it enumerated the kinds someone thought of rather than the kinds that exist. NO DEDUP GATE. create_process had no near-duplicate check and no force flag, while notes, tasks, snippets and rules all have both. It matters more here than elsewhere: two near-identical procedures don't just bloat the corpus, they compete to be followed, and which one wins is decided by a slug collision. NO DELETE. list/create/get/update, no delete — a kind that reads as one you cannot retire. Deletion was always possible via delete_note, since a Process is a note and the trash is kind-agnostic, so this was discoverability rather than capability. delete_process checks note_type before trashing: the tool is reached for by name, and letting it destroy an ordinary note whose id happened to resolve would be a destructive action taken on a mistyped argument. THE GUARD, which is the part that stops a fourth repeat. tests/test_mcp_pull_telemetry.py discovers every get_* MCP tool by AST and requires a record_pulled from any that loads a single note. Not a list of getters — a get_<newkind> added tomorrow is covered the moment it loads a note the way the others do. get_milestone is correctly excluded: it calls list_notes for a milestone's steps, which is a surfacing, not an opening. The loader NAMES are a list, and that residual weakness is pinned against a rename rather than papered over. An earlier draft tried to discover new loaders by return annotation and would have failed on create_note — which also returns a Note. Readers and writers aren't distinguishable by type, so the honest version is a pinned list, a non-empty assertion, and a docstring saying which hole remains. test_register_attaches_four_tools became a derived check of the module's public coroutines, so the next tool added can't be left unregistered. MCP _INSTRUCTIONS updated: product behaviour belongs in the instruction surfaces, not in a rule (rule #119). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs