{
  "markdown": "<!-- mcp-name: io.github.Byggarepop/unwritten -->\n\n# Unwritten\n\n[![NuGet](https://img.shields.io/nuget/v/Unwritten.svg)](https://www.nuget.org/packages/Unwritten) [![Downloads](https://img.shields.io/nuget/dt/Unwritten.svg)](https://www.nuget.org/packages/Unwritten) [![License: MIT](https://img.shields.io/github/license/Byggarepop/Unwritten.svg)](https://github.com/Byggarepop/Unwritten/blob/main/LICENSE)\n\n**Catch the files you — or your AI agent — forgot to change.** Unwritten learns\nfrom your git history which files usually change together and warns when one is\nmissing from your edit: \"you changed `OrderService.cs` but not\n`OrderServiceTests.cs`, and they change together 94% of the time.\" It calls\nthese missing companions *holes*, and every warning comes with its confidence\nscore and real example commits as proof.\n\n```text\n1 possible hole(s):\n\n  src/Orders/OrderServiceTests.cs\n    expected because you changed src/Orders/OrderService.cs\n    confidence 0.826 (90 co-changes in 100 changes)\n    e.g. 3f2a1c9 Add surcharge handling to freight calculation\n\nFAIL: at least one hole at confidence >= 0.70.\n```\n\nIt runs as an **MCP server** so AI coding agents (Claude Code, Copilot) can check\ntheir own edits mid-session, and as a **CLI** for pre-commit hooks.\nOne `dotnet tool execute`, an index in `.unwritten/`, no server, no subscription,\nno tokens.\n\n**Works on any language.** File-level rules only need git history, so hole\ndetection works the same on Python, TypeScript, Go, or mixed repos. C# repos\nadditionally get method-level rules and cosmetic-edit filtering; JSON files get\nkey-level noise filtering. Running the tool requires the\n[.NET SDK](https://dotnet.microsoft.com/download) (10+), but the repos it\nanalyzes can be anything.\n\n## Quick start\n\nFrom your repo's root:\n\n1. Warm up the index (optional — every command builds it on first use and keeps it current by itself; this just makes the first query fast).\n\n```bash\ndotnet tool execute Unwritten --yes -- reindex\n```\n\n2. Register as an MCP server (Claude Code):\n\n```bash\nclaude mcp add unwritten -- dotnet tool execute Unwritten --yes -- mcp\n```\n\n3. (Recommended if using Claude Code) Make the check deterministic — a git pre-commit hook and a Claude Code Stop hook that feeds failing holes back to the agent before a commit is made:\n\n```bash\ndotnet tool execute Unwritten --yes -- install-hook --git --claude-code\n```\n\nThat's it — your agent can now call `check_holes` after editing, and the hooks\ncatch the cases where it forgets to.\n\n## Explanatory video\n\nhttps://github.com/user-attachments/assets/4ae7a22d-c611-458f-8c48-08f87eca1d29\n\n<!-- The bare URL above renders as an inline video player on GitHub and\n     degrades to a plain clickable link on nuget.org (which cannot embed\n     video). To set or refresh it: edit README.md on github.com, drag\n     videos/Unwritten__Long_16x9_subbed.mp4 (burned-in captions — GitHub's\n     player has no CC support) into the editor at this spot, delete the\n     placeholder URL, and commit. -->\n\n## See it in action\n\n`stats` shows what the tool has learned — here, one file pair coupled strongly\nenough (confidence ≥ 0.7) to block a commit if one side is missing:\n\n![Index stats showing one high-confidence file pair](https://raw.githubusercontent.com/Byggarepop/Unwritten/main/img/demo/check-index.png)\n\nAfter editing one file of that pair, `check` warns that its companion is\nmissing and spells out the three ways to resolve it — update the companion,\ncommit anyway, or mute the rule:\n\n![check reporting a missing companion file with resolution options](https://raw.githubusercontent.com/Byggarepop/Unwritten/main/img/demo/check-stats.png)\n\nWith the pre-commit hook installed, the same check runs automatically on every\ncommit and blocks it while the companion is still missing:\n\n![Pre-commit hook blocking a commit on a missing companion file](https://raw.githubusercontent.com/Byggarepop/Unwritten/main/img/demo/use-pre-commit-hook.png)\n\n## Documentation\n\nEverything else lives in **[docs/README.md](https://github.com/Byggarepop/Unwritten/blob/main/docs/README.md)**:\n\n- [Background & the research behind it](https://github.com/Byggarepop/Unwritten/blob/main/docs/README.md#this-is-not-a-new-idea--and-thats-the-point)\n- [Why these thresholds (tested on real data)](https://github.com/Byggarepop/Unwritten/blob/main/docs/README.md#why-these-thresholds-tested-on-real-data)\n- [Use: MCP server, CLI, hooks, muting false rules](https://github.com/Byggarepop/Unwritten/blob/main/docs/README.md#use)\n- [Configuration reference](https://github.com/Byggarepop/Unwritten/blob/main/docs/README.md#configuration--unwrittenconfigjson)\n- [How it works](https://github.com/Byggarepop/Unwritten/blob/main/docs/README.md#how-it-works)\n- [What it does NOT do (yet)](https://github.com/Byggarepop/Unwritten/blob/main/docs/README.md#what-it-does-not-do-yet)\n\n## License\n\n[MIT](https://github.com/Byggarepop/Unwritten/blob/main/LICENSE)\n",
  "bytes": 4945,
  "sha": "d5c86179a7687ee984862f936092b032208d5f64e2c39e10d56bf6a1bd5aa51c",
  "repo_slug": "byggarepop/unwritten",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_byggarepop_unwritten_fb0a8949/readme"
}