Skip to main content
Glama

AI가 작성한 코드베이스를 위한 장기 기억 — 이미 시도했고 기각된 것까지 포함해서.

라인 속성(Line attribution)은 누가 무엇을 작성했는지 알려줍니다. Selvedge는 에이전트에게 다음에 작성하지 말아야 할 것을 알려줍니다: 이 코드베이스가 이미 시도했고, 되돌렸으며, 그 이유까지. AI 에이전트를 위한 git blame — 어떤 모델이 어떤 라인을 건드렸는지가 아니라 왜 그랬는지를 위한 도구입니다. 변경이 일어나는 순간 에이전트가 직접 실시간으로 포착하므로, 이후의 어떤 작업도 그것을 추측할 필요가 없습니다.

Selvedge는 로컬 MCP 서버입니다. AI 코딩 에이전트(Claude Code, Cursor, Copilot)는 작업하면서 이를 호출해 추론과 함께 구조화된 변경 이벤트를 기록합니다. 데이터는 코드 옆의 .selvedge/ 디렉토리 아래 SQLite 파일에 안전하게 보관됩니다.

기본은 로컬 우선, 팀 서버는 선택, LLM은 항상 제로.


여섯 달 전, 당신의 AI 에이전트는 user_tier_v2라는 컬럼을 추가했습니다. 당신은 그 이유를 모릅니다. git blame은 "Update schema."라는 생성된 메시지가 담긴 claude-code의 커밋을 가리킵니다. 그 변경을 만든 세션은 이미 사라졌고 — 그 변경을 만들어낸 프롬프트도 마찬가지입니다.

Selvedge를 사용하면 대신 이렇게 실행합니다:

$ selvedge blame user_tier_v2

  user_tier_v2
  Changed     2025-10-14 09:31:02
  Agent       claude-code
  Commit      3e7a991
  Reasoning   User asked to add a grandfathering flag for legacy free-tier
              users during the pricing migration. Stores the original tier
              so we can backfill discounts without touching billing history.

그 추론은 에이전트가 그 순간에 포착한 것입니다 — 변경을 만들어낸 바로 그 컨텍스트에서 Selvedge에 기록된 것입니다. 나중에 두 번째 LLM이 diff에서 유추한 것이 아닙니다. 손으로 입력한 커밋 메시지도 아닙니다.



Selvedge는 누구를 위한 도구인가

Selvedge에는 두 가지 사용자가 있습니다. 같은 도구, 같은 pip install, .selvedge/ 아래의 같은 SQLite 파일. 고통의 규모만 다릅니다.

장기 운영되는 AI 코딩 코드베이스를 가진 팀. 프로젝트가 충분히 커서 당신(또는 다른 누군가)이 6개월 후, 12개월 후, 3년 후에 다시 만지게 될 때 — 하지만 대부분이 각 PR이 배송된 날 컨텍스트가 증발해버린 에이전트에 의해 작성된 경우. git blame은 무엇이 변경되었는지 알려줍니다. Selvedge는 왜인지 알려줍니다 — 에이전트 세션, 프롬프트 템플릿, 그것을 요청한 개발자, 모델 버전이 모두 사라진 후에도. 이것이 원래의 사용 사례입니다: 프로덕션 코드베이스, 스키마 결정, 마이그레이션, 인력 교체를 견디는 감사 추적이 필요한 의존성 변경.

일상적인 프로젝트에서 Claude Code를 사용하는 개인 개발자. 사이드 프로젝트, 주말 빌드, 계속 손보는 작은 내부 도구. 엔터프라이즈 거버넌스는 필요 없습니다 — 어제, 지난주, 지난 스프린트에 당신(또는 당신의 에이전트)이 왜 그런 일을 했는지 기억하기만 하면 됩니다. selvedge init을 한 번 실행하세요. CLAUDE.md에 네 줄을 추가하세요. 그 후로 selvedge blame은 근육 기억이 됩니다 — 과거의 자신이 LLM이었을 때, 그 과거의 자신과 대화하는 방법입니다.

당신이 직접 만든 AI 기반 프로젝트로 돌아와서 "이게 뭐 하려고 만든 거였지?"라고 생각해본 적이 있다면, Selvedge가 바로 그 빠진 조각입니다.


Related MCP server: claude-engram

문제

인간이 작성한 코드는 의도를 곳곳에 새어 나오게 합니다 — 커밋 메시지, PR 설명, 인라인 주석, 그 앞에 있던 Slack 스레드. AI가 작성한 코드는 그렇지 않습니다. 에이전트는 각 결정을 내린 이유에 대해 완벽한 명확성을 가지고 있지만, 그 컨텍스트는 프롬프트 안에 살다가 대화가 끝나면 증발합니다.

여섯 달 후, 당신의 팀은 흔적이 없는 스키마 결정을 디버깅하고 있습니다. git blame은 무엇이 언제 변경되었는지 알려줍니다. 왜인지는 알려줄 수 없습니다.

Selvedge는 왜를 포착합니다 — 변경이 이루어지는 순간, 에이전트 자신이 직접 실시간으로. diff는 git의 일입니다. 왜는 Selvedge의 일입니다.


v0.3.10의 새로운 기능

메모리가 에이전트에게 전달되고, 저장소에 다이얼이 생깁니다. 두 가지 테마가 함께 출시되었습니다. 설정 부분이 나머지가 설정을 읽는 데 필요했기 때문입니다.

전달(Delivery). Selvedge는 이미 되돌려진 엔티티의 재편집을 차단했습니다. 부족했던 것은 거부할 것이 없을 때의 전달이었습니다. 두 개의 새로운 훅:

  • SessionStart는 세션이 시작될 때 간결한 요약을 주입합니다 — 재검토가 필요한 결정, 시도했다가 되돌려진 엔티티, 최근 변경 세트.

  • PreCompact는 컨텍스트 압축이 이 세션의 추론을 파괴하기 직전에 실행되며, 편집했지만 기록하지 않은 감시 중인 엔티티를 명명합니다.

둘 다 말할 것이 없을 때는 조용하고, 크기가 제한되며, 읽기 전용이고, 템플릿 기반입니다. 둘 다 어떤 것도 차단할 수 없습니다 — PreCompact는 훅 API가 제공하는 거부 권한을 의도적으로 거절합니다. 이것은 측정된 실패 모드에 대한 답입니다: 2026년 논문 두 편이 풀 모델 메모리 도구가 완전히 사용되지 않는 것을 기록했습니다(사전 시드된 저장소에 대해 114턴 동안 자발적 메모리 작업 0회) 반면 결정적 주입은 매번 성공했습니다.

selvedge export --format markdown 은 저장소를 검토 가능한 요약으로 렌더링하여 옆에 커밋할 수 있게 하므로, 포착된 의도가 바이너리 안에 숨는 대신 풀 리퀘스트에 나타납니다. 결정적입니다 — 새 이벤트 없이 재생성하면 0줄 diff입니다.

설정(Config). .selvedge/config.toml이 이제 일급 시민이며, selvedge doctor가 설정별로 출력하는 표준 우선순위 체인이 있습니다. 제공하는 것:

  • selvedge prune --include-events — 포착된 추론을 삭제할 수 있는 첫 번째 경로이므로, 확인과 SELVEDGE_DESTRUCTIVE=1 둘 다 필요합니다. 둘 중 하나만으로는 충분하지 않습니다. cron 항목의 --yes는 프롬프트를 무력화하고 셸 프로필은 환경 변수를 무력화하기 때문입니다. 이벤트 보존은 기본적으로 영구입니다.

  • 이벤트 크기 상한 (diff_bytes, reasoning_bytes) — 텍스트의 마커, 쓰기 시점의 경고, selvedge stats의 카운트로 크게 알립니다.

  • 비밀 패턴 경고 — log_change 시점에, redaction_patterns로 확장 가능하며, 이미 저장된 것을 스캔하는 doctor 행도 포함합니다. 경고만 하고 거부하지는 않습니다.

또한: 검토 이슈 5건이 해결되었습니다. 강제 훅의 허용 경로가 40% 빨라졌고 (게이트 호출당 33.6 ms → 20.1 ms), SELVEDGE_HOOK_DISABLE=1이 마침내 문서화된 대로 건너뛰기로 되어 있던 import 이전에 단락됩니다; log_change는 이름 변경과 대체 시 revisit_after / constraint / stale_when을 더 이상 버리지 않습니다; CLI의 --json과 MCP 도구가 이제 동일한 구조를 반환합니다; Docker 이미지가 더 이상 유지관리자의 자체 데이터베이스를 포함하지 않습니다. 테스트 826 → 984.


v0.3.9.3의 새로운 기능

깨진 설치를 수정하고, 전체 코드 품질 패스를 완료합니다. mcp 2.0.0(2026-07-28 출시)이 mcp.server.fastmcp를 제거했는데, Selvedge는 상한 없이 mcp>=1.0.0을 선언했습니다 — 그래서 그 날짜 이후의 어떤 pip install selvedge도 2.0.0을 가져왔고 selvedge-server가 import에서 실패했습니다. 이 릴리스는 의존성을 고정합니다. 서버가 시작을 멈췄다면, 이것이 이유입니다 — 업그레이드하세요.

코드베이스에 9번의 독립적인 패스를 적용한 검토와 함께 출시되었으며, 각 발견 사항을 조치하기 전에 반증하려고 시도했습니다. 17건의 확인된 결함이 수정되었습니다. 실제로 눈치챘을 만한 것들:

  • 강제 훅이 차단해서는 안 되는 것을 차단하던 문제. 추적된 파일 읽기 — cat, git diff, pytest, ruff check — 가 차단되었고, 오류 메시지가 실행하라고 알려준 해결 방법이 같은 게이트에 의해 차단되어 CLI에서 빠져나갈 방법이 없었습니다. 두 가지 경로가 더 같은 잘못된 차단을 유발했습니다: 주석 처리된 SQL 라인이 실제 삭제로 간주되었고, "revert"라는 단어만 포함된 커밋 메시지가 그 커밋이 건드린 모든 파일을 되돌려진 것으로 표시했습니다.

  • 조회가 대규모에서 빨라졌습니다. 주요 엔티티 읽기가 모든 행을 스캔하고 있었습니다 — 100k 이벤트에서 7.4 ms → 0.35 ms로 측정되었고, 훅은 대규모 저장소에서 수 초가 걸리던 문제가 있었습니다.

  • selvedge setup이 더 이상 CLAUDE.md의 일부를 삭제할 수 없고, 중단된 백업이 더 이상 마지막으로 유효한 백업을 파괴할 수 없으며, 두 개의 Selvedge 프로세스가 실행 중일 때 업그레이드해도 데이터베이스 손상처럼 보이는 오류로 더 이상 충돌하지 않습니다.

