Commit Graph
6 Commits
Author SHA1 Message Date
shadowdaoandClaude Opus 4.7 5b2bf7d19d fix: address code review on merged feature set
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>
2026-05-17 06:53:32 -07:00
shadowdao 5e57538719 Merge: snippets feature end-to-end (Agent B) 2026-05-17 06:48:48 -07:00
shadowdaoandClaude Opus 4.7 fcc8708dd5 feat(snippets): named reusable artifacts with scope mirroring memories
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>
2026-05-17 06:48:00 -07:00
shadowdao d65e1cb351 feat(memory): allow editing scope and project on memory.update
Extends both the Web UI edit form and the memory.update MCP tool so
existing memories can be reclassified between user-global and
project-attached scopes without delete+rewrite. Schema refines enforce
the user/project consistency invariants; audit log captures from/to
scope and projectKey on transitions.
2026-05-17 06:43:19 -07:00
shadowdaoandClaude Opus 4.7 9a8b504f51 feat: Phase 2 — embeddings + hybrid memory.search
Adds a small embedder sidecar (Xenova/bge-small-en-v1.5, ONNX, CPU-only)
that the web app calls inline on memory.write and memory.update, and on
demand from the new memory.search tool.

memory.search performs three candidate fetches in parallel — pgvector
cosine similarity, Postgres full-text via plainto_tsquery + ts_rank_cd,
and tag-set overlap — then fuses them with Reciprocal Rank Fusion
(k=60). Each result carries its per-source rank so the model can see
*why* a memory surfaced.

The migrator boot step gained an idempotent embedding backfill: any row
with embedding IS NULL is batched (32 at a time) through the embedder
after SQL migrations apply. Safe to run on every boot.

New tool memory.update fixes the missing edit path; centralises the
re-embed-on-content-change rule alongside write.

Stack additions:
- apps/embedder/ — Fastify server, persistent /data/models volume so the
  ~30 MB model only downloads once
- apps/web/lib/embedder.ts — typed HTTP client with batched embed +
  health probe
- packages/schemas — MemoryUpdateInput, MemorySearchInput
- docker-compose — embedder service, healthcheck, app + migrator both
  depend_on it healthy; EMBEDDER_URL promoted to a required env var

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 09:04:44 -07:00
shadowdaoandClaude Opus 4.7 077d0a0825 feat: Phase 1 — Authentik auth, MCP endpoint, persistent memory
End-to-end Phase 1 of shared-memory: a logged-in Authentik user can sign
into the Web UI (/me debug page), and an MCP client with an Authentik-
issued bearer token can call memory.write / memory.list / memory.get /
memory.delete plus project.identify against /api/mcp.

Stack:
- Next.js 15 (App Router) + React 19 + TypeScript, pnpm workspaces
- Drizzle ORM + Postgres 16 + pgvector + pg_trgm
- Auth.js v5 with Authentik provider (Web UI)
- jose + Authentik JWKS for MCP bearer-token validation
- JSON-RPC 2.0 dispatcher implementing the MCP wire protocol over plain
  HTTP POST (hand-rolled to fit Next.js App Router; switches to SSE in a
  later phase if server-initiated events are needed)
- bge-small embeddings sidecar deferred to Phase 2; the schema already
  reserves the vector(384) column + IVFFlat index, FTS via a STORED
  tsvector column, and the visibility enum (private/shared/team) so
  cross-user memory sharing can be added without a future migration

Deployment supports two modes (set in .env, never committed):
- Behind an external reverse proxy (HAProxy / nginx / Cloudflare Tunnel /
  Traefik) — DEFAULT; the app exposes APP_PORT on the host with
  X-Forwarded-* trusted, no in-container TLS
- Built-in TLS via Caddy — opt-in with `docker compose --profile tls up`

Discovery endpoint at /.well-known/oauth-protected-resource (RFC 9728)
points MCP clients at the Authentik authorization server after a 401.

README walks through both Authentik providers (Web UI + MCP resource
server), the audience scope mapping, redirect URIs, and includes a worked
HAProxy config snippet.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 07:04:11 -07:00