Adds project-level sharing via the new project_shares table plus the
infrastructure that makes multi-user editing safe and visible.
Authorization (lib/access.ts):
- getAccessibleProjects / getProjectAccess centralise the predicate
used by every read and write path.
- readableProjectIds / writableProjectIds drive listing-style queries.
- Web UI Server Actions and pages source group memberships from the
user_groups table so authorization works without depending on
Agent A's session callback shape.
Optimistic locking:
- memories + snippets gain version + last_edited_by columns. Every
UPDATE bumps version and stamps the editor; UPDATE WHERE clauses
require the caller's pre-fetched version, surfacing a clear
"refresh and try again" error on lost-write races rather than
silently clobbering.
- MemoryUpdateInput / SnippetPutInput accept an optional version
token.
MCP tools:
- memory.write / .update / .delete / .get / .list / .search,
snippet.put / .get / .list / .delete now respect shared-project
access (read = owner | any share, write = owner | rw share).
- project, defaults to ctx.defaultProjectKey from the X-Project-Key
header (populated by the MCP route — Agent A's wiring).
- project.identify returns shared projects you have access to and
prefers an owned project on key collision, audit-logging the
collision so an operator can debug it.
- Tool descriptions for memory.update, memory.write, snippet.put,
and project.identify updated with the co-edit / shared-project
notes.
Web UI:
- Project detail page: ownership badge, shared-with-N-groups badge,
owner-only "Manage sharing" section (add/flip/remove shares via
lib/share-actions.ts). Add-share is constrained to groups the
granter is already in.
- "Shared" chips on memory cards in /memories and /dashboard.
- "Last edited by ..." on memory + snippet detail pages, shown only
when the last editor isn't the row's original author so the chip
stays informative.
- Read-only viewers (ro shares) lose Edit/Delete affordances on
memories and snippets.
Migration 0004_project_shares.sql adds project_shares + the two new
columns on memories and snippets; it depends on Agent A's
0003_groups.sql for the groups, user_groups, and memory_access enum.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Five high-confidence findings from the post-merge reviewer pass:
1. memory.update MCP tool description claimed "Project is upserted if it
doesn't exist", but the handler used resolveProjectId and would error.
Matched the description to the actual behavior (call project.identify
first) — keeps parity with memory.write.
2. updateMemoryAction's UPDATE statement was missing the userId guard.
The preceding scoped SELECT made it not exploitable in practice, but
it diverged from deleteMemoryAction's pattern. Added the guard for
defense in depth.
3. putSnippet's UPDATE statement had the same missing userId guard —
fixed the same way.
4. MemoryUpdateInput's refine for scope='user' accepted both
project=undefined AND project=""; the snippets refine only accepted
undefined. Tightened MemoryUpdateInput to require undefined, matching
the snippets rule. Web actions already coerce "" → undefined before
parsing, so no caller is affected.
5. 0002_snippets_scope.sql created two indexes unconditionally —
replaced with CREATE INDEX IF NOT EXISTS so re-runs after a
drizzle-kit push won't trip.
Also adds .claude/ to .gitignore so worktree directories from
multi-agent builds aren't accidentally committed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Snippets are exact-name-keyed templates (PR formats, checklists, style
rules) — prescriptive artifacts the user wants applied consistently.
Distinct from memories, which are searched descriptive prose.
- Migration 0002 adds scope/project_id/deleted_at to snippets, partial
unique indexes per scope, and a scope/project CHECK constraint
mirroring memories_scope_project_chk.
- New @shared-memory/schemas: SnippetName/Put/Get/List/Delete inputs
with shared scope-project refinement.
- lib/snippets.ts: get/put/list/softDelete helpers used by both the
Web Server Actions and the MCP tool handlers.
- Four new MCP tools (snippet.put/get/list/delete) with directive
descriptions contrasting against memory.* (exact-name lookup vs
search; templates vs facts).
- /snippets pages: list, new, [name] with edit + delete and a
scope-picker when a name lives in multiple scopes.
- Nav: Snippets link between Memories and Projects.
Design note: when a name exists in both user and project scope,
snippet.get without an explicit scope prefers project (if project key
given) then falls back to user. The detail page shows a picker when
the name is ambiguous and no scope query param was supplied.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>