테스트는 739 → 826으로 증가했습니다. 스키마 변경도 도구 표면 변경도 없으므로, 0.3.9.x 사용자는 그대로 교체 가능합니다.


Selvedge가 위치하는 곳

AI 에이전트는 작업하면서 Selvedge를 호출합니다. Selvedge는 왜를 내구성 있고 쿼리 가능한 저장소에 포착하고 다시 내보냅니다 — Agent Trace 레코드로는 교차 도구 독자를 위해, Sentry/Datadog 스택 트레이스에 연결되는 관측성 메타데이터로는, SOC 2 및 EU AI Act 감사를 위한 규정 준수 산출물로.

Selvedge는 git(라인 수준의 무엇/언제), PR 검토 도구(검토 시점의 품질), 에이전트 관측성(LLM 호출 추적), 또는 일반적인 코드 호스트 AI 기능을 대체하지 않습니다. 그것들 사이에 위치합니다 — 다른 모든 것이 참조하는 출처-일급-시민 계층입니다.


Selvedge 비교

"AI 에이전트를 위한 git blame" 카테고리가 빠르게 성장하고 있습니다. Selvedge가 어디에 맞는지 — 그리고 의도적으로 어디에 맞지 않는지입니다.

Rejected paths

Reasoning source

Granularity

Mechanism

Grouping

Storage

Selvedge

쿼리 가능 — prior_attempts는 시도 → 되돌림 → 재시도 경로를 반환

변경 시점에 실시간 캡처 — 변경을 만든 동일한 컨텍스트에서 에이전트가 기록

엔티티 — DB 컬럼, 테이블, 환경 변수, 의존성, API 라우트, 함수

MCP 서버 — 작업이 발생할 때 에이전트가 호출

체인지셋 — 여러 엔티티에 걸친 명명된 기능/태스크 슬러그

SQLite, 제로 의존성

OpenLore

제거됨 — rejected는 비활성 상태로, 각 결정 동기화 후 쿼리 가능한 저장소에서 삭제됨 (주석은 동기화된 스펙 마크다운에 남음)

파생됨 — 코드 상태의 tree-sitter 정적 분석 + 커밋 게이트 결정 노트

AST 노드 (18개 언어 + 12개 IaC)

MCP 서버 — 일회성 인덱스 + 커밋 시 인증서

호출 그래프 엣지

.openlore/의 SQLite 그래프

AgentDiff (sunilmallya)

없음

세션 종료 시 diff에서 Claude Haiku가 사후 추론

라인

Claude Code 라이프사이클 훅 → 로컬 데몬

세션/태스크

디스크의 JSONL

AgentDiff (codeprakhar25)

없음

ed25519 서명된 크로스 에이전트 출처

라인

에이전트별 편집기 훅 + git 훅 (커밋 시 서명)

없음

git refs의 서명된 트레이스

Origin

없음 — rework는 사후에 근거 없이 되돌린 AI 코드에 플래그를 지정

턴마다 실시간 캡처된 프롬프트 영수증

라인

에이전트 라이프사이클 훅 + git post-commit 훅

없음

Git notes + sessions 브랜치

Git AI

없음

귀속 메타데이터

라인

에이전트 호출 체크포인트 → 커밋 시 Git notes

없음

Git notes

BlamePrompt

없음

프롬프트 영수증 — 프롬프트, 비용, 도구; 명시된 근거 없음

라인

에이전트 라이프사이클 훅 + post-commit 훅

없음

Git notes

"거부된 경로"가 중요한 이유 — 복사할 수 없는 유일한 것. 비용이 많이 드는 실패는 컬럼이 왜 존재하는지 잊는 것이 아닙니다. 팀이 좋은 이유로 이미 제거한 것을 에이전트가 자신 있게 다시 구현하는 것입니다. 그 이유를 아는 모든 사람이 컨텍스트 윈도우를 떠난 지 6개월 후에 말이죠. 위의 라인 귀속 도구 중 어느 것도 거부된 경로를 표면화하지 않으며, 이것은 릴리스 하나로 해결할 수 있는 기능 격차가 아닙니다 — 라인 중심 저장소에는 시도 → 되돌림 → 재시도 주기를 거쳐 지속된 엔티티라는 개념이 없기 때문입니다. docs/demos/prior-attempts.md를 참조하세요.

결정론이 중요한 이유. Selvedge의 근거는 에이전트 자신의 의도로, 변경을 만든 동일한 컨텍스트 윈도우에서 작성됩니다. 저장 또는 검색 경로 어디에도 모델이 없으므로, 동일한 쿼리는 오늘과 2년 후, 모델 버전을 넘어서도 동일한 답을 반환합니다. 근거를 사후에 추론하는 도구는 원래 프롬프트를 본 적 없는 두 번째 LLM을 실행하는 것입니다: 그것이 생성하는 것은 의역이며, 다시 실행하면 동일한 변경에 대해 다른 범주를 생성할 수 있습니다. 한 Hacker News 댓글 작성자가 경쟁 접근 방식에 대해 말했듯이, "grep은 'oauth-library'를 거부했기 때문에 당신의 커밋을 찾지 못할 것입니다… 결정론적 강제가 없다면 말이죠" (0x457).

결정론만으로는 더 이상 차별점이 아닙니다 — OpenLore도 결정론적 네이티브이며 그렇게 명시합니다. 차별화하는 복합 요소는 추가 전용 증언(append-only testimony) 입니다: 에이전트가 직접 작성한 근거가, 거부가 정리 대상인 비활성 상태가 아니라 일급 레코드로 취급되는 저장소에 보관되는 것입니다.

"엔티티 수준"이 중요한 이유. 대부분의 도구는 라인을 귀속시킵니다. Selvedge는 실제로 검색하는 대상을 귀속시킵니다: users.email, env/STRIPE_SECRET_KEY, api/v1/checkout, deps/stripe. git blame 이후의 첫 번째 질문은 보통 *"이 컬럼의 이력이 뭐지"*이지, *"users.py의 40–48행의 이력이 뭐지"*가 아닙니다.

"실시간 캡처"가 중요한 이유. 그 자체로는 차별점이 아닙니다 — 여기 있는 모든 도구가 어떤 형태로든 주장합니다 — 하지만 근거를 신뢰할 수 있게 만드는 메커니즘입니다. 변경이 발생한 순간, 그것을 만든 컨텍스트에서 작성하는 것이 설명을 환각할 두 번째 모델이 경로에 없는 이유입니다. 빈 reasoning 필드 자체가 정직한 신호입니다: 에이전트에게 근거가 없었다는 뜻입니다.

비교는 2026-08-05 기준; OpenLore v2.1.8 / 265★, 소스에서 검증됨. 수정 사항은 이슈로 환영합니다.

"체인지셋"이 중요한 이유. Stripe 결제 롤아웃은 users 테이블, 두 개의 새 환경 변수, 세 개의 새 API 라우트, 하나의 의존성, 그리고 코드베이스 전반의 네 개 함수에 영향을 미칩니다. 모든 이벤트에 changeset:add-stripe-billing 태그를 달면 나중에 전체 범위를 다시 가져올 수 있습니다 — 원래 PR이 한 달에 걸쳐 여덟 개의 작은 PR로 쪼개졌더라도 말이죠.

Selvedge ↔ Agent Trace. Agent Trace는 Cursor가 발행한 오픈 AI 코드 귀속 와이어 포맷입니다 (RFC, 2026년 1월). 원래 GitHub 홈은 2026년 8월에 404가 되었고 그 뒤의 멀티 벤더 모멘텀도 사라졌지만, 스펙과 스키마는 여전히 agent-trace.dev에서 v0.1.0으로 고정된 채 해석됩니다. v0.3.9부터 selvedge export --format agent-trace는 Agent Trace v0.1.0 레코드를 생성하고 selvedge import --format agent-trace는 이를 다시 읽습니다 — 파일/라인 AI 귀속을 위한 이식 가능하고 문서화된 교환 포맷으로, 각 레코드의 dev.selvedge 메타데이터에 근거와 엔티티 수준 출처가 담깁니다. 매핑은 docs/agent-trace-interop.md에 있습니다; Selvedge는 스키마를 벤더링하며 업스트림 프로젝트에 대한 런타임 의존성이 없습니다.


빠른 시작

Claude Code — 플러그인 설치 (권장)

Claude Code 내에서 두 개의 명령어만 실행하면 됩니다. 사전 pip install 불필요 — 플러그인이 uvx(또는 pipx)를 통해 서버 자체를 부트스트랩합니다:

/plugin marketplace add masondelan/selvedge
/plugin install selvedge@selvedge

이것이 에이전트가 접하는 전체 표면을 한 단계로 처리합니다:

  • MCP 서버 — 8개 도구 (log_change, prior_attempts, blame, diff, history, changeset, search, stale_decisions);

  • 스킬 — 에이전트에게 언제 호출할지 알려줍니다 — 추적 중인 엔티티를 편집하기 전, 실질적인 변경 후;

  • PreToolUse 강제 훅 — 스키마/마이그레이션 편집은 이 세션에서 prior_attempts가 확인될 때까지 차단되며, 차단 메시지에 이전 근거가 포함됩니다;

  • 슬래시 명령어 — /selvedge:status, /selvedge:blame <entity>, /selvedge:history, /selvedge:prior-attempts <entity>.

저장소(.selvedge/selvedge.db)는 첫 번째 로그 변경 시 자동 생성됩니다. 두 가지 선택적 확장은 CLI 측에 남습니다: 각 이벤트에 커밋 해시를 스탬프하는 post-commit 훅(selvedge install-hook), 그리고 — selvedge 명령어를 셸 PATH에 추가하려면 — pip install selvedge로, 런처는 이후 정확한 고정 버전을 위해 uvx보다 이를 우선합니다.

