{
  "markdown": "<!-- mcp-name: io.github.jaysinailabs/aperture-mcp -->\n\n<div align=\"center\">\n\n# Aperture\n\n### A commitment tripwire &nbsp;·&nbsp; git hook · CI · CLI · MCP\n\n**Did an agent quietly drop a commitment from your spec — and no one noticed?**\n\n[![PyPI](https://img.shields.io/badge/pypi-aperture--mcp-blue)](https://pypi.org/project/aperture-mcp/) [![MCP](https://img.shields.io/badge/MCP-server-purple)](https://modelcontextprotocol.io) [![License](https://img.shields.io/badge/license-Apache--2.0-green)](./LICENSE) [![status](https://img.shields.io/badge/status-early%2Fpre--1.0-orange)](./VERSIONING.md)\n\n**English** · [简体中文](./README.zh-CN.md)\n\n</div>\n\nAI agents now rewrite the documents that govern your work — specs, plans, ADRs, charters,\n`AGENTS.md` files. Somewhere in the edit, a constraint you set earlier can quietly disappear.\n\n**Aperture is a *commitment tripwire*.** You name the commitments you care about; it flags —\n**word for word** — when one of them vanishes between two versions of a decision document.\n\nCatching *\"a commitment silently vanished\"* is a **tripwire** job: it should fire on an event,\ndeterministically, without asking permission. So Aperture ships that check on the surface that fits\nit best first — a **git pre-commit hook / CI check** that runs with **no LLM, offline** — and also as\na **CLI** and an **MCP server** for agents to call mid-task. MCP is one adapter, not the whole\nproduct.\n\n> **A signal, not a judge.** It trips; *you* investigate.\n> Opt-in · runs locally · never trains on your data.\n\n---\n\n## What it is (and what it is not)\n\nAperture compares **two text states of the same decision** — an earlier version and a later one — and\nsurfaces a narrow, specific kind of **decision drift**: when a **tracked commitment’s exact text\ndisappeared**. One engine, several surfaces:\n\n- **git pre-commit hook / CI check** — the deterministic form. Fires on the commit / PR event with no\n  model in the loop, and blocks (or warns) automatically.\n- **`aperture check` CLI** — run the same check by hand between any two git states.\n- **MCP server** — so an agent can call the check while it edits (weaker as a tripwire, since it only\n  runs if the agent *chooses* to call it — but useful mid-task).\n\nWhat the engine does and doesn’t do:\n\n- ✅ **It does:** flag when a commitment you listed *verbatim* is present in version A and gone\n  from version B — across commits, sessions, or authors. It returns a structured, comparable\n  result with **its own blind spots written on the label**.\n- ❌ **It does *not*:** understand meaning. It matches text as a case-insensitive substring, so it\n  **misses** a commitment that was *reworded / softened / paraphrased* (it looks dropped-free even\n  though the promise weakened); it **declines/abstains** on a commitment that was merely *translated*\n  (it can’t compare verbatim across scripts, so it returns `degraded` rather than false-flag); and it\n  can still **false-flag** a commitment that was merely *reformatted* (the words moved, the meaning\n  didn’t). It does not rank options, score quality, or tell you a change was *wrong*. **That judgment\n  stays with you.** Moving to the deterministic git-hook makes the check *fire reliably* — it does\n  **not** widen what it can see. Same narrow, verbatim signal.\n\nIf you want one sentence: **Aperture is `grep` for vanished commitments, wired to fire on commit —\nand honest enough to admit what it can’t see.**\n\n---\n\n## Quickstart (≈2 minutes)\n\n```sh\npip install aperture-mcp   # installs the `aperture` CLI + the MCP server (wire the CLI as a git hook — see below)\n```\n\n> The PyPI package is named **`aperture-mcp`** because the bare name `aperture` was already taken on\n> PyPI. The `-mcp` suffix is a historical package-name artifact — the **product is Aperture**, and\n> MCP is only one of its surfaces. One `pip install` gives you all three below.\n\n### 1. The deterministic tripwire — git pre-commit hook / CI (no LLM, offline)\n\nCreate a `.aperture.toml` — a watchlist of the commitments that must not silently vanish, per file:\n\n```toml\nfail_on_drop = true\n\n[[watch]]\npath = \"CHARTER.md\"\ncommitments = [\"never train on your data\", \"data stays on the device\"]\n```\n\n`aperture check` compares two git states and flags any watched commitment that disappeared\n**verbatim**:\n\n```sh\naperture check                                    # HEAD vs working tree (default)\naperture check --staged                           # HEAD vs the staged index — for a pre-commit hook\naperture check --ref-a origin/main --ref-b HEAD   # any two refs — for CI on a PR\n```\n\nExit code **1** blocks the commit when a watched commitment dropped (the default);\n`--warn-only` prints the finding but never blocks. It’s stdlib-only, makes no network calls, and runs\nno model.\n\nWire it as a **pre-commit hook** — either through the [pre-commit](https://pre-commit.com) framework\n(uses this repo’s `.pre-commit-hooks.yaml`):\n\n```yaml\n# .pre-commit-config.yaml\nrepos:\n  - repo: https://github.com/jaysinailabs/aperture-mcp\n    rev: v0.2.0\n    hooks:\n      - id: aperture-commitment-drift\n```\n\n…or as a standalone `.git/hooks/pre-commit`:\n\n```sh\n#!/bin/sh\nexec aperture check --staged\n```\n\n…or run it in **CI** as a GitHub Action on every PR (needs `fetch-depth: 0` so both\nsides are available — see [`examples/github-action/aperture-check.yml`](./examples/github-action/aperture-check.yml)):\n\n```yaml\n- uses: actions/checkout@v4\n  with: { fetch-depth: 0 }\n- run: pip install aperture-mcp\n- run: aperture check --ref-a ${{ github.event.pull_request.base.sha }} --ref-b ${{ github.sha }}\n```\n\n> Kick the tires first with the bundled fixture (clone the repo): it trips on a dropped commitment\n> against checked-in before/after docs, no setup, fully offline —\n> `python3 examples/git_decision_drift/git_decision_drift.py`.\n\n### 2. Mid-task, from an agent — the MCP server\n\nThe same check, callable by an agent while it edits. (An MCP tool only fires if the agent *chooses*\nto call it — a weaker delivery for a tripwire than the git-hook, but handy mid-task.)\n\n```jsonc\n{\n  \"mcpServers\": {\n    \"aperture-mcp\": { \"command\": \"aperture-mcp\" }\n  }\n}\n```\n\n> Prefer zero-install? Point the client at `uvx` instead:\n> `{ \"command\": \"uvx\", \"args\": [\"aperture-mcp\"] }`.\n\n### 3. Or from your own Python\n\n```python\nfrom aperture import compare, Anchor, AnchorKind\n\nresult = compare(\n    state_a=\"We commit to: ci-gates-green before release; data-never-leaves-device.\",\n    state_b=\"We commit to: data-never-leaves-device.\",\n    anchors=[Anchor(kind=AnchorKind.COMMITMENT, id=\"ci-gates-green\")],\n)\nprint(result.status)            # DROPPED_SILENTLY\nprint(result.anchor_violations) # the commitment that vanished\n```\n\n---\n\n## A scene you’ll recognize\n\nOn a long task, your agent keeps rewriting the doc it works from — a plan, a spec — across sessions\nand edits. And every so often, a line that mattered just… vanishes.\n\n*“Always ask before you delete anything.”* Gone.\n*“User data never leaves the device.”* Gone.\n*“The free tier stays free.”* Gone.\n\nNobody meant to drop them; nobody reads all 400 lines of the diff.\n\nAperture watches the exact lines you name. Put it on the commit — a hook that fires before the drop\nlands — and it won’t try to understand the doc or judge it; it just tells you which promise was there,\nword for word, and now isn’t. A tripwire, not a judge — and honest about the rest: soften a line,\nreword it, or change a number instead of deleting it, and it’ll slip past. Better you hear that now.\n\n---\n\n## What trips it — and what slips past\n\nAperture is a **heuristic**. We measured it on our own gold corpus and we publish the numbers instead\nof a single flattering score, because *knowing where it’s blind is the product* —\n**recall 0.400, precision 0.667** on a 100-case corpus, labeled by an isolated LLM-judge panel (it\ncatches 26 of 65 real drifts; ~1 flag in 3 is noise), full breakdown in\n[docs/measured-limits.md](docs/measured-limits.md):\n\n| Kind of change | Does Aperture flag it? |\n| --- | --- |\n| A watched commitment **deleted verbatim** | ✅ Reliably — this is the one thing it’s good at (24 of 24 in the corpus) |\n| A commitment **reworded / softened** (“must” → “should”) | ❌ **Missed** — the text still “matches” |\n| A commitment **paraphrased / restructured** | ❌ **Missed** |\n| A number / scope / negation quietly changed | ❌ **Missed** |\n| A commitment **translated** to another language | ⚠️ **Declines (abstains)** for a *natural-language* anchor — it can’t compare verbatim across scripts, so it returns `degraded` rather than false-flag (a commitment dropped **and** translated is missed) |\n\n> **The deterministic surface doesn’t widen the aperture.** The git-hook fires reliably — but it still\n> only catches **verbatim deletion**. Every ❌ / ⚠️ row above is exactly as blind through the hook as\n> through MCP. What you gain is *when* it checks (on the commit, without anyone remembering to ask),\n> not *what* it can see.\n\n> **Anchor style matters for that last row:** the abstain applies to a **natural-language** anchor. A\n> **code-identifier** anchor (the `ci-gates-green` style the quickstart teaches) is treated as\n> *translation-stable* — Aperture keeps checking it across languages, so if that exact token disappears\n> it still flags `DROPPED_SILENTLY` (usually what you want for a stable identifier).\n\n**Takeaway:** treat every flag as *“look here,”* never as *“this is wrong”* — and never assume\nsilence means nothing drifted. Aperture catches the **verbatim disappearance** case well and is\nhonest that it catches little else. That narrow, reliable signal is useful precisely *because*\nit doesn’t pretend to be more.\n\n> **Hit one of those misses on your own docs?** That's the single most useful thing you can send us —\n> [report it in ~30s](#hit-a-miss-help-it-improve) (your wording is optional). Real misses guide what\n> we fix next.\n\n> Why not just `git diff` / `grep`? You can reproduce the core check by hand. What Aperture adds is\n> that it’s **wired to fire on the commit / PR event** (as a hook or CI check) *and* callable\n> **mid-task by an agent** (over MCP); it returns a **structured, directional result**\n> (`ok` / `degraded` / `DROPPED_SILENTLY` / …); and it **reports its own blind spots** in the result\n> so a human can audit the gaps. It’s ergonomics + honesty around a simple, legible check — not a\n> smarter detector.\n\n---\n\n## Why this exists\n\nLong-running and multi-agent workflows drift. A constraint set in turn 3 / session 1 / by agent A\ngets quietly edited away forty turns later, in another session, by agent B — and nobody notices\nuntil it ships. Aperture is a **preflight you can put on the documents agents maintain**: name the\ncommitments that must not silently vanish, and get a tripwire when one does — ideally on the commit\nitself, before the drop ever lands.\n\nIt is deliberately **small and legible**. It is not an AI that decides for you; it is a signal that\nhelps *you* stay consistent with yourself.\n\n---\n\n## Who it’s for\n\nTeams and builders who **(a)** let AI agents edit repo-resident decision documents — specs, plans,\nADRs, charters, and `AGENTS.md` files — and **(b)** keep those documents under version control.\nIf your agents touch text that encodes promises, Aperture gives you a cheap, honest tripwire — on the\ncommit, in CI, or mid-task — on the ones you can’t afford to lose silently.\n\n---\n\n## Privacy\n\n- **Opt-in and local.** Aperture runs on your machine — the git-hook, the CLI, and the MCP server\n  alike. It makes no network calls.\n- **Never trains on your data.** Your decision text is yours; it never leaves your process.\n- **Usage logging is off by default** and, when enabled, records only **metadata** (timestamp,\n  tool, status, counts) — never your decision text or commitment wording.\n\n---\n\n## Honesty about the demo\n\nThe repository ships a small **hand-authored fixture ADR** (a before/after pair under\n[`examples/git_decision_drift/fixtures/`](./examples/git_decision_drift/fixtures)), where Aperture\ncorrectly flags a commitment we deliberately retired and stays quiet on one we kept. It is a faithful\nillustration of the mechanism — but it is a **sample of one that we author and judge ourselves**. It\ndemonstrates *how the tripwire works*, **not** *that the signal is strong*. For the latter, see the\nmeasured per-family numbers above and in\n[`docs/measured-limits.md`](./docs/measured-limits.md).\nWe have **zero external adopters yet** — if you run Aperture on your own decision docs, we’d love to\nhear what it caught and what it missed.\n\n---\n\n## Project status\n\nEarly, **pre-1.0**, not yet a production gate. The compare contract (`v0.2`) is frozen and covered\nby a conformance suite; the package API may still move. See [VERSIONING.md](./VERSIONING.md) for the\ncompatibility policy and [CHANGELOG.md](./CHANGELOG.md) for changes.\n\n## Hit a miss? Help it improve\n\nAperture **will** miss things — that's by design (it's blind to reworded, softened, and translated\ncommitments, on every surface). When it misses a drift you cared about, or false-flags a rewrite,\ntelling us is the single most valuable contribution:\n\n- **~30 seconds, no account/usage data, your wording is optional** →\n  [open a drift-case report](../../issues/new?template=drift-case-report.md).\n- Real misses tell us **which blind spot to fix next**, and — only if you choose to share the wording —\n  can become cases in the gold corpus that keeps the numbers in\n  [`docs/measured-limits.md`](./docs/measured-limits.md) honest.\n\nWe never auto-collect anything (see [Privacy](#privacy)); this happens only when *you* choose to share.\nQuestions, or \"is this the right tool for my case?\" → **[GitHub Discussions](../../discussions)**.\n\nMore ways to help: [CONTRIBUTING.md](./CONTRIBUTING.md).\n\n## License\n\n[Apache-2.0](./LICENSE).\n",
  "bytes": 13794,
  "sha": "a1adb3eaa2801d1d731ec567a08b44accede04c5838c4bc9699477561692fa79",
  "repo_slug": "jaysinailabs/aperture-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_jaysinailabs_aperture_mcp_6bbe7b16/readme"
}