{"id":49283270,"url":"https://github.com/masondelan/selvedge","last_synced_at":"2026-04-25T20:01:04.613Z","repository":{"id":353184163,"uuid":"1217601896","full_name":"masondelan/selvedge","owner":"masondelan","description":"Change tracking for AI-era codebases. An MCP server that captures why code changed — not just what. Ask selvedge blame users.stripe_customer_id and get the reasoning that evaporated when the AI session ended. The pandas of codebase history.","archived":false,"fork":false,"pushed_at":"2026-04-22T19:28:03.000Z","size":68,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-22T21:24:28.401Z","etag":null,"topics":["ai","change-tracking","claude-code","cli","codebase","developer-tools","devtools","mcp","python","sqlite"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/masondelan.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-22T03:34:11.000Z","updated_at":"2026-04-22T19:28:00.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/masondelan/selvedge","commit_stats":null,"previous_names":["masondelan/selvedge"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/masondelan/selvedge","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/masondelan%2Fselvedge","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/masondelan%2Fselvedge/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/masondelan%2Fselvedge/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/masondelan%2Fselvedge/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/masondelan","download_url":"https://codeload.github.com/masondelan/selvedge/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/masondelan%2Fselvedge/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32274982,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-25T18:29:39.964Z","status":"ssl_error","status_checked_at":"2026-04-25T18:29:32.149Z","response_time":59,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai","change-tracking","claude-code","cli","codebase","developer-tools","devtools","mcp","python","sqlite"],"created_at":"2026-04-25T20:01:03.817Z","updated_at":"2026-04-25T20:01:04.599Z","avatar_url":"https://github.com/masondelan.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Selvedge\n\n[![Tests](https://github.com/masondelan/selvedge/actions/workflows/test.yml/badge.svg)](https://github.com/masondelan/selvedge/actions/workflows/test.yml)\n[![PyPI](https://img.shields.io/pypi/v/selvedge?cacheSeconds=3600)](https://pypi.org/project/selvedge/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)\n\n**Long-term memory for AI-coded codebases.**\nA `git blame` for AI agents — but for the *why*, not just which line which\nmodel touched. Captured live, by the agent, as the change happens.\n\n---\n\nSix months ago, your AI agent added a column called `user_tier_v2`. You don't\nknow why. `git blame` points to a commit from `claude-code` with a generated\nmessage that says \"Update schema.\" The session that made the change is long\ngone — and so is the prompt that produced it.\n\nWith Selvedge, you run this instead:\n\n```bash\n$ selvedge blame user_tier_v2\n\n  user_tier_v2\n  Changed     2025-10-14 09:31:02\n  Agent       claude-code\n  Commit      3e7a991\n  Reasoning   User asked to add a grandfathering flag for legacy free-tier\n              users during the pricing migration. Stores the original tier\n              so we can backfill discounts without touching billing history.\n```\n\nThat reasoning was **captured by the agent in the moment** — written into\nSelvedge from the same context that produced the change. Not inferred from\nthe diff afterward by a second LLM. Not a hand-typed commit message.\n\n---\n\n\u003c!-- DEMO GIF\n     Record a 30–45 second terminal session showing:\n     1. `selvedge status`  →  shows N total events\n     2. `selvedge blame payments.amount`  →  full output with reasoning\n     3. `selvedge diff users --since 30d`  →  table of recent changes\n     4. `selvedge search \"stripe\"`  →  filtered results\n     Use `vhs` (https://github.com/charmbracelet/vhs) or Asciinema.\n     Replace this comment block with: ![Selvedge demo](docs/demo.gif)\n--\u003e\n\n---\n\n## Who Selvedge is for\n\nSelvedge has two audiences. Same tool, same `pip install`, same SQLite\nfile under `.selvedge/`. Different scale of pain.\n\n**Teams running long-term, AI-coded codebases.**\nWhen the project is big enough that you (or someone else) will touch it\nagain in six months, twelve months, three years — but most of it was written\nby an agent whose context evaporated the day each PR shipped. `git blame`\ntells you what changed. Selvedge tells you *why* — even after the agent\nsession, the prompt template, the developer who asked for it, and the model\nversion are all long gone. This is the original use case: production\ncodebases, schema decisions, migrations, dependency changes that need an\naudit trail that survives turnover.\n\n**Solo developers using Claude Code on everyday projects.**\nSide projects, weekend builds, the small internal tool you keep poking at.\nYou don't need enterprise governance — you just need to remember why you (or\nyour agent) did the thing you did yesterday, last week, last sprint. Run\n`selvedge init` once. Add four lines to your `CLAUDE.md`. From then on,\n`selvedge blame` is muscle memory — a way to talk to your past self when\nyour past self was an LLM.\n\nIf you've ever come back to your own AI-built project and thought \"what was\nthis *for* again?\", Selvedge is the missing piece.\n\n---\n\n## The problem\n\nHuman-written code leaks intent everywhere — commit messages, PR descriptions,\ninline comments, the Slack thread that preceded it. AI-written code doesn't.\nThe agent has perfect clarity about why it made each decision, but that\ncontext lives in the prompt and evaporates when the conversation ends.\n\nSix months later, your team is debugging a schema decision with no trail.\n`git blame` tells you *what* changed and *when*. It can't tell you *why*.\n\n**Selvedge captures the why — live, by the agent itself, as the change is\nmade.** The diff is git's job. The why is Selvedge's.\n\n---\n\n## What's new in v0.3.2\n\nAn observability-polish release. No new feature surface — the focus is\nmaking existing functionality discoverable and debuggable, plus locking\nin WAL/`busy_timeout` assumptions across SQLite versions.\n**Drop-in upgrade for anyone on 0.3.1.**\n\n**`selvedge doctor`.** A single health-check that walks the ambient state\nagents typically run into and reports each row PASS / WARN / FAIL / INFO:\nwhich DB path is being resolved (and which precedence step matched —\n`SELVEDGE_DB`, walkup, or global fallback), whether `.selvedge/` exists,\nwhether the schema is at the latest migration version, whether the\npost-commit hook is installed and not silently failing, last `tool_calls`\ntimestamp (proxy for \"is the agent wired up?\"), and whether\n`SELVEDGE_LOG_LEVEL` is set to a recognized value. Exits 1 on any FAIL,\nso `doctor` can be wired into CI. `--json` for machine-readable output.\n\n```bash\n$ selvedge doctor\nSelvedge doctor\n\n  Check                  Status      Detail\n ───────────────────────────────────────────────────────────────────\n  Database path          i INFO      ~/.selvedge/selvedge.db  [via SELVEDGE_DB env var]\n  .selvedge/ directory   ✓ PASS      ~/.selvedge\n  Schema version         ✓ PASS      at v2 (latest)\n  Post-commit hook       ✓ PASS      ~/code/myapp/.git/hooks/post-commit\n  Last hook failure      ✓ PASS      no failures recorded\n  MCP wiring             ✓ PASS      last tool_call at 2026-04-25T14:21:08Z\n  SELVEDGE_LOG_LEVEL     i INFO      unset (defaults to WARNING)\n\nAll checks passed.\n```\n\n**Post-commit hook no longer fails silently.** The previous hook silently\ndied when `selvedge` wasn't on the PATH that git launched (a common\nsymptom under macOS GUI git clients with stripped PATHs). The new hook\nappends a single line to `.selvedge/hook.log` on failure, and both\n`selvedge status` and `selvedge doctor` surface the most recent failure.\nOld hooks keep working — re-running `selvedge install-hook` upgrades to\nthe new wrapper.\n\n**`selvedge stats` upgrades.** The coverage report now breaks calls\ndown per-agent so you can catch the case where claude-code is logging\nchanges but cursor is only querying history. A separate\n`missing_reasoning` count surfaces stored events whose reasoning fails\nthe quality validator — empty, too short, or a generic placeholder the\nagent shipped despite the warning.\n\n**Pinned-SQLite CI matrix.** A new CI job builds SQLite 3.37.2, 3.42.0,\nand 3.45.3 from source and runs the suite against each via `LD_PRELOAD`,\nlocking in WAL + `busy_timeout` behavior across the SQLite versions\nselvedge users are most likely to be on. The Python matrix also gains\n3.13.\n\n---\n\n## What's new in v0.3.1\n\nA hardening release — no new feature surface, but Selvedge is now safe to\nrun in long-lived agent pools that hammer the database from multiple threads.\n**Drop-in upgrade for anyone on 0.3.0.**\n\n**Concurrent writes no longer blow up.** Every storage write is wrapped in\na `database is locked` retry with exponential backoff, on top of WAL mode\nand a 5-second `PRAGMA busy_timeout`. The new `tests/test_concurrency.py`\nspawns 8 threads writing 25 events each and asserts all 200 land — that\ntest reliably failed before this release.\n\n**Real schema versioning.** The previous `try: ALTER TABLE ... except: pass`\npattern has been replaced with an explicit `schema_migrations` table that\nrecords every applied migration with version, name, and timestamp. Partial\nfailures roll back atomically. Pre-versioning databases are bootstrapped\nwithout re-running DDL.\n\n**Structured logging.** Set `SELVEDGE_LOG_LEVEL=DEBUG` (or `INFO`,\n`WARNING`, `ERROR`) to see what's happening inside the storage and\nmigration layers. All library modules log under the `selvedge.*`\nnamespace — entry points configure a stderr handler at startup.\n\n**Public API surface.** Library users can now import the supported\nsurface from the top-level package:\n\n```python\nfrom selvedge import (\n    SelvedgeStorage, ChangeEvent, ChangeType, EntityType,\n    get_db_path, parse_time_string, check_reasoning_quality,\n)\n```\n\nThe frozen surface is locked in by `tests/test_public_api.py` — accidental\nremovals fail CI.\n\n**MCP protocol smoke tests.** A new test suite boots the real\n`selvedge-server` subprocess and round-trips every tool over the actual\nJSON-RPC stdio transport. Catches contract drift the in-process tests miss.\n\n**CI gates.** `ruff`, `mypy`, and an 85% coverage floor are now enforced\non every PR. Current coverage is 92%.\n\n**One sneaky regex fix.** The reasoning-quality validator's `^fixed?$`\npattern was supposed to match \"fix\" and \"fixed\" but actually matched\n\"fixe\"/\"fixed\" — the `?` only made the trailing `d` optional. Same bug\nin the `add`, `remove`, `update`, `change`, and `see ...` patterns.\nRewritten as `^fix(?:ed)?$` etc. Reasoning placeholders that slipped\nthrough before now get flagged.\n\nSee [`CHANGELOG.md`](CHANGELOG.md) for the full list and reasoning.\n\n---\n\n## How Selvedge compares\n\nThere's a fast-growing \"git blame for AI agents\" category. Here's where\nSelvedge fits — and where it deliberately doesn't.\n\n|  | Reasoning source | Granularity | Mechanism | Grouping | Storage |\n|---|---|---|---|---|---|\n| **Selvedge** | **Captured live**, by the agent in the same context that produced the change | **Entity** — DB column, table, env var, dep, API route, function | **MCP server** — agent calls it as work happens | **Changesets** — named feature/task slugs across many entities | SQLite, zero deps |\n| AgentDiff | **Inferred post-hoc** by Claude Haiku from the diff at session end | Line | Git pre/post-commit hook | None | JSONL on disk |\n| Origin | Captured at commit time | Line | Git hook | None | Local |\n| Git AI | Attribution metadata | Line | Git hook + Agent Trace alliance | None | Git notes |\n| BlamePrompt | Prompt-only | Line | Git hook | None | Local |\n\n**Why \"captured live\" matters.** AgentDiff and Origin generate reasoning\n*after* the change is made, by feeding the diff back to a second LLM call.\nSelvedge's reasoning is the agent's own intent, written from the same\ncontext window that produced the change — no inference, no hallucinated\nexplanations, and an empty `reasoning` field is itself a useful signal\n(the agent didn't have one).\n\n**Why \"entity-level\" matters.** Most tools attribute *lines*. Selvedge\nattributes *things you actually search for*: `users.email`,\n`env/STRIPE_SECRET_KEY`, `api/v1/checkout`, `deps/stripe`. The first\nquestion after `git blame` is usually *\"what's the history of this column\"*,\nnot *\"what's the history of lines 40–48 of users.py\"*.\n\n**Why \"changesets\" matter.** A Stripe billing rollout touches the `users`\ntable, two new env vars, three new API routes, one dependency, and four\nfunctions across the codebase. Tag every event with `changeset:add-stripe-billing`\nand you can pull the entire scope back later — even if the original PR was\nbroken into eight smaller ones over a month.\n\n**Selvedge ↔ Agent Trace.** [Agent Trace](https://github.com/cursor/agent-trace)\n(Cursor + Cognition AI, RFC Jan 2026, backed by Cloudflare, Vercel, Google\nJules, Amp, OpenCode, and git-ai) is an emerging *open standard* for AI\ncode attribution traces. Selvedge isn't a competitor to it — it's a\ncompatible producer. The design for `selvedge export --format agent-trace`\nis at [`docs/agent-trace-interop.md`](docs/agent-trace-interop.md). Agent\nTrace is the wire format. Selvedge is the live capture + query layer that\nemits it.\n\n---\n\n## Install\n\n```bash\npip install selvedge\n```\n\n## Quickstart\n\n**1. Initialize in your project**\n\n```bash\ncd your-project\nselvedge init\n```\n\n**2. Add to your Claude Code config**\n\n`~/.claude/config.json`:\n```json\n{\n  \"mcpServers\": {\n    \"selvedge\": {\n      \"command\": \"selvedge-server\"\n    }\n  }\n}\n```\n\n**3. Tell your agent to use it**\n\nAdd to your project's `CLAUDE.md`:\n```\nYou have access to Selvedge for change tracking.\nCall selvedge.log_change immediately after adding, modifying, or removing\nany DB column, table, function, API endpoint, dependency, or env variable.\nSet `reasoning` to the user's original request or the problem being solved.\nSet `agent` to \"claude-code\".\nBefore modifying an entity, call selvedge.blame to understand its history.\n```\n\n**4. Query your history**\n\n```bash\nselvedge status                        # recent activity + missing-commit count\nselvedge diff users                    # all changes to the users table\nselvedge diff users.email              # changes to a specific column\nselvedge blame payments.amount         # what changed last and why\nselvedge history --since 30d           # last 30 days of changes\nselvedge history --since 15m           # last 15 minutes ('m' = minutes)\nselvedge changeset add-stripe-billing  # all events for a feature/task\nselvedge search \"stripe\"               # full-text search\nselvedge stats                         # log_change coverage report\nselvedge install-hook                  # auto-link commits to events\nselvedge import migrations/            # backfill from migration files\nselvedge export --format csv           # dump history to CSV\n```\n\n---\n\n## How it works\n\nSelvedge runs as an MCP server. AI agents in tools like Claude Code call\nSelvedge's tools as they work — logging structured change events to a local\nSQLite database.\n\nEach event records:\n- **What** changed (entity path, change type, diff)\n- **When** (timestamp)\n- **Who** (agent, session ID)\n- **Why** (reasoning — captured from the agent's context in the moment)\n- **Where** (git commit, project)\n\nThe diff is git's job. The *why* is Selvedge's.\n\n---\n\n## Entity path conventions\n\n```\nusers.email           DB column (table.column)\nusers                 DB table\nsrc/auth.py::login    Function in a file (path::symbol)\nsrc/auth.py           File\napi/v1/users          API route\ndeps/stripe           Dependency\nenv/STRIPE_SECRET_KEY Environment variable\n```\n\nPrefix queries work everywhere: `users` returns `users`, `users.email`,\n`users.created_at`, and any other entity under the `users.` namespace.\n\n---\n\n## MCP tools\n\nWhen connected as an MCP server, Selvedge exposes:\n\n| Tool | Description |\n|------|-------------|\n| `log_change` | Record a change event with entity, diff, and reasoning |\n| `diff` | History for an entity or entity prefix |\n| `blame` | Most recent change + context for an exact entity |\n| `history` | Filtered history across all entities |\n| `changeset` | All events grouped under a named feature/task slug |\n| `search` | Full-text search across all events |\n\n---\n\n## CLI reference\n\n```\nselvedge init [--path PATH]               Initialize in project\nselvedge status                           Recent activity summary\nselvedge diff ENTITY [--limit N]          Change history for entity\nselvedge blame ENTITY                     Most recent change + context\nselvedge history [--since SINCE]          Browse all history\n              [--entity ENTITY]\n              [--project PROJECT]\n              [--changeset CS]\n              [--summarize]\n              [--limit N]\nselvedge changeset [CHANGESET_ID]         Show events in a changeset\n                  [--list]                or list all changesets\n                  [--project NAME]\n                  [--since SINCE]\nselvedge search QUERY [--limit N]         Full-text search\nselvedge stats [--since SINCE]            Tool call coverage report (per-tool, per-agent)\nselvedge doctor [--json]                  Health check: DB path, schema, hook, MCP wiring\nselvedge install-hook [--path PATH]       Install git post-commit hook\n                     [--window MIN]       (default 60 minutes)\nselvedge backfill-commit --hash HASH      Backfill git_commit on recent events\n                        [--window MIN]    (default 60 minutes)\nselvedge import PATH                      Import migration files (SQL / Alembic)\n              [--format auto|sql|alembic]\n              [--project NAME]\n              [--dry-run]\nselvedge export [--format json|csv]       Export history to JSON or CSV\n              [--since SINCE]\n              [--entity ENTITY]\n              [--output FILE]\nselvedge log ENTITY CHANGE_TYPE           Manually log a change\n             [--diff TEXT]                CHANGE_TYPE: add, remove, modify,\n             [--reasoning TEXT]           rename, retype, create, delete,\n             [--agent NAME]               index_add, index_remove, migrate\n             [--commit HASH]\n             [--project NAME]\n             [--changeset CS]\n```\n\nAll read commands support `--json` for machine-readable output.\n\n**Relative time in `--since`:**\n- `15m` → last 15 minutes (`m` = minutes)\n- `24h` → last 24 hours\n- `7d` → last 7 days\n- `5mo` → last 5 months (`mo` or `mon` = months)\n- `1y` → last year\n\nUnparseable inputs (e.g. `--since yesterday`) exit with a clear error\nrather than silently returning empty results. ISO 8601 timestamps\nare also accepted and normalized to UTC.\n\n---\n\n## Configuration\n\n| Method | Format | Example |\n|--------|--------|---------|\n| Env var | `SELVEDGE_DB=/path/to/db` | Per-session override |\n| Project init | `selvedge init` | Creates `.selvedge/selvedge.db` in CWD |\n| Global fallback | `~/.selvedge/selvedge.db` | Used if no project DB found |\n\n---\n\n## Coverage checking\n\nWondering how often your agent actually calls `log_change`? Two ways to check:\n\n```bash\n# Quick summary in the terminal\nselvedge stats\n\n# Cross-reference against git commits\npython scripts/coverage_check.py --since 30d\n```\n\nThe coverage script compares your git log against Selvedge events and shows\nwhich commits have associated change events. Low coverage usually means the\nsystem prompt needs strengthening — see `docs/fallbacks.md` for guidance.\n\n---\n\n## Contributing\n\n```bash\ngit clone https://github.com/masondelan/selvedge\ncd selvedge\npip install -e \".[dev]\"\npytest\n```\n\nSee `CLAUDE.md` for architecture details and the phase roadmap.\n\n---\n\n## License\n\nMIT — see [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmasondelan%2Fselvedge","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmasondelan%2Fselvedge","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmasondelan%2Fselvedge/lists"}