Claude Code용 플러그인 또는 selvedge setup? 하나만 선택하세요. 둘 다 MCP 서버를 연결하며, 둘 다 실행하면 두 번 등록됩니다. 플러그인이 더 가벼운 경로이고 자체 업데이트됩니다. 플러그인을 사용 중이고 post-commit 커밋 해시 스탬핑만 원한다면 selvedge install-hook을 단독으로 실행하세요.

기타 모든 MCP 클라이언트 — selvedge setup

Cursor, Copilot, Windsurf, Codex CLI, Gemini CLI 등:

pip install selvedge
cd your-project
selvedge setup

그게 전부입니다. selvedge setup은 대화형 마법사입니다: 보유한 AI 도구(Claude Code, Cursor, Copilot)를 감지하고, 각 도구의 설정에 MCP 항목을 작성하고, 표준 에이전트 지침 블록을 프로젝트의 프롬프트 파일(CLAUDE.md / .cursorrules / copilot-instructions.md)에 넣고, PreToolUse 강제 훅을 .claude/settings.json에 설치하고(Claude Code 전용 — prior_attempts가 확인될 때까지 스키마/마이그레이션 편집을 차단; --skip-enforcement-hook으로 거부 가능), selvedge init을 실행하고, post-commit 훅을 설치합니다. 수정되는 모든 파일은 디스크에 변경 사항이 도달하기 전에 옆에 .bak 파일이 생성됩니다. 재실행은 no-op입니다.

CI 부트스트랩 또는 devcontainer.json postCreateCommand용:

selvedge setup --non-interactive --yes

연결 확인 — 같은 프로젝트에서 두 번째 터미널을 엽니다:

selvedge watch

AI 도구에서 아무 변경이나 만드세요 — 컬럼 추가, 함수 이름 변경, 환경 변수 추가. selvedge watch는 에이전트가 log_change를 호출한 후 1초 이내에 새 이벤트를 출력해야 합니다. 아무것도 도착하지 않으면 selvedge doctor를 실행하여 어느 단계가 조용히 깨졌는지 알려주는 단일 명령 건강 검사를 받으세요.

이력 쿼리:

selvedge status                        # recent activity + missing-commit count
selvedge diff users                    # all changes to the users table
selvedge diff users.email              # changes to a specific column
selvedge blame payments.amount         # what changed last and why
selvedge history --since 30d           # last 30 days of changes
selvedge history --since 15m           # last 15 minutes ('m' = minutes)
selvedge changeset add-stripe-billing  # all events for a feature/task
selvedge search "stripe"               # full-text search
selvedge stats                         # log_change coverage report (per-agent)
selvedge import migrations/            # backfill from migration files
selvedge export --format csv           # dump history to CSV

마법사를 실행하고 싶지 않다면, 마법사가 자동화하는 네 가지 수동 단계:

1. 프로젝트에서 초기화

cd your-project
selvedge init

2. MCP 서버 등록

Selvedge는 표준 stdio MCP 서버이므로 모든 MCP 클라이언트에서 작동합니다 — Claude Code, Cursor, Windsurf, Codex CLI, Gemini CLI 등. 클라이언트별 정확한 설정은 모든 MCP 클라이언트와 호환 을 참조하세요. Claude Code의 경우:

claude mcp add selvedge -- selvedge-server

3. 에이전트에게 사용하도록 지시

selvedge prompt --install CLAUDE.md

--install을 클라이언트가 읽는 프롬프트 파일로 지정하세요 — 블록 자체는 모든 클라이언트에서 동일합니다:

클라이언트

프롬프트 파일

Claude Code

CLAUDE.md

Codex CLI (및 기타 AGENTS.md 인식 도구)

AGENTS.md

Cursor

.cursor/rules/selvedge.md (또는 레거시 .cursorrules)

Gemini CLI

GEMINI.md

이렇게 하면 표준 에이전트 지침 블록이 센티널 괄호(<!-- selvedge:start --> / <!-- selvedge:end -->)로 감싸져 설치되므로, 이후 --install 호출 시 파일의 다른 부분은 건드리지 않고 괄호로 묶인 영역만 업데이트합니다. 또는 파이프로 전달할 수도 있습니다:

selvedge prompt | tee -a CLAUDE.md

복사해서 붙여넣는 것을 선호하시나요? 동일한 블록은 웹사이트에서 한 번의 클릭으로 확인할 수 있습니다: selvedge.sh/prompt-block — 복사 버튼과 에이전트가 이 블록을 어떻게 활용하는지에 대한 설명이 함께 제공됩니다.

4. post-commit 훅 설치

selvedge install-hook

위저드가 실행하는 것과 동일한 네 단계입니다.


모든 MCP 클라이언트와 호환

Selvedge는 표준 stdio MCP 서버입니다 — 실행 명령은 selvedge-server이며, pip install selvedge로 PATH에 추가됩니다. MCP를 지원하는 모든 클라이언트가 실행할 수 있습니다. 원하는 클라이언트를 선택하세요:

claude mcp add selvedge -- selvedge-server

또는 프로젝트 수준의 .mcp.json을 커밋하여 팀 전체가 사용할 수 있게 하세요:

{
  "mcpServers": {
    "selvedge": { "command": "selvedge-server" }
  }
}

문서: https://code.claude.com/docs/en/mcp

.cursor/mcp.json(프로젝트) 또는 ~/.cursor/mcp.json(전역):

{
  "mcpServers": {
    "selvedge": { "command": "selvedge-server" }
  }
}

Cursor의 최신 스키마는 명시적 "type": "stdio"도 허용합니다. command만 있는 형식도 작동합니다(Cursor는 command에서 stdio를 유추합니다). 문서: https://cursor.com/docs/mcp

~/.codeium/windsurf/mcp_config.json:

{
  "mcpServers": {
    "selvedge": { "command": "selvedge-server" }
  }
}

Windsurf는 파일을 핫 리로드하므로 재시작이 필요 없습니다. 앱 내 Plugins → View raw config 버튼을 누르면 Cascade가 읽는 정확한 파일이 열립니다. 문서: https://docs.windsurf.com/windsurf/cascade/mcp

~/.codex/config.toml:

[mcp_servers.selvedge]
command = "selvedge-server"

또는 codex mcp add selvedge -- selvedge-server를 실행하세요. 문서: https://developers.openai.com/codex/config-reference

~/.gemini/settings.json(또는 프로젝트별 .gemini/settings.json):

{
  "mcpServers": {
    "selvedge": { "command": "selvedge-server" }
  }
}

또는 gemini mcp add -s user selvedge selvedge-server를 실행하세요. 문서: https://github.com/google-gemini/gemini-cli/blob/main/docs/tools/mcp-server.md

대부분의 클라이언트는 동일한 JSON 형식을 공유합니다 — 다음을 가리키도록 설정하세요:

{
  "mcpServers": {
    "selvedge": { "command": "selvedge-server" }
  }
}

selvedge-server를 찾을 수 없는 경우 절대 경로를 사용하세요(which selvedge-server).


작동 방식

Selvedge는 MCP 서버로 실행됩니다. Claude Code와 같은 도구의 AI 에이전트는 작업하면서 Selvedge의 도구를 호출하여 구조화된 변경 이벤트를 로컬 SQLite 데이터베이스에 기록합니다.

각 이벤트는 다음을 기록합니다:

  • 무엇이 변경되었는지(엔티티 경로, 변경 유형, diff)

  • 언제(타임스탬프)

  • 누가(에이전트, 세션 ID)

  • 왜(추론 — 그 순간 에이전트의 컨텍스트에서 캡처)

  • 어디서(git 커밋, 프로젝트)

diff는 git의 역할입니다. 왜는 Selvedge의 역할입니다.


Selvedge는 자신의 이력을 추적합니다

이 저장소는 Selvedge를 직접 사용합니다(dogfooding): .selvedge/selvedge.db가 커밋되어 있어, 새로 클론하면 Selvedge 자체의 why-이력이 함께 제공됩니다. 클론한 후 Selvedge의 어떤 부분이 왜 변경되었는지 물어보세요:

git clone https://github.com/masondelan/selvedge
cd selvedge
selvedge status                       # recent changes to Selvedge itself
selvedge search "telemetry"           # why the opt-in heartbeat shipped
selvedge blame selvedge/semantic.py   # why semantic search was added

모든 이벤트는 Selvedge를 구축한 에이전트들이 기록한 것입니다 — 이 README가 여러분의 프로젝트에서 사용하도록 요청하는 것과 동일한 log_change 호출입니다.


엔티티 경로 규칙

users.email           DB column (table.column)
users                 DB table
src/auth.py::login    Function in a file (path::symbol)
src/auth.py           File
api/v1/users          API route
deps/stripe           Dependency
env/STRIPE_SECRET_KEY Environment variable

접두사 쿼리는 모든 곳에서 작동합니다: users는 users, users.email, users.created_at 및 users. 네임스페이스 아래의 다른 모든 엔티티를 반환합니다.


MCP 도구

MCP 서버로 연결되면 Selvedge는 다음을 제공합니다:

도구

설명

log_change

엔티티, diff, 추론과 함께 변경 이벤트를 기록합니다. rename_from + change_type="rename"은 이중 이벤트 이름 변경 패턴을 기록하고, change_type="supersede"는 번복된 결정을 다시 엽니다(추가 전용). 선택적 constraint / stale_when은 결정의 원칙과 무효화 조건을 쿼리 가능하게 유지합니다

diff

엔티티 또는 엔티티 접두사의 이력으로, 각 행에 superseded_by가 주석으로 표시됩니다

blame

정확한 엔티티의 가장 최근 변경 + 컨텍스트, 그리고 파생된 결정 status(active / reverted / reopened)

history

모든 엔티티에 걸친 필터링된 이력

changeset

명명된 기능/작업 슬러그 아래에 그룹화된 모든 이벤트

search

모든 이벤트에 대한 전체 텍스트 검색

prior_attempts

엔티티에 대한 이전 변경 시도 + 추론된 결과(tried → reverted → re-opened) — 편집 전에 호출하세요. 선택적 fuzzy 쿼리는 의미적으로 유사한 레코드를 추가합니다(semantic 추가 기능 필요, 없으면 부분 문자열로 대체)

