#231's premise was unifying reusable things already scattered as one-offs. The create gate PREVENTS a new duplicate and merge_snippets CURES one you point it at, but nothing FOUND the duplicates already in the record — someone had to notice them by hand, which is the exact failure the Drafter exists to remove. One indexed self-join over note_embeddings, not an N² Python scan: pgvector's cosine distance is the same operator semantic search uses, so a similarity floor is a distance ceiling and the work stays in Postgres. `left.note_id < right.note_id` yields each unordered pair once and drops the self-pair that would otherwise dominate the ranking. Pairs are collapsed into merge SETS by connected components. Transitive on purpose: A~B plus B~C puts all three together even when A and C don't directly clear the bar, which is what merge actually does (it folds every source into one survivor). The cost is that a chain of mild resemblances can rope in a member that isn't really alike — so the UI presents a set as a proposal, shows the members, and never merges without a confirm. Two scope decisions worth naming: - OWN snippets only. merge_snippets requires one owner across the set, so surfacing someone else's would propose a merge that cannot be performed. The report is bounded by what the operator can act on, not what they can see. - Threshold defaults to 0.82, LOOSER than the write gate's 0.90, and is a setting rather than a constant (rule #25). The gate blocks a create and has to be unforgiving of noise; this only suggests a merge under review, so it must reach further or it would never surface the pairs the gate already let through — which are precisely the ones that accumulated. Fixes a real bug in the merge flow while wiring the UI: selectedList filtered the selection against the CURRENT PAGE, and doMerge derives its source ids from that list. A corpus-wide suggested group with off-page members would have rendered incomplete and silently merged only the visible subset. A group under review is now the authority for that list. Refs #2088 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs
Fabled Scribe
A self-hosted work system-of-record for software projects, built to be driven by Claude Code. Notes, tasks, issues, projects, milestones, rules, and stored processes — reachable from Claude via a built-in MCP endpoint and a bundled Claude Code plugin, with a clean web UI for humans. No in-app LLM; Claude is the sole assistant.
Features
Notes and tasks with a Markdown editor, sub-tasks, milestones, issues, and kanban project workspaces. Stored processes, an engineering rulebook system, and semantic search with proactive knowledge-injection into Claude's context. A knowledge graph, per-user/group sharing, and a built-in MCP server (/mcp) plus a bundled Claude Code plugin so Claude can record and recall your work directly.
Quick Start
Prerequisites: Docker and Docker Compose. No GPU or local model needed — Claude is the sole assistant, reached over MCP.
Download docker-compose.quickstart.yml from this repo, then:
# Optional but recommended — set a secret key
export SECRET_KEY=your-random-secret-here
docker compose -f docker-compose.quickstart.yml up -d
Open http://localhost:5000. The first user to register becomes admin. To connect Claude, create an API key under Settings → API Keys and install the Claude Code plugin — see API Keys & MCP.
Development: To build from source, see Development.
Documentation
| Doc | Contents |
|---|---|
| Architecture | Stack, design decisions, data models, key services |
| Configuration | Environment variables, Docker Compose, production setup, security |
| Features | Detailed feature breakdown and keyboard shortcuts |
| Development | Dev workflow, CI/CD, migrations, release process |
| API Keys & MCP | API key management and Fable MCP install guide |
| SSO / OAuth | OIDC setup for Authentik, Keycloak, and other providers |
| API Reference | All REST API endpoints |
License
This project is privately maintained.