Four defects from the 2026-07-25 review of the recall (#227) and merge (#231)
milestones. The theme: a snippet could be recorded but not fully corrected, and
the agent and web surfaces had drifted apart.
- #2076 language was mis-derived from the first caller tag, so a snippet created
with tags and no language read that tag back as its language — corrupting the
tag set and the code fence on the next update. Only the FIRST tag can carry
the language, since compose_tags emits [language, "snippet", *caller].
- #2077 MCP update_snippet mapped "" to "unchanged", so no field could ever be
cleared and no snippet detached from its project. Now an omitted field is left
alone, an empty string clears, and project_id follows the -1 = detach
convention. A service-level UNSET sentinel keeps None available as the clear.
- #2078 surface parity: adds delete_snippet (MCP had none, so a wrong snippet
could not be retired by the agent that recorded it), locations on MCP create
and update, system_ids through the REST routes and the editor, and the
near-duplicate gate on REST create with a "record it anyway" escape.
- #2079 project scoping: list_snippets takes project_id through the service, the
MCP tool and the REST route, defaulting to every project — reaching across
projects is the point when the helper you need was written elsewhere.
Sharing the list across owners is deliberately NOT in here: query_knowledge is
shared with the Knowledge browse surface, so widening it changes behaviour well
beyond snippets. Left open on #2079 for a scope decision.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RLwAaV4DQEmVyn496HnEvt
Step 2 of the snippet-merge milestone (#231). The dedup gate only PREVENTS
new near-duplicates; merge is the CURE for the ones already scattered.
- services/snippets.py: merge_snippets(user_id, target_id, source_ids) —
keep the target's scalar fields (name/when_to_use/signature/language/
code), union the sources' locations + extra tags onto it (so the survivor
carries every call site as a location), trash the sources (recoverable),
re-embed the survivor. Pure merge_snippet_fields() factored out for unit
testing. Returns (survivor_note, merged_ids).
- mcp/tools/snippets.py: merge_snippets(target_id, source_ids) tool (5th),
and a create_snippet dedup-path nudge toward merge over a forced copy.
- routes/snippets.py: POST /api/snippets/<id>/merge {source_ids} — share-
aware (can_write target + every source, rule #78) with a same-owner guard
(cross-owner merge is out of scope).
- plugin reusing-code skill + MCP _INSTRUCTIONS: point found-duplicates at
merge as the cure (rule #119 surfaces, not a Scribe rule). plugin.json
0.1.13 -> 0.1.14 in the same change (the #1040 marketplace-ship lesson).
- Tests: pure merge-helper union/dedup; MCP tool (requires a source,
survivor+merged_ids, not-found); route handler + 5-tool registration.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pa2EsuB54BuWQ8GfJq9c7t
Record reusable functions/components once so they surface via the existing
semantic search + title-first auto-inject, instead of re-solving as one-offs.
A snippet is a Note with note_type='snippet' (no schema change): note_type is
free-text, and semantic_search_notes never filters by type, so snippets join
the recall/auto-inject pool the moment they're embedded. Structured fields
(name/language/signature/location/when_to_use/code) are stored via a body
convention — title = "name — when to use" (what auto-inject surfaces), language
+ "snippet" as tags, templated markdown body — keeping storage swappable later
without changing the tool/UI contract.
- services/snippets.py: compose/parse helpers + create/get/list/update wrappers
over notes_svc (dedup + System association reused).
- mcp/tools/snippets.py: list_snippets / create_snippet / get_snippet /
update_snippet, registered in tools/__init__.py.
- unit tests for the serialize/parse round-trip and the MCP tool surface.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pa2EsuB54BuWQ8GfJq9c7t