stale_decisions

재검토가 필요한 결정: revisit_after가 지났고 여전히 활성 사용 중(flag="revisit_due")이거나, stale_when 조건이 이후 변경과 일치하는 경우(flag="review_suggested")


CLI 참조

selvedge init [--path PATH]               Initialize in project
selvedge status                           Recent activity summary
selvedge diff ENTITY [--limit N]          Change history for entity
selvedge blame ENTITY                     Most recent change + context
selvedge history [--since SINCE]          Browse all history
              [--entity ENTITY]
              [--project PROJECT]
              [--changeset CS]
              [--summarize]
              [--limit N]
selvedge changeset [CHANGESET_ID]         Show events in a changeset
                  [--list]                or list all changesets
                  [--project NAME]
                  [--since SINCE]
selvedge search QUERY [--limit N]         Full-text search
selvedge prior-attempts ENTITY            Prior attempts + inferred outcome,
                       [--description T]   with the tried → reverted →
                       [--all]             re-opened trail + status line
                       [--window 7d]       (--all widens recall)
                       [--fuzzy TEXT]      add semantic matches (needs the
                                           semantic extra; substring fallback)
selvedge supersede ENTITY                 Re-open a reverted decision —
                  --reasoning TEXT         append-only, links the prior
                  [--constraint TEXT]      reverted event (or --supersedes ID)
                  [--stale-when TEXT]
                  [--supersedes ID]
selvedge index [--model NAME]             Build/update the optional semantic
              [--json]                     embeddings index (selvedge[semantic])
selvedge stale [--entity ENTITY]          Decisions due for a revisit: past
              [--project NAME]            revisit_after + still in use, or
              [--agent NAME]              stale_when matched by a later change
              [--json]                    ("review suggested")
selvedge stats [--since SINCE]            Tool call coverage report (per-tool, per-agent)
selvedge doctor [--json]                  Health check: DB path, schema, hook, MCP wiring
selvedge install-hook [--path PATH]       Install git post-commit hook
                     [--window MIN]       (default 60 minutes)
selvedge backfill-commit --hash HASH      Backfill git_commit on recent events
                        [--window MIN]    (default 60 minutes)
selvedge import PATH                      Import migrations (SQL / Alembic) or
              [--format auto|sql|         an Agent Trace file (agent-trace)
                 alembic|agent-trace]
              [--from-git]                or walk git history for reverts:
              [--since REF|DATE]          revert-message commits + deletions
              [--project NAME]            become change_type="revert" events
              [--dry-run]                 (idempotent on commit + entity)
selvedge export [--format json|csv|       Export history (agent-trace =
                 markdown|agent-trace]      Agent Trace v0.1.0 records;
                                            markdown = reviewable digest)
              [--since SINCE]
              [--entity ENTITY]
              [--ndjson]                  agent-trace: one record per line
              [--collapse-by-session]     agent-trace: merge a session into one
              [--output FILE]
selvedge log ENTITY CHANGE_TYPE           Manually log a change
             [--diff TEXT]                CHANGE_TYPE: add, remove, modify,
             [--reasoning TEXT]           rename, retype, create, delete,
             [--agent NAME]               index_add, index_remove, migrate,
             [--commit HASH]              revert, supersede
             [--project NAME]
             [--changeset CS]
             [--revisit-after WHEN]       ISO date or offset (e.g. 90d)
             [--rename-from OLD]          OLD path when CHANGE_TYPE is 'rename'
             [--constraint TEXT]          the principle behind the decision
             [--stale-when TEXT]          what would invalidate it
             [--supersedes ID]            with CHANGE_TYPE 'supersede'
selvedge migrate-paths                    Re-canonicalize stored entity paths
                      [--apply]           (dry-run by default; --apply writes)
                      [--json]

모든 읽기 명령은 머신이 읽을 수 있는 출력을 위해 --json을 지원합니다.

--since의 상대 시간:

  • 15m → 지난 15분(m = 분)

  • 24h → 지난 24시간

  • 7d → 지난 7일

  • 5mo → 지난 5개월(mo 또는 mon = 개월)

  • 1y → 지난 1년

파싱할 수 없는 입력(예: --since yesterday)은 조용히 빈 결과를 반환하는 대신 명확한 오류와 함께 종료됩니다. ISO 8601 타임스탬프도 허용되며 UTC로 정규화됩니다.


구성

방법

형식

예시

환경 변수

SELVEDGE_DB=/path/to/db

세션별 재정의

프로젝트 초기화

selvedge init

CWD에 .selvedge/selvedge.db 생성

전역 폴백

~/.selvedge/selvedge.db

프로젝트 DB가 없으면 사용

훅 감시 글로브

.selvedge/config.toml

[hook]watch_globs = ["**/migrations/**", "db/**/*.sql"] — 강제 훅의 기본 스키마/마이그레이션 글로브를 대체합니다

프로젝트 설정

.selvedge/config.toml

아래 키 목록 참조 — 보존 기간, 크기 제한, 마스킹 패턴

전역 설정

~/.selvedge/config.toml

동일한 키; 둘 다 설정된 경우 프로젝트 파일이 우선합니다

훅 우회

SELVEDGE_HOOK_DISABLE=1

셸에 대한 PreToolUse 강제 훅을 비활성화합니다

의미론적 추가 기능

pip install "selvedge[semantic]"

selvedge index + prior-attempts --fuzzy 활성화(로컬 model2vec 임베딩, 약 30MB, 코어는 이에 의존하지 않음)

.selvedge/config.toml

모든 키는 선택 사항이며, 파일이 없으면 아래 기본값을 의미합니다. 우선순위는 CLI 플래그 → 환경 변수 → 프로젝트 .selvedge/config.toml → 전역 ~/.selvedge/config.toml → 기본값 순입니다. SELVEDGE_DB는 유일한 예외로, 데이터베이스 해석에서 항상 우선합니다. 구성 파일이 해당 경로를 해석하여 찾아지기 때문입니다. selvedge doctor는 모든 설정에 대해 적용된 값과 그 값을 생성한 단계를 출력합니다.

retention_days_events     = 0       # 0 = never delete events (the default)
retention_days_tool_calls = 90      # local telemetry retention
backup_keep_last          = 7
diff_bytes                = 65536   # truncate oversized diffs at log time
reasoning_bytes           = 32768   # truncate oversized reasoning
db_size_warn_mb           = 500     # doctor warns above this
stale_days                = 0       # 0 = off
digest_max_bytes          = 4096    # cap on the session-start digest
redaction_patterns        = []      # extra secret shapes to warn about

[hook]
watch_globs = ["**/migrations/**", "db/**/*.sql"]

모든 키에는 환경 변수 재정의도 있습니다(SELVEDGE_DIFF_BYTES, SELVEDGE_RETENTION_DAYS_EVENTS, …).


풀 리퀘스트에서 캡처된 의도 검토

.selvedge/selvedge.db는 SQLite 파일이므로 내부의 추론은 diff에 표시되지 않습니다. 옆에 Markdown 다이제스트를 내보내고 둘 다 커밋하세요:

selvedge export --format markdown -o .selvedge/DECISIONS.md
git add .selvedge/

다이제스트는 엔티티별로 그룹화되며 번복된 결정이 먼저 표시됩니다. 또한 결정적(deterministic)입니다 — 새 이벤트 없이 재생성하면 변경 사항이 없는 diff가 생성되므로, 모두가 건너뛰는 노이즈가 되는 대신 검토 가능한 상태로 유지됩니다. 제목 앵커는 엔티티 경로에서 파생되므로 다이제스트가 커져도 링크가 계속 작동합니다. 코드와 같은 커밋에서 재생성하거나 pre-commit 훅에서 재생성하세요.


커버리지 확인

에이전트가 실제로 log_change를 얼마나 자주 호출하는지 궁금하신가요? 두 가지 확인 방법이 있습니다:

# Quick summary in the terminal
selvedge stats

# Cross-reference against git commits
python scripts/coverage_check.py --since 30d

커버리지 스크립트는 git 로그를 Selvedge 이벤트와 비교하여 어떤 커밋에 변경 이벤트가 연결되어 있는지 보여줍니다. 커버리지가 낮으면 일반적으로 시스템 프롬프트를 강화해야 한다는 뜻입니다 — docs/fallbacks.md의 지침을 참조하세요.

CI에서 (GitHub Action)

동일한 검사가 Selvedge Coverage Check 복합 Action으로 제공되므로, 푸시할 때마다 에이전트 커버리지를 추적할 수 있고, 필요에 따라 커버리지가 떨어지면 빌드를 실패시킬 수도 있습니다:

# .github/workflows/selvedge-coverage.yml
name: Selvedge coverage
on: [push, pull_request]
jobs:
  coverage:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0            # full history so commits can be matched
      - uses: masondelan/selvedge@v0.3.10   # pin to a release tag (or @main for latest)
        with:
          since: 30d
          fail-under: "0.5"         # optional: fail below 50% coverage; omit to report only

이 Action은 작업 요약에 커버리지 요약을 작성하고 coverage-ratio, covered, total을 단계 출력으로 노출합니다. 이 Action은 git 이력을 Selvedge 이벤트 로그와 교차 참조하므로, 러너에는 프로젝트의 .selvedge/selvedge.db(커밋하거나 이 단계 전에 복원)와 전체 git 이력(fetch-depth: 0)이 필요합니다. 입력: since, window, limit, fail-under, selvedge-version, python-version, working-directory, db-path.


기여

git clone https://github.com/masondelan/selvedge
cd selvedge
pip install -e ".[dev]"
pytest

아키텍처 세부 사항과 단계별 로드맵은 CLAUDE.md를 참조하세요.


라이선스

MIT — LICENSE를 참조하세요.

Available Tools

8 tools
blameBlame an entityA
Read-onlyIdempotent

Most recent change to an entity — what changed, when, who, why.

Like git blame but for semantic entities (DB columns, functions, env vars, dependencies) and AI agents. Also carries the derived decision state: status (active / reverted / reopened) and superseded_by (id of a later supersede overriding this change, or ""). If no history exists for the entity, returns {"error": "..."} with protocol-level isError: false.

ParametersJSON Schema
NameRequiredDescriptionDefault
entity_pathYesExact entity path (no prefix matching). Examples: 'users.email', 'src/auth.py::login', 'env/STRIPE_SECRET_KEY'.

Output Schema

ParametersJSON Schema
NameRequiredDescription
idYes
diffYes
agentYes
errorYes
statusYes
projectYes
metadataYes
reasoningYes
timestampYes
constraintYes
git_commitYes
session_idYes
stale_whenYes
supersedesYes
change_typeYes
entity_pathYes
entity_typeYes
changeset_idYes
expires_whenYes
revisit_afterYes
superseded_byYes

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already indicate safe read-only idempotent operation. The description adds value by detailing return fields (status, superseded_by) and error handling behavior (returns error object with isError: false). No contradiction.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two short paragraphs, no fluff. The first sentence immediately states the core purpose. Every sentence adds necessary context.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given a single parameter, existing output schema, and comprehensive annotations, the description covers the tool's functionality, return data, and error case fully and clearly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Only one parameter with 100% schema coverage. The description adds the constraint 'exact entity path (no prefix matching)' and provides examples, enhancing the schema's description.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool retrieves the most recent change to an entity, likening it to git blame for semantic entities. It distinguishes from siblings like history or diff by focusing on the latest change and including decision state.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explains what the tool does and notes error behavior when no history exists. It lacks explicit guidance on when not to use or alternatives, but the purpose is clear enough for correct selection.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

changesetGet a changesetA
Read-onlyIdempotent

All events that share a changeset_id, oldest first.

Use to reconstruct the full scope of a feature or task across multiple entities. If the changeset has no events, returns [{"error": "..."}] so the caller can distinguish "unknown changeset" from "empty history."

ParametersJSON Schema
NameRequiredDescriptionDefault
changeset_idYesThe changeset identifier (the same slug or UUID passed to `log_change`'s changeset_id parameter). Examples: 'add-stripe-billing', 'fix-auth-redirect'.

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly, idempotent, non-destructive. Description adds ordering (oldest first) and specific error format, going beyond annotations without contradiction.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, each with clear purpose. No wasted words. First sentence states what the tool does, second gives usage context and error handling.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the simple single-parameter tool with full schema coverage and an output schema, the description sufficiently covers ordering, error condition, and intended use. No gaps identified.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% and fully describes the changeset_id parameter. Description adds no new parameter semantics beyond what the schema provides, so baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Clearly states 'All events that share a changeset_id, oldest first.' It specifies the resource (events) and ordering, distinguishing it from siblings like 'history' (likely broader) and 'search' (different target).

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly says 'Use to reconstruct the full scope of a feature or task across multiple entities,' providing clear context. Also describes error behavior for empty changesets. Lacks explicit when-not or alternative comparisons.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

diffDiff an entity's historyA
Read-onlyIdempotent

Get change history for a codebase entity, newest first.

Supports prefix matching — e.g. 'users' returns all events for the users table and any users.* column. Each event carries a derived superseded_by id ("" when nothing overrode it), so the tried → reverted → re-opened trail reads straight off the history.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoMaximum number of events to return.
entity_pathYesEntity path, or a DOTTED prefix of one: 'users' also covers 'users.email'. Not a raw string prefix — 'src/' matches nothing, and 'src/auth.py' does not cover 'src/auth.py::login'.

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A3.8/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly, idempotent, and non-destructive behavior. The description adds valuable behavioral context: newest-first ordering, dotted-prefix matching scope, and the derived `superseded_by` id with empty-string semantics for the latest event. No contradiction with annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is concise and front-loaded: the first sentence gives the core purpose, and the second provides high-value examples of prefix matching and derived data. Every sentence earns its place with no redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the output schema and safety annotations, the description sufficiently covers the essential behavior: ordering, prefix semantics, and the derived superseded_by trail. It does not discuss sibling-tool selection, but the core functionality is thoroughly described.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema covers both parameters fully (100% coverage), so the baseline is 3. The description restates prefix matching with an example but does not add new parameter-level semantics beyond what the schema already documents.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool returns change history for a codebase entity, newest first, and highlights unique behaviors like prefix matching and the derived `superseded_by` field. However, it does not explicitly differentiate from the similarly-named sibling tool `history`, so it stops short of full sibling distinction.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The intended use is implied: call this when you need a chronological change history for an entity, especially with prefix matching. But the description does not compare this tool to alternatives like `history` or `blame`, nor does it mention exclusions or when not to use it.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

historyBrowse historyA
Read-onlyIdempotent

Filtered change history across all entities, newest first.

Combine since, entity_path, project, and changeset_id to scope the result. On unparseable since input the response is [{"error": "..."}] so the caller sees the problem.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoMaximum number of results.
sinceNoTime window — ISO 8601 datetime OR relative shorthand: '15m' (last 15 minutes), '24h' (last 24 hours), '7d' (last 7 days), '5mo' (last 5 months), '1y' (last year). 'm' means minutes; 'mo' or 'mon' means months. Unparseable values produce an error rather than silently returning empty results. Empty = all time.
projectNoFilter to a specific project/repository.
entity_pathNoFilter to an entity, or a DOTTED prefix of one ('users' also covers 'users.email'). Not a raw string prefix.
changeset_idNoFilter to a specific changeset (feature/task group).

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already indicate readOnly=true, idempotent=true, and destructive=false, so safety is covered. The description goes beyond by disclosing the error behavior for unparseable 'since' input, returning a JSON error array instead of silently returning empty results. This is valuable behavioral context not in the annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two sentences: the first states purpose and ordering, the second gives usage guidance and error handling. It is front-loaded, with no wasted words, and every sentence contributes meaning.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the output schema exists and the description covers purpose, filtering, ordering, and error behavior, the tool is fully specified for an agent. The description is complete for this 5-parameter optional-input tool without needing to explain return values.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% with detailed descriptions for each parameter, so the baseline is 3. The description adds minor value by explicitly stating these parameters can be combined, but it does not explain syntax or semantics beyond what the schema already provides. No compensation needed.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's function: 'Filtered change history across all entities, newest first.' It uses a specific verb ('browse' implicitly via 'history') and resource ('all entities'), and the 'newest first' ordering adds precision. This distinguishes it from siblings like log_change, diff, and search.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives explicit guidance on how to combine filter parameters ('since', 'entity_path', 'project', 'changeset_id') to scope results. It does not explicitly mention when not to use this tool or name alternatives, but the usage context is clear enough for an agent to know when to invoke it.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

log_changeLog a code changeA

Record a change to a codebase entity.

Call this immediately after making any meaningful change. The event is written to the local SQLite store and returned with its assigned id and timestamp. If the reasoning fails the quality validator (empty, too short, or a generic placeholder), or the entity_path doesn't match the usual shape for its entity_type, the result includes a warnings array — the event is still stored.

Renames: pass the new path in entity_path, set change_type="rename", and pass the old path in rename_from. Selvedge then writes two events — a rename on the old path and a create on the new path with metadata.renamed_from set — so the entity's history follows it. Example:

log_change(
    entity_path="src/auth/session.py::login",   # new path
    change_type="rename",
    rename_from="src/auth.py::login",            # old path
    entity_type="function",
    reasoning="Split auth.py into an auth/ package; login moved.",
)

Rejections: when you consider an approach and decide against it WITHOUT writing the change, record the verdict with change_type="reject" — the abandoned path is a first-class event, and the next agent's prior_attempts query finds it as a high-confidence ("exact") row instead of re-deriving the dead end. Name what was rejected AND what was chosen instead, and record the condition that would invalidate the verdict. Example:

log_change(
    entity_path="users.card_pan",
    change_type="reject",
    entity_type="column",
    reasoning="Rejected storing raw card PANs on the user row — "
              "went with provider tokens instead; PANs in our own "
              "DB put us in PCI scope.",
    stale_when="payment provider changed",
    expires_when="entity:deps/stripe:changes",
)

Use change_type="revert" for the sibling case — the change WAS written and then rolled back (clearer than a plain remove).

Superseding a reverted decision: when a reverted change becomes correct again (the constraint that killed it no longer holds), do NOT delete or edit history — log with change_type="supersede" and the reason. The new event links the prior revert (auto-resolved when supersedes is empty) and every read surface then reports the trail tried → reverted → re-opened. Never re-apply a reverted change without superseding it first.

On validation failure (invalid change_type, missing entity_path, rename_from set without change_type='rename', supersedes set without change_type='supersede', a supersede with nothing to re-open, or an expires_when outside the closed grammar) the result is {"status": "error", "error": "..."} with no event written.

ParametersJSON Schema
NameRequiredDescriptionDefault
diffNoThe actual change — SQL migration text, code diff, or a human-readable description of what changed. Optional but strongly recommended for non-trivial changes.
agentNoName/ID of the AI agent making the change (e.g. 'claude-code', 'cursor', 'copilot', 'human').
projectNoRepository or project name. Useful when one DB tracks multiple projects.
reasoningNoWhy the change was made. Include the user's original request, the problem being solved, or any context that won't be obvious from the diff alone. Good example: 'User asked to add 2FA — needs phone number to send SMS verification codes.' Avoid generic placeholders like 'user request' or 'done' — these are flagged by the quality validator and returned in `warnings`.
constraintNoOptional: the testable principle behind the decision, kept queryable (e.g. 'card data in our own DB = PCI scope').
git_commitNoThe git commit hash this change will land in. Can be backfilled later via `selvedge backfill-commit` or the post-commit hook.
session_idNoThe agent session or conversation ID, if available.
stale_whenNoOptional: what would invalidate this decision (e.g. 'payment provider changed'). stale_decisions matches it against later events and flags 'review suggested' — surfacing only.
supersedesNoId of the prior event this change overrides; only valid with change_type='supersede'. Empty auto-links the entity's most recent removal event (remove/delete/index_remove/revert/reject) — so after a standalone rejection it re-opens the rejection. Append-only — the old verdict is never edited, just derived as superseded.
change_typeYesWhat kind of change. One of: add, remove, modify, rename, retype, create, delete, index_add, index_remove, migrate, revert (tried and rolled back), reject (considered and decided against, without writing the change), supersede (re-open a reverted decision). Invalid values are rejected — pick the closest match.
entity_pathYesDot/slash-notation path to the entity. Required and non-empty. Examples: 'users.email' (DB column), 'users' (DB table), 'src/auth.py::login' (function in file), 'src/auth.py' (file), 'api/v1/users' (API route), 'deps/stripe' (dependency), 'env/STRIPE_SECRET_KEY' (env variable).
entity_typeNoCategory of entity. One of: column, table, file, function, class, endpoint, dependency, env_var, index, schema, config, other. Unknown values are coerced to 'other'.other
rename_fromNoThe entity's previous path, when this change is a rename. Set it together with change_type='rename' and put the NEW path in entity_path. Selvedge records the dual-event rename pattern: a 'rename' event on the old path and a 'create' event on the new path whose metadata.renamed_from points back to the old one, so blame/diff/prior_attempts on the new path still see the history. Leave empty for any non-rename change.
changeset_idNoOptional grouping ID for related changes that belong to the same feature or task. Use a short slug like 'add-stripe-billing'. All events sharing a changeset_id can be queried together via the `changeset` tool.
expires_whenNoOptional machine-checkable expiry condition for this decision. Closed grammar, validated at write time: 'library:NAME>=VERSION' (revisit when the named dependency reaches a version, e.g. 'library:django>=5.0'), 'entity:PATH:changes' (revisit when that entity next changes, e.g. 'entity:users.email:changes'), 'date:ISO' (revisit on a date, e.g. 'date:2027-01-01'), or 'manual:LABEL' (opaque label for human review; never auto-fires). `stale_decisions` evaluates these from local state — no network, no LLM — and flags 'expired' with the pattern that fired. Values outside the grammar are rejected.
revisit_afterNoOptional revisit date for an architectural decision (table, schema, dependency, config). An ISO date OR a relative offset from this event's timestamp (e.g. '90d', '6mo'). `stale_decisions` surfaces it once it passes, if the entity is still in active use. Leave empty otherwise.

Output Schema

ParametersJSON Schema
NameRequiredDescription
idYes
errorYes
statusYes
warningsYes
timestampYes
supersedesYes

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations carry near-zero information (all false except openWorldHint), so the description carries the full burden. It comprehensively discloses: the warnings array on quality-validator failure, the exact error shape on validation failure, the dual-event rename behavior, supersede auto-linking, and append-only semantics. No contradiction with annotations (readOnlyHint=false correctly implies a write).

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Long, but every section earns its place given the complexity — headers ('Renames:', 'Rejections:', 'Superseding a reverted decision:') with code examples make it scannable. Slightly verbose in repeating rename semantics already in the schema's rename_from field, but organized enough that the density is justified.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Comprehensive for a 16-parameter write tool with 5 complex change_type workflows. The description covers all change types, the validation grammar, failure/error shapes, examples for each major flow, and the output schema exists. Nothing an agent needs to call it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, giving a baseline of 3, but the description adds genuine orchestration semantics beyond the schema: rename's dual-event pattern (rename on old path + create on new path with metadata.renamed_from), the reject naming requirement ('name what was rejected AND what was chosen instead'), and that empty supersedes auto-links the most recent removal event. This is behavioral glue the schemas don't spell out.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource — 'Record a change to a codebase entity' — and immediately distinguishes itself: call it after a meaningful change, while siblings diff/blame/history/prior_attempts are read surfaces. An agent can clearly separate it from the sibling tools.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides explicit when-to-use for each change_type: 'Call this immediately after making any meaningful change,' with dedicated workflows for rename, reject, revert, and supersede. Names why reject is preferable to re-deriving dead ends ('the next agent's prior_attempts query finds it as a high-confidence row') and why supersede beats editing history. Nothing is left to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

prior_attemptsPrior attempts on an entityA
Read-onlyIdempotent

Prior change attempts on an entity, each with an inferred outcome.

Call this BEFORE editing an entity. If the same change was tried before and reverted, you get the prior reasoning and change_type plus an inferred outcome — so you can change your plan instead of repeating a rejected approach.

Each result is a change event plus the trail fields: outcome ("reverted" — a later removal on the path; "reopened" — closed but a later supersede re-opened it; "rejected" — a standalone reject event that closed no earlier attempt, surfaced as its own row whose reasoning IS the record; "active"), confidence ("exact" — the attempt was closed by an explicit revert/reject, or the row is a standalone rejection; "proximity_high" / "proximity_low" — the add->remove window heuristic for implicit removals), outcome_reasoning (WHY it was rejected), superseded_by + supersede_reasoning (the re-open, when present), and current_status — the entity's standing now. Treat "reverted" and "rejected" as "don't repeat this without a supersede"; "reopened" means the old verdict no longer stands. Together they read: tried → reverted → re-opened. Templated and deterministic — no LLM call; pull-only.

Conservative by design — min_confidence defaults to "proximity_high", so an empty list (nothing clearly tried-and-rejected) is the normal, preferred answer over a speculative false positive; "exact" rows always clear that default floor. Pass min_confidence="proximity_low" to widen recall. Rows carry match_type ("exact" / "substring" / "fuzzy") and similarity.

ParametersJSON Schema
NameRequiredDescriptionDefault
fuzzyNoOptional semantic query: also return attempts on entities whose prior reasoning is similar to this text — catches renames (payment_token vs card_token). Rows are labeled match_type='fuzzy' with a similarity score; without the selvedge[semantic] extra it falls back to substring matching and says so in a leading note row.
limitNoMaximum number of results.
descriptionNoFree-text description of what you're about to do, when you don't have an exact entity_path. Matched as a substring against prior reasoning, diffs, and entity paths. Provide this OR `entity_path` (entity_path takes precedence if both are given).
entity_pathNoThe entity you're about to change. Exact path with prefix matching — 'users' also covers 'users.email'. Examples: 'src/auth.py::login', 'users.email', 'env/STRIPE_SECRET_KEY'. Provide this OR `description`.
min_confidenceNoConfidence floor. 'proximity_high' (default) returns the high-signal rows: attempts closed by an explicit revert/reject (confidence 'exact' — always clears this floor, including standalone rejections) plus attempts reverted within the window. Pass 'proximity_low' to also see the noisy tail (still-active changes and far-apart reverts).proximity_high
window_minutesNoProximity window in minutes for the add->remove revert heuristic — the tiebreaker for IMPLICIT removal types only. An attempt removed within this many minutes is 'proximity_high'; beyond it, 'proximity_low'. Attempts closed by an explicit revert/reject are 'exact' regardless of the window. Default 10080 (7 days).

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.3/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already indicate read-only, idempotent, non-destructive behavior, and the description reinforces and expands this with 'Templated and deterministic — no LLM call; pull-only.' It discloses nuanced behaviors: conservative defaults, the meaning of outcome/confidence values, and that an empty list is the preferred normal answer. No contradiction with annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long but information-dense, with a sensible structure: core purpose, usage timing, outcome semantics, and confidence policy. Every sentence carries meaningful guidance, though some sections could be tightened. The front-loading is effective; the most important instruction appears early.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity, the description covers purpose, usage timing, result semantics, confidence filtering, recall widening, and edge cases like standalone rejections and reopen events. The output schema exists and the description also explains return fields thoroughly. Nothing critical is missing for an agent to select and invoke this tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the schema itself documents all parameters. The description adds meaningful extra context, such as the default min_confidence behavior, how 'exact' rows clear the confidence floor, and the role of window_minutes as a tiebreaker for implicit removals. This goes beyond simple schema repetition, though it could have been slightly more parameter-by-parameter.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly identifies the tool's purpose: retrieving prior change attempts on an entity with inferred outcomes. It states a specific action context ('Call this BEFORE editing an entity') and distinguishes the data it returns. However, it does not explicitly differentiate itself from siblings like 'history' or 'changeset', so an agent must infer which tool covers which kind of history.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly instructs when to use the tool: before editing an entity, to avoid repeating a rejected approach. It also explains how to widen recall via min_confidence. However, it does not say when NOT to use it or name any alternative tool, so the usage guidance is strong on 'when' but missing exclusions and alternatives.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

stale_decisionsStale decisions due for revisitA
Read-onlyIdempotent

Decisions due for a revisit — expired, past their date, or with a triggered stale condition.

Three deterministic rules. Expiry-based (flag="expired"): events whose expires_when condition fired, evaluated from local state only — date: against now, entity:PATH:changes against the event log, library:NAME>=VERSION against installed dist metadata; the pattern kind that fired is in expired_pattern. A library: condition whose dependency isn't locally observable surfaces as flag="manual_review" instead of a guess; manual:LABEL never auto-fires. Date-based (flag="revisit_due"): events whose revisit_after has passed AND the entity is still live (queried via blame/diff/prior_attempts after the decision, or its changeset saw later activity) — pure age alone never surfaces. Condition-based (flag="review_suggested"): events whose stale_when text shares keywords with a LATER change event — the named invalidation evidence may have happened. Surfacing only: nothing is un-retired automatically; follow up with a supersede if the condition really was triggered. A later supersede that re-opens the candidate (explicit supersedes id, or the same id-less auto-link prior_attempts uses) drops it from this list; a same-path sibling the supersede did not target still surfaces.

Each result is the change event plus flag, revisit_due, days_overdue, active_use_signals, matched_terms, matched_event_id, expires_status, expired_pattern, expires_detail, and a one-line stale_reason. Date-due rows first, most-overdue leading; filter by entity_path, project, or agent. Templated and deterministic; no LLM call, no network.

ParametersJSON Schema
NameRequiredDescriptionDefault
agentNoOptional filter to the agent that logged the decision.
limitNoMaximum number of results.
projectNoOptional filter to a specific project/repository.
entity_pathNoOptional filter to a single entity or path prefix — 'users' also covers 'users.email'. Empty = every entity.

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.4/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Even with annotations marking readOnly, deterministic, and non-destructive, the description adds substantial behavioral detail: no LLM call, no network, no automatic un-retiring, fallback to manual_review when dependency state is unobservable, and effects of later supersede events. This is far beyond what annotations alone convey.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is longer than average, but the tool has complex deterministic rules and edge cases that warrant the detail. It is front-loaded with the core purpose and organized by flag type, followed by output fields, ordering, and guarantees. The output field enumeration is slightly redundant with the existing output schema, preventing a 5.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool with this complexity, the description is complete: it explains all three surfacing mechanisms, non-obvious edge cases like manual_review, output shape, ordering, filtering, and determinism guarantees. Combined with the rich annotations and output schema, an agent has everything needed to select and invoke the tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline of 3 applies. The description mentions filtering by entity_path, project, or agent, which reinforces the schema but does not add much new semantic depth. It does not describe parameter formats beyond what the schema already provides.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource: 'Decisions due for a revisit,' then enumerates the three deterministic rules and their resulting flags. It clearly distinguishes this tool from siblings by emphasizing it is surfacing-only, deterministic, and local-state-based.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives clear context for when results surface: expiry-based, date-based, and condition-based rules, with explicit caveats like 'pure age alone never surfaces' and 'manual:LABEL never auto-fires.' It does not explicitly name sibling alternatives for exclusion, but the behavioral specificity makes intended usage unambiguous.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 1 tool updatev0.3.14
    • Changedprior_attempts1 field changed
      • changedInput schema / properties / window_minutes / maximum
        Previous value: -1000New value: +10080
  2. 2 tool updates
    • Changedlog_change3 fields changed
      • changedInput schema / properties / change_type / description
        Previous value: -"What kind of change. One of: add, remove, modify, rename, retype, create, delete, index_add, index_remove, migrate, revert (tried and rolled back), supersede (re-open a reverted decision). Invalid values are rejected — pick the closest match."New value: +"What kind of change. One of: add, remove, modify, rename, retype, create, delete, index_add, index_remove, migrate, revert (tried and rolled back), reject (considered and decided against, without writing the change), supersede (re-open a reverted decision). Invalid values are rejected — pick the closest match."
      • addedInput schema / properties / expires_when
        Added value: +{
        +  "default": "",
        +  "description": "Optional machine-checkable expiry condition for this decision. Closed grammar, validated at write time: 'library:NAME>=VERSION' (revisit when the named dependency reaches a version, e.g. 'library:django>=5.0'), 'entity:PATH:changes' (revisit when that entity next changes, e.g. 'entity:users.email:changes'), 'date:ISO' (revisit on a date, e.g. 'date:2027-01-01'), or 'manual:LABEL' (opaque label for human review; never auto-fires). `stale_decisions` evaluates these from local state — no network, no LLM — and flags 'expired' with the pattern that fired. Values outside the grammar are rejected.",
        +  "title": "Expires When",
        +  "type": "string"
        +}
      • changedInput schema / properties / supersedes / description
        Previous value: -"Id of the prior event this change overrides; only valid with change_type='supersede'. Empty auto-links the entity's most recent remove/delete. Append-only — the old verdict is never edited, just derived as superseded."New value: +"Id of the prior event this change overrides; only valid with change_type='supersede'. Empty auto-links the entity's most recent removal event (remove/delete/index_remove/revert/reject) — so after a standalone rejection it re-opens the rejection. Append-only — the old verdict is never edited, just derived as superseded."
    • Changedprior_attempts2 fields changed
      • changedInput schema / properties / min_confidence / description
        Previous value: -"Confidence floor. 'proximity_high' (default) returns only attempts that were clearly tried and then reverted within the window — the high-signal 'rejected before' cases. Pass 'proximity_low' to also see the noisy tail (still-active changes and far-apart reverts)."New value: +"Confidence floor. 'proximity_high' (default) returns the high-signal rows: attempts closed by an explicit revert/reject (confidence 'exact' — always clears this floor, including standalone rejections) plus attempts reverted within the window. Pass 'proximity_low' to also see the noisy tail (still-active changes and far-apart reverts)."
      • changedInput schema / properties / window_minutes / description
        Previous value: -"Proximity window in minutes for the add->remove revert heuristic. An attempt removed within this many minutes is 'proximity_high'; beyond it, 'proximity_low'. Default 10080 (7 days)."New value: +"Proximity window in minutes for the add->remove revert heuristic — the tiebreaker for IMPLICIT removal types only. An attempt removed within this many minutes is 'proximity_high'; beyond it, 'proximity_low'. Attempts closed by an explicit revert/reject are 'exact' regardless of the window. Default 10080 (7 days)."
  3. 5 tool updatesv0.3.11
    • Changeddiff2 fields changed
      • changedInput schema / properties / entity_path / description
        Previous value: -"Entity path or path prefix. Prefix matching is supported: 'users' returns history for the users table AND all its columns ('users.email', 'users.created_at', etc.). Use a more specific path to narrow the result."New value: +"Entity path, or a DOTTED prefix of one: 'users' also covers 'users.email'. Not a raw string prefix — 'src/' matches nothing, and 'src/auth.py' does not cover 'src/auth.py::login'."
      • addedInput schema / properties / limit / maximum
        Added value: +1000
    • Changedhistory2 fields changed
      • changedInput schema / properties / entity_path / description
        Previous value: -"Filter to a specific entity or path prefix."New value: +"Filter to an entity, or a DOTTED prefix of one ('users' also covers 'users.email'). Not a raw string prefix."
      • addedInput schema / properties / limit / maximum
        Added value: +1000
    • Changedprior_attempts2 fields changed
      • addedInput schema / properties / limit / maximum
        Added value: +1000
      • addedInput schema / properties / window_minutes / maximum
        Added value: +1000
    • Changedsearch1 field changed
      • addedInput schema / properties / limit / maximum
        Added value: +1000
    • Changedstale_decisions1 field changed
      • addedInput schema / properties / limit / maximum
        Added value: +1000
  4. 3 tool updatesv0.3.10
    • Changedblame6 fields changed
      • addedOutput schema / properties / constraint
        Added value: +{
        +  "title": "Constraint",
        +  "type": "string"
        +}
      • addedOutput schema / properties / stale_when
        Added value: +{
        +  "title": "Stale When",
        +  "type": "string"
        +}
      • addedOutput schema / properties / status
        Added value: +{
        +  "title": "Status",
        +  "type": "string"
        +}
      • addedOutput schema / properties / superseded_by
        Added value: +{
        +  "title": "Superseded By",
        +  "type": "string"
        +}
      • addedOutput schema / properties / supersedes
        Added value: +{
        +  "title": "Supersedes",
        +  "type": "string"
        +}
      • changedOutput schema / required
        Previous value: -[
        -  "id",
        -  "timestamp",
        -  "entity_type",
        -  "entity_path",
        -  "change_type",
        -  "diff",
        -  "reasoning",
        -  "agent",
        -  "session_id",
        -  "git_commit",
        -  "project",
        -  "changeset_id",
        -  "metadata",
        -  "revisit_after",
        -  "expires_when",
        -  "error"
        -]New value: +[
        +  "id",
        +  "timestamp",
        +  "entity_type",
        +  "entity_path",
        +  "change_type",
        +  "diff",
        +  "reasoning",
        +  "agent",
        +  "session_id",
        +  "git_commit",
        +  "project",
        +  "changeset_id",
        +  "metadata",
        +  "revisit_after",
        +  "expires_when",
        +  "supersedes",
        +  "constraint",
        +  "stale_when",
        +  "superseded_by",
        +  "status",
        +  "error"
        +]
    • Changedlog_change6 fields changed
      • changedInput schema / properties / change_type / description
        Previous value: -"What kind of change. One of: add, remove, modify, rename, retype, create, delete, index_add, index_remove, migrate. Invalid values are rejected — pick the closest match."New value: +"What kind of change. One of: add, remove, modify, rename, retype, create, delete, index_add, index_remove, migrate, revert (tried and rolled back), supersede (re-open a reverted decision). Invalid values are rejected — pick the closest match."
      • addedInput schema / properties / constraint
        Added value: +{
        +  "default": "",
        +  "description": "Optional: the testable principle behind the decision, kept queryable (e.g. 'card data in our own DB = PCI scope').",
        +  "title": "Constraint",
        +  "type": "string"
        +}
      • addedInput schema / properties / stale_when
        Added value: +{
        +  "default": "",
        +  "description": "Optional: what would invalidate this decision (e.g. 'payment provider changed'). stale_decisions matches it against later events and flags 'review suggested' — surfacing only.",
        +  "title": "Stale When",
        +  "type": "string"
        +}
      • addedInput schema / properties / supersedes
        Added value: +{
        +  "default": "",
        +  "description": "Id of the prior event this change overrides; only valid with change_type='supersede'. Empty auto-links the entity's most recent remove/delete. Append-only — the old verdict is never edited, just derived as superseded.",
        +  "title": "Supersedes",
        +  "type": "string"
        +}
      • addedOutput schema / properties / supersedes
        Added value: +{
        +  "title": "Supersedes",
        +  "type": "string"
        +}
      • changedOutput schema / required
        Previous value: -[
        -  "id",
        -  "timestamp",
        -  "status",
        -  "error",
        -  "warnings"
        -]New value: +[
        +  "id",
        +  "timestamp",
        +  "status",
        +  "error",
        +  "warnings",
        +  "supersedes"
        +]
    • Changedprior_attempts1 field changed
      • addedInput schema / properties / fuzzy
        Added value: +{
        +  "default": "",
        +  "description": "Optional semantic query: also return attempts on entities whose prior reasoning is similar to this text — catches renames (payment_token vs card_token). Rows are labeled match_type='fuzzy' with a similarity score; without the selvedge[semantic] extra it falls back to substring matching and says so in a leading note row.",
        +  "title": "Fuzzy",
        +  "type": "string"
        +}
  5. 4 tool updatesv0.3.8
    • Changedblame3 fields changed
      • addedOutput schema / properties / expires_when
        Added value: +{
        +  "title": "Expires When",
        +  "type": "string"
        +}
      • addedOutput schema / properties / revisit_after
        Added value: +{
        +  "title": "Revisit After",
        +  "type": "string"
        +}
      • changedOutput schema / required
        Previous value: -[
        -  "id",
        -  "timestamp",
        -  "entity_type",
        -  "entity_path",
        -  "change_type",
        -  "diff",
        -  "reasoning",
        -  "agent",
        -  "session_id",
        -  "git_commit",
        -  "project",
        -  "changeset_id",
        -  "metadata",
        -  "error"
        -]New value: +[
        +  "id",
        +  "timestamp",
        +  "entity_type",
        +  "entity_path",
        +  "change_type",
        +  "diff",
        +  "reasoning",
        +  "agent",
        +  "session_id",
        +  "git_commit",
        +  "project",
        +  "changeset_id",
        +  "metadata",
        +  "revisit_after",
        +  "expires_when",
        +  "error"
        +]
    • Changedlog_change2 fields changed
      • addedInput schema / properties / rename_from
        Added value: +{
        +  "default": "",
        +  "description": "The entity's previous path, when this change is a rename. Set it together with change_type='rename' and put the NEW path in entity_path. Selvedge records the dual-event rename pattern: a 'rename' event on the old path and a 'create' event on the new path whose metadata.renamed_from points back to the old one, so blame/diff/prior_attempts on the new path still see the history. Leave empty for any non-rename change.",
        +  "title": "Rename From",
        +  "type": "string"
        +}
      • addedInput schema / properties / revisit_after
        Added value: +{
        +  "default": "",
        +  "description": "Optional revisit date for an architectural decision (table, schema, dependency, config). An ISO date OR a relative offset from this event's timestamp (e.g. '90d', '6mo'). `stale_decisions` surfaces it once it passes, if the entity is still in active use. Leave empty otherwise.",
        +  "title": "Revisit After",
        +  "type": "string"
        +}
    • Addedprior_attempts
    • Addedstale_decisions
  6. 6 tool updatesv0.3.2
    • Changedblame2 fields changed
      • addedInput schema / properties / entity_path / description
        Added value: +"Exact entity path (no prefix matching). Examples: 'users.email', 'src/auth.py::login', 'env/STRIPE_SECRET_KEY'."
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "properties": {
        +    "agent": {
        +      "title": "Agent",
        +      "type": "string"
        +    },
        +    "change_type": {
        +      "title": "Change Type",
        +      "type": "string"
        +    },
        +    "changeset_id": {
        +      "title": "Changeset Id",
        +      "type": "string"
        +    },
        +    "diff": {
        +      "title": "Diff",
        +      "type": "string"
        +    },
        +    "entity_path": {
        +      "title": "Entity Path",
        +      "type": "string"
        +    },
        +    "entity_type": {
        +      "title": "Entity Type",
        +      "type": "string"
        +    },
        +    "error": {
        +      "title": "Error",
        +      "type": "string"
        +    },
        +    "git_commit": {
        +      "title": "Git Commit",
        +      "type": "string"
        +    },
        +    "id": {
        +      "title": "Id",
        +      "type": "string"
        +    },
        +    "metadata": {
        +      "additionalProperties": true,
        +      "title": "Metadata",
        +      "type": "object"
        +    },
        +    "project": {
        +      "title": "Project",
        +      "type": "string"
        +    },
        +    "reasoning": {
        +      "title": "Reasoning",
        +      "type": "string"
        +    },
        +    "session_id": {
        +      "title": "Session Id",
        +      "type": "string"
        +    },
        +    "timestamp": {
        +      "title": "Timestamp",
        +      "type": "string"
        +    }
        +  },
        +  "required": [
        +    "id",
        +    "timestamp",
        +    "entity_type",
        +    "entity_path",
        +    "change_type",
        +    "diff",
        +    "reasoning",
        +    "agent",
        +    "session_id",
        +    "git_commit",
        +    "project",
        +    "changeset_id",
        +    "metadata",
        +    "error"
        +  ],
        +  "title": "BlameResult",
        +  "type": "object"
        +}
    • Changedchangeset1 field changed
      • addedInput schema / properties / changeset_id / description
        Added value: +"The changeset identifier (the same slug or UUID passed to `log_change`'s changeset_id parameter). Examples: 'add-stripe-billing', 'fix-auth-redirect'."
    • Changeddiff3 fields changed
      • addedInput schema / properties / entity_path / description
        Added value: +"Entity path or path prefix. Prefix matching is supported: 'users' returns history for the users table AND all its columns ('users.email', 'users.created_at', etc.). Use a more specific path to narrow the result."
      • addedInput schema / properties / limit / description
        Added value: +"Maximum number of events to return."
      • addedInput schema / properties / limit / minimum
        Added value: +1
    • Changedhistory6 fields changed
      • addedInput schema / properties / changeset_id / description
        Added value: +"Filter to a specific changeset (feature/task group)."
      • addedInput schema / properties / entity_path / description
        Added value: +"Filter to a specific entity or path prefix."
      • addedInput schema / properties / limit / description
        Added value: +"Maximum number of results."
      • addedInput schema / properties / limit / minimum
        Added value: +1
      • addedInput schema / properties / project / description
        Added value: +"Filter to a specific project/repository."
      • addedInput schema / properties / since / description
        Added value: +"Time window — ISO 8601 datetime OR relative shorthand: '15m' (last 15 minutes), '24h' (last 24 hours), '7d' (last 7 days), '5mo' (last 5 months), '1y' (last year). 'm' means minutes; 'mo' or 'mon' means months. Unparseable values produce an error rather than silently returning empty results. Empty = all time."
    • Changedlog_change11 fields changed
      • addedInput schema / properties / agent / description
        Added value: +"Name/ID of the AI agent making the change (e.g. 'claude-code', 'cursor', 'copilot', 'human')."
      • addedInput schema / properties / change_type / description
        Added value: +"What kind of change. One of: add, remove, modify, rename, retype, create, delete, index_add, index_remove, migrate. Invalid values are rejected — pick the closest match."
      • addedInput schema / properties / changeset_id / description
        Added value: +"Optional grouping ID for related changes that belong to the same feature or task. Use a short slug like 'add-stripe-billing'. All events sharing a changeset_id can be queried together via the `changeset` tool."
      • addedInput schema / properties / diff / description
        Added value: +"The actual change — SQL migration text, code diff, or a human-readable description of what changed. Optional but strongly recommended for non-trivial changes."
      • addedInput schema / properties / entity_path / description
        Added value: +"Dot/slash-notation path to the entity. Required and non-empty. Examples: 'users.email' (DB column), 'users' (DB table), 'src/auth.py::login' (function in file), 'src/auth.py' (file), 'api/v1/users' (API route), 'deps/stripe' (dependency), 'env/STRIPE_SECRET_KEY' (env variable)."
      • addedInput schema / properties / entity_type / description
        Added value: +"Category of entity. One of: column, table, file, function, class, endpoint, dependency, env_var, index, schema, config, other. Unknown values are coerced to 'other'."
      • addedInput schema / properties / git_commit / description
        Added value: +"The git commit hash this change will land in. Can be backfilled later via `selvedge backfill-commit` or the post-commit hook."
      • addedInput schema / properties / project / description
        Added value: +"Repository or project name. Useful when one DB tracks multiple projects."
      • addedInput schema / properties / reasoning / description
        Added value: +"Why the change was made. Include the user's original request, the problem being solved, or any context that won't be obvious from the diff alone. Good example: 'User asked to add 2FA — needs phone number to send SMS verification codes.' Avoid generic placeholders like 'user request' or 'done' — these are flagged by the quality validator and returned in `warnings`."
      • addedInput schema / properties / session_id / description
        Added value: +"The agent session or conversation ID, if available."
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "properties": {
        +    "error": {
        +      "title": "Error",
        +      "type": "string"
        +    },
        +    "id": {
        +      "title": "Id",
        +      "type": "string"
        +    },
        +    "status": {
        +      "title": "Status",
        +      "type": "string"
        +    },
        +    "timestamp": {
        +      "title": "Timestamp",
        +      "type": "string"
        +    },
        +    "warnings": {
        +      "items": {
        +        "type": "string"
        +      },
        +      "title": "Warnings",
        +      "type": "array"
        +    }
        +  },
        +  "required": [
        +    "id",
        +    "timestamp",
        +    "status",
        +    "error",
        +    "warnings"
        +  ],
        +  "title": "LogChangeResult",
        +  "type": "object"
        +}
    • Changedsearch3 fields changed
      • addedInput schema / properties / limit / description
        Added value: +"Maximum number of results."
      • addedInput schema / properties / limit / minimum
        Added value: +1
      • addedInput schema / properties / query / description
        Added value: +"Search string (case-insensitive substring). Searches across entity_path, diff, reasoning, and agent fields. SQL LIKE wildcards (`_` and `%`) are escaped, so 'stripe_customer_id' matches the literal underscore rather than any single char."
  7. 6 tool updatesv0.3.1
    • First observedblame
    • First observedchangeset
    • First observeddiff
    • First observedhistory
    • First observedlog_change
    • First observedsearch

TDQS

A4.2/5.0

Scored across 8 tools

Disambiguation4/5

Most tools have clearly distinct scopes: log_change is the only writer; diff is entity-scoped history, history is cross-entity, changeset groups by id, and search is full-text. The main ambiguity is diff vs. blame — blame returns only the newest event and adds a status field, but it is effectively the first row of diff, so an agent could reasonably pick either for 'what changed most recently.'

Naming Consistency3/5

The naming mixes three conventions: git-style single-word verbs (diff, blame, search), bare nouns (history, changeset), and descriptive snake_case phrases (log_change, prior_attempts, stale_decisions). The styles are individually readable and the git-inspired cluster ties the read tools together, but there is no single predictable verb_noun pattern across the set.

Tool Count5/5

Eight tools is well within the ideal 3-15 range and each tool earns its place in the change-logging domain: one writer, four retrieval views (per-entity, latest, global, changeset-grouped), one search, one pre-edit decision helper, and one maintenance/review tool. The count feels tightly scoped with no obvious redundancy or bloat.

Completeness5/5

The surface fully covers the domain's lifecycle: log_change handles all event types (including rename, reject, revert, and supersede), and the read side provides entity-scoped history, latest state, cross-entity filters, changeset reconstruction, full-text search, pre-edit attempt lookup, and stale-decision review. The append-only design intentionally omits update/delete, which the descriptions explicitly justify, so there are no real dead ends for the stated purpose.

Maintenance

ActivityActive
ResponsivenessResponsive

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    A
    maintenance
    Local-first memory layer for AI coding agents — captures issues, attempts, fixes, and decisions, and warns at git commit before you repeat a mistake.
    17
    850
    MIT